mirror of
https://github.com/TuringSoftware/CrystalFetch.git
synced 2026-01-14 00:05:26 +08:00
Compare commits
46 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05e0b201da | ||
|
|
c0946e8781 | ||
|
|
8c801a857c | ||
|
|
30f426038a | ||
|
|
6a462109c3 | ||
|
|
53dc56900f | ||
|
|
b08cbda85e | ||
|
|
39069074b4 | ||
|
|
2dce2ff2c7 | ||
|
|
4f444d88c6 | ||
|
|
effd4aeda5 | ||
|
|
38f64ea283 | ||
|
|
7e51b9b419 | ||
|
|
e1e96ebf31 | ||
|
|
e03f718222 | ||
|
|
9761f74713 | ||
|
|
ca4e24700a | ||
|
|
de31fe1cc7 | ||
|
|
c55b261cec | ||
|
|
6a313e0658 | ||
|
|
70460147f7 | ||
|
|
81d3bc7c9c | ||
|
|
1b93e42ce7 | ||
|
|
010270f03f | ||
|
|
8206aa1567 | ||
|
|
baa045eff3 | ||
|
|
97fa4cc048 | ||
|
|
dd91aa3ccf | ||
|
|
77e4d71f69 | ||
|
|
d0fbf0fe66 | ||
|
|
2d4b1bd4ec | ||
|
|
5de7a87231 | ||
|
|
cc4f295b59 | ||
|
|
f0c2926081 | ||
|
|
a7368fdd31 | ||
|
|
8482fec6be | ||
|
|
d76d953697 | ||
|
|
5664c1bd67 | ||
|
|
604859d17a | ||
|
|
861a1fbe3d | ||
|
|
ae7d8b1379 | ||
|
|
1259bc3057 | ||
|
|
7225052413 | ||
|
|
65d00bfff6 | ||
|
|
b287caeeec | ||
|
|
7c6efa9653 |
20 changed files with 1071 additions and 103 deletions
52
.github/workflows/build.yml
vendored
52
.github/workflows/build.yml
vendored
|
|
@ -20,12 +20,12 @@ on:
|
|||
|
||||
env:
|
||||
PRODUCT_NAME: CrystalFetch
|
||||
BUILD_XCODE_PATH: /Applications/Xcode_14.2.app
|
||||
BUILD_XCODE_PATH: /Applications/Xcode_16.2.app
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: macos-12
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
|
@ -37,19 +37,19 @@ jobs:
|
|||
[[ "$(xcode-select -p)" == "${{ env.BUILD_XCODE_PATH }}"* ]] || sudo xcode-select -s "${{ env.BUILD_XCODE_PATH }}"
|
||||
- name: Build
|
||||
run: |
|
||||
xcodebuild archive -archivePath "$PRODUCT_NAME" -scheme "$PRODUCT_NAME" -configuration Release CODE_SIGN_IDENTITY="-" PRODUCT_BUNDLE_PREFIX="$PRODUCT_BUNDLE_PREFIX"
|
||||
xcodebuild archive -archivePath "$PRODUCT_NAME" -scheme "$PRODUCT_NAME" -configuration Release CODE_SIGN_IDENTITY="-" PRODUCT_BUNDLE_PREFIX="$PRODUCT_BUNDLE_PREFIX" ONLY_ACTIVE_ARCH=No
|
||||
tar -acf $PRODUCT_NAME.xcarchive.tgz $PRODUCT_NAME.xcarchive
|
||||
env:
|
||||
PRODUCT_NAME: ${{ env.PRODUCT_NAME }}
|
||||
PRODUCT_BUNDLE_PREFIX: ${{ vars.PRODUCT_BUNDLE_PREFIX }}
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.PRODUCT_NAME }}
|
||||
path: ${{ env.PRODUCT_NAME }}.xcarchive.tgz
|
||||
package:
|
||||
name: Package
|
||||
runs-on: macos-12
|
||||
runs-on: macos-15
|
||||
needs: [build]
|
||||
if: github.event_name == 'release' || github.event.inputs.test_release == 'true'
|
||||
steps:
|
||||
|
|
@ -60,6 +60,13 @@ jobs:
|
|||
with:
|
||||
p12-file-base64: ${{ secrets.SIGNING_CERTIFICATE_P12_DATA }}
|
||||
p12-password: ${{ secrets.SIGNING_CERTIFICATE_PASSWORD }}
|
||||
- name: Import App Store Connect API Key
|
||||
run: |
|
||||
mkdir -p ~/.appstoreconnect/private_keys
|
||||
echo $AUTHKEY_API_KEY | base64 --decode -o ~/.appstoreconnect/private_keys/AuthKey_$API_KEY.p8
|
||||
env:
|
||||
AUTHKEY_API_KEY: ${{ secrets.CONNECT_KEY }}
|
||||
API_KEY: ${{ vars.CONNECT_KEY_ID }}
|
||||
- name: Install Provisioning Profiles
|
||||
run: |
|
||||
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
||||
|
|
@ -68,7 +75,7 @@ jobs:
|
|||
PROFILE_DATA: ${{ vars.PROFILE_DATA }}
|
||||
PROFILE_UUID: ${{ vars.PROFILE_UUID }}
|
||||
- name: Download Artifact
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ env.PRODUCT_NAME }}
|
||||
- name: Package for Release
|
||||
|
|
@ -80,14 +87,16 @@ jobs:
|
|||
SIGNING_TEAM_ID: ${{ vars.SIGNING_TEAM_ID }}
|
||||
PROFILE_UUID: ${{ vars.PROFILE_UUID }}
|
||||
- name: Notarize app
|
||||
run: npx notarize-cli --file "$PRODUCT_NAME.dmg" --bundle-id "$BUNDLE_ID"
|
||||
run: |
|
||||
xcrun notarytool submit --issuer "$ISSUER_UUID" --key-id "$API_KEY" --key "~/.appstoreconnect/private_keys/AuthKey_$API_KEY.p8" --team-id "$SIGNING_TEAM_ID" --wait "$PRODUCT_NAME.dmg"
|
||||
xcrun stapler staple "$PRODUCT_NAME.dmg"
|
||||
env:
|
||||
BUNDLE_ID: ${{ vars.PRODUCT_BUNDLE_PREFIX }}.${{ env.PRODUCT_NAME }}
|
||||
NOTARIZE_USERNAME: ${{ secrets.SIGNING_USERNAME }}
|
||||
NOTARIZE_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
||||
SIGNING_TEAM_ID: ${{ vars.SIGNING_TEAM_ID }}
|
||||
ISSUER_UUID: ${{ vars.CONNECT_ISSUER_ID }}
|
||||
API_KEY: ${{ vars.CONNECT_KEY_ID }}
|
||||
- name: Upload Artifact
|
||||
if: github.event_name != 'release'
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.PRODUCT_NAME }}-dmg
|
||||
path: ${{ env.PRODUCT_NAME }}.dmg
|
||||
|
|
@ -103,17 +112,24 @@ jobs:
|
|||
asset_content_type: application/octet-stream
|
||||
submit:
|
||||
name: Submit
|
||||
runs-on: macos-12
|
||||
runs-on: macos-15
|
||||
needs: [build]
|
||||
if: github.event_name == 'release' || github.event.inputs.test_release == 'true'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Import signing certificate into keychain
|
||||
uses: apple-actions/import-codesign-certs@v1
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.SIGNING_CERTIFICATE_P12_DATA }}
|
||||
p12-password: ${{ secrets.SIGNING_CERTIFICATE_PASSWORD }}
|
||||
- name: Import App Store Connect API Key
|
||||
run: |
|
||||
mkdir -p ~/.appstoreconnect/private_keys
|
||||
echo $AUTHKEY_API_KEY | base64 --decode -o ~/.appstoreconnect/private_keys/AuthKey_$API_KEY.p8
|
||||
env:
|
||||
AUTHKEY_API_KEY: ${{ secrets.CONNECT_KEY }}
|
||||
API_KEY: ${{ vars.CONNECT_KEY_ID }}
|
||||
- name: Install Provisioning Profiles
|
||||
run: |
|
||||
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
||||
|
|
@ -122,7 +138,7 @@ jobs:
|
|||
PROFILE_DATA: ${{ vars.APP_STORE_PROFILE_DATA }}
|
||||
PROFILE_UUID: ${{ vars.APP_STORE_PROFILE_UUID }}
|
||||
- name: Download Artifact
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ env.PRODUCT_NAME }}
|
||||
- name: Package for App Store
|
||||
|
|
@ -135,15 +151,15 @@ jobs:
|
|||
PROFILE_UUID: ${{ vars.APP_STORE_PROFILE_UUID }}
|
||||
- name: Upload Artifact
|
||||
if: github.event_name != 'release'
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.PRODUCT_NAME }}-pkg
|
||||
path: ${{ env.PRODUCT_NAME }}.pkg
|
||||
- name: Upload app to App Store Connect
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
xcrun altool --upload-app -t macos -f "$PRODUCT_NAME.pkg" -u "$SUBMIT_USERNAME" -p "$SUBMIT_PASSWORD"
|
||||
xcrun altool --upload-app -t macos -f "$PRODUCT_NAME.pkg" --apiKey "$API_KEY" --apiIssuer "$ISSUER_UUID"
|
||||
env:
|
||||
PRODUCT_NAME: ${{ env.PRODUCT_NAME }}
|
||||
SUBMIT_USERNAME: ${{ secrets.SIGNING_USERNAME }}
|
||||
SUBMIT_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
||||
ISSUER_UUID: ${{ vars.CONNECT_ISSUER_ID }}
|
||||
API_KEY: ${{ vars.CONNECT_KEY_ID }}
|
||||
|
|
|
|||
2
.gitmodules
vendored
2
.gitmodules
vendored
|
|
@ -12,7 +12,7 @@
|
|||
url = https://github.com/ebiggers/wimlib
|
||||
[submodule "converter"]
|
||||
path = converter
|
||||
url = https://github.com/uup-dump/converter.git
|
||||
url = https://git.uupdump.net/uup-dump/converter.git
|
||||
[submodule "OpenSSL"]
|
||||
path = OpenSSL
|
||||
url = https://github.com/krzyzanowskim/OpenSSL.git
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
// Configuration settings file format documentation can be found at:
|
||||
// https://help.apple.com/xcode/#/dev745c5c974
|
||||
|
||||
MARKETING_VERSION = 2.0.0
|
||||
CURRENT_PROJECT_VERSION = 3
|
||||
MARKETING_VERSION = 2.2.0
|
||||
CURRENT_PROJECT_VERSION = 6
|
||||
|
||||
#include? "CodeSigning.xcconfig"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
844A8F032A86E86F009A389C /* EULAView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844A8F022A86E86F009A389C /* EULAView.swift */; };
|
||||
844A8F062A86F92F009A389C /* esd2iso.sh in Resources */ = {isa = PBXBuildFile; fileRef = 844A8F052A86F92F009A389C /* esd2iso.sh */; };
|
||||
84EB35722A870EA7004F252E /* ShowWindowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EB35712A870EA6004F252E /* ShowWindowButtonView.swift */; };
|
||||
CE39C8A22D97003600A83CE8 /* MCTCatalogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE39C8A12D97003600A83CE8 /* MCTCatalogs.swift */; };
|
||||
CEC09F0F2A6BB66200980857 /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC09F0E2A6BB66200980857 /* Main.swift */; };
|
||||
CEC09F112A6BB66200980857 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC09F102A6BB66200980857 /* ContentView.swift */; };
|
||||
CEC09F132A6BB66300980857 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CEC09F122A6BB66300980857 /* Assets.xcassets */; };
|
||||
|
|
@ -366,6 +367,7 @@
|
|||
CEC0A3002A70A6CD00980857 /* convert.sh in Copy Scripts */ = {isa = PBXBuildFile; fileRef = CEC0A2FE2A70A6CD00980857 /* convert.sh */; };
|
||||
CEC0A3012A70A6CD00980857 /* convert_ve_plugin in Copy Scripts */ = {isa = PBXBuildFile; fileRef = CEC0A2FF2A70A6CD00980857 /* convert_ve_plugin */; };
|
||||
CEC8BC502A7B55D80042878F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CEC8BC522A7B55D80042878F /* Localizable.strings */; };
|
||||
FFB1B52A2A929CEC00B95D56 /* CrystalFetch-InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFB1B5282A929CEC00B95D56 /* CrystalFetch-InfoPlist.strings */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
|
@ -498,6 +500,7 @@
|
|||
844A8F022A86E86F009A389C /* EULAView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EULAView.swift; sourceTree = "<group>"; };
|
||||
844A8F052A86F92F009A389C /* esd2iso.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = esd2iso.sh; sourceTree = "<group>"; };
|
||||
84EB35712A870EA6004F252E /* ShowWindowButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowWindowButtonView.swift; sourceTree = "<group>"; };
|
||||
CE39C8A12D97003600A83CE8 /* MCTCatalogs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCTCatalogs.swift; sourceTree = "<group>"; };
|
||||
CEC09F0B2A6BB66200980857 /* CrystalFetch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CrystalFetch.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
CEC09F0E2A6BB66200980857 /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Main.swift; sourceTree = "<group>"; };
|
||||
CEC09F102A6BB66200980857 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -854,6 +857,10 @@
|
|||
CEC0A3082A71BBA900980857 /* Build.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Build.xcconfig; sourceTree = "<group>"; };
|
||||
CEC8BC512A7B55D80042878F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
CEC8BC532A7B56280042878F /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
F65FBD842AA77422007637B0 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
F65FBD852AA77426007637B0 /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
FFB1B5272A929A2800B95D56 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
FFB1B5292A929CEC00B95D56 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/CrystalFetch-InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
|
@ -910,6 +917,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
844A8EFA2A860F91009A389C /* ESDCatalog.swift */,
|
||||
CE39C8A12D97003600A83CE8 /* MCTCatalogs.swift */,
|
||||
);
|
||||
path = ESDCatalog;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -971,6 +979,7 @@
|
|||
CEC09F122A6BB66300980857 /* Assets.xcassets */,
|
||||
CEC09F172A6BB66300980857 /* CrystalFetch.entitlements */,
|
||||
CEC09F3E2A6F151800980857 /* CrystalFetch-Info.plist */,
|
||||
FFB1B5282A929CEC00B95D56 /* CrystalFetch-InfoPlist.strings */,
|
||||
CEC8BC522A7B55D80042878F /* Localizable.strings */,
|
||||
);
|
||||
path = Source;
|
||||
|
|
@ -1542,6 +1551,9 @@
|
|||
en,
|
||||
Base,
|
||||
ja,
|
||||
"zh-Hant",
|
||||
"zh-Hans",
|
||||
"zh-HK",
|
||||
);
|
||||
mainGroup = CEC09F022A6BB66200980857;
|
||||
packageReferences = (
|
||||
|
|
@ -1568,6 +1580,7 @@
|
|||
844A8F062A86F92F009A389C /* esd2iso.sh in Resources */,
|
||||
CEC09F132A6BB66300980857 /* Assets.xcassets in Resources */,
|
||||
CEC8BC502A7B55D80042878F /* Localizable.strings in Resources */,
|
||||
FFB1B52A2A929CEC00B95D56 /* CrystalFetch-InfoPlist.strings in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -1578,6 +1591,7 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
CE39C8A22D97003600A83CE8 /* MCTCatalogs.swift in Sources */,
|
||||
CEC09F112A6BB66200980857 /* ContentView.swift in Sources */,
|
||||
CEC09F312A6DC8FB00980857 /* UUPPackage.swift in Sources */,
|
||||
844A8EFF2A86CA8C009A389C /* SimpleContentView.swift in Sources */,
|
||||
|
|
@ -1996,10 +2010,21 @@
|
|||
children = (
|
||||
CEC8BC512A7B55D80042878F /* en */,
|
||||
CEC8BC532A7B56280042878F /* ja */,
|
||||
FFB1B5272A929A2800B95D56 /* zh-Hant */,
|
||||
F65FBD842AA77422007637B0 /* zh-Hans */,
|
||||
F65FBD852AA77426007637B0 /* zh-HK */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FFB1B5282A929CEC00B95D56 /* CrystalFetch-InfoPlist.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
FFB1B5292A929CEC00B95D56 /* zh-Hant */,
|
||||
);
|
||||
name = "CrystalFetch-InfoPlist.strings";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
|
|
@ -2253,6 +2278,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_PREFIX:default=llc.turing).CrystalFetch.mkisofs";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
WARNING_CFLAGS = "-Wno-error=incompatible-function-pointer-types";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
|
@ -2296,6 +2322,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_PREFIX:default=llc.turing).CrystalFetch.mkisofs";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
WARNING_CFLAGS = "-Wno-error=incompatible-function-pointer-types";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
#
|
||||
# w11arm_esd2iso - download and convert Microsoft ESD files for Windows 11 ARM to ISO
|
||||
#
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
# Microsoft Product catalog from b0gdanw "ESD to ISO on macOS.txt" https://gist.github.com/b0gdanw/e36ea84828dbd19e03eff6158f1fc77c
|
||||
#
|
||||
|
||||
versionID="4.0.2 (13-July-2023)"
|
||||
versionID="4.0.3 (17-September-2023)"
|
||||
version="w11arm_esd2iso ${versionID}\n"
|
||||
verbosityLevel=0
|
||||
|
||||
|
|
@ -33,13 +33,13 @@ declare -a lTags
|
|||
declare -a lDesc
|
||||
|
||||
usage() {
|
||||
echo "Usage:\n"
|
||||
echo "$0 [-v]"
|
||||
echo "$0 [-Vh]"
|
||||
echo "\nOptions:"
|
||||
echo "\t-h\tPrint usage and exit"
|
||||
echo "\t-v\tEnable verbose output"
|
||||
echo "\t-V\tPrint program version and exit"
|
||||
echo "Usage:\n"
|
||||
echo "$0 [-v]"
|
||||
echo "$0 [-Vh]"
|
||||
echo "\nOptions:"
|
||||
echo "\t-h\tPrint usage and exit"
|
||||
echo "\t-v\tEnable verbose output"
|
||||
echo "\t-V\tPrint program version and exit"
|
||||
}
|
||||
printVersion() {
|
||||
echo $version
|
||||
|
|
@ -76,7 +76,9 @@ extractEsd(){
|
|||
|
||||
esdImageCount=$(wimlib-imagex info $eFile | $awk '/Image Count:/ {print $3}')
|
||||
verboseOn && echo "[DEBUG] image count in ESD: $esdImageCount"
|
||||
(( $esdImageCount == 6 )) && images+=("6")
|
||||
for (( i = 6; i <= esdImageCount; i++ )); do
|
||||
images+=("$i")
|
||||
done
|
||||
|
||||
#---------------
|
||||
# Extract image 1 in the ESD to create the boot environment
|
||||
|
|
@ -160,8 +162,8 @@ buildIso(){
|
|||
iFile=$2
|
||||
|
||||
if [ -e $iFile ]; then
|
||||
echo "\t[INFO] File $iFile exists, removing it"
|
||||
rm -rf $iFile
|
||||
echo "\t[INFO] File $iFile exists, removing it"
|
||||
rm -rf $iFile
|
||||
fi
|
||||
|
||||
elToritoBootFile=$iDir/efi/microsoft/boot/efisys.bin
|
||||
|
|
@ -182,6 +184,17 @@ buildIso(){
|
|||
#
|
||||
#-------------------
|
||||
|
||||
#-------------------
|
||||
# Bug fix - 16-Sept-2023 PER
|
||||
#
|
||||
# aria2c seems to have an issue with Sonoma: https://github.com/aria2/aria2/issues/2083
|
||||
# This results in an error 134 that was displayed when trying to download the
|
||||
# Windows 11 ARM catalog from Microsoft.
|
||||
#
|
||||
# suggested workaround is to set LC_MESSAGES environment variable
|
||||
#-------------------
|
||||
|
||||
export LC_MESSAGES="C"
|
||||
|
||||
#-------------------
|
||||
#
|
||||
|
|
@ -190,31 +203,29 @@ buildIso(){
|
|||
#-------------------
|
||||
|
||||
while getopts ":hr:vV" opt; do
|
||||
case $opt in
|
||||
h)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
|
||||
v)
|
||||
let verbosityLevel+=1
|
||||
;;
|
||||
V)
|
||||
printVersion
|
||||
exit 1
|
||||
;;
|
||||
:)
|
||||
echo "[ERROR] Option -$OPTARG requires an argument"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
|
||||
\?)
|
||||
echo "[ERROR] Invalid option: -$OPTARG\n"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
case $opt in
|
||||
h)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
v)
|
||||
let verbosityLevel+=1
|
||||
;;
|
||||
V)
|
||||
printVersion
|
||||
exit 1
|
||||
;;
|
||||
:)
|
||||
echo "[ERROR] Option -$OPTARG requires an argument"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
\?)
|
||||
echo "[ERROR] Invalid option: -$OPTARG\n"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift "$((OPTIND-1))"
|
||||
|
||||
|
|
@ -291,8 +302,8 @@ buildIso $extDir $isoFile $isoLabel
|
|||
retVal=$?
|
||||
if (( retVal != 0 )); then
|
||||
echo "[ERROR] ISO was NOT created"
|
||||
echo "Working directory $workingDir was not deleted, use for debugging"
|
||||
exit 1
|
||||
echo "Working directory $workingDir was not deleted, use for debugging"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Step 4 complete - ISO created"
|
||||
|
|
|
|||
32
README.zh-HK.md
Normal file
32
README.zh-HK.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
CrystalFetch
|
||||
============
|
||||
[][1]
|
||||
|
||||
CrystalFetch 是一個 macOS 應用程式,用於製作 Windows® 11 安裝程式 ISO 映像檔。它可以與 [UTM 虛擬電腦][3]及其他虛擬電腦解決方案一齊使用。
|
||||
|
||||
注意:CrystalFetch 與 Microsoft 無關聯,安裝 Windows® 11 需要有效的許可證(產品金鑰)。
|
||||
|
||||
<p align="center">
|
||||
<img alt="CrystalFetch logo" src="Source/Assets.xcassets/AppIcon.appiconset/icon_128x128.png" srcset="Source/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x@2x.png 2x" /><br />
|
||||
<img alt="CrystalFetch screenshot" src="Extras/screen.png" />
|
||||
</p>
|
||||
|
||||
構建
|
||||
--------
|
||||
1. 確保你使用 `git submodule update --init` 來獲取子模組。
|
||||
2. 如你有 Apple Developer 付費賬戶,請複製 `CodeSigning.xcconfig.sample` 到 `CodeSigning.xcconfig`,並在此檔案中填寫你的開發人員訊息。
|
||||
3. 如你沒有 Apple Developer 付費賬戶,你需要禁用庫驗證。對於項目中的每一個構建目標,轉至“Signing & Capabilities”,之後選取“Disable Library Validation”核取方塊。
|
||||
4. 現在你就可以由 Xcode 構建並執行此項目了。
|
||||
|
||||
致謝
|
||||
-------
|
||||
CrystalFetch 使用了 [UUPDump][3] 的 API 與轉換程式碼。
|
||||
|
||||
CrystalFetch 使用了由 Technogeezer 製作的 [esd2iso][4]。
|
||||
|
||||
此項目與 Microsoft Corporation 無關聯。Windows® 為 Microsoft Corporation 的註冊商標。
|
||||
|
||||
[1]: https://github.com/TuringSoftware/CrystalFetch/actions?query=event%3Arelease+workflow%3ABuild
|
||||
[2]: https://mac.getutm.app
|
||||
[3]: https://uupdump.net
|
||||
[4]: https://communities.vmware.com/t5/VMware-Fusion-Documents/w11arm-esd2iso-a-utility-to-create-Windows-11-ARM-ISOs-from/ta-p/2957381
|
||||
32
README.zh-Hans.md
Normal file
32
README.zh-Hans.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
CrystalFetch
|
||||
============
|
||||
[][1]
|
||||
|
||||
CrystalFetch 是一个用于创建 Windows® 11 安装 ISO 映像的 macOS 应用程序。它可以与 [UTM 虚拟机][3]及其他虚拟机解决方案一起使用。
|
||||
|
||||
注意:CrystalFetch 不隶属于 Microsoft(微软)。安装 Windows® 11 需要有效的许可证(产品密钥)。
|
||||
|
||||
<p align="center">
|
||||
<img alt="CrystalFetch logo" src="Source/Assets.xcassets/AppIcon.appiconset/icon_128x128.png" srcset="Source/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x@2x.png 2x" /><br />
|
||||
<img alt="CrystalFetch screenshot" src="Extras/screen.png" />
|
||||
</p>
|
||||
|
||||
编译
|
||||
--------
|
||||
1. 确保使用 `git submodule update --init` 来获取子模块。
|
||||
2. 若你有付费的 Apple Developer 账号,请将 `CodeSigning.xcconfig.sample` 拷贝到 `CodeSigning.xcconfig`,并用你的开发者信息来填写该文件。
|
||||
3. 若你没有付费的 Apple Developer 账号,需要禁用库验证(library validation)。对于此项目中的每个编译目标(build target),请前往“Signing & Capabilities”并勾选“Disable Library Validation”。
|
||||
4. 现在可以从 Xcode 编译和运行此项目了。
|
||||
|
||||
致谢
|
||||
-------
|
||||
CrystalFetch 使用了 [UUPDump][3] 的 API 与转换脚本。
|
||||
|
||||
CrystalFetch 使用了由 Technogeezer 编写的 [esd2iso][4]。
|
||||
|
||||
此项目不隶属于 Microsoft Corporation。Windows® 是 Microsoft Corporation 的注册商标。
|
||||
|
||||
[1]: https://github.com/TuringSoftware/CrystalFetch/actions?query=event%3Arelease+workflow%3ABuild
|
||||
[2]: https://mac.getutm.app
|
||||
[3]: https://uupdump.net
|
||||
[4]: https://communities.vmware.com/t5/VMware-Fusion-Documents/w11arm-esd2iso-a-utility-to-create-Windows-11-ARM-ISOs-from/ta-p/2957381
|
||||
|
|
@ -52,7 +52,7 @@ struct BuildConfigView: View {
|
|||
}.padding(.bottom, 5)
|
||||
Section("Language") {
|
||||
Picker("", selection: $selectedLocale) {
|
||||
ForEach(details.languages) { language in
|
||||
ForEach(details.sortedLanguages) { language in
|
||||
Text(language.display).tag(language.code)
|
||||
}
|
||||
}.onChange(of: selectedLocale) { newValue in
|
||||
|
|
@ -78,6 +78,9 @@ struct BuildConfigView: View {
|
|||
}))
|
||||
}
|
||||
}
|
||||
if build.arch == "arm64" && Float(build.build) ?? 0 < 21390.0 {
|
||||
Text("Note: This build does not work for virtualization on Apple Silicon.")
|
||||
}
|
||||
}.disabled(worker.isBusy)
|
||||
}
|
||||
Spacer()
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ struct BuildDetails {
|
|||
let arch: String
|
||||
let build: String
|
||||
let created: Date
|
||||
|
||||
var sortedLanguages: [Language] {
|
||||
return languages.sorted(using: KeyPathComparator(\.display))
|
||||
}
|
||||
|
||||
static var empty = BuildDetails()
|
||||
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ actor Downloader {
|
|||
try FileManager.default.moveItem(at: resultUrl, to: destinationUrl)
|
||||
} catch {
|
||||
let error = error as NSError
|
||||
NSLog("Downloading %@ failed: ", debugIdentifier, error.localizedDescription)
|
||||
NSLog("Downloading %@ failed: %@", debugIdentifier, error.localizedDescription)
|
||||
if retry > 0 {
|
||||
let newTask: URLSessionDownloadTask
|
||||
if let resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
|
||||
|
|
|
|||
246
Source/ESDCatalog/MCTCatalogs.swift
Normal file
246
Source/ESDCatalog/MCTCatalogs.swift
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
//
|
||||
// Copyright © 2025 Turing Software, LLC. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
actor MCTCatalogs {
|
||||
struct Version: Equatable {
|
||||
let latestCabUrl: URL?
|
||||
let releases: [Release]
|
||||
}
|
||||
|
||||
struct Release: Equatable, Hashable, Identifiable {
|
||||
let build: String
|
||||
let date: Date?
|
||||
let cabUrl: URL
|
||||
|
||||
var id: Int {
|
||||
hashValue
|
||||
}
|
||||
}
|
||||
|
||||
enum Windows: Int {
|
||||
case windows10 = 10
|
||||
case windows11 = 11
|
||||
}
|
||||
|
||||
private(set) var versions: [Windows: Version] = [:]
|
||||
|
||||
init(from data: Data) async throws {
|
||||
let result = try await withCheckedThrowingContinuation { continuation in
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
let parser = XMLParser(data: data)
|
||||
let coordinator = MCTCatalogsParser()
|
||||
coordinator.continuation = continuation
|
||||
parser.delegate = coordinator
|
||||
if !parser.parse() && coordinator.continuation != nil {
|
||||
continuation.resume(throwing: parser.parserError ?? ESDCatalogError.unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
let results = try result.map { try Self.parseVersion($0) }
|
||||
for (number, version) in results {
|
||||
if let windows = Windows(rawValue: number) {
|
||||
versions[windows] = version
|
||||
} else {
|
||||
NSLog("Ignoring unknown Windows version: %@", number)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func parseVersion(_ result: MCTCatalogsParser.Version) throws -> (number: Int, version: Version) {
|
||||
let latestCabUrl: URL?
|
||||
if let latestCabLink = result.latestCabLink {
|
||||
latestCabUrl = URL(string: latestCabLink)!
|
||||
} else {
|
||||
latestCabUrl = nil
|
||||
}
|
||||
guard let number = Int(result.number) else {
|
||||
throw ESDCatalogError.missingElement("number", "version")
|
||||
}
|
||||
let releases = try result.releases.map { try Self.parseRelease($0) }
|
||||
return (number, Version(latestCabUrl: latestCabUrl, releases: releases))
|
||||
}
|
||||
|
||||
private static func parseRelease(_ result: MCTCatalogsParser.Release) throws -> Release {
|
||||
let date: Date?
|
||||
if let dateString = result.date {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyyMMdd"
|
||||
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
date = dateFormatter.date(from: dateString)
|
||||
} else {
|
||||
date = nil
|
||||
}
|
||||
guard let cabLink = result.cabLink else {
|
||||
throw ESDCatalogError.missingElement("cabLink", "release")
|
||||
}
|
||||
let cabUrl: URL = URL(string: cabLink)!
|
||||
return Release(build: result.build, date: date, cabUrl: cabUrl)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate class MCTCatalogsParser: NSObject, XMLParserDelegate {
|
||||
enum Inside: String {
|
||||
case productsDb
|
||||
case versions
|
||||
case version
|
||||
case latestCabLink
|
||||
case releases
|
||||
case release
|
||||
case date
|
||||
case cabLink
|
||||
}
|
||||
|
||||
struct Version {
|
||||
var number: String
|
||||
var latestCabLink: String?
|
||||
var releases: [Release] = []
|
||||
}
|
||||
|
||||
struct Release {
|
||||
var build: String
|
||||
var date: String?
|
||||
var cabLink: String?
|
||||
}
|
||||
|
||||
var continuation: CheckedContinuation<[Version], Error>?
|
||||
private var inside: Inside?
|
||||
private var versions: [Version] = []
|
||||
private var currentVersion: Version?
|
||||
private var currentRelease: Release?
|
||||
|
||||
func parserDidStartDocument(_ xmlParser: XMLParser) {
|
||||
inside = nil
|
||||
}
|
||||
|
||||
func parserDidEndDocument(_ xmlParser: XMLParser) {
|
||||
if let c = continuation {
|
||||
continuation = nil
|
||||
c.resume(returning: versions)
|
||||
}
|
||||
}
|
||||
|
||||
func parser(_ xmlParser: XMLParser, parseErrorOccurred parseError: Error) {
|
||||
if let c = continuation {
|
||||
continuation = nil
|
||||
c.resume(throwing: parseError)
|
||||
}
|
||||
xmlParser.abortParsing()
|
||||
}
|
||||
|
||||
func parser(_ xmlParser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
|
||||
switch inside {
|
||||
case nil:
|
||||
guard expect(elementName, named: .productsDb, in: xmlParser) else {
|
||||
return
|
||||
}
|
||||
inside = .productsDb
|
||||
case .productsDb:
|
||||
guard expect(elementName, named: .versions, in: xmlParser) else {
|
||||
return
|
||||
}
|
||||
inside = .versions
|
||||
case .versions:
|
||||
guard expect(elementName, named: .version, in: xmlParser) else {
|
||||
return
|
||||
}
|
||||
guard let number = attributeDict["number"] else {
|
||||
parser(xmlParser, parseErrorOccurred: ESDCatalogError.attributeNotFound("number"))
|
||||
return
|
||||
}
|
||||
inside = .version
|
||||
currentVersion = Version(number: number)
|
||||
case .version:
|
||||
guard let seen = expect(elementName, namedOneOf: [.latestCabLink, .releases], in: xmlParser) else {
|
||||
return
|
||||
}
|
||||
inside = seen
|
||||
case .releases:
|
||||
guard expect(elementName, named: .release, in: xmlParser) else {
|
||||
return
|
||||
}
|
||||
guard let build = attributeDict["build"] else {
|
||||
parser(xmlParser, parseErrorOccurred: ESDCatalogError.attributeNotFound("build"))
|
||||
return
|
||||
}
|
||||
inside = .release
|
||||
currentRelease = Release(build: build)
|
||||
case .release:
|
||||
guard let seen = expect(elementName, namedOneOf: [.date, .cabLink], in: xmlParser) else {
|
||||
return
|
||||
}
|
||||
inside = seen
|
||||
default:
|
||||
parser(xmlParser, parseErrorOccurred: ESDCatalogError.invalidElement(elementName))
|
||||
}
|
||||
}
|
||||
|
||||
func parser(_ xmlParser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
|
||||
guard elementName == inside?.rawValue else {
|
||||
parser(xmlParser, parseErrorOccurred: ESDCatalogError.invalidElement(elementName))
|
||||
return
|
||||
}
|
||||
switch inside! {
|
||||
case .productsDb:
|
||||
inside = nil
|
||||
case .versions:
|
||||
inside = .productsDb
|
||||
case .version:
|
||||
versions.append(currentVersion!)
|
||||
currentVersion = nil
|
||||
inside = .versions
|
||||
case .latestCabLink, .releases:
|
||||
inside = .version
|
||||
case .release:
|
||||
currentVersion!.releases.append(currentRelease!)
|
||||
currentRelease = nil
|
||||
inside = .releases
|
||||
case .date, .cabLink:
|
||||
inside = .release
|
||||
}
|
||||
}
|
||||
|
||||
func parser(_ xmlParser: XMLParser, foundCharacters string: String) {
|
||||
if inside == .latestCabLink {
|
||||
currentVersion!.latestCabLink = string
|
||||
} else if inside == .date {
|
||||
currentRelease!.date = string
|
||||
} else if inside == .cabLink {
|
||||
currentRelease!.cabLink = string
|
||||
} else {
|
||||
parser(xmlParser, parseErrorOccurred: ESDCatalogError.unexpectedString(string, inside?.rawValue ?? ""))
|
||||
}
|
||||
}
|
||||
|
||||
private func expect(_ elementName: String, named element: Inside, in xmlParser: XMLParser) -> Bool {
|
||||
guard elementName == element.rawValue else {
|
||||
parser(xmlParser, parseErrorOccurred: ESDCatalogError.invalidElement(elementName))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func expect(_ elementName: String, namedOneOf elements: [Inside], in xmlParser: XMLParser) -> Inside? {
|
||||
for element in elements {
|
||||
if elementName == element.rawValue {
|
||||
return element
|
||||
}
|
||||
}
|
||||
parser(xmlParser, parseErrorOccurred: ESDCatalogError.invalidElement(elementName))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,8 @@ import SwiftUI
|
|||
@main
|
||||
struct Main: App {
|
||||
@StateObject private var worker = Worker()
|
||||
|
||||
@AppStorage("ShowAdvancedOptions") private var showAdvancedOptions: Bool = false
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup(id: "ESDConvert") {
|
||||
SimpleContentView().environmentObject(worker)
|
||||
|
|
@ -31,6 +32,9 @@ struct Main: App {
|
|||
.frame(minWidth: 800, minHeight: 400)
|
||||
}.commands {
|
||||
SidebarCommands()
|
||||
CommandGroup(after: .sidebar) {
|
||||
Toggle("Show Advanced Options", isOn: $showAdvancedOptions)
|
||||
}
|
||||
}.handlesExternalEvents(matching: Set(["UUPDump"]))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,24 +18,33 @@ import SwiftUI
|
|||
|
||||
struct SimpleContentView: View {
|
||||
@EnvironmentObject private var worker: Worker
|
||||
@AppStorage("ShowAdvancedOptions") private var showAdvancedOptions: Bool = false
|
||||
@State private var isConfirmCancelShown: Bool = false
|
||||
@State private var isDownloadCompleted: Bool = false
|
||||
|
||||
@State private var isWindows10: Bool = false
|
||||
@State private var windowsVersion: MCTCatalogs.Windows = .windows11
|
||||
@State private var selectedBuild: MCTCatalogs.Release?
|
||||
@State private var selected: SelectedTuple = .default
|
||||
@State private var selectedBuild: ESDCatalog.File?
|
||||
@State private var selectedFile: ESDCatalog.File?
|
||||
@State private var selectedEula: SelectedEULA?
|
||||
|
||||
@State private var languages: [DisplayString] = []
|
||||
@State private var editions: [DisplayString] = []
|
||||
|
||||
|
||||
private let dateFormatter: DateFormatter = {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "dd-MM-yyyy"
|
||||
return dateFormatter
|
||||
}()
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Form {
|
||||
Picker("Version", selection: $isWindows10) {
|
||||
Text("Windows® 11").tag(false)
|
||||
Text("Windows® 10").tag(true)
|
||||
Picker("Version", selection: $windowsVersion) {
|
||||
Text("Windows® 11").tag(MCTCatalogs.Windows.windows11)
|
||||
Text("Windows® 10").tag(MCTCatalogs.Windows.windows10)
|
||||
}.pickerStyle(.radioGroup)
|
||||
buildsPicker
|
||||
Picker("Architecture", selection: $selected.architecture) {
|
||||
if hasArchitecture("ARM64") {
|
||||
Text("Apple Silicon").tag("ARM64")
|
||||
|
|
@ -57,9 +66,19 @@ struct SimpleContentView: View {
|
|||
Text(edition.display).tag(edition.id)
|
||||
}
|
||||
}
|
||||
if windowsVersion == .windows10 && selected.architecture == "ARM64" {
|
||||
Text("Note: This build does not work for virtualization on Apple Silicon.")
|
||||
}
|
||||
}.disabled(worker.isBusy)
|
||||
.onChange(of: isWindows10) { newValue in
|
||||
worker.refreshEsdCatalog(windows10: newValue)
|
||||
.onChange(of: windowsVersion) { newValue in
|
||||
if selectedBuild == nil {
|
||||
worker.refreshEsdCatalog(windowsVersion: newValue)
|
||||
} else {
|
||||
selectedBuild = nil
|
||||
}
|
||||
}
|
||||
.onChange(of: selectedBuild) { newValue in
|
||||
worker.refreshEsdCatalog(windowsVersion: windowsVersion, release: newValue)
|
||||
}
|
||||
.onChange(of: worker.esdCatalog) { _ in
|
||||
refreshList()
|
||||
|
|
@ -85,10 +104,12 @@ struct SimpleContentView: View {
|
|||
}
|
||||
}
|
||||
HStack {
|
||||
ShowWindowButtonView(id: "UUPDump") {
|
||||
Text("All builds…")
|
||||
}.disabled(worker.isBusy)
|
||||
.help("Build custom installation for any build through UUP Dump.")
|
||||
if showAdvancedOptions {
|
||||
ShowWindowButtonView(id: "UUPDump") {
|
||||
Text("All builds…")
|
||||
}.disabled(worker.isBusy)
|
||||
.help("Build custom installation for any build through UUP Dump.")
|
||||
}
|
||||
Spacer()
|
||||
if worker.isBusy {
|
||||
Button(role: .cancel) {
|
||||
|
|
@ -106,21 +127,21 @@ struct SimpleContentView: View {
|
|||
}
|
||||
} else {
|
||||
Button {
|
||||
if let eula = selectedBuild?.eula {
|
||||
if let eula = selectedFile?.eula {
|
||||
selectedEula = SelectedEULA(url: URL(string: eula)!)
|
||||
} else if let selectedBuild = selectedBuild {
|
||||
worker.download(selectedBuild)
|
||||
} else if let selectedFile = selectedFile {
|
||||
worker.download(selectedFile)
|
||||
}
|
||||
} label: {
|
||||
Text("Download…")
|
||||
}.disabled(selectedBuild == nil)
|
||||
}.disabled(selectedFile == nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(item: $selectedEula) { eula in
|
||||
EULAView(url: eula.url) {
|
||||
if let selectedBuild = selectedBuild {
|
||||
worker.download(selectedBuild)
|
||||
if let selectedFile = selectedFile {
|
||||
worker.download(selectedFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -129,6 +150,7 @@ struct SimpleContentView: View {
|
|||
}
|
||||
.padding()
|
||||
.onAppear {
|
||||
worker.refreshCatalogUrls()
|
||||
worker.refreshEsdCatalog()
|
||||
refreshList()
|
||||
}
|
||||
|
|
@ -146,11 +168,27 @@ struct SimpleContentView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ViewBuilder
|
||||
var buildsPicker: some View {
|
||||
if let builds = worker.mctCatalogs[windowsVersion]?.releases {
|
||||
Picker("Build", selection: $selectedBuild) {
|
||||
Text("Latest").tag(nil as MCTCatalogs.Release?)
|
||||
ForEach(builds) { build in
|
||||
if let date = build.date {
|
||||
Text("\(build.build) (\(dateFormatter.string(from: date)))").tag(build)
|
||||
} else {
|
||||
Text(build.build).tag(build)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var progressLabel: some View {
|
||||
if let selectedBuild = selectedBuild {
|
||||
Text(selectedBuild.name).font(.caption)
|
||||
if let selectedFile = selectedFile {
|
||||
Text(selectedFile.name).font(.caption)
|
||||
} else if !worker.isBusy {
|
||||
Text("No build found.").font(.caption)
|
||||
}
|
||||
|
|
@ -167,7 +205,7 @@ struct SimpleContentView: View {
|
|||
let languageFiltered = archFiltered.filter({ $0.languageCode == selected.language })
|
||||
let editionsList = languageFiltered.map({ DisplayString(id: $0.edition, display: $0.editionPretty )})
|
||||
editions = Set(editionsList).sorted(using: KeyPathComparator(\.display))
|
||||
selectedBuild = languageFiltered.first(where: { $0.edition == selected.edition })
|
||||
selectedFile = languageFiltered.first(where: { $0.edition == selected.edition })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -176,7 +214,7 @@ private struct DisplayString: Identifiable, Hashable, Equatable {
|
|||
var display: String
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
hasher.combine(display)
|
||||
}
|
||||
|
||||
static func ==(lhs: DisplayString, rhs: DisplayString) -> Bool {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ class Worker: ObservableObject {
|
|||
@Published private(set) var progress: Float?
|
||||
@Published private(set) var progressStatus: String?
|
||||
@Published var completedDownloadUrl: URL?
|
||||
|
||||
|
||||
@Published var mctCatalogs: [MCTCatalogs.Windows: MCTCatalogs.Version] = [:]
|
||||
@Published var esdCatalog: [ESDCatalog.File] = []
|
||||
|
||||
private let api = UUPDumpAPI()
|
||||
|
|
@ -170,10 +171,14 @@ class Worker: ObservableObject {
|
|||
}
|
||||
|
||||
private nonisolated func extractLine(from data: Data) -> String? {
|
||||
if let string = String(data: data, encoding: .ascii)?.filter({ $0.isASCII }) {
|
||||
if let string = String(data: data, encoding: .utf8) {
|
||||
let lines = string.split(whereSeparator: \.isNewline)
|
||||
if let line = lines.filter({ !$0.isEmpty }).last {
|
||||
return String(line)
|
||||
let stringLine = String(line)
|
||||
if let pattern = try? NSRegularExpression(pattern: "\\033\\[(0|1).*?m") {
|
||||
return pattern.stringByReplacingMatches(in: stringLine, range: NSRange(location: 0, length: stringLine.count), withTemplate: "")
|
||||
}
|
||||
return stringLine
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
@ -313,8 +318,31 @@ extension Worker {
|
|||
private var windows11CatalogUrl: URL {
|
||||
URL(string: "https://go.microsoft.com/fwlink?linkid=2156292")!
|
||||
}
|
||||
|
||||
func refreshEsdCatalog(windows10: Bool = false) {
|
||||
|
||||
private var worprojectUrl: URL {
|
||||
URL(string: "https://worproject.com/dldserv/esd/getversions.php")!
|
||||
}
|
||||
|
||||
func refreshCatalogUrls() {
|
||||
let cacheUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||
let catalogsUrl = cacheUrl.appendingPathComponent("mctcatalogs.xml")
|
||||
withBusyIndication { [self] in
|
||||
let fm = FileManager.default
|
||||
let downloader = Downloader()
|
||||
try? fm.removeItem(at: catalogsUrl)
|
||||
await downloader.enqueue(downloadUrl: worprojectUrl, to: catalogsUrl)
|
||||
do {
|
||||
try await downloader.start()
|
||||
let data = try Data(contentsOf: catalogsUrl)
|
||||
let catalogs = try await MCTCatalogs(from: data)
|
||||
mctCatalogs = await catalogs.versions
|
||||
} catch {
|
||||
NSLog("Ignoring error while trying to fetch MCT catalogs: %@", error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func refreshEsdCatalog(windowsVersion: MCTCatalogs.Windows = .windows11, release: MCTCatalogs.Release? = nil) {
|
||||
let cacheUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||
let catalogUrl = cacheUrl.appendingPathComponent("catalog.cab")
|
||||
let productsUrl = cacheUrl.appendingPathComponent("products.xml")
|
||||
|
|
@ -322,12 +350,29 @@ extension Worker {
|
|||
let fm = FileManager.default
|
||||
let downloader = Downloader()
|
||||
try? fm.removeItem(at: catalogUrl)
|
||||
if windows10 {
|
||||
await downloader.enqueue(downloadUrl: windows10CatalogUrl, to: catalogUrl)
|
||||
// if we specify a release, then use it
|
||||
if let release = release {
|
||||
await downloader.enqueue(downloadUrl: release.cabUrl, to: catalogUrl)
|
||||
try await downloader.start()
|
||||
} else {
|
||||
await downloader.enqueue(downloadUrl: windows11CatalogUrl, to: catalogUrl)
|
||||
// next try hard coded cab url
|
||||
if windowsVersion == .windows10 {
|
||||
await downloader.enqueue(downloadUrl: windows10CatalogUrl, to: catalogUrl)
|
||||
} else {
|
||||
await downloader.enqueue(downloadUrl: windows11CatalogUrl, to: catalogUrl)
|
||||
}
|
||||
do {
|
||||
try await downloader.start()
|
||||
} catch {
|
||||
// finally, see if we got a new latest cab url
|
||||
if let url = mctCatalogs[windowsVersion]?.latestCabUrl {
|
||||
await downloader.enqueue(downloadUrl: url, to: catalogUrl)
|
||||
try await downloader.start()
|
||||
} else {
|
||||
throw error // otherwise we throw the original error
|
||||
}
|
||||
}
|
||||
}
|
||||
try await downloader.start()
|
||||
try await exec("cabextract", "-d", cacheUrl.path, catalogUrl.path)
|
||||
let data = try Data(contentsOf: productsUrl)
|
||||
let esd = try await ESDCatalog(from: data)
|
||||
|
|
@ -337,7 +382,8 @@ extension Worker {
|
|||
|
||||
func download(_ file: ESDCatalog.File) {
|
||||
let cacheUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||
let baseUrl = cacheUrl.appendingPathComponent(UUID().uuidString)
|
||||
let uuid = file.sha1.count > 0 ? file.sha1 : UUID().uuidString
|
||||
let baseUrl = cacheUrl.appendingPathComponent(uuid)
|
||||
let esdUrl = baseUrl.appendingPathComponent(file.name)
|
||||
let isoUrl = esdUrl.deletingPathExtension().appendingPathExtension("iso")
|
||||
withBusyIndication { [self] in
|
||||
|
|
@ -380,7 +426,7 @@ extension Worker {
|
|||
if build.count > 32 {
|
||||
build = String(build.prefix(32))
|
||||
}
|
||||
try await exec(at: nil, executableURL: script, esdUrl.path, isoUrl.path, build)
|
||||
try await exec(at: nil, executableURL: script, "-v", esdUrl.path, isoUrl.path, build)
|
||||
completedDownloadUrl = isoUrl
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
|
||||
/* Sources */
|
||||
|
||||
// ESDCatalog/ESDCatalog.swift
|
||||
"Unknown parser error." = "不明な解析エラーです。";
|
||||
"Failed to parse element '%@'" = "要素“%@”の解析に失敗しました";
|
||||
"Attribute '%@' not found" = "属性“%@”が見つかりません";
|
||||
"Unexpected string '%@' for field '%@'" = "フィールド“%2$@”に予期しない文字列“%1$@”があります";
|
||||
"Localization '%@' not found" = "ローカリゼーション“%@”が見つかりません";
|
||||
"Missing element '%@' in '%@'" = "“%2$@”に要素“%1$@”がありません";
|
||||
|
||||
// UUPDump/UUPDumpAPI.swift
|
||||
"Cannot find data from the server response." = "サーバの応答からデータが見つかりません。";
|
||||
"Error returned from server: %@" = "サーバからエラーが返されました: %@";
|
||||
|
|
@ -16,24 +24,44 @@
|
|||
"Release Preview" = "Release Preview";
|
||||
"Retail" = "Retail";
|
||||
|
||||
// Main.swift
|
||||
"Show Advanced Options" = "詳細オプションを表示";
|
||||
|
||||
// ContentView.swift
|
||||
"Prerelease Builds" = "プレリリースビルド";
|
||||
"Show unstable releases which previews upcoming features." = "今後の機能をプレビューする不安定なリリースを表示します。";
|
||||
"Server Builds" = "サーバビルド";
|
||||
"Show builds for running in a server environment." = "サーバ環境で実行するためのビルドを表示します。";
|
||||
"Simple…" = "シンプル…";
|
||||
"Build installation for the latest release through ESD conversion." = "ESD変換により最新リリースのインストールをビルドします。";
|
||||
"Refresh" = "更新";
|
||||
|
||||
// SimpleContentView.swift
|
||||
"Version" = "バージョン";
|
||||
"Windows® 11" = "Windows® 11";
|
||||
"Windows® 10" = "Windows® 10";
|
||||
"Architecture" = "アーキテクチャ";
|
||||
"Language" = "言語";
|
||||
"Edition" = "エディション";
|
||||
"Note: This build does not work for virtualization on Apple Silicon." = "注意: このビルドはAppleシリコン上の仮想化では動作しません。";
|
||||
"All builds…" = "すべてのビルド…";
|
||||
"Build custom installation for any build through UUP Dump." = "UUP dumpにより任意のビルドのカスタムインストールをビルドします。";
|
||||
"Cancel" = "キャンセル";
|
||||
"Are you sure you want to stop the process?" = "処理を中止してもよろしいですか?";
|
||||
"Stop" = "中止";
|
||||
"Download…" = "ダウンロード…";
|
||||
"No build found." = "ビルドが見つかりません。";
|
||||
|
||||
// EULAView.swift
|
||||
"Failed to load EULA." = "使用許諾契約の読み込みに失敗しました。";
|
||||
"Accept" = "同意";
|
||||
|
||||
// BuildConfigView.swift
|
||||
"Channel" = "チャネル";
|
||||
"Build" = "ビルド";
|
||||
"Created" = "作成日時";
|
||||
"Language" = "言語";
|
||||
"Editions" = "エディション";
|
||||
"I agree that I have a valid license to use this product." = "この製品を使用するための有効なライセンスを所有していることに同意します。";
|
||||
"Cancel" = "キャンセル";
|
||||
"Are you sure you want to stop the process?" = "処理を中止してもよろしいですか?";
|
||||
"Stop" = "中止";
|
||||
"Download…" = "ダウンロード…";
|
||||
|
||||
// BuildDetails.swift
|
||||
"Unknown Language" = "不明な言語";
|
||||
|
|
|
|||
161
Source/zh-HK.lproj/Localizable.strings
Normal file
161
Source/zh-HK.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
/* No comment provided by engineer. */
|
||||
"Accept" = "接受";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"All builds…" = "所有構建⋯";
|
||||
|
||||
/* PrettyString */
|
||||
"Apple Silicon" = "Apple 晶片";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Architecture" = "架構";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Are you sure you want to stop the process?" = "要停止這個程序嗎?";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Attribute '%@' not found" = "未找到屬性「%@」";
|
||||
|
||||
/* PrettyString */
|
||||
"Beta" = "Beta";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Build" = "構建";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Build custom installation for any build through UUP Dump." = "透過 UUP Dump 為任意構建製作自定安裝映像檔。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Build installation for the latest release through ESD conversion." = "透過 ESD 轉換為最新的 Release 構建安裝映像檔。";
|
||||
|
||||
/* PrettyString */
|
||||
"Canary" = "Canary";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "取消";
|
||||
|
||||
/* UUPDumpAPI */
|
||||
"Cannot find data from the server response." = "無法由伺服器回應找到資料。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Channel" = "頻道";
|
||||
|
||||
/* Worker */
|
||||
"Converting download to ISO..." = "正在轉換下載的資料至 ISO⋯";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Created" = "製作時間";
|
||||
|
||||
/* PrettyString */
|
||||
"Development" = "Development";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Download…" = "下載⋯";
|
||||
|
||||
/* Worker */
|
||||
"Downloading %@ of %@..." = "正在下載 %1$@ / %2$@⋯";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Edition" = "版本";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Editions" = "版本";
|
||||
|
||||
/* UUPDumpAPI */
|
||||
"Error returned from server: %@" = "伺服器返回錯誤:%@";
|
||||
|
||||
/* EULAView */
|
||||
"Failed to load EULA." = "無法載入 EULA。";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Failed to parse element '%@'" = "無法解析元素「%@」";
|
||||
|
||||
/* Worker */
|
||||
"Fetching files list..." = "正在獲取檔案清單⋯";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"I agree that I have a valid license to use this product." = "我同意我擁有使用這個產品的有效許可。";
|
||||
|
||||
/* PrettyString */
|
||||
"Intel x64" = "Intel x64";
|
||||
|
||||
/* PrettyString */
|
||||
"Intel x86" = "Intel x86";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Language" = "語言";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Localization '%@' not found" = "無法找到本地化項目「%@」";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Missing element '%@' in '%@'" = "「%2$@」中遺失元素「%1$@」";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No build found." = "無法找到構建。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Note: This build does not work for virtualization on Apple Silicon." = "注意:這個構建不適用於 Apple 晶片上的虛擬化。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prerelease Builds" = "預先發布構建";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Refresh" = "重新整理";
|
||||
|
||||
/* PrettyString */
|
||||
"Release Preview" = "Release Preview";
|
||||
|
||||
/* PrettyString */
|
||||
"Retail" = "Retail";
|
||||
|
||||
/* Worker */
|
||||
"Saving ISO..." = "正在儲存 ISO⋯";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Server Builds" = "伺服器構建";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show Advanced Options" = "顯示進階選項";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show builds for running in a server environment." = "顯示用於伺服器環境運行的構建。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show unstable releases which previews upcoming features." = "顯示不穩定的發行版本,當中可以預覽即將推出的功能。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Simple…" = "簡化⋯";
|
||||
|
||||
/* Worker */
|
||||
"Starting download..." = "正在開始下載⋯";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Stop" = "停止";
|
||||
|
||||
/* Worker */
|
||||
"The conversion script failed with error code %d" = "無法執行轉換,錯誤碼為 %d";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Unexpected string '%@' for field '%@'" = "「%2$@」欄位當中出現意外字符串「%1$@」";
|
||||
|
||||
/* PrettyString */
|
||||
"Unknown" = "未知";
|
||||
|
||||
/* BuildEditions */
|
||||
"Unknown Edition" = "未知的版本";
|
||||
|
||||
/* BuildDetails */
|
||||
"Unknown Language" = "未知的語言";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Unknown parser error." = "未知的解析器錯誤。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Version" = "版本";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Windows® 10" = "Windows® 10";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Windows® 11" = "Windows® 11";
|
||||
161
Source/zh-Hans.lproj/Localizable.strings
Normal file
161
Source/zh-Hans.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
/* No comment provided by engineer. */
|
||||
"Accept" = "接受";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"All builds…" = "全部版本…";
|
||||
|
||||
/* PrettyString */
|
||||
"Apple Silicon" = "Apple 芯片";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Architecture" = "架构";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Are you sure you want to stop the process?" = "你确定要停止此进程吗?";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Attribute '%@' not found" = "属性“%@”未找到";
|
||||
|
||||
/* PrettyString */
|
||||
"Beta" = "Beta";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Build" = "构建版本";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Build custom installation for any build through UUP Dump." = "通过 UUP Dump 为任何构建版本创建自定义安装程序。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Build installation for the latest release through ESD conversion." = "通过 ESD 转换为最新版本构建安装程序。";
|
||||
|
||||
/* PrettyString */
|
||||
"Canary" = "Canary";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "取消";
|
||||
|
||||
/* UUPDumpAPI */
|
||||
"Cannot find data from the server response." = "无法从服务器响应中找到数据。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Channel" = "通道";
|
||||
|
||||
/* Worker */
|
||||
"Converting download to ISO..." = "正在将下载转换为 ISO…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Created" = "创建时间";
|
||||
|
||||
/* PrettyString */
|
||||
"Development" = "开发";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Download…" = "下载…";
|
||||
|
||||
/* Worker */
|
||||
"Downloading %@ of %@..." = "正在下载 %1$@,共 %2$@…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Edition" = "版本";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Editions" = "版本";
|
||||
|
||||
/* UUPDumpAPI */
|
||||
"Error returned from server: %@" = "服务器返回错误:%@";
|
||||
|
||||
/* EULAView */
|
||||
"Failed to load EULA." = "无法加载 EULA (最终用户许可协议)。";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Failed to parse element '%@'" = "无法解析元素“%@”";
|
||||
|
||||
/* Worker */
|
||||
"Fetching files list..." = "获取文件列表…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"I agree that I have a valid license to use this product." = "我同意我拥有使用此产品的有效许可证。";
|
||||
|
||||
/* PrettyString */
|
||||
"Intel x64" = "Intel x64";
|
||||
|
||||
/* PrettyString */
|
||||
"Intel x86" = "Intel x86";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Language" = "语言";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Localization '%@' not found" = "本地化条目“%@”未找到";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Missing element '%@' in '%@'" = "“%2$@”中缺少元素“%1$@”";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No build found." = "未找到构建版本。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Note: This build does not work for virtualization on Apple Silicon." = "注意:此版本无法在 Apple 芯片上用于虚拟化。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prerelease Builds" = "预发行构建版本";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Refresh" = "刷新";
|
||||
|
||||
/* PrettyString */
|
||||
"Release Preview" = "发行预览版";
|
||||
|
||||
/* PrettyString */
|
||||
"Retail" = "零售版";
|
||||
|
||||
/* Worker */
|
||||
"Saving ISO..." = "正在保存 ISO…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Server Builds" = "服务器构建版本";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show Advanced Options" = "显示高级选项";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show builds for running in a server environment." = "显示在服务器环境中运行的构建版本。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show unstable releases which previews upcoming features." = "显示不稳定的发行版本,其中可预览即将推出的功能。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Simple…" = "简单模式…";
|
||||
|
||||
/* Worker */
|
||||
"Starting download..." = "开始下载…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Stop" = "停止";
|
||||
|
||||
/* Worker */
|
||||
"The conversion script failed with error code %d" = "转换脚本失败,错误代码为 %d";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Unexpected string '%@' for field '%@'" = "字段“%2$@”中存在意外字符串“%1$@”";
|
||||
|
||||
/* PrettyString */
|
||||
"Unknown" = "未知";
|
||||
|
||||
/* BuildEditions */
|
||||
"Unknown Edition" = "未知版本";
|
||||
|
||||
/* BuildDetails */
|
||||
"Unknown Language" = "未知语言";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Unknown parser error." = "未知的解析器错误。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Version" = "版本";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Windows® 10" = "Windows® 10";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Windows® 11" = "Windows® 11";
|
||||
3
Source/zh-Hant.lproj/CrystalFetch-InfoPlist.strings
Normal file
3
Source/zh-Hant.lproj/CrystalFetch-InfoPlist.strings
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
/* Bundle name */
|
||||
"CFBundleName" = "CrystalFetch";
|
||||
|
||||
156
Source/zh-Hant.lproj/Localizable.strings
Normal file
156
Source/zh-Hant.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
/* No comment provided by engineer. */
|
||||
"Accept" = "接受";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"All builds…" = "所有組建…";
|
||||
|
||||
/* PrettyString */
|
||||
"Apple Silicon" = "Apple Silicon";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Architecture" = "系統架構";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Are you sure you want to stop the process?" = "您確定要停止程序嗎?";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Attribute '%@' not found" = "找不到「%@」屬性";
|
||||
|
||||
/* PrettyString */
|
||||
"Beta" = "Beta版通道";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Build" = "組建";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Build custom installation for any build through UUP Dump." = "透過UUP Dump建構任何組建的自訂安裝程式。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Build installation for the latest release through ESD conversion." = "透過ESD轉換建構最新發行版本的安裝程式。";
|
||||
|
||||
/* PrettyString */
|
||||
"Canary" = "Canary版通道";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Cancel" = "取消";
|
||||
|
||||
/* UUPDumpAPI */
|
||||
"Cannot find data from the server response." = "從伺服器回應中無法找到資料。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Channel" = "頻道";
|
||||
|
||||
/* Worker */
|
||||
"Converting download to ISO..." = "正在將下載的資料轉換成ISO……";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Created" = "建立於";
|
||||
|
||||
/* PrettyString */
|
||||
"Development" = "Dev版通道";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Download…" = "下載…";
|
||||
|
||||
/* Worker */
|
||||
"Downloading %@ of %@..." = "已下載%1$@(共%2$@)……";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Edition" = "版本";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Editions" = "版本";
|
||||
|
||||
/* UUPDumpAPI */
|
||||
"Error returned from server: %@" = "伺服器回傳錯誤:%@";
|
||||
|
||||
/* EULAView */
|
||||
"Failed to load EULA." = "無法載入EULA。";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Failed to parse element '%@'" = "無法解析「%@」元素";
|
||||
|
||||
/* Worker */
|
||||
"Fetching files list..." = "正在抓取檔案清單……";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"I agree that I have a valid license to use this product." = "我確定我有使用本產品的合法授權。";
|
||||
|
||||
/* PrettyString */
|
||||
"Intel x64" = "Intel x64";
|
||||
|
||||
/* PrettyString */
|
||||
"Intel x86" = "Intel x86";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Language" = "語言";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Localization '%@' not found" = "找不到「%@」本地化資料";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Missing element '%@' in '%@'" = "「%2$@」中缺少「%1$@」元素";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No build found." = "找不到組建。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prerelease Builds" = "預先發布組建";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Refresh" = "重新整理";
|
||||
|
||||
/* PrettyString */
|
||||
"Release Preview" = "發行預覽通道";
|
||||
|
||||
/* PrettyString */
|
||||
"Retail" = "零售版本通道";
|
||||
|
||||
/* Worker */
|
||||
"Saving ISO..." = "正在儲存ISO……";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Server Builds" = "伺服器組建";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show builds for running in a server environment." = "顯示用來在伺服器環境運作的組建。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show unstable releases which previews upcoming features." = "顯示不穩定的發行版本,可以預覽即將推出的功能。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Simple…" = "簡單…";
|
||||
|
||||
/* Worker */
|
||||
"Starting download..." = "開始下載……";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Stop" = "停止";
|
||||
|
||||
/* Worker */
|
||||
"The conversion script failed with error code %d" = "轉換文稿執行失敗,錯誤碼 %d";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Unexpected string '%@' for field '%@'" = "「%2$@」欄位中的「%1$@」字串不符預期";
|
||||
|
||||
/* PrettyString */
|
||||
"Unknown" = "不明";
|
||||
|
||||
/* BuildEditions */
|
||||
"Unknown Edition" = "不明版本";
|
||||
|
||||
/* BuildDetails */
|
||||
"Unknown Language" = "不明語言";
|
||||
|
||||
/* ESDCatalog */
|
||||
"Unknown parser error." = "不明的解析器錯誤。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Version" = "版本";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Windows® 10" = "Windows® 10";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Windows® 11" = "Windows® 11";
|
||||
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit ae44786dcd57c6668233c51fa1c6e3a60fd459cd
|
||||
Subproject commit 4baa0ab7b8ad5c7f0a109e22e9a6c8621a7edd42
|
||||
Loading…
Add table
Reference in a new issue