Skip to content

Commit

Permalink
Add Network Walkman support
Browse files Browse the repository at this point in the history
  • Loading branch information
asivery committed Aug 18, 2024
1 parent f6cf774 commit 7fd8970
Show file tree
Hide file tree
Showing 4 changed files with 514 additions and 19 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ dist
node_modules
build
renderer
webminidisc
webminidisc

*.local
EKBROOTS.DES
94 changes: 76 additions & 18 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { app, BrowserWindow, ipcMain, Menu, protocol, dialog, FileFilter } from 'electron';
import { WebUSB } from 'usb';
import { app, BrowserWindow, ipcMain, protocol, dialog, FileFilter } from 'electron';
import { WebUSB, WebUSBDevice, usb } from 'usb';
import { DevicesIds as NetMDDevicesIds } from 'netmd-js';
import { DevicesIds as HiMDDevicesIds } from 'himd-js';
import { DeviceIds as NWDevicesIds, importKeys } from 'networkwm-js';
import path from 'path';
import fs from 'fs';
import { EWMDHiMD, EWMDNetMD } from './wmd/translations';
Expand All @@ -10,7 +11,10 @@ import fetch from 'node-fetch';
import Store from 'electron-store';
import { Connection, getSocketName, startServer } from './macos/server-bootstrap';
import { spawn } from 'child_process';
import { NetworkWMService } from './wmd/networkwm-service';
import contextMenu from 'electron-context-menu';
import prompt from 'electron-prompt';
import { EKBROOTS } from 'networkwm-js/dist/encryption';

const getOfRenderer = (...p: string[]) => path.join(__dirname, '..', 'renderer', ...p);

Expand All @@ -20,6 +24,15 @@ async function ewmdOpenDialog(window: BrowserWindow, filters: FileFilter[], dire
else return res.filePaths[0];
}

function reload(window: BrowserWindow){
// AppImages do not restart correctly
if (app.isPackaged && process.env.APPIMAGE) {
dialog.showMessageBoxSync(window, { message: "This is an AppImage. Electron has a bug where AppImages cannot restart. Please restart the app manually" });
}
app.relaunch();
app.exit();
}

app.commandLine.appendSwitch('ignore-certificate-errors');

export interface Setting {
Expand Down Expand Up @@ -82,6 +95,36 @@ function setupSettings(window: BrowserWindow) {
type: 'hostDirPath',
state: store.get('downloadPath', '') as string,
}: null,
{
family: 'Functionality',
name: 'Import NetworkWM Keyring Data',
type: 'action',
state: 0,
async handleChange() {
const resp = await prompt({
title: 'Keyring Import',
label: 'Please enter the keyring string below',
inputAttrs: {
type: 'text',
}
}, window);
if(resp === null) return;
let rawData;
let backup = { ...EKBROOTS };
Object.keys(EKBROOTS).forEach((e: any) => delete EKBROOTS[e]);
try{
rawData = Uint8Array.from(atob(resp), e => e.charCodeAt(0));
importKeys(rawData);
}catch(ex){
dialog.showMessageBoxSync(window, { message: 'Keyring import failed.' });
Object.keys(EKBROOTS).forEach((e: any) => EKBROOTS[e] = backup[e]);
return;
}
fs.writeFileSync(path.join(app.getPath('userData'), 'EKBROOTS.DES'), rawData);
dialog.showMessageBoxSync(window, { message: `Keyring import successful! Keys imported:\n${Object.keys(EKBROOTS).map(e => parseInt(e).toString(16).padStart(8, '0')).join('\n')}\nClick OK to restart the app.` });
reload(window);
}
}
];
const settings = _settings.filter(e => e);

Expand All @@ -105,6 +148,20 @@ function setupSettings(window: BrowserWindow) {
});
}

class WebUSBInterop extends WebUSB {
addKnownDevice(legacy: usb.Device, webusbInstance: WebUSBDevice){
this.knownDevices.set(legacy, webusbInstance);
}

static create(){
const webusb = new WebUSBInterop({
allowedDevices: NetMDDevicesIds.concat(HiMDDevicesIds).concat(NWDevicesIds.map(e => ({ deviceId: e.productId, ...e}))).map((n) => ({ vendorId: n.vendorId, productId: n.deviceId })),
deviceTimeout: 10000000,
});
return webusb;
}
}

