diff --git a/protocol/README.md b/protocol/README.md new file mode 100644 index 0000000..b4c741e --- /dev/null +++ b/protocol/README.md @@ -0,0 +1,4 @@ +# Protocol + +This directory stores all protocol buffers used by the various different projects here. We maintain them in this +directory so that we do not have to maintain separate files in each project. \ No newline at end of file diff --git a/protocol/playserver.proto b/protocol/playserver.proto new file mode 100644 index 0000000..84c4749 --- /dev/null +++ b/protocol/playserver.proto @@ -0,0 +1,73 @@ +syntax = "proto3"; + +enum ClientMessageType { + PING = 0; + CONNECT = 1; + SEND = 2; + BULKSEND = 3; +} + +enum ServerMessageType { + PONG = 0; + CONNACK = 1; + RECV = 2; + BULKRECV = 3; +} + +enum DsPacketType { + GENERIC = 0; + CMD = 1; + REPLY = 2; + ACK = 3; +} + +enum ConnAckFailureReason { + SERVER_FULL = 0; + INCORRECT_PASSWORD = 1; + CLIENT_VERSION_INVALID = 2; + SERVER_VERSION_INVALID = 3; + INVALID_NAME = 4; + ALREADY_CONNECTED = 5; + REMOTE_ERROR = 6; +} + +message DsPacket { + DsPacketType packet_type = 1; + bytes data = 2; + uint64 timestamp = 3; + optional uint32 aid = 4; +} + +message ConnectPacket { + uint32 client_version = 1; + string name = 2; + optional string password = 3; +} + +message ConnAckPacket { + bool success = 1; + optional ConnAckFailureReason fail_reason = 2; + optional string remote_error = 3; +} + +message BulkPackets { + repeated DsPacket packets = 1; +} + +message ClientMessage { + ClientMessageType msg_type = 1; + oneof msg_content { + ConnectPacket connect_packet = 2; + DsPacket ds_packet = 3; + BulkPackets bulk_packets = 4; + } +} + +message ServerMessage { + ServerMessageType msg_type = 1; + oneof msg_content { + ConnAckPacket connack_packet = 2; + DsPacket ds_packet = 3; + BulkPackets bulk_packets = 4; + } +} \ No newline at end of file diff --git a/webmelon-sdk/webmelon.d.ts b/webmelon-sdk/webmelon.d.ts index a481e12..1860bd5 100644 --- a/webmelon-sdk/webmelon.d.ts +++ b/webmelon-sdk/webmelon.d.ts @@ -21,6 +21,29 @@ interface WebMelonConstants { FIRMWARE_LANGUAGES: WebMelonFirmwareLanguage[]; }; +interface WebMelonRecvPacketResponse { + data: UInt8Array; + timestamp: number; +}; + +interface WebMelonRecvReplyPacketResponse { + data: UInt8Array; + timestamp: number; + aid: number; +}; + +interface WebMelonMPController { + initialize: () => void; + end: () => void; + sendPacket: (data: UInt8Array, timestamp: number) => void; + recvPacket: () => WebMelonRecvPacketResponse?; + sendCmd: (data: UInt8Array, timestamp: number) => void; + sendReply:(data: UInt8Array, timestamp: number, aid: number) => void; + sendAck: (data: Uint8Array, timestamp: number) => void; + recvHostPacket: () => WebMelonRecvPacketResponse?; + recvReplies: (timestamp: number, aidmask: number) => WebMelonRecvReplyPacketResponse[]; +}; + interface WebMelonCart { createCart: () => void; loadFileIntoCart: (filename: string) => boolean; @@ -92,6 +115,11 @@ interface WebMelonInput { setRumbleEnabled: (enabled: boolean) => void; }; +interface WebMelonMultiplayer { + setInterface: (interface: WebMelonMPController) => void; + removeInterface: () => void; +}; + interface WebMelonInterface { cart: WebMelonCart; constants: WebMelonConstants; @@ -99,6 +127,7 @@ interface WebMelonInterface { emulator: WebMelonEmulator; firmware: WebMelonFirmware; input: WebMelonInput; + multiplayer: WebMelonMultiplayer; }; interface Window { diff --git a/webmelon-sdk/webmelon.js b/webmelon-sdk/webmelon.js index 1962ad0..466908d 100644 --- a/webmelon-sdk/webmelon.js +++ b/webmelon-sdk/webmelon.js @@ -100,6 +100,39 @@ vfsInitialized: [] }; + // This class is used as the initial "offline" multiplayer inteface that just returns nothing + class OfflineMPInterface { + constructor() {} + initialize() { + console.log('Offline MP initialize called'); + } + end() { + console.log('Offline MP end called'); + } + sendPacket(data, timestamp) { + console.debug([data, timestamp]); + } + recvPacket() { + return undefined; + } + sendCmd(data, timestamp) { + console.debug([data, timestamp]); + } + sendReply(data, timestamp, aid) { + console.debug([data, timestamp, aid]); + } + sendAck(data, timestamp) { + console.debug([data, timestamp]); + } + recvHostPacket() { + return undefined; + } + recvReplies() { + return []; + } + }; + const defaultMpInterface = new OfflineMPInterface(); + let WebMelon = { // Things in here should only be used by the SDK itself. This does not need to (and should not be) // included in TypeScript bindings. The reason we do not keep it as a local variable here in the @@ -194,6 +227,7 @@ gamepadBinds: DefaultGamepadBindings, gamepadRumbleIntensity: 0.5 }, + mpInterface: defaultMpInterface, subscribers: DefaultSubscribers, storage: { initialized: false, @@ -709,6 +743,35 @@ }); } } + }, + multiplayer: { + _sendPacket: (dataPtr, length, timestamp) => { + const data = new Uint8Array(Module.HEAPU8.buffer, dataPtr, length); + WebMelon._internal.mpInterface.sendPacket(data, timestamp); + }, + _sendCmd: (dataPtr, length, timestamp) => { + const data = new Uint8Array(Module.HEAPU8.buffer, dataPtr, length); + WebMelon._internal.mpInterface.sendCmd(data, timestamp); + }, + _sendReply: (dataPtr, length, timestamp, aid) => { + const data = new Uint8Array(Module.HEAPU8.buffer, dataPtr, length); + WebMelon._internal.mpInterface.sendReply(data, timestamp, aid); + }, + _sendAck: (dataPtr, length, timestamp) => { + const data = new Uint8Array(Module.HEAPU8.buffer, dataPtr, length); + WebMelon._internal.mpInterface.sendAck(data, timestamp); + }, + _recvPacket: (dataPtr, timestampPtr) => { + const receivedPacket = WebMelon._internal.mpInterface.recvPacket(); + if (!receivedPacket) return; + + }, + setInterface: (mpInterface) => { + WebMelon._internal.mpInterface = mpInterface; + }, + removeInterface: () => { + WebMelon.multiplayer.setInterface(defaultMpInterface); + } } };