mirror of
https://github.com/yuzu-emu/citra-qt-installer.git
synced 2024-12-22 13:45:35 +00:00
278 lines
8 KiB
TypeScript
278 lines
8 KiB
TypeScript
import * as xml from "https://deno.land/x/xml@2.0.4/mod.ts";
|
|
import { which } from "https://deno.land/x/which@0.2.1/mod.ts";
|
|
|
|
interface ITargetLicense {
|
|
License: { [index: string]: string };
|
|
}
|
|
|
|
interface ITargetSource {
|
|
Name: string;
|
|
DisplayName: string;
|
|
Description: string;
|
|
Repo: string;
|
|
ScriptName: string;
|
|
Default: string;
|
|
Licenses: ITargetLicense[];
|
|
}
|
|
|
|
interface IOutputTarget {
|
|
Name: string;
|
|
DisplayName: string;
|
|
Version: string;
|
|
DownloadableArchives: string;
|
|
UpdateFile: {
|
|
"@UncompressedSize": number;
|
|
"@CompressedSize": number;
|
|
"@OS": string;
|
|
};
|
|
ReleaseDate: string;
|
|
Description: string;
|
|
Default: string;
|
|
Licenses: ITargetLicense[];
|
|
Script: string;
|
|
SHA: string;
|
|
}
|
|
|
|
const tempDir = "./temp";
|
|
const distDir = "/citra/nginx/citra_repo";
|
|
|
|
async function getReleases(repo: string) {
|
|
const result = await fetch(`https://api.github.com/repos/${repo}/releases`, {
|
|
headers: {
|
|
Accept: "application/vnd.github.v3+json",
|
|
"User-Agent": "Citra Installer - Repo (j-selby)",
|
|
},
|
|
});
|
|
return result.json();
|
|
}
|
|
|
|
async function checkExists(directory: string) {
|
|
try {
|
|
await Deno.stat(directory);
|
|
return true;
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function check7z() {
|
|
for (const bin of ["7zz", "7za"]) {
|
|
const path = await which(bin);
|
|
if (path) return path;
|
|
}
|
|
throw new Error("7-zip is not available!");
|
|
}
|
|
|
|
function bufferToHex(buffer: ArrayBuffer) {
|
|
return [...new Uint8Array(buffer)]
|
|
.map((b) => b.toString(16).padStart(2, "0"))
|
|
.join("");
|
|
}
|
|
|
|
function getTopResultFor(jsonData: any, platform: string) {
|
|
for (const releaseKey in jsonData) {
|
|
const release = jsonData[releaseKey];
|
|
for (const assetKey in release.assets) {
|
|
const asset = release.assets[assetKey];
|
|
if (asset.name.indexOf(platform) !== -1 && asset.name.endsWith(".7z")) {
|
|
return {
|
|
release_id: release.tag_name.split("-")[1],
|
|
published_at: release.published_at.substr(0, 10),
|
|
name: asset.name,
|
|
size: asset.size,
|
|
hash: release.tag_name,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
return { notFound: true };
|
|
}
|
|
|
|
// The Qt Installer Framework is a pain to build or download.
|
|
// Because all we need are a few 7-zipped + xml files, we might as well generate them for ourselves.
|
|
const targets: ITargetSource[] = [
|
|
{
|
|
Name: "org.citra.nightly.%platform%",
|
|
DisplayName: "Citra Nightly",
|
|
Description:
|
|
"The nightly builds of Citra are official, tested versions of Citra that are known to work.\n" +
|
|
"(%platform%, commit: %commithash%, release date: %releasedate%)",
|
|
Repo: "citra-emu/citra-nightly",
|
|
ScriptName: "nightly",
|
|
Default: "script",
|
|
Licenses: [
|
|
{
|
|
License: {
|
|
"@file": "license.txt",
|
|
"@name": "GNU General Public License v2.0",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
Name: "org.citra.canary.%platform%",
|
|
DisplayName: "Citra Canary",
|
|
Description:
|
|
"An in-development version of Citra that uses changes that are relatively untested.\n" +
|
|
"(%platform%, commit: %commithash%, release date: %releasedate%)",
|
|
Repo: "citra-emu/citra-canary",
|
|
ScriptName: "canary",
|
|
Default: "script",
|
|
Licenses: [
|
|
{
|
|
License: {
|
|
"@file": "license.txt",
|
|
"@name": "GNU General Public License v2.0",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
async function execute() {
|
|
const zipBin = await check7z();
|
|
// Clean up any temporary directories.
|
|
try {
|
|
await Deno.remove(tempDir, { recursive: true });
|
|
} catch (_) {
|
|
// nothing
|
|
}
|
|
|
|
// Get Git information
|
|
console.debug("Getting release info...");
|
|
for (const target of targets) {
|
|
target.Repo = await getReleases(target.Repo);
|
|
}
|
|
|
|
console.debug("Building metadata...");
|
|
|
|
// If updates available is still false at the end of the foreach loop
|
|
// then that means no releases have been made -- nothing to do.
|
|
let updatesAvailable = false;
|
|
|
|
// Updates.xml
|
|
const updates = {
|
|
ApplicationName: "{AnyApplication}",
|
|
ApplicationVersion: "1.0.0", // Separate from nightly / canary versions
|
|
Checksum: false, // As they are pulled straight from Github
|
|
PackageUpdate: new Array<IOutputTarget>(),
|
|
};
|
|
|
|
async function generate(targetSource: ITargetSource, platform: string) {
|
|
// Get Git metadata
|
|
const releaseData = getTopResultFor(targetSource.Repo, platform);
|
|
const name: string = targetSource.Name.replace("%platform%", platform);
|
|
|
|
if (releaseData.notFound === true) {
|
|
console.error(`Release information not found for ${name}!`);
|
|
return;
|
|
}
|
|
|
|
const scriptName = platform + "-" + targetSource.ScriptName;
|
|
const version = releaseData.release_id;
|
|
|
|
const targetMetadataFilePath = `${distDir}/${name}/${version}meta.7z`;
|
|
if (await checkExists(targetMetadataFilePath)) {
|
|
console.debug(
|
|
`Metadata information already exists for ${name} ${version}, skipping.`
|
|
);
|
|
} else {
|
|
console.info(`Building release information for ${name} ${version}.`);
|
|
updatesAvailable = true;
|
|
|
|
// Create the temporary working directory.
|
|
const workingDirectoryPath = `${tempDir}/${name}`;
|
|
await Deno.mkdir(workingDirectoryPath, { recursive: true });
|
|
|
|
// Copy license
|
|
await Deno.copyFile("license.txt", `${workingDirectoryPath}/license.txt`);
|
|
await Deno.copyFile(
|
|
`scripts/${scriptName}.qs`,
|
|
`${workingDirectoryPath}/installscript.qs`
|
|
);
|
|
|
|
// Create 7zip archive
|
|
const fileName = `${name}.meta.7z`;
|
|
const proc = Deno.run({
|
|
cmd: [zipBin, "a", fileName, name],
|
|
cwd: tempDir,
|
|
});
|
|
const status = (await proc.status()).code;
|
|
if (status !== 0) {
|
|
throw new Error(
|
|
`Error when creating ${name} archive. Exited with ${status}.`
|
|
);
|
|
}
|
|
|
|
// Copy the metadata file into the target path.
|
|
console.debug(
|
|
`Creating target metadata for ${name} at ${targetMetadataFilePath}`
|
|
);
|
|
await Deno.mkdir(`${distDir}/${name}`, { recursive: true });
|
|
await Deno.rename(`${tempDir}/${fileName}`, `${targetMetadataFilePath}`);
|
|
|
|
// Cleanup temporary working directory.
|
|
await Deno.remove(workingDirectoryPath, { recursive: true });
|
|
}
|
|
|
|
// Create metadata for the Update.xml
|
|
const metaHash = await crypto.subtle.digest(
|
|
"SHA-1",
|
|
await Deno.readFile(targetMetadataFilePath)
|
|
);
|
|
|
|
const target = {
|
|
Name: name,
|
|
DisplayName: targetSource.DisplayName.replace("%platform%", platform),
|
|
Version: version,
|
|
DownloadableArchives: releaseData.name,
|
|
// Because we cannot compute the uncompressed size ourselves, just give a generous estimate
|
|
// (to make sure they have enough disk space).
|
|
// OS flag is useless - i.e the installer stubs it :P
|
|
UpdateFile: {
|
|
"@UncompressedSize": Math.ceil(releaseData.size * 4.85),
|
|
"@CompressedSize": releaseData.size,
|
|
"@OS": "Any",
|
|
},
|
|
ReleaseDate: releaseData.published_at,
|
|
Description: targetSource.Description.replace("%platform%", platform)
|
|
.replace("%commithash%", releaseData.hash)
|
|
.replace("%releasedate%", releaseData.published_at),
|
|
Default: targetSource.Default,
|
|
Licenses: targetSource.Licenses,
|
|
Script: "installscript.qs",
|
|
SHA: bufferToHex(metaHash),
|
|
};
|
|
|
|
updates.PackageUpdate.push(target);
|
|
}
|
|
|
|
// 6/19/18 (Flame Sage) - MSVC builds have been disabled, removed it from the below array 'msvc'
|
|
await Promise.all(
|
|
["mingw", "osx", "linux"].map((platform) => {
|
|
return Promise.all(
|
|
targets.map((targetSource) => generate(targetSource, platform))
|
|
);
|
|
})
|
|
);
|
|
|
|
if (updatesAvailable) {
|
|
const updatesXml = xml.stringify({ Updates: updates }, { indentSize: 2 });
|
|
|
|
// Save Updates.xml
|
|
await Deno.writeTextFile(`${distDir}/Updates.xml`, updatesXml);
|
|
console.info("Wrote a new Updates.xml file -- updates available.");
|
|
} else {
|
|
console.info(
|
|
"No Citra binary release updates are available for the Updates.xml -- nothing to do."
|
|
);
|
|
}
|
|
|
|
await Deno.remove(tempDir, { recursive: true });
|
|
}
|
|
|
|
execute().catch((err) => {
|
|
console.error(err);
|
|
});
|