function setupEncoder() {
function invoke(program: string, args: string[]): Promise<boolean> {
return new Promise<boolean>(res => {
Expand Down Expand Up @@ -222,10 +279,7 @@ function traverseObject(window: BrowserWindow, objectFactory: () => any, namespa
}

async function integrate(window: BrowserWindow) {
const webusb = new WebUSB({
allowedDevices: NetMDDevicesIds.concat(HiMDDevicesIds).map((n) => ({ vendorId: n.vendorId, productId: n.deviceId })),
deviceTimeout: 10000000,
});
const webusb = WebUSBInterop.create();

Object.defineProperty(global, 'navigator', {
writable: false,
Expand All @@ -252,16 +306,7 @@ async function integrate(window: BrowserWindow) {
let factoryIface: any = null;
let factoryDefList: string[] = [];

function reload(){
// AppImages do not restart correctly
if (app.isPackaged && process.env.APPIMAGE) {
alert("This is an AppImage. Electron has a bug where AppImages cannot restart. Please restart the app manually");
}
app.relaunch();
app.exit();
}

ipcMain.handle('reload', reload);
ipcMain.handle('reload', reload.bind(null, window));

ipcMain.handle('_switchToFactory', async () => {
factoryIface = await service.factory();
Expand Down Expand Up @@ -386,6 +431,16 @@ async function integrate(window: BrowserWindow) {
console.log(list.join(', '));
});

let keyData: Uint8Array | undefined = undefined;
try{
keyData = new Uint8Array(fs.readFileSync(path.join(app.getPath('userData'), 'EKBROOTS.DES')));
}catch(_){ console.log("Can't read roots") }

const nwService = new NetworkWMService(keyData);
// TODO: macOS
const himdDeflist = traverseObject(window, () => nwService, "_nwjs_");
ipcMain.handle('_nwjs__definedParameters', () => himdDeflist);

ipcMain.handle("openFileHostDialog", async (_, filters: { name: string, extensions: string[] }[], directory?: boolean): Promise<string | null> => {
return ewmdOpenDialog(window, filters, directory);
});
Expand All @@ -394,9 +449,12 @@ async function integrate(window: BrowserWindow) {
setupEncoder();

// On a USB disconnect event, enumerate services, check if any was connected
const addKnownDeviceCB = webusb.addKnownDevice.bind(webusb);
nwService.deviceConnectedCallback = addKnownDeviceCB;
himdService.deviceConnectedCallback = addKnownDeviceCB;
webusb.ondisconnect = event => {
if([service, himdService].some(e => e.isDeviceConnected(event.device))) {
reload();
if([service, himdService, nwService].some(e => e.isDeviceConnected(event.device))) {
reload(window);
}
}
}
Expand Down
46 changes: 46 additions & 0 deletions src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,40 @@ import { contextBridge, ipcRenderer } from 'electron';
import { Codec, NetMDFactoryService } from './wmd/original/services/interfaces/netmd';
import type { Setting } from './main';

export type InlineChangelogEntry =
| string
| { type: 'code', content: string }
| { type: 'link', url?: string, clickHandler?: 'openSettings', content: string }

export type ChangelogEntry =
| InlineChangelogEntry
| InlineChangelogEntry[]
| { type: 'sublist', name: string, content: ChangelogEntry[] }

export interface ChangelogVersion {
name: string;
contents: ChangelogEntry[]
}

export interface ChangelogVersionInjection {
entry: ChangelogVersion;
before: string | null;
}

export const CHANGELOG: ChangelogVersionInjection[] = [
{
before: 'Version 1.5.0',
entry: {
name: 'ElectronWMD 0.5.0-1.5.0',
contents: [
"Add support for Sony Network Walkman devices",
"Fix stability issues on HiMD device",
"Overhauled settings - moved ElectronWMD settings to main settings dialog",
],
}
}
];

(async () => {
console.group('PRELOAD');
console.log('====PRELOAD START====');
Expand All @@ -24,7 +58,9 @@ import type { Setting } from './main';
args[i] = { interprocessType: 'function' };
}
}
console.log(`Invoke ${name}`);
const [response, error] = await ipcRenderer.invoke(name, ...args);
console.log(`Invoke ${name} done`);
if (error) throw error;
return await response;
};
Expand Down Expand Up @@ -93,6 +129,8 @@ import type { Setting } from './main';

const himdIface: any = {};
await loadNamespaced(himdIface, "_himd_");
const nwjsIface: any = {};
await loadNamespaced(nwjsIface, "_nwjs_");

async function unrestrictedFetchJSON(url: string, parameters: any) {
return JSON.parse(await ipcRenderer.invoke('_unrestrictedFetch', url, parameters));
Expand All @@ -110,18 +148,26 @@ import type { Setting } from './main';
return ipcRenderer.invoke('openFileHostDialog', filters, directory);
}

function reload(){
return ipcRenderer.invoke('reload');
}

contextBridge.exposeInMainWorld('native', {
unrestrictedFetchJSON,

getSettings: loadSettings,

interface: iface,
himdFullInterface: himdIface,
nwInterface: nwjsIface,
signHiMDDisc,
openFileHostDialog,
reload,

invokeLocalEncoder,

wrapperChangelog: CHANGELOG,

_debug_himdPullFile: (a: string, b: string) => ipcRenderer.invoke('_debug_himdPullFile', a, b),
_debug_himdList: (a: string) => ipcRenderer.invoke('_debug_himdList', a),
});
Expand Down
Loading

0 comments on commit 7fd8970

Please sign in to comment.