arm64 support

This commit is contained in:
Natsumi
2025-09-02 22:30:46 +12:00
parent d4bf9ef0f6
commit f1c8c0fa65
9 changed files with 174 additions and 146 deletions
+4 -2
View File
@@ -13,8 +13,10 @@
"test:coverage": "jest --coverage", "test:coverage": "jest --coverage",
"prod": "cross-env PLATFORM=windows webpack --config webpack.config.js --mode production", "prod": "cross-env PLATFORM=windows webpack --config webpack.config.js --mode production",
"prod-linux": "cross-env PLATFORM=linux webpack --config webpack.config.js --mode production", "prod-linux": "cross-env PLATFORM=linux webpack --config webpack.config.js --mode production",
"build-electron": "node ./src-electron/download-dotnet-runtime.js && node ./src-electron/patch-package-version.js && electron-builder --publish never", "build-electron": "node ./src-electron/download-dotnet-runtime.js --arch=x64 && node ./src-electron/patch-package-version.js && electron-builder --x64 --publish never",
"postbuild-electron": "node ./src-electron/patch-node-api-dotnet.js && node ./src-electron/rename-builds.js", "build-electron-arm64": "node ./src-electron/download-dotnet-runtime.js --arch=arm64 && node ./src-electron/patch-package-version.js && electron-builder --arm64 --publish never",
"postbuild-electron": "node ./src-electron/patch-node-api-dotnet.js --arch=x64 && node ./src-electron/rename-builds.js --arch=x64",
"postbuild-electron-arm64": "node ./src-electron/patch-node-api-dotnet.js --arch=arm64 && node ./src-electron/rename-builds.js --arch=arm64",
"start-electron": "electron ." "start-electron": "electron ."
}, },
"repository": { "repository": {
+21 -40
View File
@@ -2,30 +2,9 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const https = require('https'); const https = require('https');
const { spawnSync } = require('child_process'); const { spawnSync } = require('child_process');
const { getArchAndPlatform } = require('./utils');
let runnerArch = process.arch.toString();
let runnerPlatform = process.platform.toString();
const args = process.argv.slice(2);
for (let i = 0; i < args.length; i++) {
if (args[i] === '--arch' && i + 1 < args.length) {
runnerArch = args[i + 1];
} else if (args[i] === '--platform' && i + 1 < args.length) {
runnerPlatform = args[i + 1];
}
}
let platform = '';
if (runnerPlatform === 'linux') {
platform = 'linux';
} else if (runnerPlatform === 'darwin') {
platform = 'osx';
} else if (runnerPlatform === 'win32') {
platform = 'win';
} else {
throw new Error(`Unsupported platform: ${runnerPlatform}`);
}
const DOTNET_VERSION = '9.0.8'; const DOTNET_VERSION = '9.0.8';
const DOTNET_RUNTIME_URL = `https://builds.dotnet.microsoft.com/dotnet/Runtime/${DOTNET_VERSION}/dotnet-runtime-${DOTNET_VERSION}-${platform}-${runnerArch}.tar.gz`;
const DOTNET_RUNTIME_DIR = path.join( const DOTNET_RUNTIME_DIR = path.join(
__dirname, __dirname,
'..', '..',
@@ -78,25 +57,34 @@ async function extractTarGz(tarGzPath, extractDir) {
}); });
} }
async function main() { async function downloadDotnetRuntime(arch, platform) {
if (platform !== 'linux' && platform !== 'darwin') { if (!arch || !platform) {
console.log('Skipping .NET runtime download on non supported platform'); throw new Error('Architecture and platform must be specified');
return;
} }
console.log( let dotnetPlatform = '';
`Downloading .NET ${DOTNET_VERSION}-${platform}-${runnerArch} runtime...` if (platform === 'linux') {
); dotnetPlatform = 'linux';
} else if (platform === 'darwin') {
dotnetPlatform = 'osx';
} else if (platform === 'win32') {
dotnetPlatform = 'win';
} else {
throw new Error(`Unsupported platform: ${platform}`);
}
if (!fs.existsSync(DOTNET_RUNTIME_DIR)) { if (!fs.existsSync(DOTNET_RUNTIME_DIR)) {
fs.mkdirSync(DOTNET_RUNTIME_DIR, { recursive: true }); fs.mkdirSync(DOTNET_RUNTIME_DIR, { recursive: true });
} }
console.log(
`Downloading .NET ${DOTNET_VERSION}-${dotnetPlatform}-${arch} runtime...`
);
const tarGzPath = path.join(DOTNET_RUNTIME_DIR, 'dotnet-runtime.tar.gz'); const tarGzPath = path.join(DOTNET_RUNTIME_DIR, 'dotnet-runtime.tar.gz');
const dotnetRuntimeUrl = `https://builds.dotnet.microsoft.com/dotnet/Runtime/${DOTNET_VERSION}/dotnet-runtime-${DOTNET_VERSION}-${dotnetPlatform}-${arch}.tar.gz`;
try {
// Download .NET runtime // Download .NET runtime
await downloadFile(DOTNET_RUNTIME_URL, tarGzPath); await downloadFile(dotnetRuntimeUrl, tarGzPath);
console.log('Download completed'); console.log('Download completed');
// Extract .NET runtime to a temporary directory first // Extract .NET runtime to a temporary directory first
@@ -142,14 +130,7 @@ async function main() {
console.log( console.log(
`.NET runtime downloaded and extracted to: ${DOTNET_RUNTIME_DIR}` `.NET runtime downloaded and extracted to: ${DOTNET_RUNTIME_DIR}`
); );
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
} }
if (require.main === module) { const { arch, platform } = getArchAndPlatform();
main(); downloadDotnetRuntime(arch, platform);
}
module.exports = { downloadFile, extractTarGz };
+10 -7
View File
@@ -16,16 +16,11 @@ const https = require('https');
//app.disableHardwareAcceleration(); //app.disableHardwareAcceleration();
if (process.platform === 'linux') { const bundledDotNetPath = path.join(process.resourcesPath, 'dotnet-runtime');
// Include bundled .NET runtime
const bundledDotNetPath = path.join(
process.resourcesPath,
'dotnet-runtime'
);
if (fs.existsSync(bundledDotNetPath)) { if (fs.existsSync(bundledDotNetPath)) {
// Include bundled .NET runtime
process.env.DOTNET_ROOT = bundledDotNetPath; process.env.DOTNET_ROOT = bundledDotNetPath;
process.env.PATH = `${bundledDotNetPath}:${process.env.PATH}`; process.env.PATH = `${bundledDotNetPath}:${process.env.PATH}`;
}
} else if (process.platform === 'darwin') { } else if (process.platform === 'darwin') {
const dotnetPath = path.join('/usr/local/share/dotnet'); const dotnetPath = path.join('/usr/local/share/dotnet');
const dotnetPathArm = path.join('/usr/local/share/dotnet/x64'); const dotnetPathArm = path.join('/usr/local/share/dotnet/x64');
@@ -154,6 +149,10 @@ if (!gotTheLock) {
}); });
} }
ipcMain.handle('getArch', () => {
return process.arch.toString();
});
ipcMain.handle('applyWindowSettings', (event, position, size, state) => { ipcMain.handle('applyWindowSettings', (event, position, size, state) => {
if (position) { if (position) {
mainWindow.setPosition(parseInt(position.x), parseInt(position.y)); mainWindow.setPosition(parseInt(position.x), parseInt(position.y));
@@ -812,6 +811,10 @@ function isDotNetInstalled() {
const result = spawnSync(dotnetPath, ['--list-runtimes'], { const result = spawnSync(dotnetPath, ['--list-runtimes'], {
encoding: 'utf-8' encoding: 'utf-8'
}); });
if (result.error) {
console.error('Error checking .NET runtimes:', result.error);
return false;
}
return result.stdout?.includes('.NETCore.App 9.0'); return result.stdout?.includes('.NETCore.App 9.0');
} }
+9 -3
View File
@@ -1,5 +1,6 @@
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const { getArchAndPlatform } = require('./utils');
function patchFile(filePath) { function patchFile(filePath) {
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
@@ -30,9 +31,9 @@ managedHostPath = managedHostPath.indexOf('app.asar.unpacked') < 0 ?
return false; return false;
} }
// Paths to patch function patchNodeApiDotNet(arch, platform) {
let platformName = ''; let platformName = '';
switch (process.platform) { switch (platform) {
case 'win32': case 'win32':
platformName = 'win'; platformName = 'win';
break; break;
@@ -43,12 +44,17 @@ switch (process.platform) {
platformName = 'linux'; platformName = 'linux';
break; break;
} }
if (process.arch === 'arm64') { if (arch === 'arm64') {
platformName += '-arm64'; platformName += '-arm64';
} }
const postBuildPath = path.join( const postBuildPath = path.join(
__dirname, __dirname,
`./../build/${platformName}-unpacked/resources/app.asar.unpacked/node_modules/node-api-dotnet/init.js` `./../build/${platformName}-unpacked/resources/app.asar.unpacked/node_modules/node-api-dotnet/init.js`
); );
console.log('Patching post-build init.js...'); console.log('Patching post-build init.js...');
patchFile(postBuildPath); patchFile(postBuildPath);
}
const { arch, platform } = getArchAndPlatform();
patchNodeApiDotNet(arch, platform);
+1 -1
View File
@@ -22,6 +22,7 @@ contextBridge.exposeInMainWorld('interopApi', {
const validChannels = ['launch-command']; const validChannels = ['launch-command'];
contextBridge.exposeInMainWorld('electron', { contextBridge.exposeInMainWorld('electron', {
getArch: () => ipcRenderer.invoke('getArch'),
openFileDialog: () => ipcRenderer.invoke('dialog:openFile'), openFileDialog: () => ipcRenderer.invoke('dialog:openFile'),
openDirectoryDialog: () => ipcRenderer.invoke('dialog:openDirectory'), openDirectoryDialog: () => ipcRenderer.invoke('dialog:openDirectory'),
onWindowPositionChanged: (callback) => onWindowPositionChanged: (callback) =>
@@ -48,7 +49,6 @@ contextBridge.exposeInMainWorld('electron', {
ipcRenderer: { ipcRenderer: {
on(channel, func) { on(channel, func) {
if (validChannels.includes(channel)) { if (validChannels.includes(channel)) {
console.log('contextBridge', channel, func);
ipcRenderer.on(channel, (event, ...args) => func(...args)); ipcRenderer.on(channel, (event, ...args) => func(...args));
} }
} }
+13 -4
View File
@@ -1,5 +1,6 @@
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const { getArchAndPlatform } = require('./utils');
const rootDir = path.join(__dirname, '..'); const rootDir = path.join(__dirname, '..');
const versionFilePath = path.join(rootDir, 'Version'); const versionFilePath = path.join(rootDir, 'Version');
@@ -17,9 +18,13 @@ try {
process.exit(1); process.exit(1);
} }
if (process.platform === 'linux') { function renameBuild(arch, platform) {
if (platform === 'linux') {
const oldAppImage = path.join(buildDir, `VRCX_Version.AppImage`); const oldAppImage = path.join(buildDir, `VRCX_Version.AppImage`);
const newAppImage = path.join(buildDir, `VRCX_${version}.AppImage`); const newAppImage = path.join(
buildDir,
`VRCX_${version}_${arch}.AppImage`
);
try { try {
if (fs.existsSync(oldAppImage)) { if (fs.existsSync(oldAppImage)) {
fs.renameSync(oldAppImage, newAppImage); fs.renameSync(oldAppImage, newAppImage);
@@ -31,9 +36,9 @@ if (process.platform === 'linux') {
console.error('Error renaming files:', err); console.error('Error renaming files:', err);
process.exit(1); process.exit(1);
} }
} else if (process.platform === 'darwin') { } else if (platform === 'darwin') {
const oldDmg = path.join(buildDir, `VRCX_Version.dmg`); const oldDmg = path.join(buildDir, `VRCX_Version.dmg`);
const newDmg = path.join(buildDir, `VRCX_${version}.dmg`); const newDmg = path.join(buildDir, `VRCX_${version}_${arch}.dmg`);
try { try {
if (fs.existsSync(oldDmg)) { if (fs.existsSync(oldDmg)) {
fs.renameSync(oldDmg, newDmg); fs.renameSync(oldDmg, newDmg);
@@ -48,3 +53,7 @@ if (process.platform === 'linux') {
} else { } else {
console.log('No renaming needed for this platform.'); console.log('No renaming needed for this platform.');
} }
}
const { arch, platform } = getArchAndPlatform();
renameBuild(arch, platform);
+19
View File
@@ -0,0 +1,19 @@
function getArchAndPlatform() {
// --arch= win32, darwin, linux
// --platform= x64, arm64
const args = process.argv.slice(2);
let arch = process.arch.toString();
let platform = process.platform.toString();
for (let i = 0; i < args.length; i++) {
if (args[i].startsWith('--arch=')) {
arch = args[i].split('=')[1];
}
if (args[i].startsWith('--platform=')) {
platform = args[i].split('=')[1];
}
}
console.log(`Using arch: ${arch}, platform: ${platform}`);
return { arch, platform };
}
module.exports = { getArchAndPlatform };
+8 -1
View File
@@ -14,6 +14,7 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => {
const { t } = useI18n(); const { t } = useI18n();
const state = reactive({ const state = reactive({
arch: 'x64',
appVersion: '', appVersion: '',
autoUpdateVRCX: 'Auto Download', autoUpdateVRCX: 'Auto Download',
latestAppVersion: '', latestAppVersion: '',
@@ -40,6 +41,12 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => {
}); });
async function initVRCXUpdaterSettings() { async function initVRCXUpdaterSettings() {
if (!WINDOWS) {
const arch = await window.electron.getArch();
console.log('Architecture:', arch);
state.arch = arch;
}
const [autoUpdateVRCX, vrcxId] = await Promise.all([ const [autoUpdateVRCX, vrcxId] = await Promise.all([
configRepository.getString('VRCX_autoUpdateVRCX', 'Auto Download'), configRepository.getString('VRCX_autoUpdateVRCX', 'Auto Download'),
configRepository.getString('VRCX_id', '') configRepository.getString('VRCX_id', '')
@@ -200,7 +207,7 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => {
} }
if ( if (
LINUX && LINUX &&
asset.name.endsWith('.AppImage') && asset.name.endsWith(`${state.arch}.AppImage`) &&
asset.content_type === 'application/octet-stream' asset.content_type === 'application/octet-stream'
) { ) {
downloadUrl = asset.browser_download_url; downloadUrl = asset.browser_download_url;
+2 -1
View File
@@ -30,6 +30,7 @@ declare global {
) => Promise<any>; ) => Promise<any>;
}; };
electron: { electron: {
getArch: () => Promise<string>;
openFileDialog: () => Promise<string>; openFileDialog: () => Promise<string>;
openDirectoryDialog: () => Promise<string>; openDirectoryDialog: () => Promise<string>;
desktopNotification: ( desktopNotification: (
@@ -63,7 +64,7 @@ declare global {
overlayHand: int overlayHand: int
) => Promise<void>; ) => Promise<void>;
ipcRenderer: { ipcRenderer: {
on(channel: String, func: (...args: unknown[]) => void) on(channel: String, func: (...args: unknown[]) => void);
}; };
}; };
__APP_GLOBALS__: AppGlobals; __APP_GLOBALS__: AppGlobals;