Skip to content

Commit

Permalink
Use OscQuery to get full avatar state for every parameter, and to aut…
Browse files Browse the repository at this point in the history
…omatically determine the vrchat sendback port
  • Loading branch information
SenkyDragon committed Aug 11, 2023
1 parent c4f5298 commit 434bba9
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 37 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"got": "^12.3.0",
"io-ts": "^2.2.20",
"ip": "^2.0.0",
"osc": "^2.4.3",
"osc": "^2.4.4",
"oscquery": "^1.1.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
Expand Down
117 changes: 91 additions & 26 deletions src/main/OscConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type TypedEmitter from "typed-emitter"
import {
OSCQueryServer,
OSCTypeSimple,
OSCQAccess,
OSCQAccess, OSCQueryDiscovery, DiscoveredService, type OSCMethodDescription,
} from "oscquery";
import portfinder from 'portfinder';
import * as os from "os";
Expand All @@ -29,6 +29,7 @@ export default class OscConnection extends (EventEmitter as new () => TypedEmitt
private retryTimeout: ReturnType<typeof setTimeout> | undefined;
private lastPacket: number = 0;
public port: number = 0;
private myAddresses = new Set<string>();

constructor(
logger: (...args: unknown[]) => void,
Expand Down Expand Up @@ -99,7 +100,7 @@ export default class OscConnection extends (EventEmitter as new () => TypedEmitt

let receivedOne = false;

const myAddresses = new Set(
const myAddresses = this.myAddresses = new Set(
Object.values(os.networkInterfaces())
.flatMap(infs => infs)
.map(inf => inf?.address)
Expand All @@ -111,7 +112,6 @@ export default class OscConnection extends (EventEmitter as new () => TypedEmitt
const oscSocket = this.oscSocket = new osc.UDPPort({
localAddress: '0.0.0.0',
localPort: port,
remotePort: 9000,
metadata: true
});
oscSocket.on('ready', () => {
Expand Down Expand Up @@ -145,45 +145,110 @@ export default class OscConnection extends (EventEmitter as new () => TypedEmitt
if (!receivedOne) {
receivedOne = true;
this.log("Received an OSC message. We are probably connected.");
this.updateBulk();
}
this.lastPacket = Date.now();
this.recentlyRcvdOscCmds++;
this.lastReceiveTime = Date.now();

const address = oscMsg.address;
if (!address) return;
const path = oscMsg.address;
if (!path) return;

if (address === '/avatar/change') {
if (path === '/avatar/change') {
this.log('<-', 'Avatar change');
this._entries.clear();
this.emit('clear');
this.clearValues();
this.updateBulk();
return;
}

const arg = oscMsg.args?.[0];
if (!arg) return;
const rawValue = arg.value;

if (address.startsWith('/avatar/parameters/')) {
const key = address.substring('/avatar/parameters/'.length);
let value = this._entries.get(key);
let isNew = false;
if (!value) {
value = new OscValue();
this._entries.set(key, value);
isNew = true;
}
value.receivedUpdate(rawValue);
if (isNew) this.emit('add', key, value);
}
const param = this.parseParamFromPath(path);
this.receivedParamValue(param, oscMsg.args?.[0]?.value, false);
});

// Open the socket.
oscSocket.open();
}

private clearValues() {
this._entries.clear();
this.emit('clear');
}

private receivedParamValue(param: string | undefined, rawValue: unknown, onlyUseIfNew: boolean) {
if (param === undefined) return;
if (rawValue === undefined) return;
let value = this._entries.get(param);
let isNew = false;
if (!value) {
value = new OscValue();
this._entries.set(param, value);
isNew = true;
} else if (onlyUseIfNew) {
return;
}
value.receivedUpdate(rawValue);
if (isNew) this.emit('add', param, value);
}

private parseParamFromPath(path: string) {
if (path.startsWith('/avatar/parameters/')) {
return path.substring('/avatar/parameters/'.length);
}
return undefined;
}

public waitingForBulk = false;
private updateBulkAttempt: unknown;
private sendAddress: string | undefined;
private sendPort: number | undefined;
private async updateBulk() {
this.waitingForBulk = true;
const myAttempt = this.updateBulkAttempt = {};
const isStillValid = () => this.updateBulkAttempt == myAttempt;
while(true) {
try {
await this._updateBulk(isStillValid);
} catch(e) {
this.log("Error fetching bulk info: " + e);
if (isStillValid()) continue;
}
this.waitingForBulk = false;
break;
}
}
private async _updateBulk(isRequestStillValid: ()=>boolean) {
const discovery = new OSCQueryDiscovery();
discovery.start();
let found;
try {
found = await new Promise<[string,number,OSCMethodDescription[]]>((resolve, reject) => {
discovery.on('up', (service: DiscoveredService) => {
if (!service.hostInfo.name?.startsWith("VRChat-Client")) return;
if (!this.myAddresses.has(service.address)) return;
console.log(`FOUND OSCQUERY ${service.address} ${service.port}`);
resolve([service.hostInfo.oscIp??"", service.hostInfo.oscPort??1, service.flat()]);
});
setTimeout(() => reject(new Error("Timed out")), 10000);
});
} finally {
discovery.stop();
}

const [ip,port,nodes] = found;
if (!isRequestStillValid()) return;
this.sendAddress = ip;
this.sendPort = port;
for (const node of nodes) {
const param = this.parseParamFromPath(node.full_path ?? '');
this.receivedParamValue(param, node.arguments?.[0]?.value, true);
}
}

delayRetry() {
this.socketopen = false;
this.updateBulkAttempt = undefined;
this.waitingForBulk = false;
this.clearValues();
if (this.oscSocket) {
this.oscSocket.close();
this.oscSocket = undefined;
Expand All @@ -197,7 +262,7 @@ export default class OscConnection extends (EventEmitter as new () => TypedEmitt
}

send(paramName: string, value: number) {
if (!this.oscSocket || !this.socketopen) return;
if (!this.oscSocket || !this.socketopen || !this.sendAddress || !this.sendPort) return;
this.oscSocket.send({
address: "/avatar/parameters/" + paramName,
args: [
Expand All @@ -206,7 +271,7 @@ export default class OscConnection extends (EventEmitter as new () => TypedEmitt
value: value
}
]
});
}, this.sendAddress, this.sendPort);
}

clearDeltas() {
Expand Down
20 changes: 12 additions & 8 deletions src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,16 +158,8 @@ ipcMain.handle('oscStatus:get', async (_event, text) => {
const gameDevices = Array.from(bridge.getGameDevices());
const sections: string[] = [];

if (!oscConnection || !oscConnection.socketopen) {
sections.push(`OSC socket is starting ...`);
} else {
sections.push("OSC Port: " + oscConnection.port);
}

if (vrcConfigCheck.oscEnabled === false) {
sections.push(`OSC is disabled in your game.\nEnable it in the radial menu:\nOptions > OSC > Enabled`);
} else if (oscConnection && (!oscConnection.lastReceiveTime || oscConnection.lastReceiveTime < Date.now() - 60_000)) {
sections.push(`No data received.\nIs the game open and active?`);
}
if (vrcConfigCheck.selfInteractEnabled === false) {
sections.push('Self-Interaction is disabled in your game.\nThis breaks many OGB features.\nEnable it in the quick menu:\nSettings > Avatar Interactions > Self Interact');
Expand All @@ -176,6 +168,18 @@ ipcMain.handle('oscStatus:get', async (_event, text) => {
sections.push('Interaction is not set to everyone in game.\nEnable it in the quick menu:\nSettings > Avatar Interactions > Everyone');
}

if (!oscConnection || !oscConnection.socketopen) {
sections.push(`OSC socket is starting ...`);
} else {
sections.push("OSC Port: " + oscConnection.port);
if (oscConnection && (!oscConnection.lastReceiveTime || oscConnection.lastReceiveTime < Date.now() - 60_000)) {
sections.push(`No data received.\nIs the game open and active?`);
}
if (oscConnection?.waitingForBulk) {
sections.push(`Waiting for bulk packet from OSCQuery`);
}
}

if (vrcConfigCheck.selfInteractEnabled !== false) {
const outdated = gameDevices.some(device => {
if (device.type == 'Pen' && (device.getVersion() ?? 0) < 8) return true;
Expand Down
2 changes: 1 addition & 1 deletion src/types/osc.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ declare module 'osc' {
on(ev: 'message', cb: (message: OscMessage, timeTag: unknown, rinfo: RemoteInfo) => void);
open();
close();
send(OscMessage);
send(OscMessage, address: string, port: number);
}

interface OscMessage {
Expand Down

0 comments on commit 434bba9

Please sign in to comment.