├── .swift-version
├── .swiftformat
├── Cargo.toml
├── Mintfile
├── readme-assets
└── screenshot.png
├── swift
├── MinecraftStatus
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ ├── 100.png
│ │ │ ├── 114.png
│ │ │ ├── 120.png
│ │ │ ├── 144.png
│ │ │ ├── 152.png
│ │ │ ├── 167.png
│ │ │ ├── 180.png
│ │ │ ├── 20.png
│ │ │ ├── 29.png
│ │ │ ├── 40.png
│ │ │ ├── 50.png
│ │ │ ├── 57.png
│ │ │ ├── 58.png
│ │ │ ├── 60.png
│ │ │ ├── 72.png
│ │ │ ├── 76.png
│ │ │ ├── 80.png
│ │ │ ├── 87.png
│ │ │ ├── 1024.png
│ │ │ └── Contents.json
│ │ ├── twitterLogoWhite.imageset
│ │ │ ├── twitter1x.png
│ │ │ ├── twitter2x.png
│ │ │ ├── twitter3x.png
│ │ │ └── Contents.json
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ └── twitterBlue.colorset
│ │ │ └── Contents.json
│ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ ├── MinecraftStatusApp.swift
│ ├── Info.plist
│ ├── SettingsView.swift
│ └── McStructs.swift
├── MinecraftStatusWidgetViewExtension
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── minecraft-dirt.imageset
│ │ │ ├── minecraft_dirt.jpg
│ │ │ └── Contents.json
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── WidgetBackground.colorset
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Fonts
│ │ ├── LICENSE
│ │ └── minecraft.ttf
│ ├── MinecraftStatusWidgetViewExtension.entitlements
│ ├── Info.plist
│ ├── MinecraftStatusWidgetViewExtension.swift
│ └── MinecraftStatusWidgetViewExtension.intentdefinition
├── MinecraftStatusLib
│ └── Minecraft-Status-Bridging-Header.h
├── scripts
│ ├── swiftlint.sh
│ ├── swiftformat.sh
│ ├── bootstrap.sh
│ └── generate-rust-lib.sh
├── MinecraftStatus.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ ├── Minecraft Status Release.xcscheme
│ │ │ ├── Minecraft Status.xcscheme
│ │ │ └── McpingWidgetViewExtension.xcscheme
│ └── project.pbxproj
├── MinecraftStatus.entitlements
├── MinecraftStatusTests
│ ├── Info.plist
│ └── MinecraftStatusTests.swift
└── MinecraftStatusUITests
│ ├── Info.plist
│ └── MinecraftStatusUITests.swift
├── rust-toolchain
├── .gitignore
├── .swiftlint.yml
├── PRIVACY_POLICY.md
├── rust
├── src
│ ├── bin
│ │ └── gen_identicon.rs
│ ├── identicon.rs
│ ├── tests
│ │ └── mod.rs
│ ├── mcping_common.rs
│ ├── week_stats.rs
│ └── lib.rs
└── Cargo.toml
├── SETUP.md
├── .github
└── workflows
│ ├── rust-audit.yml
│ ├── rust-ci.yml
│ └── swift-ci.yml
├── RELEASE.md
├── LICENSE
├── README.md
├── CHANGELOG.md
└── Cargo.lock
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.3
2 |
--------------------------------------------------------------------------------
/.swiftformat:
--------------------------------------------------------------------------------
1 | --self insert
2 | --ranges no-space
3 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "rust"
4 | ]
5 |
--------------------------------------------------------------------------------
/Mintfile:
--------------------------------------------------------------------------------
1 | realm/SwiftLint@0.45.0
2 | nicklockwood/SwiftFormat@0.48.17
3 |
--------------------------------------------------------------------------------
/readme-assets/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/readme-assets/screenshot.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/rust-toolchain:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | channel = "1.56.0"
3 | components = ["rustfmt", "clippy"]
4 | targets = ["aarch64-apple-ios", "x86_64-apple-ios"]
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
3 | xcuserdata/
4 | .DS_Store
5 | swift/MinecraftStatusLib/minecraft_status.h
6 | swift/MinecraftStatusLib/minecraft_status.a
7 |
--------------------------------------------------------------------------------
/swift/MinecraftStatusWidgetViewExtension/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/swift/MinecraftStatusWidgetViewExtension/Fonts/LICENSE:
--------------------------------------------------------------------------------
1 | minecraft.ttf was obtained from https://fonts2u.com/minecraft-regular.font and is "free for commerical use"
2 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | - trailing_comma
3 | - todo
4 | - line_length
5 | - multiple_closures_with_trailing_closure
6 | identifier_name:
7 | min_length: 1
8 |
--------------------------------------------------------------------------------
/swift/MinecraftStatusWidgetViewExtension/Fonts/minecraft.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatusWidgetViewExtension/Fonts/minecraft.ttf
--------------------------------------------------------------------------------
/PRIVACY_POLICY.md:
--------------------------------------------------------------------------------
1 | # Minecraft Status Privacy Policy
2 |
3 | Minecraft Status operates entirely on-device and sends no information back to the developer or to any third-party services.
4 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/100.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/114.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/120.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/144.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/152.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/167.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/180.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/20.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/29.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/40.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/50.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/57.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/58.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/60.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/72.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/76.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/80.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/87.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/1024.png
--------------------------------------------------------------------------------
/swift/MinecraftStatusLib/Minecraft-Status-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #include "minecraft_status.h"
6 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/twitterLogoWhite.imageset/twitter1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/twitterLogoWhite.imageset/twitter1x.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/twitterLogoWhite.imageset/twitter2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/twitterLogoWhite.imageset/twitter2x.png
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/twitterLogoWhite.imageset/twitter3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatus/Assets.xcassets/twitterLogoWhite.imageset/twitter3x.png
--------------------------------------------------------------------------------
/swift/scripts/swiftlint.sh:
--------------------------------------------------------------------------------
1 | if which mint >/dev/null; then
2 | cd $SRCROOT/../
3 | xcrun --sdk macosx mint run swiftlint
4 | else
5 | echo "warning: Mint not installed, see setup instructions in README"
6 | fi
7 |
--------------------------------------------------------------------------------
/swift/scripts/swiftformat.sh:
--------------------------------------------------------------------------------
1 | if which mint >/dev/null; then
2 | cd $SRCROOT/../
3 | xcrun --sdk macosx mint run swiftformat .
4 | else
5 | echo "warning: Mint not installed, see setup instructions in README"
6 | fi
7 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/swift/MinecraftStatusWidgetViewExtension/Assets.xcassets/minecraft-dirt.imageset/minecraft_dirt.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cldfire/minecraft-status/HEAD/swift/MinecraftStatusWidgetViewExtension/Assets.xcassets/minecraft-dirt.imageset/minecraft_dirt.jpg
--------------------------------------------------------------------------------
/swift/MinecraftStatusWidgetViewExtension/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/swift/MinecraftStatusWidgetViewExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/swift/scripts/bootstrap.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "checking for homebrew updates";
4 | brew update
5 |
6 | function install_current {
7 | echo "trying to update $1"
8 | brew upgrade $1 || brew install $1 || true
9 | brew link $1
10 | }
11 |
12 | if [ -e "Mintfile" ]; then
13 | install_current mint
14 | mint bootstrap
15 | fi
16 |
--------------------------------------------------------------------------------
/rust/src/bin/gen_identicon.rs:
--------------------------------------------------------------------------------
1 | use minecraft_status::{
2 | identicon::{self, IdenticonInput},
3 | mcping_common::ProtocolType,
4 | };
5 |
6 | fn main() {
7 | let input = IdenticonInput {
8 | protocol_type: ProtocolType::Bedrock,
9 | address: "try.ok.game.org",
10 | };
11 | println!("{}", identicon::make_base64_identicon(input).unwrap());
12 | }
13 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.application-groups
6 |
7 | group.dev.cldfire.minecraft-status
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/swift/MinecraftStatusWidgetViewExtension/MinecraftStatusWidgetViewExtension.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.application-groups
6 |
7 | group.dev.cldfire.minecraft-status
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/SETUP.md:
--------------------------------------------------------------------------------
1 | # Setup
2 |
3 | 1. Install [Rust](https://www.rust-lang.org/)
4 | 2. Install [`cbindgen`](https://github.com/eqrion/cbindgen):
5 |
6 | ```
7 | cargo install cbindgen
8 | ```
9 |
10 | 3. Install [`homebrew`](https://brew.sh/)
11 |
12 | 4. Run `swift/scripts/bootstrap.sh`
13 |
14 | ```
15 | sh swift/scripts/bootstrap.sh
16 | ```
17 |
18 | After performing the above steps, open the XCode project in XCode and build the `Minecraft Status` scheme.
19 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/twitterBlue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "242",
9 | "green" : "161",
10 | "red" : "29"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/swift/MinecraftStatusWidgetViewExtension/Assets.xcassets/minecraft-dirt.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "minecraft_dirt.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/twitterLogoWhite.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "twitter1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "twitter2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "twitter3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.github/workflows/rust-audit.yml:
--------------------------------------------------------------------------------
1 | on:
2 | schedule:
3 | - cron: '0 0 * * 0'
4 | push:
5 | paths:
6 | - '**/Cargo.toml'
7 | - '**/Cargo.lock'
8 | branches:
9 | - main
10 | pull_request:
11 | paths:
12 | - '**/Cargo.toml'
13 | - '**/Cargo.lock'
14 |
15 | name: Rust Security audit
16 |
17 | jobs:
18 | audit:
19 | runs-on: ubuntu-latest
20 | steps:
21 | - uses: actions/checkout@v2
22 | - uses: actions-rs/audit-check@v1
23 | with:
24 | token: ${{ secrets.GITHUB_TOKEN }}
25 |
--------------------------------------------------------------------------------
/rust/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "minecraft-status"
3 | version = "0.1.0"
4 | authors = ["Cldfire"]
5 | edition = "2021"
6 |
7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8 |
9 | [lib]
10 | crate-type = ["lib", "staticlib"]
11 |
12 | [dependencies]
13 | mcping = { git = "https://github.com/Scetch/mcping.git" }
14 | anyhow = "1.0"
15 | serde = "1.0"
16 | serde_json = "1.0"
17 | expect-test = "1.1"
18 | identicon-rs = "2.0.2"
19 | base64 = "0.13"
20 | image = "0.23.14"
21 | chrono = { version = "0.4", features = ["serde"] }
22 |
23 | [dev-dependencies]
24 | tempfile = "3.2"
25 |
26 | [features]
27 | # enables tests that require an internet connection
28 | online = []
29 |
--------------------------------------------------------------------------------
/swift/MinecraftStatusTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/swift/MinecraftStatusUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/RELEASE.md:
--------------------------------------------------------------------------------
1 | # Release
2 |
3 | This file documents the release process for Minecraft Status.
4 |
5 | ## TestFlight
6 |
7 | 1. Bump build / version numbers for all targets
8 | 1. Update changelog
9 | 1. Do a `cargo clean`
10 | 1. Switch to the "Minecraft Status" scheme in XCode, building for "Any iOS Device"
11 | 1. Product > Archive from the menubar
12 | 1. Window > Organizer from the menubar
13 | 1. Select the new archive, click "Validate App" and run through that
14 | 1. Select the new archive, click "Distribute App" and upload the archive to the app store
15 | * Make sure to not include bitcode
16 | 1. Wait for the build to appear in App Store Connect
17 | 1. Wait for the build to be processed
18 | 1. Handle the "Manage Compliance" stuff
19 | 1. Add it to the TestFlight group so testers can install it
20 | 1. Commit and tag release (tag format: v0.0.1-3 where 3 is the build number)
21 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus/MinecraftStatusApp.swift:
--------------------------------------------------------------------------------
1 | //
2 |
3 | import SwiftUI
4 |
5 | @main
6 | struct MinecraftStatusApp: App {
7 | var body: some Scene {
8 | WindowGroup {
9 | SettingsView()
10 | }
11 | }
12 | }
13 |
14 | extension MinecraftStatusApp {
15 | /// App's current version.
16 | static var version: String? {
17 | Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
18 | }
19 |
20 | /// App's current build number.
21 | static var build: String? {
22 | Bundle.main.infoDictionary?["CFBundleVersion"] as? String
23 | }
24 |
25 | /// App's current version and build number.
26 | static var fullVersion: String? {
27 | guard let version = version else { return nil }
28 | guard let build = build else { return version }
29 | return "\(version) (\(build))"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/swift/MinecraftStatusTests/MinecraftStatusTests.swift:
--------------------------------------------------------------------------------
1 | //
2 |
3 | @testable import MinecraftStatus
4 | import XCTest
5 |
6 | class MinecraftStatusTests: XCTestCase {
7 | override func setUpWithError() throws {
8 | // Put setup code here. This method is called before the invocation of each test method in the class.
9 | }
10 |
11 | override func tearDownWithError() throws {
12 | // Put teardown code here. This method is called after the invocation of each test method in the class.
13 | }
14 |
15 | func testExample() throws {
16 | // This is an example of a functional test case.
17 | // Use XCTAssert and related functions to verify your tests produce the correct results.
18 | }
19 |
20 | func testPerformanceExample() throws {
21 | // This is an example of a performance test case.
22 | measure {
23 | // Put the code you want to measure the time of here.
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Jarek Samic
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/swift/MinecraftStatusWidgetViewExtension/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | MinecraftStatusWidgetViewExtension
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSExtension
24 |
25 | NSExtensionPointIdentifier
26 | com.apple.widgetkit-extension
27 |
28 | UIAppFonts
29 |
30 | minecraft.ttf
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/rust/src/identicon.rs:
--------------------------------------------------------------------------------
1 | use identicon_rs::Identicon;
2 | use image::EncodableLayout;
3 |
4 | use crate::mcping_common::ProtocolType;
5 |
6 | pub struct IdenticonInput<'a> {
7 | pub protocol_type: ProtocolType,
8 | pub address: &'a str,
9 | }
10 |
11 | impl<'a> IdenticonInput<'a> {
12 | fn make_string(&self) -> String {
13 | format!("{:?}{}", self.protocol_type, self.address)
14 | }
15 | }
16 |
17 | pub fn make_base64_identicon(input: IdenticonInput) -> Option {
18 | let identicon = Identicon::new(input.make_string())
19 | .size(9)
20 | .unwrap()
21 | .scale(54)
22 | .unwrap()
23 | .border(6)
24 | .background_color((0, 0, 0));
25 | let dynamic_image = identicon.generate_image();
26 | let mut rgba_image = dynamic_image.to_rgba8();
27 |
28 | // Replace the background color with transparency
29 | //
30 | // We handle the background in swiftui land so we can react to system theme
31 | // changes
32 | rgba_image
33 | .pixels_mut()
34 | .filter(|p| *p == &image::Rgba([0, 0, 0, 255]))
35 | .for_each(|p| *p = image::Rgba([0, 0, 0, 0]));
36 |
37 | let mut buffer = Vec::new();
38 |
39 | image::png::PngEncoder::new(&mut buffer)
40 | .encode(
41 | rgba_image.as_bytes(),
42 | rgba_image.width(),
43 | rgba_image.height(),
44 | image::ColorType::Rgba8,
45 | )
46 | .ok()?;
47 |
48 | Some(base64::encode(&buffer))
49 | }
50 |
--------------------------------------------------------------------------------
/swift/MinecraftStatusUITests/MinecraftStatusUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 |
3 | import XCTest
4 |
5 | class MinecraftStatusUITests: XCTestCase {
6 | override func setUpWithError() throws {
7 | // Put setup code here. This method is called before the invocation of each test method in the class.
8 |
9 | // In UI tests it is usually best to stop immediately when a failure occurs.
10 | continueAfterFailure = false
11 |
12 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
13 | }
14 |
15 | override func tearDownWithError() throws {
16 | // Put teardown code here. This method is called after the invocation of each test method in the class.
17 | }
18 |
19 | func testExample() throws {
20 | // UI tests must launch the application that they test.
21 | let app = XCUIApplication()
22 | app.launch()
23 |
24 | // Use recording to get started writing UI tests.
25 | // Use XCTAssert and related functions to verify your tests produce the correct results.
26 | }
27 |
28 | func testLaunchPerformance() throws {
29 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
30 | // This measures how long it takes to launch your application.
31 | measure(metrics: [XCTApplicationLaunchMetric()]) {
32 | XCUIApplication().launch()
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/.github/workflows/rust-ci.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - main
5 | pull_request:
6 |
7 | name: Rust CI
8 |
9 | jobs:
10 | check:
11 | name: Check
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v2
15 | - uses: actions-rs/toolchain@v1
16 | with:
17 | profile: minimal
18 | toolchain: stable
19 | - uses: actions-rs/cargo@v1
20 | with:
21 | command: check
22 |
23 | test:
24 | name: Test
25 | runs-on: ubuntu-latest
26 | steps:
27 | - uses: actions/checkout@v2
28 | - uses: actions-rs/toolchain@v1
29 | with:
30 | profile: minimal
31 | toolchain: stable
32 | - uses: actions-rs/cargo@v1
33 | with:
34 | command: test
35 |
36 | fmt:
37 | name: Rustfmt
38 | runs-on: ubuntu-latest
39 | steps:
40 | - uses: actions/checkout@v2
41 | - uses: actions-rs/toolchain@v1
42 | with:
43 | profile: minimal
44 | toolchain: stable
45 | components: rustfmt
46 | - uses: actions-rs/cargo@v1
47 | with:
48 | command: fmt
49 | args: --all -- --check
50 |
51 | clippy:
52 | name: Clippy
53 | runs-on: ubuntu-latest
54 | steps:
55 | - uses: actions/checkout@v2
56 | - uses: actions-rs/toolchain@v1
57 | with:
58 | profile: minimal
59 | toolchain: stable
60 | components: clippy
61 | - uses: actions-rs/cargo@v1
62 | with:
63 | command: clippy
64 | args: -- -D warnings
65 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # minecraft-status
2 |
3 | 
4 | 
5 | [](https://deps.rs/repo/github/cldfire/minecraft-status)
6 |
7 | An iOS app with widgets that display the results of pinging a Minecraft server. Both Bedrock and Java edition servers are supported.
8 |
9 | Built on top of the [mcping](https://github.com/Scetch/mcping) crate.
10 |
11 | ## Note
12 |
13 | **Development of this app has been stopped.** Apple rejected it from the App Store for "not having enough useful content", and I have no desire to bloat the app with unnecessary features beyond being an eye-catching, functional home screen widget for Minecraft servers. Additionally, I would have to pay Apple $99/year for the rest of my life to keep this app available for download, and I have no plans to monetize it in any way to recoup any of that cost.
14 |
15 | Feel free to compile and run for yourself or fork the project. If you decide to fork the project and develop this to a point where you can get it onto the App Store, please take care to remove anything referencing me from the app. (I'd love to hear about it though! 😄.)
16 |
17 | ## Screenshots
18 |
19 |
20 |
21 | ## Features
22 |
23 | * Java and Bedrock support
24 | * Generated identicons for servers without icons
25 |
26 | ## Building
27 |
28 | See [`SETUP.md`](./SETUP.md).
29 |
30 | ## Links
31 |
32 | [Privacy Policy](./PRIVACY_POLICY.md)
33 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Minecraft Status
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | LSRequiresIPhoneOS
24 |
25 | NSUserActivityTypes
26 |
27 | ConfigurationIntent
28 | SelectServerAddressIntent
29 |
30 | UIApplicationSceneManifest
31 |
32 | UIApplicationSupportsMultipleScenes
33 |
34 |
35 | UIApplicationSupportsIndirectInputEvents
36 |
37 | UILaunchScreen
38 |
39 | UIRequiredDeviceCapabilities
40 |
41 | armv7
42 |
43 | UISupportedInterfaceOrientations
44 |
45 | UIInterfaceOrientationPortrait
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 | UISupportedInterfaceOrientations~ipad
50 |
51 | UIInterfaceOrientationPortrait
52 | UIInterfaceOrientationPortraitUpsideDown
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/.github/workflows/swift-ci.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - main
5 | pull_request:
6 |
7 | name: Swift CI
8 |
9 | jobs:
10 | Lint:
11 | runs-on: macos-latest
12 | env:
13 | MINT_PATH: ${{ github.workspace }}/mint
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v1
17 |
18 | - name: Cache Mint Packages
19 | uses: actions/cache@v1
20 | with:
21 | path: ${{ env.MINT_PATH }}
22 | key: ${{ runner.os }}-mint-${{ hashFiles('**/Mintfile') }}
23 | restore-keys: ${{ runner.os }}-mint-
24 |
25 | - name: Swift Bootstrap
26 | run: sh swift/scripts/bootstrap.sh
27 |
28 | - name: SwiftLint
29 | run: mint run swiftlint
30 |
31 | - name: SwiftFormat
32 | run: mint run swiftformat . --lint
33 |
34 | Test:
35 | runs-on: macos-latest
36 | env:
37 | MINT_PATH: ${{ github.workspace }}/mint
38 | steps:
39 | - name: Checkout
40 | uses: actions/checkout@v1
41 |
42 | - name: Cache Mint Packages
43 | uses: actions/cache@v1
44 | with:
45 | path: ${{ env.MINT_PATH }}
46 | key: ${{ runner.os }}-mint-${{ hashFiles('**/Mintfile') }}
47 | restore-keys: ${{ runner.os }}-mint-
48 |
49 | - name: Swift Bootstrap
50 | run: sh swift/scripts/bootstrap.sh
51 |
52 | - name: Rust Toolchain
53 | uses: actions-rs/toolchain@v1
54 | with:
55 | profile: minimal
56 | toolchain: stable
57 |
58 | - name: Install cbindgen
59 | uses: actions-rs/cargo@v1
60 | with:
61 | command: install
62 | args: cbindgen
63 |
64 | - name: Test
65 | uses: sersoft-gmbh/xcodebuild-action@v1
66 | with:
67 | project: swift/MinecraftStatus.xcodeproj
68 | scheme: Minecraft Status
69 | destination: platform=iOS Simulator,name=iPhone 12
70 | action: test
71 | # quiet: false
72 | # use-xcpretty: false
73 | # hide-shell-script-environment: false
74 |
75 |
--------------------------------------------------------------------------------
/swift/MinecraftStatusWidgetViewExtension/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/swift/scripts/generate-rust-lib.sh:
--------------------------------------------------------------------------------
1 | set -x
2 | set -e
3 | # Location of rust project
4 | RUST_PROJ="../rust"
5 | # Location of the lib folder in the XCode project
6 | XCODE_PROJ_LIB="$PROJECT_DIR/MinecraftStatusLib"
7 | # Provide access to Rust utilities
8 | PATH="$HOME/.cargo/bin:$PATH"
9 |
10 | if [ $CONFIGURATION = "Release" ]
11 | then
12 | export RELEASE_OR_DEBUG="release"
13 | else
14 | export RELEASE_OR_DEBUG="debug"
15 | fi
16 |
17 | cd "$RUST_PROJ"
18 |
19 | AARCH64_OBJECT_PATH=../target/aarch64-apple-ios/$RELEASE_OR_DEBUG/libminecraft_status.a
20 | X86_OBJECT_PATH=../target/x86_64-apple-ios/$RELEASE_OR_DEBUG/libminecraft_status.a
21 | UNIVERSAL_OBJECT_PATH=$XCODE_PROJ_LIB/minecraft_status.a
22 | HEADER_FILE_PATH=$XCODE_PROJ_LIB/minecraft_status.h
23 |
24 | if [[ -n "${DEVELOPER_SDK_DIR:-}" ]]; then
25 | # Assume we're in Xcode, which means we're probably cross-compiling.
26 | # In this case, we need to add an extra library search path for build scripts and proc-macros,
27 | # which run on the host instead of the target.
28 | # (macOS Big Sur does not have linkable libraries in /usr/lib/.)
29 | #
30 | # Workaround taken from https://github.com/signalapp/libsignal-client/blob/master/swift/build_ffi.sh
31 | export LIBRARY_PATH="${DEVELOPER_SDK_DIR}/MacOSX.sdk/usr/lib:${LIBRARY_PATH:-}"
32 | fi
33 |
34 | # Build for iOS architectures
35 | #
36 | # For some reason, having LIBRARY_PATH set when building for x86_64-apple-ios breaks
37 | # the build on my machine, so unsetting it prior to building those targets
38 | if [ $RELEASE_OR_DEBUG = "release" ]
39 | then
40 | cargo build --release --target aarch64-apple-ios
41 | export LIBRARY_PATH=""
42 | cargo build --release --target x86_64-apple-ios
43 | else
44 | cargo build --target aarch64-apple-ios
45 | export LIBRARY_PATH=""
46 | cargo build --target x86_64-apple-ios
47 | fi
48 |
49 | # We only want to perform these actions if cargo had to rebuild something (the file cargo generated has a newer timestamp
50 | # than what we generated the last time cargo rebuilt something) or if the header file doesn't exist
51 | #
52 | # This keeps live-updating previews working in the swiftui code
53 | #
54 | # TODO: if you build for debug, then build for release, then build for debug again, this will not overwrite the release version
55 | if [ $AARCH64_OBJECT_PATH -nt $UNIVERSAL_OBJECT_PATH -o ! -a $HEADER_FILE_PATH ]
56 | then
57 | # Generate C bindings
58 | cbindgen -l C -o $HEADER_FILE_PATH
59 | # Combine object files into a universal library
60 | lipo -create $AARCH64_OBJECT_PATH $X86_OBJECT_PATH -output $UNIVERSAL_OBJECT_PATH
61 | fi
62 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus.xcodeproj/xcshareddata/xcschemes/Minecraft Status Release.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]}
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # minecraft-status changelog
2 |
3 | Notable `minecraft-status` changes, tracked in the [keep a changelog](https://keepachangelog.com/en/1.0.0/) format with the addition of the `Internal` change type.
4 |
5 | ## [Unreleased]
6 |
7 | ## [v1.0.0-2] - 2021-10-24
8 |
9 | A quick build with no changes from the previous one to replace the expired 1.0.0-1 build. Apple required some changes I haven't had time to make yet prior to release on the App Store.
10 |
11 | ## [v1.0.0-1] - 2021-07-05
12 |
13 | The 1.0 release that will be going live on the App Store soon! While I was initially planning to add more widget types to the app prior to launching on the App Store, real-life time constraints put an end to those ideas, so I've cleaned up the wonderful existing functionality in the "Minecraft Server Icon" widget and gotten the necessary bits together to release Minecraft Status to the world.
14 |
15 | ### Changed
16 |
17 | * The "Minecraft Server Icon" widget now has an improved appearance in the large size
18 | * The app icon has been updated to be more release-worthy
19 |
20 | ### Fixed
21 |
22 | * "Widget Setup" now links to the iPad widget help article on iPads
23 |
24 | ### Internal
25 |
26 | * Set up a Swift CI test job
27 | * Renamed a bunch of stuff for more consistent naming
28 | * Fixed multiple build issues
29 | * Disabled bitcode in the xcode project settings
30 |
31 | ## [v0.1.0-5] - 2021-04-14
32 |
33 | This release adds support for pinging Bedrock Minecraft servers! The widgets default to an "Auto" mode that can ping any Minecraft server type successfully, and you can choose a specific type in the widget's settings as well if necessary.
34 |
35 | ### Added
36 |
37 | * Added support for pinging Bedrock servers
38 | * Added an auto-detect mode that will successfully ping both Bedrock and Java servers
39 | * Added identicon generation to provide an icon for servers that don't set one
40 | * Bedrock servers can't set icons at all, so this is especially important there
41 | * Added a toggle to choose between displaying the server's favicon or a generated identicon
42 | * Added a link to my Twitter so you can follow me ;)
43 |
44 | ### Internal
45 |
46 | * Moved all of the business logic entirely into Rust
47 | * Created a basic test suite to ensure the core logic doesn't break
48 |
49 | ## [v0.1.0-4] - 2021-02-28
50 |
51 | A small iteration on the first beta release with some quality of life improvements.
52 |
53 | ### Added
54 |
55 | * Server favicons are now cached
56 | * Offline servers are now neatly displayed as being offline in the widget (as long as the server was successfully pinged before)
57 |
58 | ### Changed
59 |
60 | * Latency is no longer considered when choosing a color for the status circle
61 | * It's now either green when the server is online or gray when it's offline
62 | * I personally found using latency as a data point to be more annoying than helpful
63 | * TCP connections are now performed with a five-second timeout
64 | * A widget trying to ping "google.com" will now timeout and display an error message instead of remaining in the redacted view forever
65 |
66 | ### Fixed
67 |
68 | * The app no longer crashes when tapping "Share" on an iPad
69 |
70 | ### Internal
71 |
72 | * Set up CI
73 | * Added build setup guide
74 | * Reorganized the repo
75 |
76 | ## [v0.1.0-3] - 2021-01-28
77 |
78 | Initial release.
79 |
80 | [Unreleased]: https://github.com/Cldfire/minecraft-status/compare/v1.0.0-2...HEAD
81 | [v1.0.0-2]: https://github.com/Cldfire/minecraft-status/compare/v1.0.0-1...v1.0.0-2
82 | [v1.0.0-1]: https://github.com/Cldfire/minecraft-status/compare/v0.0.1-5...v1.0.0-1
83 | [v0.1.0-5]: https://github.com/Cldfire/minecraft-status/compare/v0.0.1-4...v0.0.1-5
84 | [v0.1.0-4]: https://github.com/Cldfire/minecraft-status/compare/v0.0.1-3...v0.0.1-4
85 | [v0.1.0-3]: https://github.com/Cldfire/minecraft-status/releases/tag/v0.0.1-3
86 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus.xcodeproj/xcshareddata/xcschemes/Minecraft Status.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus.xcodeproj/xcshareddata/xcschemes/McpingWidgetViewExtension.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
9 |
10 |
16 |
22 |
23 |
24 |
30 |
36 |
37 |
38 |
39 |
40 |
45 |
46 |
47 |
48 |
60 |
63 |
69 |
70 |
71 |
72 |
78 |
79 |
80 |
81 |
85 |
86 |
90 |
91 |
95 |
96 |
97 |
98 |
105 |
107 |
113 |
114 |
115 |
116 |
118 |
119 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/rust/src/tests/mod.rs:
--------------------------------------------------------------------------------
1 | use crate::{free_status_response, get_server_status_rust, mcping_common::ProtocolType};
2 | use expect_test::{expect, Expect};
3 | use tempfile::tempdir;
4 |
5 | fn check(
6 | server_address: &str,
7 | app_group_container: Option<&str>,
8 | protocol_type: ProtocolType,
9 | always_use_identicon: bool,
10 | expect: Expect,
11 | ) {
12 | let dir = tempdir().unwrap();
13 |
14 | let app_group_container = app_group_container.unwrap_or_else(|| dir.path().to_str().unwrap());
15 |
16 | let result = get_server_status_rust(
17 | server_address,
18 | protocol_type,
19 | always_use_identicon,
20 | app_group_container,
21 | )
22 | // Use display impl since most of the debug values are unstable
23 | .map(|status| {
24 | let string = status.to_string();
25 | free_status_response(status);
26 |
27 | string
28 | });
29 | expect.assert_debug_eq(&result);
30 | }
31 |
32 | #[test]
33 | fn blank_server_address() {
34 | check(
35 | "",
36 | None,
37 | ProtocolType::Java,
38 | false,
39 | expect![[r#"
40 | Err(
41 | "empty server address",
42 | )
43 | "#]],
44 | );
45 | }
46 |
47 | #[test]
48 | fn blank_app_group_container_path() {
49 | check(
50 | "test",
51 | Some(""),
52 | ProtocolType::Java,
53 | false,
54 | expect![[r#"
55 | Err(
56 | "empty app group container path",
57 | )
58 | "#]],
59 | );
60 | }
61 |
62 | #[test]
63 | fn ping_success_basic() {
64 | check(
65 | "test.server.basic",
66 | None,
67 | ProtocolType::Java,
68 | false,
69 | expect![[r#"
70 | Ok(
71 | "Online: McInfoRaw { protocol_type: Java, favicon: \"Generated\" }",
72 | )
73 | "#]],
74 | );
75 | }
76 |
77 | #[test]
78 | fn ping_success_full() {
79 | check(
80 | "test.server.full",
81 | None,
82 | ProtocolType::Java,
83 | false,
84 | expect![[r#"
85 | Ok(
86 | "Online: McInfoRaw { protocol_type: Java, favicon: \"ServerProvided\" }",
87 | )
88 | "#]],
89 | );
90 | }
91 |
92 | #[test]
93 | fn ping_failure_dnslookupfails() {
94 | check(
95 | "test.server.dnslookupfails",
96 | None,
97 | ProtocolType::Java,
98 | false,
99 | expect![[r#"
100 | Err(
101 | DnsLookupFailed,
102 | )
103 | "#]],
104 | );
105 | }
106 |
107 | #[test]
108 | fn always_use_identicon() {
109 | check(
110 | "test.server.full",
111 | None,
112 | ProtocolType::Java,
113 | true,
114 | expect![[r#"
115 | Ok(
116 | "Online: McInfoRaw { protocol_type: Java, favicon: \"Generated\" }",
117 | )
118 | "#]],
119 | );
120 | }
121 |
122 | // TODO: tests around file handling, caching
123 | // TODO: tests using the C api
124 |
125 | #[test]
126 | #[cfg(feature = "online")]
127 | fn ping_hypixel() {
128 | check(
129 | "mc.hypixel.net",
130 | None,
131 | ProtocolType::Java,
132 | false,
133 | expect![[r#"
134 | Ok(
135 | "Online: McInfoRaw { protocol_type: Java, favicon: \"ServerProvided\" }",
136 | )
137 | "#]],
138 | );
139 | }
140 |
141 | #[test]
142 | #[cfg(feature = "online")]
143 | fn ping_google_lol() {
144 | check(
145 | "google.com",
146 | None,
147 | ProtocolType::Java,
148 | false,
149 | expect![[r#"
150 | Err(
151 | IoError(
152 | Custom {
153 | kind: TimedOut,
154 | error: "connection timed out",
155 | },
156 | ),
157 | )
158 | "#]],
159 | );
160 | }
161 |
162 | #[test]
163 | #[cfg(feature = "online")]
164 | fn ping_hyperlands() {
165 | check(
166 | "play.hyperlandsmc.net:19132",
167 | None,
168 | ProtocolType::Bedrock,
169 | false,
170 | expect![[r#"
171 | Ok(
172 | "Online: McInfoRaw { protocol_type: Bedrock, favicon: \"Generated\" }",
173 | )
174 | "#]],
175 | );
176 | }
177 |
178 | #[test]
179 | #[cfg(feature = "online")]
180 | fn ping_hypixel_auto() {
181 | check(
182 | "mc.hypixel.net",
183 | None,
184 | ProtocolType::Auto,
185 | false,
186 | expect![[r#"
187 | Ok(
188 | "Online: McInfoRaw { protocol_type: Java, favicon: \"ServerProvided\" }",
189 | )
190 | "#]],
191 | );
192 | }
193 |
194 | #[test]
195 | #[cfg(feature = "online")]
196 | fn ping_hyperlands_auto() {
197 | check(
198 | "play.hyperlandsmc.net",
199 | None,
200 | ProtocolType::Auto,
201 | false,
202 | expect![[r#"
203 | Ok(
204 | "Online: McInfoRaw { protocol_type: Bedrock, favicon: \"Generated\" }",
205 | )
206 | "#]],
207 | );
208 | }
209 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus/SettingsView.swift:
--------------------------------------------------------------------------------
1 | // UI design inspired by https://github.com/AnderGoig/github-contributions-ios
2 |
3 | import SwiftUI
4 |
5 | struct SettingsView: View {
6 | var body: some View {
7 | NavigationView {
8 | List {
9 | Section {
10 | ForEach(headerRows) { item in
11 | Button(action: { item.action.performAction() }) {
12 | SettingsRow(item: item)
13 | }
14 | }
15 | }
16 |
17 | Section(footer: footer) {
18 | ForEach(footerRows) { item in
19 | Button(action: { item.action.performAction() }) {
20 | SettingsRow(item: item)
21 | }
22 | }
23 | }
24 | }
25 | .listStyle(InsetGroupedListStyle())
26 | .navigationBarTitle("Minecraft Status", displayMode: .inline)
27 | }
28 | .navigationViewStyle(StackNavigationViewStyle())
29 | }
30 |
31 | var footer: some View {
32 | MinecraftStatusApp.fullVersion
33 | .map { Text("version \($0)") }
34 | .textCase(.uppercase)
35 | .foregroundColor(.secondary)
36 | .font(.caption2)
37 | .multilineTextAlignment(.center)
38 | .frame(maxWidth: .infinity, alignment: .center)
39 | .listRowInsets(EdgeInsets(top: 24.0, leading: 0.0, bottom: 24.0, trailing: 0.0))
40 | }
41 | }
42 |
43 | struct SettingsRow: View {
44 | let item: SettingsRowItem
45 |
46 | var body: some View {
47 | HStack(alignment: .center, spacing: 16.0) {
48 | item.imageName.imageForName()
49 | .resizable()
50 | .aspectRatio(contentMode: .fit)
51 | .frame(width: 17.0, height: 17.0)
52 | .padding(6.0)
53 | .background(item.color)
54 | .foregroundColor(.white)
55 | .clipShape(RoundedRectangle(cornerRadius: 6.0, style: .continuous))
56 |
57 | VStack(alignment: .leading, spacing: 3.0) {
58 | Text(item.title)
59 | .font(.body)
60 | .foregroundColor(.primary)
61 | .lineLimit(1)
62 |
63 | Text(item.subtitle)
64 | .font(.footnote)
65 | .foregroundColor(.secondary)
66 | .lineLimit(1)
67 | }
68 | }
69 | .padding(.vertical, 5.0)
70 | }
71 | }
72 |
73 | struct ContentView_Previews: PreviewProvider {
74 | static var previews: some View {
75 | SettingsView()
76 | }
77 | }
78 |
79 | enum SettingsRowItemAction {
80 | case openUrl(URL)
81 | case shareUrl(URL)
82 |
83 | func performAction() {
84 | switch self {
85 | case let .openUrl(url):
86 | UIApplication.shared.open(url)
87 | case let .shareUrl(url):
88 | present(UIActivityViewController(activityItems: [url], applicationActivities: nil), animated: true)
89 | }
90 | }
91 | }
92 |
93 | enum ImageName {
94 | case system(String)
95 | case asset(String)
96 |
97 | func imageForName() -> Image {
98 | switch self {
99 | case let .system(name):
100 | return Image(systemName: name)
101 | case let .asset(name):
102 | return Image(name)
103 | }
104 | }
105 | }
106 |
107 | func present(_ viewController: UIViewController, animated: Bool, completion: (() -> Void)? = nil) {
108 | guard var topController = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.rootViewController else { return }
109 |
110 | while let presentedViewController = topController.presentedViewController {
111 | topController = presentedViewController
112 | }
113 |
114 | // TODO: temporary fix so this doesn't crash on iPads, make this better
115 | let popover = viewController.popoverPresentationController
116 | popover?.sourceView = topController.view
117 | popover?.sourceRect = CGRect(x: 0, y: 0, width: 64, height: 64)
118 |
119 | topController.present(viewController, animated: animated, completion: completion)
120 | }
121 |
122 | func widgetHelpURL() -> URL {
123 | switch UIDevice.current.userInterfaceIdiom {
124 | case .pad:
125 | // Link to iPad widget help article
126 | return URL(string: "https://support.apple.com/en-us/HT211328")!
127 | default:
128 | // Link to iPhone widget help article
129 | return URL(string: "https://support.apple.com/en-us/HT207122")!
130 | }
131 | }
132 |
133 | /// The data for a settings row item.
134 | struct SettingsRowItem: Identifiable {
135 | var id = UUID()
136 | var title: String
137 | var subtitle: String
138 | var imageName: ImageName
139 | var color: Color
140 | var action: SettingsRowItemAction
141 | }
142 |
143 | let headerRows = [
144 | SettingsRowItem(title: "Widget Setup", subtitle: "Learn how to use widgets", imageName: .system("questionmark"), color: .blue, action: .openUrl(widgetHelpURL())),
145 | ]
146 |
147 | let footerRows = [
148 | SettingsRowItem(title: "Rate the App", subtitle: "Reviews are greatly appreciated!", imageName: .system("star.fill"), color: .pink, action: .openUrl(URL(string: "itms-apps://apps.apple.com/app/id1549596839?action=write-review")!)),
149 | SettingsRowItem(title: "Share", subtitle: "Tell your friends!", imageName: .system("square.and.arrow.up"), color: .green, action: .shareUrl(URL(string: "https://apps.apple.com/app/id1549596839")!)),
150 | SettingsRowItem(title: "Open Source", subtitle: "Contribute and file issues", imageName: .system("swift"), color: .orange, action: .openUrl(URL(string: "https://github.com/Cldfire/minecraft-status")!)),
151 | SettingsRowItem(title: "@_cldfire", subtitle: "Follow me on Twitter for updates", imageName: .asset("twitterLogoWhite"), color: Color("twitterBlue"), action: .openUrl(URL(string: "https://twitter.com/_cldfire")!)),
152 | ]
153 |
--------------------------------------------------------------------------------
/rust/src/mcping_common.rs:
--------------------------------------------------------------------------------
1 | //! Provides a common Rust abstraction over both the Java and Bedrock ping protocols.
2 | //!
3 | //! This abstraction provides the ability to both ping an address with a given
4 | //! protocol and ping an address with both protocols, returning in all cases a
5 | //! unified response type that communicates which protocol was successful.
6 |
7 | use std::{io, sync::mpsc, thread, time::Duration};
8 |
9 | /// The various protocol types that can be used for a ping.
10 | #[repr(C)]
11 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
12 | pub enum ProtocolType {
13 | /// Ping using the Java protocol only.
14 | Java,
15 | /// Ping using the Bedrock protocol only.
16 | Bedrock,
17 | /// Ping using all protocols, returning the first successful ping result.
18 | Auto,
19 | }
20 |
21 | impl std::fmt::Display for ProtocolType {
22 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 | match self {
24 | ProtocolType::Java => f.write_str("java"),
25 | ProtocolType::Bedrock => f.write_str("bedrock"),
26 | ProtocolType::Auto => f.write_str("auto"),
27 | }
28 | }
29 | }
30 |
31 | #[derive(Debug, Clone, Eq, PartialEq, Hash)]
32 | pub struct Response {
33 | pub protocol_type: ProtocolType,
34 | pub latency: u64,
35 | pub version: Version,
36 | pub players: Players,
37 | // TODO: turn this into a rich text type
38 | pub motd: String,
39 | /// The server icon (a Base64-encoded PNG image).
40 | pub favicon: Option,
41 | }
42 |
43 | impl Response {
44 | fn from_java(latency: u64, v: mcping::JavaResponse) -> Self {
45 | Self {
46 | protocol_type: ProtocolType::Java,
47 | latency,
48 | version: Version {
49 | name: v.version.name,
50 | protocol: Some(v.version.protocol),
51 | },
52 | players: Players {
53 | online: v.players.online,
54 | max: v.players.max,
55 | sample: v
56 | .players
57 | .sample
58 | .into_iter()
59 | .flatten()
60 | .map(|p| Player {
61 | name: p.name,
62 | id: p.id,
63 | })
64 | .collect(),
65 | },
66 | motd: v.description.text().to_string(),
67 | favicon: v.favicon,
68 | }
69 | }
70 |
71 | fn from_bedrock(latency: u64, v: mcping::BedrockResponse) -> Self {
72 | Self {
73 | protocol_type: ProtocolType::Bedrock,
74 | latency,
75 | version: Version {
76 | name: v.version_name,
77 | protocol: v.protocol_version,
78 | },
79 | players: Players {
80 | online: v.players_online.unwrap_or(0),
81 | max: v.players_max.unwrap_or(0),
82 | sample: vec![],
83 | },
84 | motd: format!(
85 | "motd1: {} motd2: {}",
86 | v.motd_1,
87 | v.motd_2.unwrap_or_default()
88 | ),
89 | favicon: None,
90 | }
91 | }
92 | }
93 |
94 | #[derive(Debug, Clone, Eq, PartialEq, Hash)]
95 | pub struct Version {
96 | pub name: String,
97 | pub protocol: Option,
98 | }
99 |
100 | #[derive(Debug, Clone, Eq, PartialEq, Hash)]
101 | pub struct Players {
102 | pub online: i64,
103 | pub max: i64,
104 | pub sample: Vec,
105 | }
106 |
107 | #[derive(Debug, Clone, Eq, PartialEq, Hash)]
108 | pub struct Player {
109 | pub name: String,
110 | pub id: String,
111 | }
112 |
113 | /// A common `get_status` function that can ping Java or Bedrock (or intelligently
114 | /// try both).
115 | pub fn get_status(
116 | server_address: String,
117 | timeout: Option,
118 | protocol_type: ProtocolType,
119 | ) -> Result {
120 | match protocol_type {
121 | ProtocolType::Java => mcping::get_status(mcping::Java {
122 | server_address,
123 | timeout,
124 | })
125 | .map(|(latency, response)| Response::from_java(latency, response)),
126 | ProtocolType::Bedrock => mcping::get_status(mcping::Bedrock {
127 | server_address,
128 | timeout,
129 | ..Default::default()
130 | })
131 | .map(|(latency, response)| Response::from_bedrock(latency, response)),
132 | ProtocolType::Auto => get_status_auto(server_address, timeout),
133 | }
134 | }
135 |
136 | /// Implements trying both protocol pings and returning the first successful result.
137 | fn get_status_auto(
138 | server_address: String,
139 | timeout: Option,
140 | ) -> Result {
141 | enum ResponseType {
142 | Java((u64, mcping::JavaResponse)),
143 | Bedrock((u64, mcping::BedrockResponse)),
144 | }
145 |
146 | let (tx, rx) = mpsc::channel::>();
147 |
148 | let tx2 = tx.clone();
149 | let server_address2 = server_address.clone();
150 |
151 | thread::spawn(move || {
152 | let _ = tx.send(
153 | mcping::get_status(mcping::Java {
154 | server_address,
155 | timeout,
156 | })
157 | .map(|(latency, response)| ResponseType::Java((latency, response))),
158 | );
159 | });
160 |
161 | thread::spawn(move || {
162 | let _ = tx2.send(
163 | mcping::get_status(mcping::Bedrock {
164 | server_address: server_address2,
165 | timeout,
166 | ..Default::default()
167 | })
168 | .map(|(latency, response)| ResponseType::Bedrock((latency, response))),
169 | );
170 | });
171 |
172 | for _ in 0..2 {
173 | // Return the first successful response, if any
174 | if let Ok(Ok(response_type)) = rx.recv() {
175 | return Ok(match response_type {
176 | ResponseType::Java((latency, response)) => Response::from_java(latency, response),
177 | ResponseType::Bedrock((latency, response)) => {
178 | Response::from_bedrock(latency, response)
179 | }
180 | });
181 | }
182 | }
183 |
184 | Err(mcping::Error::IoError(io::Error::new(
185 | io::ErrorKind::TimedOut,
186 | "neither thread returned a valid response",
187 | )))
188 | }
189 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus/McStructs.swift:
--------------------------------------------------------------------------------
1 | //
2 |
3 | import Foundation
4 | import SwiftUI
5 |
6 | let sharedContainer: URL = {
7 | // Write to shared app group container so both the widget and the host app can access
8 | FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.dev.cldfire.minecraft-status")!
9 | }()
10 |
11 | /// Represents the server status we were able to determine
12 | enum ServerStatus {
13 | /// The server was online, and we got the given info
14 | case online(OnlineResponse)
15 | /// The server was unreachable, but we were able to reach it at some point in the past.
16 | ///
17 | /// We may have a cached favicon to make use of.
18 | case offline(OfflineResponse)
19 | /// The server was unreachable, and we've never reached it in the past.
20 | case unreachable(UnreachableResponse)
21 |
22 | /// Attempt to ping the server at the given address.
23 | static func forServerAddress(_ serverAddress: String, _ protocolType: ProtocolType, _ alwaysUseIdenticon: Bool) -> Self {
24 | let status = get_server_status(serverAddress, protocolType, alwaysUseIdenticon, sharedContainer.path)
25 |
26 | defer {
27 | free_status_response(status)
28 | }
29 |
30 | switch status.tag {
31 | case Online:
32 | return .online(OnlineResponse(mcInfo: McInfo(status.online.mcinfo), weekStats: WeekStatsSwift(status.online.week_stats)))
33 | case Offline:
34 | return .offline(OfflineResponse(favicon: Favicon.fromRaw(status.offline.favicon), weekStats: WeekStatsSwift(status.offline.week_stats)))
35 | case Unreachable:
36 | let errorString: String
37 | if let errorStringCstr = status.unreachable.error_string {
38 | errorString = String(cString: errorStringCstr)
39 | } else {
40 | errorString = ""
41 | }
42 |
43 | return .unreachable(UnreachableResponse(errorString: errorString))
44 | default:
45 | fatalError("unexpected type of server status response")
46 | }
47 | }
48 |
49 | func favicon() -> Favicon {
50 | switch self {
51 | case let .online(response):
52 | return response.mcInfo.favicon
53 | case let .offline(response):
54 | return response.favicon
55 | case .unreachable:
56 | return .noFavicon
57 | }
58 | }
59 |
60 | // We return a Text view here so the resulting string has separators in the numbers
61 | func playersOnlineText() -> Text {
62 | switch self {
63 | case let .online(response):
64 | return Text("\(response.mcInfo.players.online) / \(response.mcInfo.players.max)")
65 | case .offline:
66 | return Text("-- / --")
67 | case .unreachable:
68 | return Text("")
69 | }
70 | }
71 |
72 | func statusColor() -> Color {
73 | if case .online = self {
74 | // We always return green if the server is online, regardless of latency.
75 | //
76 | // This decision was made because latency is oftentimes irregular in the context
77 | // of a phone widget; for instance, when using data rather than wifi. This
78 | // irregularity makes latency a poor data point to use to change the status color,
79 | // and I personally found it more annoying than useful.
80 | return Color.green
81 | } else {
82 | return Color.gray
83 | }
84 | }
85 | }
86 |
87 | struct OnlineResponse {
88 | var mcInfo: McInfo
89 | var weekStats: WeekStatsSwift
90 | }
91 |
92 | struct OfflineResponse {
93 | /// The server icon (a Base64-encoded PNG image)
94 | var favicon: Favicon
95 | var weekStats: WeekStatsSwift
96 | }
97 |
98 | struct UnreachableResponse {
99 | var errorString: String
100 | }
101 |
102 | struct McInfo {
103 | /// Latency to the server
104 | var latency: UInt64
105 | var version: Version
106 | /// Information about online players
107 | var players: Players
108 | /// The server's description text
109 | var description: String
110 | /// The server icon (a Base64-encoded PNG image)
111 | var favicon: Favicon
112 | }
113 |
114 | // Using a separate extension for this initializer keeps the default memberwise initializer
115 | // around
116 | extension McInfo {
117 | /// Copies data from the given `McInfoRaw` in order to create this struct.
118 | init(_ from: McInfoRaw) {
119 | self.latency = from.latency
120 |
121 | self.version = Version(from.version)
122 | self.players = Players(from.players)
123 |
124 | if let descriptionCstr = from.description {
125 | self.description = String(cString: descriptionCstr)
126 | } else {
127 | self.description = ""
128 | }
129 |
130 | self.favicon = Favicon.fromRaw(from.favicon)
131 | }
132 | }
133 |
134 | struct Version {
135 | var name: String
136 | var protocolVersion: Int64
137 | }
138 |
139 | extension Version {
140 | /// Copies data from the given `VersionRaw` in order to create this struct.
141 | init(_ from: VersionRaw) {
142 | if let nameCstr = from.name {
143 | self.name = String(cString: nameCstr)
144 | } else {
145 | self.name = ""
146 | }
147 |
148 | self.protocolVersion = from.protocol
149 | }
150 | }
151 |
152 | struct Player {
153 | var name: String
154 | var id: String
155 | }
156 |
157 | extension Player {
158 | /// Copies data from the given `PlayerRaw` in order to create this struct.
159 | init(_ from: PlayerRaw) {
160 | if let nameCstr = from.name {
161 | self.name = String(cString: nameCstr)
162 | } else {
163 | self.name = ""
164 | }
165 |
166 | if let idCstr = from.id {
167 | self.id = String(cString: idCstr)
168 | } else {
169 | self.id = ""
170 | }
171 | }
172 | }
173 |
174 | struct Players {
175 | var max: Int64
176 | var online: Int64
177 | var sample: [Player]
178 | }
179 |
180 | extension Players {
181 | /// Copies data from the given `PlayersRaw` in order to create this struct.
182 | init(_ from: PlayersRaw) {
183 | self.max = from.max
184 | self.online = from.online
185 |
186 | self.sample = [Player]()
187 | self.sample.reserveCapacity(Int(from.sample_len))
188 |
189 | for i in 0.. Self {
205 | switch from.tag {
206 | case ServerProvided:
207 | let favicon: String?
208 | if let faviconCstr = from.server_provided {
209 | favicon = String(cString: faviconCstr)
210 | } else {
211 | favicon = nil
212 | }
213 | return .serverProvided(favicon!)
214 | case Generated:
215 | let favicon: String?
216 | if let faviconCstr = from.generated {
217 | favicon = String(cString: faviconCstr)
218 | } else {
219 | favicon = nil
220 | }
221 | return .generated(favicon!)
222 | case NoFavicon:
223 | return noFavicon
224 | default:
225 | fatalError("unexpected type of favicon")
226 | }
227 | }
228 |
229 | func faviconString() -> String? {
230 | switch self {
231 | case let .serverProvided(faviconString):
232 | return faviconString
233 | case let .generated(faviconString):
234 | return faviconString
235 | case .noFavicon:
236 | return nil
237 | }
238 | }
239 |
240 | func isGenerated() -> Bool {
241 | switch self {
242 | case .generated:
243 | return true
244 | default:
245 | return false
246 | }
247 | }
248 | }
249 |
250 | /// Week stats but with an array instead of a tuple.
251 | struct WeekStatsSwift {
252 | var dailyStats: [RangeStats]
253 | var peakOnline: Int64
254 | var peakMax: Int64
255 | }
256 |
257 | extension WeekStatsSwift {
258 | /// Copies data from the given `WeekStats` in order to create this struct.
259 | init(_ from: WeekStats) {
260 | self.dailyStats = [from.daily_stats.0, from.daily_stats.1, from.daily_stats.2, from.daily_stats.3, from.daily_stats.4, from.daily_stats.5, from.daily_stats.6, from.daily_stats.7]
261 | self.peakOnline = from.peak_online
262 | self.peakMax = from.peak_max
263 | }
264 | }
265 |
--------------------------------------------------------------------------------
/swift/MinecraftStatusWidgetViewExtension/MinecraftStatusWidgetViewExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 |
3 | import Intents
4 | import SwiftUI
5 | import WidgetKit
6 |
7 | struct Provider: IntentTimelineProvider {
8 | func placeholder(in _: Context) -> McServerStatusEntry {
9 | previewData[0]
10 | }
11 |
12 | func getSnapshot(for _: ConfigurationIntent, in _: Context, completion: @escaping (McServerStatusEntry) -> Void) {
13 | // Preview uses a ping response from mc.hypixel.net
14 | let entry = previewData[2]
15 | completion(entry)
16 | }
17 |
18 | func getTimeline(for configuration: ConfigurationIntent, in _: Context, completion: @escaping (Timeline) -> Void) {
19 | let currentDate = Date()
20 | let refreshDate = Calendar.current.date(byAdding: .minute, value: 15, to: currentDate)!
21 | let protocolType = protocolTypeFromConfig(configuration.protocolTypeConfig)
22 | let alwaysUseIdenticon = !(configuration.useServerFavicon as? Bool ?? true)
23 |
24 | McPinger.ping(configuration.serverAddress ?? "", protocolType, alwaysUseIdenticon) { serverStatus in
25 | let entry = McServerStatusEntry(date: currentDate, configuration: configuration, status: serverStatus)
26 | let timeline = Timeline(entries: [entry], policy: .after(refreshDate))
27 | completion(timeline)
28 | }
29 | }
30 | }
31 |
32 | struct McServerStatusEntry: TimelineEntry {
33 | let date: Date
34 | let configuration: ConfigurationIntent
35 | let status: ServerStatus
36 | // TODO: relevance
37 | }
38 |
39 | enum McPinger {
40 | static func ping(_ serverAddress: String, _ protocolType: ProtocolType, _ alwaysUseIdenticon: Bool, completion: @escaping (ServerStatus) -> Void) {
41 | // TODO: not sure that this is the correct way of making a backgroud network request
42 | // in a widget
43 | DispatchQueue.global(qos: .background).async {
44 | let status = ServerStatus.forServerAddress(serverAddress, protocolType, alwaysUseIdenticon)
45 | completion(status)
46 | }
47 | }
48 | }
49 |
50 | func protocolTypeFromConfig(_ protocolTypeConfig: ProtocolTypeConfig) -> ProtocolType {
51 | switch protocolTypeConfig {
52 | case .auto:
53 | return Auto
54 | case .java:
55 | return Java
56 | case .bedrock:
57 | return Bedrock
58 | case .unknown:
59 | return Auto
60 | }
61 | }
62 |
63 | /// Attempts to convert a maybe-present base64 image string into a `UIImage`.
64 | func convertBase64StringToImage(imageBase64String: String?) -> UIImage? {
65 | guard let imageString = imageBase64String else {
66 | return nil
67 | }
68 |
69 | guard let imageData = Data(base64Encoded: imageString) else {
70 | return nil
71 | }
72 |
73 | return UIImage(data: imageData)
74 | }
75 |
76 | func chooseBestHeaderText(for configuration: ConfigurationIntent) -> String {
77 | if let serverName = configuration.serverName, !serverName.isEmpty {
78 | return serverName
79 | } else {
80 | return configuration.serverAddress ?? ""
81 | }
82 | }
83 |
84 | struct ServerFavicon: View {
85 | let favicon: Favicon
86 | @Environment(\.colorScheme) var colorScheme
87 |
88 | var body: some View {
89 | if favicon.isGenerated() {
90 | // Add a background behind generated favicons
91 | Rectangle().foregroundColor(Color(UIColor.systemBackground))
92 | } else {
93 | // Minecraft dirt block backing
94 | Image("minecraft-dirt").interpolation(.none).antialiased(false).resizable().aspectRatio(contentMode: .fill).unredacted()
95 | Rectangle().opacity(0.60).foregroundColor(.black)
96 | }
97 |
98 | if let faviconImage = convertBase64StringToImage(imageBase64String: favicon.faviconString()) {
99 | Image(uiImage: faviconImage).interpolation(.none).antialiased(false).resizable().aspectRatio(contentMode: .fit).shadow(radius: favicon.isGenerated() ? 0 : 30).colorScheme(.light)
100 | }
101 | }
102 | }
103 |
104 | struct OverlayBanner: View {
105 | var entry: Provider.Entry
106 | @Environment(\.widgetFamily) var family
107 |
108 | var body: some View {
109 | VStack {
110 | // These spacers move the banner towards the bottom of the widget
111 | Spacer()
112 | Spacer()
113 | Spacer()
114 | Spacer()
115 | Spacer()
116 |
117 | HStack {
118 | // The banner content
119 | VStack(alignment: .leading, spacing: 4) {
120 | // Server address
121 | Text("\(chooseBestHeaderText(for: entry.configuration))").foregroundColor(.white).shadow(color: .black, radius: 0.5, x: 1, y: 1).lineLimit(1)
122 |
123 | // Players online and status indicator
124 | HStack(spacing: 5) {
125 | entry.status.playersOnlineText().foregroundColor(.white).shadow(color: .black, radius: 0.5, x: 1, y: 1).minimumScaleFactor(0.8).lineLimit(1)
126 |
127 | Circle().foregroundColor(entry.status.statusColor()).fixedSize().scaleEffect(family == .systemLarge ? 1.0 : 0.9)
128 | }
129 | }
130 | .font(.custom("minecraft", size: family == .systemLarge ? 14 : 12))
131 | .frame(height: 45, alignment: .leading)
132 | .frame(maxWidth: family == .systemLarge ? .none : .infinity)
133 | .padding(.leading, family == .systemLarge ? 20 : 10)
134 | .padding(.trailing, family == .systemLarge ? 20 : 10)
135 | .padding(.top, family == .systemLarge ? 5 : 0)
136 | .padding(.bottom, family == .systemLarge ? 5 : 0)
137 | .background(
138 | Color.black
139 | .opacity(0.7)
140 | .cornerRadius(family == .systemLarge ? 8 : 0)
141 | )
142 |
143 | if family == .systemLarge {
144 | // Shove the banner to the left side for the large widget
145 | Spacer()
146 | }
147 | }
148 | .padding(.leading, family == .systemLarge ? 20 : 0)
149 | .padding(.trailing, family == .systemLarge ? 100 : 0)
150 |
151 | // This spacer separates the banner from the bottom of the widget
152 | Spacer()
153 | }
154 | }
155 | }
156 |
157 | struct MinecraftStatusWidgetExtensionEntryView: View {
158 | var entry: Provider.Entry
159 | @Environment(\.widgetFamily) var family
160 |
161 | var body: some View {
162 | if case .unreachable = entry.status {
163 | if let serverAddress = entry.configuration.serverAddress, !serverAddress.isEmpty {
164 | Text("Unable to ping Minecraft server \"\(serverAddress)\"").padding()
165 | } else {
166 | Text("Please edit the widget and set a server address").padding()
167 | }
168 | } else {
169 | ZStack {
170 | ServerFavicon(favicon: entry.status.favicon())
171 | OverlayBanner(entry: entry)
172 | }
173 | }
174 | }
175 | }
176 |
177 | @main
178 | struct MinecraftStatusWidgetExtension: Widget {
179 | let kind: String = "MinecraftStatusWidgetViewExtension"
180 |
181 | var body: some WidgetConfiguration {
182 | IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
183 | MinecraftStatusWidgetExtensionEntryView(entry: entry)
184 | }
185 | .configurationDisplayName("Minecraft Server Icon")
186 | .description("Information about a Minecraft server on top of its icon")
187 | // TODO: work on the large widget a bit
188 | .supportedFamilies([.systemSmall, .systemLarge])
189 | }
190 | }
191 |
192 | struct MinecraftStatusWidgetExtension_Previews: PreviewProvider {
193 | static var previews: some View {
194 | Group {
195 | ForEach(0..
2 |
3 |
4 |
5 | INEnums
6 |
7 |
8 | INEnumDisplayName
9 | Server Type
10 | INEnumDisplayNameID
11 | GwwkNo
12 | INEnumGeneratesHeader
13 |
14 | INEnumName
15 | ProtocolTypeConfig
16 | INEnumType
17 | Regular
18 | INEnumValues
19 |
20 |
21 | INEnumValueDisplayName
22 | unknown
23 | INEnumValueDisplayNameID
24 | RjxXwA
25 | INEnumValueName
26 | unknown
27 |
28 |
29 | INEnumValueDisplayName
30 | Auto
31 | INEnumValueDisplayNameID
32 | b2e3oo
33 | INEnumValueIndex
34 | 1
35 | INEnumValueName
36 | auto
37 |
38 |
39 | INEnumValueDisplayName
40 | Java
41 | INEnumValueDisplayNameID
42 | ccO2Yy
43 | INEnumValueIndex
44 | 2
45 | INEnumValueName
46 | java
47 |
48 |
49 | INEnumValueDisplayName
50 | Bedrock
51 | INEnumValueDisplayNameID
52 | igreet
53 | INEnumValueIndex
54 | 3
55 | INEnumValueName
56 | bedrock
57 |
58 |
59 |
60 |
61 | INIntentDefinitionModelVersion
62 | 1.2
63 | INIntentDefinitionNamespace
64 | 88xZPY
65 | INIntentDefinitionSystemVersion
66 | 20D91
67 | INIntentDefinitionToolsBuildVersion
68 | 12D4e
69 | INIntentDefinitionToolsVersion
70 | 12.4
71 | INIntents
72 |
73 |
74 | INIntentCategory
75 | information
76 | INIntentDescription
77 | Minecraft Status Widget configuration parameters
78 | INIntentDescriptionID
79 | tVvJ9c
80 | INIntentEligibleForWidgets
81 |
82 | INIntentIneligibleForSuggestions
83 |
84 | INIntentLastParameterTag
85 | 6
86 | INIntentName
87 | Configuration
88 | INIntentParameters
89 |
90 |
91 | INIntentParameterConfigurable
92 |
93 | INIntentParameterDisplayName
94 | Server Address
95 | INIntentParameterDisplayNameID
96 | hWx9l3
97 | INIntentParameterDisplayPriority
98 | 1
99 | INIntentParameterMetadata
100 |
101 | INIntentParameterMetadataCapitalization
102 | None
103 | INIntentParameterMetadataDefaultValue
104 | mc.hypixel.net
105 | INIntentParameterMetadataDefaultValueID
106 | MEFOca
107 | INIntentParameterMetadataDisableAutocorrect
108 |
109 | INIntentParameterMetadataDisableSmartDashes
110 |
111 | INIntentParameterMetadataDisableSmartQuotes
112 |
113 |
114 | INIntentParameterName
115 | serverAddress
116 | INIntentParameterPromptDialogs
117 |
118 |
119 | INIntentParameterPromptDialogCustom
120 |
121 | INIntentParameterPromptDialogType
122 | Configuration
123 |
124 |
125 | INIntentParameterPromptDialogCustom
126 |
127 | INIntentParameterPromptDialogType
128 | Primary
129 |
130 |
131 | INIntentParameterTag
132 | 1
133 | INIntentParameterType
134 | String
135 |
136 |
137 | INIntentParameterConfigurable
138 |
139 | INIntentParameterDisplayName
140 | Server Name
141 | INIntentParameterDisplayNameID
142 | 0zurXT
143 | INIntentParameterDisplayPriority
144 | 2
145 | INIntentParameterMetadata
146 |
147 | INIntentParameterMetadataCapitalization
148 | Sentences
149 | INIntentParameterMetadataDefaultValueID
150 | Emhv7x
151 | INIntentParameterMetadataDisableAutocorrect
152 |
153 | INIntentParameterMetadataDisableSmartDashes
154 |
155 | INIntentParameterMetadataDisableSmartQuotes
156 |
157 |
158 | INIntentParameterName
159 | serverName
160 | INIntentParameterPromptDialogs
161 |
162 |
163 | INIntentParameterPromptDialogCustom
164 |
165 | INIntentParameterPromptDialogType
166 | Configuration
167 |
168 |
169 | INIntentParameterPromptDialogCustom
170 |
171 | INIntentParameterPromptDialogType
172 | Primary
173 |
174 |
175 | INIntentParameterTag
176 | 2
177 | INIntentParameterType
178 | String
179 |
180 |
181 | INIntentParameterConfigurable
182 |
183 | INIntentParameterDisplayName
184 | Server Type
185 | INIntentParameterDisplayNameID
186 | 215ixD
187 | INIntentParameterDisplayPriority
188 | 3
189 | INIntentParameterEnumType
190 | ProtocolTypeConfig
191 | INIntentParameterEnumTypeNamespace
192 | 88xZPY
193 | INIntentParameterMetadata
194 |
195 | INIntentParameterMetadataDefaultValue
196 | auto
197 |
198 | INIntentParameterName
199 | protocolTypeConfig
200 | INIntentParameterPromptDialogs
201 |
202 |
203 | INIntentParameterPromptDialogCustom
204 |
205 | INIntentParameterPromptDialogType
206 | Configuration
207 |
208 |
209 | INIntentParameterPromptDialogCustom
210 |
211 | INIntentParameterPromptDialogType
212 | Primary
213 |
214 |
215 | INIntentParameterPromptDialogCustom
216 |
217 | INIntentParameterPromptDialogFormatString
218 | There are ${count} options matching ‘${protocolTypeConfig}’.
219 | INIntentParameterPromptDialogFormatStringID
220 | QAt7pk
221 | INIntentParameterPromptDialogType
222 | DisambiguationIntroduction
223 |
224 |
225 | INIntentParameterPromptDialogCustom
226 |
227 | INIntentParameterPromptDialogFormatString
228 | Just to confirm, you wanted ‘${protocolTypeConfig}’?
229 | INIntentParameterPromptDialogFormatStringID
230 | JH7A5L
231 | INIntentParameterPromptDialogType
232 | Confirmation
233 |
234 |
235 | INIntentParameterTag
236 | 4
237 | INIntentParameterType
238 | Integer
239 |
240 |
241 | INIntentParameterConfigurable
242 |
243 | INIntentParameterDisplayName
244 | Use Server Favicon
245 | INIntentParameterDisplayNameID
246 | NOA5RA
247 | INIntentParameterDisplayPriority
248 | 4
249 | INIntentParameterMetadata
250 |
251 | INIntentParameterMetadataDefaultValue
252 |
253 | INIntentParameterMetadataFalseDisplayName
254 | false
255 | INIntentParameterMetadataFalseDisplayNameID
256 | 7NKgoi
257 | INIntentParameterMetadataTrueDisplayName
258 | true
259 | INIntentParameterMetadataTrueDisplayNameID
260 | Ov5gzh
261 |
262 | INIntentParameterName
263 | useServerFavicon
264 | INIntentParameterPromptDialogs
265 |
266 |
267 | INIntentParameterPromptDialogCustom
268 |
269 | INIntentParameterPromptDialogType
270 | Configuration
271 |
272 |
273 | INIntentParameterPromptDialogCustom
274 |
275 | INIntentParameterPromptDialogType
276 | Primary
277 |
278 |
279 | INIntentParameterTag
280 | 6
281 | INIntentParameterType
282 | Boolean
283 |
284 |
285 | INIntentResponse
286 |
287 | INIntentResponseCodes
288 |
289 |
290 | INIntentResponseCodeName
291 | success
292 | INIntentResponseCodeSuccess
293 |
294 |
295 |
296 | INIntentResponseCodeName
297 | failure
298 |
299 |
300 |
301 | INIntentTitle
302 | Configuration
303 | INIntentTitleID
304 | gpCwrM
305 | INIntentType
306 | Custom
307 | INIntentVerb
308 | View
309 |
310 |
311 | INTypes
312 |
313 |
314 |
315 |
--------------------------------------------------------------------------------
/rust/src/week_stats.rs:
--------------------------------------------------------------------------------
1 | //! Implements the week stats backend.
2 | //!
3 | //! Collects, stores, and hands out ping stats about a Minecraft server over the
4 | //! last week or so.
5 |
6 | use std::{collections::BTreeMap, fs, ops::RangeBounds, path::Path};
7 |
8 | use anyhow::Context;
9 | use chrono::{DateTime, Duration, Local, Timelike, Utc};
10 | use serde::{Deserialize, Serialize};
11 |
12 | #[derive(Serialize, Deserialize, Default)]
13 | struct PingStatsOnDisk {
14 | /// History entries keyed by unix timestamp.
15 | ping_history: BTreeMap,
16 | }
17 |
18 | impl PingStatsOnDisk {
19 | /// Trim outdated entries from the beginning of the stored ping history.
20 | ///
21 | /// An entry older than 10 days ago is considered to be outdated.
22 | pub fn trim_outdated(&mut self, now: DateTime) {
23 | let cutoff = now - Duration::days(10);
24 | let cutoff_timestamp = cutoff.timestamp();
25 |
26 | // TODO: use BTreeMap::retain when it's stable
27 | let remaining = self.ping_history.split_off(&cutoff_timestamp);
28 | self.ping_history = remaining;
29 | }
30 |
31 | /// Incorporate the given ping data appropriately into the stored entries.
32 | pub fn add_data(&mut self, now: DateTime, current_online: i64, current_max: i64) {
33 | self.ping_history
34 | .entry(now.timestamp())
35 | .or_default()
36 | .update(current_online, current_max);
37 | }
38 |
39 | /// Return `RangeStats` built from data within the given timestamp range.
40 | pub fn range_stats(&self, timestamp_range: impl RangeBounds) -> RangeStats {
41 | let mut num_entries = 0;
42 | let mut total_online = 0;
43 | let mut peak_online = 0;
44 | let mut peak_max = 0;
45 |
46 | for (_, v) in self.ping_history.range(timestamp_range) {
47 | num_entries += 1;
48 | total_online += v.online;
49 |
50 | peak_online = peak_online.max(v.online);
51 | peak_max = peak_max.max(v.max);
52 | }
53 |
54 | RangeStats {
55 | average_online: if num_entries == 0 {
56 | 0
57 | } else {
58 | total_online / num_entries
59 | },
60 | peak_online,
61 | peak_max,
62 | }
63 | }
64 |
65 | /// Build `WeekStats` from the current state of the data.
66 | pub fn week_stats(&self, now_timestamp: i64, seconds_from_midnight: i64) -> WeekStats {
67 | let today_midnight = now_timestamp - seconds_from_midnight;
68 |
69 | /// Returns the number of seconds in `n` days.
70 | fn days(n: i64) -> i64 {
71 | 60 * 60 * 24 * n
72 | }
73 |
74 | let daily_stats = [
75 | self.range_stats((today_midnight - days(7))..(today_midnight - days(6))),
76 | self.range_stats((today_midnight - days(6))..(today_midnight - days(5))),
77 | self.range_stats((today_midnight - days(5))..(today_midnight - days(4))),
78 | self.range_stats((today_midnight - days(4))..(today_midnight - days(3))),
79 | self.range_stats((today_midnight - days(3))..(today_midnight - days(2))),
80 | self.range_stats((today_midnight - days(2))..(today_midnight - days(1))),
81 | self.range_stats((today_midnight - days(1))..today_midnight),
82 | self.range_stats(today_midnight..=now_timestamp),
83 | ];
84 |
85 | let peak_online = daily_stats
86 | .iter()
87 | .map(|s| s.peak_online)
88 | .max()
89 | .unwrap_or_default();
90 |
91 | let peak_max = daily_stats
92 | .iter()
93 | .map(|s| s.peak_max)
94 | .max()
95 | .unwrap_or_default();
96 |
97 | WeekStats {
98 | daily_stats,
99 | peak_online,
100 | peak_max,
101 | }
102 | }
103 | }
104 |
105 | /// A ping history entry.
106 | #[derive(Serialize, Deserialize, Default, Clone)]
107 | struct HistoryEntry {
108 | /// The number of players online at this time.
109 | pub online: i64,
110 | /// The max number of players allowed online at this time.
111 | pub max: i64,
112 | }
113 |
114 | impl HistoryEntry {
115 | /// Update this history entry with new data.
116 | fn update(&mut self, current_online: i64, current_max: i64) {
117 | self.online = current_online;
118 | self.max = current_max;
119 | }
120 | }
121 |
122 | /// Stats representing some range of time.
123 | #[repr(C)]
124 | #[derive(Default, Debug, Eq, PartialEq)]
125 | pub struct RangeStats {
126 | /// The average number of players online during this period.
127 | pub average_online: i64,
128 | /// The peak number of online players during this period.
129 | pub peak_online: i64,
130 | /// The peak max allowed online players during this period.
131 | pub peak_max: i64,
132 | }
133 |
134 | #[repr(C)]
135 | #[derive(Debug, Default)]
136 | pub struct WeekStats {
137 | /// Stats for the last eight days.
138 | pub daily_stats: [RangeStats; 8],
139 | /// The peak number of online players during this period.
140 | pub peak_online: i64,
141 | /// The peak max allowed online players during this period.
142 | pub peak_max: i64,
143 | }
144 |
145 | pub fn determine_week_stats(
146 | path: impl AsRef,
147 | current_online: i64,
148 | current_max: i64,
149 | ) -> Result {
150 | let path = path.as_ref();
151 |
152 | let now_local = Local::now();
153 | let now_utc = Utc::now();
154 |
155 | let mut data = if path.exists() {
156 | let data = fs::read(path)
157 | .with_context(|| format!("failed to read week stats file from {}", path.display()))?;
158 | // If parsing fails, we start fresh
159 | serde_json::from_slice(&data).unwrap_or_default()
160 | } else {
161 | PingStatsOnDisk::default()
162 | };
163 |
164 | data.trim_outdated(now_utc);
165 | data.add_data(now_utc, current_online, current_max);
166 |
167 | let week_stats = data.week_stats(
168 | now_local.timestamp(),
169 | now_local.num_seconds_from_midnight() as i64,
170 | );
171 |
172 | let updated_data =
173 | serde_json::to_string(&data).with_context(|| "failed to serialize week stats")?;
174 | fs::write(&path, &updated_data)
175 | .with_context(|| format!("failed to write week stats file to {}", path.display()))?;
176 |
177 | Ok(week_stats)
178 | }
179 |
180 | #[cfg(test)]
181 | mod tests {
182 | use chrono::TimeZone;
183 | use tempfile::TempDir;
184 |
185 | use super::*;
186 |
187 | fn moment_utc() -> DateTime {
188 | Utc.ymd(2021, 2, 14).and_hms(8, 12, 43)
189 | }
190 |
191 | fn test_data() -> PingStatsOnDisk {
192 | let mut data = PingStatsOnDisk::default();
193 | let moment = moment_utc();
194 |
195 | data.add_data(moment - Duration::days(12) - Duration::hours(3), 20, 70);
196 | data.add_data(moment - Duration::days(10) - Duration::hours(3), 20, 70);
197 | data.add_data(moment - Duration::days(10) + Duration::hours(4), 20, 40);
198 | data.add_data(moment - Duration::days(9), 20, 40);
199 |
200 | data.add_data(moment - Duration::days(6) - Duration::minutes(12), 13, 40);
201 | data.add_data(moment - Duration::days(6) + Duration::hours(5), 40, 40);
202 |
203 | data.add_data(moment - Duration::days(1) - Duration::hours(1), 4, 30);
204 | data.add_data(moment - Duration::days(1) - Duration::minutes(30), 3, 50);
205 | data.add_data(moment - Duration::days(1), 20, 30);
206 |
207 | data.add_data(moment - Duration::hours(2), 15, 30);
208 | data.add_data(moment - Duration::minutes(15), 5, 30);
209 | data.add_data(moment, 10, 30);
210 |
211 | data
212 | }
213 |
214 | #[test]
215 | fn trim_outdated() {
216 | let mut data = test_data();
217 | let original_length = data.ping_history.len();
218 | let moment = moment_utc();
219 |
220 | data.trim_outdated(moment);
221 |
222 | assert!(data.ping_history.len() < original_length);
223 |
224 | // These entries were outdated and should have been trimmed
225 | assert_eq!(
226 | data.ping_history
227 | .contains_key(&(moment - Duration::days(12) - Duration::hours(3)).timestamp()),
228 | false
229 | );
230 | assert_eq!(
231 | data.ping_history
232 | .contains_key(&(moment - Duration::days(10) - Duration::hours(3)).timestamp()),
233 | false
234 | );
235 |
236 | // These entries should have been kept
237 | assert_eq!(
238 | data.ping_history
239 | .contains_key(&(moment - Duration::days(10) + Duration::hours(4)).timestamp()),
240 | true
241 | );
242 | assert_eq!(
243 | data.ping_history
244 | .contains_key(&(moment - Duration::days(1)).timestamp()),
245 | true
246 | );
247 | assert_eq!(data.ping_history.contains_key(&moment.timestamp()), true);
248 | }
249 |
250 | #[test]
251 | fn week_stats() {
252 | let data = test_data();
253 | let moment = moment_utc();
254 |
255 | let week_stats = data.week_stats(
256 | moment.timestamp(),
257 | moment.num_seconds_from_midnight() as i64,
258 | );
259 |
260 | assert_eq!(week_stats.peak_online, 40);
261 | assert_eq!(week_stats.peak_max, 50);
262 |
263 | assert_eq!(
264 | week_stats.daily_stats,
265 | [
266 | RangeStats::default(),
267 | RangeStats {
268 | average_online: 26,
269 | peak_online: 40,
270 | peak_max: 40,
271 | },
272 | RangeStats::default(),
273 | RangeStats::default(),
274 | RangeStats::default(),
275 | RangeStats::default(),
276 | RangeStats {
277 | average_online: 9,
278 | peak_online: 20,
279 | peak_max: 50,
280 | },
281 | RangeStats {
282 | average_online: 10,
283 | peak_online: 15,
284 | peak_max: 30,
285 | },
286 | ]
287 | );
288 |
289 | let week_stats = data.week_stats(moment.timestamp(), 300);
290 |
291 | assert_eq!(week_stats.peak_online, 40);
292 | assert_eq!(week_stats.peak_max, 50);
293 |
294 | assert_eq!(
295 | week_stats.daily_stats,
296 | [
297 | RangeStats {
298 | average_online: 13,
299 | peak_online: 13,
300 | peak_max: 40
301 | },
302 | RangeStats {
303 | average_online: 40,
304 | peak_online: 40,
305 | peak_max: 40
306 | },
307 | RangeStats::default(),
308 | RangeStats::default(),
309 | RangeStats::default(),
310 | RangeStats {
311 | average_online: 3,
312 | peak_online: 4,
313 | peak_max: 50
314 | },
315 | RangeStats {
316 | average_online: 13,
317 | peak_online: 20,
318 | peak_max: 30
319 | },
320 | RangeStats {
321 | average_online: 10,
322 | peak_online: 10,
323 | peak_max: 30
324 | }
325 | ]
326 | );
327 | }
328 |
329 | // Test some aspects of interaction with the storage file
330 | #[test]
331 | fn file_handling() -> Result<(), anyhow::Error> {
332 | let tmp_dir = TempDir::new()?;
333 | let filepath = tmp_dir.path().join("week_stats");
334 |
335 | // File doesn't exist
336 | assert!(!filepath.exists());
337 |
338 | let _ = determine_week_stats(&filepath, 10, 40)?;
339 |
340 | // File exists now
341 | assert!(filepath.exists());
342 |
343 | let stats = determine_week_stats(&filepath, 20, 50)?;
344 | assert_eq!(stats.peak_online, 20);
345 |
346 | // Corrupt the file
347 | fs::write(&filepath, "getrekt")?;
348 |
349 | // Make sure we recover and start the file over
350 | let stats = determine_week_stats(&filepath, 10, 40)?;
351 | assert_eq!(stats.peak_online, 10);
352 |
353 | Ok(())
354 | }
355 | }
356 |
--------------------------------------------------------------------------------
/rust/src/lib.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | ffi::CStr,
3 | fs, mem,
4 | os::raw::{c_uint, c_ulonglong},
5 | panic,
6 | path::Path,
7 | time::Duration,
8 | };
9 | use std::{
10 | ffi::CString,
11 | os::raw::{c_char, c_longlong},
12 | };
13 |
14 | use anyhow::{anyhow, Context};
15 | use identicon::{make_base64_identicon, IdenticonInput};
16 | use mcping_common::{Player, Players, ProtocolType, Response, Version};
17 | use serde::{Deserialize, Serialize};
18 | use week_stats::{determine_week_stats, WeekStats};
19 |
20 | pub mod identicon;
21 | pub mod mcping_common;
22 | #[cfg(test)]
23 | mod tests;
24 | mod week_stats;
25 |
26 | /// The overall status response.
27 | #[repr(C)]
28 | #[derive(Debug)]
29 | pub enum ServerStatus {
30 | /// The server was online and we got a valid ping response.
31 | Online(OnlineResponse),
32 | /// The server was offline and couldn't be reached, but we've been able to
33 | /// get a valid response from it before.
34 | ///
35 | /// This struct contains cached data.
36 | Offline(OfflineResponse),
37 | /// The server was offline and couldn't be reached, and we've never gotten
38 | /// a response from it previously.
39 | ///
40 | /// This struct contains an error message string and also ends up being a
41 | /// bit of a catch-all for any errors that can occur. In the future it could
42 | /// be nice to break this up into finer-grained variants (one for a legit
43 | /// error and one for the common case of an invalid server address).
44 | Unreachable(UnreachableResponse),
45 | }
46 |
47 | impl std::fmt::Display for ServerStatus {
48 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 | match self {
50 | ServerStatus::Online(r) => f.write_fmt(format_args!("Online: {}", r)),
51 | ServerStatus::Offline(r) => f.write_fmt(format_args!("Offline: {}", r)),
52 | ServerStatus::Unreachable(_) => f.write_str("Unreachable"),
53 | }
54 | }
55 | }
56 |
57 | #[repr(C)]
58 | #[derive(Debug)]
59 | pub struct OnlineResponse {
60 | /// The data obtained from the server's ping response.
61 | pub mcinfo: McInfoRaw,
62 | /// Statistics about the server over the past week or so.
63 | pub week_stats: WeekStats,
64 | }
65 |
66 | impl std::fmt::Display for OnlineResponse {
67 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 | f.write_fmt(format_args!("{}", self.mcinfo))
69 | }
70 | }
71 |
72 | #[repr(C)]
73 | #[derive(Debug)]
74 | pub struct OfflineResponse {
75 | /// The server's favicon (a cached copy or generated favicon).
76 | pub favicon: FaviconRaw,
77 | /// Statistics about the server over the past week or so.
78 | pub week_stats: WeekStats,
79 | }
80 |
81 | impl std::fmt::Display for OfflineResponse {
82 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 | f.write_fmt(format_args!("{}", self.favicon))
84 | }
85 | }
86 |
87 | #[repr(C)]
88 | #[derive(Debug)]
89 | pub struct UnreachableResponse {
90 | /// An error string describing why the server wasn't reachable.
91 | pub error_string: *mut c_char,
92 | }
93 |
94 | /// Represents the format in which a favicon is cached on-disk.
95 | #[derive(Debug, Default, Serialize, Deserialize)]
96 | struct CachedFavicon {
97 | favicon: Option,
98 | }
99 |
100 | /// The server status response
101 | #[repr(C)]
102 | #[derive(Debug)]
103 | pub struct McInfoRaw {
104 | /// The protocol type of the successful ping.
105 | pub protocol_type: ProtocolType,
106 | /// Latency to the server
107 | pub latency: c_ulonglong,
108 | pub version: VersionRaw,
109 | /// Information about online players
110 | pub players: PlayersRaw,
111 | /// The server's description text
112 | pub description: *mut c_char,
113 | /// The server's favicon.
114 | pub favicon: FaviconRaw,
115 | }
116 |
117 | impl std::fmt::Display for McInfoRaw {
118 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 | f.debug_struct("McInfoRaw")
120 | .field("protocol_type", &self.protocol_type)
121 | .field("favicon", &format!("{}", self.favicon))
122 | .finish()
123 | }
124 | }
125 |
126 | impl McInfoRaw {
127 | /// Build this struct from a server's ping response data and some data to build
128 | /// and identicon from if necessary.
129 | fn new(status: Response, identicon_input: IdenticonInput, always_use_identicon: bool) -> Self {
130 | let description = CString::new(status.motd).unwrap();
131 | let favicon = FaviconRaw::from_data_and_options(
132 | status.favicon.as_deref(),
133 | identicon_input,
134 | always_use_identicon,
135 | );
136 |
137 | Self {
138 | protocol_type: status.protocol_type,
139 | latency: status.latency,
140 | version: VersionRaw::from(status.version),
141 | players: PlayersRaw::from(status.players),
142 | description: description.into_raw(),
143 | favicon,
144 | }
145 | }
146 | }
147 | /// Trim off the non-base64 part of the favicon string to make it easier to get
148 | /// an image in Swift land.
149 | fn process_favicon(favicon: &str) -> &str {
150 | favicon.trim_start_matches("data:image/png;base64,")
151 | }
152 |
153 | /// Information about the server's version
154 | #[repr(C)]
155 | #[derive(Debug)]
156 | pub struct VersionRaw {
157 | /// The name of the version the server is running
158 | ///
159 | /// In practice this comes in a large variety of different formats.
160 | pub name: *mut c_char,
161 | /// See https://wiki.vg/Protocol_version_numbers
162 | pub protocol: c_longlong,
163 | }
164 |
165 | impl From for VersionRaw {
166 | fn from(version: Version) -> Self {
167 | let name = CString::new(version.name).unwrap();
168 | Self {
169 | name: name.into_raw(),
170 | protocol: version.protocol.unwrap_or_default(),
171 | }
172 | }
173 | }
174 |
175 | #[repr(C)]
176 | #[derive(Debug)]
177 | pub struct PlayerRaw {
178 | /// The player's name
179 | pub name: *mut c_char,
180 | /// The player's UUID
181 | pub id: *mut c_char,
182 | }
183 |
184 | impl From for PlayerRaw {
185 | fn from(player: Player) -> Self {
186 | let name = CString::new(player.name).unwrap();
187 | let id = CString::new(player.id).unwrap();
188 | Self {
189 | name: name.into_raw(),
190 | id: id.into_raw(),
191 | }
192 | }
193 | }
194 |
195 | #[repr(C)]
196 | #[derive(Debug)]
197 | pub struct PlayersRaw {
198 | pub max: c_longlong,
199 | pub online: c_longlong,
200 | /// A preview of which players are online
201 | ///
202 | /// In practice servers often don't send this or use it for more advertising.
203 | /// This will be a null pointer if not present.
204 | pub sample: *mut PlayerRaw,
205 | pub sample_len: c_uint,
206 | }
207 |
208 | impl From for PlayersRaw {
209 | fn from(players: Players) -> Self {
210 | let (sample, sample_len) = if !players.sample.is_empty() {
211 | // Map into a vector of our repr(C) `Player` struct
212 | let mut sample = players
213 | .sample
214 | .into_iter()
215 | .map(PlayerRaw::from)
216 | .collect::>();
217 | sample.shrink_to_fit();
218 | assert!(sample.len() == sample.capacity());
219 | let ptr = sample.as_mut_ptr();
220 | let len = sample.len();
221 |
222 | mem::forget(sample);
223 |
224 | (ptr, len)
225 | } else {
226 | (std::ptr::null_mut(), 0)
227 | };
228 |
229 | Self {
230 | max: players.max,
231 | online: players.online,
232 | sample,
233 | sample_len: sample_len as _,
234 | }
235 | }
236 | }
237 |
238 | /// The server's favicon image.
239 | #[repr(C)]
240 | #[derive(Debug)]
241 | pub enum FaviconRaw {
242 | /// The server provided a favicon.
243 | ServerProvided(*mut c_char),
244 | /// We generated a favicon because the server didn't provide one.
245 | Generated(*mut c_char),
246 | /// There is no favicon image.
247 | NoFavicon,
248 | }
249 |
250 | impl std::fmt::Display for FaviconRaw {
251 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252 | match self {
253 | FaviconRaw::ServerProvided(_) => f.write_str("ServerProvided"),
254 | FaviconRaw::Generated(_) => f.write_str("Generated"),
255 | FaviconRaw::NoFavicon => f.write_str("NoFavicon"),
256 | }
257 | }
258 | }
259 |
260 | impl FaviconRaw {
261 | /// Picks the best favicon based on the given data and options.
262 | fn from_data_and_options(
263 | server_favicon: Option<&str>,
264 | identicon_input: IdenticonInput,
265 | always_use_identicon: bool,
266 | ) -> Self {
267 | let make_generated = || {
268 | make_base64_identicon(identicon_input)
269 | .and_then(|s| CString::new(s).ok())
270 | .map(|s| Self::Generated(s.into_raw()))
271 | .unwrap_or(Self::NoFavicon)
272 | };
273 |
274 | if always_use_identicon {
275 | // Always generate an identicon
276 | make_generated()
277 | } else {
278 | // Try to use the server favicon and fallback to a generated identicon
279 | server_favicon
280 | .map(process_favicon)
281 | .and_then(|s| CString::new(s).ok())
282 | .map(|s| Self::ServerProvided(s.into_raw()))
283 | .unwrap_or_else(make_generated)
284 | }
285 | }
286 | }
287 |
288 | /// Wrapper around `mcping_common::get_status`.
289 | ///
290 | /// This wrapper enables both offline and online testing.
291 | fn mcping_get_status_wrapper(
292 | address: String,
293 | timeout: Option,
294 | protocol_type: ProtocolType,
295 | ) -> Result {
296 | // Mock some responses for use during testing
297 | #[cfg(test)]
298 | {
299 | let mut response = Response {
300 | protocol_type: mcping_common::ProtocolType::Java,
301 | latency: 63,
302 | version: Version {
303 | name: "".to_string(),
304 | protocol: Some(187),
305 | },
306 | players: Players {
307 | max: 200,
308 | online: 103,
309 | sample: vec![],
310 | },
311 | motd: "".to_string(),
312 | favicon: None,
313 | };
314 |
315 | match address.as_str() {
316 | "test.server.basic" => return Ok(response),
317 | "test.server.full" => {
318 | response.version.name = "something".to_string();
319 | response.motd = "hello! description test".to_string();
320 | response.favicon = Some("abase64string".to_string());
321 | response.players.sample = vec![
322 | Player {
323 | id: "1".to_string(),
324 | name: "test1".to_string(),
325 | },
326 | Player {
327 | id: "2".to_string(),
328 | name: "test2".to_string(),
329 | },
330 | ];
331 |
332 | return Ok(response);
333 | }
334 | "test.server.dnslookupfails" => return Err(mcping::Error::DnsLookupFailed),
335 | _ => {
336 | // panic if online testing isn't enabled
337 | if cfg!(not(feature = "online")) {
338 | panic!("can only use mocked addresses while testing offline")
339 | }
340 | }
341 | }
342 | }
343 |
344 | mcping_common::get_status(address, timeout, protocol_type)
345 | }
346 |
347 | /// The rusty version of what we need to get done.
348 | ///
349 | /// The main logic of pinging a server and caching / processing the relevant data
350 | /// should be implemented here. It's perfectly okay to panic and return errors as
351 | /// needed.
352 | fn get_server_status_rust(
353 | address: &str,
354 | protocol_type: ProtocolType,
355 | always_use_identicon: bool,
356 | app_group_container: &str,
357 | ) -> Result {
358 | if address.is_empty() {
359 | // The following logic is meaningless if the server address is a blank
360 | // string
361 | return Err(anyhow!("empty server address"));
362 | }
363 |
364 | if app_group_container.is_empty() {
365 | // The following logic is meaningless if the app group container path
366 | // is blank
367 | return Err(anyhow!("empty app group container path"));
368 | }
369 |
370 | // Data for a specific server is stored within a folder specifically for
371 | // ping data, and within that a folder specifically for the address being
372 | // pinged.
373 | //
374 | // Note that the port will be a part of this address, so this will properly
375 | // handle multiple servers with the same IP / hostname but differing ports.
376 | // The server address is lowercased for optimal cache hits. It will not
377 | // handle unifying `mc.server.net` and `mc.server.net:25565`, though.
378 | let server_folder = Path::new(app_group_container)
379 | .join("mc_server_data")
380 | .join(format!(
381 | "{}_{}",
382 | address.to_lowercase().replace('.', "_").replace(':', "_"),
383 | protocol_type
384 | ));
385 | // Make sure the folders have been created
386 | fs::create_dir_all(&server_folder).with_context(|| {
387 | format!(
388 | "creating server folder(s): {}",
389 | server_folder.to_string_lossy()
390 | )
391 | })?;
392 |
393 | let cached_favicon_path = server_folder.join("cached_favicon");
394 | let week_stats_path = server_folder.join("week_stats");
395 | // Drop `server_folder` so we don't accidentally use it again
396 | drop(server_folder);
397 |
398 | // Prepare the data to create identicons with if necessary
399 | let identicon_input = IdenticonInput {
400 | protocol_type,
401 | address,
402 | };
403 |
404 | // A five-second timeout is used to avoid exceeding the amount of time our
405 | // widget process is given to run in.
406 | //
407 | // For example, this will end an attempt to ping "google.com" in about five
408 | // seconds; otherwise, we'd wait until the OS timed out the request, before
409 | // which time our process would likely end up being killed. This would
410 | // result in the widget being left in the placeholder view rather than
411 | // being updated with an error message.
412 | match mcping_get_status_wrapper(
413 | address.to_string(),
414 | Some(Duration::from_secs(5)),
415 | protocol_type,
416 | ) {
417 | Ok(status) => {
418 | // Cache the favicon
419 | let cached_favicon = CachedFavicon {
420 | favicon: status
421 | .favicon
422 | .as_deref()
423 | .map(process_favicon)
424 | .map(|s| s.to_owned()),
425 | };
426 | let cached_favicon = serde_json::to_string(&cached_favicon)?;
427 | fs::write(&cached_favicon_path, &cached_favicon).with_context(|| {
428 | format!(
429 | "writing cached favicon struct to {}",
430 | cached_favicon_path.to_string_lossy()
431 | )
432 | })?;
433 |
434 | // Handle week stats
435 | let week_stats =
436 | determine_week_stats(&week_stats_path, status.players.online, status.players.max)?;
437 |
438 | let mcinfo = McInfoRaw::new(status, identicon_input, always_use_identicon);
439 | Ok(ServerStatus::Online(OnlineResponse { mcinfo, week_stats }))
440 | }
441 | Err(e) => {
442 | if cached_favicon_path.exists() {
443 | let data = fs::read(&cached_favicon_path).with_context(|| {
444 | format!(
445 | "reading cached favicon data from {}",
446 | cached_favicon_path.to_string_lossy()
447 | )
448 | })?;
449 | let cached_favicon: CachedFavicon =
450 | serde_json::from_slice(&data).with_context(|| {
451 | format!(
452 | "deserializing cached favicon data: {}",
453 | String::from_utf8(data).unwrap_or_else(|_| "invalid utf-8".to_string())
454 | )
455 | })?;
456 |
457 | let favicon = FaviconRaw::from_data_and_options(
458 | cached_favicon.favicon.as_deref(),
459 | identicon_input,
460 | always_use_identicon,
461 | );
462 |
463 | // Handle week stats (server is offline, so just use zeroes)
464 | let week_stats = determine_week_stats(&week_stats_path, 0, 0)?;
465 |
466 | Ok(ServerStatus::Offline(OfflineResponse {
467 | favicon,
468 | week_stats,
469 | }))
470 | } else {
471 | Err(e.into())
472 | }
473 | }
474 | }
475 | }
476 |
477 | /// This function is responsible for catching any panics that could possibly
478 | /// occur.
479 | fn get_server_status_catch_panic(
480 | address: *const c_char,
481 | protocol_type: ProtocolType,
482 | always_use_identicon: bool,
483 | app_group_container: *const c_char,
484 | ) -> Result {
485 | match panic::catch_unwind(|| {
486 | if address.is_null() {
487 | return Err(anyhow!("server address pointer was null"));
488 | }
489 |
490 | let address = unsafe { CStr::from_ptr(address) };
491 | let address = address
492 | .to_str()
493 | .with_context(|| "converting server address from cstr to rust str")?;
494 |
495 | if app_group_container.is_null() {
496 | return Err(anyhow!("app group container pointer was null"));
497 | }
498 |
499 | let app_group_container = unsafe { CStr::from_ptr(app_group_container) };
500 | let app_group_container = app_group_container
501 | .to_str()
502 | .with_context(|| "converting app group container from cstr to rust str")?;
503 |
504 | get_server_status_rust(
505 | address,
506 | protocol_type,
507 | always_use_identicon,
508 | app_group_container,
509 | )
510 | }) {
511 | Ok(result) => Ok(result?),
512 | Err(e) => Err(anyhow!("a panic occurred in rust code: {:?}", e)),
513 | }
514 | }
515 |
516 | /// Ping a Minecraft server at the given `address`, working with data stored in
517 | /// the given `app_group_container`.
518 | ///
519 | /// # Safety
520 | ///
521 | /// The provided pointers must point to valid cstrings.
522 | #[no_mangle]
523 | pub unsafe extern "C" fn get_server_status(
524 | address: *const c_char,
525 | protocol_type: ProtocolType,
526 | always_use_identicon: bool,
527 | app_group_container: *const c_char,
528 | ) -> ServerStatus {
529 | match get_server_status_catch_panic(
530 | address,
531 | protocol_type,
532 | always_use_identicon,
533 | app_group_container,
534 | ) {
535 | Ok(status) => status,
536 | Err(e) => {
537 | // Note that we need to be careful not to panic here
538 | let error_string = format!("failed to ping server: {}", e);
539 | let error_string = CString::new(error_string).unwrap_or_default();
540 |
541 | ServerStatus::Unreachable(UnreachableResponse {
542 | error_string: error_string.into_raw(),
543 | })
544 | }
545 | }
546 | }
547 |
548 | #[no_mangle]
549 | pub extern "C" fn free_status_response(response: ServerStatus) {
550 | match response {
551 | ServerStatus::Online(OnlineResponse { mcinfo, week_stats }) => {
552 | free_mcinfo(mcinfo);
553 | // `WeekStats` doesn't have any heap-allocated stuff, so we don't need
554 | // to free it
555 | drop(week_stats);
556 | }
557 | ServerStatus::Offline(OfflineResponse {
558 | favicon,
559 | week_stats,
560 | }) => {
561 | free_favicon(favicon);
562 | // `WeekStats` doesn't have any heap-allocated stuff, so we don't need
563 | // to free it
564 | drop(week_stats);
565 | }
566 | ServerStatus::Unreachable(UnreachableResponse { error_string }) => {
567 | if !error_string.is_null() {
568 | let _ = unsafe { CString::from_raw(error_string) };
569 | }
570 | }
571 | }
572 | }
573 |
574 | #[no_mangle]
575 | pub extern "C" fn free_mcinfo(mcinfo: McInfoRaw) {
576 | let _ = unsafe { CString::from_raw(mcinfo.description) };
577 |
578 | free_favicon(mcinfo.favicon);
579 |
580 | let _ = unsafe { CString::from_raw(mcinfo.version.name) };
581 |
582 | if !mcinfo.players.sample.is_null() {
583 | let sample = unsafe {
584 | Vec::from_raw_parts(
585 | mcinfo.players.sample,
586 | mcinfo.players.sample_len as _,
587 | mcinfo.players.sample_len as _,
588 | )
589 | };
590 |
591 | for player in sample.iter() {
592 | let _ = unsafe { CString::from_raw(player.name) };
593 | let _ = unsafe { CString::from_raw(player.id) };
594 | }
595 | }
596 | }
597 |
598 | #[no_mangle]
599 | pub extern "C" fn free_favicon(favicon: FaviconRaw) {
600 | match favicon {
601 | FaviconRaw::ServerProvided(p) | FaviconRaw::Generated(p) => {
602 | if !p.is_null() {
603 | let _ = unsafe { CString::from_raw(p) };
604 | }
605 | }
606 | FaviconRaw::NoFavicon => {}
607 | }
608 | }
609 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | [[package]]
4 | name = "adler"
5 | version = "1.0.2"
6 | source = "registry+https://github.com/rust-lang/crates.io-index"
7 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
8 |
9 | [[package]]
10 | name = "adler32"
11 | version = "1.2.0"
12 | source = "registry+https://github.com/rust-lang/crates.io-index"
13 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
14 |
15 | [[package]]
16 | name = "anyhow"
17 | version = "1.0.40"
18 | source = "registry+https://github.com/rust-lang/crates.io-index"
19 | checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
20 |
21 | [[package]]
22 | name = "approx"
23 | version = "0.3.2"
24 | source = "registry+https://github.com/rust-lang/crates.io-index"
25 | checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3"
26 | dependencies = [
27 | "num-traits",
28 | ]
29 |
30 | [[package]]
31 | name = "async-trait"
32 | version = "0.1.48"
33 | source = "registry+https://github.com/rust-lang/crates.io-index"
34 | checksum = "36ea56748e10732c49404c153638a15ec3d6211ec5ff35d9bb20e13b93576adf"
35 | dependencies = [
36 | "proc-macro2",
37 | "quote",
38 | "syn",
39 | ]
40 |
41 | [[package]]
42 | name = "autocfg"
43 | version = "1.0.1"
44 | source = "registry+https://github.com/rust-lang/crates.io-index"
45 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
46 |
47 | [[package]]
48 | name = "base64"
49 | version = "0.13.0"
50 | source = "registry+https://github.com/rust-lang/crates.io-index"
51 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
52 |
53 | [[package]]
54 | name = "bitflags"
55 | version = "1.2.1"
56 | source = "registry+https://github.com/rust-lang/crates.io-index"
57 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
58 |
59 | [[package]]
60 | name = "block-buffer"
61 | version = "0.9.0"
62 | source = "registry+https://github.com/rust-lang/crates.io-index"
63 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
64 | dependencies = [
65 | "generic-array",
66 | ]
67 |
68 | [[package]]
69 | name = "bytemuck"
70 | version = "1.5.1"
71 | source = "registry+https://github.com/rust-lang/crates.io-index"
72 | checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58"
73 |
74 | [[package]]
75 | name = "byteorder"
76 | version = "1.4.3"
77 | source = "registry+https://github.com/rust-lang/crates.io-index"
78 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
79 |
80 | [[package]]
81 | name = "cfg-if"
82 | version = "1.0.0"
83 | source = "registry+https://github.com/rust-lang/crates.io-index"
84 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
85 |
86 | [[package]]
87 | name = "chrono"
88 | version = "0.4.19"
89 | source = "registry+https://github.com/rust-lang/crates.io-index"
90 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
91 | dependencies = [
92 | "libc",
93 | "num-integer",
94 | "num-traits",
95 | "serde",
96 | "time",
97 | "winapi",
98 | ]
99 |
100 | [[package]]
101 | name = "color_quant"
102 | version = "1.1.0"
103 | source = "registry+https://github.com/rust-lang/crates.io-index"
104 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
105 |
106 | [[package]]
107 | name = "cpuid-bool"
108 | version = "0.1.2"
109 | source = "registry+https://github.com/rust-lang/crates.io-index"
110 | checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
111 |
112 | [[package]]
113 | name = "crc32fast"
114 | version = "1.2.1"
115 | source = "registry+https://github.com/rust-lang/crates.io-index"
116 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
117 | dependencies = [
118 | "cfg-if",
119 | ]
120 |
121 | [[package]]
122 | name = "crossbeam-channel"
123 | version = "0.5.1"
124 | source = "registry+https://github.com/rust-lang/crates.io-index"
125 | checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
126 | dependencies = [
127 | "cfg-if",
128 | "crossbeam-utils",
129 | ]
130 |
131 | [[package]]
132 | name = "crossbeam-deque"
133 | version = "0.8.0"
134 | source = "registry+https://github.com/rust-lang/crates.io-index"
135 | checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
136 | dependencies = [
137 | "cfg-if",
138 | "crossbeam-epoch",
139 | "crossbeam-utils",
140 | ]
141 |
142 | [[package]]
143 | name = "crossbeam-epoch"
144 | version = "0.9.3"
145 | source = "registry+https://github.com/rust-lang/crates.io-index"
146 | checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12"
147 | dependencies = [
148 | "cfg-if",
149 | "crossbeam-utils",
150 | "lazy_static",
151 | "memoffset",
152 | "scopeguard",
153 | ]
154 |
155 | [[package]]
156 | name = "crossbeam-utils"
157 | version = "0.8.3"
158 | source = "registry+https://github.com/rust-lang/crates.io-index"
159 | checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
160 | dependencies = [
161 | "autocfg",
162 | "cfg-if",
163 | "lazy_static",
164 | ]
165 |
166 | [[package]]
167 | name = "data-encoding"
168 | version = "2.3.2"
169 | source = "registry+https://github.com/rust-lang/crates.io-index"
170 | checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
171 |
172 | [[package]]
173 | name = "deflate"
174 | version = "0.8.6"
175 | source = "registry+https://github.com/rust-lang/crates.io-index"
176 | checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
177 | dependencies = [
178 | "adler32",
179 | "byteorder",
180 | ]
181 |
182 | [[package]]
183 | name = "digest"
184 | version = "0.9.0"
185 | source = "registry+https://github.com/rust-lang/crates.io-index"
186 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
187 | dependencies = [
188 | "generic-array",
189 | ]
190 |
191 | [[package]]
192 | name = "dissimilar"
193 | version = "1.0.2"
194 | source = "registry+https://github.com/rust-lang/crates.io-index"
195 | checksum = "fc4b29f4b9bb94bf267d57269fd0706d343a160937108e9619fe380645428abb"
196 |
197 | [[package]]
198 | name = "either"
199 | version = "1.6.1"
200 | source = "registry+https://github.com/rust-lang/crates.io-index"
201 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
202 |
203 | [[package]]
204 | name = "enum-as-inner"
205 | version = "0.3.3"
206 | source = "registry+https://github.com/rust-lang/crates.io-index"
207 | checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595"
208 | dependencies = [
209 | "heck",
210 | "proc-macro2",
211 | "quote",
212 | "syn",
213 | ]
214 |
215 | [[package]]
216 | name = "expect-test"
217 | version = "1.1.0"
218 | source = "registry+https://github.com/rust-lang/crates.io-index"
219 | checksum = "2300477aab3a378f2ca00a4fbd4dc713654ab7ed790e4017493cb33656280633"
220 | dependencies = [
221 | "dissimilar",
222 | "once_cell",
223 | ]
224 |
225 | [[package]]
226 | name = "form_urlencoded"
227 | version = "1.0.1"
228 | source = "registry+https://github.com/rust-lang/crates.io-index"
229 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
230 | dependencies = [
231 | "matches",
232 | "percent-encoding",
233 | ]
234 |
235 | [[package]]
236 | name = "futures-channel"
237 | version = "0.3.14"
238 | source = "registry+https://github.com/rust-lang/crates.io-index"
239 | checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25"
240 | dependencies = [
241 | "futures-core",
242 | ]
243 |
244 | [[package]]
245 | name = "futures-core"
246 | version = "0.3.14"
247 | source = "registry+https://github.com/rust-lang/crates.io-index"
248 | checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815"
249 |
250 | [[package]]
251 | name = "futures-io"
252 | version = "0.3.14"
253 | source = "registry+https://github.com/rust-lang/crates.io-index"
254 | checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04"
255 |
256 | [[package]]
257 | name = "futures-task"
258 | version = "0.3.14"
259 | source = "registry+https://github.com/rust-lang/crates.io-index"
260 | checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc"
261 |
262 | [[package]]
263 | name = "futures-util"
264 | version = "0.3.14"
265 | source = "registry+https://github.com/rust-lang/crates.io-index"
266 | checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025"
267 | dependencies = [
268 | "futures-core",
269 | "futures-task",
270 | "pin-project-lite",
271 | "pin-utils",
272 | "slab",
273 | ]
274 |
275 | [[package]]
276 | name = "generic-array"
277 | version = "0.14.4"
278 | source = "registry+https://github.com/rust-lang/crates.io-index"
279 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
280 | dependencies = [
281 | "typenum",
282 | "version_check",
283 | ]
284 |
285 | [[package]]
286 | name = "getrandom"
287 | version = "0.1.16"
288 | source = "registry+https://github.com/rust-lang/crates.io-index"
289 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
290 | dependencies = [
291 | "cfg-if",
292 | "libc",
293 | "wasi 0.9.0+wasi-snapshot-preview1",
294 | ]
295 |
296 | [[package]]
297 | name = "getrandom"
298 | version = "0.2.2"
299 | source = "registry+https://github.com/rust-lang/crates.io-index"
300 | checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
301 | dependencies = [
302 | "cfg-if",
303 | "libc",
304 | "wasi 0.10.2+wasi-snapshot-preview1",
305 | ]
306 |
307 | [[package]]
308 | name = "gif"
309 | version = "0.11.2"
310 | source = "registry+https://github.com/rust-lang/crates.io-index"
311 | checksum = "5a668f699973d0f573d15749b7002a9ac9e1f9c6b220e7b165601334c173d8de"
312 | dependencies = [
313 | "color_quant",
314 | "weezl",
315 | ]
316 |
317 | [[package]]
318 | name = "heck"
319 | version = "0.3.2"
320 | source = "registry+https://github.com/rust-lang/crates.io-index"
321 | checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
322 | dependencies = [
323 | "unicode-segmentation",
324 | ]
325 |
326 | [[package]]
327 | name = "hermit-abi"
328 | version = "0.1.18"
329 | source = "registry+https://github.com/rust-lang/crates.io-index"
330 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
331 | dependencies = [
332 | "libc",
333 | ]
334 |
335 | [[package]]
336 | name = "hostname"
337 | version = "0.3.1"
338 | source = "registry+https://github.com/rust-lang/crates.io-index"
339 | checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
340 | dependencies = [
341 | "libc",
342 | "match_cfg",
343 | "winapi",
344 | ]
345 |
346 | [[package]]
347 | name = "identicon-rs"
348 | version = "2.0.2"
349 | source = "registry+https://github.com/rust-lang/crates.io-index"
350 | checksum = "cee84d4fca7be7c46a45ed4d26068bd4eeb18a853242b6080ee46cdb63ce4367"
351 | dependencies = [
352 | "image",
353 | "palette",
354 | "sha2",
355 | ]
356 |
357 | [[package]]
358 | name = "idna"
359 | version = "0.2.2"
360 | source = "registry+https://github.com/rust-lang/crates.io-index"
361 | checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21"
362 | dependencies = [
363 | "matches",
364 | "unicode-bidi",
365 | "unicode-normalization",
366 | ]
367 |
368 | [[package]]
369 | name = "image"
370 | version = "0.23.14"
371 | source = "registry+https://github.com/rust-lang/crates.io-index"
372 | checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
373 | dependencies = [
374 | "bytemuck",
375 | "byteorder",
376 | "color_quant",
377 | "gif",
378 | "jpeg-decoder",
379 | "num-iter",
380 | "num-rational",
381 | "num-traits",
382 | "png",
383 | "scoped_threadpool",
384 | "tiff",
385 | ]
386 |
387 | [[package]]
388 | name = "instant"
389 | version = "0.1.9"
390 | source = "registry+https://github.com/rust-lang/crates.io-index"
391 | checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
392 | dependencies = [
393 | "cfg-if",
394 | ]
395 |
396 | [[package]]
397 | name = "ipconfig"
398 | version = "0.2.2"
399 | source = "registry+https://github.com/rust-lang/crates.io-index"
400 | checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7"
401 | dependencies = [
402 | "socket2",
403 | "widestring",
404 | "winapi",
405 | "winreg",
406 | ]
407 |
408 | [[package]]
409 | name = "ipnet"
410 | version = "2.3.0"
411 | source = "registry+https://github.com/rust-lang/crates.io-index"
412 | checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135"
413 |
414 | [[package]]
415 | name = "itoa"
416 | version = "0.4.7"
417 | source = "registry+https://github.com/rust-lang/crates.io-index"
418 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
419 |
420 | [[package]]
421 | name = "jpeg-decoder"
422 | version = "0.1.22"
423 | source = "registry+https://github.com/rust-lang/crates.io-index"
424 | checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
425 | dependencies = [
426 | "rayon",
427 | ]
428 |
429 | [[package]]
430 | name = "lazy_static"
431 | version = "1.4.0"
432 | source = "registry+https://github.com/rust-lang/crates.io-index"
433 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
434 |
435 | [[package]]
436 | name = "libc"
437 | version = "0.2.93"
438 | source = "registry+https://github.com/rust-lang/crates.io-index"
439 | checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
440 |
441 | [[package]]
442 | name = "linked-hash-map"
443 | version = "0.5.4"
444 | source = "registry+https://github.com/rust-lang/crates.io-index"
445 | checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
446 |
447 | [[package]]
448 | name = "lock_api"
449 | version = "0.4.3"
450 | source = "registry+https://github.com/rust-lang/crates.io-index"
451 | checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176"
452 | dependencies = [
453 | "scopeguard",
454 | ]
455 |
456 | [[package]]
457 | name = "log"
458 | version = "0.4.14"
459 | source = "registry+https://github.com/rust-lang/crates.io-index"
460 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
461 | dependencies = [
462 | "cfg-if",
463 | ]
464 |
465 | [[package]]
466 | name = "lru-cache"
467 | version = "0.1.2"
468 | source = "registry+https://github.com/rust-lang/crates.io-index"
469 | checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
470 | dependencies = [
471 | "linked-hash-map",
472 | ]
473 |
474 | [[package]]
475 | name = "match_cfg"
476 | version = "0.1.0"
477 | source = "registry+https://github.com/rust-lang/crates.io-index"
478 | checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
479 |
480 | [[package]]
481 | name = "matches"
482 | version = "0.1.8"
483 | source = "registry+https://github.com/rust-lang/crates.io-index"
484 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
485 |
486 | [[package]]
487 | name = "mcping"
488 | version = "0.2.0"
489 | source = "git+https://github.com/Scetch/mcping.git#7e74d2220a61557b5514768edb7da42d2758e143"
490 | dependencies = [
491 | "byteorder",
492 | "rand 0.8.3",
493 | "serde",
494 | "serde_json",
495 | "thiserror",
496 | "trust-dns-resolver",
497 | ]
498 |
499 | [[package]]
500 | name = "memoffset"
501 | version = "0.6.3"
502 | source = "registry+https://github.com/rust-lang/crates.io-index"
503 | checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d"
504 | dependencies = [
505 | "autocfg",
506 | ]
507 |
508 | [[package]]
509 | name = "minecraft-status"
510 | version = "0.1.0"
511 | dependencies = [
512 | "anyhow",
513 | "base64",
514 | "chrono",
515 | "expect-test",
516 | "identicon-rs",
517 | "image",
518 | "mcping",
519 | "serde",
520 | "serde_json",
521 | "tempfile",
522 | ]
523 |
524 | [[package]]
525 | name = "miniz_oxide"
526 | version = "0.3.7"
527 | source = "registry+https://github.com/rust-lang/crates.io-index"
528 | checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
529 | dependencies = [
530 | "adler32",
531 | ]
532 |
533 | [[package]]
534 | name = "miniz_oxide"
535 | version = "0.4.4"
536 | source = "registry+https://github.com/rust-lang/crates.io-index"
537 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
538 | dependencies = [
539 | "adler",
540 | "autocfg",
541 | ]
542 |
543 | [[package]]
544 | name = "mio"
545 | version = "0.7.11"
546 | source = "registry+https://github.com/rust-lang/crates.io-index"
547 | checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956"
548 | dependencies = [
549 | "libc",
550 | "log",
551 | "miow",
552 | "ntapi",
553 | "winapi",
554 | ]
555 |
556 | [[package]]
557 | name = "miow"
558 | version = "0.3.7"
559 | source = "registry+https://github.com/rust-lang/crates.io-index"
560 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
561 | dependencies = [
562 | "winapi",
563 | ]
564 |
565 | [[package]]
566 | name = "ntapi"
567 | version = "0.3.6"
568 | source = "registry+https://github.com/rust-lang/crates.io-index"
569 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
570 | dependencies = [
571 | "winapi",
572 | ]
573 |
574 | [[package]]
575 | name = "num-integer"
576 | version = "0.1.44"
577 | source = "registry+https://github.com/rust-lang/crates.io-index"
578 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
579 | dependencies = [
580 | "autocfg",
581 | "num-traits",
582 | ]
583 |
584 | [[package]]
585 | name = "num-iter"
586 | version = "0.1.42"
587 | source = "registry+https://github.com/rust-lang/crates.io-index"
588 | checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
589 | dependencies = [
590 | "autocfg",
591 | "num-integer",
592 | "num-traits",
593 | ]
594 |
595 | [[package]]
596 | name = "num-rational"
597 | version = "0.3.2"
598 | source = "registry+https://github.com/rust-lang/crates.io-index"
599 | checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
600 | dependencies = [
601 | "autocfg",
602 | "num-integer",
603 | "num-traits",
604 | ]
605 |
606 | [[package]]
607 | name = "num-traits"
608 | version = "0.2.14"
609 | source = "registry+https://github.com/rust-lang/crates.io-index"
610 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
611 | dependencies = [
612 | "autocfg",
613 | ]
614 |
615 | [[package]]
616 | name = "num_cpus"
617 | version = "1.13.0"
618 | source = "registry+https://github.com/rust-lang/crates.io-index"
619 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
620 | dependencies = [
621 | "hermit-abi",
622 | "libc",
623 | ]
624 |
625 | [[package]]
626 | name = "once_cell"
627 | version = "1.7.2"
628 | source = "registry+https://github.com/rust-lang/crates.io-index"
629 | checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
630 |
631 | [[package]]
632 | name = "opaque-debug"
633 | version = "0.3.0"
634 | source = "registry+https://github.com/rust-lang/crates.io-index"
635 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
636 |
637 | [[package]]
638 | name = "palette"
639 | version = "0.5.0"
640 | source = "registry+https://github.com/rust-lang/crates.io-index"
641 | checksum = "a05c0334468e62a4dfbda34b29110aa7d70d58c7fdb2c9857b5874dd9827cc59"
642 | dependencies = [
643 | "approx",
644 | "num-traits",
645 | "palette_derive",
646 | "phf",
647 | "phf_codegen",
648 | ]
649 |
650 | [[package]]
651 | name = "palette_derive"
652 | version = "0.5.0"
653 | source = "registry+https://github.com/rust-lang/crates.io-index"
654 | checksum = "0b4b5f600e60dd3a147fb57b4547033d382d1979eb087af310e91cb45a63b1f4"
655 | dependencies = [
656 | "proc-macro2",
657 | "quote",
658 | "syn",
659 | ]
660 |
661 | [[package]]
662 | name = "parking_lot"
663 | version = "0.11.1"
664 | source = "registry+https://github.com/rust-lang/crates.io-index"
665 | checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
666 | dependencies = [
667 | "instant",
668 | "lock_api",
669 | "parking_lot_core",
670 | ]
671 |
672 | [[package]]
673 | name = "parking_lot_core"
674 | version = "0.8.3"
675 | source = "registry+https://github.com/rust-lang/crates.io-index"
676 | checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
677 | dependencies = [
678 | "cfg-if",
679 | "instant",
680 | "libc",
681 | "redox_syscall",
682 | "smallvec",
683 | "winapi",
684 | ]
685 |
686 | [[package]]
687 | name = "percent-encoding"
688 | version = "2.1.0"
689 | source = "registry+https://github.com/rust-lang/crates.io-index"
690 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
691 |
692 | [[package]]
693 | name = "phf"
694 | version = "0.8.0"
695 | source = "registry+https://github.com/rust-lang/crates.io-index"
696 | checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
697 | dependencies = [
698 | "phf_shared",
699 | ]
700 |
701 | [[package]]
702 | name = "phf_codegen"
703 | version = "0.8.0"
704 | source = "registry+https://github.com/rust-lang/crates.io-index"
705 | checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
706 | dependencies = [
707 | "phf_generator",
708 | "phf_shared",
709 | ]
710 |
711 | [[package]]
712 | name = "phf_generator"
713 | version = "0.8.0"
714 | source = "registry+https://github.com/rust-lang/crates.io-index"
715 | checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
716 | dependencies = [
717 | "phf_shared",
718 | "rand 0.7.3",
719 | ]
720 |
721 | [[package]]
722 | name = "phf_shared"
723 | version = "0.8.0"
724 | source = "registry+https://github.com/rust-lang/crates.io-index"
725 | checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
726 | dependencies = [
727 | "siphasher",
728 | ]
729 |
730 | [[package]]
731 | name = "pin-project-lite"
732 | version = "0.2.6"
733 | source = "registry+https://github.com/rust-lang/crates.io-index"
734 | checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
735 |
736 | [[package]]
737 | name = "pin-utils"
738 | version = "0.1.0"
739 | source = "registry+https://github.com/rust-lang/crates.io-index"
740 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
741 |
742 | [[package]]
743 | name = "png"
744 | version = "0.16.8"
745 | source = "registry+https://github.com/rust-lang/crates.io-index"
746 | checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
747 | dependencies = [
748 | "bitflags",
749 | "crc32fast",
750 | "deflate",
751 | "miniz_oxide 0.3.7",
752 | ]
753 |
754 | [[package]]
755 | name = "ppv-lite86"
756 | version = "0.2.10"
757 | source = "registry+https://github.com/rust-lang/crates.io-index"
758 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
759 |
760 | [[package]]
761 | name = "proc-macro2"
762 | version = "1.0.26"
763 | source = "registry+https://github.com/rust-lang/crates.io-index"
764 | checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
765 | dependencies = [
766 | "unicode-xid",
767 | ]
768 |
769 | [[package]]
770 | name = "quick-error"
771 | version = "1.2.3"
772 | source = "registry+https://github.com/rust-lang/crates.io-index"
773 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
774 |
775 | [[package]]
776 | name = "quote"
777 | version = "1.0.9"
778 | source = "registry+https://github.com/rust-lang/crates.io-index"
779 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
780 | dependencies = [
781 | "proc-macro2",
782 | ]
783 |
784 | [[package]]
785 | name = "rand"
786 | version = "0.7.3"
787 | source = "registry+https://github.com/rust-lang/crates.io-index"
788 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
789 | dependencies = [
790 | "getrandom 0.1.16",
791 | "libc",
792 | "rand_chacha 0.2.2",
793 | "rand_core 0.5.1",
794 | "rand_hc 0.2.0",
795 | "rand_pcg",
796 | ]
797 |
798 | [[package]]
799 | name = "rand"
800 | version = "0.8.3"
801 | source = "registry+https://github.com/rust-lang/crates.io-index"
802 | checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
803 | dependencies = [
804 | "libc",
805 | "rand_chacha 0.3.0",
806 | "rand_core 0.6.2",
807 | "rand_hc 0.3.0",
808 | ]
809 |
810 | [[package]]
811 | name = "rand_chacha"
812 | version = "0.2.2"
813 | source = "registry+https://github.com/rust-lang/crates.io-index"
814 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
815 | dependencies = [
816 | "ppv-lite86",
817 | "rand_core 0.5.1",
818 | ]
819 |
820 | [[package]]
821 | name = "rand_chacha"
822 | version = "0.3.0"
823 | source = "registry+https://github.com/rust-lang/crates.io-index"
824 | checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
825 | dependencies = [
826 | "ppv-lite86",
827 | "rand_core 0.6.2",
828 | ]
829 |
830 | [[package]]
831 | name = "rand_core"
832 | version = "0.5.1"
833 | source = "registry+https://github.com/rust-lang/crates.io-index"
834 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
835 | dependencies = [
836 | "getrandom 0.1.16",
837 | ]
838 |
839 | [[package]]
840 | name = "rand_core"
841 | version = "0.6.2"
842 | source = "registry+https://github.com/rust-lang/crates.io-index"
843 | checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
844 | dependencies = [
845 | "getrandom 0.2.2",
846 | ]
847 |
848 | [[package]]
849 | name = "rand_hc"
850 | version = "0.2.0"
851 | source = "registry+https://github.com/rust-lang/crates.io-index"
852 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
853 | dependencies = [
854 | "rand_core 0.5.1",
855 | ]
856 |
857 | [[package]]
858 | name = "rand_hc"
859 | version = "0.3.0"
860 | source = "registry+https://github.com/rust-lang/crates.io-index"
861 | checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
862 | dependencies = [
863 | "rand_core 0.6.2",
864 | ]
865 |
866 | [[package]]
867 | name = "rand_pcg"
868 | version = "0.2.1"
869 | source = "registry+https://github.com/rust-lang/crates.io-index"
870 | checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
871 | dependencies = [
872 | "rand_core 0.5.1",
873 | ]
874 |
875 | [[package]]
876 | name = "rayon"
877 | version = "1.5.0"
878 | source = "registry+https://github.com/rust-lang/crates.io-index"
879 | checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
880 | dependencies = [
881 | "autocfg",
882 | "crossbeam-deque",
883 | "either",
884 | "rayon-core",
885 | ]
886 |
887 | [[package]]
888 | name = "rayon-core"
889 | version = "1.9.0"
890 | source = "registry+https://github.com/rust-lang/crates.io-index"
891 | checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
892 | dependencies = [
893 | "crossbeam-channel",
894 | "crossbeam-deque",
895 | "crossbeam-utils",
896 | "lazy_static",
897 | "num_cpus",
898 | ]
899 |
900 | [[package]]
901 | name = "redox_syscall"
902 | version = "0.2.6"
903 | source = "registry+https://github.com/rust-lang/crates.io-index"
904 | checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041"
905 | dependencies = [
906 | "bitflags",
907 | ]
908 |
909 | [[package]]
910 | name = "remove_dir_all"
911 | version = "0.5.3"
912 | source = "registry+https://github.com/rust-lang/crates.io-index"
913 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
914 | dependencies = [
915 | "winapi",
916 | ]
917 |
918 | [[package]]
919 | name = "resolv-conf"
920 | version = "0.7.0"
921 | source = "registry+https://github.com/rust-lang/crates.io-index"
922 | checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
923 | dependencies = [
924 | "hostname",
925 | "quick-error",
926 | ]
927 |
928 | [[package]]
929 | name = "ryu"
930 | version = "1.0.5"
931 | source = "registry+https://github.com/rust-lang/crates.io-index"
932 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
933 |
934 | [[package]]
935 | name = "scoped_threadpool"
936 | version = "0.1.9"
937 | source = "registry+https://github.com/rust-lang/crates.io-index"
938 | checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
939 |
940 | [[package]]
941 | name = "scopeguard"
942 | version = "1.1.0"
943 | source = "registry+https://github.com/rust-lang/crates.io-index"
944 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
945 |
946 | [[package]]
947 | name = "serde"
948 | version = "1.0.125"
949 | source = "registry+https://github.com/rust-lang/crates.io-index"
950 | checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
951 | dependencies = [
952 | "serde_derive",
953 | ]
954 |
955 | [[package]]
956 | name = "serde_derive"
957 | version = "1.0.125"
958 | source = "registry+https://github.com/rust-lang/crates.io-index"
959 | checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
960 | dependencies = [
961 | "proc-macro2",
962 | "quote",
963 | "syn",
964 | ]
965 |
966 | [[package]]
967 | name = "serde_json"
968 | version = "1.0.64"
969 | source = "registry+https://github.com/rust-lang/crates.io-index"
970 | checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
971 | dependencies = [
972 | "itoa",
973 | "ryu",
974 | "serde",
975 | ]
976 |
977 | [[package]]
978 | name = "sha2"
979 | version = "0.9.3"
980 | source = "registry+https://github.com/rust-lang/crates.io-index"
981 | checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de"
982 | dependencies = [
983 | "block-buffer",
984 | "cfg-if",
985 | "cpuid-bool",
986 | "digest",
987 | "opaque-debug",
988 | ]
989 |
990 | [[package]]
991 | name = "siphasher"
992 | version = "0.3.5"
993 | source = "registry+https://github.com/rust-lang/crates.io-index"
994 | checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27"
995 |
996 | [[package]]
997 | name = "slab"
998 | version = "0.4.2"
999 | source = "registry+https://github.com/rust-lang/crates.io-index"
1000 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
1001 |
1002 | [[package]]
1003 | name = "smallvec"
1004 | version = "1.6.1"
1005 | source = "registry+https://github.com/rust-lang/crates.io-index"
1006 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
1007 |
1008 | [[package]]
1009 | name = "socket2"
1010 | version = "0.3.19"
1011 | source = "registry+https://github.com/rust-lang/crates.io-index"
1012 | checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
1013 | dependencies = [
1014 | "cfg-if",
1015 | "libc",
1016 | "winapi",
1017 | ]
1018 |
1019 | [[package]]
1020 | name = "syn"
1021 | version = "1.0.69"
1022 | source = "registry+https://github.com/rust-lang/crates.io-index"
1023 | checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb"
1024 | dependencies = [
1025 | "proc-macro2",
1026 | "quote",
1027 | "unicode-xid",
1028 | ]
1029 |
1030 | [[package]]
1031 | name = "tempfile"
1032 | version = "3.2.0"
1033 | source = "registry+https://github.com/rust-lang/crates.io-index"
1034 | checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
1035 | dependencies = [
1036 | "cfg-if",
1037 | "libc",
1038 | "rand 0.8.3",
1039 | "redox_syscall",
1040 | "remove_dir_all",
1041 | "winapi",
1042 | ]
1043 |
1044 | [[package]]
1045 | name = "thiserror"
1046 | version = "1.0.24"
1047 | source = "registry+https://github.com/rust-lang/crates.io-index"
1048 | checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
1049 | dependencies = [
1050 | "thiserror-impl",
1051 | ]
1052 |
1053 | [[package]]
1054 | name = "thiserror-impl"
1055 | version = "1.0.24"
1056 | source = "registry+https://github.com/rust-lang/crates.io-index"
1057 | checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
1058 | dependencies = [
1059 | "proc-macro2",
1060 | "quote",
1061 | "syn",
1062 | ]
1063 |
1064 | [[package]]
1065 | name = "tiff"
1066 | version = "0.6.1"
1067 | source = "registry+https://github.com/rust-lang/crates.io-index"
1068 | checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437"
1069 | dependencies = [
1070 | "jpeg-decoder",
1071 | "miniz_oxide 0.4.4",
1072 | "weezl",
1073 | ]
1074 |
1075 | [[package]]
1076 | name = "time"
1077 | version = "0.1.43"
1078 | source = "registry+https://github.com/rust-lang/crates.io-index"
1079 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
1080 | dependencies = [
1081 | "libc",
1082 | "winapi",
1083 | ]
1084 |
1085 | [[package]]
1086 | name = "tinyvec"
1087 | version = "1.2.0"
1088 | source = "registry+https://github.com/rust-lang/crates.io-index"
1089 | checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342"
1090 | dependencies = [
1091 | "tinyvec_macros",
1092 | ]
1093 |
1094 | [[package]]
1095 | name = "tinyvec_macros"
1096 | version = "0.1.0"
1097 | source = "registry+https://github.com/rust-lang/crates.io-index"
1098 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
1099 |
1100 | [[package]]
1101 | name = "tokio"
1102 | version = "1.5.0"
1103 | source = "registry+https://github.com/rust-lang/crates.io-index"
1104 | checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5"
1105 | dependencies = [
1106 | "autocfg",
1107 | "libc",
1108 | "mio",
1109 | "num_cpus",
1110 | "pin-project-lite",
1111 | ]
1112 |
1113 | [[package]]
1114 | name = "trust-dns-proto"
1115 | version = "0.20.1"
1116 | source = "registry+https://github.com/rust-lang/crates.io-index"
1117 | checksum = "8d57e219ba600dd96c2f6d82eb79645068e14edbc5c7e27514af40436b88150c"
1118 | dependencies = [
1119 | "async-trait",
1120 | "cfg-if",
1121 | "data-encoding",
1122 | "enum-as-inner",
1123 | "futures-channel",
1124 | "futures-io",
1125 | "futures-util",
1126 | "idna",
1127 | "ipnet",
1128 | "lazy_static",
1129 | "log",
1130 | "rand 0.8.3",
1131 | "smallvec",
1132 | "thiserror",
1133 | "tinyvec",
1134 | "tokio",
1135 | "url",
1136 | ]
1137 |
1138 | [[package]]
1139 | name = "trust-dns-resolver"
1140 | version = "0.20.1"
1141 | source = "registry+https://github.com/rust-lang/crates.io-index"
1142 | checksum = "b0437eea3a6da51acc1e946545ff53d5b8fb2611ff1c3bed58522dde100536ae"
1143 | dependencies = [
1144 | "cfg-if",
1145 | "futures-util",
1146 | "ipconfig",
1147 | "lazy_static",
1148 | "log",
1149 | "lru-cache",
1150 | "parking_lot",
1151 | "resolv-conf",
1152 | "smallvec",
1153 | "thiserror",
1154 | "tokio",
1155 | "trust-dns-proto",
1156 | ]
1157 |
1158 | [[package]]
1159 | name = "typenum"
1160 | version = "1.13.0"
1161 | source = "registry+https://github.com/rust-lang/crates.io-index"
1162 | checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
1163 |
1164 | [[package]]
1165 | name = "unicode-bidi"
1166 | version = "0.3.5"
1167 | source = "registry+https://github.com/rust-lang/crates.io-index"
1168 | checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0"
1169 | dependencies = [
1170 | "matches",
1171 | ]
1172 |
1173 | [[package]]
1174 | name = "unicode-normalization"
1175 | version = "0.1.17"
1176 | source = "registry+https://github.com/rust-lang/crates.io-index"
1177 | checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef"
1178 | dependencies = [
1179 | "tinyvec",
1180 | ]
1181 |
1182 | [[package]]
1183 | name = "unicode-segmentation"
1184 | version = "1.7.1"
1185 | source = "registry+https://github.com/rust-lang/crates.io-index"
1186 | checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
1187 |
1188 | [[package]]
1189 | name = "unicode-xid"
1190 | version = "0.2.1"
1191 | source = "registry+https://github.com/rust-lang/crates.io-index"
1192 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
1193 |
1194 | [[package]]
1195 | name = "url"
1196 | version = "2.2.1"
1197 | source = "registry+https://github.com/rust-lang/crates.io-index"
1198 | checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b"
1199 | dependencies = [
1200 | "form_urlencoded",
1201 | "idna",
1202 | "matches",
1203 | "percent-encoding",
1204 | ]
1205 |
1206 | [[package]]
1207 | name = "version_check"
1208 | version = "0.9.3"
1209 | source = "registry+https://github.com/rust-lang/crates.io-index"
1210 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
1211 |
1212 | [[package]]
1213 | name = "wasi"
1214 | version = "0.9.0+wasi-snapshot-preview1"
1215 | source = "registry+https://github.com/rust-lang/crates.io-index"
1216 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1217 |
1218 | [[package]]
1219 | name = "wasi"
1220 | version = "0.10.2+wasi-snapshot-preview1"
1221 | source = "registry+https://github.com/rust-lang/crates.io-index"
1222 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
1223 |
1224 | [[package]]
1225 | name = "weezl"
1226 | version = "0.1.4"
1227 | source = "registry+https://github.com/rust-lang/crates.io-index"
1228 | checksum = "4a32b378380f4e9869b22f0b5177c68a5519f03b3454fde0b291455ddbae266c"
1229 |
1230 | [[package]]
1231 | name = "widestring"
1232 | version = "0.4.3"
1233 | source = "registry+https://github.com/rust-lang/crates.io-index"
1234 | checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
1235 |
1236 | [[package]]
1237 | name = "winapi"
1238 | version = "0.3.9"
1239 | source = "registry+https://github.com/rust-lang/crates.io-index"
1240 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1241 | dependencies = [
1242 | "winapi-i686-pc-windows-gnu",
1243 | "winapi-x86_64-pc-windows-gnu",
1244 | ]
1245 |
1246 | [[package]]
1247 | name = "winapi-i686-pc-windows-gnu"
1248 | version = "0.4.0"
1249 | source = "registry+https://github.com/rust-lang/crates.io-index"
1250 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1251 |
1252 | [[package]]
1253 | name = "winapi-x86_64-pc-windows-gnu"
1254 | version = "0.4.0"
1255 | source = "registry+https://github.com/rust-lang/crates.io-index"
1256 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1257 |
1258 | [[package]]
1259 | name = "winreg"
1260 | version = "0.6.2"
1261 | source = "registry+https://github.com/rust-lang/crates.io-index"
1262 | checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
1263 | dependencies = [
1264 | "winapi",
1265 | ]
1266 |
--------------------------------------------------------------------------------
/swift/MinecraftStatus.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 4E1FCB35259BF07B0018AAA3 /* minecraft_status.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E1FCB34259BF07B0018AAA3 /* minecraft_status.a */; };
11 | 4E1FCB36259BF07B0018AAA3 /* minecraft_status.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E1FCB34259BF07B0018AAA3 /* minecraft_status.a */; };
12 | 4E258F24259AC7D90093B65F /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E258F23259AC7D90093B65F /* WidgetKit.framework */; };
13 | 4E258F26259AC7D90093B65F /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E258F25259AC7D90093B65F /* SwiftUI.framework */; };
14 | 4E258F29259AC7D90093B65F /* MinecraftStatusWidgetViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E258F28259AC7D90093B65F /* MinecraftStatusWidgetViewExtension.swift */; };
15 | 4E258F2C259AC7DA0093B65F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4E258F2B259AC7DA0093B65F /* Assets.xcassets */; };
16 | 4E258F2E259AC7DA0093B65F /* MinecraftStatusWidgetViewExtension.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 4E258F2A259AC7D90093B65F /* MinecraftStatusWidgetViewExtension.intentdefinition */; };
17 | 4E258F2F259AC7DA0093B65F /* MinecraftStatusWidgetViewExtension.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 4E258F2A259AC7D90093B65F /* MinecraftStatusWidgetViewExtension.intentdefinition */; };
18 | 4E258F32259AC7DA0093B65F /* MinecraftStatusWidgetViewExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 4E258F21259AC7D90093B65F /* MinecraftStatusWidgetViewExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
19 | 4E258F51259ACBA90093B65F /* McStructs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E6EC426259AAA58004B142B /* McStructs.swift */; };
20 | 4E258F5B259BCD300093B65F /* PreviewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E258F5A259BCD300093B65F /* PreviewData.swift */; };
21 | 4E308A8825915FB80073C5D7 /* MinecraftStatusApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E308A8725915FB80073C5D7 /* MinecraftStatusApp.swift */; };
22 | 4E308A8A25915FB80073C5D7 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E308A8925915FB80073C5D7 /* SettingsView.swift */; };
23 | 4E308A8C25915FBA0073C5D7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4E308A8B25915FBA0073C5D7 /* Assets.xcassets */; };
24 | 4E308A8F25915FBA0073C5D7 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4E308A8E25915FBA0073C5D7 /* Preview Assets.xcassets */; };
25 | 4E308A9A25915FBA0073C5D7 /* MinecraftStatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E308A9925915FBA0073C5D7 /* MinecraftStatusTests.swift */; };
26 | 4E308AA525915FBA0073C5D7 /* MinecraftStatusUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E308AA425915FBA0073C5D7 /* MinecraftStatusUITests.swift */; };
27 | 4E3C08DE259C1DBB00303A35 /* minecraft.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4E3C08DD259C1DBB00303A35 /* minecraft.ttf */; };
28 | 4E3C08DF259C1DBB00303A35 /* minecraft.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4E3C08DD259C1DBB00303A35 /* minecraft.ttf */; };
29 | 4E6EC427259AAA58004B142B /* McStructs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E6EC426259AAA58004B142B /* McStructs.swift */; };
30 | /* End PBXBuildFile section */
31 |
32 | /* Begin PBXContainerItemProxy section */
33 | 4E258F30259AC7DA0093B65F /* PBXContainerItemProxy */ = {
34 | isa = PBXContainerItemProxy;
35 | containerPortal = 4E308A7C25915FB80073C5D7 /* Project object */;
36 | proxyType = 1;
37 | remoteGlobalIDString = 4E258F20259AC7D90093B65F;
38 | remoteInfo = MinecraftStatusWidgetViewExtension;
39 | };
40 | 4E308A9625915FBA0073C5D7 /* PBXContainerItemProxy */ = {
41 | isa = PBXContainerItemProxy;
42 | containerPortal = 4E308A7C25915FB80073C5D7 /* Project object */;
43 | proxyType = 1;
44 | remoteGlobalIDString = 4E308A8325915FB80073C5D7;
45 | remoteInfo = MinecraftStatus;
46 | };
47 | 4E308AA125915FBA0073C5D7 /* PBXContainerItemProxy */ = {
48 | isa = PBXContainerItemProxy;
49 | containerPortal = 4E308A7C25915FB80073C5D7 /* Project object */;
50 | proxyType = 1;
51 | remoteGlobalIDString = 4E308A8325915FB80073C5D7;
52 | remoteInfo = MinecraftStatus;
53 | };
54 | /* End PBXContainerItemProxy section */
55 |
56 | /* Begin PBXCopyFilesBuildPhase section */
57 | 4E258F33259AC7DA0093B65F /* Embed App Extensions */ = {
58 | isa = PBXCopyFilesBuildPhase;
59 | buildActionMask = 2147483647;
60 | dstPath = "";
61 | dstSubfolderSpec = 13;
62 | files = (
63 | 4E258F32259AC7DA0093B65F /* MinecraftStatusWidgetViewExtension.appex in Embed App Extensions */,
64 | );
65 | name = "Embed App Extensions";
66 | runOnlyForDeploymentPostprocessing = 0;
67 | };
68 | /* End PBXCopyFilesBuildPhase section */
69 |
70 | /* Begin PBXFileReference section */
71 | 4E1FCB34259BF07B0018AAA3 /* minecraft_status.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = minecraft_status.a; sourceTree = ""; };
72 | 4E1FCB3F259BF1350018AAA3 /* minecraft_status.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = minecraft_status.h; sourceTree = ""; };
73 | 4E258F21259AC7D90093B65F /* MinecraftStatusWidgetViewExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = MinecraftStatusWidgetViewExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
74 | 4E258F23259AC7D90093B65F /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
75 | 4E258F25259AC7D90093B65F /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
76 | 4E258F28259AC7D90093B65F /* MinecraftStatusWidgetViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinecraftStatusWidgetViewExtension.swift; sourceTree = ""; };
77 | 4E258F2A259AC7D90093B65F /* MinecraftStatusWidgetViewExtension.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = MinecraftStatusWidgetViewExtension.intentdefinition; sourceTree = ""; };
78 | 4E258F2B259AC7DA0093B65F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
79 | 4E258F2D259AC7DA0093B65F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
80 | 4E258F5A259BCD300093B65F /* PreviewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewData.swift; sourceTree = ""; };
81 | 4E308A8425915FB80073C5D7 /* MinecraftStatus.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MinecraftStatus.app; sourceTree = BUILT_PRODUCTS_DIR; };
82 | 4E308A8725915FB80073C5D7 /* MinecraftStatusApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinecraftStatusApp.swift; sourceTree = ""; };
83 | 4E308A8925915FB80073C5D7 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; };
84 | 4E308A8B25915FBA0073C5D7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
85 | 4E308A8E25915FBA0073C5D7 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
86 | 4E308A9025915FBA0073C5D7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
87 | 4E308A9525915FBA0073C5D7 /* MinecraftStatusTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MinecraftStatusTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
88 | 4E308A9925915FBA0073C5D7 /* MinecraftStatusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinecraftStatusTests.swift; sourceTree = ""; };
89 | 4E308A9B25915FBA0073C5D7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
90 | 4E308AA025915FBA0073C5D7 /* MinecraftStatusUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MinecraftStatusUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
91 | 4E308AA425915FBA0073C5D7 /* MinecraftStatusUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinecraftStatusUITests.swift; sourceTree = ""; };
92 | 4E308AA625915FBA0073C5D7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
93 | 4E3C08DD259C1DBB00303A35 /* minecraft.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = minecraft.ttf; sourceTree = ""; };
94 | 4E3C08E9259C23C300303A35 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; };
95 | 4E3C153B259160C800549980 /* Minecraft-Status-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Minecraft-Status-Bridging-Header.h"; sourceTree = ""; };
96 | 4E4C387625D8E49C00D8C18B /* MinecraftStatus.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MinecraftStatus.entitlements; sourceTree = ""; };
97 | 4E4C387B25D8E4EB00D8C18B /* MinecraftStatusWidgetViewExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MinecraftStatusWidgetViewExtension.entitlements; sourceTree = ""; };
98 | 4E6EC426259AAA58004B142B /* McStructs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = McStructs.swift; sourceTree = ""; };
99 | /* End PBXFileReference section */
100 |
101 | /* Begin PBXFrameworksBuildPhase section */
102 | 4E258F1E259AC7D90093B65F /* Frameworks */ = {
103 | isa = PBXFrameworksBuildPhase;
104 | buildActionMask = 2147483647;
105 | files = (
106 | 4E258F26259AC7D90093B65F /* SwiftUI.framework in Frameworks */,
107 | 4E258F24259AC7D90093B65F /* WidgetKit.framework in Frameworks */,
108 | 4E1FCB36259BF07B0018AAA3 /* minecraft_status.a in Frameworks */,
109 | );
110 | runOnlyForDeploymentPostprocessing = 0;
111 | };
112 | 4E308A8125915FB80073C5D7 /* Frameworks */ = {
113 | isa = PBXFrameworksBuildPhase;
114 | buildActionMask = 2147483647;
115 | files = (
116 | 4E1FCB35259BF07B0018AAA3 /* minecraft_status.a in Frameworks */,
117 | );
118 | runOnlyForDeploymentPostprocessing = 0;
119 | };
120 | 4E308A9225915FBA0073C5D7 /* Frameworks */ = {
121 | isa = PBXFrameworksBuildPhase;
122 | buildActionMask = 2147483647;
123 | files = (
124 | );
125 | runOnlyForDeploymentPostprocessing = 0;
126 | };
127 | 4E308A9D25915FBA0073C5D7 /* Frameworks */ = {
128 | isa = PBXFrameworksBuildPhase;
129 | buildActionMask = 2147483647;
130 | files = (
131 | );
132 | runOnlyForDeploymentPostprocessing = 0;
133 | };
134 | /* End PBXFrameworksBuildPhase section */
135 |
136 | /* Begin PBXGroup section */
137 | 4E258F22259AC7D90093B65F /* Frameworks */ = {
138 | isa = PBXGroup;
139 | children = (
140 | 4E258F23259AC7D90093B65F /* WidgetKit.framework */,
141 | 4E258F25259AC7D90093B65F /* SwiftUI.framework */,
142 | );
143 | name = Frameworks;
144 | sourceTree = "";
145 | };
146 | 4E258F27259AC7D90093B65F /* MinecraftStatusWidgetViewExtension */ = {
147 | isa = PBXGroup;
148 | children = (
149 | 4E4C387B25D8E4EB00D8C18B /* MinecraftStatusWidgetViewExtension.entitlements */,
150 | 4E3C08E4259C227C00303A35 /* Fonts */,
151 | 4E258F28259AC7D90093B65F /* MinecraftStatusWidgetViewExtension.swift */,
152 | 4E258F2A259AC7D90093B65F /* MinecraftStatusWidgetViewExtension.intentdefinition */,
153 | 4E258F2B259AC7DA0093B65F /* Assets.xcassets */,
154 | 4E258F2D259AC7DA0093B65F /* Info.plist */,
155 | 4E258F5A259BCD300093B65F /* PreviewData.swift */,
156 | );
157 | path = MinecraftStatusWidgetViewExtension;
158 | sourceTree = "";
159 | };
160 | 4E308A7B25915FB80073C5D7 = {
161 | isa = PBXGroup;
162 | children = (
163 | 4E4C387625D8E49C00D8C18B /* MinecraftStatus.entitlements */,
164 | 4E3C153A2591609B00549980 /* MinecraftStatusLib */,
165 | 4E308A8625915FB80073C5D7 /* MinecraftStatus */,
166 | 4E308A9825915FBA0073C5D7 /* MinecraftStatusTests */,
167 | 4E308AA325915FBA0073C5D7 /* MinecraftStatusUITests */,
168 | 4E258F27259AC7D90093B65F /* MinecraftStatusWidgetViewExtension */,
169 | 4E258F22259AC7D90093B65F /* Frameworks */,
170 | 4E308A8525915FB80073C5D7 /* Products */,
171 | );
172 | sourceTree = "";
173 | };
174 | 4E308A8525915FB80073C5D7 /* Products */ = {
175 | isa = PBXGroup;
176 | children = (
177 | 4E308A8425915FB80073C5D7 /* MinecraftStatus.app */,
178 | 4E308A9525915FBA0073C5D7 /* MinecraftStatusTests.xctest */,
179 | 4E308AA025915FBA0073C5D7 /* MinecraftStatusUITests.xctest */,
180 | 4E258F21259AC7D90093B65F /* MinecraftStatusWidgetViewExtension.appex */,
181 | );
182 | name = Products;
183 | sourceTree = "";
184 | };
185 | 4E308A8625915FB80073C5D7 /* MinecraftStatus */ = {
186 | isa = PBXGroup;
187 | children = (
188 | 4E308A9025915FBA0073C5D7 /* Info.plist */,
189 | 4E308A8925915FB80073C5D7 /* SettingsView.swift */,
190 | 4E308A8725915FB80073C5D7 /* MinecraftStatusApp.swift */,
191 | 4E308A8B25915FBA0073C5D7 /* Assets.xcassets */,
192 | 4E308A8D25915FBA0073C5D7 /* Preview Content */,
193 | 4E6EC426259AAA58004B142B /* McStructs.swift */,
194 | );
195 | path = MinecraftStatus;
196 | sourceTree = "";
197 | };
198 | 4E308A8D25915FBA0073C5D7 /* Preview Content */ = {
199 | isa = PBXGroup;
200 | children = (
201 | 4E308A8E25915FBA0073C5D7 /* Preview Assets.xcassets */,
202 | );
203 | path = "Preview Content";
204 | sourceTree = "";
205 | };
206 | 4E308A9825915FBA0073C5D7 /* MinecraftStatusTests */ = {
207 | isa = PBXGroup;
208 | children = (
209 | 4E308A9925915FBA0073C5D7 /* MinecraftStatusTests.swift */,
210 | 4E308A9B25915FBA0073C5D7 /* Info.plist */,
211 | );
212 | path = MinecraftStatusTests;
213 | sourceTree = "";
214 | };
215 | 4E308AA325915FBA0073C5D7 /* MinecraftStatusUITests */ = {
216 | isa = PBXGroup;
217 | children = (
218 | 4E308AA425915FBA0073C5D7 /* MinecraftStatusUITests.swift */,
219 | 4E308AA625915FBA0073C5D7 /* Info.plist */,
220 | );
221 | path = MinecraftStatusUITests;
222 | sourceTree = "";
223 | };
224 | 4E3C08E4259C227C00303A35 /* Fonts */ = {
225 | isa = PBXGroup;
226 | children = (
227 | 4E3C08DD259C1DBB00303A35 /* minecraft.ttf */,
228 | 4E3C08E9259C23C300303A35 /* LICENSE */,
229 | );
230 | path = Fonts;
231 | sourceTree = "";
232 | };
233 | 4E3C153A2591609B00549980 /* MinecraftStatusLib */ = {
234 | isa = PBXGroup;
235 | children = (
236 | 4E3C153B259160C800549980 /* Minecraft-Status-Bridging-Header.h */,
237 | 4E1FCB3F259BF1350018AAA3 /* minecraft_status.h */,
238 | 4E1FCB34259BF07B0018AAA3 /* minecraft_status.a */,
239 | );
240 | path = MinecraftStatusLib;
241 | sourceTree = "";
242 | };
243 | /* End PBXGroup section */
244 |
245 | /* Begin PBXNativeTarget section */
246 | 4E258F20259AC7D90093B65F /* MinecraftStatusWidgetViewExtension */ = {
247 | isa = PBXNativeTarget;
248 | buildConfigurationList = 4E258F36259AC7DA0093B65F /* Build configuration list for PBXNativeTarget "MinecraftStatusWidgetViewExtension" */;
249 | buildPhases = (
250 | 4E1FCB23259BEE610018AAA3 /* ShellScript */,
251 | 4E258F1D259AC7D90093B65F /* Sources */,
252 | 4E38E85B25CF898500BF6025 /* ShellScript */,
253 | 4E258F1E259AC7D90093B65F /* Frameworks */,
254 | 4E258F1F259AC7D90093B65F /* Resources */,
255 | );
256 | buildRules = (
257 | );
258 | dependencies = (
259 | );
260 | name = MinecraftStatusWidgetViewExtension;
261 | productName = MinecraftStatusWidgetViewExtension;
262 | productReference = 4E258F21259AC7D90093B65F /* MinecraftStatusWidgetViewExtension.appex */;
263 | productType = "com.apple.product-type.app-extension";
264 | };
265 | 4E308A8325915FB80073C5D7 /* MinecraftStatus */ = {
266 | isa = PBXNativeTarget;
267 | buildConfigurationList = 4E308AA925915FBA0073C5D7 /* Build configuration list for PBXNativeTarget "MinecraftStatus" */;
268 | buildPhases = (
269 | 4E308A8025915FB80073C5D7 /* Sources */,
270 | 4E308A8125915FB80073C5D7 /* Frameworks */,
271 | 4E308A8225915FB80073C5D7 /* Resources */,
272 | 4E258F33259AC7DA0093B65F /* Embed App Extensions */,
273 | );
274 | buildRules = (
275 | );
276 | dependencies = (
277 | 4E258F31259AC7DA0093B65F /* PBXTargetDependency */,
278 | );
279 | name = MinecraftStatus;
280 | productName = MinecraftStatus;
281 | productReference = 4E308A8425915FB80073C5D7 /* MinecraftStatus.app */;
282 | productType = "com.apple.product-type.application";
283 | };
284 | 4E308A9425915FBA0073C5D7 /* MinecraftStatusTests */ = {
285 | isa = PBXNativeTarget;
286 | buildConfigurationList = 4E308AAC25915FBA0073C5D7 /* Build configuration list for PBXNativeTarget "MinecraftStatusTests" */;
287 | buildPhases = (
288 | 4E308A9125915FBA0073C5D7 /* Sources */,
289 | 4E308A9225915FBA0073C5D7 /* Frameworks */,
290 | 4E308A9325915FBA0073C5D7 /* Resources */,
291 | );
292 | buildRules = (
293 | );
294 | dependencies = (
295 | 4E308A9725915FBA0073C5D7 /* PBXTargetDependency */,
296 | );
297 | name = MinecraftStatusTests;
298 | productName = MinecraftStatusTests;
299 | productReference = 4E308A9525915FBA0073C5D7 /* MinecraftStatusTests.xctest */;
300 | productType = "com.apple.product-type.bundle.unit-test";
301 | };
302 | 4E308A9F25915FBA0073C5D7 /* MinecraftStatusUITests */ = {
303 | isa = PBXNativeTarget;
304 | buildConfigurationList = 4E308AAF25915FBA0073C5D7 /* Build configuration list for PBXNativeTarget "MinecraftStatusUITests" */;
305 | buildPhases = (
306 | 4E308A9C25915FBA0073C5D7 /* Sources */,
307 | 4E308A9D25915FBA0073C5D7 /* Frameworks */,
308 | 4E308A9E25915FBA0073C5D7 /* Resources */,
309 | );
310 | buildRules = (
311 | );
312 | dependencies = (
313 | 4E308AA225915FBA0073C5D7 /* PBXTargetDependency */,
314 | );
315 | name = MinecraftStatusUITests;
316 | productName = MinecraftStatusUITests;
317 | productReference = 4E308AA025915FBA0073C5D7 /* MinecraftStatusUITests.xctest */;
318 | productType = "com.apple.product-type.bundle.ui-testing";
319 | };
320 | /* End PBXNativeTarget section */
321 |
322 | /* Begin PBXProject section */
323 | 4E308A7C25915FB80073C5D7 /* Project object */ = {
324 | isa = PBXProject;
325 | attributes = {
326 | LastSwiftUpdateCheck = 1230;
327 | LastUpgradeCheck = 1230;
328 | TargetAttributes = {
329 | 4E258F20259AC7D90093B65F = {
330 | CreatedOnToolsVersion = 12.3;
331 | };
332 | 4E308A8325915FB80073C5D7 = {
333 | CreatedOnToolsVersion = 12.3;
334 | LastSwiftMigration = 1230;
335 | };
336 | 4E308A9425915FBA0073C5D7 = {
337 | CreatedOnToolsVersion = 12.3;
338 | TestTargetID = 4E308A8325915FB80073C5D7;
339 | };
340 | 4E308A9F25915FBA0073C5D7 = {
341 | CreatedOnToolsVersion = 12.3;
342 | TestTargetID = 4E308A8325915FB80073C5D7;
343 | };
344 | };
345 | };
346 | buildConfigurationList = 4E308A7F25915FB80073C5D7 /* Build configuration list for PBXProject "MinecraftStatus" */;
347 | compatibilityVersion = "Xcode 9.3";
348 | developmentRegion = en;
349 | hasScannedForEncodings = 0;
350 | knownRegions = (
351 | en,
352 | Base,
353 | );
354 | mainGroup = 4E308A7B25915FB80073C5D7;
355 | productRefGroup = 4E308A8525915FB80073C5D7 /* Products */;
356 | projectDirPath = "";
357 | projectRoot = "";
358 | targets = (
359 | 4E308A8325915FB80073C5D7 /* MinecraftStatus */,
360 | 4E308A9425915FBA0073C5D7 /* MinecraftStatusTests */,
361 | 4E308A9F25915FBA0073C5D7 /* MinecraftStatusUITests */,
362 | 4E258F20259AC7D90093B65F /* MinecraftStatusWidgetViewExtension */,
363 | );
364 | };
365 | /* End PBXProject section */
366 |
367 | /* Begin PBXResourcesBuildPhase section */
368 | 4E258F1F259AC7D90093B65F /* Resources */ = {
369 | isa = PBXResourcesBuildPhase;
370 | buildActionMask = 2147483647;
371 | files = (
372 | 4E3C08DF259C1DBB00303A35 /* minecraft.ttf in Resources */,
373 | 4E258F2C259AC7DA0093B65F /* Assets.xcassets in Resources */,
374 | );
375 | runOnlyForDeploymentPostprocessing = 0;
376 | };
377 | 4E308A8225915FB80073C5D7 /* Resources */ = {
378 | isa = PBXResourcesBuildPhase;
379 | buildActionMask = 2147483647;
380 | files = (
381 | 4E308A8F25915FBA0073C5D7 /* Preview Assets.xcassets in Resources */,
382 | 4E308A8C25915FBA0073C5D7 /* Assets.xcassets in Resources */,
383 | 4E3C08DE259C1DBB00303A35 /* minecraft.ttf in Resources */,
384 | );
385 | runOnlyForDeploymentPostprocessing = 0;
386 | };
387 | 4E308A9325915FBA0073C5D7 /* Resources */ = {
388 | isa = PBXResourcesBuildPhase;
389 | buildActionMask = 2147483647;
390 | files = (
391 | );
392 | runOnlyForDeploymentPostprocessing = 0;
393 | };
394 | 4E308A9E25915FBA0073C5D7 /* Resources */ = {
395 | isa = PBXResourcesBuildPhase;
396 | buildActionMask = 2147483647;
397 | files = (
398 | );
399 | runOnlyForDeploymentPostprocessing = 0;
400 | };
401 | /* End PBXResourcesBuildPhase section */
402 |
403 | /* Begin PBXShellScriptBuildPhase section */
404 | 4E1FCB23259BEE610018AAA3 /* ShellScript */ = {
405 | isa = PBXShellScriptBuildPhase;
406 | buildActionMask = 2147483647;
407 | files = (
408 | );
409 | inputFileListPaths = (
410 | );
411 | inputPaths = (
412 | );
413 | outputFileListPaths = (
414 | );
415 | outputPaths = (
416 | );
417 | runOnlyForDeploymentPostprocessing = 0;
418 | shellPath = /bin/sh;
419 | shellScript = "# TODO: the rust stuff should be split out into its own framework\n$SRCROOT/scripts/generate-rust-lib.sh\n\n# cd's are needed to operate with the mintfile in context\ncd $SRCROOT/../\n$SRCROOT/scripts/bootstrap.sh\ncd -\n\n$SRCROOT/scripts/swiftformat.sh\n";
420 | };
421 | 4E38E85B25CF898500BF6025 /* ShellScript */ = {
422 | isa = PBXShellScriptBuildPhase;
423 | buildActionMask = 2147483647;
424 | files = (
425 | );
426 | inputFileListPaths = (
427 | );
428 | inputPaths = (
429 | );
430 | outputFileListPaths = (
431 | );
432 | outputPaths = (
433 | );
434 | runOnlyForDeploymentPostprocessing = 0;
435 | shellPath = /bin/sh;
436 | shellScript = "$SRCROOT/scripts/swiftlint.sh\n";
437 | };
438 | /* End PBXShellScriptBuildPhase section */
439 |
440 | /* Begin PBXSourcesBuildPhase section */
441 | 4E258F1D259AC7D90093B65F /* Sources */ = {
442 | isa = PBXSourcesBuildPhase;
443 | buildActionMask = 2147483647;
444 | files = (
445 | 4E258F5B259BCD300093B65F /* PreviewData.swift in Sources */,
446 | 4E258F2E259AC7DA0093B65F /* MinecraftStatusWidgetViewExtension.intentdefinition in Sources */,
447 | 4E258F51259ACBA90093B65F /* McStructs.swift in Sources */,
448 | 4E258F29259AC7D90093B65F /* MinecraftStatusWidgetViewExtension.swift in Sources */,
449 | );
450 | runOnlyForDeploymentPostprocessing = 0;
451 | };
452 | 4E308A8025915FB80073C5D7 /* Sources */ = {
453 | isa = PBXSourcesBuildPhase;
454 | buildActionMask = 2147483647;
455 | files = (
456 | 4E308A8A25915FB80073C5D7 /* SettingsView.swift in Sources */,
457 | 4E6EC427259AAA58004B142B /* McStructs.swift in Sources */,
458 | 4E258F2F259AC7DA0093B65F /* MinecraftStatusWidgetViewExtension.intentdefinition in Sources */,
459 | 4E308A8825915FB80073C5D7 /* MinecraftStatusApp.swift in Sources */,
460 | );
461 | runOnlyForDeploymentPostprocessing = 0;
462 | };
463 | 4E308A9125915FBA0073C5D7 /* Sources */ = {
464 | isa = PBXSourcesBuildPhase;
465 | buildActionMask = 2147483647;
466 | files = (
467 | 4E308A9A25915FBA0073C5D7 /* MinecraftStatusTests.swift in Sources */,
468 | );
469 | runOnlyForDeploymentPostprocessing = 0;
470 | };
471 | 4E308A9C25915FBA0073C5D7 /* Sources */ = {
472 | isa = PBXSourcesBuildPhase;
473 | buildActionMask = 2147483647;
474 | files = (
475 | 4E308AA525915FBA0073C5D7 /* MinecraftStatusUITests.swift in Sources */,
476 | );
477 | runOnlyForDeploymentPostprocessing = 0;
478 | };
479 | /* End PBXSourcesBuildPhase section */
480 |
481 | /* Begin PBXTargetDependency section */
482 | 4E258F31259AC7DA0093B65F /* PBXTargetDependency */ = {
483 | isa = PBXTargetDependency;
484 | target = 4E258F20259AC7D90093B65F /* MinecraftStatusWidgetViewExtension */;
485 | targetProxy = 4E258F30259AC7DA0093B65F /* PBXContainerItemProxy */;
486 | };
487 | 4E308A9725915FBA0073C5D7 /* PBXTargetDependency */ = {
488 | isa = PBXTargetDependency;
489 | target = 4E308A8325915FB80073C5D7 /* MinecraftStatus */;
490 | targetProxy = 4E308A9625915FBA0073C5D7 /* PBXContainerItemProxy */;
491 | };
492 | 4E308AA225915FBA0073C5D7 /* PBXTargetDependency */ = {
493 | isa = PBXTargetDependency;
494 | target = 4E308A8325915FB80073C5D7 /* MinecraftStatus */;
495 | targetProxy = 4E308AA125915FBA0073C5D7 /* PBXContainerItemProxy */;
496 | };
497 | /* End PBXTargetDependency section */
498 |
499 | /* Begin XCBuildConfiguration section */
500 | 4E258F34259AC7DA0093B65F /* Debug */ = {
501 | isa = XCBuildConfiguration;
502 | buildSettings = {
503 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
504 | ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
505 | CODE_SIGN_ENTITLEMENTS = MinecraftStatusWidgetViewExtension/MinecraftStatusWidgetViewExtension.entitlements;
506 | CODE_SIGN_STYLE = Automatic;
507 | CURRENT_PROJECT_VERSION = 2;
508 | DEVELOPMENT_TEAM = Z79T9AJ776;
509 | ENABLE_BITCODE = NO;
510 | INFOPLIST_FILE = MinecraftStatusWidgetViewExtension/Info.plist;
511 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
512 | LD_RUNPATH_SEARCH_PATHS = (
513 | "$(inherited)",
514 | "@executable_path/Frameworks",
515 | "@executable_path/../../Frameworks",
516 | );
517 | LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/MinecraftStatusLib";
518 | MARKETING_VERSION = 1.0.0;
519 | PRODUCT_BUNDLE_IDENTIFIER = "dev.cldfire.minecraft-status.MinecraftStatusWidgetViewExtension";
520 | PRODUCT_NAME = "$(TARGET_NAME)";
521 | SKIP_INSTALL = YES;
522 | SWIFT_OBJC_BRIDGING_HEADER = "MinecraftStatusLib/Minecraft-Status-Bridging-Header.h";
523 | SWIFT_VERSION = 5.0;
524 | TARGETED_DEVICE_FAMILY = "1,2";
525 | };
526 | name = Debug;
527 | };
528 | 4E258F35259AC7DA0093B65F /* Release */ = {
529 | isa = XCBuildConfiguration;
530 | buildSettings = {
531 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
532 | ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
533 | CODE_SIGN_ENTITLEMENTS = MinecraftStatusWidgetViewExtension/MinecraftStatusWidgetViewExtension.entitlements;
534 | CODE_SIGN_STYLE = Automatic;
535 | CURRENT_PROJECT_VERSION = 2;
536 | DEVELOPMENT_TEAM = Z79T9AJ776;
537 | ENABLE_BITCODE = NO;
538 | INFOPLIST_FILE = MinecraftStatusWidgetViewExtension/Info.plist;
539 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
540 | LD_RUNPATH_SEARCH_PATHS = (
541 | "$(inherited)",
542 | "@executable_path/Frameworks",
543 | "@executable_path/../../Frameworks",
544 | );
545 | LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/MinecraftStatusLib";
546 | MARKETING_VERSION = 1.0.0;
547 | PRODUCT_BUNDLE_IDENTIFIER = "dev.cldfire.minecraft-status.MinecraftStatusWidgetViewExtension";
548 | PRODUCT_NAME = "$(TARGET_NAME)";
549 | SKIP_INSTALL = YES;
550 | SWIFT_OBJC_BRIDGING_HEADER = "MinecraftStatusLib/Minecraft-Status-Bridging-Header.h";
551 | SWIFT_VERSION = 5.0;
552 | TARGETED_DEVICE_FAMILY = "1,2";
553 | };
554 | name = Release;
555 | };
556 | 4E308AA725915FBA0073C5D7 /* Debug */ = {
557 | isa = XCBuildConfiguration;
558 | buildSettings = {
559 | ALWAYS_SEARCH_USER_PATHS = NO;
560 | CLANG_ANALYZER_NONNULL = YES;
561 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
562 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
563 | CLANG_CXX_LIBRARY = "libc++";
564 | CLANG_ENABLE_MODULES = YES;
565 | CLANG_ENABLE_OBJC_ARC = YES;
566 | CLANG_ENABLE_OBJC_WEAK = YES;
567 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
568 | CLANG_WARN_BOOL_CONVERSION = YES;
569 | CLANG_WARN_COMMA = YES;
570 | CLANG_WARN_CONSTANT_CONVERSION = YES;
571 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
572 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
573 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
574 | CLANG_WARN_EMPTY_BODY = YES;
575 | CLANG_WARN_ENUM_CONVERSION = YES;
576 | CLANG_WARN_INFINITE_RECURSION = YES;
577 | CLANG_WARN_INT_CONVERSION = YES;
578 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
579 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
580 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
581 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
582 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
583 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
584 | CLANG_WARN_STRICT_PROTOTYPES = YES;
585 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
586 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
587 | CLANG_WARN_UNREACHABLE_CODE = YES;
588 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
589 | COPY_PHASE_STRIP = NO;
590 | DEBUG_INFORMATION_FORMAT = dwarf;
591 | ENABLE_STRICT_OBJC_MSGSEND = YES;
592 | ENABLE_TESTABILITY = YES;
593 | GCC_C_LANGUAGE_STANDARD = gnu11;
594 | GCC_DYNAMIC_NO_PIC = NO;
595 | GCC_NO_COMMON_BLOCKS = YES;
596 | GCC_OPTIMIZATION_LEVEL = 0;
597 | GCC_PREPROCESSOR_DEFINITIONS = (
598 | "DEBUG=1",
599 | "$(inherited)",
600 | );
601 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
602 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
603 | GCC_WARN_UNDECLARED_SELECTOR = YES;
604 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
605 | GCC_WARN_UNUSED_FUNCTION = YES;
606 | GCC_WARN_UNUSED_VARIABLE = YES;
607 | IPHONEOS_DEPLOYMENT_TARGET = 14.3;
608 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
609 | MTL_FAST_MATH = YES;
610 | ONLY_ACTIVE_ARCH = YES;
611 | SDKROOT = iphoneos;
612 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
613 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
614 | };
615 | name = Debug;
616 | };
617 | 4E308AA825915FBA0073C5D7 /* Release */ = {
618 | isa = XCBuildConfiguration;
619 | buildSettings = {
620 | ALWAYS_SEARCH_USER_PATHS = NO;
621 | CLANG_ANALYZER_NONNULL = YES;
622 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
623 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
624 | CLANG_CXX_LIBRARY = "libc++";
625 | CLANG_ENABLE_MODULES = YES;
626 | CLANG_ENABLE_OBJC_ARC = YES;
627 | CLANG_ENABLE_OBJC_WEAK = YES;
628 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
629 | CLANG_WARN_BOOL_CONVERSION = YES;
630 | CLANG_WARN_COMMA = YES;
631 | CLANG_WARN_CONSTANT_CONVERSION = YES;
632 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
633 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
634 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
635 | CLANG_WARN_EMPTY_BODY = YES;
636 | CLANG_WARN_ENUM_CONVERSION = YES;
637 | CLANG_WARN_INFINITE_RECURSION = YES;
638 | CLANG_WARN_INT_CONVERSION = YES;
639 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
640 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
641 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
642 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
643 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
644 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
645 | CLANG_WARN_STRICT_PROTOTYPES = YES;
646 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
647 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
648 | CLANG_WARN_UNREACHABLE_CODE = YES;
649 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
650 | COPY_PHASE_STRIP = NO;
651 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
652 | ENABLE_NS_ASSERTIONS = NO;
653 | ENABLE_STRICT_OBJC_MSGSEND = YES;
654 | GCC_C_LANGUAGE_STANDARD = gnu11;
655 | GCC_NO_COMMON_BLOCKS = YES;
656 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
657 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
658 | GCC_WARN_UNDECLARED_SELECTOR = YES;
659 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
660 | GCC_WARN_UNUSED_FUNCTION = YES;
661 | GCC_WARN_UNUSED_VARIABLE = YES;
662 | IPHONEOS_DEPLOYMENT_TARGET = 14.3;
663 | MTL_ENABLE_DEBUG_INFO = NO;
664 | MTL_FAST_MATH = YES;
665 | SDKROOT = iphoneos;
666 | SWIFT_COMPILATION_MODE = wholemodule;
667 | SWIFT_OPTIMIZATION_LEVEL = "-O";
668 | VALIDATE_PRODUCT = YES;
669 | };
670 | name = Release;
671 | };
672 | 4E308AAA25915FBA0073C5D7 /* Debug */ = {
673 | isa = XCBuildConfiguration;
674 | buildSettings = {
675 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
676 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
677 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
678 | CLANG_ENABLE_MODULES = YES;
679 | CODE_SIGN_ENTITLEMENTS = MinecraftStatus.entitlements;
680 | CODE_SIGN_STYLE = Automatic;
681 | CURRENT_PROJECT_VERSION = 2;
682 | DEVELOPMENT_ASSET_PATHS = "\"MinecraftStatus/Preview Content\"";
683 | DEVELOPMENT_TEAM = Z79T9AJ776;
684 | ENABLE_BITCODE = NO;
685 | ENABLE_PREVIEWS = YES;
686 | INFOPLIST_FILE = MinecraftStatus/Info.plist;
687 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
688 | LD_RUNPATH_SEARCH_PATHS = (
689 | "$(inherited)",
690 | "@executable_path/Frameworks",
691 | );
692 | LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/MinecraftStatusLib";
693 | MARKETING_VERSION = 1.0.0;
694 | PRODUCT_BUNDLE_IDENTIFIER = "dev.cldfire.minecraft-status";
695 | PRODUCT_NAME = "$(TARGET_NAME)";
696 | SWIFT_OBJC_BRIDGING_HEADER = "MinecraftStatusLib/Minecraft-Status-Bridging-Header.h";
697 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
698 | SWIFT_VERSION = 5.0;
699 | TARGETED_DEVICE_FAMILY = "1,2";
700 | };
701 | name = Debug;
702 | };
703 | 4E308AAB25915FBA0073C5D7 /* Release */ = {
704 | isa = XCBuildConfiguration;
705 | buildSettings = {
706 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
707 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
708 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
709 | CLANG_ENABLE_MODULES = YES;
710 | CODE_SIGN_ENTITLEMENTS = MinecraftStatus.entitlements;
711 | CODE_SIGN_STYLE = Automatic;
712 | CURRENT_PROJECT_VERSION = 2;
713 | DEVELOPMENT_ASSET_PATHS = "\"MinecraftStatus/Preview Content\"";
714 | DEVELOPMENT_TEAM = Z79T9AJ776;
715 | ENABLE_BITCODE = NO;
716 | ENABLE_PREVIEWS = YES;
717 | INFOPLIST_FILE = MinecraftStatus/Info.plist;
718 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
719 | LD_RUNPATH_SEARCH_PATHS = (
720 | "$(inherited)",
721 | "@executable_path/Frameworks",
722 | );
723 | LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/MinecraftStatusLib";
724 | MARKETING_VERSION = 1.0.0;
725 | PRODUCT_BUNDLE_IDENTIFIER = "dev.cldfire.minecraft-status";
726 | PRODUCT_NAME = "$(TARGET_NAME)";
727 | SWIFT_OBJC_BRIDGING_HEADER = "MinecraftStatusLib/Minecraft-Status-Bridging-Header.h";
728 | SWIFT_VERSION = 5.0;
729 | TARGETED_DEVICE_FAMILY = "1,2";
730 | };
731 | name = Release;
732 | };
733 | 4E308AAD25915FBA0073C5D7 /* Debug */ = {
734 | isa = XCBuildConfiguration;
735 | buildSettings = {
736 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
737 | BUNDLE_LOADER = "$(TEST_HOST)";
738 | CODE_SIGN_STYLE = Automatic;
739 | DEVELOPMENT_TEAM = Z79T9AJ776;
740 | INFOPLIST_FILE = MinecraftStatusTests/Info.plist;
741 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
742 | LD_RUNPATH_SEARCH_PATHS = (
743 | "$(inherited)",
744 | "@executable_path/Frameworks",
745 | "@loader_path/Frameworks",
746 | );
747 | PRODUCT_BUNDLE_IDENTIFIER = dev.cldfire.MinecraftStatusTests;
748 | PRODUCT_NAME = "$(TARGET_NAME)";
749 | SWIFT_VERSION = 5.0;
750 | TARGETED_DEVICE_FAMILY = "1,2";
751 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MinecraftStatus.app/MinecraftStatus";
752 | };
753 | name = Debug;
754 | };
755 | 4E308AAE25915FBA0073C5D7 /* Release */ = {
756 | isa = XCBuildConfiguration;
757 | buildSettings = {
758 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
759 | BUNDLE_LOADER = "$(TEST_HOST)";
760 | CODE_SIGN_STYLE = Automatic;
761 | DEVELOPMENT_TEAM = Z79T9AJ776;
762 | INFOPLIST_FILE = MinecraftStatusTests/Info.plist;
763 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
764 | LD_RUNPATH_SEARCH_PATHS = (
765 | "$(inherited)",
766 | "@executable_path/Frameworks",
767 | "@loader_path/Frameworks",
768 | );
769 | PRODUCT_BUNDLE_IDENTIFIER = dev.cldfire.MinecraftStatusTests;
770 | PRODUCT_NAME = "$(TARGET_NAME)";
771 | SWIFT_VERSION = 5.0;
772 | TARGETED_DEVICE_FAMILY = "1,2";
773 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MinecraftStatus.app/MinecraftStatus";
774 | };
775 | name = Release;
776 | };
777 | 4E308AB025915FBA0073C5D7 /* Debug */ = {
778 | isa = XCBuildConfiguration;
779 | buildSettings = {
780 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
781 | CODE_SIGN_STYLE = Automatic;
782 | DEVELOPMENT_TEAM = Z79T9AJ776;
783 | INFOPLIST_FILE = MinecraftStatusUITests/Info.plist;
784 | LD_RUNPATH_SEARCH_PATHS = (
785 | "$(inherited)",
786 | "@executable_path/Frameworks",
787 | "@loader_path/Frameworks",
788 | );
789 | PRODUCT_BUNDLE_IDENTIFIER = dev.cldfire.MinecraftStatusUITests;
790 | PRODUCT_NAME = "$(TARGET_NAME)";
791 | SWIFT_VERSION = 5.0;
792 | TARGETED_DEVICE_FAMILY = "1,2";
793 | TEST_TARGET_NAME = MinecraftStatus;
794 | };
795 | name = Debug;
796 | };
797 | 4E308AB125915FBA0073C5D7 /* Release */ = {
798 | isa = XCBuildConfiguration;
799 | buildSettings = {
800 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
801 | CODE_SIGN_STYLE = Automatic;
802 | DEVELOPMENT_TEAM = Z79T9AJ776;
803 | INFOPLIST_FILE = MinecraftStatusUITests/Info.plist;
804 | LD_RUNPATH_SEARCH_PATHS = (
805 | "$(inherited)",
806 | "@executable_path/Frameworks",
807 | "@loader_path/Frameworks",
808 | );
809 | PRODUCT_BUNDLE_IDENTIFIER = dev.cldfire.MinecraftStatusUITests;
810 | PRODUCT_NAME = "$(TARGET_NAME)";
811 | SWIFT_VERSION = 5.0;
812 | TARGETED_DEVICE_FAMILY = "1,2";
813 | TEST_TARGET_NAME = MinecraftStatus;
814 | };
815 | name = Release;
816 | };
817 | /* End XCBuildConfiguration section */
818 |
819 | /* Begin XCConfigurationList section */
820 | 4E258F36259AC7DA0093B65F /* Build configuration list for PBXNativeTarget "MinecraftStatusWidgetViewExtension" */ = {
821 | isa = XCConfigurationList;
822 | buildConfigurations = (
823 | 4E258F34259AC7DA0093B65F /* Debug */,
824 | 4E258F35259AC7DA0093B65F /* Release */,
825 | );
826 | defaultConfigurationIsVisible = 0;
827 | defaultConfigurationName = Release;
828 | };
829 | 4E308A7F25915FB80073C5D7 /* Build configuration list for PBXProject "MinecraftStatus" */ = {
830 | isa = XCConfigurationList;
831 | buildConfigurations = (
832 | 4E308AA725915FBA0073C5D7 /* Debug */,
833 | 4E308AA825915FBA0073C5D7 /* Release */,
834 | );
835 | defaultConfigurationIsVisible = 0;
836 | defaultConfigurationName = Release;
837 | };
838 | 4E308AA925915FBA0073C5D7 /* Build configuration list for PBXNativeTarget "MinecraftStatus" */ = {
839 | isa = XCConfigurationList;
840 | buildConfigurations = (
841 | 4E308AAA25915FBA0073C5D7 /* Debug */,
842 | 4E308AAB25915FBA0073C5D7 /* Release */,
843 | );
844 | defaultConfigurationIsVisible = 0;
845 | defaultConfigurationName = Release;
846 | };
847 | 4E308AAC25915FBA0073C5D7 /* Build configuration list for PBXNativeTarget "MinecraftStatusTests" */ = {
848 | isa = XCConfigurationList;
849 | buildConfigurations = (
850 | 4E308AAD25915FBA0073C5D7 /* Debug */,
851 | 4E308AAE25915FBA0073C5D7 /* Release */,
852 | );
853 | defaultConfigurationIsVisible = 0;
854 | defaultConfigurationName = Release;
855 | };
856 | 4E308AAF25915FBA0073C5D7 /* Build configuration list for PBXNativeTarget "MinecraftStatusUITests" */ = {
857 | isa = XCConfigurationList;
858 | buildConfigurations = (
859 | 4E308AB025915FBA0073C5D7 /* Debug */,
860 | 4E308AB125915FBA0073C5D7 /* Release */,
861 | );
862 | defaultConfigurationIsVisible = 0;
863 | defaultConfigurationName = Release;
864 | };
865 | /* End XCConfigurationList section */
866 | };
867 | rootObject = 4E308A7C25915FB80073C5D7 /* Project object */;
868 | }
869 |
--------------------------------------------------------------------------------