IpcService.cs
1 using Ryujinx.Common.Logging; 2 using Ryujinx.HLE.Exceptions; 3 using Ryujinx.HLE.HOS.Ipc; 4 using System; 5 using System.Collections.Generic; 6 using System.IO; 7 using System.Linq; 8 using System.Reflection; 9 10 namespace Ryujinx.HLE.HOS.Services 11 { 12 abstract class IpcService 13 { 14 public IReadOnlyDictionary<int, MethodInfo> CmifCommands { get; } 15 public IReadOnlyDictionary<int, MethodInfo> TipcCommands { get; } 16 17 public ServerBase Server { get; private set; } 18 19 private IpcService _parent; 20 private readonly IdDictionary _domainObjects; 21 private int _selfId; 22 private bool _isDomain; 23 24 public IpcService(ServerBase server = null) 25 { 26 CmifCommands = typeof(IpcService).Assembly.GetTypes() 27 .Where(type => type == GetType()) 28 .SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public)) 29 .SelectMany(methodInfo => methodInfo.GetCustomAttributes(typeof(CommandCmifAttribute)) 30 .Select(command => (((CommandCmifAttribute)command).Id, methodInfo))) 31 .ToDictionary(command => command.Id, command => command.methodInfo); 32 33 TipcCommands = typeof(IpcService).Assembly.GetTypes() 34 .Where(type => type == GetType()) 35 .SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public)) 36 .SelectMany(methodInfo => methodInfo.GetCustomAttributes(typeof(CommandTipcAttribute)) 37 .Select(command => (((CommandTipcAttribute)command).Id, methodInfo))) 38 .ToDictionary(command => command.Id, command => command.methodInfo); 39 40 Server = server; 41 42 _parent = this; 43 _domainObjects = new IdDictionary(); 44 _selfId = -1; 45 } 46 47 public int ConvertToDomain() 48 { 49 if (_selfId == -1) 50 { 51 _selfId = _domainObjects.Add(this); 52 } 53 54 _isDomain = true; 55 56 return _selfId; 57 } 58 59 public void ConvertToSession() 60 { 61 _isDomain = false; 62 } 63 64 public void CallCmifMethod(ServiceCtx context) 65 { 66 IpcService service = this; 67 68 if (_isDomain) 69 { 70 int domainWord0 = context.RequestData.ReadInt32(); 71 int domainObjId = context.RequestData.ReadInt32(); 72 73 int domainCmd = (domainWord0 >> 0) & 0xff; 74 int inputObjCount = (domainWord0 >> 8) & 0xff; 75 int dataPayloadSize = (domainWord0 >> 16) & 0xffff; 76 77 context.RequestData.BaseStream.Seek(0x10 + dataPayloadSize, SeekOrigin.Begin); 78 79 context.Request.ObjectIds.EnsureCapacity(inputObjCount); 80 81 for (int index = 0; index < inputObjCount; index++) 82 { 83 context.Request.ObjectIds.Add(context.RequestData.ReadInt32()); 84 } 85 86 context.RequestData.BaseStream.Seek(0x10, SeekOrigin.Begin); 87 88 if (domainCmd == 1) 89 { 90 service = GetObject(domainObjId); 91 92 context.ResponseData.Write(0L); 93 context.ResponseData.Write(0L); 94 } 95 else if (domainCmd == 2) 96 { 97 Delete(domainObjId); 98 99 context.ResponseData.Write(0L); 100 101 return; 102 } 103 else 104 { 105 throw new NotImplementedException($"Domain command: {domainCmd}"); 106 } 107 } 108 109 #pragma warning disable IDE0059 // Remove unnecessary value assignment 110 long sfciMagic = context.RequestData.ReadInt64(); 111 #pragma warning restore IDE0059 112 int commandId = (int)context.RequestData.ReadInt64(); 113 114 bool serviceExists = service.CmifCommands.TryGetValue(commandId, out MethodInfo processRequest); 115 116 if (context.Device.Configuration.IgnoreMissingServices || serviceExists) 117 { 118 ResultCode result = ResultCode.Success; 119 120 context.ResponseData.BaseStream.Seek(_isDomain ? 0x20 : 0x10, SeekOrigin.Begin); 121 122 if (serviceExists) 123 { 124 Logger.Trace?.Print(LogClass.KernelIpc, $"{service.GetType().Name}: {processRequest.Name}"); 125 126 result = (ResultCode)processRequest.Invoke(service, new object[] { context }); 127 } 128 else 129 { 130 string serviceName; 131 132 133 serviceName = (service is not DummyService dummyService) ? service.GetType().FullName : dummyService.ServiceName; 134 135 Logger.Warning?.Print(LogClass.KernelIpc, $"Missing service {serviceName}: {commandId} ignored"); 136 } 137 138 if (_isDomain) 139 { 140 foreach (int id in context.Response.ObjectIds) 141 { 142 context.ResponseData.Write(id); 143 } 144 145 context.ResponseData.BaseStream.Seek(0, SeekOrigin.Begin); 146 147 context.ResponseData.Write(context.Response.ObjectIds.Count); 148 } 149 150 context.ResponseData.BaseStream.Seek(_isDomain ? 0x10 : 0, SeekOrigin.Begin); 151 152 context.ResponseData.Write(IpcMagic.Sfco); 153 context.ResponseData.Write((long)result); 154 } 155 else 156 { 157 string dbgMessage = $"{service.GetType().FullName}: {commandId}"; 158 159 throw new ServiceNotImplementedException(service, context, dbgMessage); 160 } 161 } 162 163 public void CallTipcMethod(ServiceCtx context) 164 { 165 int commandId = (int)context.Request.Type - 0x10; 166 167 bool serviceExists = TipcCommands.TryGetValue(commandId, out MethodInfo processRequest); 168 169 if (context.Device.Configuration.IgnoreMissingServices || serviceExists) 170 { 171 ResultCode result = ResultCode.Success; 172 173 context.ResponseData.BaseStream.Seek(0x4, SeekOrigin.Begin); 174 175 if (serviceExists) 176 { 177 Logger.Debug?.Print(LogClass.KernelIpc, $"{GetType().Name}: {processRequest.Name}"); 178 179 result = (ResultCode)processRequest.Invoke(this, new object[] { context }); 180 } 181 else 182 { 183 string serviceName; 184 185 186 serviceName = (this is not DummyService dummyService) ? GetType().FullName : dummyService.ServiceName; 187 188 Logger.Warning?.Print(LogClass.KernelIpc, $"Missing service {serviceName}: {commandId} ignored"); 189 } 190 191 context.ResponseData.BaseStream.Seek(0, SeekOrigin.Begin); 192 193 context.ResponseData.Write((uint)result); 194 } 195 else 196 { 197 string dbgMessage = $"{GetType().FullName}: {commandId}"; 198 199 throw new ServiceNotImplementedException(this, context, dbgMessage); 200 } 201 } 202 203 protected void MakeObject(ServiceCtx context, IpcService obj) 204 { 205 obj.TrySetServer(_parent.Server); 206 207 if (_parent._isDomain) 208 { 209 obj._parent = _parent; 210 211 context.Response.ObjectIds.Add(_parent.Add(obj)); 212 } 213 else 214 { 215 context.Device.System.KernelContext.Syscall.CreateSession(out int serverSessionHandle, out int clientSessionHandle, false, 0); 216 217 obj.Server.AddSessionObj(serverSessionHandle, obj); 218 219 context.Response.HandleDesc = IpcHandleDesc.MakeMove(clientSessionHandle); 220 } 221 } 222 223 protected T GetObject<T>(ServiceCtx context, int index) where T : IpcService 224 { 225 int objId = context.Request.ObjectIds[index]; 226 227 IpcService obj = _parent.GetObject(objId); 228 229 return obj is T t ? t : null; 230 } 231 232 public bool TrySetServer(ServerBase newServer) 233 { 234 if (Server == null) 235 { 236 Server = newServer; 237 238 return true; 239 } 240 241 return false; 242 } 243 244 private int Add(IpcService obj) 245 { 246 return _domainObjects.Add(obj); 247 } 248 249 private bool Delete(int id) 250 { 251 object obj = _domainObjects.Delete(id); 252 253 if (obj is IDisposable disposableObj) 254 { 255 disposableObj.Dispose(); 256 } 257 258 return obj != null; 259 } 260 261 private IpcService GetObject(int id) 262 { 263 return _domainObjects.GetData<IpcService>(id); 264 } 265 266 public void SetParent(IpcService parent) 267 { 268 _parent = parent._parent; 269 } 270 271 public virtual void DestroyAtExit() 272 { 273 foreach (object domainObject in _domainObjects.Values) 274 { 275 if (domainObject != this && domainObject is IDisposable disposableObj) 276 { 277 disposableObj.Dispose(); 278 } 279 } 280 281 _domainObjects.Clear(); 282 } 283 } 284 }