mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge pull request #9684 from yahya-nusseibeh/fix/nightly-v2-signatures
Nightly: sign each build for its own version (SHA256SUMS.v2.sig)
This commit is contained in:
98 files changed
+788
-178
No files matched your search
@@ -165,11 +165,15 @@ jobs:
|
||||
path: dist
|
||||
pattern: horizon-*
|
||||
merge-multiple: true
|
||||
- name: Sign the checksums (SHA256SUMS, SHA256SUMS.sig) for the nightly channel
|
||||
- name: Sign the checksums for the nightly channel (SHA256SUMS.sig) and for this build (SHA256SUMS.v2.sig)
|
||||
# SHA256SUMS.sig is for launchers from before v2 (signed for "nightly"); SHA256SUMS.v2.sig is signed for
|
||||
# nightly-<date>-<commit>, the version the release title names, which current launchers and servers
|
||||
# require (docs/security.md, "Nightly signatures"). The publish step titles the release from the same values.
|
||||
env:
|
||||
RELEASE_SIGNING_KEY: ${{ secrets.RELEASE_SIGNING_KEY }}
|
||||
RELEASE_SIGNING_KEY_NEXT: ${{ secrets.RELEASE_SIGNING_KEY_NEXT }}
|
||||
EPHEMERAL: ${{ needs.pick.outputs.dry_run == 'true' && 'yes' || '' }}
|
||||
ALSO_FOR: nightly-${{ needs.pick.outputs.date }}-${{ needs.pick.outputs.short }}
|
||||
run: |
|
||||
# Windows ARM64's build may fail without holding the Nightly (game-build.yml): then it is left out.
|
||||
arm64=(); if [[ -f dist/horizon-windows-arm64.zip ]]; then arm64=(dist/horizon-windows-arm64.zip)
|
||||
@@ -208,12 +212,17 @@ jobs:
|
||||
with:
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
- name: The signature, checked as launchers check it
|
||||
- name: The signatures, checked as launchers check them
|
||||
env:
|
||||
EPHEMERAL: ${{ needs.pick.outputs.dry_run == 'true' && 'yes' || '' }}
|
||||
DATE: ${{ needs.pick.outputs.date }}
|
||||
SHORT: ${{ needs.pick.outputs.short }}
|
||||
run: |
|
||||
key=(); [[ -n "$EPHEMERAL" ]] && key=(--key "$(cat dist/ephemeral.pub)")
|
||||
cargo run --locked --quiet -p horizon-release-sign -- verify --tag nightly --publishing "${key[@]}" dist
|
||||
# The version's own signature, which current launchers require: the title below names the same version.
|
||||
cargo run --locked --quiet -p horizon-release-sign -- verify --tag "nightly-${DATE}-${SHORT}" \
|
||||
--signature SHA256SUMS.v2.sig --publishing "${key[@]}" dist
|
||||
- name: Ready to publish (the smoke test drew a world, no release-blocker is open)
|
||||
# Right before publishing, as for a release: an issue labelled while the builds ran still holds it. A dry run
|
||||
# only reports what a real run would do (that is how a blocker's fix is tried).
|
||||
@@ -230,12 +239,16 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
SHA: ${{ needs.pick.outputs.sha }}
|
||||
SHORT: ${{ needs.pick.outputs.short }}
|
||||
DATE: ${{ needs.pick.outputs.date }}
|
||||
SOURCE_TAG: ${{ needs.pick.outputs.source_tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 tools/ci/nightly.py rotate
|
||||
signature=(); [[ -f dist/SHA256SUMS.sig ]] && signature=(dist/SHA256SUMS.sig)
|
||||
[[ -f dist/SHA256SUMS.v2.sig ]] && signature+=(dist/SHA256SUMS.v2.sig)
|
||||
# The title is what launchers parse (strictly) into the version SHA256SUMS.v2.sig is signed for.
|
||||
[[ "${SHA:0:7}" == "$SHORT" ]] || { echo "::error::the build's commit isn't the one signed"; exit 1; }
|
||||
# The ARM64 archive only when its build succeeded (the sign job left it out of SHA256SUMS otherwise).
|
||||
arm64=()
|
||||
if [[ -f dist/horizon-windows-arm64.zip ]]; then
|
||||
|
||||
@@ -20,6 +20,11 @@ use ring::signature::{ED25519, Ed25519KeyPair, KeyPair, UnparsedPublicKey};
|
||||
pub const SUMS: &str = "SHA256SUMS";
|
||||
/// Its signatures' asset name.
|
||||
pub const SIGNATURE: &str = "SHA256SUMS.sig";
|
||||
/// A Nightly's second signature file: the same checksum list signed for the Nightly's own version
|
||||
/// (`horizon release nightly-<date>-<commit>`) rather than for the channel, so an older Nightly can't
|
||||
/// be served as a newer one. Nightlies carry both while launchers that only read [`SIGNATURE`] are
|
||||
/// in use (docs/security.md, "Nightly signatures"); current launchers and servers require this one.
|
||||
pub const SIGNATURE_V2: &str = "SHA256SUMS.v2.sig";
|
||||
|
||||
const KEYS: &str = include_str!("../release-keys.txt");
|
||||
const LEGACY: &str = include_str!("../legacy-releases.txt");
|
||||
@@ -124,9 +129,10 @@ pub fn is_nightly(tag: &str) -> bool {
|
||||
tag == NIGHTLY || tag.starts_with("nightly-")
|
||||
}
|
||||
|
||||
/// The version a nightly installs as, from its release title ("Nightly 2026-10-28 · 0c02067"):
|
||||
/// The version a nightly is listed as, from its release title ("Nightly 2026-10-28 · 0c02067"):
|
||||
/// `nightly-2026-10-28-0c02067`. `published` (GitHub's `published_at`) dates a title without one.
|
||||
/// The launcher and `horizon-server` name it the same way, and native mods read the commit back.
|
||||
/// For listing only: what installs is checked against [`nightly_title_version`], which has no
|
||||
/// fallback, since nothing signs `published`.
|
||||
pub fn nightly_version(title: &str, published: &str) -> String {
|
||||
let mut words = title
|
||||
.strip_prefix("Nightly ")
|
||||
@@ -182,15 +188,61 @@ pub fn signed_digest(
|
||||
digest_in(tag, tag, asset, sums, signature, keys)
|
||||
}
|
||||
|
||||
/// The signed SHA-256 of `asset` in the nightly build: its checksums must be signed for the
|
||||
/// [`NIGHTLY`] channel, so a release's signed list (or one replayed from a release) is refused.
|
||||
/// The version a Nightly's GitHub title names, strictly: `Nightly 2026-10-28 · 0c02067` (as
|
||||
/// nightly-build.yml writes it) gives `nightly-2026-10-28-0c02067`. Anything else is None: a
|
||||
/// Nightly is verified against the version it installs as, so a title that doesn't name its date
|
||||
/// and commit is refused rather than dated by when GitHub says it was published.
|
||||
pub fn nightly_title_version(title: &str) -> Option<String> {
|
||||
let (date, commit) = title.strip_prefix("Nightly ")?.split_once(" · ")?;
|
||||
let commit_ok = (7..=40).contains(&commit.len())
|
||||
&& commit
|
||||
.bytes()
|
||||
.all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b));
|
||||
(is_date(date) && commit_ok).then(|| format!("{NIGHTLY}-{date}-{commit}"))
|
||||
}
|
||||
|
||||
/// Whether `version` is a Nightly build's full version, `nightly-<date>-<commit>`, the form its
|
||||
/// [`SIGNATURE_V2`] is signed for.
|
||||
pub fn is_nightly_build(version: &str) -> bool {
|
||||
version
|
||||
.strip_prefix("nightly-")
|
||||
.and_then(|rest| rest.split_at_checked(10))
|
||||
.is_some_and(|(date, commit)| {
|
||||
is_date(date)
|
||||
&& commit.strip_prefix('-').is_some_and(|c| {
|
||||
(7..=40).contains(&c.len())
|
||||
&& c.bytes()
|
||||
.all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// The signed SHA-256 of `asset` in the Nightly that installs as `version`
|
||||
/// (`nightly-<date>-<commit>`, from [`nightly_title_version`]): `signature` is its
|
||||
/// [`SIGNATURE_V2`], which must sign `sums` for exactly that version. A release's list, the channel
|
||||
/// signature (`nightly`) and another Nightly's are all refused, so an older Nightly can't be passed off
|
||||
/// as tonight's.
|
||||
pub fn nightly_digest(
|
||||
version: &str,
|
||||
asset: &str,
|
||||
sums: Option<&[u8]>,
|
||||
signature: Option<&str>,
|
||||
keys: &[PublicKey],
|
||||
) -> Result<String, String> {
|
||||
digest_in(NIGHTLY, "The Nightly build", asset, sums, signature, keys)
|
||||
if !is_nightly_build(version) {
|
||||
return Err(format!(
|
||||
"{version:?} doesn't name a Nightly's date and commit, so it can't be checked: not installed."
|
||||
));
|
||||
}
|
||||
let Some(signature) = signature else {
|
||||
return Err(format!(
|
||||
"{version} has no {SIGNATURE_V2} (its signature for this exact build): it was published \
|
||||
before Nightlies were signed for their own version, so it can't be checked and was not \
|
||||
installed. Pick a Nightly published since then; `horizon-server rollback` still goes back \
|
||||
to a build a server already has."
|
||||
));
|
||||
};
|
||||
digest_in(version, version, asset, sums, Some(signature), keys)
|
||||
}
|
||||
|
||||
/// `asset`'s SHA-256 in `sums`, once one of `keys` is found to have signed them for `signed_for`;
|
||||
@@ -404,30 +456,112 @@ mod tests {
|
||||
/// round: one key signs both, and only the message's channel keeps them apart.
|
||||
#[test]
|
||||
fn nightly_and_release_signatures_never_pass_for_each_other() {
|
||||
let (nightly, sums, signature) = signed(NIGHTLY);
|
||||
const TONIGHT: &str = "nightly-2026-10-28-0c02067";
|
||||
let (nightly, sums, signature) = signed(TONIGHT);
|
||||
let keys = [nightly.public()];
|
||||
let asset = "horizon-windows-x64.zip";
|
||||
let digest = nightly_digest(asset, Some(sums.as_bytes()), Some(&signature), &keys);
|
||||
let digest = nightly_digest(
|
||||
TONIGHT,
|
||||
asset,
|
||||
Some(sums.as_bytes()),
|
||||
Some(&signature),
|
||||
&keys,
|
||||
);
|
||||
assert_eq!(digest, Ok(sha256_hex(b"windows")));
|
||||
// Served under a release's tag: the message names the nightly channel, not v0.2.81.
|
||||
// Served under a release's tag: the message names the Nightly, not v0.2.81.
|
||||
for tag in ["v0.2.81", "v2026.10.28", "0.2.81"] {
|
||||
let as_release =
|
||||
signed_digest(tag, asset, Some(sums.as_bytes()), Some(&signature), &keys);
|
||||
assert!(as_release.is_err(), "{tag} took a nightly's signature");
|
||||
}
|
||||
// Asked for by a nightly tag on the release channel: refused before any signature is read.
|
||||
for tag in [NIGHTLY, "nightly-2026-10-27"] {
|
||||
for tag in [NIGHTLY, "nightly-2026-10-27", TONIGHT] {
|
||||
let err = signed_digest(tag, asset, Some(sums.as_bytes()), Some(&signature), &keys);
|
||||
assert!(err.unwrap_err().contains("Nightly build"), "{tag}");
|
||||
}
|
||||
// A release's signed list replayed as tonight's nightly.
|
||||
let (release, sums, signature) = signed("v0.2.81");
|
||||
let keys = [release.public()];
|
||||
assert!(nightly_digest(asset, Some(sums.as_bytes()), Some(&signature), &keys).is_err());
|
||||
assert!(nightly_digest(asset, Some(sums.as_bytes()), None, &keys).is_err());
|
||||
assert!(
|
||||
nightly_digest(
|
||||
TONIGHT,
|
||||
asset,
|
||||
Some(sums.as_bytes()),
|
||||
Some(&signature),
|
||||
&keys
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert!(nightly_digest(TONIGHT, asset, Some(sums.as_bytes()), None, &keys).is_err());
|
||||
assert!(is_nightly("nightly-2026-10-27") && !is_nightly("v0.2.81"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_older_nightly_is_not_tonights() {
|
||||
let asset = "horizon-windows-x64.zip";
|
||||
// Last week's build, re-served under tonight's title: its v2 signature names last week.
|
||||
let (key, sums, old) = signed("nightly-2026-10-21-aaaaaaa");
|
||||
let keys = [key.public()];
|
||||
let tonight = nightly_title_version("Nightly 2026-10-28 · 0c02067").unwrap();
|
||||
let err = nightly_digest(&tonight, asset, Some(sums.as_bytes()), Some(&old), &keys);
|
||||
assert!(err.is_err(), "an older Nightly passed as {tonight}");
|
||||
// The channel signature (SHA256SUMS.sig, for launchers from before v2) is no v2 signature.
|
||||
let (key, sums, channel) = signed(NIGHTLY);
|
||||
let keys = [key.public()];
|
||||
assert!(
|
||||
nightly_digest(
|
||||
&tonight,
|
||||
asset,
|
||||
Some(sums.as_bytes()),
|
||||
Some(&channel),
|
||||
&keys
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
// And a version that isn't a full Nightly build is refused whatever signs it.
|
||||
for version in [NIGHTLY, "nightly-2026-10-28", "v0.2.81"] {
|
||||
let (key, sums, signature) = signed(version);
|
||||
let err = nightly_digest(
|
||||
version,
|
||||
asset,
|
||||
Some(sums.as_bytes()),
|
||||
Some(&signature),
|
||||
&[key.public()],
|
||||
);
|
||||
assert!(err.unwrap_err().contains("doesn't name"), "{version}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_nightlys_title_is_parsed_strictly() {
|
||||
assert_eq!(
|
||||
nightly_title_version("Nightly 2026-10-28 · 0c02067").as_deref(),
|
||||
Some("nightly-2026-10-28-0c02067")
|
||||
);
|
||||
assert_eq!(
|
||||
nightly_title_version(&format!("Nightly 2026-10-28 · {}", "a".repeat(40))),
|
||||
Some(format!("nightly-2026-10-28-{}", "a".repeat(40)))
|
||||
);
|
||||
for title in [
|
||||
"Something else",
|
||||
"Nightly 2026-10-28",
|
||||
"Nightly 2026-10-28 · ",
|
||||
"Nightly 2026-10-28 · 0c020", // too short
|
||||
"Nightly 2026-10-28 · 0C02067", // GitHub's short SHAs are lowercase
|
||||
"Nightly 2026-10-28 · 0c02067 x", // trailing words
|
||||
"Nightly 2026-1-28 · 0c02067", // not a date
|
||||
"nightly 2026-10-28 · 0c02067",
|
||||
" Nightly 2026-10-28 · 0c02067",
|
||||
"Nightly 2026-10-28 · 0c02067/../x",
|
||||
] {
|
||||
assert_eq!(nightly_title_version(title), None, "{title:?}");
|
||||
}
|
||||
assert!(is_nightly_build("nightly-2026-10-28-0c02067"));
|
||||
assert!(!is_nightly_build("nightly-2026-10-28"));
|
||||
assert!(!is_nightly_build("nightly-2026-10-28-"));
|
||||
assert!(!is_nightly_build("nightly-2026-10-28-0c02067-x"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_nightly_is_named_by_its_date_and_commit() {
|
||||
let v = nightly_version("Nightly 2026-10-28 · 0c02067", "2026-10-28T20:40:00Z");
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
//! job that holds the private key runs only the runner's sha256sum and OpenSSL, no code from the
|
||||
//! repository.
|
||||
//!
|
||||
//! horizon-release-sign verify --tag v2026.10.19 [--key <base64>] [--publishing] <folder>
|
||||
//! horizon-release-sign verify --tag v2026.10.19 [--key <base64>] [--signature <file>] [--publishing] <folder>
|
||||
//! horizon-release-sign keygen --public-out key.pub | gh secret set RELEASE_SIGNING_KEY --env release
|
||||
//!
|
||||
//! `verify` checks SHA256SUMS.sig and every file SHA256SUMS lists with the keys built in from
|
||||
//! `verify` checks SHA256SUMS.sig (or `--signature`, e.g. a Nightly's SHA256SUMS.v2.sig with
|
||||
//! `--tag nightly-<date>-<commit>`) and every file SHA256SUMS lists with the keys built in from
|
||||
//! release-keys.txt (or `--key`), as the launcher does. With `--publishing` (release.yml) a release
|
||||
//! may go out unsigned, or signed with a key nobody can check yet, only while release-keys.txt
|
||||
//! lists no key; once it lists one, anything launchers would refuse fails the release.
|
||||
@@ -22,8 +23,8 @@ fn main() -> ExitCode {
|
||||
Some("verify") => check_folder(&args[1..]),
|
||||
Some("keygen") => keygen(&args[1..]),
|
||||
_ => Err(
|
||||
"usage: horizon-release-sign verify --tag <tag> [--key <base64>]... [--publishing] \
|
||||
<folder> | keygen --public-out <file>"
|
||||
"usage: horizon-release-sign verify --tag <tag> [--key <base64>]... [--signature <file>] \
|
||||
[--publishing] <folder> | keygen --public-out <file>"
|
||||
.into(),
|
||||
),
|
||||
};
|
||||
@@ -76,6 +77,16 @@ fn file_hash(path: &Path) -> Result<String, String> {
|
||||
fn check_folder(args: &[String]) -> Result<(), String> {
|
||||
let (tag, rest) = tag_of(args)?;
|
||||
let (keys, rest) = options(&rest, "--key");
|
||||
let (signature_file, rest) = options(&rest, "--signature");
|
||||
let signature_file = signature_file
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap_or_else(|| SIGNATURE.to_owned());
|
||||
if signature_file.contains(['/', '\\']) {
|
||||
return Err(format!(
|
||||
"--signature names a file of the folder, not {signature_file:?}"
|
||||
));
|
||||
}
|
||||
let publishing = rest.iter().any(|a| a == "--publishing");
|
||||
let folder = PathBuf::from(
|
||||
rest.iter()
|
||||
@@ -88,7 +99,7 @@ fn check_folder(args: &[String]) -> Result<(), String> {
|
||||
parse_keys(&keys.join("\n"))?
|
||||
};
|
||||
let sums = std::fs::read(folder.join(SUMS)).map_err(|e| format!("{SUMS}: {e}"))?;
|
||||
let listed = match std::fs::read_to_string(folder.join(SIGNATURE)) {
|
||||
let listed = match std::fs::read_to_string(folder.join(&signature_file)) {
|
||||
Ok(signature) if !keys.is_empty() => verify(&tag, &sums, &signature, &keys)?,
|
||||
Ok(_) if publishing => {
|
||||
println!(
|
||||
@@ -105,7 +116,7 @@ fn check_folder(args: &[String]) -> Result<(), String> {
|
||||
Ok(_) => return Err("no key to check the signature with".into()),
|
||||
Err(_) => {
|
||||
return Err(format!(
|
||||
"{tag} has no {SIGNATURE}: launchers would refuse it (is the release environment's \
|
||||
"{tag} has no {signature_file}: launchers would refuse it (is the release environment's \
|
||||
RELEASE_SIGNING_KEY set?)"
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! The release steps as release.yml runs them: `sums`, tools/ci/sign_release.sh (OpenSSL), `verify`.
|
||||
use horizon_release_sign::{SIGNATURE, SUMS, SigningKey, trusted_keys};
|
||||
use horizon_release_sign::{SIGNATURE, SIGNATURE_V2, SUMS, SigningKey, trusted_keys};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Output};
|
||||
|
||||
@@ -60,10 +60,14 @@ fn openssl() -> Option<String> {
|
||||
}
|
||||
|
||||
fn sign_script(dir: &Path, env: &[(&str, &str)]) -> Output {
|
||||
sign_script_for(TAG, dir, env)
|
||||
}
|
||||
|
||||
fn sign_script_for(tag: &str, dir: &Path, env: &[(&str, &str)]) -> Output {
|
||||
let script = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tools/ci/sign_release.sh");
|
||||
let mut cmd = Command::new("bash");
|
||||
cmd.arg(script)
|
||||
.arg(TAG)
|
||||
.arg(tag)
|
||||
.arg(dir.join("horizon-linux-x86_64.tar.gz"))
|
||||
.arg(dir.join("horizon-windows-x64.zip"))
|
||||
.env_remove("RELEASE_SIGNING_KEY")
|
||||
@@ -182,3 +186,70 @@ fn keygen_writes_the_secret_to_a_pipe_and_the_public_key_to_a_file() {
|
||||
);
|
||||
std::fs::remove_dir_all(dir).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_nightly_is_signed_for_its_channel_and_for_its_version() {
|
||||
if cfg!(windows) || openssl().is_none() {
|
||||
eprintln!("skipped: tools/ci/sign_release.sh is tested on Linux (bash, OpenSSL 3)");
|
||||
return;
|
||||
}
|
||||
const VERSION: &str = "nightly-2026-10-28-0c02067";
|
||||
let dir = folder("nightly");
|
||||
std::fs::remove_file(dir.join(SUMS)).unwrap();
|
||||
let (key, secret) = SigningKey::generate().unwrap();
|
||||
let out = sign_script_for(
|
||||
"nightly",
|
||||
&dir,
|
||||
&[("RELEASE_SIGNING_KEY", &secret), ("ALSO_FOR", VERSION)],
|
||||
);
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
let public = key.public_base64();
|
||||
let check = |tag: &str, signature: &str| {
|
||||
tool(
|
||||
&[
|
||||
"verify",
|
||||
"--tag",
|
||||
tag,
|
||||
"--key",
|
||||
&public,
|
||||
"--signature",
|
||||
signature,
|
||||
],
|
||||
&[&dir],
|
||||
)
|
||||
.status
|
||||
.success()
|
||||
};
|
||||
// Launchers from before v2 check the channel's; current ones, the version's.
|
||||
assert!(check("nightly", SIGNATURE));
|
||||
assert!(check(VERSION, SIGNATURE_V2));
|
||||
assert!(!check(VERSION, SIGNATURE));
|
||||
assert!(!check("nightly", SIGNATURE_V2));
|
||||
assert!(!check("nightly-2026-10-27-0c02067", SIGNATURE_V2));
|
||||
// What a current launcher does with it.
|
||||
let sums = std::fs::read(dir.join(SUMS)).unwrap();
|
||||
let v2 = std::fs::read_to_string(dir.join(SIGNATURE_V2)).unwrap();
|
||||
let digest = horizon_release_sign::nightly_digest(
|
||||
VERSION,
|
||||
"horizon-windows-x64.zip",
|
||||
Some(&sums),
|
||||
Some(&v2),
|
||||
&[key.public()],
|
||||
);
|
||||
assert_eq!(
|
||||
digest,
|
||||
Ok(horizon_release_sign::sha256_hex(b"windows build"))
|
||||
);
|
||||
// A bad ALSO_FOR is refused before anything is signed.
|
||||
let out = sign_script_for(
|
||||
"nightly",
|
||||
&dir,
|
||||
&[("RELEASE_SIGNING_KEY", &secret), ("ALSO_FOR", "v0.2.81")],
|
||||
);
|
||||
assert!(!out.status.success());
|
||||
std::fs::remove_dir_all(dir).unwrap();
|
||||
}
|
||||
@@ -341,15 +341,18 @@ fn pick_for_assets(
|
||||
}
|
||||
};
|
||||
let sums = attached(horizon_release_sign::SUMS)?;
|
||||
let signature = attached(horizon_release_sign::SIGNATURE)?
|
||||
.map(|bytes| String::from_utf8_lossy(&bytes).into_owned());
|
||||
// A Nightly is signed for its channel, never for a tag, and installs under its date and commit.
|
||||
let text = |bytes: Vec<u8>| String::from_utf8_lossy(&bytes).into_owned();
|
||||
// A Nightly installs under its date and commit, which its title names (strictly: nothing signs
|
||||
// GitHub's publication time), and its SHA256SUMS.v2.sig must sign the checksums for exactly that
|
||||
// version, so an older Nightly can't be served as a newer one.
|
||||
let (tag, sha256) = if horizon_release_sign::is_nightly(tag) {
|
||||
let version = horizon_release_sign::nightly_version(
|
||||
release["name"].as_str().unwrap_or_default(),
|
||||
release["published_at"].as_str().unwrap_or_default(),
|
||||
);
|
||||
let title = release["name"].as_str().unwrap_or_default();
|
||||
let version = horizon_release_sign::nightly_title_version(title).ok_or_else(|| {
|
||||
format!("the Nightly's title {title:?} doesn't name its date and commit, so it can't be checked")
|
||||
})?;
|
||||
let signature = attached(horizon_release_sign::SIGNATURE_V2)?.map(text);
|
||||
let sha256 = horizon_release_sign::nightly_digest(
|
||||
&version,
|
||||
name,
|
||||
sums.as_deref(),
|
||||
signature.as_deref(),
|
||||
@@ -357,6 +360,7 @@ fn pick_for_assets(
|
||||
)?;
|
||||
(version, sha256)
|
||||
} else {
|
||||
let signature = attached(horizon_release_sign::SIGNATURE)?.map(text);
|
||||
let sha256 = horizon_release_sign::signed_digest(
|
||||
tag,
|
||||
name,
|
||||
@@ -684,6 +688,13 @@ pub fn command(dir: &Path, args: &[String]) -> Result<(), String> {
|
||||
println!("Already on the newest release ({current}).");
|
||||
return Ok(());
|
||||
}
|
||||
if newer(¤t, &release.tag) {
|
||||
// Asked for by --version: a rollback is allowed, never done without saying so.
|
||||
println!(
|
||||
"{} is older than {current}, which this server runs: installing it as a rollback.",
|
||||
release.tag
|
||||
);
|
||||
}
|
||||
println!("Downloading Horizon {} ({})...", release.tag, release.asset);
|
||||
let archive = download(&install, &release)?;
|
||||
(release.tag, archive, true)
|
||||
@@ -967,36 +978,56 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_nightly_is_picked_with_its_channel_signature_and_named_by_its_build() {
|
||||
fn a_nightly_is_picked_with_its_version_signature_and_named_by_its_build() {
|
||||
use horizon_release_sign::{SigningKey, sha256_hex, sums_file};
|
||||
let Some(first) = ASSETS.first() else { return };
|
||||
let asset = |name: &str| json!({ "name": name, "browser_download_url": format!("https://example.invalid/{name}") });
|
||||
let (key, _) = SigningKey::generate().unwrap();
|
||||
let sums = sums_file(&[(first.to_string(), sha256_hex(b"server"))]);
|
||||
let serving = |signed_for: &str| {
|
||||
let (sums, signature) = (sums.clone(), key.sign(signed_for, sums.as_bytes()));
|
||||
// SHA256SUMS.sig signed for `channel`, SHA256SUMS.v2.sig for `v2`.
|
||||
let serving = |channel: &str, v2: &str| {
|
||||
let sums = sums.clone();
|
||||
let (one, two) = (
|
||||
key.sign(channel, sums.as_bytes()),
|
||||
key.sign(v2, sums.as_bytes()),
|
||||
);
|
||||
move |url: &str| -> Result<Vec<u8>, String> {
|
||||
match url.rsplit('/').next() {
|
||||
Some("SHA256SUMS") => Ok(sums.clone().into_bytes()),
|
||||
Some("SHA256SUMS.sig") => Ok(signature.clone().into_bytes()),
|
||||
Some("SHA256SUMS.sig") => Ok(one.clone().into_bytes()),
|
||||
Some("SHA256SUMS.v2.sig") => Ok(two.clone().into_bytes()),
|
||||
_ => Err("404".into()),
|
||||
}
|
||||
}
|
||||
};
|
||||
const TONIGHT: &str = "nightly-2026-10-28-0c02067";
|
||||
let nightly = json!({
|
||||
"tag_name": "nightly", "name": "Nightly 2026-10-28 · 0c02067",
|
||||
"published_at": "2026-10-28T20:40:00Z",
|
||||
"assets": [asset(first), asset("SHA256SUMS"), asset("SHA256SUMS.sig")],
|
||||
"assets": [asset(first), asset("SHA256SUMS"), asset("SHA256SUMS.sig"), asset("SHA256SUMS.v2.sig")],
|
||||
});
|
||||
let picked = pick(&nightly, serving("nightly"), &[key.public()]).unwrap();
|
||||
assert_eq!(picked.tag, "nightly-2026-10-28-0c02067");
|
||||
let picked = pick(&nightly, serving("nightly", TONIGHT), &[key.public()]).unwrap();
|
||||
assert_eq!(picked.tag, TONIGHT);
|
||||
assert_eq!(picked.sha256, sha256_hex(b"server"));
|
||||
// An older Nightly's files under tonight's title: refused.
|
||||
let old = serving("nightly", "nightly-2026-10-21-aaaaaaa");
|
||||
assert!(pick(&nightly, old, &[key.public()]).is_err());
|
||||
// Only the channel's signature (a Nightly from before v2): refused.
|
||||
let mut v1_only = nightly.clone();
|
||||
v1_only["assets"] = json!([asset(first), asset("SHA256SUMS"), asset("SHA256SUMS.sig")]);
|
||||
let err = pick(&v1_only, serving("nightly", TONIGHT), &[key.public()]).unwrap_err();
|
||||
assert!(err.contains("SHA256SUMS.v2.sig"), "{err}");
|
||||
// A title that doesn't name its date and commit: refused, never dated by `published_at`.
|
||||
let mut untitled = nightly.clone();
|
||||
untitled["name"] = json!("Nightly");
|
||||
let err = pick(&untitled, serving("nightly", TONIGHT), &[key.public()]).unwrap_err();
|
||||
assert!(err.contains("doesn't name"), "{err}");
|
||||
// A release's signed list served as the Nightly: refused.
|
||||
assert!(pick(&nightly, serving("v0.2.95"), &[key.public()]).is_err());
|
||||
assert!(pick(&nightly, serving("v0.2.95", "v0.2.95"), &[key.public()]).is_err());
|
||||
// A Nightly's signed list served as a release: refused.
|
||||
let mut release = nightly.clone();
|
||||
release["tag_name"] = json!("v0.2.95");
|
||||
assert!(pick(&release, serving("nightly"), &[key.public()]).is_err());
|
||||
assert!(pick(&release, serving(TONIGHT, TONIGHT), &[key.public()]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+18
-1
@@ -66,6 +66,22 @@ and some are waiting for Ludovic:
|
||||
a secret of GitHub's `release` environment, which only real releases from main can use. The job that signs runs
|
||||
nothing from the repository but a short script calling OpenSSL, so no pull request can read the key by changing
|
||||
the code that gets built. Nobody, Ludovic included, ever needs to see it.
|
||||
- **Nightly signatures.** A Nightly carries two signatures over the same `SHA256SUMS`. `SHA256SUMS.sig` is signed
|
||||
for the channel (`horizon release nightly`), for launchers and servers from before this change, which read only
|
||||
that one. `SHA256SUMS.v2.sig` is signed for the Nightly's own version (`horizon release nightly-<date>-<commit>`),
|
||||
which its release title names (`Nightly <date> · <commit>`). Current launchers, `horizon-server update` and
|
||||
`install.sh` parse that title strictly, refuse a Nightly whose title doesn't parse, and install it only with a v2
|
||||
signature for exactly that version. That way an older Nightly can't be served as tonight's: its signature names its own date. Installing
|
||||
an older Nightly on purpose still works as a rollback, with a warning, and never happens as an automatic update.
|
||||
**The changeover:** Nightlies published before this change have no `SHA256SUMS.v2.sig`, so they can't be installed
|
||||
or picked with `--version` any more; they leave GitHub with the 7-day rotation. Until a v2 Nightly exists, a
|
||||
launcher has nothing to update to, so the first Nightly is built right after this merges. A server can still go back
|
||||
to a build it already has with `horizon-server rollback`, which checks nothing it hasn't already checked.
|
||||
**When the old signature can go:** once every launcher and server still updating reads v2. That is, when the
|
||||
telemetry live count shows no launcher (and the server update log no `horizon-server`) older than the first build
|
||||
with this change, or at the latest 60 days after it shipped. Then drop `SHA256SUMS.sig` from nightly-build.yml's
|
||||
publish step and the `--tag nightly` check before it, and remove `NIGHTLY` from horizon-release-sign. Releases (not
|
||||
Nightlies) keep their single `SHA256SUMS.sig`, which is already signed for their tag.
|
||||
- **The release machinery needs Ludovic.** The workflows, the release and signing tools, the update-checking code
|
||||
and the installers are listed in `.github/CODEOWNERS`. Once main's protection requires code owners' reviews, a
|
||||
change to any of them waits for Ludovic's approval. Everything else still merges as soon as its checks pass.
|
||||
@@ -100,7 +116,8 @@ and some are waiting for Ludovic:
|
||||
not a deliberate attack. The protection is review: only agents and maintainers push to this repository, and a
|
||||
maintainer reads a fork's change before labelling it.
|
||||
- **The first install** with `install.sh` checks the signature when OpenSSL 3 is installed: it verifies
|
||||
`SHA256SUMS.sig` against the release key built into the script, then the archive against `SHA256SUMS`. With
|
||||
`SHA256SUMS.sig` (a Nightly's `SHA256SUMS.v2.sig`, for its own version) against the release key built into the
|
||||
script, then the archive against `SHA256SUMS`. With
|
||||
an older OpenSSL, LibreSSL (what macOS ships) or none, it says so and checks only the SHA-256 GitHub
|
||||
publishes. `install.ps1` still checks only that SHA-256. Every update after that checks the signature.
|
||||
|
||||
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Die Stigtersstel is joune.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "طقم المؤسسين أصبح لك.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Təsisçi dəsti artıq sizindir.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Нигеҙ һалыусылар йыйылмаһы — һеҙҙеке.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Набор заснавальнікаў цяпер ваш.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Основателският комплект е ваш.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Lakaet ar grouerien a zo deoc'h.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Osnivački set je vaš.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "El Conjunt del fundador és teu.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Zakladatelský set je váš.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Учредительсен пуххи — сирӗн.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Mae'r Set sylfaenydd yn eiddo i chi.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Stifter sæt er dit.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Das Gründer-Set gehört dir.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Το Ιδρυτικό σύνολο είναι δικό σου.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "La Fondinto-aro estas via.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "El Set fundador es tuyo.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Asutaja komplekt on sinu.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Sortzaile multzoa zurea da.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "مجموعه موسس مال شماست.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Perustaja-setti on sinun.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Sa iyo na ang Set ng founder.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Stovnara settið er títt.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Le Set fondateur est à vous.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "L'Insiemi di fondadôrs al è to.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "De Oprjochter set is fan dy.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Is leatsa an tSraith bunaitheoir.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "'S ann leatsa a tha an Seata stèidheadair.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "O Set fundador é teu.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Nou ka Hoʻonohonoho hoʻokumu.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "סט המייסד שלכם.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "फ़ाउंडर सेट अब आपका है।",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Osnivački set je vaš.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Az Alapító készlet a tiéd.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Հիմնադիր հավաքածուն ձերն է։",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Set pendiri kini milikmu.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Ntọala ntọala bụ nke gị.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Stofnandi settið er þitt.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "L'Insieme del fondatore è tuo.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "ファウンダーセットはあなたのものです。",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "დამფუძნებელი ნაკრები შენია.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Құрылтайшы жиынтығы енді сіздікі.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "ಸ್ಥಾಪಕ ಸೆಟ್ ಈಗ ನಿಮ್ಮದು.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "창립자 세트를 받았습니다.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Негиздөөчү топтому эми сиздики.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Conditor set tuum est.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "De Grënner Set gehéiert Iech.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "De Oprichter set is van dich.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "El Set fondatur l'è tò.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "ຊຸດຜູ້ກໍ່ຕັ້ງເປັນຂອງທ່ານແລ້ວ.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Steigėjų rinkinys jūsų.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Dibinātāja komplekts ir jūsu.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Основачкиот сет е ваш.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Үүсгэн байгуулагчийн багц таных боллоо.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Set pengasas kini milik anda.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Is-Sett tal-fundatur huwa tiegħek.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "De Oprichtersset is van jou.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Grunnleggersettet er ditt.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "L'Ensemble de fondators es vòstre.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Zestaw założyciela jest Twój.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "O Conjunto fundador é seu.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "O Conjunto de fundador é seu.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Setul fondator este al tău.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Набор основателя теперь ваш.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Төрүттээччи набора — эйиэнэ.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Ásaheaddji searvi lea du.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Zakladateľská súprava je vaša.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Ustanoviteljski set je vaš.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Aasaasaha dhigay waa kaaga.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Seti themelues është yti.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Оснивачки сет је ваш.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Grundaruppsättningen är din.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Zestow założycieli je twōj.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "நிறுவனர் தொகுப்பு இப்போது உங்களுடையது.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "ชุดผู้ก่อตั้งเป็นของคุณแล้ว",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Kurucu seti artık senin.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Нигез салучы җыелмасы — сезнеке.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Набір засновника тепер ваш.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "بانی سیٹ اب آپ کا ہے۔",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Ta'sischilar to'plami endi sizniki.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "El Set fondador el xe tuo.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Bộ sáng lập giờ là của bạn.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "דער גרינדער שטעלן איז דײַנער.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Oludasile ṣeto jẹ́ tìrẹ.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "创始者套装已归你所有。",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
@@ -1141,5 +1141,7 @@
|
||||
"Sign in with `horizon-launcher account sign-in`.": "Sign in with `horizon-launcher account sign-in`.",
|
||||
"The Founder Set is yours.": "Umsunguli usethiwe ungowakho.",
|
||||
"{file} unpacks to more than {limit}: it isn't imported.": "{file} unpacks to more than {limit}: it isn't imported.",
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back."
|
||||
"Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.": "Horizon's Vulkan renderer crashed while starting on this graphics card, so the game now uses {api}. Press Play to start it again. Video Settings > Graphics API switches back.",
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.": "This Nightly's title ({title}) doesn't name its date and commit as Horizon signs them, so it can't be checked: it was not installed.",
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it.": "{tag} is older than {newest}, which is installed: installing it as a rollback. The selection only moves to it when you pick it."
|
||||
}
|
||||
+80
-8
@@ -1215,13 +1215,27 @@ pub fn install(release: &Release, progress: impl Fn(f32)) -> Result<(), String>
|
||||
.to_string()
|
||||
})?;
|
||||
let sha256 = match &release.nightly {
|
||||
// Signed for the Nightly channel, served under its own GitHub tag.
|
||||
Some(published) => crate::signed::nightly_digest(
|
||||
published,
|
||||
asset,
|
||||
&horizon_release_sign::trusted_keys(),
|
||||
fetch_small,
|
||||
)?,
|
||||
// Signed for its own version (SHA256SUMS.v2.sig), which its title names and it installs as;
|
||||
// served under its own GitHub tag. A title that doesn't parse strictly is refused: nothing
|
||||
// signs GitHub's publication time.
|
||||
Some(github_tag) => {
|
||||
let version = horizon_release_sign::nightly_title_version(&release.name)
|
||||
.filter(|v| *v == release.tag)
|
||||
.ok_or_else(|| {
|
||||
crate::i18n::trf(
|
||||
"This Nightly's title ({title}) doesn't name its date and commit as Horizon \
|
||||
signs them, so it can't be checked: it was not installed.",
|
||||
&[("title", &format!("{:?}", release.name))],
|
||||
)
|
||||
})?;
|
||||
crate::signed::nightly_digest(
|
||||
github_tag,
|
||||
&version,
|
||||
asset,
|
||||
&horizon_release_sign::trusted_keys(),
|
||||
fetch_small,
|
||||
)?
|
||||
}
|
||||
None if is_game_tag(tag) => signed_digest(tag, asset)?,
|
||||
None => return Err(format!("{tag} is not a Horizon release.")),
|
||||
};
|
||||
@@ -3247,9 +3261,16 @@ pub fn install_tag(tag: &str, as_update: bool, progress: impl Fn(f32)) -> Result
|
||||
return Err(format!("{} has no build for this platform.", release.tag));
|
||||
}
|
||||
let before = installed();
|
||||
let downgrade = nightly_downgrade(release, &before);
|
||||
if let Some(warning) = &downgrade {
|
||||
eprintln!("{warning}");
|
||||
}
|
||||
install(release, progress)?;
|
||||
let mut settings = Settings::load();
|
||||
if !as_update || select_after_update(settings.selected.as_deref(), &before) {
|
||||
// An older Nightly is a deliberate rollback: installed, but an update never moves the selection back.
|
||||
let select = !as_update
|
||||
|| (downgrade.is_none() && select_after_update(settings.selected.as_deref(), &before));
|
||||
if select {
|
||||
settings.selected = Some(release.tag.clone());
|
||||
settings.save()?;
|
||||
}
|
||||
@@ -3260,6 +3281,24 @@ pub fn install_tag(tag: &str, as_update: bool, progress: impl Fn(f32)) -> Result
|
||||
Ok(release.tag.clone())
|
||||
}
|
||||
|
||||
/// A warning when `release` is a Nightly older than a Nightly already installed: a rollback, which is
|
||||
/// allowed, but never what an update should do silently (an older build served as the current one
|
||||
/// shows its own date, since that date is signed). None otherwise.
|
||||
pub fn nightly_downgrade(release: &Release, installed: &[String]) -> Option<String> {
|
||||
release.nightly.as_ref()?;
|
||||
let newest = installed
|
||||
.iter()
|
||||
.filter(|v| crate::nightly::date_of(v).is_some())
|
||||
.max_by(|a, b| compare_versions(a, b))?;
|
||||
compare_versions(&release.tag, newest).is_lt().then(|| {
|
||||
crate::i18n::trf(
|
||||
"{tag} is older than {newest}, which is installed: installing it as a rollback. The selection \
|
||||
only moves to it when you pick it.",
|
||||
&[("tag", &release.tag), ("newest", newest)],
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// The launcher's own path, read once at start: after a self-update the running file is gone.
|
||||
pub fn exe_path() -> &'static Path {
|
||||
static EXE: std::sync::OnceLock<PathBuf> = std::sync::OnceLock::new();
|
||||
@@ -5432,6 +5471,39 @@ pub fn {f}("
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_older_nightly_installs_as_a_rollback_with_a_warning() {
|
||||
let nightly = |tag: &str| Release {
|
||||
tag: tag.into(),
|
||||
name: String::new(),
|
||||
body: String::new(),
|
||||
date: String::new(),
|
||||
time: String::new(),
|
||||
asset: Some(("u".into(), 1)),
|
||||
prerelease: false,
|
||||
nightly: Some("nightly".into()),
|
||||
};
|
||||
let installed = [
|
||||
"v0.2.81".to_owned(),
|
||||
"nightly-2026-10-28-0c02067".to_owned(),
|
||||
];
|
||||
let warning =
|
||||
nightly_downgrade(&nightly("nightly-2026-10-21-aaaaaaa"), &installed).unwrap();
|
||||
assert!(
|
||||
warning.contains("older than nightly-2026-10-28-0c02067"),
|
||||
"{warning}"
|
||||
);
|
||||
assert!(nightly_downgrade(&nightly("nightly-2026-10-29-bbbbbbb"), &installed).is_none());
|
||||
assert!(nightly_downgrade(&nightly("nightly-2026-10-28-0c02067"), &installed).is_none());
|
||||
// No Nightly installed, or a release: nothing to warn about.
|
||||
assert!(
|
||||
nightly_downgrade(&nightly("nightly-2026-10-21-aaaaaaa"), &installed[..1]).is_none()
|
||||
);
|
||||
let mut release = nightly("v0.2.80");
|
||||
release.nightly = None;
|
||||
assert!(nightly_downgrade(&release, &installed).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_launcher_knows_which_line_it_was_released_on() {
|
||||
// launcher.yml gives the tag; before it did, a release had only its `launcher-v` version.
|
||||
|
||||
+51
-15
@@ -5,7 +5,7 @@
|
||||
//! with a message saying why.
|
||||
|
||||
use crate::backend::REPO;
|
||||
use horizon_release_sign::{PublicKey, SIGNATURE, SUMS};
|
||||
use horizon_release_sign::{PublicKey, SIGNATURE, SIGNATURE_V2, SUMS};
|
||||
|
||||
/// Where a release's attached file downloads from (GitHub's fixed address for it).
|
||||
pub fn asset_url(tag: &str, name: &str) -> String {
|
||||
@@ -31,18 +31,27 @@ pub fn digest(
|
||||
}
|
||||
|
||||
/// The signed SHA-256 of `asset` in the Nightly published under `tag` (`nightly`, or a dated
|
||||
/// `nightly-<date>`): its checksums must be signed for the nightly channel
|
||||
/// (`horizon_release_sign::nightly_digest`), whatever tag serves them.
|
||||
/// `nightly-<date>`) that installs as `version` (`nightly-<date>-<commit>`, parsed strictly from its
|
||||
/// title by `horizon_release_sign::nightly_title_version`): its SHA256SUMS.v2.sig must sign the
|
||||
/// checksums for exactly that version, so an older Nightly can't be served as a newer one. The
|
||||
/// channel's SHA256SUMS.sig is for launchers from before and is not read.
|
||||
pub fn nightly_digest(
|
||||
tag: &str,
|
||||
version: &str,
|
||||
asset: &str,
|
||||
keys: &[PublicKey],
|
||||
fetch: impl Fn(&str) -> Result<Option<Vec<u8>>, String>,
|
||||
) -> Result<String, String> {
|
||||
let sums = fetch(&asset_url(tag, SUMS))?;
|
||||
let signature = fetch(&asset_url(tag, SIGNATURE))?
|
||||
let signature = fetch(&asset_url(tag, SIGNATURE_V2))?
|
||||
.map(|bytes| String::from_utf8_lossy(&bytes).into_owned());
|
||||
horizon_release_sign::nightly_digest(asset, sums.as_deref(), signature.as_deref(), keys)
|
||||
horizon_release_sign::nightly_digest(
|
||||
version,
|
||||
asset,
|
||||
sums.as_deref(),
|
||||
signature.as_deref(),
|
||||
keys,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -109,20 +118,28 @@ mod tests {
|
||||
assert!(digest("v2099.01.01", "horizon.zip", &keys, offline).is_err());
|
||||
}
|
||||
|
||||
/// The release channel never installs what was signed for the nightly channel, whatever tag it
|
||||
/// is served under, and the nightly channel never installs a release's signed list.
|
||||
/// The release channel never installs what was signed for a Nightly, whatever tag it is served
|
||||
/// under; a Nightly installs only with its own version's signature (SHA256SUMS.v2.sig), never the
|
||||
/// channel's, another Nightly's or a release's.
|
||||
#[test]
|
||||
fn the_release_channel_refuses_nightly_signatures() {
|
||||
const VERSION: &str = "nightly-2026-10-28-0c02067";
|
||||
let (key, _) = SigningKey::generate().unwrap();
|
||||
let keys = [key.public()];
|
||||
let sums = sums_file(&[("horizon.zip".into(), sha256_hex(b"game"))]);
|
||||
let serving = |signed_for: &'static str| {
|
||||
let signature = key.sign(signed_for, sums.as_bytes());
|
||||
// Serves SHA256SUMS, SHA256SUMS.sig signed for `channel` and SHA256SUMS.v2.sig for `v2`.
|
||||
let serving = |channel: &'static str, v2: &'static str| {
|
||||
let (one, two) = (
|
||||
key.sign(channel, sums.as_bytes()),
|
||||
key.sign(v2, sums.as_bytes()),
|
||||
);
|
||||
let sums = sums.clone();
|
||||
move |url: &str| -> Result<Option<Vec<u8>>, String> {
|
||||
Ok(Some(
|
||||
if url.ends_with(".sig") {
|
||||
signature.clone()
|
||||
if url.ends_with(SIGNATURE_V2) {
|
||||
two.clone()
|
||||
} else if url.ends_with(SIGNATURE) {
|
||||
one.clone()
|
||||
} else {
|
||||
sums.clone()
|
||||
}
|
||||
@@ -130,19 +147,38 @@ mod tests {
|
||||
))
|
||||
}
|
||||
};
|
||||
let nightly = serving(horizon_release_sign::NIGHTLY);
|
||||
let nightly = serving(horizon_release_sign::NIGHTLY, VERSION);
|
||||
assert_eq!(
|
||||
nightly_digest("nightly", "horizon.zip", &keys, &nightly),
|
||||
nightly_digest("nightly", VERSION, "horizon.zip", &keys, &nightly),
|
||||
Ok(sha256_hex(b"game"))
|
||||
);
|
||||
// Another version than the one signed (an older Nightly served as tonight's, or the reverse).
|
||||
assert!(nightly_digest(
|
||||
"nightly",
|
||||
"nightly-2026-10-29-1111111",
|
||||
"horizon.zip",
|
||||
&keys,
|
||||
&nightly
|
||||
)
|
||||
.is_err());
|
||||
for tag in ["v0.2.81", "nightly", "nightly-2026-10-27"] {
|
||||
assert!(
|
||||
digest(tag, "horizon.zip", &keys, &nightly).is_err(),
|
||||
"{tag}"
|
||||
);
|
||||
}
|
||||
let release = serving("v0.2.81");
|
||||
// Only the channel's signature (a Nightly from before v2): refused by a current launcher.
|
||||
let old = |url: &str| -> Result<Option<Vec<u8>>, String> {
|
||||
if url.ends_with(SIGNATURE_V2) {
|
||||
Ok(None)
|
||||
} else {
|
||||
nightly(url)
|
||||
}
|
||||
};
|
||||
let refused = nightly_digest("nightly", VERSION, "horizon.zip", &keys, old).unwrap_err();
|
||||
assert!(refused.contains("SHA256SUMS.v2.sig"), "{refused}");
|
||||
let release = serving("v0.2.81", "v0.2.81");
|
||||
assert!(digest("v0.2.81", "horizon.zip", &keys, &release).is_ok());
|
||||
assert!(nightly_digest("nightly", "horizon.zip", &keys, &release).is_err());
|
||||
assert!(nightly_digest("nightly", VERSION, "horizon.zip", &keys, &release).is_err());
|
||||
}
|
||||
}
|
||||
@@ -74,11 +74,14 @@ else
|
||||
[ -n "$TAG" ] || die "could not read the release's tag"
|
||||
RAWTAG=$TAG
|
||||
# A Nightly installs under its date and commit, as `horizon-server update` names it:
|
||||
# "Nightly 2026-10-28 · 0c02067" is nightly-2026-10-28-0c02067.
|
||||
# "Nightly 2026-10-28 · 0c02067" is nightly-2026-10-28-0c02067. Its signature is checked for exactly that
|
||||
# version, so the title must name it (as horizon_release_sign::nightly_title_version reads it, strictly).
|
||||
NAME=
|
||||
case "$TAG" in
|
||||
nightly|nightly-*)
|
||||
NAME=$(printf '%s\n' "$JSON" | sed -n 's/^ "name": "Nightly \([0-9-]\{10\}\) · \([0-9a-f]\{1,40\}\)".*/nightly-\1-\2/p' | head -n 1)
|
||||
[ -z "$NAME" ] || TAG=$NAME ;;
|
||||
NAME=$(printf '%s\n' "$JSON" | sed -n 's/^ "name": "Nightly \([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\) · \([0-9a-f]\{7,40\}\)"[,}]*$/nightly-\1-\2/p' | head -n 1)
|
||||
[ -n "$NAME" ] || die "this Nightly's title doesn't name its date and commit as Horizon signs them, so it can't be checked: not installed"
|
||||
TAG=$NAME ;;
|
||||
esac
|
||||
# A release asset's download address, from its block in the release's JSON.
|
||||
asset_url() {
|
||||
@@ -100,23 +103,27 @@ else
|
||||
[ -n "$URL" ] || die "release $TAG has none of $ASSETS"
|
||||
|
||||
# The signature (docs/release-flow.md, "Signed checksums"): SHA256SUMS.sig holds one base64 Ed25519 signature per
|
||||
# line over "horizon release <tag>\n" + SHA256SUMS, a Nightly's for the channel "nightly" instead of its tag.
|
||||
# line over "horizon release <tag>\n" + SHA256SUMS. A Nightly is checked against SHA256SUMS.v2.sig instead, signed
|
||||
# for its own version (nightly-<date>-<commit>, from its title), never the channel's SHA256SUMS.sig: an older
|
||||
# Nightly can't be passed off as the current one (docs/security.md, "Nightly signatures").
|
||||
# It needs OpenSSL 3 (LibreSSL, which macOS ships, and OpenSSL 1.x cannot verify Ed25519 this way); without it
|
||||
# the install says so and keeps the GitHub SHA-256 check below.
|
||||
EXPECT=$DIGEST
|
||||
case "$RAWTAG" in nightly|nightly-*) SIGTAG=nightly ;; *) SIGTAG=$RAWTAG ;; esac
|
||||
if [ -n "$NAME" ]; then SIGTAG=$NAME; SIGFILE=SHA256SUMS.v2.sig; else SIGTAG=$RAWTAG; SIGFILE=SHA256SUMS.sig; fi
|
||||
if ! command -v openssl >/dev/null; then
|
||||
echo "Note: OpenSSL isn't installed, so the release signature was not checked; checking GitHub's SHA-256 only."
|
||||
elif ! openssl version | grep -Eq '^OpenSSL ([3-9]|[1-9][0-9])\.'; then
|
||||
echo "Note: $(openssl version | cut -d' ' -f1-2) can't check Ed25519 signatures (OpenSSL 3 is needed), so the release signature was not checked; checking GitHub's SHA-256 only."
|
||||
else
|
||||
SUMS_URL=$(asset_url SHA256SUMS)
|
||||
SIG_URL=$(asset_url SHA256SUMS.sig)
|
||||
if [ -z "$SUMS_URL" ] || [ -z "$SIG_URL" ]; then
|
||||
SIG_URL=$(asset_url "$SIGFILE")
|
||||
if [ -n "$NAME" ] && { [ -z "$SUMS_URL" ] || [ -z "$SIG_URL" ]; }; then
|
||||
die "$TAG has no $SIGFILE (its signature for this exact build): it was published before Nightlies were signed for their own version, so it can't be checked. Install the current Nightly (--version latest) instead."
|
||||
elif [ -z "$SUMS_URL" ] || [ -z "$SIG_URL" ]; then
|
||||
echo "Note: release $TAG has no signed checksums (it was published before signing began), so the signature was not checked; checking GitHub's SHA-256 only."
|
||||
else
|
||||
curl -fsSL -o "$TMP/SHA256SUMS" "$SUMS_URL" || die "could not download SHA256SUMS"
|
||||
curl -fsSL -o "$TMP/SHA256SUMS.sig" "$SIG_URL" || die "could not download SHA256SUMS.sig"
|
||||
curl -fsSL -o "$TMP/SHA256SUMS.sig" "$SIG_URL" || die "could not download $SIGFILE"
|
||||
{ printf 'horizon release %s\n' "$SIGTAG"; cat "$TMP/SHA256SUMS"; } > "$TMP/message"
|
||||
SIGNED=
|
||||
for KEY in $RELEASE_KEYS; do
|
||||
|
||||
@@ -21,22 +21,29 @@ check() { # name, expected exit (0 or 1), output grep (may be empty), then the
|
||||
else echo "FAIL $name (exit $rc, wanted $want, pattern '$pat')"; sed 's/^/ /' "$out"; fails=$((fails+1)); fi
|
||||
}
|
||||
|
||||
# A fake release: an archive holding horizon-server, signed for TAG with a throwaway key.
|
||||
make_release() { # dir tag
|
||||
local d=$1 tag=$2
|
||||
# A fake release: an archive holding horizon-server, signed for TAG with a throwaway key. A Nightly (TAG nightly)
|
||||
# is also signed for its own version ALSO (nightly-<date>-<commit>) in SHA256SUMS.v2.sig, titled as
|
||||
# nightly-build.yml titles it.
|
||||
make_release() { # dir tag [also]
|
||||
local d=$1 tag=$2 also=${3:-}
|
||||
mkdir -p "$d/src/horizon-server-x" "$d/files"
|
||||
printf '#!/bin/sh\necho fake\n' > "$d/src/horizon-server-x/horizon-server"; chmod +x "$d/src/horizon-server-x/horizon-server"
|
||||
tar -czf "$d/files/horizon-server-linux-x86_64.tar.gz" -C "$d/src" horizon-server-x
|
||||
EPHEMERAL=yes bash "$root/tools/ci/sign_release.sh" "$tag" "$d/files/horizon-server-linux-x86_64.tar.gz" > /dev/null
|
||||
json "$d" "$tag" yes
|
||||
ALSO_FOR=$also EPHEMERAL=yes bash "$root/tools/ci/sign_release.sh" "$tag" "$d/files/horizon-server-linux-x86_64.tar.gz" > /dev/null
|
||||
json "$d" "$tag" yes "$(title "$tag" "$also")"
|
||||
}
|
||||
json() { # dir tag with-sums: GitHub's release JSON, as install.sh reads it
|
||||
local d=$1 tag=$2 sums=$3 sha
|
||||
title() { # tag also: the release's GitHub title
|
||||
if [ -n "$2" ]; then printf 'Nightly %s · %s' "$(printf '%s' "$2" | cut -c9-18)" "$(printf '%s' "$2" | cut -c20-)"
|
||||
else printf '%s' "$1"; fi
|
||||
}
|
||||
json() { # dir tag with-sums [title]: GitHub's release JSON, as install.sh reads it
|
||||
local d=$1 tag=$2 sums=$3 name=${4:-$2} sha
|
||||
sha=$(sha256sum "$d/files/horizon-server-linux-x86_64.tar.gz" | cut -d' ' -f1)
|
||||
{
|
||||
printf '{\n "tag_name": "%s",\n "name": "%s",\n "assets": [\n' "$tag" "$tag"
|
||||
printf '{\n "tag_name": "%s",\n "name": "%s",\n "assets": [\n' "$tag" "$name"
|
||||
printf ' {\n "name": "horizon-server-linux-x86_64.tar.gz",\n "digest": "sha256:%s",\n "browser_download_url": "https://dl.test/horizon-server-linux-x86_64.tar.gz"\n }' "$sha"
|
||||
if [ "$sums" = yes ]; then for f in SHA256SUMS SHA256SUMS.sig; do
|
||||
if [ "$sums" = yes ]; then for f in SHA256SUMS SHA256SUMS.sig SHA256SUMS.v2.sig; do
|
||||
[ -f "$d/files/$f" ] || continue
|
||||
printf ',\n {\n "name": "%s",\n "browser_download_url": "https://dl.test/%s"\n }' "$f" "$f"; done; fi
|
||||
printf '\n ]\n}\n'
|
||||
} > "$d/release.json"
|
||||
@@ -81,11 +88,26 @@ edit() { # sed-expression file
|
||||
mkdir -p "$W/home"
|
||||
P=/usr/bin:/bin
|
||||
|
||||
# 1. Good signature, a Nightly (signed for the channel "nightly" under the tag nightly-2026-11-03).
|
||||
d=$W/good; make_release "$d" nightly; with_key "$d"
|
||||
edit 's/"tag_name": "nightly"/"tag_name": "nightly-2026-11-03"/' "$d/release.json"
|
||||
run "$d" "$P"; verdict "good signature installs (nightly channel)" 0 "Release signature OK" "$d"
|
||||
# 1. Good signature, a Nightly: SHA256SUMS.v2.sig signed for the version its title names.
|
||||
NIGHTLY=nightly-2026-11-03-0c02067
|
||||
d=$W/good; make_release "$d" nightly "$NIGHTLY"; with_key "$d"
|
||||
run "$d" "$P"; verdict "good signature installs (Nightly, signed for its own version)" 0 "Release signature OK" "$d"
|
||||
[ -x "$d/inst/current/horizon-server" ] || { echo "FAIL nothing installed"; fails=$((fails+1)); }
|
||||
grep -q "Downloading Horizon $NIGHTLY " "$d/out" || { echo "FAIL not installed as $NIGHTLY"; fails=$((fails+1)); }
|
||||
|
||||
# 1b. A Nightly with only the channel's signature (published before v2): refused, never the channel's signature.
|
||||
d=$W/channel; make_release "$d" nightly; with_key "$d"; json "$d" nightly yes "$(title nightly "$NIGHTLY")"
|
||||
run "$d" "$P"; verdict "Nightly without SHA256SUMS.v2.sig refused" 1 "has no SHA256SUMS.v2.sig" "$d"
|
||||
[ ! -e "$d/inst/current" ] || { echo "FAIL a Nightly without v2 was installed"; fails=$((fails+1)); }
|
||||
|
||||
# 1c. An older Nightly's files and signature retitled as a newer one: its v2 signature names the old version.
|
||||
d=$W/rollback; make_release "$d" nightly nightly-2026-11-01-1111111; with_key "$d"
|
||||
json "$d" nightly yes "$(title nightly "$NIGHTLY")"
|
||||
run "$d" "$P"; verdict "older Nightly passed off as a newer one refused" 1 "signature doesn't match" "$d"
|
||||
|
||||
# 1d. A Nightly whose title doesn't name its date and commit: refused, not dated some other way.
|
||||
d=$W/title; make_release "$d" nightly "$NIGHTLY"; with_key "$d"; json "$d" nightly yes "Nightly"
|
||||
run "$d" "$P"; verdict "Nightly with an unreadable title refused" 1 "title doesn't name its date and commit" "$d"
|
||||
|
||||
# 2. Good signature, a normal tag.
|
||||
d=$W/tag; make_release "$d" v1.2.3; with_key "$d"
|
||||
|
||||
@@ -41,8 +41,11 @@ class OptionalReleaseAssetsTests(unittest.TestCase):
|
||||
script_dir = root / "tools/ci"
|
||||
script_dir.mkdir(parents=True)
|
||||
shutil.copy(ROOT / "tools/ci/sign_release.sh", script_dir)
|
||||
env = dict(os.environ, EPHEMERAL="yes", SHA="0123456789abcdef", DATE="2026-11-04",
|
||||
SOURCE_TAG="nightly-test", RELEASE_SIGNING_KEY="", RELEASE_SIGNING_KEY_NEXT="")
|
||||
# The steps' own env from nightly-build.yml: SHORT is the commit the title and
|
||||
# SHA256SUMS.v2.sig name (ALSO_FOR, nightly-<date>-<short>).
|
||||
env = dict(os.environ, EPHEMERAL="yes", SHA="0123456789abcdef", SHORT="0123456", DATE="2026-11-04",
|
||||
ALSO_FOR="nightly-2026-11-04-0123456", SOURCE_TAG="nightly-test",
|
||||
RELEASE_SIGNING_KEY="", RELEASE_SIGNING_KEY_NEXT="")
|
||||
signed = subprocess.run(["bash", "-e", "-c", run_step("sign_release.sh nightly")],
|
||||
cwd=root, env=env, text=True, capture_output=True)
|
||||
self.assertEqual(signed.returncode, 0, signed.stderr)
|
||||
@@ -50,6 +53,7 @@ class OptionalReleaseAssetsTests(unittest.TestCase):
|
||||
(line.split() for line in (dist / "SHA256SUMS").read_text().splitlines())}
|
||||
self.assertEqual(sums, {name: hashlib.sha256(name.encode()).hexdigest() for name in present})
|
||||
self.assertTrue((dist / "SHA256SUMS.sig").stat().st_size)
|
||||
self.assertTrue((dist / "SHA256SUMS.v2.sig").stat().st_size)
|
||||
(dist / "smoke-test.json").write_text("{}")
|
||||
(dist / "nightly-notes.md").write_text("test")
|
||||
# Isolate GitHub and tag rotation; validate the exact attachment arguments
|
||||
@@ -70,7 +74,8 @@ gh() {
|
||||
cwd=root, env=env, text=True, capture_output=True)
|
||||
self.assertEqual(published.returncode, 0, published.stderr)
|
||||
files = {Path(name).name for name in (root / "published").read_text().splitlines()}
|
||||
self.assertEqual(files, present | {"SHA256SUMS", "SHA256SUMS.sig", "smoke-test.json", "nightly-notes.md"})
|
||||
self.assertEqual(files, present | {"SHA256SUMS", "SHA256SUMS.sig", "SHA256SUMS.v2.sig", "smoke-test.json",
|
||||
"nightly-notes.md"})
|
||||
for name in set(OPTIONAL) - present:
|
||||
self.assertIn(f"::warning::{name} is missing", signed.stdout)
|
||||
self.assertIn(f"::warning::{name} is missing", published.stdout)
|
||||
@@ -6,7 +6,11 @@
|
||||
# but this file, which calls only the runner's sha256sum and OpenSSL; the publish job then checks the result
|
||||
# with horizon-release-sign, as launchers do.
|
||||
# EPHEMERAL=yes (dry runs) signs with a throwaway key instead and writes its public key to ephemeral.pub.
|
||||
# ALSO_FOR=<tag> (Nightlies) signs the same SHA256SUMS a second time, for "horizon release <ALSO_FOR>\n", into
|
||||
# SHA256SUMS.v2.sig, with the same key(s): the Nightly's own version (nightly-<date>-<commit>), which current
|
||||
# launchers require, while SHA256SUMS.sig stays signed for the channel for launchers from before (docs/security.md).
|
||||
# tools/ci/sign_release.sh v2026.10.19 dist/horizon-linux-x86_64.tar.gz dist/horizon-windows-x64.zip ...
|
||||
# ALSO_FOR=nightly-2026-10-28-0c02067 tools/ci/sign_release.sh nightly dist/...
|
||||
set -euo pipefail
|
||||
tag=$1; shift
|
||||
dir=$(dirname "$1")
|
||||
@@ -21,16 +25,31 @@ for file in "$@"; do
|
||||
[[ "$(dirname "$file")" == "$dir" ]] || { echo "$file: every file must be in $dir" >&2; exit 1; }
|
||||
names+=("$(basename "$file")")
|
||||
done
|
||||
also=${ALSO_FOR:-}
|
||||
if [[ -n "$also" && ! "$also" =~ ^nightly-[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9a-f]{7,40}$ ]]; then
|
||||
echo "ALSO_FOR=$also: not a Nightly's nightly-<date>-<commit>" >&2; exit 1
|
||||
fi
|
||||
(cd "$dir" && "${sha[@]}" -- "${names[@]}") > "$dir/SHA256SUMS"
|
||||
{ printf 'horizon release %s\n' "$tag"; cat "$dir/SHA256SUMS"; } > "$work/message"
|
||||
rm -f "$dir/SHA256SUMS.sig" "$dir/ephemeral.pub"
|
||||
if [[ -n "$also" ]]; then
|
||||
{ printf 'horizon release %s\n' "$also"; cat "$dir/SHA256SUMS"; } > "$work/message.v2"
|
||||
fi
|
||||
rm -f "$dir/SHA256SUMS.sig" "$dir/SHA256SUMS.v2.sig" "$dir/ephemeral.pub"
|
||||
|
||||
sign_with() { # the key file in $1
|
||||
"$openssl" pkeyutl -sign -rawin -inkey "$1" -keyform DER -in "$work/message" -out "$work/signature"
|
||||
sign_one() { # key file, message file, signature file to append to
|
||||
"$openssl" pkeyutl -sign -rawin -inkey "$1" -keyform DER -in "$2" -out "$work/signature"
|
||||
# base64 of the 64 bytes on one line (GNU base64 wraps at 76 characters).
|
||||
base64 < "$work/signature" | tr -d '\n' >> "$dir/SHA256SUMS.sig"
|
||||
echo >> "$dir/SHA256SUMS.sig"
|
||||
rm -f "$1" "$work/signature"
|
||||
base64 < "$work/signature" | tr -d '\n' >> "$3"
|
||||
echo >> "$3"
|
||||
rm -f "$work/signature"
|
||||
}
|
||||
|
||||
sign_with() { # the key file in $1: both messages, then the key goes
|
||||
sign_one "$1" "$work/message" "$dir/SHA256SUMS.sig"
|
||||
if [[ -n "$also" ]]; then
|
||||
sign_one "$1" "$work/message.v2" "$dir/SHA256SUMS.v2.sig"
|
||||
fi
|
||||
rm -f "$1"
|
||||
}
|
||||
|
||||
if [[ -n "${EPHEMERAL:-}" ]]; then
|
||||
@@ -46,4 +65,4 @@ else
|
||||
fi
|
||||
done
|
||||
fi
|
||||
echo "$tag: SHA256SUMS lists ${#names[@]} files; $( [[ -f "$dir/SHA256SUMS.sig" ]] && echo "signed with $(wc -l < "$dir/SHA256SUMS.sig" | tr -d ' ') key(s)" || echo "no signing key, so unsigned")"
|
||||
echo "$tag: SHA256SUMS lists ${#names[@]} files; $( [[ -f "$dir/SHA256SUMS.sig" ]] && echo "signed with $(wc -l < "$dir/SHA256SUMS.sig" | tr -d ' ') key(s)" || echo "no signing key, so unsigned")$( [[ -f "$dir/SHA256SUMS.v2.sig" ]] && echo ", and for $also in SHA256SUMS.v2.sig")"
|
||||
Reference in new issue
Block a user