public object SendCommand(usbint_server_opcode_e opcode, usbint_server_space_e space, usbint_server_flags_e flags, [CanBeNull] params object[] args) { byte[] tBuffer = new byte[512]; object ret = null; // send directory command Array.Clear(tBuffer, 0, tBuffer.Length); tBuffer[0] = Convert.ToByte('U'); tBuffer[1] = Convert.ToByte('S'); tBuffer[2] = Convert.ToByte('B'); tBuffer[3] = Convert.ToByte('A'); // directory listing tBuffer[4] = Convert.ToByte(opcode); // opcode tBuffer[5] = Convert.ToByte(space); // space tBuffer[6] = Convert.ToByte(flags); // flags // assign arguments if (space == usbint_server_space_e.FILE) { switch (opcode) { case usbint_server_opcode_e.GET: case usbint_server_opcode_e.PUT: case usbint_server_opcode_e.LS: case usbint_server_opcode_e.MKDIR: case usbint_server_opcode_e.RM: case usbint_server_opcode_e.MV: case usbint_server_opcode_e.BOOT: { // name if (!(args[0] is string)) { throw new Exception("Command: " + opcode + " missing arg[0] string"); } string s = (string)args[0]; Buffer.BlockCopy(Encoding.ASCII.GetBytes(s.ToArray()), 0, tBuffer, 256, Math.Min(255, s.Length)); if (opcode == usbint_server_opcode_e.MV) { if (!(args[1] is string)) { throw new Exception("Command: " + opcode + " missing arg[1] string"); } // new name string n = (string)args[1]; Buffer.BlockCopy(Encoding.ASCII.GetBytes(n.ToArray()), 0, tBuffer, 8, n.Length); } else if (opcode == usbint_server_opcode_e.PUT) { // size if (!(args[1] is uint)) { throw new Exception("Command: " + opcode + " missing arg[1] uint"); } uint size = (uint)args[1]; tBuffer[252] = Convert.ToByte((size >> 24) & 0xFF); tBuffer[253] = Convert.ToByte((size >> 16) & 0xFF); tBuffer[254] = Convert.ToByte((size >> 8) & 0xFF); tBuffer[255] = Convert.ToByte((size >> 0) & 0xFF); } break; } // passthrough command case usbint_server_opcode_e.RESET: case usbint_server_opcode_e.MENU_RESET: case usbint_server_opcode_e.INFO: case usbint_server_opcode_e.STREAM: case usbint_server_opcode_e.POWER_CYCLE: break; //case usbint_server_opcode_e.EXECUTE: //case usbint_server_opcode_e.ATOMIC: //case usbint_server_opcode_e.MENU_LOCK: //case usbint_server_opcode_e.MENU_UNLOCK: //case usbint_server_opcode_e.MENU_RESET: //case usbint_server_opcode_e.EXE: //case usbint_server_opcode_e.TIME: default: throw new Exception("Unhandled Command: " + opcode + " space: " + space + " flags: " + flags); } } else { switch (opcode) { case usbint_server_opcode_e.GET: case usbint_server_opcode_e.PUT: { // offset if (!(args[0] is uint)) { throw new Exception("Command: " + opcode + " missing arg[0] uint"); } uint offset = (uint)args[0]; tBuffer[256] = Convert.ToByte((offset >> 24) & 0xFF); tBuffer[257] = Convert.ToByte((offset >> 16) & 0xFF); tBuffer[258] = Convert.ToByte((offset >> 8) & 0xFF); tBuffer[259] = Convert.ToByte((offset >> 0) & 0xFF); // size if (!(args[1] is uint)) { throw new Exception("Command: " + opcode + " missing arg[1] uint"); } uint size = (uint)args[1]; tBuffer[252] = Convert.ToByte((size >> 24) & 0xFF); tBuffer[253] = Convert.ToByte((size >> 16) & 0xFF); tBuffer[254] = Convert.ToByte((size >> 8) & 0xFF); tBuffer[255] = Convert.ToByte((size >> 0) & 0xFF); break; } case usbint_server_opcode_e.VGET: case usbint_server_opcode_e.VPUT: { if (args.Length == 0 || args.Length > 8) { throw new Exception("Command: " + opcode + " need 2 <= args <= 16 and a multiple of 2. Format: (size0, offset0), ..."); } uint i = 0; foreach (object a in args) { Tuple <int, int> t = (Tuple <int, int>)a; byte size = Convert.ToByte(t.Item2); uint offset = Convert.ToUInt32(t.Item1); tBuffer[32 + i * 4] = size; tBuffer[33 + i * 4] = Convert.ToByte((offset >> 16) & 0xFF); tBuffer[34 + i * 4] = Convert.ToByte((offset >> 8) & 0xFF); tBuffer[35 + i * 4] = Convert.ToByte((offset >> 0) & 0xFF); i++; } break; } // passthrough command case usbint_server_opcode_e.STREAM: case usbint_server_opcode_e.RESET: case usbint_server_opcode_e.MENU_RESET: case usbint_server_opcode_e.INFO: case usbint_server_opcode_e.POWER_CYCLE: break; default: throw new Exception("Unhandled Request: " + opcode + " space: " + space + " flags: " + flags); } } // handle 64B commands here int cmdSize = (opcode == usbint_server_opcode_e.VGET || opcode == usbint_server_opcode_e.VPUT) ? 64 : tBuffer.Length; serialPort.Write(tBuffer, 0, cmdSize); // read response command int curSize = 0; if ((flags & usbint_server_flags_e.NORESP) == 0) { Array.Clear(tBuffer, 0, tBuffer.Length); while (curSize < 512) { curSize += serialPort.Read(tBuffer, (curSize % 512), 512 - (curSize % 512)); } if (tBuffer[0] != 'U' || tBuffer[1] != 'S' || tBuffer[2] != 'B' || tBuffer[3] != 'A' || tBuffer[4] != Convert.ToByte(usbint_server_opcode_e.RESPONSE) || tBuffer[5] == 1) { throw new Exception("Response Error Request: " + opcode + " space: " + space + " flags: " + flags + " Response: " + tBuffer[4]); } } // handle response switch (opcode) { case usbint_server_opcode_e.INFO: { List <string> sL = new List <string>(); string s = Encoding.UTF8.GetString(tBuffer, 256 + 4, Array.IndexOf <byte>(tBuffer, 0, 256 + 4) - (256 + 4)); sL.Add(s); int v = (tBuffer[256] << 24) | (tBuffer[257] << 16) | (tBuffer[258] << 8) | (tBuffer[259] << 0); s = v.ToString("X"); sL.Add(s); s = Encoding.UTF8.GetString(tBuffer, 16, Array.IndexOf <byte>(tBuffer, 0, 16) - 16); sL.Add(s); byte f = tBuffer[6]; List <string> sList = new List <string>(); if ((f & 0x01) != 0) { sList.Add("FEAT_DSPX"); } if ((f & 0x02) != 0) { sList.Add("FEAT_ST0010"); } if ((f & 0x04) != 0) { sList.Add("FEAT_SRTC"); } if ((f & 0x08) != 0) { sList.Add("FEAT_MSU1"); } if ((f & 0x10) != 0) { sList.Add("FEAT_213F"); } if ((f & 0x20) != 0) { sList.Add("FEAT_CMD_UNLOCK"); } if ((f & 0x40) != 0) { sList.Add("FEAT_USB1"); } if ((f & 0x80) != 0) { sList.Add("FEAT_DMA1"); } sL.Add(string.Join("|", sList)); ret = sL; break; } case usbint_server_opcode_e.LS: { int type = 0; List <Tuple <int, string> > list = new List <Tuple <int, string> >(); ret = list; // read directory listing packets do { int bytesRead = serialPort.Read(tBuffer, (curSize % 512), 512 - (curSize % 512)); if (bytesRead != 0) { curSize += bytesRead; if (curSize % 512 == 0) { // parse strings for (int i = 0; i < 512;) { type = tBuffer[i++]; if (type == 0 || type == 1) { string name = ""; while (tBuffer[i] != 0x0) { name += (char)tBuffer[i++]; } i++; list.Add(Tuple.Create(type, name)); } else if (type == 2 || type == 0xFF) { // continued on the next packet break; } else { throw new IndexOutOfRangeException(); } } } } } while (type != 0xFF); break; } case usbint_server_opcode_e.VGET: case usbint_server_opcode_e.GET: { int fileSize = 0; fileSize |= tBuffer[252]; fileSize <<= 8; fileSize |= tBuffer[253]; fileSize <<= 8; fileSize |= tBuffer[254]; fileSize <<= 8; fileSize |= tBuffer[255]; fileSize <<= 0; ret = fileSize; break; } //default: break; } return(ret); }
/// <summary> /// Run is the main operation thread. /// </summary> public void Run() { var serializer = new JavaScriptSerializer(); var md5 = MD5.Create(); while (!_stop) { RequestQueueElementType t = null; bool done = false; try { t = Queue.Take(); } catch (Exception x) { done = true; } if (!done) { //var elem = t.Item2; var elem = t; var req = elem.Request; var socket = elem.Socket; OpcodeType socketOpcode; usbint_server_flags_e flags; log.Info("USB Begin: " + serializer.Serialize(req)); try { socketOpcode = (OpcodeType)Enum.Parse(typeof(OpcodeType), req.Opcode); // convert flags flags = usbint_server_flags_e.NONE; if (req.Flags != null) { foreach (var flag in req.Flags) { flags |= (usbint_server_flags_e)Enum.Parse(typeof(usbint_server_flags_e), flag); } } } catch (Exception x) { log.Warn("Invalid Command Exception: " + x.Message); socket.Context.WebSocket.Close(CloseStatusCode.ProtocolError, "Invalid Command Exception: " + x.Message); continue; } try { // perform snes operation switch (socketOpcode) { case OpcodeType.Boot: { usbint_server_opcode_e opcode = usbint_server_opcode_e.BOOT; _p.SendCommand(opcode, usbint_server_space_e.FILE, flags, req.Operands[0]); break; } case OpcodeType.Menu: case OpcodeType.Reset: { usbint_server_opcode_e opcode = (socketOpcode == OpcodeType.Menu) ? usbint_server_opcode_e.MENU_RESET : usbint_server_opcode_e.RESET; _p.SendCommand(opcode, usbint_server_space_e.FILE, flags); break; } case OpcodeType.Info: { usbint_server_opcode_e opcode = usbint_server_opcode_e.INFO; var version = (List <string>)_p.SendCommand(opcode, usbint_server_space_e.SNES, flags); ResponseType rsp = new ResponseType(); rsp.Results = version; socket.Queue.Add(new ResponseQueueElementType() { Response = rsp, Done = true }); break; } case OpcodeType.Stream: { usbint_server_opcode_e opcode = usbint_server_opcode_e.STREAM; _p.SendCommand(opcode, usbint_server_space_e.MSU, flags | usbint_server_flags_e.DATA64B); // generate data int readByte = 0; Byte[] tempData = new Byte[64]; bool streamEnd = false; while (!streamEnd) { int readSize = 64; readByte += _p.GetData(tempData, readByte, 64 - readByte); // send data if (readByte == readSize) { if ((flags & usbint_server_flags_e.STREAM_BURST) != 0) { // look for NOP to exit streamEnd = (tempData[readSize - 2] == 0xFF && tempData[readSize - 1] == 0xFF); } socket.Queue.Add(new ResponseQueueElementType() { Data = new ArraySegment <byte>(tempData, 0, readSize).ToArray(), Done = streamEnd }); readByte = 0; } } break; } case OpcodeType.GetAddress: { // TODO: decide if we want to pack more optimally int totalSize = 0; bool sizeOperand = false; // vector operands only support up to 8 tuples Tuple <int, int>[] vOperands = (req.Operands.Count <= 16) ? new Tuple <int, int> [req.Operands.Count / 2] : null; string nameOperand = ""; int operandNum = 0; foreach (var operand in req.Operands) { if (sizeOperand) { int size = int.Parse(operand, System.Globalization.NumberStyles.HexNumber); int name = int.Parse(nameOperand, System.Globalization.NumberStyles.HexNumber); totalSize += size; if (size > 255) { vOperands = null; } else if (vOperands != null) { vOperands[operandNum++] = Tuple.Create(name, size); } } else { nameOperand = operand; } sizeOperand = !sizeOperand; } // allocate full buffer to make indexing easier // pad for max blockSize Byte[] data = new Byte[totalSize + 512]; int sendCount = 0; int recvCount = 0; //try { for (int i = 0; i < req.Operands.Count; i += 2) { var name = req.Operands[i + 0]; var sizeStr = req.Operands[i + 1]; usbint_server_space_e space = (usbint_server_space_e)Enum.Parse(typeof(usbint_server_space_e), req.Space); uint address = uint.Parse(name, System.Globalization.NumberStyles.HexNumber); int size = (vOperands != null) ? totalSize : int.Parse(sizeStr, System.Globalization.NumberStyles.HexNumber); if (vOperands == null) { flags |= usbint_server_flags_e.NORESP /* | usbint_server_flags_e.DATA64B*/; _p.SendCommand(usbint_server_opcode_e.GET, space, flags, address, (uint)size); } else { flags |= usbint_server_flags_e.NORESP | usbint_server_flags_e.DATA64B; i = req.Operands.Count - 2; _p.SendCommand(usbint_server_opcode_e.VGET, space, flags, vOperands); } int blockSize = ((flags & usbint_server_flags_e.DATA64B) == 0) ? 512 : 64; int transferSize = (size + blockSize - 1) & ~(blockSize - 1); // must be ceiling of the multiple of the block size int transferCount = 0; // actual size int localRecvCount = 0; do { // fill up to one of the following conditions // 1) local operation complete // 2) MaxMessageSize data available while (transferCount < transferSize && recvCount + localRecvCount - sendCount < Constants.MaxMessageSize) { int packetOffset = localRecvCount % blockSize; int count = _p.GetData(data, recvCount + localRecvCount, blockSize - packetOffset); // Math.Min(readSize - readByte, Math.Min(Constants.MaxMessageSize - readOffset, blockSize - packetOffset))); if (count == 0) { continue; } transferCount += count; // if we received over the size then clamp it the size localRecvCount = Math.Min(transferCount, size); } // send message if // 1) We have no more data to send for this GET // 2) MaxMessage data available while (recvCount + localRecvCount - sendCount >= Math.Min(totalSize - sendCount, Constants.MaxMessageSize) && sendCount < totalSize) { // recalculate last data beat to send on every message int bytesToSend = Math.Min(totalSize - sendCount, Constants.MaxMessageSize); socket.Queue.Add(new ResponseQueueElementType() { Data = (Byte[])(new ArraySegment <byte>(data, sendCount, bytesToSend).ToArray()).Clone(), Done = sendCount + bytesToSend >= totalSize }); sendCount += bytesToSend; } // check if we are are done and have to send the final message } while (transferCount < transferSize); recvCount += localRecvCount; } log.Info("USB MD5: " + BitConverter.ToString(md5.ComputeHash(data, 0, totalSize)).Replace("-", "")); break; } case OpcodeType.PutAddress: { // TODO: decide if we want to pack more optimally int totalSize = 0; bool sizeOperand = false; // vector operands only support up to 8 tuples Tuple <int, int>[] vOperands = (req.Operands.Count <= 16) ? new Tuple <int, int> [req.Operands.Count / 2] : null; string nameOperand = ""; int operandNum = 0; foreach (var operand in req.Operands) { if (sizeOperand) { int size = int.Parse(operand, System.Globalization.NumberStyles.HexNumber); int name = int.Parse(nameOperand, System.Globalization.NumberStyles.HexNumber); totalSize += size; if (size > 255) { vOperands = null; } else if (vOperands != null) { vOperands[operandNum++] = Tuple.Create(name, size); } } else { nameOperand = operand; } sizeOperand = !sizeOperand; } // allocate full buffer to make indexing easier // pad for max blockSize Byte[] data = new Byte[totalSize + 512]; int sendCount = 0; int recvCount = 0; for (int i = 0; i < req.Operands.Count; i += 2) { var name = req.Operands[i + 0]; var sizeStr = req.Operands[i + 1]; usbint_server_space_e space = (usbint_server_space_e)Enum.Parse(typeof(usbint_server_space_e), req.Space); uint address = uint.Parse(name, System.Globalization.NumberStyles.HexNumber); int size = (vOperands != null) ? totalSize : int.Parse(sizeStr, System.Globalization.NumberStyles.HexNumber); if (vOperands == null) { flags |= usbint_server_flags_e.NORESP /* | usbint_server_flags_e.DATA64B*/; _p.SendCommand(usbint_server_opcode_e.PUT, space, flags, address, (uint)size); } else { flags |= usbint_server_flags_e.NORESP | usbint_server_flags_e.DATA64B; i = req.Operands.Count - 2; _p.SendCommand(usbint_server_opcode_e.VPUT, space, flags, vOperands); } int blockSize = ((flags & usbint_server_flags_e.DATA64B) == 0) ? 512 : 64; int localSendCount = 0; do { // fill up to one of the following conditions // 1) local operation complete // 2) blockSize data available while (recvCount - (sendCount + localSendCount) < blockSize && recvCount < totalSize) { Byte[] d = null; try { d = socket.DataQueue.Take(); } catch (Exception x) { continue; } Array.Copy(d, 0, data, recvCount, d.Length); recvCount += d.Length; } // send message if // 1) We haven't finished sending this particular PUT // 2) Block available bool dataDone = sendCount + blockSize >= totalSize; while (localSendCount < size && recvCount - (sendCount + localSendCount) >= Math.Min(totalSize - (sendCount + localSendCount), blockSize)) { _p.SendData(new ArraySegment <Byte>(data, sendCount + localSendCount, blockSize).ToArray(), blockSize); localSendCount += blockSize; } // if we received over the size then clamp it the size localSendCount = Math.Min(localSendCount, size); } while (localSendCount < size); sendCount += localSendCount; } log.Info("USB MD5: " + BitConverter.ToString(md5.ComputeHash(data, 0, totalSize)).Replace("-", "")); break; } case OpcodeType.PutIPS: { var name = req.Operands[0]; var sizeStr = req.Operands[1]; usbint_server_space_e space = (usbint_server_space_e)Enum.Parse(typeof(usbint_server_space_e), req.Space); int size = int.Parse(sizeStr, System.Globalization.NumberStyles.HexNumber); // collect the full IPS patch var data = new Byte[size]; int blockSize = ((flags & usbint_server_flags_e.DATA64B) == 0) ? 512 : 64; int recvCount = 0; while (recvCount < size) { Byte[] d = null; try { d = socket.DataQueue.Take(); } catch (Exception x) { continue; } Array.Copy(d, 0, data, recvCount, d.Length); recvCount += d.Length; } // parse IPS IPS ips = new IPS(); ips.Parse(data); int patchNum = 0; // loop through patches and send to USB foreach (var patch in ips.Items) { var tBuffer = new Byte[patch.data.Count + blockSize]; Array.Copy(patch.data.ToArray(), 0, tBuffer, 0, patch.data.Count); var localFlags = flags | usbint_server_flags_e.NORESP /* | usbint_server_flags_e.DATA64B*/; // handle hook executables if (name == "hook" && patchNum == 0) { localFlags |= usbint_server_flags_e.CLRX; } if (name == "hook" && patchNum == ips.Items.Count - 1) { localFlags |= usbint_server_flags_e.SETX; } _p.SendCommand(usbint_server_opcode_e.PUT, space, localFlags, (uint)patch.address, (uint)patch.data.Count); int sendCount = 0; while (sendCount < patch.data.Count) { int toWriteSize = Math.Min(patch.data.Count - sendCount, Constants.MaxMessageSize); _p.SendData(new ArraySegment <Byte>(tBuffer, sendCount, blockSize).ToArray(), blockSize); sendCount += blockSize; } patchNum++; } break; } case OpcodeType.List: foreach (var dir in req.Operands) { usbint_server_opcode_e opcode = usbint_server_opcode_e.LS; usbint_server_space_e space = usbint_server_space_e.FILE; var list = (List <Tuple <int, string> >)_p.SendCommand(opcode, space, flags, dir); ResponseType rsp = new ResponseType(); rsp.Results = new List <string>(); foreach (var item in list) { rsp.Results.Add(item.Item1.ToString()); rsp.Results.Add(item.Item2); } // send entire list (or last) socket.Queue.Add(new ResponseQueueElementType() { Response = rsp, Done = true }); } break; case OpcodeType.GetFile: { foreach (var name in req.Operands) { usbint_server_opcode_e opcode = usbint_server_opcode_e.GET; usbint_server_space_e space = usbint_server_space_e.FILE; int size = (int)_p.SendCommand(opcode, space, flags, name); Byte[] tempData = new Byte[Constants.MaxMessageSize]; // send back response with size ResponseType rsp = new ResponseType(); rsp.Results = new List <string>(); rsp.Results.Add(size.ToString("X")); socket.Queue.Add(new ResponseQueueElementType() { Response = rsp, Done = false }); int blockSize = ((flags & usbint_server_flags_e.DATA64B) == 0) ? 512 : 64; int readSize = (size + blockSize - 1) & ~(blockSize - 1); int readByte = 0; int writeSize = size; int writeByte = 0; while (readByte < readSize) { int readOffset = readByte % Constants.MaxMessageSize; int packetOffset = readByte % blockSize; var count = _p.GetData(tempData, readOffset, blockSize - packetOffset); if (count == 0) { continue; } readByte += count; // send data if ((readByte == readSize || (readByte - writeByte >= Constants.MaxMessageSize)) && (writeByte < writeSize)) { int toWriteSize = Math.Min(writeSize - writeByte, Constants.MaxMessageSize); writeByte += toWriteSize; socket.Queue.Add(new ResponseQueueElementType() { Data = new ArraySegment <byte>(tempData, 0, toWriteSize).ToArray(), Done = (writeByte >= writeSize) }); } } } break; } case OpcodeType.PutFile: for (int i = 0; i < req.Operands.Count; i += 2) { var name = req.Operands[i + 0]; var sizeStr = req.Operands[i + 1]; usbint_server_space_e space = usbint_server_space_e.FILE; int size = int.Parse(sizeStr, System.Globalization.NumberStyles.HexNumber); _p.SendCommand(usbint_server_opcode_e.PUT, space, flags, name, (uint)size); // get data and write to USB int blockSize = ((flags & usbint_server_flags_e.DATA64B) == 0) ? 512 : 64; // simplify data receipt by allocating a full size buffer Byte[] receiveBuffer = new Byte[blockSize]; Byte[] fileBuffer = new Byte[size + Constants.MaxMessageSize]; int getCount = 0; int putCount = 0; do { Byte[] d = null; bool dataDone = false; try { d = socket.DataQueue.Take(); } catch (Exception x) { dataDone = true; } if (!dataDone) { Array.Copy(d, 0, fileBuffer, getCount, d.Length); getCount += d.Length; int nextCount = (getCount >= size) ? ((size + blockSize - 1) & ~(blockSize - 1)) : (getCount & ~(blockSize - 1)); // send data over USB while (putCount < nextCount) { // copies for now. Would be better to work with array segments Array.Copy(fileBuffer, putCount, receiveBuffer, 0, blockSize); putCount += blockSize; _p.SendData(receiveBuffer, blockSize); } } } while (putCount < size); } break; case OpcodeType.Rename: { for (int i = 0; i < req.Operands.Count; i += 2) { string name = req.Operands[i + 0]; string newName = req.Operands[i + 1]; usbint_server_opcode_e opcode = usbint_server_opcode_e.MV; usbint_server_space_e space = usbint_server_space_e.FILE; _p.SendCommand(opcode, space, flags, name, newName); } break; } case OpcodeType.MakeDir: case OpcodeType.Remove: { foreach (var name in req.Operands) { usbint_server_opcode_e opcode = (socketOpcode == OpcodeType.MakeDir) ? usbint_server_opcode_e.MKDIR : usbint_server_opcode_e.RM; usbint_server_space_e space = usbint_server_space_e.FILE; _p.SendCommand(opcode, space, flags, name); } break; } } } catch (Exception e) { // TODO: close all sockets and clear queues log.Error("USB Exception: " + e.Message); lock (_ports) { var p = _ports[_p.PortName()]; lock (p.Clients) { // close all clients try { foreach (var c in p.Clients) { c.Value.Context.WebSocket.Close(); } // clear them out p.Clients.Clear(); } catch (Exception x) { // Clients have been deleted } } // remove the port so a new one needs to be created _ports.Remove(_p.PortName()); } break; } log.Info("USB End: " + serializer.Serialize(req)); } } }