From b7e1d9930db6d80fcb1f7c5c6b0aa627e42e6595 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sun, 4 Feb 2018 20:08:20 -0300 Subject: [PATCH] aloha --- .gitattributes | 63 + .gitignore | 160 +++ GLScreen.cs | 366 +++++ LICENSE.txt | 24 + Program.cs | 57 + Ryujinx.csproj | 12 + Ryujinx.sln | 25 + Ryujinx/Cpu/ABitUtils.cs | 57 + Ryujinx/Cpu/AOpCodeTable.cs | 374 +++++ Ryujinx/Cpu/AThread.cs | 74 + Ryujinx/Cpu/ATranslatedSub.cs | 104 ++ Ryujinx/Cpu/ATranslator.cs | 106 ++ Ryujinx/Cpu/Decoder/ABlock.cs | 35 + Ryujinx/Cpu/Decoder/ACond.cs | 22 + Ryujinx/Cpu/Decoder/ADataOp.cs | 10 + Ryujinx/Cpu/Decoder/ADecoder.cs | 206 +++ Ryujinx/Cpu/Decoder/ADecoderHelper.cs | 107 ++ Ryujinx/Cpu/Decoder/AIntType.cs | 14 + Ryujinx/Cpu/Decoder/AOpCode.cs | 36 + Ryujinx/Cpu/Decoder/AOpCodeAdr.cs | 18 + Ryujinx/Cpu/Decoder/AOpCodeAlu.cs | 24 + Ryujinx/Cpu/Decoder/AOpCodeAluImm.cs | 41 + Ryujinx/Cpu/Decoder/AOpCodeAluRs.cs | 21 + Ryujinx/Cpu/Decoder/AOpCodeAluRx.cs | 19 + Ryujinx/Cpu/Decoder/AOpCodeBImm.cs | 11 + Ryujinx/Cpu/Decoder/AOpCodeBImmAl.cs | 12 + Ryujinx/Cpu/Decoder/AOpCodeBImmCmp.cs | 16 + Ryujinx/Cpu/Decoder/AOpCodeBImmCond.cs | 25 + Ryujinx/Cpu/Decoder/AOpCodeBImmTest.cs | 20 + Ryujinx/Cpu/Decoder/AOpCodeBReg.cs | 24 + Ryujinx/Cpu/Decoder/AOpCodeBfm.cs | 29 + Ryujinx/Cpu/Decoder/AOpCodeCcmp.cs | 31 + Ryujinx/Cpu/Decoder/AOpCodeCcmpImm.cs | 11 + Ryujinx/Cpu/Decoder/AOpCodeCcmpReg.cs | 15 + Ryujinx/Cpu/Decoder/AOpCodeCsel.cs | 17 + Ryujinx/Cpu/Decoder/AOpCodeException.cs | 14 + Ryujinx/Cpu/Decoder/AOpCodeMem.cs | 19 + Ryujinx/Cpu/Decoder/AOpCodeMemEx.cs | 16 + Ryujinx/Cpu/Decoder/AOpCodeMemImm.cs | 53 + Ryujinx/Cpu/Decoder/AOpCodeMemLit.cs | 28 + Ryujinx/Cpu/Decoder/AOpCodeMemPair.cs | 25 + Ryujinx/Cpu/Decoder/AOpCodeMemReg.cs | 20 + Ryujinx/Cpu/Decoder/AOpCodeMov.cs | 36 + Ryujinx/Cpu/Decoder/AOpCodeMul.cs | 16 + Ryujinx/Cpu/Decoder/AOpCodeSimd.cs | 27 + Ryujinx/Cpu/Decoder/AOpCodeSimdCvt.cs | 31 + Ryujinx/Cpu/Decoder/AOpCodeSimdFcond.cs | 17 + Ryujinx/Cpu/Decoder/AOpCodeSimdFmov.cs | 33 + Ryujinx/Cpu/Decoder/AOpCodeSimdImm.cs | 94 ++ Ryujinx/Cpu/Decoder/AOpCodeSimdIns.cs | 36 + Ryujinx/Cpu/Decoder/AOpCodeSimdMemImm.cs | 19 + Ryujinx/Cpu/Decoder/AOpCodeSimdMemLit.cs | 31 + Ryujinx/Cpu/Decoder/AOpCodeSimdMemMult.cs | 54 + Ryujinx/Cpu/Decoder/AOpCodeSimdMemPair.cs | 16 + Ryujinx/Cpu/Decoder/AOpCodeSimdMemReg.cs | 14 + Ryujinx/Cpu/Decoder/AOpCodeSimdReg.cs | 16 + Ryujinx/Cpu/Decoder/AOpCodeSimdRegElem.cs | 26 + Ryujinx/Cpu/Decoder/AOpCodeSimdShImm.cs | 26 + Ryujinx/Cpu/Decoder/AOpCodeSimdTbl.cs | 12 + Ryujinx/Cpu/Decoder/AOpCodeSystem.cs | 24 + Ryujinx/Cpu/Decoder/AShiftType.cs | 10 + Ryujinx/Cpu/Decoder/IAOpCode.cs | 13 + Ryujinx/Cpu/Decoder/IAOpCodeAlu.cs | 10 + Ryujinx/Cpu/Decoder/IAOpCodeAluImm.cs | 7 + Ryujinx/Cpu/Decoder/IAOpCodeAluRs.cs | 10 + Ryujinx/Cpu/Decoder/IAOpCodeAluRx.cs | 10 + Ryujinx/Cpu/Decoder/IAOpCodeCond.cs | 7 + Ryujinx/Cpu/Decoder/IAOpCodeLit.cs | 11 + Ryujinx/Cpu/Decoder/IAOpCodeSimd.cs | 7 + .../Exceptions/VmmAccessViolationException.cs | 14 + .../Cpu/Exceptions/VmmOutOfMemoryException.cs | 13 + .../Cpu/Exceptions/VmmPageFaultException.cs | 13 + Ryujinx/Cpu/Instruction/AInst.cs | 18 + Ryujinx/Cpu/Instruction/AInstEmitAlu.cs | 296 ++++ Ryujinx/Cpu/Instruction/AInstEmitAluHelper.cs | 159 +++ Ryujinx/Cpu/Instruction/AInstEmitBfm.cs | 208 +++ Ryujinx/Cpu/Instruction/AInstEmitCcmp.cs | 81 ++ Ryujinx/Cpu/Instruction/AInstEmitCsel.cs | 59 + Ryujinx/Cpu/Instruction/AInstEmitException.cs | 33 + Ryujinx/Cpu/Instruction/AInstEmitFlow.cs | 124 ++ Ryujinx/Cpu/Instruction/AInstEmitMemory.cs | 252 ++++ Ryujinx/Cpu/Instruction/AInstEmitMemoryEx.cs | 180 +++ .../Cpu/Instruction/AInstEmitMemoryHelper.cs | 97 ++ Ryujinx/Cpu/Instruction/AInstEmitMove.cs | 41 + Ryujinx/Cpu/Instruction/AInstEmitMul.cs | 80 ++ Ryujinx/Cpu/Instruction/AInstEmitScalar.cs | 659 +++++++++ Ryujinx/Cpu/Instruction/AInstEmitSimd.cs | 965 +++++++++++++ Ryujinx/Cpu/Instruction/AInstEmitSystem.cs | 84 ++ Ryujinx/Cpu/Instruction/AInstEmitter.cs | 6 + Ryujinx/Cpu/Instruction/ASoftFallback.cs | 1207 +++++++++++++++++ Ryujinx/Cpu/Memory/AMemory.cs | 237 ++++ Ryujinx/Cpu/Memory/AMemoryAlloc.cs | 35 + Ryujinx/Cpu/Memory/AMemoryHelper.cs | 73 + Ryujinx/Cpu/Memory/AMemoryMapInfo.cs | 19 + Ryujinx/Cpu/Memory/AMemoryMgr.cs | 336 +++++ Ryujinx/Cpu/Memory/AMemoryPerm.cs | 15 + Ryujinx/Cpu/State/ACoreType.cs | 8 + Ryujinx/Cpu/State/APState.cs | 23 + Ryujinx/Cpu/State/ARegister.cs | 142 ++ Ryujinx/Cpu/State/ARegisterSize.cs | 10 + Ryujinx/Cpu/State/ARegisterType.cs | 9 + Ryujinx/Cpu/State/ARegisters.cs | 126 ++ Ryujinx/Cpu/State/AVec.cs | 243 ++++ Ryujinx/Cpu/State/SvcEventArgs.cs | 14 + Ryujinx/Cpu/Translation/AILBlock.cs | 65 + Ryujinx/Cpu/Translation/AILConv.cs | 113 ++ Ryujinx/Cpu/Translation/AILEmitter.cs | 190 +++ Ryujinx/Cpu/Translation/AILEmitterCtx.cs | 548 ++++++++ Ryujinx/Cpu/Translation/AILLabel.cs | 28 + Ryujinx/Cpu/Translation/AILOpCode.cs | 19 + Ryujinx/Cpu/Translation/AILOpCodeBranch.cs | 21 + Ryujinx/Cpu/Translation/AILOpCodeCall.cs | 20 + Ryujinx/Cpu/Translation/AILOpCodeConst.cs | 81 ++ Ryujinx/Cpu/Translation/AILOpCodeLoad.cs | 82 ++ Ryujinx/Cpu/Translation/AILOpCodeLog.cs | 17 + Ryujinx/Cpu/Translation/AILOpCodeStore.cs | 82 ++ Ryujinx/Cpu/Translation/AIoType.cs | 18 + Ryujinx/Cpu/Translation/ALocalAlloc.cs | 231 ++++ Ryujinx/Cpu/Translation/IAILEmit.cs | 7 + Ryujinx/Cpu/Translation/ILGeneratorEx.cs | 129 ++ Ryujinx/Gal/GalPrimitiveType.cs | 21 + Ryujinx/Gal/GalVertexAttrib.cs | 33 + Ryujinx/Gal/GalVertexAttribSize.cs | 20 + Ryujinx/Gal/GalVertexAttribType.cs | 13 + Ryujinx/Gal/IGalRenderer.cs | 17 + Ryujinx/Gal/OpenGL/OpenGLRenderer.cs | 282 ++++ Ryujinx/Gpu/BCn.cs | 468 +++++++ Ryujinx/Gpu/NsGpu.cs | 22 + Ryujinx/Gpu/NsGpuEngine.cs | 13 + Ryujinx/Gpu/NsGpuMemoryMgr.cs | 204 +++ Ryujinx/Gpu/NsGpuPBEntry.cs | 79 ++ Ryujinx/Gpu/NsGpuPGraph.cs | 276 ++++ Ryujinx/Gpu/NsGpuRegister.cs | 93 ++ Ryujinx/Gpu/NsGpuTexture.cs | 10 + Ryujinx/Gpu/NsGpuTextureFormat.cs | 9 + Ryujinx/Gpu/SwizzleAddr.cs | 144 ++ Ryujinx/Loaders/Compression/Lz4.cs | 78 ++ Ryujinx/Loaders/ElfDyn.cs | 15 + Ryujinx/Loaders/ElfDynTag.cs | 72 + Ryujinx/Loaders/ElfRel.cs | 19 + Ryujinx/Loaders/ElfRelType.cs | 128 ++ Ryujinx/Loaders/ElfSym.cs | 43 + Ryujinx/Loaders/ElfSymBinding.cs | 9 + Ryujinx/Loaders/ElfSymType.cs | 13 + Ryujinx/Loaders/ElfSymVisibility.cs | 10 + Ryujinx/Loaders/Executable.cs | 144 ++ Ryujinx/Loaders/Executables/IElf.cs | 17 + Ryujinx/Loaders/Executables/Nro.cs | 62 + Ryujinx/Loaders/Executables/Nso.cs | 122 ++ Ryujinx/OsHle/CondVar.cs | 86 ++ Ryujinx/OsHle/Display.cs | 12 + Ryujinx/OsHle/FileDesc.cs | 12 + Ryujinx/OsHle/Handles/HDomain.cs | 58 + Ryujinx/OsHle/Handles/HEvent.cs | 7 + Ryujinx/OsHle/Handles/HNvMap.cs | 18 + Ryujinx/OsHle/Handles/HSession.cs | 27 + Ryujinx/OsHle/Handles/HSessionObj.cs | 12 + Ryujinx/OsHle/Handles/HSharedMem.cs | 12 + Ryujinx/OsHle/Handles/HThread.cs | 14 + Ryujinx/OsHle/Handles/HTransferMem.cs | 23 + Ryujinx/OsHle/Horizon.cs | 163 +++ Ryujinx/OsHle/Ipc/IpcBuffDesc.cs | 27 + Ryujinx/OsHle/Ipc/IpcDomCmd.cs | 8 + Ryujinx/OsHle/Ipc/IpcHandleDesc.cs | 90 ++ Ryujinx/OsHle/Ipc/IpcHandler.cs | 358 +++++ Ryujinx/OsHle/Ipc/IpcMessage.cs | 231 ++++ Ryujinx/OsHle/Ipc/IpcMessageType.cs | 10 + Ryujinx/OsHle/Ipc/IpcPtrBuffDesc.cs | 26 + Ryujinx/OsHle/Ipc/IpcRecvListBuffDesc.cs | 19 + Ryujinx/OsHle/MemoryInfo.cs | 28 + Ryujinx/OsHle/MemoryType.cs | 25 + Ryujinx/OsHle/Mutex.cs | 89 ++ .../Objects/AccIManagerForApplication.cs | 17 + Ryujinx/OsHle/Objects/AccIProfile.cs | 18 + .../OsHle/Objects/AmIApplicationFunctions.cs | 56 + Ryujinx/OsHle/Objects/AmIApplicationProxy.cs | 63 + Ryujinx/OsHle/Objects/AmIAudioController.cs | 7 + Ryujinx/OsHle/Objects/AmICommonStateGetter.cs | 55 + Ryujinx/OsHle/Objects/AmIDebugFunctions.cs | 7 + Ryujinx/OsHle/Objects/AmIDisplayController.cs | 7 + .../OsHle/Objects/AmILibraryAppletCreator.cs | 7 + .../Objects/AmIParentalControlService.cs | 7 + Ryujinx/OsHle/Objects/AmISelfController.cs | 28 + Ryujinx/OsHle/Objects/AmIStorage.cs | 23 + Ryujinx/OsHle/Objects/AmIStorageAccessor.cs | 56 + Ryujinx/OsHle/Objects/AmIWindowController.cs | 17 + Ryujinx/OsHle/Objects/ApmISession.cs | 13 + Ryujinx/OsHle/Objects/AudIAudioRenderer.cs | 36 + Ryujinx/OsHle/Objects/FriendIFriendService.cs | 7 + Ryujinx/OsHle/Objects/FspSrvIFile.cs | 72 + Ryujinx/OsHle/Objects/FspSrvIFileSystem.cs | 70 + Ryujinx/OsHle/Objects/FspSrvIStorage.cs | 44 + Ryujinx/OsHle/Objects/HidIAppletResource.cs | 22 + Ryujinx/OsHle/Objects/ObjHelper.cs | 24 + Ryujinx/OsHle/Objects/Parcel.cs | 58 + Ryujinx/OsHle/Objects/TimeISteadyClock.cs | 7 + Ryujinx/OsHle/Objects/TimeISystemClock.cs | 16 + Ryujinx/OsHle/Objects/TimeITimeZoneService.cs | 7 + .../Objects/ViIApplicationDisplayService.cs | 148 ++ Ryujinx/OsHle/Objects/ViIHOSBinderDriver.cs | 211 +++ .../OsHle/Objects/ViIManagerDisplayService.cs | 17 + .../OsHle/Objects/ViISystemDisplayService.cs | 10 + Ryujinx/OsHle/Process.cs | 179 +++ Ryujinx/OsHle/ServiceCtx.cs | 52 + Ryujinx/OsHle/Services/ServiceAcc.cs | 33 + Ryujinx/OsHle/Services/ServiceApm.cs | 16 + Ryujinx/OsHle/Services/ServiceAppletOE.cs | 16 + Ryujinx/OsHle/Services/ServiceAud.cs | 60 + Ryujinx/OsHle/Services/ServiceFriend.cs | 16 + Ryujinx/OsHle/Services/ServiceFspSrv.cs | 42 + Ryujinx/OsHle/Services/ServiceHid.cs | 56 + Ryujinx/OsHle/Services/ServiceLm.cs | 12 + Ryujinx/OsHle/Services/ServiceNvDrv.cs | 601 ++++++++ Ryujinx/OsHle/Services/ServicePctl.cs | 16 + Ryujinx/OsHle/Services/ServicePl.cs | 35 + Ryujinx/OsHle/Services/ServiceSet.cs | 32 + Ryujinx/OsHle/Services/ServiceSm.cs | 48 + Ryujinx/OsHle/Services/ServiceTime.cs | 37 + Ryujinx/OsHle/Services/ServiceVi.cs | 18 + Ryujinx/OsHle/Svc/SvcHandler.cs | 79 ++ Ryujinx/OsHle/Svc/SvcMemory.cs | 126 ++ Ryujinx/OsHle/Svc/SvcSystem.cs | 163 +++ Ryujinx/OsHle/Svc/SvcThread.cs | 79 ++ Ryujinx/OsHle/Svc/SvcThreadSync.cs | 87 ++ Ryujinx/OsHle/Utilities/IdPool.cs | 53 + Ryujinx/OsHle/Utilities/IdPoolWithObj.cs | 78 ++ Ryujinx/OsHle/Utilities/MemReader.cs | 44 + Ryujinx/OsHle/Utilities/MemWriter.cs | 38 + Ryujinx/Switch.cs | 42 + Ryujinx/VirtualFs.cs | 65 + 230 files changed, 17548 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 GLScreen.cs create mode 100644 LICENSE.txt create mode 100644 Program.cs create mode 100644 Ryujinx.csproj create mode 100644 Ryujinx.sln create mode 100644 Ryujinx/Cpu/ABitUtils.cs create mode 100644 Ryujinx/Cpu/AOpCodeTable.cs create mode 100644 Ryujinx/Cpu/AThread.cs create mode 100644 Ryujinx/Cpu/ATranslatedSub.cs create mode 100644 Ryujinx/Cpu/ATranslator.cs create mode 100644 Ryujinx/Cpu/Decoder/ABlock.cs create mode 100644 Ryujinx/Cpu/Decoder/ACond.cs create mode 100644 Ryujinx/Cpu/Decoder/ADataOp.cs create mode 100644 Ryujinx/Cpu/Decoder/ADecoder.cs create mode 100644 Ryujinx/Cpu/Decoder/ADecoderHelper.cs create mode 100644 Ryujinx/Cpu/Decoder/AIntType.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCode.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeAdr.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeAlu.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeAluImm.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeAluRs.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeAluRx.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeBImm.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeBImmAl.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeBImmCmp.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeBImmCond.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeBImmTest.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeBReg.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeBfm.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeCcmp.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeCcmpImm.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeCcmpReg.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeCsel.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeException.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeMem.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeMemEx.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeMemImm.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeMemLit.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeMemPair.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeMemReg.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeMov.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeMul.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeSimd.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeSimdCvt.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeSimdFcond.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeSimdFmov.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeSimdImm.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeSimdIns.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeSimdMemImm.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeSimdMemLit.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeSimdMemMult.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeSimdMemPair.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeSimdMemReg.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeSimdReg.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeSimdRegElem.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeSimdShImm.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeSimdTbl.cs create mode 100644 Ryujinx/Cpu/Decoder/AOpCodeSystem.cs create mode 100644 Ryujinx/Cpu/Decoder/AShiftType.cs create mode 100644 Ryujinx/Cpu/Decoder/IAOpCode.cs create mode 100644 Ryujinx/Cpu/Decoder/IAOpCodeAlu.cs create mode 100644 Ryujinx/Cpu/Decoder/IAOpCodeAluImm.cs create mode 100644 Ryujinx/Cpu/Decoder/IAOpCodeAluRs.cs create mode 100644 Ryujinx/Cpu/Decoder/IAOpCodeAluRx.cs create mode 100644 Ryujinx/Cpu/Decoder/IAOpCodeCond.cs create mode 100644 Ryujinx/Cpu/Decoder/IAOpCodeLit.cs create mode 100644 Ryujinx/Cpu/Decoder/IAOpCodeSimd.cs create mode 100644 Ryujinx/Cpu/Exceptions/VmmAccessViolationException.cs create mode 100644 Ryujinx/Cpu/Exceptions/VmmOutOfMemoryException.cs create mode 100644 Ryujinx/Cpu/Exceptions/VmmPageFaultException.cs create mode 100644 Ryujinx/Cpu/Instruction/AInst.cs create mode 100644 Ryujinx/Cpu/Instruction/AInstEmitAlu.cs create mode 100644 Ryujinx/Cpu/Instruction/AInstEmitAluHelper.cs create mode 100644 Ryujinx/Cpu/Instruction/AInstEmitBfm.cs create mode 100644 Ryujinx/Cpu/Instruction/AInstEmitCcmp.cs create mode 100644 Ryujinx/Cpu/Instruction/AInstEmitCsel.cs create mode 100644 Ryujinx/Cpu/Instruction/AInstEmitException.cs create mode 100644 Ryujinx/Cpu/Instruction/AInstEmitFlow.cs create mode 100644 Ryujinx/Cpu/Instruction/AInstEmitMemory.cs create mode 100644 Ryujinx/Cpu/Instruction/AInstEmitMemoryEx.cs create mode 100644 Ryujinx/Cpu/Instruction/AInstEmitMemoryHelper.cs create mode 100644 Ryujinx/Cpu/Instruction/AInstEmitMove.cs create mode 100644 Ryujinx/Cpu/Instruction/AInstEmitMul.cs create mode 100644 Ryujinx/Cpu/Instruction/AInstEmitScalar.cs create mode 100644 Ryujinx/Cpu/Instruction/AInstEmitSimd.cs create mode 100644 Ryujinx/Cpu/Instruction/AInstEmitSystem.cs create mode 100644 Ryujinx/Cpu/Instruction/AInstEmitter.cs create mode 100644 Ryujinx/Cpu/Instruction/ASoftFallback.cs create mode 100644 Ryujinx/Cpu/Memory/AMemory.cs create mode 100644 Ryujinx/Cpu/Memory/AMemoryAlloc.cs create mode 100644 Ryujinx/Cpu/Memory/AMemoryHelper.cs create mode 100644 Ryujinx/Cpu/Memory/AMemoryMapInfo.cs create mode 100644 Ryujinx/Cpu/Memory/AMemoryMgr.cs create mode 100644 Ryujinx/Cpu/Memory/AMemoryPerm.cs create mode 100644 Ryujinx/Cpu/State/ACoreType.cs create mode 100644 Ryujinx/Cpu/State/APState.cs create mode 100644 Ryujinx/Cpu/State/ARegister.cs create mode 100644 Ryujinx/Cpu/State/ARegisterSize.cs create mode 100644 Ryujinx/Cpu/State/ARegisterType.cs create mode 100644 Ryujinx/Cpu/State/ARegisters.cs create mode 100644 Ryujinx/Cpu/State/AVec.cs create mode 100644 Ryujinx/Cpu/State/SvcEventArgs.cs create mode 100644 Ryujinx/Cpu/Translation/AILBlock.cs create mode 100644 Ryujinx/Cpu/Translation/AILConv.cs create mode 100644 Ryujinx/Cpu/Translation/AILEmitter.cs create mode 100644 Ryujinx/Cpu/Translation/AILEmitterCtx.cs create mode 100644 Ryujinx/Cpu/Translation/AILLabel.cs create mode 100644 Ryujinx/Cpu/Translation/AILOpCode.cs create mode 100644 Ryujinx/Cpu/Translation/AILOpCodeBranch.cs create mode 100644 Ryujinx/Cpu/Translation/AILOpCodeCall.cs create mode 100644 Ryujinx/Cpu/Translation/AILOpCodeConst.cs create mode 100644 Ryujinx/Cpu/Translation/AILOpCodeLoad.cs create mode 100644 Ryujinx/Cpu/Translation/AILOpCodeLog.cs create mode 100644 Ryujinx/Cpu/Translation/AILOpCodeStore.cs create mode 100644 Ryujinx/Cpu/Translation/AIoType.cs create mode 100644 Ryujinx/Cpu/Translation/ALocalAlloc.cs create mode 100644 Ryujinx/Cpu/Translation/IAILEmit.cs create mode 100644 Ryujinx/Cpu/Translation/ILGeneratorEx.cs create mode 100644 Ryujinx/Gal/GalPrimitiveType.cs create mode 100644 Ryujinx/Gal/GalVertexAttrib.cs create mode 100644 Ryujinx/Gal/GalVertexAttribSize.cs create mode 100644 Ryujinx/Gal/GalVertexAttribType.cs create mode 100644 Ryujinx/Gal/IGalRenderer.cs create mode 100644 Ryujinx/Gal/OpenGL/OpenGLRenderer.cs create mode 100644 Ryujinx/Gpu/BCn.cs create mode 100644 Ryujinx/Gpu/NsGpu.cs create mode 100644 Ryujinx/Gpu/NsGpuEngine.cs create mode 100644 Ryujinx/Gpu/NsGpuMemoryMgr.cs create mode 100644 Ryujinx/Gpu/NsGpuPBEntry.cs create mode 100644 Ryujinx/Gpu/NsGpuPGraph.cs create mode 100644 Ryujinx/Gpu/NsGpuRegister.cs create mode 100644 Ryujinx/Gpu/NsGpuTexture.cs create mode 100644 Ryujinx/Gpu/NsGpuTextureFormat.cs create mode 100644 Ryujinx/Gpu/SwizzleAddr.cs create mode 100644 Ryujinx/Loaders/Compression/Lz4.cs create mode 100644 Ryujinx/Loaders/ElfDyn.cs create mode 100644 Ryujinx/Loaders/ElfDynTag.cs create mode 100644 Ryujinx/Loaders/ElfRel.cs create mode 100644 Ryujinx/Loaders/ElfRelType.cs create mode 100644 Ryujinx/Loaders/ElfSym.cs create mode 100644 Ryujinx/Loaders/ElfSymBinding.cs create mode 100644 Ryujinx/Loaders/ElfSymType.cs create mode 100644 Ryujinx/Loaders/ElfSymVisibility.cs create mode 100644 Ryujinx/Loaders/Executable.cs create mode 100644 Ryujinx/Loaders/Executables/IElf.cs create mode 100644 Ryujinx/Loaders/Executables/Nro.cs create mode 100644 Ryujinx/Loaders/Executables/Nso.cs create mode 100644 Ryujinx/OsHle/CondVar.cs create mode 100644 Ryujinx/OsHle/Display.cs create mode 100644 Ryujinx/OsHle/FileDesc.cs create mode 100644 Ryujinx/OsHle/Handles/HDomain.cs create mode 100644 Ryujinx/OsHle/Handles/HEvent.cs create mode 100644 Ryujinx/OsHle/Handles/HNvMap.cs create mode 100644 Ryujinx/OsHle/Handles/HSession.cs create mode 100644 Ryujinx/OsHle/Handles/HSessionObj.cs create mode 100644 Ryujinx/OsHle/Handles/HSharedMem.cs create mode 100644 Ryujinx/OsHle/Handles/HThread.cs create mode 100644 Ryujinx/OsHle/Handles/HTransferMem.cs create mode 100644 Ryujinx/OsHle/Horizon.cs create mode 100644 Ryujinx/OsHle/Ipc/IpcBuffDesc.cs create mode 100644 Ryujinx/OsHle/Ipc/IpcDomCmd.cs create mode 100644 Ryujinx/OsHle/Ipc/IpcHandleDesc.cs create mode 100644 Ryujinx/OsHle/Ipc/IpcHandler.cs create mode 100644 Ryujinx/OsHle/Ipc/IpcMessage.cs create mode 100644 Ryujinx/OsHle/Ipc/IpcMessageType.cs create mode 100644 Ryujinx/OsHle/Ipc/IpcPtrBuffDesc.cs create mode 100644 Ryujinx/OsHle/Ipc/IpcRecvListBuffDesc.cs create mode 100644 Ryujinx/OsHle/MemoryInfo.cs create mode 100644 Ryujinx/OsHle/MemoryType.cs create mode 100644 Ryujinx/OsHle/Mutex.cs create mode 100644 Ryujinx/OsHle/Objects/AccIManagerForApplication.cs create mode 100644 Ryujinx/OsHle/Objects/AccIProfile.cs create mode 100644 Ryujinx/OsHle/Objects/AmIApplicationFunctions.cs create mode 100644 Ryujinx/OsHle/Objects/AmIApplicationProxy.cs create mode 100644 Ryujinx/OsHle/Objects/AmIAudioController.cs create mode 100644 Ryujinx/OsHle/Objects/AmICommonStateGetter.cs create mode 100644 Ryujinx/OsHle/Objects/AmIDebugFunctions.cs create mode 100644 Ryujinx/OsHle/Objects/AmIDisplayController.cs create mode 100644 Ryujinx/OsHle/Objects/AmILibraryAppletCreator.cs create mode 100644 Ryujinx/OsHle/Objects/AmIParentalControlService.cs create mode 100644 Ryujinx/OsHle/Objects/AmISelfController.cs create mode 100644 Ryujinx/OsHle/Objects/AmIStorage.cs create mode 100644 Ryujinx/OsHle/Objects/AmIStorageAccessor.cs create mode 100644 Ryujinx/OsHle/Objects/AmIWindowController.cs create mode 100644 Ryujinx/OsHle/Objects/ApmISession.cs create mode 100644 Ryujinx/OsHle/Objects/AudIAudioRenderer.cs create mode 100644 Ryujinx/OsHle/Objects/FriendIFriendService.cs create mode 100644 Ryujinx/OsHle/Objects/FspSrvIFile.cs create mode 100644 Ryujinx/OsHle/Objects/FspSrvIFileSystem.cs create mode 100644 Ryujinx/OsHle/Objects/FspSrvIStorage.cs create mode 100644 Ryujinx/OsHle/Objects/HidIAppletResource.cs create mode 100644 Ryujinx/OsHle/Objects/ObjHelper.cs create mode 100644 Ryujinx/OsHle/Objects/Parcel.cs create mode 100644 Ryujinx/OsHle/Objects/TimeISteadyClock.cs create mode 100644 Ryujinx/OsHle/Objects/TimeISystemClock.cs create mode 100644 Ryujinx/OsHle/Objects/TimeITimeZoneService.cs create mode 100644 Ryujinx/OsHle/Objects/ViIApplicationDisplayService.cs create mode 100644 Ryujinx/OsHle/Objects/ViIHOSBinderDriver.cs create mode 100644 Ryujinx/OsHle/Objects/ViIManagerDisplayService.cs create mode 100644 Ryujinx/OsHle/Objects/ViISystemDisplayService.cs create mode 100644 Ryujinx/OsHle/Process.cs create mode 100644 Ryujinx/OsHle/ServiceCtx.cs create mode 100644 Ryujinx/OsHle/Services/ServiceAcc.cs create mode 100644 Ryujinx/OsHle/Services/ServiceApm.cs create mode 100644 Ryujinx/OsHle/Services/ServiceAppletOE.cs create mode 100644 Ryujinx/OsHle/Services/ServiceAud.cs create mode 100644 Ryujinx/OsHle/Services/ServiceFriend.cs create mode 100644 Ryujinx/OsHle/Services/ServiceFspSrv.cs create mode 100644 Ryujinx/OsHle/Services/ServiceHid.cs create mode 100644 Ryujinx/OsHle/Services/ServiceLm.cs create mode 100644 Ryujinx/OsHle/Services/ServiceNvDrv.cs create mode 100644 Ryujinx/OsHle/Services/ServicePctl.cs create mode 100644 Ryujinx/OsHle/Services/ServicePl.cs create mode 100644 Ryujinx/OsHle/Services/ServiceSet.cs create mode 100644 Ryujinx/OsHle/Services/ServiceSm.cs create mode 100644 Ryujinx/OsHle/Services/ServiceTime.cs create mode 100644 Ryujinx/OsHle/Services/ServiceVi.cs create mode 100644 Ryujinx/OsHle/Svc/SvcHandler.cs create mode 100644 Ryujinx/OsHle/Svc/SvcMemory.cs create mode 100644 Ryujinx/OsHle/Svc/SvcSystem.cs create mode 100644 Ryujinx/OsHle/Svc/SvcThread.cs create mode 100644 Ryujinx/OsHle/Svc/SvcThreadSync.cs create mode 100644 Ryujinx/OsHle/Utilities/IdPool.cs create mode 100644 Ryujinx/OsHle/Utilities/IdPoolWithObj.cs create mode 100644 Ryujinx/OsHle/Utilities/MemReader.cs create mode 100644 Ryujinx/OsHle/Utilities/MemWriter.cs create mode 100644 Ryujinx/Switch.cs create mode 100644 Ryujinx/VirtualFs.cs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..1ff0c4230 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..82d9719b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates +.vs +.vscode + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings +packages/* +*.config + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + + +#LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac desktop service store files +.DS_Store diff --git a/GLScreen.cs b/GLScreen.cs new file mode 100644 index 000000000..d757db8fc --- /dev/null +++ b/GLScreen.cs @@ -0,0 +1,366 @@ +// This code was written for the OpenTK library and has been released +// to the Public Domain. +// It is provided "as is" without express or implied warranty of any kind. + +using Gal; +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Graphics.OpenGL; +using System; + +namespace Ryujinx +{ + public class GLScreen : GameWindow + { + class ScreenTexture : IDisposable + { + private Switch Ns; + private IGalRenderer Renderer; + + private int Width; + private int Height; + private int TexHandle; + + private int[] Pixels; + + public ScreenTexture(Switch Ns, IGalRenderer Renderer, int Width, int Height) + { + this.Ns = Ns; + this.Renderer = Renderer; + this.Width = Width; + this.Height = Height; + + Pixels = new int[Width * Height]; + + TexHandle = GL.GenTexture(); + + GL.BindTexture(TextureTarget.Texture2D, TexHandle); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + GL.TexImage2D(TextureTarget.Texture2D, + 0, + PixelInternalFormat.Rgba, + Width, + Height, + 0, + PixelFormat.Rgba, + PixelType.UnsignedByte, + IntPtr.Zero); + } + + public int Texture + { + get + { + UploadBitmap(); + + return TexHandle; + } + } + + unsafe void UploadBitmap() + { + if (Renderer.FrameBufferPtr == 0) + { + return; + } + + byte* SrcPtr = (byte*)IntPtr.Add(Ns.Ram, (int)Renderer.FrameBufferPtr); + + for (int Y = 0; Y < Height; Y++) + { + for (int X = 0; X < Width; X++) + { + int SrcOffs = GetSwizzleOffset(X, Y, 4); + + Pixels[X + Y * Width] = *((int*)(SrcPtr + SrcOffs)); + } + } + + GL.BindTexture(TextureTarget.Texture2D, TexHandle); + GL.TexSubImage2D(TextureTarget.Texture2D, + 0, + 0, + 0, + Width, + Height, + PixelFormat.Rgba, + PixelType.UnsignedByte, + Pixels); + } + + private int GetSwizzleOffset(int X, int Y, int Bpp) + { + int Pos; + + Pos = (Y & 0x7f) >> 4; + Pos += (X >> 4) << 3; + Pos += (Y >> 7) * ((Width >> 4) << 3); + Pos *= 1024; + Pos += ((Y & 0xf) >> 3) << 9; + Pos += ((X & 0xf) >> 3) << 8; + Pos += ((Y & 0x7) >> 1) << 6; + Pos += ((X & 0x7) >> 2) << 5; + Pos += ((Y & 0x1) >> 0) << 4; + Pos += ((X & 0x3) >> 0) << 2; + + return Pos; + } + + private bool disposed; + + public void Dispose() + { + Dispose(true); + + GC.SuppressFinalize(this); + } + + void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + GL.DeleteTexture(TexHandle); + } + + disposed = true; + } + } + } + + private string VtxShaderSource = @" +#version 330 core + +precision highp float; + +layout(location = 0) in vec3 in_position; +layout(location = 1) in vec4 in_color; +layout(location = 2) in vec2 in_tex_coord; + +out vec4 color; +out vec2 tex_coord; + +void main(void) { + color = in_color; + tex_coord = in_tex_coord; + gl_Position = vec4((in_position + vec3(-960, 270, 0)) / vec3(1920, 270, 1), 1); +}"; + + private string FragShaderSource = @" +#version 330 core + +precision highp float; + +uniform sampler2D tex; + +in vec4 color; +in vec2 tex_coord; +out vec4 out_frag_color; + +void main(void) { + out_frag_color = vec4(texture(tex, tex_coord).rgb, color.a); +}"; + + private int VtxShaderHandle, + FragShaderHandle, + PrgShaderHandle; + + private int VaoHandle; + private int VboHandle; + + private Switch Ns; + + private IGalRenderer Renderer; + + private ScreenTexture ScreenTex; + + public GLScreen(Switch Ns, IGalRenderer Renderer) + : base(1280, 720, + new GraphicsMode(), "Ryujinx", 0, + DisplayDevice.Default, 3, 3, + GraphicsContextFlags.ForwardCompatible) + { + this.Ns = Ns; + this.Renderer = Renderer; + + ScreenTex = new ScreenTexture(Ns, Renderer, 1280, 720); + } + + protected override void OnLoad (EventArgs e) + { + VSync = VSyncMode.On; + + CreateShaders(); + CreateVbo(); + + GL.Enable(EnableCap.Blend); + GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); + } + + protected override void OnUnload(EventArgs e) + { + ScreenTex.Dispose(); + + GL.DeleteVertexArray(VaoHandle); + GL.DeleteBuffer(VboHandle); + } + + private void CreateVbo() + { + VaoHandle = GL.GenVertexArray(); + VboHandle = GL.GenBuffer(); + + uint[] Buffer = new uint[] + { + 0xc4700000, 0x80000000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x45340000, 0x80000000, 0x00000000, 0xffffffff, 0x00000000, 0x3f800000, 0x00000000, + 0xc4700000, 0xc4070000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x3f800000, + 0x45340000, 0xc4070000, 0x00000000, 0xffffffff, 0x00000000, 0x3f800000, 0x3f800000 + }; + + IntPtr Length = new IntPtr(Buffer.Length * 4); + + GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); + GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); + GL.BindBuffer(BufferTarget.ArrayBuffer, 0); + + GL.BindVertexArray(VaoHandle); + + GL.EnableVertexAttribArray(0); + + GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); + + GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 28, 0); + + GL.EnableVertexAttribArray(1); + + GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); + + GL.VertexAttribPointer(1, 4, VertexAttribPointerType.UnsignedByte, false, 28, 12); + + GL.EnableVertexAttribArray(2); + + GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); + + GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, 28, 20); + + GL.BindVertexArray(0); + } + + private void CreateShaders() + { + VtxShaderHandle = GL.CreateShader(ShaderType.VertexShader); + FragShaderHandle = GL.CreateShader(ShaderType.FragmentShader); + + GL.ShaderSource(VtxShaderHandle, VtxShaderSource); + GL.ShaderSource(FragShaderHandle, FragShaderSource); + GL.CompileShader(VtxShaderHandle); + GL.CompileShader(FragShaderHandle); + + PrgShaderHandle = GL.CreateProgram(); + + GL.AttachShader(PrgShaderHandle, VtxShaderHandle); + GL.AttachShader(PrgShaderHandle, FragShaderHandle); + GL.LinkProgram(PrgShaderHandle); + GL.UseProgram(PrgShaderHandle); + + int TexLocation = GL.GetUniformLocation(PrgShaderHandle, "tex"); + + GL.Uniform1(TexLocation, 0); + } + + protected override void OnUpdateFrame(FrameEventArgs e) + { + unsafe + { + byte* Ptr = (byte*)IntPtr.Add(Ns.Ram, (int)Ns.Os.HidOffset); + + int State = 0; + + if (Keyboard[OpenTK.Input.Key.Up]) + { + State |= 0x2000; + } + + if (Keyboard[OpenTK.Input.Key.Down]) + { + State |= 0x8000; + } + + if (Keyboard[OpenTK.Input.Key.Left]) + { + State |= 0x1000; + } + + if (Keyboard[OpenTK.Input.Key.Right]) + { + State |= 0x4000; + } + + if (Keyboard[OpenTK.Input.Key.A]) + { + State |= 0x1; + } + + if (Keyboard[OpenTK.Input.Key.S]) + { + State |= 0x2; + } + + if (Keyboard[OpenTK.Input.Key.Z]) + { + State |= 0x4; + } + + if (Keyboard[OpenTK.Input.Key.X]) + { + State |= 0x8; + } + + if (Keyboard[OpenTK.Input.Key.Enter]) + { + State |= 0x400; + } + + if (Keyboard[OpenTK.Input.Key.Tab]) + { + State |= 0x800; + } + + *((int*)(Ptr + 0xae38)) = (int)State; + } + + if (Keyboard[OpenTK.Input.Key.Escape]) + { + this.Exit(); + } + } + + protected override void OnRenderFrame(FrameEventArgs e) + { + GL.Viewport(0, 0, 1280, 720); + + GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); + + RenderFb(); + + GL.UseProgram(PrgShaderHandle); + + Renderer.RunActions(); + Renderer.BindTexture(0); + Renderer.Render(); + + SwapBuffers(); + } + + void RenderFb() + { + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, ScreenTex.Texture); + GL.BindVertexArray(VaoHandle); + GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4); + } + } +} \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..00d2e135a --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 000000000..44dc67c1f --- /dev/null +++ b/Program.cs @@ -0,0 +1,57 @@ +using Gal; +using Gal.OpenGL; +using System; +using System.IO; + +namespace Ryujinx +{ + class Program + { + static void Main(string[] args) + { + IGalRenderer Renderer = new OpenGLRenderer(); + + Switch Ns = new Switch(Renderer); + + if (args.Length == 1) + { + if (Directory.Exists(args[0])) + { + string[] RomFsFiles = Directory.GetFiles(args[0], "*.istorage"); + + if (RomFsFiles.Length > 0) + { + Console.WriteLine("Loading as cart with RomFS."); + + Ns.Os.LoadCart(args[0], RomFsFiles[0]); + } + else + { + Console.WriteLine("Loading as cart WITHOUT RomFS."); + + Ns.Os.LoadCart(args[0]); + } + } + else if (File.Exists(args[0])) + { + Console.WriteLine("Loading as homebrew."); + + Ns.Os.LoadProgram(args[0]); + } + } + else + { + Console.WriteLine("Please specify the folder with the NSOs/IStorage or a NSO/NRO."); + } + + using (GLScreen Screen = new GLScreen(Ns, Renderer)) + { + Screen.Run(60.0); + } + + Ns.Os.StopAllProcesses(); + + Ns.Dispose(); + } + } +} diff --git a/Ryujinx.csproj b/Ryujinx.csproj new file mode 100644 index 000000000..2c25afc0f --- /dev/null +++ b/Ryujinx.csproj @@ -0,0 +1,12 @@ + + + Exe + netcoreapp2.0 + win10-x64 + true + + + + + + \ No newline at end of file diff --git a/Ryujinx.sln b/Ryujinx.sln new file mode 100644 index 000000000..c75364f71 --- /dev/null +++ b/Ryujinx.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.8 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "Ryujinx.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {110169B3-3328-4730-8AB0-BA05BEF75C1A} + EndGlobalSection +EndGlobal diff --git a/Ryujinx/Cpu/ABitUtils.cs b/Ryujinx/Cpu/ABitUtils.cs new file mode 100644 index 000000000..357dd45d1 --- /dev/null +++ b/Ryujinx/Cpu/ABitUtils.cs @@ -0,0 +1,57 @@ +namespace ChocolArm64 +{ + static class ABitUtils + { + public static int CountBitsSet(long Value) + { + int Count = 0; + + for (int Bit = 0; Bit < 64; Bit++) + { + Count += (int)(Value >> Bit) & 1; + } + + return Count; + } + + public static int HighestBitSet32(int Value) + { + for (int Bit = 31; Bit >= 0; Bit--) + { + if (((Value >> Bit) & 1) != 0) + { + return Bit; + } + } + + return -1; + } + + public static long Replicate(long Bits, int Size) + { + long Output = 0; + + for (int Bit = 0; Bit < 64; Bit += Size) + { + Output |= Bits << Bit; + } + + return Output; + } + + public static long FillWithOnes(int Bits) + { + return Bits == 64 ? -1L : (1L << Bits) - 1; + } + + public static long RotateRight(long Bits, int Shift, int Size) + { + return (Bits >> Shift) | (Bits << (Size - Shift)); + } + + public static bool IsPow2(int Value) + { + return Value != 0 && (Value & (Value - 1)) == 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/AOpCodeTable.cs b/Ryujinx/Cpu/AOpCodeTable.cs new file mode 100644 index 000000000..d15d2e123 --- /dev/null +++ b/Ryujinx/Cpu/AOpCodeTable.cs @@ -0,0 +1,374 @@ +using ChocolArm64.Decoder; +using ChocolArm64.Instruction; +using System; + +namespace ChocolArm64 +{ + static class AOpCodeTable + { + static AOpCodeTable() + { + #region "OpCode Table" + //Integer + Set("x0010001xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Add, typeof(AOpCodeAluImm)); + Set("x0001011xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Add, typeof(AOpCodeAluRs)); + Set("x0001011001xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Add, typeof(AOpCodeAluRx)); + Set("x0110001xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Adds, typeof(AOpCodeAluImm)); + Set("x0101011xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Adds, typeof(AOpCodeAluRs)); + Set("x0101011001xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Adds, typeof(AOpCodeAluRx)); + Set("0xx10000xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Adr, typeof(AOpCodeAdr)); + Set("1xx10000xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Adrp, typeof(AOpCodeAdr)); + Set("x00100100xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.And, typeof(AOpCodeAluImm)); + Set("x0001010xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.And, typeof(AOpCodeAluRs)); + Set("x11100100xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ands, typeof(AOpCodeAluImm)); + Set("x1101010xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ands, typeof(AOpCodeAluRs)); + Set("x0011010110xxxxx001010xxxxxxxxxx", AInstEmit.Asrv, typeof(AOpCodeAluRs)); + Set("000101xxxxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.B, typeof(AOpCodeBImmAl)); + Set("01010100xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.B_Cond, typeof(AOpCodeBImmCond)); + Set("x01100110xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Bfm, typeof(AOpCodeBfm)); + Set("x0001010xx1xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Bic, typeof(AOpCodeAluRs)); + Set("x1101010xx1xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Bics, typeof(AOpCodeAluRs)); + Set("100101xxxxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Bl, typeof(AOpCodeBImmAl)); + Set("11010110001xxxxx000000xxxxxxxxxx", AInstEmit.Blr, typeof(AOpCodeBReg)); + Set("11010110000xxxxx000000xxxxxxxxxx", AInstEmit.Br, typeof(AOpCodeBReg)); + Set("x0110101xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Cbnz, typeof(AOpCodeBImmCmp)); + Set("x0110100xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Cbz, typeof(AOpCodeBImmCmp)); + Set("x0111010010xxxxxxxxx10xxxxxxxxxx", AInstEmit.Ccmn, typeof(AOpCodeCcmpImm)); + Set("x0111010010xxxxxxxxx00xxxxxxxxxx", AInstEmit.Ccmn, typeof(AOpCodeCcmpReg)); + Set("x1111010010xxxxxxxxx10xxxxxxxxxx", AInstEmit.Ccmp, typeof(AOpCodeCcmpImm)); + Set("x1111010010xxxxxxxxx00xxxxxxxxxx", AInstEmit.Ccmp, typeof(AOpCodeCcmpReg)); + Set("11010101000000110011xxxx01011111", AInstEmit.Clrex, typeof(AOpCodeSystem)); + Set("x101101011000000000100xxxxxxxxxx", AInstEmit.Clz, typeof(AOpCodeAlu)); + Set("x0011010100xxxxxxxxx00xxxxxxxxxx", AInstEmit.Csel, typeof(AOpCodeCsel)); + Set("x0011010100xxxxxxxxx01xxxxxxxxxx", AInstEmit.Csinc, typeof(AOpCodeCsel)); + Set("x1011010100xxxxxxxxx00xxxxxxxxxx", AInstEmit.Csinv, typeof(AOpCodeCsel)); + Set("x1011010100xxxxxxxxx01xxxxxxxxxx", AInstEmit.Csneg, typeof(AOpCodeCsel)); + Set("11010101000000110011xxxx10111111", AInstEmit.Dmb, typeof(AOpCodeSystem)); + Set("11010101000000110011xxxx10011111", AInstEmit.Dsb, typeof(AOpCodeSystem)); + Set("x10100100xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Eor, typeof(AOpCodeAluImm)); + Set("x1001010xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Eor, typeof(AOpCodeAluRs)); + Set("x00100111x0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Extr, typeof(AOpCodeAluRs)); + Set("xx001000110xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Ldar, typeof(AOpCodeMemEx)); + Set("1x001000011xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Ldaxp, typeof(AOpCodeMemEx)); + Set("xx001000010xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Ldaxr, typeof(AOpCodeMemEx)); + Set("<<10100xx1xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldp, typeof(AOpCodeMemPair)); + Set("xx111000010xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeMemImm)); + Set("xx11100101xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeMemImm)); + Set("xx111000011xxxxxxxxx10xxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeMemReg)); + Set("xx011000xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.LdrLit, typeof(AOpCodeMemLit)); + Set("0x1110001x0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemImm)); + Set("0x1110011xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemImm)); + Set("10111000100xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemImm)); + Set("1011100110xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemImm)); + Set("0x1110001x1xxxxxxxxx10xxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemReg)); + Set("10111000101xxxxxxxxx10xxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemReg)); + Set("xx001000010xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Ldxr, typeof(AOpCodeMemEx)); + Set("1x001000011xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Ldxp, typeof(AOpCodeMemEx)); + Set("x0011010110xxxxx001000xxxxxxxxxx", AInstEmit.Lslv, typeof(AOpCodeAluRs)); + Set("x0011010110xxxxx001001xxxxxxxxxx", AInstEmit.Lsrv, typeof(AOpCodeAluRs)); + Set("x0011011000xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Madd, typeof(AOpCodeMul)); + Set("x11100101xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Movk, typeof(AOpCodeMov)); + Set("x00100101xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Movn, typeof(AOpCodeMov)); + Set("x10100101xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Movz, typeof(AOpCodeMov)); + Set("110101010011xxxxxxxxxxxxxxxxxxxx", AInstEmit.Mrs, typeof(AOpCodeSystem)); + Set("110101010001xxxxxxxxxxxxxxxxxxxx", AInstEmit.Msr, typeof(AOpCodeSystem)); + Set("x0011011000xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Msub, typeof(AOpCodeMul)); + Set("11010101000000110010000000011111", AInstEmit.Nop, typeof(AOpCodeSystem)); + Set("x0101010xx1xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Orn, typeof(AOpCodeAluRs)); + Set("x01100100xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Orr, typeof(AOpCodeAluImm)); + Set("x0101010xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Orr, typeof(AOpCodeAluRs)); + Set("1111100110xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Pfrm, typeof(AOpCodeMemImm)); + Set("11011000xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Pfrm, typeof(AOpCodeMemLit)); + Set("x101101011000000000000xxxxxxxxxx", AInstEmit.Rbit, typeof(AOpCodeAlu)); + Set("11010110010xxxxx000000xxxxxxxxxx", AInstEmit.Ret, typeof(AOpCodeBReg)); + Set("x101101011000000000001xxxxxxxxxx", AInstEmit.Rev16, typeof(AOpCodeAlu)); + Set("x101101011000000000010xxxxxxxxxx", AInstEmit.Rev32, typeof(AOpCodeAlu)); + Set("1101101011000000000011xxxxxxxxxx", AInstEmit.Rev64, typeof(AOpCodeAlu)); + Set("x0011010110xxxxx001011xxxxxxxxxx", AInstEmit.Rorv, typeof(AOpCodeAluRs)); + Set("x00100110xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Sbfm, typeof(AOpCodeBfm)); + Set("x0011010110xxxxx000011xxxxxxxxxx", AInstEmit.Sdiv, typeof(AOpCodeAluRs)); + Set("10011011001xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Smaddl, typeof(AOpCodeMul)); + Set("10011011001xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Smsubl, typeof(AOpCodeMul)); + Set("10011011010xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Smulh, typeof(AOpCodeMul)); + Set("xx001000100xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Stlr, typeof(AOpCodeMemEx)); + Set("1x001000001xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Stlxp, typeof(AOpCodeMemEx)); + Set("xx001000000xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Stlxr, typeof(AOpCodeMemEx)); + Set("x010100xx0xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Stp, typeof(AOpCodeMemPair)); + Set("xx111000000xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeMemImm)); + Set("xx11100100xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeMemImm)); + Set("xx111000001xxxxxxxxx10xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeMemReg)); + Set("1x001000001xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Stxp, typeof(AOpCodeMemEx)); + Set("xx001000000xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Stxr, typeof(AOpCodeMemEx)); + Set("x1010001xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Sub, typeof(AOpCodeAluImm)); + Set("x1001011xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Sub, typeof(AOpCodeAluRs)); + Set("x1001011001xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Sub, typeof(AOpCodeAluRx)); + Set("x1110001xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Subs, typeof(AOpCodeAluImm)); + Set("x1101011xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Subs, typeof(AOpCodeAluRs)); + Set("x1101011001xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Subs, typeof(AOpCodeAluRx)); + Set("11010100000xxxxxxxxxxxxxxxx00001", AInstEmit.Svc, typeof(AOpCodeException)); + Set("1101010100001xxxxxxxxxxxxxxxxxxx", AInstEmit.Sys, typeof(AOpCodeSystem)); + Set("x0110111xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Tbnz, typeof(AOpCodeBImmTest)); + Set("x0110110xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Tbz, typeof(AOpCodeBImmTest)); + Set("x10100110xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ubfm, typeof(AOpCodeBfm)); + Set("x0011010110xxxxx000010xxxxxxxxxx", AInstEmit.Udiv, typeof(AOpCodeAluRs)); + Set("10011011101xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Umaddl, typeof(AOpCodeMul)); + Set("10011011101xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Umsubl, typeof(AOpCodeMul)); + Set("10011011110xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Umulh, typeof(AOpCodeMul)); + + //Vector + Set("0x001110xx1xxxxx100001xxxxxxxxxx", AInstEmit.Add_V, typeof(AOpCodeSimdReg)); + Set("01011110xx110001101110xxxxxxxxxx", AInstEmit.Addp_S, typeof(AOpCodeSimd)); + Set("0x001110xx1xxxxx101111xxxxxxxxxx", AInstEmit.Addp_V, typeof(AOpCodeSimdReg)); + Set("0x001110<<110001101110xxxxxxxxxx", AInstEmit.Addv_V, typeof(AOpCodeSimd)); + Set("0x001110001xxxxx000111xxxxxxxxxx", AInstEmit.And_V, typeof(AOpCodeSimdReg)); + Set("0x001110011xxxxx000111xxxxxxxxxx", AInstEmit.Bic_V, typeof(AOpCodeSimdReg)); + Set("0x10111100000xxx<>xxxxx111111xxxxxxxxxx", AInstEmit.Fcvtzu_V_Fix, typeof(AOpCodeSimdShImm)); + Set("x0011110xx011000xxxxxxxxxxxxxxxx", AInstEmit.Fcvtzs_Fix, typeof(AOpCodeSimdCvt)); + Set("x0011110xx011001xxxxxxxxxxxxxxxx", AInstEmit.Fcvtzu_Fix, typeof(AOpCodeSimdCvt)); + Set("00011110xx1xxxxx000110xxxxxxxxxx", AInstEmit.Fdiv_S, typeof(AOpCodeSimdReg)); + Set("00011110xx1xxxxx010010xxxxxxxxxx", AInstEmit.Fmax_S, typeof(AOpCodeSimdReg)); + Set("00011110xx1xxxxx011010xxxxxxxxxx", AInstEmit.Fmaxnm_S, typeof(AOpCodeSimdReg)); + Set("00011110xx1xxxxx010110xxxxxxxxxx", AInstEmit.Fmin_S, typeof(AOpCodeSimdReg)); + Set("00011110xx1xxxxx011110xxxxxxxxxx", AInstEmit.Fminnm_S, typeof(AOpCodeSimdReg)); + Set("0x0011100x1xxxxx110011xxxxxxxxxx", AInstEmit.Fmla_V, typeof(AOpCodeSimdReg)); + Set("0x0011111<>>>xxx010101xxxxxxxxxx", AInstEmit.Shl_S, typeof(AOpCodeSimdShImm)); + Set("0x0011110>>>>xxx010101xxxxxxxxxx", AInstEmit.Shl_V, typeof(AOpCodeSimdShImm)); + Set("0x001110<<1xxxxx011001xxxxxxxxxx", AInstEmit.Smax_V, typeof(AOpCodeSimdReg)); + Set("0x001110<<1xxxxx011011xxxxxxxxxx", AInstEmit.Smin_V, typeof(AOpCodeSimdReg)); + Set("0x00111100>>>xxx101001xxxxxxxxxx", AInstEmit.Sshll_V, typeof(AOpCodeSimdShImm)); + Set("010111110>>>>xxx000001xxxxxxxxxx", AInstEmit.Sshr_S, typeof(AOpCodeSimdShImm)); + Set("0x0011110>>>>xxx000001xxxxxxxxxx", AInstEmit.Sshr_V, typeof(AOpCodeSimdShImm)); + Set("0x00110000000000xxxxxxxxxxxxxxxx", AInstEmit.St__V, typeof(AOpCodeSimdMemMult)); + Set("0x001100100xxxxxxxxxxxxxxxxxxxxx", AInstEmit.St__V, typeof(AOpCodeSimdMemMult)); + Set("xx10110xx0xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Stp, typeof(AOpCodeSimdMemPair)); + Set("xx111100x00xxxxxxxxx00xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemImm)); + Set("xx111100x00xxxxxxxxx01xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemImm)); + Set("xx111100x00xxxxxxxxx11xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemImm)); + Set("xx111101x0xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemImm)); + Set("xx111100x01xxxxxxxxx10xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemReg)); + Set("01111110xx1xxxxx100001xxxxxxxxxx", AInstEmit.Sub_S, typeof(AOpCodeSimdReg)); + Set("0x101110xx1xxxxx100001xxxxxxxxxx", AInstEmit.Sub_V, typeof(AOpCodeSimdReg)); + Set("x0011110xx100010000000xxxxxxxxxx", AInstEmit.Scvtf_Gp, typeof(AOpCodeSimdCvt)); + Set("010111100x100001110110xxxxxxxxxx", AInstEmit.Scvtf_S, typeof(AOpCodeSimd)); + Set("0x001110000xxxxx0xx000xxxxxxxxxx", AInstEmit.Tbl_V, typeof(AOpCodeSimdTbl)); + Set("001011100x110000001110xxxxxxxxxx", AInstEmit.Uaddlv_V, typeof(AOpCodeSimd)); + Set("01101110<<110000001110xxxxxxxxxx", AInstEmit.Uaddlv_V, typeof(AOpCodeSimd)); + Set("0x101110<<1xxxxx000100xxxxxxxxxx", AInstEmit.Uaddw_V, typeof(AOpCodeSimdReg)); + Set("x0011110xx100011000000xxxxxxxxxx", AInstEmit.Ucvtf_Gp, typeof(AOpCodeSimdCvt)); + Set("011111100x100001110110xxxxxxxxxx", AInstEmit.Ucvtf_V, typeof(AOpCodeSimdReg)); + Set("0x001110000xxxxx001111xxxxxxxxxx", AInstEmit.Umov_S, typeof(AOpCodeSimdIns)); + Set("0x101110xx1xxxxx010001xxxxxxxxxx", AInstEmit.Ushl_V, typeof(AOpCodeSimdReg)); + Set("0x10111100>>>xxx101001xxxxxxxxxx", AInstEmit.Ushll_V, typeof(AOpCodeSimdShImm)); + Set("0x1011110>>>>xxx000001xxxxxxxxxx", AInstEmit.Ushr_V, typeof(AOpCodeSimdShImm)); + Set("0x1011110>>>>xxx000101xxxxxxxxxx", AInstEmit.Usra_V, typeof(AOpCodeSimdShImm)); + Set("0x001110xx0xxxxx000110xxxxxxxxxx", AInstEmit.Uzp1_V, typeof(AOpCodeSimdReg)); + Set("0x001110<<100001001010xxxxxxxxxx", AInstEmit.Xtn_V, typeof(AOpCodeSimd)); +#endregion + } + + private class TreeNode + { + public int Mask; + public int Value; + + public TreeNode Next; + public TreeNode Child; + + public AInst Inst; + + public TreeNode(int Mask, int Value, AInst Inst) + { + this.Mask = Mask; + this.Value = Value; + this.Inst = Inst; + } + } + + private static TreeNode Root; + + private static void Set(string Encoding, AInstEmitter Emitter, Type Type) + { + Set(Encoding, new AInst(Emitter, Type)); + } + + private static void Set(string Encoding, AInst Inst) + { + int Bit = Encoding.Length - 1; + int Value = 0; + int XMask = 0; + int ZCount = 0; + int OCount = 0; + + int[] ZPos = new int[Encoding.Length]; + int[] OPos = new int[Encoding.Length]; + + for (int Index = 0; Index < Encoding.Length; Index++, Bit--) + { + //Note: < and > are used on special encodings. + //The < means that we should never have ALL bits with the '<' set. + //So, when the encoding has <<, it means that 00, 01, and 10 are valid, + //but not 11. <<< is 000, 001, ..., 110 but NOT 111, and so on... + //For >, the invalid value is zero. So, for << 01, 10 and 11 are valid, + //but 00 isn't. + switch (Encoding[Index]) + { + case '0': /* Do nothing. */ break; + case '1': Value |= 1 << Bit; break; + case 'x': XMask |= 1 << Bit; break; + + case '<': OPos[OCount++] = Bit; break; + case '>': ZPos[ZCount++] = Bit; break; + + default: throw new ArgumentException(nameof(Encoding)); + } + } + + if (ZCount + OCount == 0) + { + InsertTop(XMask, Value, Inst); + } + else if ((ZCount & OCount) != 0) + { + for (int OCtr = 0; (uint)OCtr < (1 << OCount) - 1; OCtr++) + { + int OVal = Value; + + for (int O = 0; O < OCount; O++) + { + OVal |= ((OCtr >> O) & 1) << OPos[O]; + } + + InsertWithCtr(1, 1 << ZCount, ZCount, ZPos, XMask, OVal, Inst); + } + } + else if (ZCount != 0) + { + InsertWithCtr(1, 1 << ZCount, ZCount, ZPos, XMask, Value, Inst); + } + else if (OCount != 0) + { + InsertWithCtr(0, (1 << OCount) - 1, OCount, OPos, XMask, Value, Inst); + } + } + + private static void InsertWithCtr( + int Start, + int End, + int Cnt, + int[] Pos, + int XMask, + int Value, + AInst Inst) + { + for (int Ctr = Start; (uint)Ctr < End; Ctr++) + { + int Val = Value; + + for (int Index = 0; Index < Cnt; Index++) + { + Val |= ((Ctr >> Index) & 1) << Pos[Index]; + } + + InsertTop(XMask, Val, Inst); + } + } + + private static void InsertTop(int XMask, int Value, AInst Inst) + { + TreeNode Next = Root; + + Root = new TreeNode(~XMask, Value, Inst); + + Root.Next = Next; + } + + public static AInst GetInst(int OpCode) + { + TreeNode Node = Root; + + do + { + if ((OpCode & Node.Mask) == Node.Value) + { + return Node.Inst; + } + } + while ((Node = Node.Next) != null); + + return AInst.Undefined; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/AThread.cs b/Ryujinx/Cpu/AThread.cs new file mode 100644 index 000000000..2fafcdd53 --- /dev/null +++ b/Ryujinx/Cpu/AThread.cs @@ -0,0 +1,74 @@ +using ChocolArm64.Memory; +using ChocolArm64.State; +using System; +using System.Threading; + +namespace ChocolArm64 +{ + class AThread + { + public ARegisters Registers { get; private set; } + public AMemory Memory { get; private set; } + + private ATranslator Translator; + private Thread Work; + + public event EventHandler WorkFinished; + + public int ThreadId => Registers.ThreadId; + + public bool IsAlive => Work.IsAlive; + + public long EntryPoint { get; private set; } + public int Priority { get; private set; } + + public AThread(AMemory Memory, long EntryPoint = 0, int Priority = 0) + { + this.Memory = Memory; + this.EntryPoint = EntryPoint; + this.Priority = Priority; + + Registers = new ARegisters(); + Translator = new ATranslator(this); + } + + public void StopExecution() => Translator.StopExecution(); + + public void Execute() => Execute(EntryPoint); + + public void Execute(long EntryPoint) + { + Work = new Thread(delegate() + { + Translator.ExecuteSubroutine(EntryPoint); + + Memory.RemoveMonitor(ThreadId); + + WorkFinished?.Invoke(this, EventArgs.Empty); + }); + + if (Priority < 12) + { + Work.Priority = ThreadPriority.Highest; + } + else if (Priority < 24) + { + Work.Priority = ThreadPriority.AboveNormal; + } + else if (Priority < 36) + { + Work.Priority = ThreadPriority.Normal; + } + else if (Priority < 48) + { + Work.Priority = ThreadPriority.BelowNormal; + } + else + { + Work.Priority = ThreadPriority.Lowest; + } + + Work.Start(); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/ATranslatedSub.cs b/Ryujinx/Cpu/ATranslatedSub.cs new file mode 100644 index 000000000..1a821f479 --- /dev/null +++ b/Ryujinx/Cpu/ATranslatedSub.cs @@ -0,0 +1,104 @@ +using ChocolArm64.Memory; +using ChocolArm64.State; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; + +namespace ChocolArm64 +{ + class ATranslatedSub + { + private delegate long AA64Subroutine(ARegisters Register, AMemory Memory); + + private AA64Subroutine ExecDelegate; + + private bool HasDelegate; + + public static Type[] FixedArgTypes { get; private set; } + + public static int RegistersArgIdx { get; private set; } + public static int MemoryArgIdx { get; private set; } + + public DynamicMethod Method { get; private set; } + + public HashSet SubCalls { get; private set; } + + public List Params { get; private set; } + + public bool NeedsReJit { get; private set; } + + public ATranslatedSub() + { + SubCalls = new HashSet(); + } + + public ATranslatedSub(DynamicMethod Method, List Params) : this() + { + if (Params == null) + { + throw new ArgumentNullException(nameof(Params)); + } + + this.Method = Method; + this.Params = Params; + } + + static ATranslatedSub() + { + MethodInfo MthdInfo = typeof(AA64Subroutine).GetMethod("Invoke"); + + ParameterInfo[] Params = MthdInfo.GetParameters(); + + FixedArgTypes = new Type[Params.Length]; + + for (int Index = 0; Index < Params.Length; Index++) + { + Type ParamType = Params[Index].ParameterType; + + FixedArgTypes[Index] = ParamType; + + if (ParamType == typeof(ARegisters)) + { + RegistersArgIdx = Index; + } + else if (ParamType == typeof(AMemory)) + { + MemoryArgIdx = Index; + } + } + } + + public long Execute(ARegisters Registers, AMemory Memory) + { + if (!HasDelegate) + { + string Name = $"{Method.Name}_Dispatch"; + + DynamicMethod Mthd = new DynamicMethod(Name, typeof(long), FixedArgTypes); + + ILGenerator Generator = Mthd.GetILGenerator(); + + Generator.EmitLdargSeq(FixedArgTypes.Length); + + foreach (ARegister Reg in Params) + { + Generator.EmitLdarg(RegistersArgIdx); + + Generator.Emit(OpCodes.Ldfld, Reg.GetField()); + } + + Generator.Emit(OpCodes.Call, Method); + Generator.Emit(OpCodes.Ret); + + ExecDelegate = (AA64Subroutine)Mthd.CreateDelegate(typeof(AA64Subroutine)); + + HasDelegate = true; + } + + return ExecDelegate(Registers, Memory); + } + + public void MarkForReJit() => NeedsReJit = true; + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/ATranslator.cs b/Ryujinx/Cpu/ATranslator.cs new file mode 100644 index 000000000..ba7f3df6f --- /dev/null +++ b/Ryujinx/Cpu/ATranslator.cs @@ -0,0 +1,106 @@ +using ChocolArm64.Decoder; +using ChocolArm64.Instruction; +using ChocolArm64.Translation; +using System.Collections.Generic; +using System.Reflection.Emit; + +namespace ChocolArm64 +{ + class ATranslator + { + private Dictionary CachedSubs; + + public AThread Thread { get; private set; } + + private bool KeepRunning; + + public ATranslator(AThread Parent) + { + this.Thread = Parent; + + CachedSubs = new Dictionary(); + + KeepRunning = true; + } + + public void StopExecution() => KeepRunning = false; + + public void ExecuteSubroutine(long Position) + { + do + { + if (CachedSubs.TryGetValue(Position, out ATranslatedSub Sub) && !Sub.NeedsReJit) + { + Position = Sub.Execute(Thread.Registers, Thread.Memory); + } + else + { + Position = TranslateSubroutine(Position).Execute(Thread.Registers, Thread.Memory); + } + } + while (Position != 0 && KeepRunning); + } + + public bool TryGetCachedSub(AOpCode OpCode, out ATranslatedSub Sub) + { + if (OpCode.Emitter != AInstEmit.Bl) + { + Sub = null; + + return false; + } + + return TryGetCachedSub(((AOpCodeBImmAl)OpCode).Imm, out Sub); + } + + public bool TryGetCachedSub(long Position, out ATranslatedSub Sub) + { + return CachedSubs.TryGetValue(Position, out Sub); + } + + public bool HasCachedSub(long Position) + { + return CachedSubs.ContainsKey(Position); + } + + private ATranslatedSub TranslateSubroutine(long Position) + { + (ABlock[] Graph, ABlock Root) Cfg = ADecoder.DecodeSubroutine(this, Position); + + AILEmitterCtx Context = new AILEmitterCtx( + this, + Cfg.Graph, + Cfg.Root); + + if (Context.CurrBlock.Position != Position) + { + Context.Emit(OpCodes.Br, Context.GetLabel(Position)); + } + + do + { + Context.EmitOpCode(); + } + while (Context.AdvanceOpCode()); + + //Mark all methods that calls this method for ReJiting, + //since we can now call it directly which is faster. + foreach (ATranslatedSub TS in CachedSubs.Values) + { + if (TS.SubCalls.Contains(Position)) + { + TS.MarkForReJit(); + } + } + + ATranslatedSub Subroutine = Context.GetSubroutine(); + + if (!CachedSubs.TryAdd(Position, Subroutine)) + { + CachedSubs[Position] = Subroutine; + } + + return Subroutine; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/ABlock.cs b/Ryujinx/Cpu/Decoder/ABlock.cs new file mode 100644 index 000000000..32974c1ab --- /dev/null +++ b/Ryujinx/Cpu/Decoder/ABlock.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; + +namespace ChocolArm64.Decoder +{ + class ABlock + { + public long Position { get; set; } + public long EndPosition { get; set; } + + public ABlock Next { get; set; } + public ABlock Branch { get; set; } + + public List OpCodes { get; private set; } + + public ABlock() + { + OpCodes = new List(); + } + + public ABlock(long Position) : this() + { + this.Position = Position; + } + + public AOpCode GetLastOp() + { + if (OpCodes.Count > 0) + { + return OpCodes[OpCodes.Count - 1]; + } + + return null; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/ACond.cs b/Ryujinx/Cpu/Decoder/ACond.cs new file mode 100644 index 000000000..f2da8bd29 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/ACond.cs @@ -0,0 +1,22 @@ +namespace ChocolArm64.Decoder +{ + enum ACond + { + Eq = 0, + Ne = 1, + Ge_Un = 2, + Lt_Un = 3, + Mi = 4, + Pl = 5, + Vs = 6, + Vc = 7, + Gt_Un = 8, + Le_Un = 9, + Ge = 10, + Lt = 11, + Gt = 12, + Le = 13, + Al = 14, + Nv = 15 + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/ADataOp.cs b/Ryujinx/Cpu/Decoder/ADataOp.cs new file mode 100644 index 000000000..a5601a3ab --- /dev/null +++ b/Ryujinx/Cpu/Decoder/ADataOp.cs @@ -0,0 +1,10 @@ +namespace ChocolArm64.Decoder +{ + enum ADataOp + { + Adr = 0, + Arithmetic = 1, + Logical = 2, + BitField = 3 + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/ADecoder.cs b/Ryujinx/Cpu/Decoder/ADecoder.cs new file mode 100644 index 000000000..1d3400396 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/ADecoder.cs @@ -0,0 +1,206 @@ +using ChocolArm64.Instruction; +using ChocolArm64.Memory; +using System; +using System.Collections.Generic; +using System.Reflection.Emit; + +namespace ChocolArm64.Decoder +{ + static class ADecoder + { + public static (ABlock[] Graph, ABlock Root) DecodeSubroutine(ATranslator Translator, long Start) + { + Dictionary Visited = new Dictionary(); + Dictionary VisitedEnd = new Dictionary(); + + Queue Blocks = new Queue(); + + ABlock Enqueue(long Position) + { + if (!Visited.TryGetValue(Position, out ABlock Output)) + { + Output = new ABlock(Position); + + Blocks.Enqueue(Output); + + Visited.Add(Position, Output); + } + + return Output; + } + + ABlock Root = Enqueue(Start); + + while (Blocks.Count > 0) + { + ABlock Current = Blocks.Dequeue(); + + FillBlock(Translator.Thread.Memory, Current); + + //Set child blocks. "Branch" is the block the branch instruction + //points to (when taken), "Next" is the block at the next address, + //executed when the branch is not taken. For Unconditional Branches + //(except BL/BLR that are sub calls) or end of executable, Next is null. + if (Current.OpCodes.Count > 0) + { + bool HasCachedSub = false; + + AOpCode LastOp = Current.GetLastOp(); + + if (LastOp is AOpCodeBImm Op) + { + if (Op.Emitter == AInstEmit.Bl) + { + HasCachedSub = Translator.HasCachedSub(Op.Imm); + } + else + { + Current.Branch = Enqueue(Op.Imm); + } + } + + if ((!(LastOp is AOpCodeBImmAl) && + !(LastOp is AOpCodeBReg)) || HasCachedSub) + { + Current.Next = Enqueue(Current.EndPosition); + } + } + + //If we have on the tree two blocks with the same end position, + //then we need to split the bigger block and have two small blocks, + //the end position of the bigger "Current" block should then be == to + //the position of the "Smaller" block. + while (VisitedEnd.TryGetValue(Current.EndPosition, out ABlock Smaller)) + { + if (Current.Position > Smaller.Position) + { + ABlock Temp = Smaller; + + Smaller = Current; + Current = Temp; + } + + Current.EndPosition = Smaller.Position; + Current.Next = Smaller; + Current.Branch = null; + + Current.OpCodes.RemoveRange( + Current.OpCodes.Count - Smaller.OpCodes.Count, + Smaller.OpCodes.Count); + + VisitedEnd[Smaller.EndPosition] = Smaller; + } + + VisitedEnd.Add(Current.EndPosition, Current); + } + + //Make and sort Graph blocks array by position. + ABlock[] Graph = new ABlock[Visited.Count]; + + while (Visited.Count > 0) + { + ulong FirstPos = ulong.MaxValue; + + foreach (ABlock Block in Visited.Values) + { + if (FirstPos > (ulong)Block.Position) + FirstPos = (ulong)Block.Position; + } + + ABlock Current = Visited[(long)FirstPos]; + + do + { + Graph[Graph.Length - Visited.Count] = Current; + + Visited.Remove(Current.Position); + + Current = Current.Next; + } + while (Current != null); + } + + return (Graph, Root); + } + + private static void FillBlock(AMemory Memory, ABlock Block) + { + long Position = Block.Position; + + AOpCode OpCode; + + do + { + OpCode = DecodeOpCode(Memory, Position); + + Block.OpCodes.Add(OpCode); + + Position += 4; + } + while (!(IsBranch(OpCode) || IsException(OpCode))); + + Block.EndPosition = Position; + } + + private static bool IsBranch(AOpCode OpCode) + { + return OpCode is AOpCodeBImm || + OpCode is AOpCodeBReg; + } + + private static bool IsException(AOpCode OpCode) + { + return OpCode.Emitter == AInstEmit.Svc || + OpCode.Emitter == AInstEmit.Und; + } + + public static AOpCode DecodeOpCode(AMemory Memory, long Position) + { + int OpCode = Memory.ReadInt32(Position); + + AInst Inst = AOpCodeTable.GetInst(OpCode); + + AOpCode DecodedOpCode = new AOpCode(AInst.Undefined, Position); + + if (Inst.Type != null) + { + DecodedOpCode = CreateOpCode(Inst.Type, Inst, Position, OpCode); + } + + return DecodedOpCode; + } + + private delegate object OpActivator(AInst Inst, long Position, int OpCode); + + private static Dictionary Activators = new Dictionary(); + + private static AOpCode CreateOpCode(Type Type, AInst Inst, long Position, int OpCode) + { + if (Type == null) + { + throw new ArgumentNullException(nameof(Type)); + } + + if (!Activators.TryGetValue(Type, out OpActivator CreateInstance)) + { + Type[] ArgTypes = new Type[] { typeof(AInst), typeof(long), typeof(int) }; + + DynamicMethod Mthd = new DynamicMethod($"{Type.Name}_Create", Type, ArgTypes); + + ILGenerator Generator = Mthd.GetILGenerator(); + + Generator.Emit(OpCodes.Ldarg_0); + Generator.Emit(OpCodes.Ldarg_1); + Generator.Emit(OpCodes.Ldarg_2); + Generator.Emit(OpCodes.Newobj, Type.GetConstructor(ArgTypes)); + Generator.Emit(OpCodes.Ret); + + CreateInstance = (OpActivator)Mthd.CreateDelegate(typeof(OpActivator)); + + Activators.Add(Type, CreateInstance); + } + + return (AOpCode)CreateInstance(Inst, Position, OpCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/ADecoderHelper.cs b/Ryujinx/Cpu/Decoder/ADecoderHelper.cs new file mode 100644 index 000000000..a2179f49e --- /dev/null +++ b/Ryujinx/Cpu/Decoder/ADecoderHelper.cs @@ -0,0 +1,107 @@ +using System; + +namespace ChocolArm64.Decoder +{ + static class ADecoderHelper + { + public struct BitMask + { + public long WMask; + public long TMask; + public int Pos; + public int Shift; + public bool IsUndefined; + + public static BitMask Invalid => new BitMask { IsUndefined = true }; + } + + public static BitMask DecodeBitMask(int OpCode, bool Immediate) + { + int ImmS = (OpCode >> 10) & 0x3f; + int ImmR = (OpCode >> 16) & 0x3f; + + int N = (OpCode >> 22) & 1; + int SF = (OpCode >> 31) & 1; + + int Length = ABitUtils.HighestBitSet32((~ImmS & 0x3f) | (N << 6)); + + if (Length < 1 || (SF == 0 && N != 0)) + { + return BitMask.Invalid; + } + + int Size = 1 << Length; + + int Levels = Size - 1; + + int S = ImmS & Levels; + int R = ImmR & Levels; + + if (Immediate && S == Levels) + { + return BitMask.Invalid; + } + + long WMask = ABitUtils.FillWithOnes(S + 1); + long TMask = ABitUtils.FillWithOnes(((S - R) & Levels) + 1); + + if (R > 0) + { + WMask = ABitUtils.RotateRight(WMask, R, Size); + WMask &= ABitUtils.FillWithOnes(Size); + } + + return new BitMask() + { + WMask = ABitUtils.Replicate(WMask, Size), + TMask = ABitUtils.Replicate(TMask, Size), + + Pos = ImmS, + Shift = ImmR + }; + } + + public static long DecodeImm8Float(long Imm, int Size) + { + int E = 0, F = 0; + + switch (Size) + { + case 0: E = 8; F = 23; break; + case 1: E = 11; F = 52; break; + + default: throw new ArgumentOutOfRangeException(nameof(Size)); + } + + long Value = (Imm & 0x3f) << F - 4; + + long EBit = (Imm >> 6) & 1; + long SBit = (Imm >> 7) & 1; + + if (EBit != 0) + { + Value |= (1L << E - 3) - 1 << F + 2; + } + + Value |= (EBit ^ 1) << F + E - 1; + Value |= SBit << F + E; + + return Value; + } + + public static long DecodeImm26_2(int OpCode) + { + return ((long)OpCode << 38) >> 36; + } + + public static long DecodeImmS19_2(int OpCode) + { + return (((long)OpCode << 40) >> 43) & ~3; + } + + public static long DecodeImmS14_2(int OpCode) + { + return (((long)OpCode << 45) >> 48) & ~3; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AIntType.cs b/Ryujinx/Cpu/Decoder/AIntType.cs new file mode 100644 index 000000000..242fdada1 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AIntType.cs @@ -0,0 +1,14 @@ +namespace ChocolArm64.Decoder +{ + enum AIntType + { + UInt8 = 0, + UInt16 = 1, + UInt32 = 2, + UInt64 = 3, + Int8 = 4, + Int16 = 5, + Int32 = 6, + Int64 = 7 + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCode.cs b/Ryujinx/Cpu/Decoder/AOpCode.cs new file mode 100644 index 000000000..4e5a80700 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCode.cs @@ -0,0 +1,36 @@ +using ChocolArm64.Instruction; +using ChocolArm64.State; +using System; + +namespace ChocolArm64.Decoder +{ + class AOpCode : IAOpCode + { + public long Position { get; private set; } + + public AInstEmitter Emitter { get; protected set; } + public ARegisterSize RegisterSize { get; protected set; } + + public AOpCode(AInst Inst, long Position) + { + this.Position = Position; + + RegisterSize = ARegisterSize.Int64; + + Emitter = Inst.Emitter; + } + + public int GetBitsCount() + { + switch (RegisterSize) + { + case ARegisterSize.Int32: return 32; + case ARegisterSize.Int64: return 64; + case ARegisterSize.SIMD64: return 64; + case ARegisterSize.SIMD128: return 128; + } + + throw new InvalidOperationException(); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeAdr.cs b/Ryujinx/Cpu/Decoder/AOpCodeAdr.cs new file mode 100644 index 000000000..49f756e72 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeAdr.cs @@ -0,0 +1,18 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeAdr : AOpCode + { + public int Rd { get; private set; } + public long Imm { get; private set; } + + public AOpCodeAdr(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + Rd = OpCode & 0x1f; + + Imm = ADecoderHelper.DecodeImmS19_2(OpCode); + Imm |= ((long)OpCode >> 29) & 3; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeAlu.cs b/Ryujinx/Cpu/Decoder/AOpCodeAlu.cs new file mode 100644 index 000000000..981af800e --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeAlu.cs @@ -0,0 +1,24 @@ +using ChocolArm64.Instruction; +using ChocolArm64.State; + +namespace ChocolArm64.Decoder +{ + class AOpCodeAlu : AOpCode, IAOpCodeAlu + { + public int Rd { get; protected set; } + public int Rn { get; private set; } + + public ADataOp DataOp { get; private set; } + + public AOpCodeAlu(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + Rd = (OpCode >> 0) & 0x1f; + Rn = (OpCode >> 5) & 0x1f; + DataOp = (ADataOp)((OpCode >> 24) & 0x3); + + RegisterSize = (OpCode >> 31) != 0 + ? ARegisterSize.Int64 + : ARegisterSize.Int32; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeAluImm.cs b/Ryujinx/Cpu/Decoder/AOpCodeAluImm.cs new file mode 100644 index 000000000..6b0adbc25 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeAluImm.cs @@ -0,0 +1,41 @@ +using ChocolArm64.Instruction; +using System; + +namespace ChocolArm64.Decoder +{ + class AOpCodeAluImm : AOpCodeAlu, IAOpCodeAluImm + { + public long Imm { get; private set; } + + public AOpCodeAluImm(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + if (DataOp == ADataOp.Arithmetic) + { + Imm = (OpCode >> 10) & 0xfff; + + int Shift = (OpCode >> 22) & 3; + + //Assert Shift < 2 + + Imm <<= Shift * 12; + } + else if (DataOp == ADataOp.Logical) + { + var BM = ADecoderHelper.DecodeBitMask(OpCode, true); + + if (BM.IsUndefined) + { + Emitter = AInstEmit.Und; + + return; + } + + Imm = BM.WMask; + } + else + { + throw new ArgumentException(nameof(OpCode)); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeAluRs.cs b/Ryujinx/Cpu/Decoder/AOpCodeAluRs.cs new file mode 100644 index 000000000..8439df6f2 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeAluRs.cs @@ -0,0 +1,21 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeAluRs : AOpCodeAlu, IAOpCodeAluRs + { + public int Shift { get; private set; } + public int Rm { get; private set; } + + public AShiftType ShiftType { get; private set; } + + public AOpCodeAluRs(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + Shift = (OpCode >> 10) & 0x3f; + Rm = (OpCode >> 16) & 0x1f; + ShiftType = (AShiftType)((OpCode >> 22) & 0x3); + + //Assert ShiftType != 3 + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeAluRx.cs b/Ryujinx/Cpu/Decoder/AOpCodeAluRx.cs new file mode 100644 index 000000000..7dd72a684 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeAluRx.cs @@ -0,0 +1,19 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeAluRx : AOpCodeAlu, IAOpCodeAluRx + { + public int Shift { get; private set; } + public int Rm { get; private set; } + + public AIntType IntType { get; private set; } + + public AOpCodeAluRx(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + Shift = (OpCode >> 10) & 0x7; + IntType = (AIntType)((OpCode >> 13) & 0x7); + Rm = (OpCode >> 16) & 0x1f; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeBImm.cs b/Ryujinx/Cpu/Decoder/AOpCodeBImm.cs new file mode 100644 index 000000000..2a56d4af4 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeBImm.cs @@ -0,0 +1,11 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeBImm : AOpCode + { + public long Imm { get; protected set; } + + public AOpCodeBImm(AInst Inst, long Position) : base(Inst, Position) { } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeBImmAl.cs b/Ryujinx/Cpu/Decoder/AOpCodeBImmAl.cs new file mode 100644 index 000000000..1b6a98e7c --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeBImmAl.cs @@ -0,0 +1,12 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeBImmAl : AOpCodeBImm + { + public AOpCodeBImmAl(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + Imm = Position + ADecoderHelper.DecodeImm26_2(OpCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeBImmCmp.cs b/Ryujinx/Cpu/Decoder/AOpCodeBImmCmp.cs new file mode 100644 index 000000000..e0ce57e38 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeBImmCmp.cs @@ -0,0 +1,16 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeBImmCmp : AOpCodeBImm + { + public int Rt { get; private set; } + + public AOpCodeBImmCmp(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + Rt = OpCode & 0x1f; + + Imm = Position + ADecoderHelper.DecodeImmS19_2(OpCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeBImmCond.cs b/Ryujinx/Cpu/Decoder/AOpCodeBImmCond.cs new file mode 100644 index 000000000..e4ae845fe --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeBImmCond.cs @@ -0,0 +1,25 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeBImmCond : AOpCodeBImm, IAOpCodeCond + { + public ACond Cond { get; private set; } + + public AOpCodeBImmCond(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + int O0 = (OpCode >> 4) & 1; + + if (O0 != 0) + { + Emitter = AInstEmit.Und; + + return; + } + + Cond = (ACond)(OpCode & 0xf); + + Imm = Position + ADecoderHelper.DecodeImmS19_2(OpCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeBImmTest.cs b/Ryujinx/Cpu/Decoder/AOpCodeBImmTest.cs new file mode 100644 index 000000000..6b8b966db --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeBImmTest.cs @@ -0,0 +1,20 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeBImmTest : AOpCodeBImm + { + public int Rt { get; private set; } + public int Pos { get; private set; } + + public AOpCodeBImmTest(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + Rt = OpCode & 0x1f; + + Imm = Position + ADecoderHelper.DecodeImmS14_2(OpCode); + + Pos = (OpCode >> 19) & 0x1f; + Pos |= (OpCode >> 26) & 0x20; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeBReg.cs b/Ryujinx/Cpu/Decoder/AOpCodeBReg.cs new file mode 100644 index 000000000..a71fc338a --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeBReg.cs @@ -0,0 +1,24 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeBReg : AOpCode + { + public int Rn { get; private set; } + + public AOpCodeBReg(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + int Op4 = (OpCode >> 0) & 0x1f; + int Op2 = (OpCode >> 16) & 0x1f; + + if (Op2 != 0b11111 || Op4 != 0b00000) + { + Emitter = AInstEmit.Und; + + return; + } + + Rn = (OpCode >> 5) & 0x1f; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeBfm.cs b/Ryujinx/Cpu/Decoder/AOpCodeBfm.cs new file mode 100644 index 000000000..6498d8ec6 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeBfm.cs @@ -0,0 +1,29 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeBfm : AOpCodeAlu + { + public long WMask { get; private set; } + public long TMask { get; private set; } + public int Pos { get; private set; } + public int Shift { get; private set; } + + public AOpCodeBfm(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + var BM = ADecoderHelper.DecodeBitMask(OpCode, false); + + if (BM.IsUndefined) + { + Emitter = AInstEmit.Und; + + return; + } + + WMask = BM.WMask; + TMask = BM.TMask; + Pos = BM.Pos; + Shift = BM.Shift; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeCcmp.cs b/Ryujinx/Cpu/Decoder/AOpCodeCcmp.cs new file mode 100644 index 000000000..ab7aa7545 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeCcmp.cs @@ -0,0 +1,31 @@ +using ChocolArm64.Instruction; +using ChocolArm64.State; + +namespace ChocolArm64.Decoder +{ + class AOpCodeCcmp : AOpCodeAlu, IAOpCodeCond + { + public int NZCV { get; private set; } + protected int RmImm; + + public ACond Cond { get; private set; } + + public AOpCodeCcmp(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + int O3 = (OpCode >> 4) & 1; + + if (O3 != 0) + { + Emitter = AInstEmit.Und; + + return; + } + + NZCV = (OpCode >> 0) & 0xf; + Cond = (ACond)((OpCode >> 12) & 0xf); + RmImm = (OpCode >> 16) & 0x1f; + + Rd = ARegisters.ZRIndex; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeCcmpImm.cs b/Ryujinx/Cpu/Decoder/AOpCodeCcmpImm.cs new file mode 100644 index 000000000..803eefc24 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeCcmpImm.cs @@ -0,0 +1,11 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeCcmpImm : AOpCodeCcmp, IAOpCodeAluImm + { + public long Imm => RmImm; + + public AOpCodeCcmpImm(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) { } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeCcmpReg.cs b/Ryujinx/Cpu/Decoder/AOpCodeCcmpReg.cs new file mode 100644 index 000000000..c364ae68b --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeCcmpReg.cs @@ -0,0 +1,15 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeCcmpReg : AOpCodeCcmp, IAOpCodeAluRs + { + public int Rm => RmImm; + + public int Shift => 0; + + public AShiftType ShiftType => AShiftType.Lsl; + + public AOpCodeCcmpReg(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) { } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeCsel.cs b/Ryujinx/Cpu/Decoder/AOpCodeCsel.cs new file mode 100644 index 000000000..cdef3e745 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeCsel.cs @@ -0,0 +1,17 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeCsel : AOpCodeAlu, IAOpCodeCond + { + public int Rm { get; private set; } + + public ACond Cond { get; private set; } + + public AOpCodeCsel(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + Rm = (OpCode >> 16) & 0x1f; + Cond = (ACond)((OpCode >> 12) & 0xf); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeException.cs b/Ryujinx/Cpu/Decoder/AOpCodeException.cs new file mode 100644 index 000000000..6d4a03861 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeException.cs @@ -0,0 +1,14 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeException : AOpCode + { + public int Id { get; private set; } + + public AOpCodeException(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + Id = (OpCode >> 5) & 0xfff; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeMem.cs b/Ryujinx/Cpu/Decoder/AOpCodeMem.cs new file mode 100644 index 000000000..1950b2867 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeMem.cs @@ -0,0 +1,19 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeMem : AOpCode + { + public int Rt { get; protected set; } + public int Rn { get; protected set; } + public int Size { get; protected set; } + public bool Extend64 { get; protected set; } + + public AOpCodeMem(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + Rt = (OpCode >> 0) & 0x1f; + Rn = (OpCode >> 5) & 0x1f; + Size = (OpCode >> 30) & 0x3; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeMemEx.cs b/Ryujinx/Cpu/Decoder/AOpCodeMemEx.cs new file mode 100644 index 000000000..3a28cfd73 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeMemEx.cs @@ -0,0 +1,16 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeMemEx : AOpCodeMem + { + public int Rt2 { get; private set; } + public int Rs { get; private set; } + + public AOpCodeMemEx(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + Rt2 = (OpCode >> 10) & 0x1f; + Rs = (OpCode >> 16) & 0x1f; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeMemImm.cs b/Ryujinx/Cpu/Decoder/AOpCodeMemImm.cs new file mode 100644 index 000000000..14edc5148 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeMemImm.cs @@ -0,0 +1,53 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeMemImm : AOpCodeMem + { + public long Imm { get; protected set; } + public bool WBack { get; protected set; } + public bool PostIdx { get; protected set; } + protected bool Unscaled { get; private set; } + + private enum MemOp + { + Unscaled = 0, + PostIndexed = 1, + Unprivileged = 2, + PreIndexed = 3, + Unsigned + } + + public AOpCodeMemImm(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + Extend64 = ((OpCode >> 22) & 3) == 2; + WBack = ((OpCode >> 24) & 1) == 0; + + //The type is not valid for the Unsigned Immediate 12-bits encoding, + //because the bits 11:10 are used for the larger Immediate offset. + MemOp Type = WBack ? (MemOp)((OpCode >> 10) & 3) : MemOp.Unsigned; + + PostIdx = Type == MemOp.PostIndexed; + Unscaled = Type == MemOp.Unscaled || + Type == MemOp.Unprivileged; + + //Unscaled and Unprivileged doesn't write back, + //but they do use the 9-bits Signed Immediate. + if (Unscaled) + { + WBack = false; + } + + if (WBack || Unscaled) + { + //9-bits Signed Immediate. + Imm = (OpCode << 43) >> 55; + } + else + { + //12-bits Unsigned Immediate. + Imm = ((OpCode >> 10) & 0xfff) << Size; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeMemLit.cs b/Ryujinx/Cpu/Decoder/AOpCodeMemLit.cs new file mode 100644 index 000000000..b94294353 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeMemLit.cs @@ -0,0 +1,28 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeMemLit : AOpCode, IAOpCodeLit + { + public int Rt { get; private set; } + public long Imm { get; private set; } + public int Size { get; private set; } + public bool Signed { get; private set; } + public bool Prefetch { get; private set; } + + public AOpCodeMemLit(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + Rt = OpCode & 0x1f; + + Imm = Position + ADecoderHelper.DecodeImmS19_2(OpCode); + + switch ((OpCode >> 30) & 3) + { + case 0: Size = 2; Signed = false; Prefetch = false; break; + case 1: Size = 3; Signed = false; Prefetch = false; break; + case 2: Size = 2; Signed = true; Prefetch = false; break; + case 3: Size = 0; Signed = false; Prefetch = true; break; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeMemPair.cs b/Ryujinx/Cpu/Decoder/AOpCodeMemPair.cs new file mode 100644 index 000000000..ec866c84e --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeMemPair.cs @@ -0,0 +1,25 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeMemPair : AOpCodeMemImm + { + public int Rt2 { get; private set; } + + public AOpCodeMemPair(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + Rt2 = (OpCode >> 10) & 0x1f; + WBack = ((OpCode >> 23) & 0x1) != 0; + PostIdx = ((OpCode >> 23) & 0x3) == 1; + Extend64 = ((OpCode >> 30) & 0x3) == 1; + Size = ((OpCode >> 31) & 0x1) | 2; + + DecodeImm(OpCode); + } + + protected void DecodeImm(int OpCode) + { + Imm = ((long)(OpCode >> 15) << 57) >> (57 - Size); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeMemReg.cs b/Ryujinx/Cpu/Decoder/AOpCodeMemReg.cs new file mode 100644 index 000000000..989271282 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeMemReg.cs @@ -0,0 +1,20 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeMemReg : AOpCodeMem + { + public bool Shift { get; private set; } + public int Rm { get; private set; } + + public AIntType IntType { get; private set; } + + public AOpCodeMemReg(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + Shift = ((OpCode >> 12) & 0x1) != 0; + IntType = (AIntType)((OpCode >> 13) & 0x7); + Rm = (OpCode >> 16) & 0x1f; + Extend64 = ((OpCode >> 22) & 0x3) == 2; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeMov.cs b/Ryujinx/Cpu/Decoder/AOpCodeMov.cs new file mode 100644 index 000000000..3d1431fb1 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeMov.cs @@ -0,0 +1,36 @@ +using ChocolArm64.Instruction; +using ChocolArm64.State; + +namespace ChocolArm64.Decoder +{ + class AOpCodeMov : AOpCode + { + public int Rd { get; private set; } + public long Imm { get; private set; } + public int Pos { get; private set; } + + public AOpCodeMov(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + int P1 = (OpCode >> 22) & 1; + int SF = (OpCode >> 31) & 1; + + if (SF == 0 && P1 != 0) + { + Emitter = AInstEmit.Und; + + return; + } + + Rd = (OpCode >> 0) & 0x1f; + Imm = (OpCode >> 5) & 0xffff; + Pos = (OpCode >> 21) & 0x3; + + Pos <<= 4; + Imm <<= Pos; + + RegisterSize = (OpCode >> 31) != 0 + ? ARegisterSize.Int64 + : ARegisterSize.Int32; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeMul.cs b/Ryujinx/Cpu/Decoder/AOpCodeMul.cs new file mode 100644 index 000000000..ca2b0cdb3 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeMul.cs @@ -0,0 +1,16 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeMul : AOpCodeAlu + { + public int Rm { get; private set; } + public int Ra { get; private set; } + + public AOpCodeMul(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + Ra = (OpCode >> 10) & 0x1f; + Rm = (OpCode >> 16) & 0x1f; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimd.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimd.cs new file mode 100644 index 000000000..796199845 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeSimd.cs @@ -0,0 +1,27 @@ +using ChocolArm64.Instruction; +using ChocolArm64.State; + +namespace ChocolArm64.Decoder +{ + class AOpCodeSimd : AOpCode, IAOpCodeSimd + { + public int Rd { get; private set; } + public int Rn { get; private set; } + public int Opc { get; private set; } + public int Size { get; protected set; } + + public int SizeF => Size & 1; + + public AOpCodeSimd(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + Rd = (OpCode >> 0) & 0x1f; + Rn = (OpCode >> 5) & 0x1f; + Opc = (OpCode >> 15) & 0x3; + Size = (OpCode >> 22) & 0x3; + + RegisterSize = ((OpCode >> 30) & 1) != 0 + ? ARegisterSize.SIMD128 + : ARegisterSize.SIMD64; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdCvt.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdCvt.cs new file mode 100644 index 000000000..41f4d3b14 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdCvt.cs @@ -0,0 +1,31 @@ +using ChocolArm64.Instruction; +using ChocolArm64.State; + +namespace ChocolArm64.Decoder +{ + class AOpCodeSimdCvt : AOpCodeSimd + { + public int FBits { get; private set; } + + public AOpCodeSimdCvt(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + //TODO: + //Und of Fixed Point variants. + int Scale = (OpCode >> 10) & 0x3f; + int SF = (OpCode >> 31) & 0x1; + + /*if (Type != SF && !(Type == 2 && SF == 1)) + { + Emitter = AInstEmit.Und; + + return; + }*/ + + FBits = 64 - Scale; + + RegisterSize = SF != 0 + ? ARegisterSize.Int64 + : ARegisterSize.Int32; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdFcond.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdFcond.cs new file mode 100644 index 000000000..e38e74247 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdFcond.cs @@ -0,0 +1,17 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeSimdFcond : AOpCodeSimdReg, IAOpCodeCond + { + public int NZCV { get; private set; } + + public ACond Cond { get; private set; } + + public AOpCodeSimdFcond(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + NZCV = (OpCode >> 0) & 0xf; + Cond = (ACond)((OpCode >> 12) & 0xf); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdFmov.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdFmov.cs new file mode 100644 index 000000000..1047beffc --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdFmov.cs @@ -0,0 +1,33 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeSimdFmov : AOpCode, IAOpCodeSimd + { + public int Rd { get; private set; } + public long Imm { get; private set; } + public int Size { get; private set; } + + public AOpCodeSimdFmov(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + int Imm5 = (OpCode >> 5) & 0x1f; + int Type = (OpCode >> 22) & 0x3; + + if (Imm5 != 0b00000 || Type > 1) + { + Emitter = AInstEmit.Und; + + return; + } + + Size = Type; + + long Imm; + + Rd = (OpCode >> 0) & 0x1f; + Imm = (OpCode >> 13) & 0xff; + + this.Imm = ADecoderHelper.DecodeImm8Float(Imm, Type); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdImm.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdImm.cs new file mode 100644 index 000000000..3a08ce63a --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdImm.cs @@ -0,0 +1,94 @@ +using ChocolArm64.Instruction; +using ChocolArm64.State; + +namespace ChocolArm64.Decoder +{ + class AOpCodeSimdImm : AOpCode, IAOpCodeSimd + { + public int Rd { get; private set; } + public long Imm { get; private set; } + public int Size { get; private set; } + + public AOpCodeSimdImm(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + Rd = OpCode & 0x1f; + + int CMode = (OpCode >> 12) & 0xf; + int Op = (OpCode >> 29) & 0x1; + + int ModeLow = CMode & 1; + int ModeHigh = CMode >> 1; + + long Imm; + + Imm = ((uint)OpCode >> 5) & 0x1f; + Imm |= ((uint)OpCode >> 11) & 0xe0; + + if (ModeHigh == 0b111) + { + Size = ModeLow != 0 ? Op : 3; + + switch (Op | (ModeLow << 1)) + { + case 0: + //64-bits Immediate. + //Transform abcd efgh into abcd efgh abcd efgh ... + Imm = (long)((ulong)Imm * 0x0101010101010101); + break; + + case 1: + //64-bits Immediate. + //Transform abcd efgh into aaaa aaaa bbbb bbbb ... + Imm = (Imm & 0xf0) >> 4 | (Imm & 0x0f) << 4; + Imm = (Imm & 0xcc) >> 2 | (Imm & 0x33) << 2; + Imm = (Imm & 0xaa) >> 1 | (Imm & 0x55) << 1; + + Imm = (long)((ulong)Imm * 0x8040201008040201); + Imm = (long)((ulong)Imm & 0x8080808080808080); + + Imm |= Imm >> 4; + Imm |= Imm >> 2; + Imm |= Imm >> 1; + break; + + case 2: + case 3: + //Floating point Immediate. + Imm = ADecoderHelper.DecodeImm8Float(Imm, Size); + break; + } + } + else if ((ModeHigh & 0b110) == 0b100) + { + //16-bits shifted Immediate. + Size = 1; Imm <<= (ModeHigh & 1) << 3; + } + else if ((ModeHigh & 0b100) == 0b000) + { + //32-bits shifted Immediate. + Size = 2; Imm <<= ModeHigh << 3; + } + else if ((ModeHigh & 0b111) == 0b110) + { + //32-bits shifted Immediate (fill with ones). + Size = 2; Imm = ShlOnes(Imm, 8 << ModeLow); + } + else + { + //8 bits without shift. + Size = 0; + } + + this.Imm = Imm; + + RegisterSize = ((OpCode >> 30) & 1) != 0 + ? ARegisterSize.SIMD128 + : ARegisterSize.SIMD64; + } + + private static long ShlOnes(long Value, int Shift) + { + return Value << Shift | (long)(ulong.MaxValue >> (64 - Shift)); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdIns.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdIns.cs new file mode 100644 index 000000000..0b60bbe83 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdIns.cs @@ -0,0 +1,36 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeSimdIns : AOpCodeSimd + { + public int SrcIndex { get; private set; } + public int DstIndex { get; private set; } + + public AOpCodeSimdIns(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + int Imm4 = (OpCode >> 11) & 0xf; + int Imm5 = (OpCode >> 16) & 0x1f; + + if (Imm5 == 0b10000) + { + Emitter = AInstEmit.Und; + + return; + } + + Size = Imm5 & -Imm5; + + switch (Size) + { + case 1: Size = 0; break; + case 2: Size = 1; break; + case 4: Size = 2; break; + case 8: Size = 3; break; + } + + SrcIndex = Imm4 >> Size; + DstIndex = Imm5 >> (Size + 1); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdMemImm.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemImm.cs new file mode 100644 index 000000000..1ef19a5d6 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemImm.cs @@ -0,0 +1,19 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeSimdMemImm : AOpCodeMemImm, IAOpCodeSimd + { + public AOpCodeSimdMemImm(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + Size |= (OpCode >> 21) & 4; + + if (!WBack && !Unscaled && Size >= 4) + { + Imm <<= 4; + } + + Extend64 = false; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdMemLit.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemLit.cs new file mode 100644 index 000000000..cf6915f56 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemLit.cs @@ -0,0 +1,31 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeSimdMemLit : AOpCode, IAOpCodeSimd, IAOpCodeLit + { + public int Rt { get; private set; } + public long Imm { get; private set; } + public int Size { get; private set; } + public bool Signed => false; + public bool Prefetch => false; + + public AOpCodeSimdMemLit(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + int Opc = (OpCode >> 30) & 3; + + if (Opc == 3) + { + Emitter = AInstEmit.Und; + + return; + } + + Rt = OpCode & 0x1f; + + Imm = Position + ADecoderHelper.DecodeImmS19_2(OpCode); + + Size = Opc + 2; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdMemMult.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemMult.cs new file mode 100644 index 000000000..9731c7e74 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemMult.cs @@ -0,0 +1,54 @@ +using ChocolArm64.Instruction; +using ChocolArm64.State; + +namespace ChocolArm64.Decoder +{ + class AOpCodeSimdMemMult : AOpCode, IAOpCodeSimd + { + public int Rt { get; private set; } + public int Rn { get; private set; } + public int Size { get; private set; } + public int Rm { get; private set; } + public int Reps { get; private set; } + public int SElems { get; private set; } + public int Elems { get; private set; } + public bool WBack { get; private set; } + + public AOpCodeSimdMemMult(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + switch ((OpCode >> 12) & 0xf) + { + case 0b0000: Reps = 1; SElems = 4; break; + case 0b0010: Reps = 4; SElems = 1; break; + case 0b0100: Reps = 1; SElems = 3; break; + case 0b0110: Reps = 3; SElems = 1; break; + case 0b0111: Reps = 1; SElems = 1; break; + case 0b1000: Reps = 1; SElems = 2; break; + case 0b1010: Reps = 2; SElems = 1; break; + + default: Inst = AInst.Undefined; return; + } + + Rt = (OpCode >> 0) & 0x1f; + Rn = (OpCode >> 5) & 0x1f; + Size = (OpCode >> 10) & 0x3; + Rm = (OpCode >> 16) & 0x1f; + WBack = ((OpCode >> 23) & 0x1) != 0; + + bool Q = ((OpCode >> 30) & 1) != 0; + + if (!Q && Size == 3 && SElems != 1) + { + Inst = AInst.Undefined; + + return; + } + + RegisterSize = Q + ? ARegisterSize.SIMD128 + : ARegisterSize.SIMD64; + + Elems = (GetBitsCount() >> 3) >> Size; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdMemPair.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemPair.cs new file mode 100644 index 000000000..db99e3d44 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemPair.cs @@ -0,0 +1,16 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeSimdMemPair : AOpCodeMemPair, IAOpCodeSimd + { + public AOpCodeSimdMemPair(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + Size = ((OpCode >> 30) & 3) + 2; + + Extend64 = false; + + DecodeImm(OpCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdMemReg.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemReg.cs new file mode 100644 index 000000000..aabf48461 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemReg.cs @@ -0,0 +1,14 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeSimdMemReg : AOpCodeMemReg, IAOpCodeSimd + { + public AOpCodeSimdMemReg(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + Size |= (OpCode >> 21) & 4; + + Extend64 = false; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdReg.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdReg.cs new file mode 100644 index 000000000..d3a8b76aa --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdReg.cs @@ -0,0 +1,16 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeSimdReg : AOpCodeSimd + { + public int Rm { get; private set; } + public bool Bit3 { get; private set; } + + public AOpCodeSimdReg(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + Rm = (OpCode >> 16) & 0x1f; + Bit3 = ((OpCode >> 3) & 0x1) != 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdRegElem.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdRegElem.cs new file mode 100644 index 000000000..828fe7884 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdRegElem.cs @@ -0,0 +1,26 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeSimdRegElem : AOpCodeSimd + { + public int Rm { get; private set; } + public int Index { get; private set; } + + public AOpCodeSimdRegElem(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + Rm = (OpCode >> 16) & 0x1f; + Size = (OpCode >> 22) & 0x1; + + if (Size != 0) + { + Index = (OpCode >> 11) & 1; + } + else + { + Index = (OpCode >> 21) & 1 | + (OpCode >> 10) & 2; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdShImm.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdShImm.cs new file mode 100644 index 000000000..a677e579b --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdShImm.cs @@ -0,0 +1,26 @@ +using ChocolArm64.Instruction; +using ChocolArm64.State; + +namespace ChocolArm64.Decoder +{ + class AOpCodeSimdShImm : AOpCode, IAOpCodeSimd + { + public int Rd { get; private set; } + public int Rn { get; private set; } + public int Imm { get; private set; } + public int Size { get; private set; } + + public AOpCodeSimdShImm(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + Rd = (OpCode >> 0) & 0x1f; + Rn = (OpCode >> 5) & 0x1f; + Imm = (OpCode >> 16) & 0x7f; + + Size = ABitUtils.HighestBitSet32(Imm >> 3); + + RegisterSize = ((OpCode >> 30) & 1) != 0 + ? ARegisterSize.SIMD128 + : ARegisterSize.SIMD64; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdTbl.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdTbl.cs new file mode 100644 index 000000000..c8ae5bac7 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdTbl.cs @@ -0,0 +1,12 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeSimdTbl : AOpCodeSimdReg + { + public AOpCodeSimdTbl(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + { + Size = ((OpCode >> 13) & 3) + 1; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSystem.cs b/Ryujinx/Cpu/Decoder/AOpCodeSystem.cs new file mode 100644 index 000000000..95b291001 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AOpCodeSystem.cs @@ -0,0 +1,24 @@ +using ChocolArm64.Instruction; + +namespace ChocolArm64.Decoder +{ + class AOpCodeSystem : AOpCode + { + public int Rt { get; private set; } + public int Op2 { get; private set; } + public int CRm { get; private set; } + public int CRn { get; private set; } + public int Op1 { get; private set; } + public int Op0 { get; private set; } + + public AOpCodeSystem(AInst Inst, long Position, int OpCode) : base(Inst, Position) + { + Rt = (OpCode >> 0) & 0x1f; + Op2 = (OpCode >> 5) & 0x7; + CRm = (OpCode >> 8) & 0xf; + CRn = (OpCode >> 12) & 0xf; + Op1 = (OpCode >> 16) & 0x7; + Op0 = ((OpCode >> 19) & 0x1) | 2; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/AShiftType.cs b/Ryujinx/Cpu/Decoder/AShiftType.cs new file mode 100644 index 000000000..34ceea208 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/AShiftType.cs @@ -0,0 +1,10 @@ +namespace ChocolArm64.Decoder +{ + enum AShiftType + { + Lsl, + Lsr, + Asr, + Ror + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/IAOpCode.cs b/Ryujinx/Cpu/Decoder/IAOpCode.cs new file mode 100644 index 000000000..44bf9cb2f --- /dev/null +++ b/Ryujinx/Cpu/Decoder/IAOpCode.cs @@ -0,0 +1,13 @@ +using ChocolArm64.Instruction; +using ChocolArm64.State; + +namespace ChocolArm64.Decoder +{ + interface IAOpCode + { + long Position { get; } + + AInstEmitter Emitter { get; } + ARegisterSize RegisterSize { get; } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/IAOpCodeAlu.cs b/Ryujinx/Cpu/Decoder/IAOpCodeAlu.cs new file mode 100644 index 000000000..22af4c82d --- /dev/null +++ b/Ryujinx/Cpu/Decoder/IAOpCodeAlu.cs @@ -0,0 +1,10 @@ +namespace ChocolArm64.Decoder +{ + interface IAOpCodeAlu : IAOpCode + { + int Rd { get; } + int Rn { get; } + + ADataOp DataOp { get; } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/IAOpCodeAluImm.cs b/Ryujinx/Cpu/Decoder/IAOpCodeAluImm.cs new file mode 100644 index 000000000..04b5c5f7d --- /dev/null +++ b/Ryujinx/Cpu/Decoder/IAOpCodeAluImm.cs @@ -0,0 +1,7 @@ +namespace ChocolArm64.Decoder +{ + interface IAOpCodeAluImm : IAOpCodeAlu + { + long Imm { get; } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/IAOpCodeAluRs.cs b/Ryujinx/Cpu/Decoder/IAOpCodeAluRs.cs new file mode 100644 index 000000000..5ca9de403 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/IAOpCodeAluRs.cs @@ -0,0 +1,10 @@ +namespace ChocolArm64.Decoder +{ + interface IAOpCodeAluRs : IAOpCodeAlu + { + int Shift { get; } + int Rm { get; } + + AShiftType ShiftType { get; } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/IAOpCodeAluRx.cs b/Ryujinx/Cpu/Decoder/IAOpCodeAluRx.cs new file mode 100644 index 000000000..b49d5325a --- /dev/null +++ b/Ryujinx/Cpu/Decoder/IAOpCodeAluRx.cs @@ -0,0 +1,10 @@ +namespace ChocolArm64.Decoder +{ + interface IAOpCodeAluRx : IAOpCodeAlu + { + int Shift { get; } + int Rm { get; } + + AIntType IntType { get; } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/IAOpCodeCond.cs b/Ryujinx/Cpu/Decoder/IAOpCodeCond.cs new file mode 100644 index 000000000..1655abaac --- /dev/null +++ b/Ryujinx/Cpu/Decoder/IAOpCodeCond.cs @@ -0,0 +1,7 @@ +namespace ChocolArm64.Decoder +{ + interface IAOpCodeCond : IAOpCode + { + ACond Cond { get; } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/IAOpCodeLit.cs b/Ryujinx/Cpu/Decoder/IAOpCodeLit.cs new file mode 100644 index 000000000..0f5092d07 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/IAOpCodeLit.cs @@ -0,0 +1,11 @@ +namespace ChocolArm64.Decoder +{ + interface IAOpCodeLit : IAOpCode + { + int Rt { get; } + long Imm { get; } + int Size { get; } + bool Signed { get; } + bool Prefetch { get; } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Decoder/IAOpCodeSimd.cs b/Ryujinx/Cpu/Decoder/IAOpCodeSimd.cs new file mode 100644 index 000000000..19032ad94 --- /dev/null +++ b/Ryujinx/Cpu/Decoder/IAOpCodeSimd.cs @@ -0,0 +1,7 @@ +namespace ChocolArm64.Decoder +{ + interface IAOpCodeSimd : IAOpCode + { + int Size { get; } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Exceptions/VmmAccessViolationException.cs b/Ryujinx/Cpu/Exceptions/VmmAccessViolationException.cs new file mode 100644 index 000000000..dd9d7a646 --- /dev/null +++ b/Ryujinx/Cpu/Exceptions/VmmAccessViolationException.cs @@ -0,0 +1,14 @@ +using ChocolArm64.Memory; +using System; + +namespace ChocolArm64.Exceptions +{ + public class VmmAccessViolationException : Exception + { + private const string ExMsg = "Value at address 0x{0:x16} could not be \"{1}\"!"; + + public VmmAccessViolationException() { } + + public VmmAccessViolationException(long Position, AMemoryPerm Perm) : base(string.Format(ExMsg, Position, Perm)) { } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Exceptions/VmmOutOfMemoryException.cs b/Ryujinx/Cpu/Exceptions/VmmOutOfMemoryException.cs new file mode 100644 index 000000000..c11384dae --- /dev/null +++ b/Ryujinx/Cpu/Exceptions/VmmOutOfMemoryException.cs @@ -0,0 +1,13 @@ +using System; + +namespace ChocolArm64.Exceptions +{ + public class VmmOutOfMemoryException : Exception + { + private const string ExMsg = "Failed to allocate {0} bytes of memory!"; + + public VmmOutOfMemoryException() { } + + public VmmOutOfMemoryException(long Size) : base(string.Format(ExMsg, Size)) { } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Exceptions/VmmPageFaultException.cs b/Ryujinx/Cpu/Exceptions/VmmPageFaultException.cs new file mode 100644 index 000000000..d55c2c1ca --- /dev/null +++ b/Ryujinx/Cpu/Exceptions/VmmPageFaultException.cs @@ -0,0 +1,13 @@ +using System; + +namespace ChocolArm64.Exceptions +{ + public class VmmPageFaultException : Exception + { + private const string ExMsg = "Tried to access unmapped address 0x{0:x16}!"; + + public VmmPageFaultException() { } + + public VmmPageFaultException(long Position) : base(string.Format(ExMsg, Position)) { } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/AInst.cs b/Ryujinx/Cpu/Instruction/AInst.cs new file mode 100644 index 000000000..cab597d65 --- /dev/null +++ b/Ryujinx/Cpu/Instruction/AInst.cs @@ -0,0 +1,18 @@ +using System; + +namespace ChocolArm64.Instruction +{ + struct AInst + { + public AInstEmitter Emitter { get; private set; } + public Type Type { get; private set; } + + public static AInst Undefined => new AInst(AInstEmit.Und, null); + + public AInst(AInstEmitter Emitter, Type Type) + { + this.Emitter = Emitter; + this.Type = Type; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/AInstEmitAlu.cs b/Ryujinx/Cpu/Instruction/AInstEmitAlu.cs new file mode 100644 index 000000000..c09b18632 --- /dev/null +++ b/Ryujinx/Cpu/Instruction/AInstEmitAlu.cs @@ -0,0 +1,296 @@ +using ChocolArm64.Decoder; +using ChocolArm64.State; +using ChocolArm64.Translation; +using System.Reflection.Emit; + +using static ChocolArm64.Instruction.AInstEmitAluHelper; + +namespace ChocolArm64.Instruction +{ + static partial class AInstEmit + { + public static void Add(AILEmitterCtx Context) => EmitDataOp(Context, OpCodes.Add); + + public static void Adds(AILEmitterCtx Context) + { + Context.TryOptMarkCondWithoutCmp(); + + EmitDataLoadOpers(Context); + + Context.Emit(OpCodes.Add); + + Context.EmitZNFlagCheck(); + + EmitAddsCCheck(Context); + EmitAddsVCheck(Context); + EmitDataStoreS(Context); + } + + public static void And(AILEmitterCtx Context) => EmitDataOp(Context, OpCodes.And); + + public static void Ands(AILEmitterCtx Context) + { + EmitDataLoadOpers(Context); + + Context.Emit(OpCodes.And); + + Context.EmitZNFlagCheck(); + + EmitDataStoreS(Context); + } + + public static void Asrv(AILEmitterCtx Context) => EmitDataOpShift(Context, OpCodes.Shr); + + public static void Bic(AILEmitterCtx Context) => EmitBic(Context, false); + public static void Bics(AILEmitterCtx Context) => EmitBic(Context, true); + + private static void EmitBic(AILEmitterCtx Context, bool SetFlags) + { + EmitDataLoadOpers(Context); + + Context.Emit(OpCodes.Not); + Context.Emit(OpCodes.And); + + if (SetFlags) + { + Context.EmitZNFlagCheck(); + } + + EmitDataStore(Context, SetFlags); + } + + public static void Clz(AILEmitterCtx Context) + { + AOpCodeAlu Op = (AOpCodeAlu)Context.CurrOp; + + Context.EmitLdintzr(Op.Rn); + + if (Op.RegisterSize == ARegisterSize.Int32) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.CountLeadingZeros32)); + } + else + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.CountLeadingZeros64)); + } + + Context.EmitStintzr(Op.Rd); + } + + public static void Eor(AILEmitterCtx Context) => EmitDataOp(Context, OpCodes.Xor); + + public static void Extr(AILEmitterCtx Context) + { + //TODO: Ensure that the Shift is valid for the Is64Bits. + AOpCodeAluRs Op = (AOpCodeAluRs)Context.CurrOp; + + Context.EmitLdintzr(Op.Rm); + + if (Op.Shift > 0) + { + Context.EmitLdc_I4(Op.Shift); + + Context.Emit(OpCodes.Shr_Un); + + Context.EmitLdintzr(Op.Rn); + Context.EmitLdc_I4(Op.GetBitsCount() - Op.Shift); + + Context.Emit(OpCodes.Shl); + Context.Emit(OpCodes.Or); + } + + EmitDataStore(Context); + } + + public static void Lslv(AILEmitterCtx Context) => EmitDataOpShift(Context, OpCodes.Shl); + public static void Lsrv(AILEmitterCtx Context) => EmitDataOpShift(Context, OpCodes.Shr_Un); + + public static void Sub(AILEmitterCtx Context) => EmitDataOp(Context, OpCodes.Sub); + + public static void Subs(AILEmitterCtx Context) + { + Context.TryOptMarkCondWithoutCmp(); + + EmitDataLoadOpers(Context); + + Context.Emit(OpCodes.Sub); + + Context.EmitZNFlagCheck(); + + EmitSubsCCheck(Context); + EmitSubsVCheck(Context); + EmitDataStoreS(Context); + } + + public static void Orn(AILEmitterCtx Context) + { + EmitDataLoadOpers(Context); + + Context.Emit(OpCodes.Not); + Context.Emit(OpCodes.Or); + + EmitDataStore(Context); + } + + public static void Orr(AILEmitterCtx Context) => EmitDataOp(Context, OpCodes.Or); + + public static void Rbit(AILEmitterCtx Context) => EmitFallback32_64(Context, + nameof(ASoftFallback.ReverseBits32), + nameof(ASoftFallback.ReverseBits64)); + + public static void Rev16(AILEmitterCtx Context) => EmitFallback32_64(Context, + nameof(ASoftFallback.ReverseBytes16_32), + nameof(ASoftFallback.ReverseBytes16_64)); + + public static void Rev32(AILEmitterCtx Context) => EmitFallback32_64(Context, + nameof(ASoftFallback.ReverseBytes32_32), + nameof(ASoftFallback.ReverseBytes32_64)); + + public static void EmitFallback32_64(AILEmitterCtx Context, string Name32, string Name64) + { + AOpCodeAlu Op = (AOpCodeAlu)Context.CurrOp; + + Context.EmitLdintzr(Op.Rn); + + if (Op.RegisterSize == ARegisterSize.Int32) + { + ASoftFallback.EmitCall(Context, Name32); + } + else + { + ASoftFallback.EmitCall(Context, Name64); + } + + Context.EmitStintzr(Op.Rd); + } + + public static void Rev64(AILEmitterCtx Context) + { + AOpCodeAlu Op = (AOpCodeAlu)Context.CurrOp; + + Context.EmitLdintzr(Op.Rn); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.ReverseBytes64)); + + Context.EmitStintzr(Op.Rd); + } + + private static void EmitRev(AILEmitterCtx Context, string Name) + { + AOpCodeAlu Op = (AOpCodeAlu)Context.CurrOp; + + Context.EmitLdintzr(Op.Rn); + + ASoftFallback.EmitCall(Context, Name); + + Context.EmitStintzr(Op.Rd); + } + + public static void Rorv(AILEmitterCtx Context) + { + EmitDataLoadRn(Context); + EmitDataLoadShift(Context); + + Context.Emit(OpCodes.Shr_Un); + + EmitDataLoadRn(Context); + + Context.EmitLdc_I4(Context.CurrOp.GetBitsCount()); + + EmitDataLoadShift(Context); + + Context.Emit(OpCodes.Sub); + Context.Emit(OpCodes.Shl); + Context.Emit(OpCodes.Or); + + EmitDataStore(Context); + } + + public static void Sdiv(AILEmitterCtx Context) => EmitDiv(Context, OpCodes.Div); + public static void Udiv(AILEmitterCtx Context) => EmitDiv(Context, OpCodes.Div_Un); + + private static void EmitDiv(AILEmitterCtx Context, OpCode ILOp) + { + //If Rm == 0, Rd = 0 (division by zero). + Context.EmitLdc_I(0); + + EmitDataLoadRm(Context); + + Context.EmitLdc_I(0); + + AILLabel BadDiv = new AILLabel(); + + Context.Emit(OpCodes.Beq_S, BadDiv); + Context.Emit(OpCodes.Pop); + + if (ILOp == OpCodes.Div) + { + //If Rn == INT_MIN && Rm == -1, Rd = INT_MIN (overflow). + long IntMin = 1L << (Context.CurrOp.GetBitsCount() - 1); + + Context.EmitLdc_I(IntMin); + + EmitDataLoadRn(Context); + + Context.EmitLdc_I(IntMin); + + Context.Emit(OpCodes.Ceq); + + EmitDataLoadRm(Context); + + Context.EmitLdc_I(-1); + + Context.Emit(OpCodes.Ceq); + Context.Emit(OpCodes.And); + Context.Emit(OpCodes.Brtrue_S, BadDiv); + Context.Emit(OpCodes.Pop); + } + + EmitDataLoadRn(Context); + EmitDataLoadRm(Context); + + Context.Emit(ILOp); + + Context.MarkLabel(BadDiv); + + EmitDataStore(Context); + } + + private static void EmitDataOp(AILEmitterCtx Context, OpCode ILOp) + { + EmitDataLoadOpers(Context); + + Context.Emit(ILOp); + + EmitDataStore(Context); + } + + private static void EmitDataOpShift(AILEmitterCtx Context, OpCode ILOp) + { + EmitDataLoadRn(Context); + EmitDataLoadShift(Context); + + Context.Emit(ILOp); + + EmitDataStore(Context); + } + + private static void EmitDataLoadShift(AILEmitterCtx Context) + { + EmitDataLoadRm(Context); + + Context.EmitLdc_I(Context.CurrOp.GetBitsCount() - 1); + + Context.Emit(OpCodes.And); + + //Note: Only 32-bits shift values are valid, so when the value is 64-bits + //we need to cast it to a 32-bits integer. This is fine because we + //AND the value and only keep the lower 5 or 6 bits anyway -- it + //could very well fit on a byte. + if (Context.CurrOp.RegisterSize != ARegisterSize.Int32) + { + Context.Emit(OpCodes.Conv_I4); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/AInstEmitAluHelper.cs b/Ryujinx/Cpu/Instruction/AInstEmitAluHelper.cs new file mode 100644 index 000000000..03355ebad --- /dev/null +++ b/Ryujinx/Cpu/Instruction/AInstEmitAluHelper.cs @@ -0,0 +1,159 @@ +using ChocolArm64.Decoder; +using ChocolArm64.State; +using ChocolArm64.Translation; +using System.Reflection.Emit; + +namespace ChocolArm64.Instruction +{ + static class AInstEmitAluHelper + { + public static void EmitAddsCCheck(AILEmitterCtx Context) + { + //C = Rd < Rn + Context.Emit(OpCodes.Dup); + + EmitDataLoadRn(Context); + + Context.Emit(OpCodes.Clt_Un); + + Context.EmitStflg((int)APState.CBit); + } + + public static void EmitAddsVCheck(AILEmitterCtx Context) + { + //V = (Rd ^ Rn) & (Rd ^ Rm) & ~(Rn ^ Rm) < 0 + Context.EmitSttmp(); + Context.EmitLdtmp(); + Context.EmitLdtmp(); + + EmitDataLoadRn(Context); + + Context.Emit(OpCodes.Xor); + + Context.EmitLdtmp(); + + EmitDataLoadOper2(Context); + + Context.Emit(OpCodes.Xor); + Context.Emit(OpCodes.And); + + EmitDataLoadOpers(Context); + + Context.Emit(OpCodes.Xor); + Context.Emit(OpCodes.Not); + Context.Emit(OpCodes.And); + + Context.EmitLdc_I(0); + + Context.Emit(OpCodes.Clt); + + Context.EmitStflg((int)APState.VBit); + } + + public static void EmitSubsCCheck(AILEmitterCtx Context) + { + //C = Rn == Rm || Rn > Rm + EmitDataLoadOpers(Context); + + Context.Emit(OpCodes.Ceq); + + EmitDataLoadOpers(Context); + + Context.Emit(OpCodes.Cgt_Un); + Context.Emit(OpCodes.Or); + + Context.EmitStflg((int)APState.CBit); + } + + public static void EmitSubsVCheck(AILEmitterCtx Context) + { + //V = (Rd ^ Rn) & (Rn ^ Rm) < 0 + Context.Emit(OpCodes.Dup); + + EmitDataLoadRn(Context); + + Context.Emit(OpCodes.Xor); + + EmitDataLoadOpers(Context); + + Context.Emit(OpCodes.Xor); + Context.Emit(OpCodes.And); + + Context.EmitLdc_I(0); + + Context.Emit(OpCodes.Clt); + + Context.EmitStflg((int)APState.VBit); + } + + public static void EmitDataLoadRm(AILEmitterCtx Context) + { + Context.EmitLdintzr(((IAOpCodeAluRs)Context.CurrOp).Rm); + } + + public static void EmitDataLoadOpers(AILEmitterCtx Context) + { + EmitDataLoadRn(Context); + EmitDataLoadOper2(Context); + } + + public static void EmitDataLoadRn(AILEmitterCtx Context) + { + IAOpCodeAlu Op = (IAOpCodeAlu)Context.CurrOp; + + if (Op.DataOp == ADataOp.Logical || Op is IAOpCodeAluRs) + { + Context.EmitLdintzr(Op.Rn); + } + else + { + Context.EmitLdint(Op.Rn); + } + } + + public static void EmitDataLoadOper2(AILEmitterCtx Context) + { + switch (Context.CurrOp) + { + case IAOpCodeAluImm Op: + Context.EmitLdc_I(Op.Imm); + break; + + case IAOpCodeAluRs Op: + Context.EmitLdintzr(Op.Rm); + + switch (Op.ShiftType) + { + case AShiftType.Lsl: Context.EmitLsl(Op.Shift); break; + case AShiftType.Lsr: Context.EmitLsr(Op.Shift); break; + case AShiftType.Asr: Context.EmitAsr(Op.Shift); break; + case AShiftType.Ror: Context.EmitRor(Op.Shift); break; + } + break; + + case IAOpCodeAluRx Op: + Context.EmitLdintzr(Op.Rm); + Context.EmitCast(Op.IntType); + Context.EmitLsl(Op.Shift); + break; + } + } + + public static void EmitDataStore(AILEmitterCtx Context) => EmitDataStore(Context, false); + public static void EmitDataStoreS(AILEmitterCtx Context) => EmitDataStore(Context, true); + + public static void EmitDataStore(AILEmitterCtx Context, bool SetFlags) + { + IAOpCodeAlu Op = (IAOpCodeAlu)Context.CurrOp; + + if (SetFlags || Op is IAOpCodeAluRs) + { + Context.EmitStintzr(Op.Rd); + } + else + { + Context.EmitStint(Op.Rd); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/AInstEmitBfm.cs b/Ryujinx/Cpu/Instruction/AInstEmitBfm.cs new file mode 100644 index 000000000..4eff013d1 --- /dev/null +++ b/Ryujinx/Cpu/Instruction/AInstEmitBfm.cs @@ -0,0 +1,208 @@ +using ChocolArm64.Decoder; +using ChocolArm64.State; +using ChocolArm64.Translation; +using System.Reflection.Emit; + +namespace ChocolArm64.Instruction +{ + static partial class AInstEmit + { + public static void Bfm(AILEmitterCtx Context) + { + AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp; + + EmitBfmLoadRn(Context); + + Context.EmitLdintzr(Op.Rd); + Context.EmitLdc_I(~Op.WMask & Op.TMask); + + Context.Emit(OpCodes.And); + Context.Emit(OpCodes.Or); + + Context.EmitLdintzr(Op.Rd); + Context.EmitLdc_I(~Op.TMask); + + Context.Emit(OpCodes.And); + Context.Emit(OpCodes.Or); + + Context.EmitStintzr(Op.Rd); + } + + public static void Sbfm(AILEmitterCtx Context) + { + AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp; + + int BitsCount = Op.GetBitsCount(); + + if (Op.Pos + 1 == BitsCount) + { + EmitBfmShift(Context, OpCodes.Shr); + } + else if (Op.Pos < Op.Shift) + { + EmitSbfiz(Context); + } + else if (Op.Pos == 7 && Op.Shift == 0) + { + EmitSbfmCast(Context, OpCodes.Conv_I1); + } + else if (Op.Pos == 15 && Op.Shift == 0) + { + EmitSbfmCast(Context, OpCodes.Conv_I2); + } + else if (Op.Pos == 31 && Op.Shift == 0) + { + EmitSbfmCast(Context, OpCodes.Conv_I4); + } + else if (Op.Shift == 0) + { + Context.EmitLdintzr(Op.Rn); + + Context.EmitLsl(BitsCount - 1 - Op.Pos); + Context.EmitAsr(BitsCount - 1); + + Context.EmitStintzr(Op.Rd); + } + else + { + EmitBfmLoadRn(Context); + + Context.EmitLdintzr(Op.Rn); + + Context.EmitLsl(BitsCount - 1 - Op.Pos); + Context.EmitAsr(BitsCount - 1); + + Context.EmitLdc_I(~Op.TMask); + + Context.Emit(OpCodes.And); + Context.Emit(OpCodes.Or); + + Context.EmitStintzr(Op.Rd); + } + } + + public static void Ubfm(AILEmitterCtx Context) + { + AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp; + + if (Op.Pos + 1 == Op.GetBitsCount()) + { + EmitBfmShift(Context, OpCodes.Shr_Un); + } + else if (Op.Pos < Op.Shift) + { + EmitUbfiz(Context); + } + else if (Op.Pos + 1 == Op.Shift) + { + EmitBfmLsl(Context); + } + else if (Op.Pos == 7 && Op.Shift == 0) + { + EmitUbfmCast(Context, OpCodes.Conv_U1); + } + else if (Op.Pos == 15 && Op.Shift == 0) + { + EmitUbfmCast(Context, OpCodes.Conv_U2); + } + else + { + EmitBfmLoadRn(Context); + + Context.EmitStintzr(Op.Rd); + } + } + + private static void EmitSbfiz(AILEmitterCtx Context) => EmitBfiz(Context, true); + private static void EmitUbfiz(AILEmitterCtx Context) => EmitBfiz(Context, false); + + private static void EmitBfiz(AILEmitterCtx Context, bool Signed) + { + AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp; + + int Width = Op.Pos + 1; + + Context.EmitLdintzr(Op.Rn); + + Context.EmitLsl(Op.GetBitsCount() - Width); + + if (Signed) + { + Context.EmitAsr(Op.Shift - Width); + } + else + { + Context.EmitLsr(Op.Shift - Width); + } + + Context.EmitStintzr(Op.Rd); + } + + private static void EmitSbfmCast(AILEmitterCtx Context, OpCode ILOp) + { + EmitBfmCast(Context, ILOp, true); + } + + private static void EmitUbfmCast(AILEmitterCtx Context, OpCode ILOp) + { + EmitBfmCast(Context, ILOp, false); + } + + private static void EmitBfmCast(AILEmitterCtx Context, OpCode ILOp, bool Signed) + { + AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp; + + Context.EmitLdintzr(Op.Rn); + + Context.Emit(ILOp); + + if (Op.RegisterSize != ARegisterSize.Int32) + { + Context.Emit(Signed + ? OpCodes.Conv_I8 + : OpCodes.Conv_U8); + } + + Context.EmitStintzr(Op.Rd); + } + + private static void EmitBfmShift(AILEmitterCtx Context, OpCode ILOp) + { + AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp; + + if (Op.Shift > 0) + { + Context.EmitLdintzr(Op.Rn); + Context.EmitLdc_I4(Op.Shift); + + Context.Emit(ILOp); + + Context.EmitStintzr(Op.Rd); + } + } + + private static void EmitBfmLsl(AILEmitterCtx Context) + { + AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp; + + Context.EmitLdintzr(Op.Rn); + + Context.EmitLsl(Op.GetBitsCount() - Op.Shift); + + Context.EmitStintzr(Op.Rd); + } + + private static void EmitBfmLoadRn(AILEmitterCtx Context) + { + AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp; + + Context.EmitLdintzr(Op.Rn); + + Context.EmitRor(Op.Shift); + + Context.EmitLdc_I(Op.WMask & Op.TMask); + + Context.Emit(OpCodes.And); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/AInstEmitCcmp.cs b/Ryujinx/Cpu/Instruction/AInstEmitCcmp.cs new file mode 100644 index 000000000..7153a6a0d --- /dev/null +++ b/Ryujinx/Cpu/Instruction/AInstEmitCcmp.cs @@ -0,0 +1,81 @@ +using ChocolArm64.Decoder; +using ChocolArm64.State; +using ChocolArm64.Translation; +using System; +using System.Reflection.Emit; + +using static ChocolArm64.Instruction.AInstEmitAluHelper; + +namespace ChocolArm64.Instruction +{ + static partial class AInstEmit + { + private enum CcmpOp + { + Cmp, + Cmn + } + + public static void Ccmn(AILEmitterCtx Context) => EmitCcmp(Context, CcmpOp.Cmn); + public static void Ccmp(AILEmitterCtx Context) => EmitCcmp(Context, CcmpOp.Cmp); + + private static void EmitCcmp(AILEmitterCtx Context, CcmpOp CmpOp) + { + AOpCodeCcmp Op = (AOpCodeCcmp)Context.CurrOp; + + AILLabel LblTrue = new AILLabel(); + AILLabel LblEnd = new AILLabel(); + + Context.EmitCondBranch(LblTrue, Op.Cond); + + Context.EmitLdc_I4((Op.NZCV >> 0) & 1); + + Context.EmitStflg((int)APState.VBit); + + Context.EmitLdc_I4((Op.NZCV >> 1) & 1); + + Context.EmitStflg((int)APState.CBit); + + Context.EmitLdc_I4((Op.NZCV >> 2) & 1); + + Context.EmitStflg((int)APState.ZBit); + + Context.EmitLdc_I4((Op.NZCV >> 3) & 1); + + Context.EmitStflg((int)APState.NBit); + + Context.Emit(OpCodes.Br_S, LblEnd); + + Context.MarkLabel(LblTrue); + + EmitDataLoadOpers(Context); + + if (CmpOp == CcmpOp.Cmp) + { + Context.Emit(OpCodes.Sub); + + Context.EmitZNFlagCheck(); + + EmitSubsCCheck(Context); + EmitSubsVCheck(Context); + } + else if (CmpOp == CcmpOp.Cmn) + { + Context.Emit(OpCodes.Add); + + Context.EmitZNFlagCheck(); + + EmitAddsCCheck(Context); + EmitAddsVCheck(Context); + } + else + { + throw new ArgumentException(nameof(CmpOp)); + } + + Context.Emit(OpCodes.Pop); + + Context.MarkLabel(LblEnd); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/AInstEmitCsel.cs b/Ryujinx/Cpu/Instruction/AInstEmitCsel.cs new file mode 100644 index 000000000..330809806 --- /dev/null +++ b/Ryujinx/Cpu/Instruction/AInstEmitCsel.cs @@ -0,0 +1,59 @@ +using ChocolArm64.Decoder; +using ChocolArm64.Translation; +using System.Reflection.Emit; + +namespace ChocolArm64.Instruction +{ + static partial class AInstEmit + { + private enum CselOperation + { + None, + Increment, + Invert, + Negate + } + + public static void Csel(AILEmitterCtx Context) => EmitCsel(Context, CselOperation.None); + public static void Csinc(AILEmitterCtx Context) => EmitCsel(Context, CselOperation.Increment); + public static void Csinv(AILEmitterCtx Context) => EmitCsel(Context, CselOperation.Invert); + public static void Csneg(AILEmitterCtx Context) => EmitCsel(Context, CselOperation.Negate); + + private static void EmitCsel(AILEmitterCtx Context, CselOperation CselOp) + { + AOpCodeCsel Op = (AOpCodeCsel)Context.CurrOp; + + AILLabel LblTrue = new AILLabel(); + AILLabel LblEnd = new AILLabel(); + + Context.EmitCondBranch(LblTrue, Op.Cond); + Context.EmitLdintzr(Op.Rm); + + if (CselOp == CselOperation.Increment) + { + Context.EmitLdc_I(1); + + Context.Emit(OpCodes.Add); + } + else if (CselOp == CselOperation.Invert) + { + Context.Emit(OpCodes.Not); + } + else if (CselOp == CselOperation.Negate) + { + Context.Emit(OpCodes.Neg); + } + + Context.EmitStintzr(Op.Rd); + + Context.Emit(OpCodes.Br_S, LblEnd); + + Context.MarkLabel(LblTrue); + + Context.EmitLdintzr(Op.Rn); + Context.EmitStintzr(Op.Rd); + + Context.MarkLabel(LblEnd); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/AInstEmitException.cs b/Ryujinx/Cpu/Instruction/AInstEmitException.cs new file mode 100644 index 000000000..6e5665fbc --- /dev/null +++ b/Ryujinx/Cpu/Instruction/AInstEmitException.cs @@ -0,0 +1,33 @@ +using ChocolArm64.Decoder; +using ChocolArm64.State; +using ChocolArm64.Translation; +using System; + +namespace ChocolArm64.Instruction +{ + static partial class AInstEmit + { + public static void Svc(AILEmitterCtx Context) + { + AOpCodeException Op = (AOpCodeException)Context.CurrOp; + + Context.EmitStoreState(); + + Context.EmitLdarg(ATranslatedSub.RegistersArgIdx); + + Context.EmitLdc_I4(Op.Id); + + Context.EmitCall(typeof(ARegisters), nameof(ARegisters.OnSvcCall)); + + if (Context.CurrBlock.Next != null) + { + Context.EmitLoadState(Context.CurrBlock.Next); + } + } + + public static void Und(AILEmitterCtx Context) + { + throw new Exception("und inst! " + Context.CurrOp.Position.ToString("x8")); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/AInstEmitFlow.cs b/Ryujinx/Cpu/Instruction/AInstEmitFlow.cs new file mode 100644 index 000000000..6fa19c9fc --- /dev/null +++ b/Ryujinx/Cpu/Instruction/AInstEmitFlow.cs @@ -0,0 +1,124 @@ +using ChocolArm64.Decoder; +using ChocolArm64.State; +using ChocolArm64.Translation; +using System.Reflection.Emit; + +namespace ChocolArm64.Instruction +{ + static partial class AInstEmit + { + public static void B(AILEmitterCtx Context) + { + AOpCodeBImmAl Op = (AOpCodeBImmAl)Context.CurrOp; + + Context.Emit(OpCodes.Br, Context.GetLabel(Op.Imm)); + } + + public static void B_Cond(AILEmitterCtx Context) + { + AOpCodeBImmCond Op = (AOpCodeBImmCond)Context.CurrOp; + + Context.EmitCondBranch(Context.GetLabel(Op.Imm), Op.Cond); + } + + public static void Bl(AILEmitterCtx Context) + { + AOpCodeBImmAl Op = (AOpCodeBImmAl)Context.CurrOp; + + Context.EmitLdc_I(Op.Position + 4); + Context.EmitStint(ARegisters.LRIndex); + Context.EmitStoreState(); + + if (Context.TryOptEmitSubroutineCall()) + { + //Note: the return value of the called method will be placed + //at the Stack, the return value is always a Int64 with the + //return address of the function. We check if the address is + //correct, if it isn't we keep returning until we reach the dispatcher. + Context.Emit(OpCodes.Dup); + + Context.EmitLdc_I8(Op.Position + 4); + + AILLabel LblContinue = new AILLabel(); + + Context.Emit(OpCodes.Beq_S, LblContinue); + Context.Emit(OpCodes.Ret); + + Context.MarkLabel(LblContinue); + + Context.Emit(OpCodes.Pop); + + if (Context.CurrBlock.Next != null) + { + Context.EmitLoadState(Context.CurrBlock.Next); + } + } + else + { + Context.EmitLdc_I8(Op.Imm); + + Context.Emit(OpCodes.Ret); + } + } + + public static void Blr(AILEmitterCtx Context) + { + AOpCodeBReg Op = (AOpCodeBReg)Context.CurrOp; + + Context.EmitLdc_I(Op.Position + 4); + Context.EmitStint(ARegisters.LRIndex); + Context.EmitStoreState(); + Context.EmitLdintzr(Op.Rn); + + Context.Emit(OpCodes.Ret); + } + + public static void Br(AILEmitterCtx Context) + { + AOpCodeBReg Op = (AOpCodeBReg)Context.CurrOp; + + Context.EmitStoreState(); + Context.EmitLdintzr(Op.Rn); + + Context.Emit(OpCodes.Ret); + } + + public static void Cbnz(AILEmitterCtx Context) => EmitCb(Context, OpCodes.Bne_Un); + public static void Cbz(AILEmitterCtx Context) => EmitCb(Context, OpCodes.Beq); + + private static void EmitCb(AILEmitterCtx Context, OpCode ILOp) + { + AOpCodeBImmCmp Op = (AOpCodeBImmCmp)Context.CurrOp; + + Context.EmitLdintzr(Op.Rt); + Context.EmitLdc_I(0); + + Context.Emit(ILOp, Context.GetLabel(Op.Imm)); + } + + public static void Ret(AILEmitterCtx Context) + { + Context.EmitStoreState(); + Context.EmitLdint(ARegisters.LRIndex); + + Context.Emit(OpCodes.Ret); + } + + public static void Tbnz(AILEmitterCtx Context) => EmitTb(Context, OpCodes.Bne_Un); + public static void Tbz(AILEmitterCtx Context) => EmitTb(Context, OpCodes.Beq); + + private static void EmitTb(AILEmitterCtx Context, OpCode ILOp) + { + AOpCodeBImmTest Op = (AOpCodeBImmTest)Context.CurrOp; + + Context.EmitLdintzr(Op.Rt); + Context.EmitLdc_I(1L << Op.Pos); + + Context.Emit(OpCodes.And); + + Context.EmitLdc_I(0); + + Context.Emit(ILOp, Context.GetLabel(Op.Imm)); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/AInstEmitMemory.cs b/Ryujinx/Cpu/Instruction/AInstEmitMemory.cs new file mode 100644 index 000000000..ca0c82a37 --- /dev/null +++ b/Ryujinx/Cpu/Instruction/AInstEmitMemory.cs @@ -0,0 +1,252 @@ +using ChocolArm64.Decoder; +using ChocolArm64.Translation; +using System.Reflection.Emit; + +using static ChocolArm64.Instruction.AInstEmitMemoryHelper; + +namespace ChocolArm64.Instruction +{ + static partial class AInstEmit + { + public static void Adr(AILEmitterCtx Context) + { + AOpCodeAdr Op = (AOpCodeAdr)Context.CurrOp; + + Context.EmitLdc_I(Op.Position + Op.Imm); + Context.EmitStintzr(Op.Rd); + } + + public static void Adrp(AILEmitterCtx Context) + { + AOpCodeAdr Op = (AOpCodeAdr)Context.CurrOp; + + Context.EmitLdc_I((Op.Position & ~0xfff) + (Op.Imm << 12)); + Context.EmitStintzr(Op.Rd); + } + + public static void Ldr(AILEmitterCtx Context) => EmitLdr(Context, false); + public static void Ldrs(AILEmitterCtx Context) => EmitLdr(Context, true); + + public static void EmitLdr(AILEmitterCtx Context, bool Signed) + { + AOpCodeMem Op = (AOpCodeMem)Context.CurrOp; + + Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); + + EmitLoadAddress(Context); + + if (Signed && Op.Extend64) + { + EmitReadSx64Call(Context, Op.Size); + } + else if (Signed) + { + EmitReadSx32Call(Context, Op.Size); + } + else + { + EmitReadZxCall(Context, Op.Size); + } + + if (Op is IAOpCodeSimd) + { + Context.EmitStvec(Op.Rt); + } + else + { + Context.EmitStintzr(Op.Rt); + } + + EmitWBackIfNeeded(Context); + } + + public static void LdrLit(AILEmitterCtx Context) + { + IAOpCodeLit Op = (IAOpCodeLit)Context.CurrOp; + + if (Op.Prefetch) + { + return; + } + + Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); + Context.EmitLdc_I8(Op.Imm); + + if (Op.Signed) + { + EmitReadSx64Call(Context, Op.Size); + } + else + { + EmitReadZxCall(Context, Op.Size); + } + + if (Op is IAOpCodeSimd) + { + Context.EmitStvec(Op.Rt); + } + else + { + Context.EmitStint(Op.Rt); + } + } + + public static void Ldp(AILEmitterCtx Context) + { + AOpCodeMemPair Op = (AOpCodeMemPair)Context.CurrOp; + + void EmitReadAndStore(int Rt) + { + if (Op.Extend64) + { + EmitReadSx64Call(Context, Op.Size); + } + else + { + EmitReadZxCall(Context, Op.Size); + } + + if (Op is IAOpCodeSimd) + { + Context.EmitStvec(Rt); + } + else + { + Context.EmitStintzr(Rt); + } + } + + Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); + + EmitLoadAddress(Context); + + EmitReadAndStore(Op.Rt); + + Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); + Context.EmitLdtmp(); + Context.EmitLdc_I8(1 << Op.Size); + + Context.Emit(OpCodes.Add); + + EmitReadAndStore(Op.Rt2); + + EmitWBackIfNeeded(Context); + } + + public static void Str(AILEmitterCtx Context) + { + AOpCodeMem Op = (AOpCodeMem)Context.CurrOp; + + Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); + + EmitLoadAddress(Context); + + if (Op is IAOpCodeSimd) + { + Context.EmitLdvec(Op.Rt); + } + else + { + Context.EmitLdintzr(Op.Rt); + } + + EmitWriteCall(Context, Op.Size); + + EmitWBackIfNeeded(Context); + } + + public static void Stp(AILEmitterCtx Context) + { + AOpCodeMemPair Op = (AOpCodeMemPair)Context.CurrOp; + + Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); + + EmitLoadAddress(Context); + + if (Op is IAOpCodeSimd) + { + Context.EmitLdvec(Op.Rt); + } + else + { + Context.EmitLdintzr(Op.Rt); + } + + EmitWriteCall(Context, Op.Size); + + Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); + Context.EmitLdtmp(); + Context.EmitLdc_I8(1 << Op.Size); + + Context.Emit(OpCodes.Add); + + if (Op is IAOpCodeSimd) + { + Context.EmitLdvec(Op.Rt2); + } + else + { + Context.EmitLdintzr(Op.Rt2); + } + + EmitWriteCall(Context, Op.Size); + + EmitWBackIfNeeded(Context); + } + + private static void EmitLoadAddress(AILEmitterCtx Context) + { + switch (Context.CurrOp) + { + case AOpCodeMemImm Op: + Context.EmitLdint(Op.Rn); + + if (!Op.PostIdx) + { + //Pre-indexing. + Context.EmitLdc_I(Op.Imm); + + Context.Emit(OpCodes.Add); + } + break; + + case AOpCodeMemReg Op: + Context.EmitLdint(Op.Rn); + Context.EmitLdintzr(Op.Rm); + Context.EmitCast(Op.IntType); + + if (Op.Shift) + { + Context.EmitLsl(Op.Size); + } + + Context.Emit(OpCodes.Add); + break; + } + + //Save address to Scratch var since the register value may change. + Context.Emit(OpCodes.Dup); + + Context.EmitSttmp(); + } + + private static void EmitWBackIfNeeded(AILEmitterCtx Context) + { + //Check whenever the current OpCode has post-indexed write back, if so write it. + //Note: AOpCodeMemPair inherits from AOpCodeMemImm, so this works for both. + if (Context.CurrOp is AOpCodeMemImm Op && Op.WBack) + { + Context.EmitLdtmp(); + + if (Op.PostIdx) + { + Context.EmitLdc_I(Op.Imm); + + Context.Emit(OpCodes.Add); + } + + Context.EmitStint(Op.Rn); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/AInstEmitMemoryEx.cs b/Ryujinx/Cpu/Instruction/AInstEmitMemoryEx.cs new file mode 100644 index 000000000..ebb6e9328 --- /dev/null +++ b/Ryujinx/Cpu/Instruction/AInstEmitMemoryEx.cs @@ -0,0 +1,180 @@ +using ChocolArm64.Decoder; +using ChocolArm64.Memory; +using ChocolArm64.Translation; +using System; +using System.Reflection.Emit; +using System.Threading; + +using static ChocolArm64.Instruction.AInstEmitMemoryHelper; + +namespace ChocolArm64.Instruction +{ + static partial class AInstEmit + { + [Flags] + private enum AccessType + { + None = 0, + Ordered = 1, + Exclusive = 2, + OrderedEx = Ordered | Exclusive + } + + public static void Clrex(AILEmitterCtx Context) + { + EmitMemoryCall(Context, nameof(AMemory.ClearExclusive)); + } + + public static void Dmb(AILEmitterCtx Context) => EmitBarrier(Context); + public static void Dsb(AILEmitterCtx Context) => EmitBarrier(Context); + + public static void Ldar(AILEmitterCtx Context) => EmitLdr(Context, AccessType.Ordered); + public static void Ldaxr(AILEmitterCtx Context) => EmitLdr(Context, AccessType.OrderedEx); + public static void Ldxr(AILEmitterCtx Context) => EmitLdr(Context, AccessType.Exclusive); + public static void Ldxp(AILEmitterCtx Context) => EmitLdp(Context, AccessType.Exclusive); + public static void Ldaxp(AILEmitterCtx Context) => EmitLdp(Context, AccessType.OrderedEx); + + private static void EmitLdr(AILEmitterCtx Context, AccessType AccType) + { + EmitLoad(Context, AccType, false); + } + + private static void EmitLdp(AILEmitterCtx Context, AccessType AccType) + { + EmitLoad(Context, AccType, true); + } + + private static void EmitLoad(AILEmitterCtx Context, AccessType AccType, bool Pair) + { + AOpCodeMemEx Op = (AOpCodeMemEx)Context.CurrOp; + + Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); + Context.EmitLdint(Op.Rn); + + EmitReadZxCall(Context, Op.Size); + + Context.EmitStintzr(Op.Rt); + + if (Pair) + { + Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); + Context.EmitLdint(Op.Rn); + Context.EmitLdc_I(8 << Op.Size); + + Context.Emit(OpCodes.Add); + + EmitReadZxCall(Context, Op.Size); + + Context.EmitStintzr(Op.Rt2); + } + + if (AccType.HasFlag(AccessType.Exclusive)) + { + EmitMemoryCall(Context, nameof(AMemory.SetExclusive), Op.Rn); + } + + if (AccType.HasFlag(AccessType.Ordered)) + { + EmitBarrier(Context); + } + } + + public static void Pfrm(AILEmitterCtx Context) + { + //Memory Prefetch, execute as no-op. + } + + public static void Stlr(AILEmitterCtx Context) => EmitStr(Context, AccessType.Ordered); + public static void Stlxr(AILEmitterCtx Context) => EmitStr(Context, AccessType.OrderedEx); + public static void Stxr(AILEmitterCtx Context) => EmitStr(Context, AccessType.Exclusive); + public static void Stxp(AILEmitterCtx Context) => EmitStp(Context, AccessType.Exclusive); + public static void Stlxp(AILEmitterCtx Context) => EmitStp(Context, AccessType.OrderedEx); + + private static void EmitStr(AILEmitterCtx Context, AccessType AccType) + { + EmitStore(Context, AccType, false); + } + + private static void EmitStp(AILEmitterCtx Context, AccessType AccType) + { + EmitStore(Context, AccType, true); + } + + private static void EmitStore(AILEmitterCtx Context, AccessType AccType, bool Pair) + { + AOpCodeMemEx Op = (AOpCodeMemEx)Context.CurrOp; + + if (AccType.HasFlag(AccessType.Ordered)) + { + EmitBarrier(Context); + } + + AILLabel LblEx = new AILLabel(); + AILLabel LblEnd = new AILLabel(); + + if (AccType.HasFlag(AccessType.Exclusive)) + { + EmitMemoryCall(Context, nameof(AMemory.TestExclusive), Op.Rn); + + Context.Emit(OpCodes.Brtrue_S, LblEx); + + Context.EmitLdc_I8(1); + Context.EmitStintzr(Op.Rs); + + Context.Emit(OpCodes.Br_S, LblEnd); + } + + Context.MarkLabel(LblEx); + + Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); + Context.EmitLdint(Op.Rn); + Context.EmitLdintzr(Op.Rt); + + EmitWriteCall(Context, Op.Size); + + if (Pair) + { + Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); + Context.EmitLdint(Op.Rn); + Context.EmitLdc_I(8 << Op.Size); + + Context.Emit(OpCodes.Add); + + Context.EmitLdintzr(Op.Rt2); + + EmitWriteCall(Context, Op.Size); + } + + if (AccType.HasFlag(AccessType.Exclusive)) + { + Context.EmitLdc_I8(0); + Context.EmitStintzr(Op.Rs); + + Clrex(Context); + } + + Context.MarkLabel(LblEnd); + } + + private static void EmitMemoryCall(AILEmitterCtx Context, string Name, int Rn = -1) + { + Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); + Context.EmitLdarg(ATranslatedSub.RegistersArgIdx); + + if (Rn != -1) + { + Context.EmitLdint(Rn); + } + + Context.EmitCall(typeof(AMemory), Name); + } + + private static void EmitBarrier(AILEmitterCtx Context) + { + //Note: This barrier is most likely not necessary, and probably + //doesn't make any difference since we need to do a ton of stuff + //(software MMU emulation) to read or write anything anyway. + Context.EmitCall(typeof(Thread), nameof(Thread.MemoryBarrier)); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/AInstEmitMemoryHelper.cs b/Ryujinx/Cpu/Instruction/AInstEmitMemoryHelper.cs new file mode 100644 index 000000000..e05153d99 --- /dev/null +++ b/Ryujinx/Cpu/Instruction/AInstEmitMemoryHelper.cs @@ -0,0 +1,97 @@ +using ChocolArm64.Memory; +using ChocolArm64.Translation; +using System; +using System.Reflection.Emit; + +namespace ChocolArm64.Instruction +{ + static class AInstEmitMemoryHelper + { + private enum Extension + { + Zx, + Sx32, + Sx64 + } + + public static void EmitReadZxCall(AILEmitterCtx Context, int Size) + { + EmitReadCall(Context, Extension.Zx, Size); + } + + public static void EmitReadSx32Call(AILEmitterCtx Context, int Size) + { + EmitReadCall(Context, Extension.Sx32, Size); + } + + public static void EmitReadSx64Call(AILEmitterCtx Context, int Size) + { + EmitReadCall(Context, Extension.Sx64, Size); + } + + private static void EmitReadCall(AILEmitterCtx Context, Extension Ext, int Size) + { + if (Size < 0 || Size > 4) + { + throw new ArgumentOutOfRangeException(nameof(Size)); + } + + string Name = null; + + switch (Size) + { + case 0: Name = nameof(AMemory.ReadByte); break; + case 1: Name = nameof(AMemory.ReadUInt16); break; + case 2: Name = nameof(AMemory.ReadUInt32); break; + case 3: Name = nameof(AMemory.ReadUInt64); break; + case 4: Name = nameof(AMemory.ReadVector128); break; + } + + Context.EmitCall(typeof(AMemory), Name); + + if (Ext == Extension.Sx32 || + Ext == Extension.Sx64) + { + switch (Size) + { + case 0: Context.Emit(OpCodes.Conv_I1); break; + case 1: Context.Emit(OpCodes.Conv_I2); break; + case 2: Context.Emit(OpCodes.Conv_I4); break; + } + } + + if (Size < 3) + { + Context.Emit(Ext == Extension.Sx64 + ? OpCodes.Conv_I8 + : OpCodes.Conv_U8); + } + } + + public static void EmitWriteCall(AILEmitterCtx Context, int Size) + { + if (Size < 0 || Size > 4) + { + throw new ArgumentOutOfRangeException(nameof(Size)); + } + + if (Size < 3) + { + Context.Emit(OpCodes.Conv_I4); + } + + string Name = null; + + switch (Size) + { + case 0: Name = nameof(AMemory.WriteByte); break; + case 1: Name = nameof(AMemory.WriteUInt16); break; + case 2: Name = nameof(AMemory.WriteUInt32); break; + case 3: Name = nameof(AMemory.WriteUInt64); break; + case 4: Name = nameof(AMemory.WriteVector128); break; + } + + Context.EmitCall(typeof(AMemory), Name); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/AInstEmitMove.cs b/Ryujinx/Cpu/Instruction/AInstEmitMove.cs new file mode 100644 index 000000000..719b53d5d --- /dev/null +++ b/Ryujinx/Cpu/Instruction/AInstEmitMove.cs @@ -0,0 +1,41 @@ +using ChocolArm64.Decoder; +using ChocolArm64.Translation; +using System.Reflection.Emit; + +namespace ChocolArm64.Instruction +{ + static partial class AInstEmit + { + public static void Movk(AILEmitterCtx Context) + { + AOpCodeMov Op = (AOpCodeMov)Context.CurrOp; + + Context.EmitLdintzr(Op.Rd); + Context.EmitLdc_I(~(0xffffL << Op.Pos)); + + Context.Emit(OpCodes.And); + + Context.EmitLdc_I(Op.Imm); + + Context.Emit(OpCodes.Or); + + Context.EmitStintzr(Op.Rd); + } + + public static void Movn(AILEmitterCtx Context) + { + AOpCodeMov Op = (AOpCodeMov)Context.CurrOp; + + Context.EmitLdc_I(~Op.Imm); + Context.EmitStintzr(Op.Rd); + } + + public static void Movz(AILEmitterCtx Context) + { + AOpCodeMov Op = (AOpCodeMov)Context.CurrOp; + + Context.EmitLdc_I(Op.Imm); + Context.EmitStintzr(Op.Rd); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/AInstEmitMul.cs b/Ryujinx/Cpu/Instruction/AInstEmitMul.cs new file mode 100644 index 000000000..3713c81f6 --- /dev/null +++ b/Ryujinx/Cpu/Instruction/AInstEmitMul.cs @@ -0,0 +1,80 @@ +using ChocolArm64.Decoder; +using ChocolArm64.Translation; +using System.Reflection.Emit; + +namespace ChocolArm64.Instruction +{ + static partial class AInstEmit + { + public static void Madd(AILEmitterCtx Context) => EmitMul(Context, OpCodes.Add); + public static void Msub(AILEmitterCtx Context) => EmitMul(Context, OpCodes.Sub); + + private static void EmitMul(AILEmitterCtx Context, OpCode ILOp) + { + AOpCodeMul Op = (AOpCodeMul)Context.CurrOp; + + Context.EmitLdintzr(Op.Ra); + Context.EmitLdintzr(Op.Rn); + Context.EmitLdintzr(Op.Rm); + + Context.Emit(OpCodes.Mul); + Context.Emit(ILOp); + + Context.EmitStintzr(Op.Rd); + } + + public static void Smaddl(AILEmitterCtx Context) => EmitMull(Context, OpCodes.Add, true); + public static void Smsubl(AILEmitterCtx Context) => EmitMull(Context, OpCodes.Sub, true); + public static void Umaddl(AILEmitterCtx Context) => EmitMull(Context, OpCodes.Add, false); + public static void Umsubl(AILEmitterCtx Context) => EmitMull(Context, OpCodes.Sub, false); + + private static void EmitMull(AILEmitterCtx Context, OpCode AddSubOp, bool Signed) + { + AOpCodeMul Op = (AOpCodeMul)Context.CurrOp; + + OpCode CastOp = Signed + ? OpCodes.Conv_I8 + : OpCodes.Conv_U8; + + Context.EmitLdintzr(Op.Ra); + Context.EmitLdintzr(Op.Rn); + + Context.Emit(OpCodes.Conv_I4); + Context.Emit(CastOp); + + Context.EmitLdintzr(Op.Rm); + + Context.Emit(OpCodes.Conv_I4); + Context.Emit(CastOp); + Context.Emit(OpCodes.Mul); + + Context.Emit(AddSubOp); + + Context.EmitStintzr(Op.Rd); + } + + public static void Smulh(AILEmitterCtx Context) + { + AOpCodeMul Op = (AOpCodeMul)Context.CurrOp; + + Context.EmitLdintzr(Op.Rn); + Context.EmitLdintzr(Op.Rm); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SMulHi128)); + + Context.EmitStintzr(Op.Rd); + } + + public static void Umulh(AILEmitterCtx Context) + { + AOpCodeMul Op = (AOpCodeMul)Context.CurrOp; + + Context.EmitLdintzr(Op.Rn); + Context.EmitLdintzr(Op.Rm); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.UMulHi128)); + + Context.EmitStintzr(Op.Rd); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/AInstEmitScalar.cs b/Ryujinx/Cpu/Instruction/AInstEmitScalar.cs new file mode 100644 index 000000000..fcddb0c98 --- /dev/null +++ b/Ryujinx/Cpu/Instruction/AInstEmitScalar.cs @@ -0,0 +1,659 @@ +using ChocolArm64.Decoder; +using ChocolArm64.State; +using ChocolArm64.Translation; +using System; +using System.Reflection; +using System.Reflection.Emit; + +namespace ChocolArm64.Instruction +{ + static partial class AInstEmit + { + public static void Addp_S(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Addp_S)); + + Context.EmitStvec(Op.Rd); + } + + public static void Dup_S(AILEmitterCtx Context) + { + AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdc_I4(Op.DstIndex); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Dup_S)); + + Context.EmitStvec(Op.Rd); + } + + public static void Fabs_S(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvecsf(Op.Rn); + + MethodInfo MthdInfo; + + if (Op.Size == 0) + { + MthdInfo = typeof(MathF).GetMethod(nameof(MathF.Abs), new Type[] { typeof(float) }); + } + else if (Op.Size == 1) + { + MthdInfo = typeof(Math).GetMethod(nameof(Math.Abs), new Type[] { typeof(double) }); + } + else + { + throw new InvalidOperationException(); + } + + Context.EmitCall(MthdInfo); + + Context.EmitStvecsf(Op.Rd); + } + + public static void Fadd_S(AILEmitterCtx Context) => EmitScalarOp(Context, OpCodes.Add); + + public static void Fccmp_S(AILEmitterCtx Context) + { + AOpCodeSimdFcond Op = (AOpCodeSimdFcond)Context.CurrOp; + + AILLabel LblTrue = new AILLabel(); + AILLabel LblEnd = new AILLabel(); + + Context.EmitCondBranch(LblTrue, Op.Cond); + + //TODO: Share this logic with Ccmp. + Context.EmitLdc_I4((Op.NZCV >> 0) & 1); + + Context.EmitStflg((int)APState.VBit); + + Context.EmitLdc_I4((Op.NZCV >> 1) & 1); + + Context.EmitStflg((int)APState.CBit); + + Context.EmitLdc_I4((Op.NZCV >> 2) & 1); + + Context.EmitStflg((int)APState.ZBit); + + Context.EmitLdc_I4((Op.NZCV >> 3) & 1); + + Context.EmitStflg((int)APState.NBit); + + Context.Emit(OpCodes.Br_S, LblEnd); + + Context.MarkLabel(LblTrue); + + Fcmp_S(Context); + + Context.MarkLabel(LblEnd); + } + + public static void Fcmp_S(AILEmitterCtx Context) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + bool CmpWithZero = !(Op is AOpCodeSimdFcond) ? Op.Bit3 : false; + + //todo + //Context.TryMarkCondWithoutCmp(); + + void EmitLoadOpers() + { + Context.EmitLdvecsf(Op.Rn); + + if (CmpWithZero) + { + EmitLdcImmF(Context, 0, Op.Size); + } + else + { + Context.EmitLdvecsf(Op.Rm); + } + } + + //Z = Rn == Rm + EmitLoadOpers(); + + Context.Emit(OpCodes.Ceq); + Context.Emit(OpCodes.Dup); + + Context.EmitStflg((int)APState.ZBit); + + //C = Rn >= Rm + EmitLoadOpers(); + + Context.Emit(OpCodes.Cgt); + Context.Emit(OpCodes.Or); + + Context.EmitStflg((int)APState.CBit); + + //N = Rn < Rm + EmitLoadOpers(); + + Context.Emit(OpCodes.Clt); + + Context.EmitStflg((int)APState.NBit); + + //Handle NaN case. If any number is NaN, then NZCV = 0011. + AILLabel LblNotNaN = new AILLabel(); + + if (CmpWithZero) + { + EmitNaNCheck(Context, Op.Rn); + } + else + { + EmitNaNCheck(Context, Op.Rn); + EmitNaNCheck(Context, Op.Rm); + + Context.Emit(OpCodes.Or); + } + + Context.Emit(OpCodes.Brfalse_S, LblNotNaN); + + Context.EmitLdc_I4(1); + Context.EmitLdc_I4(1); + + Context.EmitStflg((int)APState.CBit); + Context.EmitStflg((int)APState.VBit); + + Context.MarkLabel(LblNotNaN); + } + + public static void Fcsel_S(AILEmitterCtx Context) + { + AOpCodeSimdFcond Op = (AOpCodeSimdFcond)Context.CurrOp; + + AILLabel LblTrue = new AILLabel(); + AILLabel LblEnd = new AILLabel(); + + Context.EmitCondBranch(LblTrue, Op.Cond); + Context.EmitLdvecsf(Op.Rm); + Context.EmitStvecsf(Op.Rd); + + Context.Emit(OpCodes.Br_S, LblEnd); + + Context.MarkLabel(LblTrue); + + Context.EmitLdvecsf(Op.Rn); + Context.EmitStvecsf(Op.Rd); + + Context.MarkLabel(LblEnd); + } + + public static void Fcvt_S(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvecsf(Op.Rn); + + EmitFloatCast(Context, Op.Opc); + + Context.EmitStvecsf(Op.Rd); + } + + public static void Fcvtms_S(AILEmitterCtx Context) => EmitMathOpCvtToInt(Context, nameof(Math.Floor)); + public static void Fcvtps_S(AILEmitterCtx Context) => EmitMathOpCvtToInt(Context, nameof(Math.Ceiling)); + + public static void Fcvtzs_S(AILEmitterCtx Context) => EmitFcvtz_(Context, true); + public static void Fcvtzu_S(AILEmitterCtx Context) => EmitFcvtz_(Context, false); + + private static void EmitFcvtz_(AILEmitterCtx Context, bool Signed) + { + AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp; + + Context.EmitLdvecsf(Op.Rn); + + if (Signed) + { + EmitCvtToInt(Context, Op.Size); + } + else + { + EmitCvtToUInt(Context, Op.Size); + } + + Context.EmitStintzr(Op.Rd); + } + + public static void Fcvtzs_Fix(AILEmitterCtx Context) => EmitFcvtz__Fix(Context, true); + public static void Fcvtzu_Fix(AILEmitterCtx Context) => EmitFcvtz__Fix(Context, false); + + private static void EmitFcvtz__Fix(AILEmitterCtx Context, bool Signed) + { + AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp; + + Context.EmitLdvecsf(Op.Rn); + + EmitLdcImmF(Context, 1L << Op.FBits, Op.Size); + + Context.Emit(OpCodes.Mul); + + if (Signed) + { + EmitCvtToInt(Context, Op.Size); + } + else + { + EmitCvtToUInt(Context, Op.Size); + } + + Context.EmitStintzr(Op.Rd); + } + + public static void Fdiv_S(AILEmitterCtx Context) => EmitScalarOp(Context, OpCodes.Div); + + public static void Fmax_S(AILEmitterCtx Context) => EmitMathOp3(Context, nameof(Math.Max)); + public static void Fmin_S(AILEmitterCtx Context) => EmitMathOp3(Context, nameof(Math.Min)); + + public static void Fmaxnm_S(AILEmitterCtx Context) => EmitMathOp3(Context, nameof(Math.Max)); + public static void Fminnm_S(AILEmitterCtx Context) => EmitMathOp3(Context, nameof(Math.Min)); + + public static void Fmov_S(AILEmitterCtx Context) + { + AOpCodeSimdFmov Op = (AOpCodeSimdFmov)Context.CurrOp; + + Context.EmitLdc_I8(Op.Imm); + Context.EmitLdc_I4(0); + Context.EmitLdc_I4(Op.Size + 2); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Fmov_S)); + + Context.EmitStvec(Op.Rd); + } + + public static void Fmov_Ftoi(AILEmitterCtx Context) + { + AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp; + + Context.EmitLdvecsi(Op.Rn); + Context.EmitStintzr(Op.Rd); + } + + public static void Fmov_Itof(AILEmitterCtx Context) + { + AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp; + + Context.EmitLdintzr(Op.Rn); + Context.EmitStvecsi(Op.Rd); + } + + public static void Fmov_Ftoi1(AILEmitterCtx Context) + { + AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdc_I4(1); + Context.EmitLdc_I4(3); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.ExtractVec)); + + Context.EmitStintzr(Op.Rd); + } + + public static void Fmov_Itof1(AILEmitterCtx Context) + { + AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp; + + Context.EmitLdintzr(Op.Rn); + Context.EmitLdc_I4(1); + Context.EmitLdc_I4(3); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Fmov_S)); + + Context.EmitStvec(Op.Rd); + } + + public static void Fmul_S(AILEmitterCtx Context) => EmitScalarOp(Context, OpCodes.Mul); + + public static void Fneg_S(AILEmitterCtx Context) => EmitScalarOp(Context, OpCodes.Neg); + + public static void Fnmul_S(AILEmitterCtx Context) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + Context.EmitLdvecsf(Op.Rn); + Context.EmitLdvecsf(Op.Rm); + + Context.Emit(OpCodes.Mul); + Context.Emit(OpCodes.Neg); + + Context.EmitStvecsf(Op.Rd); + } + + public static void Frinta_S(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvecsf(Op.Rn); + Context.EmitLdc_I4((int)MidpointRounding.AwayFromZero); + + MethodInfo MthdInfo; + + if (Op.Size == 0) + { + Type[] Types = new Type[] { typeof(float), typeof(MidpointRounding) }; + + MthdInfo = typeof(MathF).GetMethod(nameof(MathF.Round), Types); + } + else if (Op.Size == 1) + { + Type[] Types = new Type[] { typeof(double), typeof(MidpointRounding) }; + + MthdInfo = typeof(Math).GetMethod(nameof(Math.Round), Types); + } + else + { + throw new InvalidOperationException(); + } + + Context.EmitCall(MthdInfo); + + Context.EmitStvecsf(Op.Rd); + } + + public static void Frintm_S(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvecsf(Op.Rn); + + MethodInfo MthdInfo; + + if (Op.Size == 0) + { + MthdInfo = typeof(MathF).GetMethod(nameof(MathF.Floor), new Type[] { typeof(float) }); + } + else if (Op.Size == 1) + { + MthdInfo = typeof(Math).GetMethod(nameof(Math.Floor), new Type[] { typeof(double) }); + } + else + { + throw new InvalidOperationException(); + } + + Context.EmitCall(MthdInfo); + + Context.EmitStvecsf(Op.Rd); + } + + public static void Fsqrt_S(AILEmitterCtx Context) => EmitMathOp2(Context, nameof(Math.Sqrt)); + + public static void Fsub_S(AILEmitterCtx Context) => EmitScalarOp(Context, OpCodes.Sub); + + public static void Scvtf_Gp(AILEmitterCtx Context) + { + AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp; + + Context.EmitLdintzr(Op.Rn); + + EmitFloatCast(Context, Op.Size); + + Context.EmitStvecsf(Op.Rd); + } + + public static void Scvtf_S(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvecsi(Op.Rn); + + EmitFloatCast(Context, Op.Size); + + Context.EmitStvecsf(Op.Rd); + } + + public static void Shl_S(AILEmitterCtx Context) + { + AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; + + Context.EmitLdvecsi(Op.Rn); + Context.EmitLdc_I4(Op.Imm - (8 << Op.Size)); + + Context.Emit(OpCodes.Shl); + + Context.EmitStvecsi(Op.Rd); + } + + public static void Sshr_S(AILEmitterCtx Context) + { + AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; + + Context.EmitLdvecsi(Op.Rn); + Context.EmitLdc_I4((8 << (Op.Size + 1)) - Op.Imm); + + Context.Emit(OpCodes.Shr); + + Context.EmitStvecsi(Op.Rd); + } + + public static void Sub_S(AILEmitterCtx Context) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + Context.EmitLdvecsi(Op.Rn); + Context.EmitLdvecsi(Op.Rm); + + Context.Emit(OpCodes.Sub); + + Context.EmitStvecsi(Op.Rd); + } + + public static void Ucvtf_Gp(AILEmitterCtx Context) + { + AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp; + + Context.EmitLdintzr(Op.Rn); + + Context.Emit(OpCodes.Conv_R_Un); + + EmitFloatCast(Context, Op.Size); + + Context.EmitStvecsf(Op.Rd); + } + + private static void EmitScalarOp(AILEmitterCtx Context, OpCode ILOp) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + Context.EmitLdvecsf(Op.Rn); + + //Negate and Not are the only unary operations supported on IL. + //"Not" doesn't work with floats, so we don't need to compare it. + if (ILOp != OpCodes.Neg) + { + Context.EmitLdvecsf(Op.Rm); + } + + Context.Emit(ILOp); + + Context.EmitStvecsf(Op.Rd); + } + + private static void EmitMathOp2(AILEmitterCtx Context, string Name) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvecsf(Op.Rn); + + EmitMathOpCall(Context, Name); + + Context.EmitStvecsf(Op.Rd); + } + + private static void EmitMathOp3(AILEmitterCtx Context, string Name) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + Context.EmitLdvecsf(Op.Rn); + Context.EmitLdvecsf(Op.Rm); + + EmitMathOpCall(Context, Name); + + Context.EmitStvecsf(Op.Rd); + } + + public static void EmitMathOpCvtToInt(AILEmitterCtx Context, string Name) + { + AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp; + + Context.EmitLdvecsf(Op.Rn); + + EmitMathOpCall(Context, Name); + + EmitCvtToInt(Context, Op.Size); + + Context.EmitStintzr(Op.Rd); + } + + private static void EmitMathOpCall(AILEmitterCtx Context, string Name) + { + IAOpCodeSimd Op = (IAOpCodeSimd)Context.CurrOp; + + MethodInfo MthdInfo; + + if (Op.Size == 0) + { + MthdInfo = typeof(MathF).GetMethod(Name); + } + else if (Op.Size == 1) + { + MthdInfo = typeof(Math).GetMethod(Name); + } + else + { + throw new InvalidOperationException(); + } + + Context.EmitCall(MthdInfo); + } + + private static void EmitCvtToInt(AILEmitterCtx Context, int Size) + { + if (Size < 0 || Size > 1) + { + throw new ArgumentOutOfRangeException(nameof(Size)); + } + + Context.EmitLdc_I4(0); + + if (Context.CurrOp.RegisterSize == ARegisterSize.Int32) + { + if (Size == 0) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SatSingleToInt32)); + } + else /* if (Size == 1) */ + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SatDoubleToInt32)); + } + } + else + { + if (Size == 0) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SatSingleToInt64)); + } + else /* if (Size == 1) */ + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SatDoubleToInt64)); + } + } + } + + private static void EmitCvtToUInt(AILEmitterCtx Context, int Size) + { + if (Size < 0 || Size > 1) + { + throw new ArgumentOutOfRangeException(nameof(Size)); + } + + Context.EmitLdc_I4(0); + + if (Context.CurrOp.RegisterSize == ARegisterSize.Int32) + { + if (Size == 0) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SatSingleToUInt32)); + } + else /* if (Size == 1) */ + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SatDoubleToUInt32)); + } + } + else + { + if (Size == 0) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SatSingleToUInt64)); + } + else /* if (Size == 1) */ + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SatDoubleToUInt64)); + } + } + } + + private static void EmitFloatCast(AILEmitterCtx Context, int Size) + { + if (Size == 0) + { + Context.Emit(OpCodes.Conv_R4); + } + else if (Size == 1) + { + Context.Emit(OpCodes.Conv_R8); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Size)); + } + } + + private static void EmitLdcImmF(AILEmitterCtx Context, double ImmF, int Size) + { + if (Size == 0) + { + Context.EmitLdc_R4((float)ImmF); + } + else if (Size == 1) + { + Context.EmitLdc_R8(ImmF); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Size)); + } + } + + private static void EmitNaNCheck(AILEmitterCtx Context, int Index) + { + IAOpCodeSimd Op = (IAOpCodeSimd)Context.CurrOp; + + Context.EmitLdvecsf(Index); + + if (Op.Size == 0) + { + Context.EmitCall(typeof(float), nameof(float.IsNaN)); + } + else if (Op.Size == 1) + { + Context.EmitCall(typeof(double), nameof(double.IsNaN)); + } + else + { + throw new InvalidOperationException(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/AInstEmitSimd.cs b/Ryujinx/Cpu/Instruction/AInstEmitSimd.cs new file mode 100644 index 000000000..0801716ad --- /dev/null +++ b/Ryujinx/Cpu/Instruction/AInstEmitSimd.cs @@ -0,0 +1,965 @@ +using ChocolArm64.Decoder; +using ChocolArm64.State; +using ChocolArm64.Translation; +using System; +using System.Reflection; +using System.Reflection.Emit; + +using static ChocolArm64.Instruction.AInstEmitMemoryHelper; + +namespace ChocolArm64.Instruction +{ + static partial class AInstEmit + { + public static void Add_V(AILEmitterCtx Context) => EmitVectorBinaryZx(Context, OpCodes.Add); + + public static void Addp_V(AILEmitterCtx Context) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdvec(Op.Rm); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Addp64), + nameof(ASoftFallback.Addp128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Addv_V(AILEmitterCtx Context) => EmitVectorAddv(Context); + + public static void And_V(AILEmitterCtx Context) => EmitVectorBinaryZx(Context, OpCodes.And); + + public static void Bic_V(AILEmitterCtx Context) => EmitVectorBic(Context); + public static void Bic_Vi(AILEmitterCtx Context) + { + AOpCodeSimdImm Op = (AOpCodeSimdImm)Context.CurrOp; + + Context.EmitLdvec(Op.Rd); + Context.EmitLdc_I8(Op.Imm); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Bic_Vi64), + nameof(ASoftFallback.Bic_Vi128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Bsl_V(AILEmitterCtx Context) => EmitVectorBsl(Context); + + public static void Cmeq_V(AILEmitterCtx Context) => EmitVectorCmp(Context, OpCodes.Beq_S); + public static void Cmge_V(AILEmitterCtx Context) => EmitVectorCmp(Context, OpCodes.Bge_S); + public static void Cmgt_V(AILEmitterCtx Context) => EmitVectorCmp(Context, OpCodes.Bgt_S); + public static void Cmhi_V(AILEmitterCtx Context) => EmitVectorCmp(Context, OpCodes.Bgt_Un_S); + public static void Cmhs_V(AILEmitterCtx Context) => EmitVectorCmp(Context, OpCodes.Bge_Un_S); + public static void Cmle_V(AILEmitterCtx Context) => EmitVectorCmp(Context, OpCodes.Ble_S); + public static void Cmlt_V(AILEmitterCtx Context) => EmitVectorCmp(Context, OpCodes.Blt_S); + + public static void Cnt_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Cnt64), + nameof(ASoftFallback.Cnt128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Dup_Gp(AILEmitterCtx Context) + { + AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp; + + Context.EmitLdintzr(Op.Rn); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Dup_Gp64), + nameof(ASoftFallback.Dup_Gp128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Dup_V(AILEmitterCtx Context) + { + AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdc_I4(Op.DstIndex); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Dup_V64), + nameof(ASoftFallback.Dup_V128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Eor_V(AILEmitterCtx Context) => EmitVectorBinaryZx(Context, OpCodes.Xor); + + public static void Fadd_V(AILEmitterCtx Context) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdvec(Op.Rm); + Context.EmitLdc_I4(Op.SizeF); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Fadd64), + nameof(ASoftFallback.Fadd128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Fcvtzs_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdc_I4(Op.SizeF); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Fcvtzs_V64), + nameof(ASoftFallback.Fcvtzs_V128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Fcvtzu_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdc_I4(0); + Context.EmitLdc_I4(Op.SizeF); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Fcvtzu_V_64), + nameof(ASoftFallback.Fcvtzu_V_128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Fcvtzu_V_Fix(AILEmitterCtx Context) + { + AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdc_I4((8 << (Op.Size + 1)) - Op.Imm); + Context.EmitLdc_I4(Op.Size - 2); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Fcvtzu_V_64), + nameof(ASoftFallback.Fcvtzu_V_128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Fmla_V(AILEmitterCtx Context) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + Context.EmitLdvec(Op.Rd); + Context.EmitLdvec(Op.Rn); + Context.EmitLdvec(Op.Rm); + Context.EmitLdc_I4(Op.SizeF); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Fmla64), + nameof(ASoftFallback.Fmla128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Fmla_Vs(AILEmitterCtx Context) + { + AOpCodeSimdRegElem Op = (AOpCodeSimdRegElem)Context.CurrOp; + + Context.EmitLdvec(Op.Rd); + Context.EmitLdvec(Op.Rn); + Context.EmitLdvec(Op.Rm); + Context.EmitLdc_I4(Op.Index); + Context.EmitLdc_I4(Op.SizeF); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Fmla_Ve64), + nameof(ASoftFallback.Fmla_Ve128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Fmov_V(AILEmitterCtx Context) + { + AOpCodeSimdImm Op = (AOpCodeSimdImm)Context.CurrOp; + + Context.EmitLdc_I8(Op.Imm); + Context.EmitLdc_I4(Op.Size + 2); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Dup_Gp64), + nameof(ASoftFallback.Dup_Gp128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Fmul_V(AILEmitterCtx Context) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdvec(Op.Rm); + Context.EmitLdc_I4(Op.SizeF); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Fmul64), + nameof(ASoftFallback.Fmul128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Fmul_Vs(AILEmitterCtx Context) + { + AOpCodeSimdRegElem Op = (AOpCodeSimdRegElem)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdvec(Op.Rm); + Context.EmitLdc_I4(Op.Index); + Context.EmitLdc_I4(Op.SizeF); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Fmul_Ve64), + nameof(ASoftFallback.Fmul_Ve128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Fsub_V(AILEmitterCtx Context) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdvec(Op.Rm); + Context.EmitLdc_I4(Op.SizeF); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Fsub64), + nameof(ASoftFallback.Fsub128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Ins_Gp(AILEmitterCtx Context) + { + AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp; + + Context.EmitLdvec(Op.Rd); + Context.EmitLdintzr(Op.Rn); + Context.EmitLdc_I4(Op.DstIndex); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Ins_Gp)); + + Context.EmitStvec(Op.Rd); + } + + public static void Ins_V(AILEmitterCtx Context) + { + AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp; + + Context.EmitLdvec(Op.Rd); + Context.EmitLdintzr(Op.Rn); + Context.EmitLdc_I4(Op.SrcIndex); + Context.EmitLdc_I4(Op.DstIndex); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Ins_V)); + + Context.EmitStvec(Op.Rd); + } + + public static void Ld__V(AILEmitterCtx Context) => EmitSimdMultLdSt(Context, IsLoad: true); + + public static void Mla_V(AILEmitterCtx Context) => EmitVectorMla(Context); + + public static void Movi_V(AILEmitterCtx Context) => EmitMovi_V(Context, false); + + public static void Mul_V(AILEmitterCtx Context) => EmitVectorBinaryZx(Context, OpCodes.Mul); + + public static void Mvni_V(AILEmitterCtx Context) => EmitMovi_V(Context, true); + + private static void EmitMovi_V(AILEmitterCtx Context, bool Not) + { + AOpCodeSimdImm Op = (AOpCodeSimdImm)Context.CurrOp; + + Context.EmitLdc_I8(Not ? ~Op.Imm : Op.Imm); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Dup_Gp64), + nameof(ASoftFallback.Dup_Gp128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Neg_V(AILEmitterCtx Context) => EmitVectorUnarySx(Context, OpCodes.Neg); + + public static void Not_V(AILEmitterCtx Context) => EmitVectorUnaryZx(Context, OpCodes.Not); + + public static void Orr_V(AILEmitterCtx Context) => EmitVectorBinaryZx(Context, OpCodes.Or); + + public static void Orr_Vi(AILEmitterCtx Context) + { + AOpCodeSimdImm Op = (AOpCodeSimdImm)Context.CurrOp; + + Context.EmitLdvec(Op.Rd); + Context.EmitLdc_I8(Op.Imm); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Orr_Vi64), + nameof(ASoftFallback.Orr_Vi128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Saddw_V(AILEmitterCtx Context) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdvec(Op.Rm); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Saddw), + nameof(ASoftFallback.Saddw2)); + + Context.EmitStvec(Op.Rd); + } + + public static void Scvtf_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdc_I4(Op.SizeF); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Scvtf_V64), + nameof(ASoftFallback.Scvtf_V128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Shl_V(AILEmitterCtx Context) + { + AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdc_I4(Op.Imm - (8 << Op.Size)); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Shl64), + nameof(ASoftFallback.Shl128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Smax_V(AILEmitterCtx Context) => EmitVectorSmax(Context); + public static void Smin_V(AILEmitterCtx Context) => EmitVectorSmin(Context); + + public static void Sshll_V(AILEmitterCtx Context) + { + AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdc_I4(Op.Imm - (8 << Op.Size)); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Sshll), + nameof(ASoftFallback.Sshll2)); + + Context.EmitStvec(Op.Rd); + } + + public static void Sshr_V(AILEmitterCtx Context) + { + AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdc_I4((8 << (Op.Size + 1)) - Op.Imm); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Sshr64), + nameof(ASoftFallback.Sshr128)); + + Context.EmitStvec(Op.Rd); + } + + public static void St__V(AILEmitterCtx Context) => EmitSimdMultLdSt(Context, IsLoad: false); + + public static void Sub_V(AILEmitterCtx Context) => EmitVectorBinaryZx(Context, OpCodes.Sub); + + public static void Tbl_V(AILEmitterCtx Context) + { + AOpCodeSimdTbl Op = (AOpCodeSimdTbl)Context.CurrOp; + + Context.EmitLdvec(Op.Rm); + + for (int Index = 0; Index < Op.Size; Index++) + { + Context.EmitLdvec((Op.Rn + Index) & 0x1f); + } + + switch (Op.Size) + { + case 1: ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Tbl1_V64), + nameof(ASoftFallback.Tbl1_V128)); break; + + case 2: ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Tbl2_V64), + nameof(ASoftFallback.Tbl2_V128)); break; + + case 3: ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Tbl3_V64), + nameof(ASoftFallback.Tbl3_V128)); break; + + case 4: ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Tbl4_V64), + nameof(ASoftFallback.Tbl4_V128)); break; + + default: throw new InvalidOperationException(); + } + + Context.EmitStvec(Op.Rd); + } + + public static void Uaddlv_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Uaddlv64), + nameof(ASoftFallback.Uaddlv128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Uaddw_V(AILEmitterCtx Context) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdvec(Op.Rm); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Uaddw), + nameof(ASoftFallback.Uaddw2)); + + Context.EmitStvec(Op.Rd); + } + + public static void Ucvtf_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + + if (Op.Size == 0) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Ucvtf_V_F)); + } + else if (Op.Size == 1) + { + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Ucvtf_V_D)); + } + else + { + throw new InvalidOperationException(); + } + + Context.EmitStvec(Op.Rd); + } + + public static void Umov_S(AILEmitterCtx Context) + { + AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdc_I4(Op.DstIndex); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.ExtractVec)); + + Context.EmitStintzr(Op.Rd); + } + + public static void Ushl_V(AILEmitterCtx Context) => EmitVectorUshl(Context); + + public static void Ushll_V(AILEmitterCtx Context) + { + AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdc_I4(Op.Imm - (8 << Op.Size)); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Ushll), + nameof(ASoftFallback.Ushll2)); + + Context.EmitStvec(Op.Rd); + } + + public static void Ushr_V(AILEmitterCtx Context) + { + AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdc_I4((8 << (Op.Size + 1)) - Op.Imm); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Ushr64), + nameof(ASoftFallback.Ushr128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Usra_V(AILEmitterCtx Context) + { + AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; + + Context.EmitLdvec(Op.Rd); + Context.EmitLdvec(Op.Rn); + Context.EmitLdc_I4((8 << (Op.Size + 1)) - Op.Imm); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Usra64), + nameof(ASoftFallback.Usra128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Uzp1_V(AILEmitterCtx Context) + { + AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdvec(Op.Rm); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Uzp1_V64), + nameof(ASoftFallback.Uzp1_V128)); + + Context.EmitStvec(Op.Rd); + } + + public static void Xtn_V(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvec(Op.Rn); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, + nameof(ASoftFallback.Xtn), + nameof(ASoftFallback.Xtn2)); + + Context.EmitStvec(Op.Rd); + } + + private static void EmitSimdMultLdSt(AILEmitterCtx Context, bool IsLoad) + { + AOpCodeSimdMemMult Op = (AOpCodeSimdMemMult)Context.CurrOp; + + int Offset = 0; + + for (int Rep = 0; Rep < Op.Reps; Rep++) + for (int Elem = 0; Elem < Op.Elems; Elem++) + for (int SElem = 0; SElem < Op.SElems; SElem++) + { + int Rtt = (Op.Rt + Rep + SElem) & 0x1f; + + if (IsLoad) + { + Context.EmitLdvec(Rtt); + Context.EmitLdc_I4(Elem); + Context.EmitLdc_I4(Op.Size); + Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); + Context.EmitLdint(Op.Rn); + Context.EmitLdc_I8(Offset); + + Context.Emit(OpCodes.Add); + + EmitReadZxCall(Context, Op.Size); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.InsertVec)); + + Context.EmitStvec(Rtt); + + if (Op.RegisterSize == ARegisterSize.SIMD64 && Elem == Op.Elems - 1) + { + EmitVectorZeroUpper(Context, Rtt); + } + } + else + { + Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); + Context.EmitLdint(Op.Rn); + Context.EmitLdc_I8(Offset); + + Context.Emit(OpCodes.Add); + + Context.EmitLdvec(Rtt); + Context.EmitLdc_I4(Elem); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.ExtractVec)); + + EmitWriteCall(Context, Op.Size); + } + + Offset += 1 << Op.Size; + } + + if (Op.WBack) + { + Context.EmitLdint(Op.Rn); + + if (Op.Rm != ARegisters.ZRIndex) + { + Context.EmitLdint(Op.Rm); + } + else + { + Context.EmitLdc_I8(Offset); + } + + Context.Emit(OpCodes.Add); + + Context.EmitStint(Op.Rn); + } + } + + private static void EmitVectorAddv(AILEmitterCtx Context) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + int Bytes = Context.CurrOp.GetBitsCount() >> 3; + + EmitVectorZeroLower(Context, Op.Rd); + EmitVectorZeroUpper(Context, Op.Rd); + + Context.EmitLdvec(Op.Rd); + Context.EmitLdc_I4(0); + Context.EmitLdc_I4(Op.Size); + + EmitVectorExtractZx(Context, Op.Rn, 0); + + for (int Index = 1; Index < (Bytes >> Op.Size); Index++) + { + EmitVectorExtractZx(Context, Op.Rn, Index); + + Context.Emit(OpCodes.Add); + } + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.InsertVec)); + + Context.EmitStvec(Op.Rd); + } + + private static void EmitVectorBic(AILEmitterCtx Context) + { + EmitVectorBinaryZx(Context, () => + { + Context.Emit(OpCodes.Not); + Context.Emit(OpCodes.And); + }); + } + + private static void EmitVectorBsl(AILEmitterCtx Context) + { + EmitVectorTernaryZx(Context, () => + { + Context.EmitSttmp(); + Context.EmitLdtmp(); + + Context.Emit(OpCodes.Xor); + Context.Emit(OpCodes.And); + + Context.EmitLdtmp(); + + Context.Emit(OpCodes.Xor); + }); + } + + private static void EmitVectorMla(AILEmitterCtx Context) + { + EmitVectorTernaryZx(Context, () => + { + Context.Emit(OpCodes.Mul); + Context.Emit(OpCodes.Add); + }); + } + + private static void EmitVectorSmax(AILEmitterCtx Context) + { + Type[] Types = new Type[] { typeof(long), typeof(long) }; + + MethodInfo MthdInfo = typeof(Math).GetMethod(nameof(Math.Max), Types); + + EmitVectorBinarySx(Context, () => Context.EmitCall(MthdInfo)); + } + + private static void EmitVectorSmin(AILEmitterCtx Context) + { + Type[] Types = new Type[] { typeof(long), typeof(long) }; + + MethodInfo MthdInfo = typeof(Math).GetMethod(nameof(Math.Min), Types); + + EmitVectorBinarySx(Context, () => Context.EmitCall(MthdInfo)); + } + + private static void EmitVectorUshl(AILEmitterCtx Context) + { + //This instruction shifts the value on vector A by the number of bits + //specified on the signed, lower 8 bits of vector B. If the shift value + //is greater or equal to the data size of each lane, then the result is zero. + //Additionally, negative shifts produces right shifts by the negated shift value. + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + int MaxShift = 8 << Op.Size; + + EmitVectorBinaryZx(Context, () => + { + AILLabel LblShl = new AILLabel(); + AILLabel LblZero = new AILLabel(); + AILLabel LblEnd = new AILLabel(); + + void EmitShift(OpCode ILOp) + { + Context.Emit(OpCodes.Dup); + + Context.EmitLdc_I4(MaxShift); + + Context.Emit(OpCodes.Bge_S, LblZero); + Context.Emit(ILOp); + Context.Emit(OpCodes.Br_S, LblEnd); + } + + Context.Emit(OpCodes.Conv_I1); + Context.Emit(OpCodes.Dup); + + Context.EmitLdc_I4(0); + + Context.Emit(OpCodes.Bge_S, LblShl); + Context.Emit(OpCodes.Neg); + + EmitShift(OpCodes.Shr_Un); + + Context.MarkLabel(LblShl); + + EmitShift(OpCodes.Shl); + + Context.MarkLabel(LblZero); + + Context.Emit(OpCodes.Pop); + Context.Emit(OpCodes.Pop); + + Context.EmitLdc_I8(0); + + Context.MarkLabel(LblEnd); + }); + } + + private static void EmitVectorUnarySx(AILEmitterCtx Context, OpCode ILOp) + { + EmitVectorUnarySx(Context, () => Context.Emit(ILOp)); + } + + private static void EmitVectorUnaryZx(AILEmitterCtx Context, OpCode ILOp) + { + EmitVectorUnaryZx(Context, () => Context.Emit(ILOp)); + } + + private static void EmitVectorBinaryZx(AILEmitterCtx Context, OpCode ILOp) + { + EmitVectorBinaryZx(Context, () => Context.Emit(ILOp)); + } + + private static void EmitVectorUnarySx(AILEmitterCtx Context, Action Emit) + { + EmitVectorOp(Context, Emit, 1, true); + } + + private static void EmitVectorBinarySx(AILEmitterCtx Context, Action Emit) + { + EmitVectorOp(Context, Emit, 2, true); + } + + private static void EmitVectorUnaryZx(AILEmitterCtx Context, Action Emit) + { + EmitVectorOp(Context, Emit, 1, false); + } + + private static void EmitVectorBinaryZx(AILEmitterCtx Context, Action Emit) + { + EmitVectorOp(Context, Emit, 2, false); + } + + private static void EmitVectorTernaryZx(AILEmitterCtx Context, Action Emit) + { + EmitVectorOp(Context, Emit, 3, false); + } + + private static void EmitVectorOp(AILEmitterCtx Context, Action Emit, int Opers, bool Signed) + { + if (Opers < 1 || Opers > 3) + { + throw new ArgumentOutOfRangeException(nameof(Opers)); + } + + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + int Bytes = Context.CurrOp.GetBitsCount() >> 3; + + for (int Index = 0; Index < (Bytes >> Op.Size); Index++) + { + Context.EmitLdvec(Op.Rd); + Context.EmitLdc_I4(Index); + Context.EmitLdc_I4(Op.Size); + + if (Opers == 3) + { + EmitVectorExtract(Context, Op.Rd, Index, Signed); + } + + if (Opers >= 1) + { + EmitVectorExtract(Context, Op.Rn, Index, Signed); + } + + if (Opers >= 2) + { + EmitVectorExtract(Context, ((AOpCodeSimdReg)Op).Rm, Index, Signed); + } + + Emit(); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.InsertVec)); + + Context.EmitStvec(Op.Rd); + } + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + + private static void EmitVectorCmp(AILEmitterCtx Context, OpCode ILOp) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + int Bytes = Context.CurrOp.GetBitsCount() >> 3; + + ulong SzMask = ulong.MaxValue >> (64 - (8 << Op.Size)); + + for (int Index = 0; Index < (Bytes >> Op.Size); Index++) + { + EmitVectorExtractSx(Context, Op.Rn, Index); + + if (Op is AOpCodeSimdReg BinOp) + { + EmitVectorExtractSx(Context, BinOp.Rm, Index); + } + else + { + Context.EmitLdc_I8(0); + } + + AILLabel LblTrue = new AILLabel(); + AILLabel LblEnd = new AILLabel(); + + Context.Emit(ILOp, LblTrue); + + EmitVectorInsert(Context, Op.Rd, Index, Op.Size, 0); + + Context.Emit(OpCodes.Br_S, LblEnd); + + Context.MarkLabel(LblTrue); + + EmitVectorInsert(Context, Op.Rd, Index, Op.Size, (long)SzMask); + + Context.MarkLabel(LblEnd); + } + + if (Op.RegisterSize == ARegisterSize.SIMD64) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + + private static void EmitVectorExtractSx(AILEmitterCtx Context, int Reg, int Index) + { + EmitVectorExtract(Context, Reg, Index, true); + } + + private static void EmitVectorExtractZx(AILEmitterCtx Context, int Reg, int Index) + { + EmitVectorExtract(Context, Reg, Index, false); + } + + private static void EmitVectorExtract(AILEmitterCtx Context, int Reg, int Index, bool Signed) + { + AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; + + Context.EmitLdvec(Reg); + Context.EmitLdc_I4(Index); + Context.EmitLdc_I4(Op.Size); + + ASoftFallback.EmitCall(Context, Signed + ? nameof(ASoftFallback.ExtractSVec) + : nameof(ASoftFallback.ExtractVec)); + } + + private static void EmitVectorZeroLower(AILEmitterCtx Context, int Rd) + { + EmitVectorInsert(Context, Rd, 0, 3, 0); + } + + private static void EmitVectorZeroUpper(AILEmitterCtx Context, int Rd) + { + EmitVectorInsert(Context, Rd, 1, 3, 0); + } + + private static void EmitVectorInsert(AILEmitterCtx Context, int Reg, int Index, int Size, long Value) + { + Context.EmitLdvec(Reg); + Context.EmitLdc_I4(Index); + Context.EmitLdc_I4(Size); + Context.EmitLdc_I8(Value); + + ASoftFallback.EmitCall(Context, nameof(ASoftFallback.InsertVec)); + + Context.EmitStvec(Reg); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/AInstEmitSystem.cs b/Ryujinx/Cpu/Instruction/AInstEmitSystem.cs new file mode 100644 index 000000000..23a0b6b2d --- /dev/null +++ b/Ryujinx/Cpu/Instruction/AInstEmitSystem.cs @@ -0,0 +1,84 @@ +using ChocolArm64.Decoder; +using ChocolArm64.State; +using ChocolArm64.Translation; +using System.Reflection.Emit; + +namespace ChocolArm64.Instruction +{ + static partial class AInstEmit + { + public static void Mrs(AILEmitterCtx Context) + { + AOpCodeSystem Op = (AOpCodeSystem)Context.CurrOp; + + Context.EmitLdarg(ATranslatedSub.RegistersArgIdx); + + Context.EmitLdc_I4(Op.Op0); + Context.EmitLdc_I4(Op.Op1); + Context.EmitLdc_I4(Op.CRn); + Context.EmitLdc_I4(Op.CRm); + Context.EmitLdc_I4(Op.Op2); + + Context.EmitCall(typeof(ARegisters), nameof(ARegisters.GetSystemReg)); + + Context.EmitStintzr(Op.Rt); + } + + public static void Msr(AILEmitterCtx Context) + { + AOpCodeSystem Op = (AOpCodeSystem)Context.CurrOp; + + Context.EmitLdarg(ATranslatedSub.RegistersArgIdx); + + Context.EmitLdc_I4(Op.Op0); + Context.EmitLdc_I4(Op.Op1); + Context.EmitLdc_I4(Op.CRn); + Context.EmitLdc_I4(Op.CRm); + Context.EmitLdc_I4(Op.Op2); + Context.EmitLdintzr(Op.Rt); + + Context.EmitCall(typeof(ARegisters), nameof(ARegisters.SetSystemReg)); + } + + public static void Nop(AILEmitterCtx Context) + { + //Do nothing. + } + + public static void Sys(AILEmitterCtx Context) + { + //This instruction is used to do some operations on the CPU like cache invalidation, + //address translation and the like. + //We treat it as no-op here since we don't have any cache being emulated anyway. + AOpCodeSystem Op = (AOpCodeSystem)Context.CurrOp; + + int Id; + + Id = Op.Op2 << 0; + Id |= Op.CRm << 3; + Id |= Op.CRn << 7; + Id |= Op.Op1 << 11; + + switch (Id) + { + case 0b011_0111_0100_001: + { + //DC ZVA + for (int Offs = 0; Offs < 64; Offs += 8) + { + Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); + Context.EmitLdint(Op.Rt); + Context.EmitLdc_I(Offs); + + Context.Emit(OpCodes.Add); + + Context.EmitLdc_I8(0); + + AInstEmitMemoryHelper.EmitWriteCall(Context, 3); + } + break; + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/AInstEmitter.cs b/Ryujinx/Cpu/Instruction/AInstEmitter.cs new file mode 100644 index 000000000..8712a7367 --- /dev/null +++ b/Ryujinx/Cpu/Instruction/AInstEmitter.cs @@ -0,0 +1,6 @@ +using ChocolArm64.Translation; + +namespace ChocolArm64.Instruction +{ + delegate void AInstEmitter(AILEmitterCtx Context); +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Instruction/ASoftFallback.cs b/Ryujinx/Cpu/Instruction/ASoftFallback.cs new file mode 100644 index 000000000..910123148 --- /dev/null +++ b/Ryujinx/Cpu/Instruction/ASoftFallback.cs @@ -0,0 +1,1207 @@ +using ChocolArm64.State; +using ChocolArm64.Translation; +using System; + +namespace ChocolArm64.Instruction +{ + static class ASoftFallback + { + public static void EmitCall(AILEmitterCtx Context, string Name64, string Name128) + { + bool IsSimd64 = Context.CurrOp.RegisterSize == ARegisterSize.SIMD64; + + Context.EmitCall(typeof(ASoftFallback), IsSimd64 ? Name64 : Name128); + } + + public static void EmitCall(AILEmitterCtx Context, string MthdName) + { + Context.EmitCall(typeof(ASoftFallback), MthdName); + } + + public static uint CountLeadingZeros32(uint Value) => (uint)CountLeadingZeros(Value, 32); + public static ulong CountLeadingZeros64(ulong Value) => (ulong)CountLeadingZeros(Value, 64); + + private static ulong CountLeadingZeros(ulong Value, int Size) + { + int HighBit = Size - 1; + + for (int Bit = HighBit; Bit >= 0; Bit--) + { + if (((Value >> Bit) & 1) != 0) + { + return (ulong)(HighBit - Bit); + } + } + + return (ulong)Size; + } + + public static uint ReverseBits32(uint Value) + { + Value = ((Value & 0xaaaaaaaa) >> 1) | ((Value & 0x55555555) << 1); + Value = ((Value & 0xcccccccc) >> 2) | ((Value & 0x33333333) << 2); + Value = ((Value & 0xf0f0f0f0) >> 4) | ((Value & 0x0f0f0f0f) << 4); + Value = ((Value & 0xff00ff00) >> 8) | ((Value & 0x00ff00ff) << 8); + + return (Value >> 16) | (Value << 16); + } + + public static ulong ReverseBits64(ulong Value) + { + Value = ((Value & 0xaaaaaaaaaaaaaaaa) >> 1) | ((Value & 0x5555555555555555) << 1); + Value = ((Value & 0xcccccccccccccccc) >> 2) | ((Value & 0x3333333333333333) << 2); + Value = ((Value & 0xf0f0f0f0f0f0f0f0) >> 4) | ((Value & 0x0f0f0f0f0f0f0f0f) << 4); + Value = ((Value & 0xff00ff00ff00ff00) >> 8) | ((Value & 0x00ff00ff00ff00ff) << 8); + Value = ((Value & 0xffff0000ffff0000) >> 16) | ((Value & 0x0000ffff0000ffff) << 16); + + return (Value >> 32) | (Value << 32); + } + + public static uint ReverseBytes16_32(uint Value) => (uint)ReverseBytes16_64(Value); + public static uint ReverseBytes32_32(uint Value) => (uint)ReverseBytes32_64(Value); + + public static ulong ReverseBytes16_64(ulong Value) => ReverseBytes(Value, RevSize.Rev16); + public static ulong ReverseBytes32_64(ulong Value) => ReverseBytes(Value, RevSize.Rev32); + public static ulong ReverseBytes64(ulong Value) => ReverseBytes(Value, RevSize.Rev64); + + private enum RevSize + { + Rev16, + Rev32, + Rev64 + } + + private static ulong ReverseBytes(ulong Value, RevSize Size) + { + Value = ((Value & 0xff00ff00ff00ff00) >> 8) | ((Value & 0x00ff00ff00ff00ff) << 8); + + if (Size == RevSize.Rev16) + { + return Value; + } + + Value = ((Value & 0xffff0000ffff0000) >> 16) | ((Value & 0x0000ffff0000ffff) << 16); + + if (Size == RevSize.Rev32) + { + return Value; + } + + Value = ((Value & 0xffffffff00000000) >> 32) | ((Value & 0x00000000ffffffff) << 32); + + if (Size == RevSize.Rev64) + { + return Value; + } + + throw new ArgumentException(nameof(Size)); + } + + public static int SatDoubleToInt32(double Value, int FBits = 0) + { + if (FBits != 0) Value *= Math.Pow(2, FBits); + + return Value > int.MaxValue ? int.MaxValue : + Value < int.MinValue ? int.MinValue : (int)Value; + } + + public static long SatDoubleToInt64(double Value, int FBits = 0) + { + if (FBits != 0) Value *= Math.Pow(2, FBits); + + return Value > long.MaxValue ? long.MaxValue : + Value < long.MinValue ? long.MinValue : (long)Value; + } + + public static uint SatDoubleToUInt32(double Value, int FBits = 0) + { + if (FBits != 0) Value *= Math.Pow(2, FBits); + + return Value > uint.MaxValue ? uint.MaxValue : + Value < uint.MinValue ? uint.MinValue : (uint)Value; + } + + public static ulong SatDoubleToUInt64(double Value, int FBits = 0) + { + if (FBits != 0) Value *= Math.Pow(2, FBits); + + return Value > ulong.MaxValue ? ulong.MaxValue : + Value < ulong.MinValue ? ulong.MinValue : (ulong)Value; + } + + public static int SatSingleToInt32(float Value, int FBits = 0) + { + if (FBits != 0) Value *= MathF.Pow(2, FBits); + + return Value > int.MaxValue ? int.MaxValue : + Value < int.MinValue ? int.MinValue : (int)Value; + } + + public static long SatSingleToInt64(float Value, int FBits = 0) + { + if (FBits != 0) Value *= MathF.Pow(2, FBits); + + return Value > long.MaxValue ? long.MaxValue : + Value < long.MinValue ? long.MinValue : (long)Value; + } + + public static uint SatSingleToUInt32(float Value, int FBits = 0) + { + if (FBits != 0) Value *= MathF.Pow(2, FBits); + + return Value > uint.MaxValue ? uint.MaxValue : + Value < uint.MinValue ? uint.MinValue : (uint)Value; + } + + public static ulong SatSingleToUInt64(float Value, int FBits = 0) + { + if (FBits != 0) Value *= MathF.Pow(2, FBits); + + return Value > ulong.MaxValue ? ulong.MaxValue : + Value < ulong.MinValue ? ulong.MinValue : (ulong)Value; + } + + public static ulong SMulHi128(ulong LHS, ulong RHS) + { + long LLo = (uint)(LHS >> 0); + long LHi = (int)(LHS >> 32); + long RLo = (uint)(RHS >> 0); + long RHi = (int)(RHS >> 32); + + long LHiRHi = LHi * RHi; + long LHiRLo = LHi * RLo; + long LLoRHi = LLo * RHi; + long LLoRLo = LLo * RLo; + + long Carry = ((uint)LHiRLo + ((uint)LLoRHi + (LLoRLo >> 32))) >> 32; + + long ResHi = LHiRHi + (LHiRLo >> 32) + (LLoRHi >> 32) + Carry; + + return (ulong)ResHi; + } + + public static ulong UMulHi128(ulong LHS, ulong RHS) + { + ulong LLo = (uint)(LHS >> 0); + ulong LHi = (uint)(LHS >> 32); + ulong RLo = (uint)(RHS >> 0); + ulong RHi = (uint)(RHS >> 32); + + ulong LHiRHi = LHi * RHi; + ulong LHiRLo = LHi * RLo; + ulong LLoRHi = LLo * RHi; + ulong LLoRLo = LLo * RLo; + + ulong Carry = ((uint)LHiRLo + ((uint)LLoRHi + (LLoRLo >> 32))) >> 32; + + ulong ResHi = LHiRHi + (LHiRLo >> 32) + (LLoRHi >> 32) + Carry; + + return ResHi; + } + + public static AVec Addp_S(AVec Vector, int Size) + { + ulong Low = ExtractVec(Vector, 0, Size); + ulong High = ExtractVec(Vector, 1, Size); + + return InsertVec(new AVec(), 0, Size, Low + High); + } + + public static AVec Addp64(AVec LHS, AVec RHS, int Size) + { + return Addp(LHS, RHS, Size, 8); + } + + public static AVec Addp128(AVec LHS, AVec RHS, int Size) + { + return Addp(LHS, RHS, Size, 16); + } + + private static AVec Addp(AVec LHS, AVec RHS, int Size, int Bytes) + { + AVec Res = new AVec(); + + int Elems = Bytes >> Size; + int Half = Elems >> 1; + + for (int Index = 0; Index < Elems; Index++) + { + int Elem = (Index & (Half - 1)) << 1; + + ulong L = Index < Half + ? ExtractVec(LHS, Elem + 0, Size) + : ExtractVec(RHS, Elem + 0, Size); + + ulong R = Index < Half + ? ExtractVec(LHS, Elem + 1, Size) + : ExtractVec(RHS, Elem + 1, Size); + + Res = InsertVec(Res, Index, Size, L + R); + } + + return Res; + } + + public static AVec Bic_Vi64(AVec Res, ulong Imm, int Size) + { + return Bic_Vi(Res, Imm, Size, 8); + } + + public static AVec Bic_Vi128(AVec Res, ulong Imm, int Size) + { + return Bic_Vi(Res, Imm, Size, 16); + } + + private static AVec Bic_Vi(AVec Res, ulong Imm, int Size, int Bytes) + { + int Elems = Bytes >> Size; + + for (int Index = 0; Index < Elems; Index++) + { + ulong Value = ExtractVec(Res, Index, Size); + + Res = InsertVec(Res, Index, Size, Value & ~Imm); + } + + return Res; + } + + public static AVec Cnt64(AVec Vector) + { + AVec Res = new AVec(); + + Res.B0 = (byte)CountSetBits8(Vector.B0); + Res.B1 = (byte)CountSetBits8(Vector.B1); + Res.B2 = (byte)CountSetBits8(Vector.B2); + Res.B3 = (byte)CountSetBits8(Vector.B3); + Res.B4 = (byte)CountSetBits8(Vector.B4); + Res.B5 = (byte)CountSetBits8(Vector.B5); + Res.B6 = (byte)CountSetBits8(Vector.B6); + Res.B7 = (byte)CountSetBits8(Vector.B7); + + return Res; + } + + public static AVec Cnt128(AVec Vector) + { + AVec Res = new AVec(); + + Res.B0 = (byte)CountSetBits8(Vector.B0); + Res.B1 = (byte)CountSetBits8(Vector.B1); + Res.B2 = (byte)CountSetBits8(Vector.B2); + Res.B3 = (byte)CountSetBits8(Vector.B3); + Res.B4 = (byte)CountSetBits8(Vector.B4); + Res.B5 = (byte)CountSetBits8(Vector.B5); + Res.B6 = (byte)CountSetBits8(Vector.B6); + Res.B7 = (byte)CountSetBits8(Vector.B7); + Res.B8 = (byte)CountSetBits8(Vector.B8); + Res.B9 = (byte)CountSetBits8(Vector.B9); + Res.B10 = (byte)CountSetBits8(Vector.B10); + Res.B11 = (byte)CountSetBits8(Vector.B11); + Res.B12 = (byte)CountSetBits8(Vector.B12); + Res.B13 = (byte)CountSetBits8(Vector.B13); + Res.B14 = (byte)CountSetBits8(Vector.B14); + Res.B15 = (byte)CountSetBits8(Vector.B15); + + return Res; + } + + private static int CountSetBits8(byte Value) + { + return (Value >> 0) & 1 + (Value >> 1) & 1 + + (Value >> 2) & 1 + (Value >> 3) & 1 + + (Value >> 4) & 1 + (Value >> 5) & 1 + + (Value >> 6) & 1 + (Value >> 7); + } + + public static AVec Dup_Gp64(ulong Value, int Size) + { + return Dup_Gp(Value, Size, 8); + } + + public static AVec Dup_Gp128(ulong Value, int Size) + { + return Dup_Gp(Value, Size, 16); + } + + private static AVec Dup_Gp(ulong Value, int Size, int Bytes) + { + AVec Res = new AVec(); + + for (int Index = 0; Index < (Bytes >> Size); Index++) + { + Res = InsertVec(Res, Index, Size, Value); + } + + return Res; + } + + public static AVec Dup_S(AVec Vector, int Elem, int Size) + { + return InsertVec(new AVec(), 0, Size, ExtractVec(Vector, Elem, Size)); + } + + public static AVec Dup_V64(AVec Vector, int Elem, int Size) + { + return Dup_V(Vector, Elem, Size, 8); + } + + public static AVec Dup_V128(AVec Vector, int Elem, int Size) + { + return Dup_V(Vector, Elem, Size, 16); + } + + private static AVec Dup_V(AVec Vector, int Elem, int Size, int Bytes) + { + AVec Res = new AVec(); + + ulong Value = ExtractVec(Vector, Elem, Size); + + for (Elem = 0; Elem < (Bytes >> Size); Elem++) + { + Res = InsertVec(Res, Elem, Size, Value); + } + + return Res; + } + + public static AVec Fadd64(AVec LHS, AVec RHS, int Size) + { + return Fadd(LHS, RHS, Size, 2); + } + + public static AVec Fadd128(AVec LHS, AVec RHS, int Size) + { + return Fadd(LHS, RHS, Size, 4); + } + + private static AVec Fadd(AVec LHS, AVec RHS, int Size, int Bytes) + { + AVec Res = new AVec(); + + int Elems = Bytes >> Size; + + if (Size == 0) + { + for (int Index = 0; Index < Elems; Index++) + { + float L = LHS.ExtractSingle(Index); + float R = RHS.ExtractSingle(Index); + + Res = AVec.InsertSingle(Res, Index, L + R); + } + } + else + { + for (int Index = 0; Index < Elems; Index++) + { + double L = LHS.ExtractDouble(Index); + double R = RHS.ExtractDouble(Index); + + Res = AVec.InsertDouble(Res, Index, L + R); + } + } + + return Res; + } + + public static AVec Fcvtzs_V64(AVec Vector, int Size) + { + return Fcvtzs_V(Vector, Size, 2); + } + + public static AVec Fcvtzs_V128(AVec Vector, int Size) + { + return Fcvtzs_V(Vector, Size, 4); + } + + private static AVec Fcvtzs_V(AVec Vector, int Size, int Bytes) + { + AVec Res = new AVec(); + + int Elems = Bytes >> Size; + + if (Size == 0) + { + for (int Index = 0; Index < Elems; Index++) + { + float Value = Vector.ExtractSingle(Index); + + Res = InsertSVec(Res, Index, Size + 2, SatSingleToInt32(Value)); + } + } + else + { + for (int Index = 0; Index < Elems; Index++) + { + double Value = Vector.ExtractDouble(Index); + + Res = InsertSVec(Res, Index, Size + 2, SatDoubleToInt64(Value)); + } + } + + return Res; + } + + public static AVec Fcvtzu_V_64(AVec Vector, int FBits, int Size) + { + return Fcvtzu_V(Vector, FBits, Size, 2); + } + + public static AVec Fcvtzu_V_128(AVec Vector, int FBits, int Size) + { + return Fcvtzu_V(Vector, FBits, Size, 4); + } + + private static AVec Fcvtzu_V(AVec Vector, int FBits, int Size, int Bytes) + { + AVec Res = new AVec(); + + int Elems = Bytes >> Size; + + if (Size == 0) + { + for (int Index = 0; Index < Elems; Index++) + { + float Value = Vector.ExtractSingle(Index); + + Res = InsertVec(Res, Index, Size + 2, SatSingleToUInt32(Value, FBits)); + } + } + else + { + for (int Index = 0; Index < Elems; Index++) + { + double Value = Vector.ExtractDouble(Index); + + Res = InsertVec(Res, Index, Size + 2, SatDoubleToUInt64(Value, FBits)); + } + } + + return Res; + } + + public static AVec Fmla64(AVec Res, AVec LHS, AVec RHS, int Size) + { + return Fmla(Res, LHS, RHS, Size, 2); + } + + public static AVec Fmla128(AVec Res, AVec LHS, AVec RHS, int Size) + { + return Fmla(Res, LHS, RHS, Size, 4); + } + + private static AVec Fmla(AVec Res, AVec LHS, AVec RHS, int Size, int Bytes) + { + int Elems = Bytes >> Size; + + if (Size == 0) + { + for (int Index = 0; Index < Elems; Index++) + { + float L = LHS.ExtractSingle(Index); + float R = RHS.ExtractSingle(Index); + float Addend = Res.ExtractSingle(Index); + + Res = AVec.InsertSingle(Res, Index, Addend + L * R); + } + } + else + { + for (int Index = 0; Index < Elems; Index++) + { + double L = LHS.ExtractDouble(Index); + double R = RHS.ExtractDouble(Index); + double Addend = Res.ExtractDouble(Index); + + Res = AVec.InsertDouble(Res, Index, Addend + L * R); + } + } + + return Res; + } + + public static AVec Fmla_Ve64(AVec Res, AVec LHS, AVec RHS, int SIdx, int Size) + { + return Fmla_Ve(Res, LHS, RHS, SIdx, Size, 2); + } + + public static AVec Fmla_Ve128(AVec Res, AVec LHS, AVec RHS, int SIdx, int Size) + { + return Fmla_Ve(Res, LHS, RHS, SIdx, Size, 4); + } + + private static AVec Fmla_Ve(AVec Res, AVec LHS, AVec RHS, int SIdx, int Size, int Bytes) + { + int Elems = Bytes >> Size; + + if (Size == 0) + { + float R = RHS.ExtractSingle(SIdx); + + for (int Index = 0; Index < Elems; Index++) + { + float L = LHS.ExtractSingle(Index); + float Addend = Res.ExtractSingle(Index); + + Res = AVec.InsertSingle(Res, Index, Addend + L * R); + } + } + else + { + double R = RHS.ExtractDouble(SIdx); + + for (int Index = 0; Index < Elems; Index++) + { + double L = LHS.ExtractDouble(Index); + double Addend = Res.ExtractDouble(Index); + + Res = AVec.InsertDouble(Res, Index, Addend + L * R); + } + } + + return Res; + } + + public static AVec Fmov_S(ulong Value, int Elem, int Size) + { + return InsertVec(new AVec(), Elem, Size, Value); + } + + public static AVec Fmul64(AVec LHS, AVec RHS, int Size) + { + return Fmul(LHS, RHS, Size, 2); + } + + public static AVec Fmul128(AVec LHS, AVec RHS, int Size) + { + return Fmul(LHS, RHS, Size, 4); + } + + private static AVec Fmul(AVec LHS, AVec RHS, int Size, int Bytes) + { + AVec Res = new AVec(); + + int Elems = Bytes >> Size; + + if (Size == 0) + { + for (int Index = 0; Index < Elems; Index++) + { + float L = LHS.ExtractSingle(Index); + float R = RHS.ExtractSingle(Index); + + Res = AVec.InsertSingle(Res, Index, L * R); + } + } + else + { + for (int Index = 0; Index < Elems; Index++) + { + double L = LHS.ExtractDouble(Index); + double R = RHS.ExtractDouble(Index); + + Res = AVec.InsertDouble(Res, Index, L * R); + } + } + + return Res; + } + + public static AVec Fmul_Ve64(AVec LHS, AVec RHS, int SIdx, int Size) + { + return Fmul_Ve(LHS, RHS, SIdx, Size, 2); + } + + public static AVec Fmul_Ve128(AVec LHS, AVec RHS, int SIdx, int Size) + { + return Fmul_Ve(LHS, RHS, SIdx, Size, 4); + } + + private static AVec Fmul_Ve(AVec LHS, AVec RHS, int SIdx, int Size, int Bytes) + { + AVec Res = new AVec(); + + int Elems = Bytes >> Size; + + if (Size == 0) + { + float R = RHS.ExtractSingle(SIdx); + + for (int Index = 0; Index < Elems; Index++) + { + float L = LHS.ExtractSingle(Index); + + Res = AVec.InsertSingle(Res, Index, L * R); + } + } + else + { + double R = RHS.ExtractDouble(SIdx); + + for (int Index = 0; Index < Elems; Index++) + { + double L = LHS.ExtractDouble(Index); + + Res = AVec.InsertDouble(Res, Index, L * R); + } + } + + return Res; + } + + public static AVec Fsub64(AVec LHS, AVec RHS, int Size) + { + return Fsub(LHS, RHS, Size, 2); + } + + public static AVec Fsub128(AVec LHS, AVec RHS, int Size) + { + return Fsub(LHS, RHS, Size, 4); + } + + private static AVec Fsub(AVec LHS, AVec RHS, int Size, int Bytes) + { + AVec Res = new AVec(); + + int Elems = Bytes >> Size; + + if (Size == 0) + { + for (int Index = 0; Index < Elems; Index++) + { + float L = LHS.ExtractSingle(Index); + float R = RHS.ExtractSingle(Index); + + Res = AVec.InsertSingle(Res, Index, L - R); + } + } + else + { + for (int Index = 0; Index < Elems; Index++) + { + double L = LHS.ExtractDouble(Index); + double R = RHS.ExtractDouble(Index); + + Res = AVec.InsertDouble(Res, Index, L - R); + } + } + + return Res; + } + + public static AVec Ins_Gp(AVec Res, ulong Value, int Elem, int Size) + { + return InsertVec(Res, Elem, Size, Value); + } + + public static AVec Ins_V(AVec Res, AVec Value, int Src, int Dst, int Size) + { + return InsertVec(Res, Dst, Size, ExtractVec(Value, Src, Size));; + } + + public static AVec Orr_Vi64(AVec Res, ulong Imm, int Size) + { + return Orr_Vi(Res, Imm, Size, 8); + } + + public static AVec Orr_Vi128(AVec Res, ulong Imm, int Size) + { + return Orr_Vi(Res, Imm, Size, 16); + } + + private static AVec Orr_Vi(AVec Res, ulong Imm, int Size, int Bytes) + { + int Elems = Bytes >> Size; + + for (int Index = 0; Index < Elems; Index++) + { + ulong Value = ExtractVec(Res, Index, Size); + + Res = InsertVec(Res, Index, Size, Value | Imm); + } + + return Res; + } + + public static AVec Saddw(AVec LHS, AVec RHS, int Size) + { + return Saddw_(LHS, RHS, Size, false); + } + + public static AVec Saddw2(AVec LHS, AVec RHS, int Size) + { + return Saddw_(LHS, RHS, Size, true); + } + + private static AVec Saddw_(AVec LHS, AVec RHS, int Size, bool High) + { + AVec Res = new AVec(); + + int Elems = 8 >> Size; + int Part = High ? Elems : 0; + + for (int Index = 0; Index < Elems; Index++) + { + long L = ExtractSVec(LHS, Index, Size + 1); + long R = ExtractSVec(RHS, Index + Part, Size); + + Res = InsertSVec(Res, Index, Size + 1, L + R); + } + + return Res; + } + + public static AVec Scvtf_V64(AVec Vector, int Size) + { + return Scvtf_V(Vector, Size, 2); + } + + public static AVec Scvtf_V128(AVec Vector, int Size) + { + return Scvtf_V(Vector, Size, 4); + } + + private static AVec Scvtf_V(AVec Vector, int Size, int Bytes) + { + AVec Res = new AVec(); + + int Elems = Bytes >> Size; + + if (Size == 0) + { + for (int Index = 0; Index < Elems; Index++) + { + int Value = (int)ExtractSVec(Vector, Index, Size + 2); + + Res = AVec.InsertSingle(Res, Index, Value); + } + } + else + { + for (int Index = 0; Index < Elems; Index++) + { + long Value = ExtractSVec(Vector, Index, Size + 2); + + Res = AVec.InsertDouble(Res, Index, Value); + } + } + + return Res; + } + + public static AVec Shl64(AVec Vector, int Shift, int Size) + { + return Shl(Vector, Shift, Size, 8); + } + + public static AVec Shl128(AVec Vector, int Shift, int Size) + { + return Shl(Vector, Shift, Size, 16); + } + + private static AVec Shl(AVec Vector, int Shift, int Size, int Bytes) + { + AVec Res = new AVec(); + + int Elems = Bytes >> Size; + + for (int Index = 0; Index < Elems; Index++) + { + ulong Value = ExtractVec(Vector, Index, Size); + + Res = InsertVec(Res, Index, Size, Value << Shift); + } + + return Res; + } + + public static AVec Sshll(AVec Vector, int Shift, int Size) + { + return Sshll_(Vector, Shift, Size, false); + } + + public static AVec Sshll2(AVec Vector, int Shift, int Size) + { + return Sshll_(Vector, Shift, Size, true); + } + + private static AVec Sshll_(AVec Vector, int Shift, int Size, bool High) + { + AVec Res = new AVec(); + + int Elems = 8 >> Size; + int Part = High ? Elems : 0; + + for (int Index = 0; Index < Elems; Index++) + { + long Value = ExtractSVec(Vector, Index + Part, Size); + + Res = InsertSVec(Res, Index, Size + 1, Value << Shift); + } + + return Res; + } + + public static AVec Sshr64(AVec Vector, int Shift, int Size) + { + return Sshr(Vector, Shift, Size, 8); + } + + public static AVec Sshr128(AVec Vector, int Shift, int Size) + { + return Sshr(Vector, Shift, Size, 16); + } + + private static AVec Sshr(AVec Vector, int Shift, int Size, int Bytes) + { + AVec Res = new AVec(); + + int Elems = Bytes >> Size; + + for (int Index = 0; Index < Elems; Index++) + { + long Value = ExtractSVec(Vector, Index, Size); + + Res = InsertSVec(Res, Index, Size, Value >> Shift); + } + + return Res; + } + + public static AVec Tbl1_V64(AVec Vector, AVec Tb0) + { + return Tbl(Vector, 8, Tb0); + } + + public static AVec Tbl1_V128(AVec Vector, AVec Tb0) + { + return Tbl(Vector, 16, Tb0); + } + + public static AVec Tbl2_V64(AVec Vector, AVec Tb0, AVec Tb1) + { + return Tbl(Vector, 8, Tb0, Tb1); + } + + public static AVec Tbl2_V128(AVec Vector, AVec Tb0, AVec Tb1) + { + return Tbl(Vector, 16, Tb0, Tb1); + } + + public static AVec Tbl3_V64(AVec Vector, AVec Tb0, AVec Tb1, AVec Tb2) + { + return Tbl(Vector, 8, Tb0, Tb1, Tb2); + } + + public static AVec Tbl3_V128(AVec Vector, AVec Tb0, AVec Tb1, AVec Tb2) + { + return Tbl(Vector, 16, Tb0, Tb1, Tb2); + } + + public static AVec Tbl4_V64(AVec Vector, AVec Tb0, AVec Tb1, AVec Tb2, AVec Tb3) + { + return Tbl(Vector, 8, Tb0, Tb1, Tb2, Tb3); + } + + public static AVec Tbl4_V128(AVec Vector, AVec Tb0, AVec Tb1, AVec Tb2, AVec Tb3) + { + return Tbl(Vector, 16, Tb0, Tb1, Tb2, Tb3); + } + + private static AVec Tbl(AVec Vector, int Bytes, params AVec[] Tb) + { + AVec Res = new AVec(); + + byte[] Table = new byte[Tb.Length * 16]; + + for (int Index = 0; Index < Tb.Length; Index++) + for (int Index2 = 0; Index2 < 16; Index2++) + { + Table[Index * 16 + Index2] = (byte)ExtractVec(Tb[Index], Index2, 0); + } + + for (int Index = 0; Index < Bytes; Index++) + { + byte TblIdx = (byte)ExtractVec(Vector, Index, 0); + + if (TblIdx < Table.Length) + { + Res = InsertVec(Res, Index, 0, Table[TblIdx]); + } + } + + return Res; + } + + public static AVec Uaddlv64(AVec Vector, int Size) + { + return Uaddlv(Vector, Size, 8); + } + + public static AVec Uaddlv128(AVec Vector, int Size) + { + return Uaddlv(Vector, Size, 16); + } + + private static AVec Uaddlv(AVec Vector, int Size, int Bytes) + { + int Elems = Bytes >> Size; + + ulong Sum = 0; + + for (int Index = 0; Index < Elems; Index++) + { + Sum += ExtractVec(Vector, Index, Size); + } + + return InsertVec(new AVec(), 0, 3, Sum); + } + + public static AVec Uaddw(AVec LHS, AVec RHS, int Size) + { + return Uaddw_(LHS, RHS, Size, false); + } + + public static AVec Uaddw2(AVec LHS, AVec RHS, int Size) + { + return Uaddw_(LHS, RHS, Size, true); + } + + private static AVec Uaddw_(AVec LHS, AVec RHS, int Size, bool High) + { + AVec Res = new AVec(); + + int Elems = 8 >> Size; + int Part = High ? Elems : 0; + + for (int Index = 0; Index < Elems; Index++) + { + ulong L = ExtractVec(LHS, Index, Size + 1); + ulong R = ExtractVec(RHS, Index + Part, Size); + + Res = InsertVec(Res, Index, Size + 1, L + R); + } + + return Res; + } + + public static AVec Ucvtf_V_F(AVec Vector) + { + return new AVec() + { + S0 = (uint)Vector.W0, + S1 = (uint)Vector.W1, + S2 = (uint)Vector.W2, + S3 = (uint)Vector.W3 + }; + } + + public static AVec Ucvtf_V_D(AVec Vector) + { + return new AVec() + { + D0 = (ulong)Vector.X0, + D1 = (ulong)Vector.X1 + }; + } + + public static AVec Ushll(AVec Vector, int Shift, int Size) + { + return Ushll_(Vector, Shift, Size, false); + } + + public static AVec Ushll2(AVec Vector, int Shift, int Size) + { + return Ushll_(Vector, Shift, Size, true); + } + + private static AVec Ushll_(AVec Vector, int Shift, int Size, bool High) + { + AVec Res = new AVec(); + + int Elems = 8 >> Size; + int Part = High ? Elems : 0; + + for (int Index = 0; Index < Elems; Index++) + { + ulong Value = ExtractVec(Vector, Index + Part, Size); + + Res = InsertVec(Res, Index, Size + 1, Value << Shift); + } + + return Res; + } + + public static AVec Ushr64(AVec Vector, int Shift, int Size) + { + return Ushr(Vector, Shift, Size, 8); + } + + public static AVec Ushr128(AVec Vector, int Shift, int Size) + { + return Ushr(Vector, Shift, Size, 16); + } + + private static AVec Ushr(AVec Vector, int Shift, int Size, int Bytes) + { + AVec Res = new AVec(); + + int Elems = Bytes >> Size; + + for (int Index = 0; Index < Elems; Index++) + { + ulong Value = ExtractVec(Vector, Index, Size); + + Res = InsertVec(Res, Index, Size, Value >> Shift); + } + + return Res; + } + + public static AVec Usra64(AVec Res, AVec Vector, int Shift, int Size) + { + return Usra(Res, Vector, Shift, Size, 8); + } + + public static AVec Usra128(AVec Res, AVec Vector, int Shift, int Size) + { + return Usra(Res, Vector, Shift, Size, 16); + } + + private static AVec Usra(AVec Res, AVec Vector, int Shift, int Size, int Bytes) + { + int Elems = Bytes >> Size; + + for (int Index = 0; Index < Elems; Index++) + { + ulong Value = ExtractVec(Vector, Index, Size); + ulong Addend = ExtractVec(Res, Index, Size); + + Res = InsertVec(Res, Index, Size, Addend + (Value >> Shift)); + } + + return Res; + } + + public static AVec Uzp1_V64(AVec LHS, AVec RHS, int Size) + { + return Uzp(LHS, RHS, Size, 0, 8); + } + + public static AVec Uzp1_V128(AVec LHS, AVec RHS, int Size) + { + return Uzp(LHS, RHS, Size, 0, 16); + } + + public static AVec Uzp2_V64(AVec LHS, AVec RHS, int Size) + { + return Uzp(LHS, RHS, Size, 1, 8); + } + + public static AVec Uzp2_V128(AVec LHS, AVec RHS, int Size) + { + return Uzp(LHS, RHS, Size, 1, 16); + } + + private static AVec Uzp(AVec LHS, AVec RHS, int Size, int Part, int Bytes) + { + AVec Res = new AVec(); + + int Elems = Bytes >> Size; + int Half = Elems >> 1; + + for (int Index = 0; Index < Elems; Index++) + { + int Elem = (Index & (Half - 1)) << 1; + + ulong Value = Index < Half + ? ExtractVec(LHS, Elem + Part, Size) + : ExtractVec(RHS, Elem + Part, Size); + + Res = InsertVec(Res, Index, Size, Value); + } + + return Res; + } + + public static AVec Xtn(AVec Vector, int Size) + { + return Xtn_(Vector, Size, false); + } + + public static AVec Xtn2(AVec Vector, int Size) + { + return Xtn_(Vector, Size, true); + } + + private static AVec Xtn_(AVec Vector, int Size, bool High) + { + AVec Res = new AVec(); + + int Elems = 8 >> Size; + int Part = High ? Elems : 0; + + for (int Index = 0; Index < Elems; Index++) + { + ulong Value = ExtractVec(Vector, Index, Size + 1); + + Res = InsertVec(Res, Index + Part, Size, Value); + } + + return Res; + } + + public static ulong ExtractVec(AVec Vector, int Index, int Size) + { + switch (Size) + { + case 0: return Vector.ExtractByte(Index); + case 1: return Vector.ExtractUInt16(Index); + case 2: return Vector.ExtractUInt32(Index); + case 3: return Vector.ExtractUInt64(Index); + } + + throw new ArgumentOutOfRangeException(nameof(Size)); + } + + public static long ExtractSVec(AVec Vector, int Index, int Size) + { + switch (Size) + { + case 0: return (sbyte)Vector.ExtractByte(Index); + case 1: return (short)Vector.ExtractUInt16(Index); + case 2: return (int)Vector.ExtractUInt32(Index); + case 3: return (long)Vector.ExtractUInt64(Index); + } + + throw new ArgumentOutOfRangeException(nameof(Size)); + } + + public static AVec InsertVec(AVec Vector, int Index, int Size, ulong Value) + { + switch (Size) + { + case 0: return AVec.InsertByte(Vector, Index, (byte)Value); + case 1: return AVec.InsertUInt16(Vector, Index, (ushort)Value); + case 2: return AVec.InsertUInt32(Vector, Index, (uint)Value); + case 3: return AVec.InsertUInt64(Vector, Index, (ulong)Value); + } + + throw new ArgumentOutOfRangeException(nameof(Size)); + } + + public static AVec InsertSVec(AVec Vector, int Index, int Size, long Value) + { + switch (Size) + { + case 0: return AVec.InsertByte(Vector, Index, (byte)Value); + case 1: return AVec.InsertUInt16(Vector, Index, (ushort)Value); + case 2: return AVec.InsertUInt32(Vector, Index, (uint)Value); + case 3: return AVec.InsertUInt64(Vector, Index, (ulong)Value); + } + + throw new ArgumentOutOfRangeException(nameof(Size)); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Memory/AMemory.cs b/Ryujinx/Cpu/Memory/AMemory.cs new file mode 100644 index 000000000..4e9bd53fc --- /dev/null +++ b/Ryujinx/Cpu/Memory/AMemory.cs @@ -0,0 +1,237 @@ +using ChocolArm64.State; +using System; +using System.Collections.Generic; + +namespace ChocolArm64.Memory +{ + public unsafe class AMemory + { + public AMemoryMgr Manager { get; private set; } + + private struct ExMonitor + { + public long Position { get; private set; } + + private bool ExState; + + public ExMonitor(long Position, bool ExState) + { + this.Position = Position; + this.ExState = ExState; + } + + public bool HasExclusiveAccess(long Position) + { + return this.Position == Position && ExState; + } + + public void Reset() + { + ExState = false; + } + } + + private Dictionary Monitors; + + private HashSet ExAddrs; + + private byte* RamPtr; + + public AMemory(IntPtr Ram, AMemoryAlloc Allocator) + { + Manager = new AMemoryMgr(Allocator); + + Monitors = new Dictionary(); + + ExAddrs = new HashSet(); + + RamPtr = (byte*)Ram; + } + + public void RemoveMonitor(int ThreadId) + { + lock (Monitors) + { + Monitors.Remove(ThreadId); + } + } + + public void SetExclusive(ARegisters Registers, long Position) + { + lock (Monitors) + { + bool ExState = !ExAddrs.Contains(Position); + + if (ExState) + { + ExAddrs.Add(Position); + } + + ExMonitor Monitor = new ExMonitor(Position, ExState); + + if (!Monitors.TryAdd(Registers.ThreadId, Monitor)) + { + Monitors[Registers.ThreadId] = Monitor; + } + } + } + + public bool TestExclusive(ARegisters Registers, long Position) + { + lock (Monitors) + { + if (!Monitors.TryGetValue(Registers.ThreadId, out ExMonitor Monitor)) + { + return false; + } + + return Monitor.HasExclusiveAccess(Position); + } + } + + public void ClearExclusive(ARegisters Registers) + { + lock (Monitors) + { + if (Monitors.TryGetValue(Registers.ThreadId, out ExMonitor Monitor)) + { + Monitor.Reset(); + ExAddrs.Remove(Monitor.Position); + } + } + } + + public sbyte ReadSByte(long Position) => (sbyte)ReadByte (Position); + public short ReadInt16(long Position) => (short)ReadUInt16(Position); + public int ReadInt32(long Position) => (int)ReadUInt32(Position); + public long ReadInt64(long Position) => (long)ReadUInt64(Position); + + public byte ReadByte(long Position) + { + return *((byte*)(RamPtr + Manager.GetPhys(Position, AMemoryPerm.Read))); + } + + public ushort ReadUInt16(long Position) + { + long PhysPos = Manager.GetPhys(Position, AMemoryPerm.Read); + + if (BitConverter.IsLittleEndian && !IsPageCrossed(Position, 2)) + { + return *((ushort*)(RamPtr + PhysPos)); + } + else + { + return (ushort)( + ReadByte(Position + 0) << 0 | + ReadByte(Position + 1) << 8); + } + } + + public uint ReadUInt32(long Position) + { + long PhysPos = Manager.GetPhys(Position, AMemoryPerm.Read); + + if (BitConverter.IsLittleEndian && !IsPageCrossed(Position, 4)) + { + return *((uint*)(RamPtr + PhysPos)); + } + else + { + return (uint)( + ReadUInt16(Position + 0) << 0 | + ReadUInt16(Position + 2) << 16); + } + } + + public ulong ReadUInt64(long Position) + { + long PhysPos = Manager.GetPhys(Position, AMemoryPerm.Read); + + if (BitConverter.IsLittleEndian && !IsPageCrossed(Position, 8)) + { + return *((ulong*)(RamPtr + PhysPos)); + } + else + { + return + (ulong)ReadUInt32(Position + 0) << 0 | + (ulong)ReadUInt32(Position + 4) << 32; + } + } + + public AVec ReadVector128(long Position) + { + return new AVec() + { + X0 = ReadUInt64(Position + 0), + X1 = ReadUInt64(Position + 8) + }; + } + + public void WriteSByte(long Position, sbyte Value) => WriteByte (Position, (byte)Value); + public void WriteInt16(long Position, short Value) => WriteUInt16(Position, (ushort)Value); + public void WriteInt32(long Position, int Value) => WriteUInt32(Position, (uint)Value); + public void WriteInt64(long Position, long Value) => WriteUInt64(Position, (ulong)Value); + + public void WriteByte(long Position, byte Value) + { + *((byte*)(RamPtr + Manager.GetPhys(Position, AMemoryPerm.Write))) = Value; + } + + public void WriteUInt16(long Position, ushort Value) + { + long PhysPos = Manager.GetPhys(Position, AMemoryPerm.Write); + + if (BitConverter.IsLittleEndian && !IsPageCrossed(Position, 2)) + { + *((ushort*)(RamPtr + PhysPos)) = Value; + } + else + { + WriteByte(Position + 0, (byte)(Value >> 0)); + WriteByte(Position + 1, (byte)(Value >> 8)); + } + } + + public void WriteUInt32(long Position, uint Value) + { + long PhysPos = Manager.GetPhys(Position, AMemoryPerm.Write); + + if (BitConverter.IsLittleEndian && !IsPageCrossed(Position, 4)) + { + *((uint*)(RamPtr + PhysPos)) = Value; + } + else + { + WriteUInt16(Position + 0, (ushort)(Value >> 0)); + WriteUInt16(Position + 2, (ushort)(Value >> 16)); + } + } + + public void WriteUInt64(long Position, ulong Value) + { + long PhysPos = Manager.GetPhys(Position, AMemoryPerm.Write); + + if (BitConverter.IsLittleEndian && !IsPageCrossed(Position, 8)) + { + *((ulong*)(RamPtr + PhysPos)) = Value; + } + else + { + WriteUInt32(Position + 0, (uint)(Value >> 0)); + WriteUInt32(Position + 4, (uint)(Value >> 32)); + } + } + + public void WriteVector128(long Position, AVec Value) + { + WriteUInt64(Position + 0, Value.X0); + WriteUInt64(Position + 8, Value.X1); + } + + private bool IsPageCrossed(long Position, int Size) + { + return (Position & AMemoryMgr.PageMask) + Size > AMemoryMgr.PageSize; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Memory/AMemoryAlloc.cs b/Ryujinx/Cpu/Memory/AMemoryAlloc.cs new file mode 100644 index 000000000..b11e77931 --- /dev/null +++ b/Ryujinx/Cpu/Memory/AMemoryAlloc.cs @@ -0,0 +1,35 @@ +using ChocolArm64.Exceptions; + +namespace ChocolArm64.Memory +{ + public class AMemoryAlloc + { + private long PhysPos; + + public long Alloc(long Size) + { + long Position = PhysPos; + + Size = AMemoryHelper.PageRoundUp(Size); + + PhysPos += Size; + + if (PhysPos > AMemoryMgr.RamSize || PhysPos < 0) + { + throw new VmmOutOfMemoryException(Size); + } + + return Position; + } + + public void Free(long Position) + { + //TODO + } + + public long GetFreeMem() + { + return AMemoryMgr.RamSize - PhysPos; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Memory/AMemoryHelper.cs b/Ryujinx/Cpu/Memory/AMemoryHelper.cs new file mode 100644 index 000000000..219aeebf9 --- /dev/null +++ b/Ryujinx/Cpu/Memory/AMemoryHelper.cs @@ -0,0 +1,73 @@ +using System.IO; +using System.Text; + +namespace ChocolArm64.Memory +{ + public static class AMemoryHelper + { + public static void FillWithZeros(AMemory Memory, long Position, int Size) + { + int Size8 = Size & ~(8 - 1); + + for (int Offs = 0; Offs < Size8; Offs += 8) + { + Memory.WriteInt64(Position + Offs, 0); + } + + for (int Offs = Size8; Offs < (Size - Size8); Offs++) + { + Memory.WriteByte(Position + Offs, 0); + } + } + + public static byte[] ReadBytes(AMemory Memory, long Position, int Size) + { + byte[] Data = new byte[Size]; + + for (int Offs = 0; Offs < Size; Offs++) + { + Data[Offs] = (byte)Memory.ReadByte(Position + Offs); + } + + return Data; + } + + public static void WriteBytes(AMemory Memory, long Position, byte[] Data) + { + for (int Offs = 0; Offs < Data.Length; Offs++) + { + Memory.WriteByte(Position + Offs, Data[Offs]); + } + } + + public static string ReadAsciiString(AMemory Memory, long Position, int MaxSize = -1) + { + using (MemoryStream MS = new MemoryStream()) + { + for (int Offs = 0; Offs < MaxSize || MaxSize == -1; Offs++) + { + byte Value = (byte)Memory.ReadByte(Position + Offs); + + if (Value == 0) + { + break; + } + + MS.WriteByte(Value); + } + + return Encoding.ASCII.GetString(MS.ToArray()); + } + } + + public static long PageRoundUp(long Value) + { + return (Value + AMemoryMgr.PageMask) & ~AMemoryMgr.PageMask; + } + + public static long PageRoundDown(long Value) + { + return Value & ~AMemoryMgr.PageMask; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Memory/AMemoryMapInfo.cs b/Ryujinx/Cpu/Memory/AMemoryMapInfo.cs new file mode 100644 index 000000000..8ba6c25e6 --- /dev/null +++ b/Ryujinx/Cpu/Memory/AMemoryMapInfo.cs @@ -0,0 +1,19 @@ +namespace ChocolArm64.Memory +{ + public struct AMemoryMapInfo + { + public long Position { get; private set; } + public long Size { get; private set; } + public int Type { get; private set; } + + public AMemoryPerm Perm { get; private set; } + + public AMemoryMapInfo(long Position, long Size, int Type, AMemoryPerm Perm) + { + this.Position = Position; + this.Size = Size; + this.Type = Type; + this.Perm = Perm; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Memory/AMemoryMgr.cs b/Ryujinx/Cpu/Memory/AMemoryMgr.cs new file mode 100644 index 000000000..4d995469f --- /dev/null +++ b/Ryujinx/Cpu/Memory/AMemoryMgr.cs @@ -0,0 +1,336 @@ +using ChocolArm64.Exceptions; +using System; +using System.Runtime.CompilerServices; + +namespace ChocolArm64.Memory +{ + public class AMemoryMgr + { + public const long AddrSize = 1L << 36; + public const long RamSize = 2L * 1024 * 1024 * 1024; + + private const int PTLvl0Bits = 11; + private const int PTLvl1Bits = 13; + private const int PTPageBits = 12; + + private const int PTLvl0Size = 1 << PTLvl0Bits; + private const int PTLvl1Size = 1 << PTLvl1Bits; + public const int PageSize = 1 << PTPageBits; + + private const int PTLvl0Mask = PTLvl0Size - 1; + private const int PTLvl1Mask = PTLvl1Size - 1; + public const int PageMask = PageSize - 1; + + private const int PTLvl0Bit = PTPageBits + PTLvl0Bits; + private const int PTLvl1Bit = PTPageBits; + + private AMemoryAlloc Allocator; + + private enum PTMap + { + Unmapped, + Physical, + Mirror + } + + private struct PTEntry + { + public long Position; + public int Type; + + public PTMap Map; + public AMemoryPerm Perm; + + public PTEntry(long Position, int Type, PTMap Map, AMemoryPerm Perm) + { + this.Position = Position; + this.Type = Type; + this.Map = Map; + this.Perm = Perm; + } + } + + private PTEntry[][] PageTable; + + private bool IsHeapInitialized; + + public long HeapAddr { get; private set; } + public int HeapSize { get; private set; } + + public AMemoryMgr(AMemoryAlloc Allocator) + { + this.Allocator = Allocator; + + PageTable = new PTEntry[PTLvl0Size][]; + } + + public long GetTotalMemorySize() + { + return Allocator.GetFreeMem() + GetUsedMemorySize(); + } + + public long GetUsedMemorySize() + { + long Size = 0; + + for (int L0 = 0; L0 < PageTable.Length; L0++) + { + if (PageTable[L0] == null) + { + continue; + } + + for (int L1 = 0; L1 < PageTable[L0].Length; L1++) + { + Size += PageTable[L0][L1].Map != PTMap.Unmapped ? PageSize : 0; + } + } + + return Size; + } + + public bool SetHeapAddr(long Position) + { + if (!IsHeapInitialized) + { + HeapAddr = Position; + + IsHeapInitialized = true; + + return true; + } + + return false; + } + + public void SetHeapSize(int Size, int Type) + { + //TODO: Return error when theres no enough space to allocate heap. + Size = (int)AMemoryHelper.PageRoundUp(Size); + + long Position = HeapAddr; + + if ((ulong)Size < (ulong)HeapSize) + { + //Try to free now free area if size is smaller than old size. + Position += Size; + + while ((ulong)Size < (ulong)HeapSize) + { + Allocator.Free(GetPhys(Position, AMemoryPerm.None)); + + Position += PageSize; + } + } + else + { + //Allocate extra needed size. + Position += HeapSize; + Size -= HeapSize; + + MapPhys(Position, Size, Type, AMemoryPerm.RW); + } + + HeapSize = Size; + } + + public bool MapPhys(long Src, long Dst, long Size, int Type, AMemoryPerm Perm) + { + Src = AMemoryHelper.PageRoundDown(Src); + Dst = AMemoryHelper.PageRoundDown(Dst); + + Size = AMemoryHelper.PageRoundUp(Size); + + if (Dst < 0 || Dst + Size >= RamSize) + { + return false; + } + + long PagesCount = Size / PageSize; + + while (PagesCount-- > 0) + { + SetPTEntry(Src, new PTEntry(Dst, Type, PTMap.Physical, Perm)); + + Src += PageSize; + Dst += PageSize; + } + + return true; + } + + public void MapPhys(long Position, long Size, int Type, AMemoryPerm Perm) + { + while (Size > 0) + { + if (!HasPTEntry(Position)) + { + long PhysPos = Allocator.Alloc(PageSize); + + SetPTEntry(Position, new PTEntry(PhysPos, Type, PTMap.Physical, Perm)); + } + + long CPgSize = PageSize - (Position & PageMask); + + Position += CPgSize; + Size -= CPgSize; + } + } + + public void MapMirror(long Src, long Dst, long Size, int Type) + { + Src = AMemoryHelper.PageRoundDown(Src); + Dst = AMemoryHelper.PageRoundDown(Dst); + + Size = AMemoryHelper.PageRoundUp(Size); + + long PagesCount = Size / PageSize; + + while (PagesCount-- > 0) + { + PTEntry Entry = GetPTEntry(Src); + + Entry.Type = Type; + Entry.Map = PTMap.Mirror; + Entry.Position = Dst; + + SetPTEntry(Src, Entry); + + Src += PageSize; + Dst += PageSize; + } + } + + public void Reprotect(long Position, long Size, AMemoryPerm Perm) + { + Position = AMemoryHelper.PageRoundDown(Position); + + Size = AMemoryHelper.PageRoundUp(Size); + + long PagesCount = Size / PageSize; + + while (PagesCount-- > 0) + { + PTEntry Entry = GetPTEntry(Position); + + Entry.Perm = Perm; + + SetPTEntry(Position, Entry); + + Position += PageSize; + } + } + + public AMemoryMapInfo GetMapInfo(long Position) + { + Position = AMemoryHelper.PageRoundDown(Position); + + PTEntry BaseEntry = GetPTEntry(Position); + + bool IsSameSegment(long Pos) + { + PTEntry Entry = GetPTEntry(Pos); + + return Entry.Type == BaseEntry.Type && + Entry.Map == BaseEntry.Map && + Entry.Perm == BaseEntry.Perm; + } + + long Start = Position; + long End = Position + PageSize; + + while (Start > 0 && IsSameSegment(Start - PageSize)) + { + Start -= PageSize; + } + + while (End < AddrSize && IsSameSegment(End)) + { + End += PageSize; + } + + long Size = End - Start; + + return new AMemoryMapInfo(Start, Size, BaseEntry.Type, BaseEntry.Perm); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long GetPhys(long Position, AMemoryPerm Perm) + { + if (!HasPTEntry(Position)) + { + if (Position < 0x08000000) + { + Console.WriteLine($"HACK: Ignoring bad access at {Position:x16}"); + + return 0; + } + + throw new VmmPageFaultException(Position); + } + + PTEntry Entry = GetPTEntry(Position); + + long AbsPos = Entry.Position + (Position & PageMask); + + if (Entry.Map == PTMap.Mirror) + { + return GetPhys(AbsPos, Perm); + } + + if (Entry.Map == PTMap.Unmapped) + { + throw new VmmPageFaultException(Position); + } + + return AbsPos; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool HasPTEntry(long Position) + { + if (Position >> PTLvl0Bits + PTLvl1Bits + PTPageBits != 0) + { + return false; + } + + long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask; + long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask; + + if (PageTable[L0] == null) + { + return false; + } + + return PageTable[L0][L1].Map != PTMap.Unmapped; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private PTEntry GetPTEntry(long Position) + { + long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask; + long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask; + + if (PageTable[L0] == null) + { + return default(PTEntry); + } + + return PageTable[L0][L1]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetPTEntry(long Position, PTEntry Entry) + { + long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask; + long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask; + + if (PageTable[L0] == null) + { + PageTable[L0] = new PTEntry[PTLvl1Size]; + } + + PageTable[L0][L1] = Entry; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Memory/AMemoryPerm.cs b/Ryujinx/Cpu/Memory/AMemoryPerm.cs new file mode 100644 index 000000000..b425eb94b --- /dev/null +++ b/Ryujinx/Cpu/Memory/AMemoryPerm.cs @@ -0,0 +1,15 @@ +using System; + +namespace ChocolArm64.Memory +{ + [Flags] + public enum AMemoryPerm + { + None = 0, + Read = 1 << 0, + Write = 1 << 1, + Execute = 1 << 2, + RW = Read | Write, + RX = Read | Execute + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/State/ACoreType.cs b/Ryujinx/Cpu/State/ACoreType.cs new file mode 100644 index 000000000..3fed78cf6 --- /dev/null +++ b/Ryujinx/Cpu/State/ACoreType.cs @@ -0,0 +1,8 @@ +namespace ChocolArm64.State +{ + public enum ACoreType + { + CortexA53, + CortexA57 + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/State/APState.cs b/Ryujinx/Cpu/State/APState.cs new file mode 100644 index 000000000..f55431a66 --- /dev/null +++ b/Ryujinx/Cpu/State/APState.cs @@ -0,0 +1,23 @@ +using System; + +namespace ChocolArm64.State +{ + [Flags] + public enum APState + { + VBit = 28, + CBit = 29, + ZBit = 30, + NBit = 31, + + V = 1 << VBit, + C = 1 << CBit, + Z = 1 << ZBit, + N = 1 << NBit, + + NZ = N | Z, + CV = C | V, + + NZCV = NZ | CV + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/State/ARegister.cs b/Ryujinx/Cpu/State/ARegister.cs new file mode 100644 index 000000000..5146bc313 --- /dev/null +++ b/Ryujinx/Cpu/State/ARegister.cs @@ -0,0 +1,142 @@ +using System; +using System.Reflection; + +namespace ChocolArm64.State +{ + struct ARegister + { + public int Index; + + public ARegisterType Type; + + public ARegister(int Index, ARegisterType Type) + { + this.Index = Index; + this.Type = Type; + } + + public override int GetHashCode() + { + return (ushort)Index | ((ushort)Type << 16); + } + + public override bool Equals(object Obj) + { + return Obj is ARegister Reg && + Reg.Index == Index && + Reg.Type == Type; + } + + public FieldInfo GetField() + { + switch (Type) + { + case ARegisterType.Flag: return GetFieldFlag(); + case ARegisterType.Int: return GetFieldInt(); + case ARegisterType.Vector: return GetFieldVector(); + } + + throw new InvalidOperationException(); + } + + private FieldInfo GetFieldFlag() + { + switch ((APState)Index) + { + case APState.VBit: return GetField(nameof(ARegisters.Overflow)); + case APState.CBit: return GetField(nameof(ARegisters.Carry)); + case APState.ZBit: return GetField(nameof(ARegisters.Zero)); + case APState.NBit: return GetField(nameof(ARegisters.Negative)); + } + + throw new InvalidOperationException(); + } + + private FieldInfo GetFieldInt() + { + switch (Index) + { + case 0: return GetField(nameof(ARegisters.X0)); + case 1: return GetField(nameof(ARegisters.X1)); + case 2: return GetField(nameof(ARegisters.X2)); + case 3: return GetField(nameof(ARegisters.X3)); + case 4: return GetField(nameof(ARegisters.X4)); + case 5: return GetField(nameof(ARegisters.X5)); + case 6: return GetField(nameof(ARegisters.X6)); + case 7: return GetField(nameof(ARegisters.X7)); + case 8: return GetField(nameof(ARegisters.X8)); + case 9: return GetField(nameof(ARegisters.X9)); + case 10: return GetField(nameof(ARegisters.X10)); + case 11: return GetField(nameof(ARegisters.X11)); + case 12: return GetField(nameof(ARegisters.X12)); + case 13: return GetField(nameof(ARegisters.X13)); + case 14: return GetField(nameof(ARegisters.X14)); + case 15: return GetField(nameof(ARegisters.X15)); + case 16: return GetField(nameof(ARegisters.X16)); + case 17: return GetField(nameof(ARegisters.X17)); + case 18: return GetField(nameof(ARegisters.X18)); + case 19: return GetField(nameof(ARegisters.X19)); + case 20: return GetField(nameof(ARegisters.X20)); + case 21: return GetField(nameof(ARegisters.X21)); + case 22: return GetField(nameof(ARegisters.X22)); + case 23: return GetField(nameof(ARegisters.X23)); + case 24: return GetField(nameof(ARegisters.X24)); + case 25: return GetField(nameof(ARegisters.X25)); + case 26: return GetField(nameof(ARegisters.X26)); + case 27: return GetField(nameof(ARegisters.X27)); + case 28: return GetField(nameof(ARegisters.X28)); + case 29: return GetField(nameof(ARegisters.X29)); + case 30: return GetField(nameof(ARegisters.X30)); + case 31: return GetField(nameof(ARegisters.X31)); + } + + throw new InvalidOperationException(); + } + + private FieldInfo GetFieldVector() + { + switch (Index) + { + case 0: return GetField(nameof(ARegisters.V0)); + case 1: return GetField(nameof(ARegisters.V1)); + case 2: return GetField(nameof(ARegisters.V2)); + case 3: return GetField(nameof(ARegisters.V3)); + case 4: return GetField(nameof(ARegisters.V4)); + case 5: return GetField(nameof(ARegisters.V5)); + case 6: return GetField(nameof(ARegisters.V6)); + case 7: return GetField(nameof(ARegisters.V7)); + case 8: return GetField(nameof(ARegisters.V8)); + case 9: return GetField(nameof(ARegisters.V9)); + case 10: return GetField(nameof(ARegisters.V10)); + case 11: return GetField(nameof(ARegisters.V11)); + case 12: return GetField(nameof(ARegisters.V12)); + case 13: return GetField(nameof(ARegisters.V13)); + case 14: return GetField(nameof(ARegisters.V14)); + case 15: return GetField(nameof(ARegisters.V15)); + case 16: return GetField(nameof(ARegisters.V16)); + case 17: return GetField(nameof(ARegisters.V17)); + case 18: return GetField(nameof(ARegisters.V18)); + case 19: return GetField(nameof(ARegisters.V19)); + case 20: return GetField(nameof(ARegisters.V20)); + case 21: return GetField(nameof(ARegisters.V21)); + case 22: return GetField(nameof(ARegisters.V22)); + case 23: return GetField(nameof(ARegisters.V23)); + case 24: return GetField(nameof(ARegisters.V24)); + case 25: return GetField(nameof(ARegisters.V25)); + case 26: return GetField(nameof(ARegisters.V26)); + case 27: return GetField(nameof(ARegisters.V27)); + case 28: return GetField(nameof(ARegisters.V28)); + case 29: return GetField(nameof(ARegisters.V29)); + case 30: return GetField(nameof(ARegisters.V30)); + case 31: return GetField(nameof(ARegisters.V31)); + } + + throw new InvalidOperationException(); + } + + private FieldInfo GetField(string Name) + { + return typeof(ARegisters).GetField(Name); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/State/ARegisterSize.cs b/Ryujinx/Cpu/State/ARegisterSize.cs new file mode 100644 index 000000000..144f36b92 --- /dev/null +++ b/Ryujinx/Cpu/State/ARegisterSize.cs @@ -0,0 +1,10 @@ +namespace ChocolArm64.State +{ + enum ARegisterSize + { + Int32, + Int64, + SIMD64, + SIMD128 + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/State/ARegisterType.cs b/Ryujinx/Cpu/State/ARegisterType.cs new file mode 100644 index 000000000..f9776bb7d --- /dev/null +++ b/Ryujinx/Cpu/State/ARegisterType.cs @@ -0,0 +1,9 @@ +namespace ChocolArm64.State +{ + enum ARegisterType + { + Flag, + Int, + Vector + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/State/ARegisters.cs b/Ryujinx/Cpu/State/ARegisters.cs new file mode 100644 index 000000000..3a424a7f9 --- /dev/null +++ b/Ryujinx/Cpu/State/ARegisters.cs @@ -0,0 +1,126 @@ +using System; + +namespace ChocolArm64.State +{ + public class ARegisters + { + internal const int LRIndex = 30; + internal const int ZRIndex = 31; + + public ulong X0, X1, X2, X3, X4, X5, X6, X7, + X8, X9, X10, X11, X12, X13, X14, X15, + X16, X17, X18, X19, X20, X21, X22, X23, + X24, X25, X26, X27, X28, X29, X30, X31; + + public AVec V0, V1, V2, V3, V4, V5, V6, V7, + V8, V9, V10, V11, V12, V13, V14, V15, + V16, V17, V18, V19, V20, V21, V22, V23, + V24, V25, V26, V27, V28, V29, V30, V31; + + public bool Overflow; + public bool Carry; + public bool Zero; + public bool Negative; + + public int ProcessId; + public int ThreadId; + public long TlsAddrEl0; + public long TlsAddr; + + private int FPCR; + private int FPSR; + + public ACoreType CoreType; + + private const ulong A53DczidEl0 = 4; + private const ulong A53CtrEl0 = 0x84448004; + private const ulong A57CtrEl0 = 0x8444c004; + + private const ulong TicksPerS = 19_200_000; + private const ulong TicksPerMS = TicksPerS / 1_000; + + public event EventHandler SvcCall; + public event EventHandler Undefined; + + public ulong GetSystemReg(int Op0, int Op1, int CRn, int CRm, int Op2) + { + switch (PackRegId(Op0, Op1, CRn, CRm, Op2)) + { + case 0b11_011_0000_0000_001: return GetCtrEl0(); + case 0b11_011_0000_0000_111: return GetDczidEl0(); + case 0b11_011_0100_0100_000: return (ulong)PackFPCR(); + case 0b11_011_0100_0100_001: return (ulong)PackFPSR(); + case 0b11_011_1101_0000_010: return (ulong)TlsAddrEl0; + case 0b11_011_1101_0000_011: return (ulong)TlsAddr; + case 0b11_011_1110_0000_001: return (ulong)Environment.TickCount * TicksPerMS; + + default: throw new ArgumentException(); + } + } + + public void SetSystemReg(int Op0, int Op1, int CRn, int CRm, int Op2, ulong Value) + { + switch (PackRegId(Op0, Op1, CRn, CRm, Op2)) + { + case 0b11_011_0100_0100_000: UnpackFPCR((int)Value); break; + case 0b11_011_0100_0100_001: UnpackFPSR((int)Value); break; + case 0b11_011_1101_0000_010: TlsAddrEl0 = (long)Value; break; + + default: throw new ArgumentException(); + } + } + + private int PackRegId(int Op0, int Op1, int CRn, int CRm, int Op2) + { + int Id; + + Id = Op2 << 0; + Id |= CRm << 3; + Id |= CRn << 7; + Id |= Op1 << 11; + Id |= Op0 << 14; + + return Id; + } + + public ulong GetCtrEl0() + { + return CoreType == ACoreType.CortexA53 ? A53CtrEl0 : A57CtrEl0; + } + + public ulong GetDczidEl0() + { + return A53DczidEl0; + } + + public int PackFPCR() + { + return FPCR; //TODO + } + + public int PackFPSR() + { + return FPSR; //TODO + } + + public void UnpackFPCR(int Value) + { + FPCR = Value; + } + + public void UnpackFPSR(int Value) + { + FPSR = Value; + } + + public void OnSvcCall(int Imm) + { + SvcCall?.Invoke(this, new SvcEventArgs(Imm)); + } + + public void OnUndefined() + { + Undefined?.Invoke(this, EventArgs.Empty); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/State/AVec.cs b/Ryujinx/Cpu/State/AVec.cs new file mode 100644 index 000000000..f7eb2e221 --- /dev/null +++ b/Ryujinx/Cpu/State/AVec.cs @@ -0,0 +1,243 @@ +using System; +using System.Runtime.InteropServices; + +namespace ChocolArm64.State +{ + [StructLayout(LayoutKind.Explicit, Size = 16)] + public struct AVec + { + [FieldOffset(0x0)] public byte B0; + [FieldOffset(0x1)] public byte B1; + [FieldOffset(0x2)] public byte B2; + [FieldOffset(0x3)] public byte B3; + [FieldOffset(0x4)] public byte B4; + [FieldOffset(0x5)] public byte B5; + [FieldOffset(0x6)] public byte B6; + [FieldOffset(0x7)] public byte B7; + [FieldOffset(0x8)] public byte B8; + [FieldOffset(0x9)] public byte B9; + [FieldOffset(0xa)] public byte B10; + [FieldOffset(0xb)] public byte B11; + [FieldOffset(0xc)] public byte B12; + [FieldOffset(0xd)] public byte B13; + [FieldOffset(0xe)] public byte B14; + [FieldOffset(0xf)] public byte B15; + + [FieldOffset(0x0)] public ushort H0; + [FieldOffset(0x2)] public ushort H1; + [FieldOffset(0x4)] public ushort H2; + [FieldOffset(0x6)] public ushort H3; + [FieldOffset(0x8)] public ushort H4; + [FieldOffset(0xa)] public ushort H5; + [FieldOffset(0xc)] public ushort H6; + [FieldOffset(0xe)] public ushort H7; + + [FieldOffset(0x0)] public uint W0; + [FieldOffset(0x4)] public uint W1; + [FieldOffset(0x8)] public uint W2; + [FieldOffset(0xc)] public uint W3; + + [FieldOffset(0x0)] public float S0; + [FieldOffset(0x4)] public float S1; + [FieldOffset(0x8)] public float S2; + [FieldOffset(0xc)] public float S3; + + [FieldOffset(0x0)] public ulong X0; + [FieldOffset(0x8)] public ulong X1; + + [FieldOffset(0x0)] public double D0; + [FieldOffset(0x8)] public double D1; + + public byte ExtractByte(int Index) + { + switch (Index) + { + case 0: return B0; + case 1: return B1; + case 2: return B2; + case 3: return B3; + case 4: return B4; + case 5: return B5; + case 6: return B6; + case 7: return B7; + case 8: return B8; + case 9: return B9; + case 10: return B10; + case 11: return B11; + case 12: return B12; + case 13: return B13; + case 14: return B14; + case 15: return B15; + } + + throw new ArgumentOutOfRangeException(nameof(Index)); + } + + public ushort ExtractUInt16(int Index) + { + switch (Index) + { + case 0: return H0; + case 1: return H1; + case 2: return H2; + case 3: return H3; + case 4: return H4; + case 5: return H5; + case 6: return H6; + case 7: return H7; + } + + throw new ArgumentOutOfRangeException(nameof(Index)); + } + + public uint ExtractUInt32(int Index) + { + switch (Index) + { + case 0: return W0; + case 1: return W1; + case 2: return W2; + case 3: return W3; + } + + throw new ArgumentOutOfRangeException(nameof(Index)); + } + + public float ExtractSingle(int Index) + { + switch (Index) + { + case 0: return S0; + case 1: return S1; + case 2: return S2; + case 3: return S3; + } + + throw new ArgumentOutOfRangeException(nameof(Index)); + } + + public ulong ExtractUInt64(int Index) + { + switch (Index) + { + case 0: return X0; + case 1: return X1; + } + + throw new ArgumentOutOfRangeException(nameof(Index)); + } + + public double ExtractDouble(int Index) + { + switch (Index) + { + case 0: return D0; + case 1: return D1; + } + + throw new ArgumentOutOfRangeException(nameof(Index)); + } + + public static AVec InsertByte(AVec Vec, int Index, byte Value) + { + switch (Index) + { + case 0: Vec.B0 = Value; break; + case 1: Vec.B1 = Value; break; + case 2: Vec.B2 = Value; break; + case 3: Vec.B3 = Value; break; + case 4: Vec.B4 = Value; break; + case 5: Vec.B5 = Value; break; + case 6: Vec.B6 = Value; break; + case 7: Vec.B7 = Value; break; + case 8: Vec.B8 = Value; break; + case 9: Vec.B9 = Value; break; + case 10: Vec.B10 = Value; break; + case 11: Vec.B11 = Value; break; + case 12: Vec.B12 = Value; break; + case 13: Vec.B13 = Value; break; + case 14: Vec.B14 = Value; break; + case 15: Vec.B15 = Value; break; + + default: throw new ArgumentOutOfRangeException(nameof(Index)); + } + + return Vec; + } + + public static AVec InsertUInt16(AVec Vec, int Index, ushort Value) + { + switch (Index) + { + case 0: Vec.H0 = Value; break; + case 1: Vec.H1 = Value; break; + case 2: Vec.H2 = Value; break; + case 3: Vec.H3 = Value; break; + case 4: Vec.H4 = Value; break; + case 5: Vec.H5 = Value; break; + case 6: Vec.H6 = Value; break; + case 7: Vec.H7 = Value; break; + + default: throw new ArgumentOutOfRangeException(nameof(Index)); + } + + return Vec; + } + + public static AVec InsertUInt32(AVec Vec, int Index, uint Value) + { + switch (Index) + { + case 0: Vec.W0 = Value; break; + case 1: Vec.W1 = Value; break; + case 2: Vec.W2 = Value; break; + case 3: Vec.W3 = Value; break; + + default: throw new ArgumentOutOfRangeException(nameof(Index)); + } + + return Vec; + } + + public static AVec InsertSingle(AVec Vec, int Index, float Value) + { + switch (Index) + { + case 0: Vec.S0 = Value; break; + case 1: Vec.S1 = Value; break; + case 2: Vec.S2 = Value; break; + case 3: Vec.S3 = Value; break; + + default: throw new ArgumentOutOfRangeException(nameof(Index)); + } + + return Vec; + } + + public static AVec InsertUInt64(AVec Vec, int Index, ulong Value) + { + switch (Index) + { + case 0: Vec.X0 = Value; break; + case 1: Vec.X1 = Value; break; + + default: throw new ArgumentOutOfRangeException(nameof(Index)); + } + + return Vec; + } + + public static AVec InsertDouble(AVec Vec, int Index, double Value) + { + switch (Index) + { + case 0: Vec.D0 = Value; break; + case 1: Vec.D1 = Value; break; + + default: throw new ArgumentOutOfRangeException(nameof(Index)); + } + + return Vec; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/State/SvcEventArgs.cs b/Ryujinx/Cpu/State/SvcEventArgs.cs new file mode 100644 index 000000000..3a43241a6 --- /dev/null +++ b/Ryujinx/Cpu/State/SvcEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace ChocolArm64.State +{ + public class SvcEventArgs : EventArgs + { + public int Id { get; private set; } + + public SvcEventArgs(int Id) + { + this.Id = Id; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Translation/AILBlock.cs b/Ryujinx/Cpu/Translation/AILBlock.cs new file mode 100644 index 000000000..2746e4288 --- /dev/null +++ b/Ryujinx/Cpu/Translation/AILBlock.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; + +namespace ChocolArm64.Translation +{ + class AILBlock : IAILEmit + { + public long IntInputs { get; private set; } + public long IntOutputs { get; private set; } + + public long VecInputs { get; private set; } + public long VecOutputs { get; private set; } + + public bool HasStateStore { get; private set; } + + public List ILEmitters { get; private set; } + + public AILBlock Next { get; set; } + public AILBlock Branch { get; set; } + + public AILBlock() + { + ILEmitters = new List(); + } + + public void Add(IAILEmit ILEmitter) + { + if (ILEmitter is AILOpCodeLoad Ld && AILEmitter.IsRegIndex(Ld.Index)) + { + switch (Ld.IoType & AIoType.Mask) + { + case AIoType.Flag: IntInputs |= ((1L << Ld.Index) << 32) & ~IntOutputs; break; + case AIoType.Int: IntInputs |= (1L << Ld.Index) & ~IntOutputs; break; + case AIoType.Vector: VecInputs |= (1L << Ld.Index) & ~VecOutputs; break; + } + } + else if (ILEmitter is AILOpCodeStore St) + { + if (AILEmitter.IsRegIndex(St.Index)) + { + switch (St.IoType & AIoType.Mask) + { + case AIoType.Flag: IntOutputs |= (1L << St.Index) << 32; break; + case AIoType.Int: IntOutputs |= 1L << St.Index; break; + case AIoType.Vector: VecOutputs |= 1L << St.Index; break; + } + } + + if (St.IoType == AIoType.Fields) + { + HasStateStore = true; + } + } + + ILEmitters.Add(ILEmitter); + } + + public void Emit(AILEmitter Context) + { + foreach (IAILEmit ILEmitter in ILEmitters) + { + ILEmitter.Emit(Context); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Translation/AILConv.cs b/Ryujinx/Cpu/Translation/AILConv.cs new file mode 100644 index 000000000..8969dc4e9 --- /dev/null +++ b/Ryujinx/Cpu/Translation/AILConv.cs @@ -0,0 +1,113 @@ +using ChocolArm64.State; +using System; +using System.Reflection; +using System.Reflection.Emit; + +namespace ChocolArm64.Translation +{ + static class AILConv + { + public static void EmitConv(AILEmitter Context, Type SrcType, Type TgtType) + { + if (SrcType == TgtType) + { + //If both types are equal we don't need to cast anything. + return; + } + + if (SrcType.IsPrimitive) + { + if (TgtType == typeof(byte)) + { + Context.Generator.Emit(OpCodes.Conv_U1); + } + else if (TgtType == typeof(ushort)) + { + Context.Generator.Emit(OpCodes.Conv_U2); + } + else if (TgtType == typeof(uint)) + { + Context.Generator.Emit(OpCodes.Conv_U4); + } + else if (TgtType == typeof(ulong)) + { + Context.Generator.Emit(OpCodes.Conv_U8); + } + else if (TgtType == typeof(float)) + { + Context.Generator.Emit(OpCodes.Conv_R4); + } + else if (TgtType == typeof(double)) + { + Context.Generator.Emit(OpCodes.Conv_R8); + } + else if (TgtType == typeof(AVec)) + { + EmitMakeVec(Context, SrcType); + } + else + { + throw new ArgumentException(nameof(TgtType)); + } + } + else if (SrcType == typeof(AVec)) + { + if (TgtType == typeof(float)) + { + EmitScalarLdfld(Context, nameof(AVec.S0)); + } + else if (TgtType == typeof(double)) + { + EmitScalarLdfld(Context, nameof(AVec.D0)); + } + else if (TgtType == typeof(byte)) + { + EmitScalarLdfld(Context, nameof(AVec.B0)); + } + else if (TgtType == typeof(ushort)) + { + EmitScalarLdfld(Context, nameof(AVec.H0)); + } + else if (TgtType == typeof(uint)) + { + EmitScalarLdfld(Context, nameof(AVec.W0)); + } + else if (TgtType == typeof(ulong)) + { + EmitScalarLdfld(Context, nameof(AVec.X0)); + } + else + { + throw new ArgumentException(nameof(TgtType)); + } + } + else + { + throw new ArgumentException(nameof(SrcType)); + } + } + + private static void EmitScalarLdfld(AILEmitter Context,string FldName) + { + Context.Generator.Emit(OpCodes.Ldfld, typeof(AVec).GetField(FldName)); + } + + private static void EmitMakeVec(AILEmitter Context, Type SrcType) + { + string MthdName = nameof(MakeScalar); + + Type[] MthdTypes = new Type[] { SrcType }; + + MethodInfo MthdInfo = typeof(AILConv).GetMethod(MthdName, MthdTypes); + + Context.Generator.Emit(OpCodes.Call, MthdInfo); + } + + public static AVec MakeScalar(byte Value) => new AVec { B0 = Value }; + public static AVec MakeScalar(ushort Value) => new AVec { H0 = Value }; + public static AVec MakeScalar(uint Value) => new AVec { W0 = Value }; + public static AVec MakeScalar(float Value) => new AVec { S0 = Value }; + public static AVec MakeScalar(ulong Value) => new AVec { X0 = Value }; + public static AVec MakeScalar(double Value) => new AVec { D0 = Value }; + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Translation/AILEmitter.cs b/Ryujinx/Cpu/Translation/AILEmitter.cs new file mode 100644 index 000000000..0619149c3 --- /dev/null +++ b/Ryujinx/Cpu/Translation/AILEmitter.cs @@ -0,0 +1,190 @@ +using ChocolArm64.Decoder; +using ChocolArm64.State; +using System; +using System.Collections.Generic; +using System.Reflection.Emit; + +namespace ChocolArm64.Translation +{ + class AILEmitter + { + public ALocalAlloc LocalAlloc { get; private set; } + + public ILGenerator Generator { get; private set; } + + private Dictionary Locals; + + private AILBlock[] ILBlocks; + + private AILBlock Root; + + private ATranslatedSub Subroutine; + + private string SubName; + + private int LocalsCount; + + public AILEmitter(ABlock[] Graph, ABlock Root, string SubName) + { + this.SubName = SubName; + + Locals = new Dictionary(); + + ILBlocks = new AILBlock[Graph.Length]; + + AILBlock GetBlock(int Index) + { + if (Index < 0 || Index >= ILBlocks.Length) + { + return null; + } + + if (ILBlocks[Index] == null) + { + ILBlocks[Index] = new AILBlock(); + } + + return ILBlocks[Index]; + } + + for (int Index = 0; Index < ILBlocks.Length; Index++) + { + AILBlock Block = GetBlock(Index); + + Block.Next = GetBlock(Array.IndexOf(Graph, Graph[Index].Next)); + Block.Branch = GetBlock(Array.IndexOf(Graph, Graph[Index].Branch)); + } + + this.Root = ILBlocks[Array.IndexOf(Graph, Root)]; + } + + public ATranslatedSub GetSubroutine() + { + LocalAlloc = new ALocalAlloc(ILBlocks, Root); + + InitSubroutine(); + InitLocals(); + + foreach (AILBlock ILBlock in ILBlocks) + { + ILBlock.Emit(this); + } + + return Subroutine; + } + + public AILBlock GetILBlock(int Index) => ILBlocks[Index]; + + private void InitLocals() + { + int ParamsStart = ATranslatedSub.FixedArgTypes.Length; + + Locals = new Dictionary(); + + for (int Index = 0; Index < Subroutine.Params.Count; Index++) + { + ARegister Reg = Subroutine.Params[Index]; + + Generator.EmitLdarg(Index + ParamsStart); + + AILConv.EmitConv(this, GetFieldType(Reg.Type), GetLocalType(Reg)); + + Generator.EmitStloc(GetLocalIndex(Reg)); + } + } + + private void InitSubroutine() + { + List Params = new List(); + + void SetParams(long Inputs, ARegisterType BaseType) + { + for (int Bit = 0; Bit < 64; Bit++) + { + long Mask = 1L << Bit; + + if ((Inputs & Mask) != 0) + { + Params.Add(GetRegFromBit(Bit, BaseType)); + } + } + } + + SetParams(LocalAlloc.GetIntInputs(Root), ARegisterType.Int); + SetParams(LocalAlloc.GetVecInputs(Root), ARegisterType.Vector); + + DynamicMethod Mthd = new DynamicMethod(SubName, typeof(long), GetParamTypes(Params)); + + Generator = Mthd.GetILGenerator(); + + Subroutine = new ATranslatedSub(Mthd, Params); + } + + private Type[] GetParamTypes(IList Params) + { + Type[] FixedArgs = ATranslatedSub.FixedArgTypes; + + Type[] Output = new Type[Params.Count + FixedArgs.Length]; + + FixedArgs.CopyTo(Output, 0); + + int TypeIdx = FixedArgs.Length; + + for (int Index = 0; Index < Params.Count; Index++) + { + Output[TypeIdx++] = GetFieldType(Params[Index].Type); + } + + return Output; + } + + public int GetLocalIndex(ARegister Reg) + { + if (!Locals.TryGetValue(Reg, out int Index)) + { + Generator.DeclareLocal(GetLocalType(Reg)); + + Index = LocalsCount++; + + Locals.Add(Reg, Index); + } + + return Index; + } + + public Type GetLocalType(ARegister Reg) => GetFieldType(Reg.Type); + + public Type GetFieldType(ARegisterType RegType) + { + switch (RegType) + { + case ARegisterType.Flag: return typeof(bool); + case ARegisterType.Int: return typeof(ulong); + case ARegisterType.Vector: return typeof(AVec); + } + + throw new ArgumentException(nameof(RegType)); + } + + public static ARegister GetRegFromBit(int Bit, ARegisterType BaseType) + { + if (Bit < 32) + { + return new ARegister(Bit, BaseType); + } + else if (BaseType == ARegisterType.Int) + { + return new ARegister(Bit & 0x1f, ARegisterType.Flag); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Bit)); + } + } + + public static bool IsRegIndex(int Index) + { + return Index >= 0 && Index < 32; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Translation/AILEmitterCtx.cs b/Ryujinx/Cpu/Translation/AILEmitterCtx.cs new file mode 100644 index 000000000..88841db77 --- /dev/null +++ b/Ryujinx/Cpu/Translation/AILEmitterCtx.cs @@ -0,0 +1,548 @@ +using ChocolArm64.Decoder; +using ChocolArm64.Instruction; +using ChocolArm64.State; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; + +namespace ChocolArm64.Translation +{ + class AILEmitterCtx + { + private ATranslator Translator; + + private Dictionary Labels; + + private AILEmitter Emitter; + + private AILBlock ILBlock; + + private AOpCode LastCmpOp; + private AOpCode LastFlagOp; + + private int BlkIndex; + private int OpcIndex; + + private ABlock[] Graph; + private ABlock Root; + public ABlock CurrBlock => Graph[BlkIndex]; + public AOpCode CurrOp => Graph[BlkIndex].OpCodes[OpcIndex]; + + //This is the index of the temporary register, used to store temporary + //values needed by some functions, since IL doesn't have a swap instruction. + //You can use any value here as long it doesn't conflict with the indices + //for the other registers. Any value >= 64 or < 0 will do. + private const int Tmp1Index = -1; + private const int Tmp2Index = -2; + private const int Tmp3Index = -3; + private const int Tmp4Index = -4; + + public AILEmitterCtx(ATranslator Translator, ABlock[] Graph, ABlock Root) + { + this.Translator = Translator; + this.Graph = Graph; + this.Root = Root; + + string SubName = $"Sub{Root.Position:X16}"; + + Labels = new Dictionary(); + + Emitter = new AILEmitter(Graph, Root, SubName); + + ILBlock = Emitter.GetILBlock(0); + + OpcIndex = -1; + + if (!AdvanceOpCode()) + { + throw new ArgumentException(nameof(Graph)); + } + } + + public ATranslatedSub GetSubroutine() => Emitter.GetSubroutine(); + + public bool AdvanceOpCode() + { + while (++OpcIndex >= (CurrBlock?.OpCodes.Count ?? 0)) + { + if (BlkIndex + 1 >= Graph.Length) + { + return false; + } + + BlkIndex++; + OpcIndex = -1; + + ILBlock = Emitter.GetILBlock(BlkIndex); + } + + return true; + } + + public void EmitOpCode() + { + if (OpcIndex == 0) + { + MarkLabel(GetLabel(CurrBlock.Position)); + } + + CurrOp.Emitter(this); + } + + public bool TryOptEmitSubroutineCall() + { + if (!Translator.TryGetCachedSub(CurrOp, out ATranslatedSub Sub)) + { + return false; + } + + for (int Index = 0; Index < ATranslatedSub.FixedArgTypes.Length; Index++) + { + EmitLdarg(Index); + } + + foreach (ARegister Reg in Sub.Params) + { + switch (Reg.Type) + { + case ARegisterType.Flag: Ldloc(Reg.Index, AIoType.Flag); break; + case ARegisterType.Int: Ldloc(Reg.Index, AIoType.Int); break; + case ARegisterType.Vector: Ldloc(Reg.Index, AIoType.Vector); break; + } + } + + EmitCall(Sub.Method); + + return true; + } + + public void TryOptMarkCondWithoutCmp() + { + LastCmpOp = CurrOp; + + AInstEmitAluHelper.EmitDataLoadOpers(this); + + Stloc(Tmp4Index, AIoType.Int); + Stloc(Tmp3Index, AIoType.Int); + } + + private Dictionary BranchOps = new Dictionary() + { + { ACond.Eq, OpCodes.Beq }, + { ACond.Ne, OpCodes.Bne_Un }, + { ACond.Ge_Un, OpCodes.Bge_Un }, + { ACond.Lt_Un, OpCodes.Blt_Un }, + { ACond.Gt_Un, OpCodes.Bgt_Un }, + { ACond.Le_Un, OpCodes.Ble_Un }, + { ACond.Ge, OpCodes.Bge }, + { ACond.Lt, OpCodes.Blt }, + { ACond.Gt, OpCodes.Bgt }, + { ACond.Le, OpCodes.Ble } + }; + + public void EmitCondBranch(AILLabel Target, ACond Cond) + { + OpCode ILOp; + + int IntCond = (int)Cond; + + if (LastFlagOp == LastCmpOp && BranchOps.ContainsKey(Cond)) + { + Ldloc(Tmp3Index, AIoType.Int, GetIntType(LastCmpOp)); + Ldloc(Tmp4Index, AIoType.Int, GetIntType(LastCmpOp)); + + if (LastCmpOp.Emitter == AInstEmit.Adds) + { + Emit(OpCodes.Neg); + } + + ILOp = BranchOps[Cond]; + } + else if (IntCond < 14) + { + int CondTrue = IntCond >> 1; + + switch (CondTrue) + { + case 0: EmitLdflg((int)APState.ZBit); break; + case 1: EmitLdflg((int)APState.CBit); break; + case 2: EmitLdflg((int)APState.NBit); break; + case 3: EmitLdflg((int)APState.VBit); break; + + case 4: + EmitLdflg((int)APState.CBit); + EmitLdflg((int)APState.ZBit); + + Emit(OpCodes.Not); + Emit(OpCodes.And); + break; + + case 5: + case 6: + EmitLdflg((int)APState.NBit); + EmitLdflg((int)APState.VBit); + + Emit(OpCodes.Ceq); + + if (CondTrue == 6) + { + EmitLdflg((int)APState.ZBit); + + Emit(OpCodes.Not); + Emit(OpCodes.And); + } + break; + } + + ILOp = (IntCond & 1) != 0 + ? OpCodes.Brfalse + : OpCodes.Brtrue; + } + else + { + ILOp = OpCodes.Br; + } + + Emit(ILOp, Target); + } + + public void EmitCast(AIntType IntType) + { + switch (IntType) + { + case AIntType.UInt8: Emit(OpCodes.Conv_U1); break; + case AIntType.UInt16: Emit(OpCodes.Conv_U2); break; + case AIntType.UInt32: Emit(OpCodes.Conv_U4); break; + case AIntType.UInt64: Emit(OpCodes.Conv_U8); break; + case AIntType.Int8: Emit(OpCodes.Conv_I1); break; + case AIntType.Int16: Emit(OpCodes.Conv_I2); break; + case AIntType.Int32: Emit(OpCodes.Conv_I4); break; + case AIntType.Int64: Emit(OpCodes.Conv_I8); break; + } + + if (IntType == AIntType.UInt64 || + IntType == AIntType.Int64) + { + return; + } + + if (CurrOp.RegisterSize != ARegisterSize.Int32) + { + Emit(IntType >= AIntType.Int8 + ? OpCodes.Conv_I8 + : OpCodes.Conv_U8); + } + } + + public void EmitLsl(int Amount) => EmitILShift(Amount, OpCodes.Shl); + public void EmitLsr(int Amount) => EmitILShift(Amount, OpCodes.Shr_Un); + public void EmitAsr(int Amount) => EmitILShift(Amount, OpCodes.Shr); + + private void EmitILShift(int Amount, OpCode ILOp) + { + if (Amount > 0) + { + EmitLdc_I4(Amount); + + Emit(ILOp); + } + } + + public void EmitRor(int Amount) + { + if (Amount > 0) + { + Stloc(Tmp2Index, AIoType.Int); + Ldloc(Tmp2Index, AIoType.Int); + + EmitLdc_I4(Amount); + + Emit(OpCodes.Shr_Un); + + Ldloc(Tmp2Index, AIoType.Int); + + EmitLdc_I4(CurrOp.GetBitsCount() - Amount); + + Emit(OpCodes.Shl); + Emit(OpCodes.Or); + } + } + + public AILLabel GetLabel(long Position) + { + if (!Labels.TryGetValue(Position, out AILLabel Output)) + { + Output = new AILLabel(); + + Labels.Add(Position, Output); + } + + return Output; + } + + public void MarkLabel(AILLabel Label) + { + ILBlock.Add(Label); + } + + public void Emit(OpCode ILOp) + { + ILBlock.Add(new AILOpCode(ILOp)); + } + + public void Emit(OpCode ILOp, AILLabel Label) + { + ILBlock.Add(new AILOpCodeBranch(ILOp, Label)); + } + + public void Emit(string Text) + { + ILBlock.Add(new AILOpCodeLog(Text)); + } + + public void EmitLdarg(int Index) + { + ILBlock.Add(new AILOpCodeLoad(Index, AIoType.Arg)); + } + + public void EmitLdintzr(int Index) + { + if (Index != ARegisters.ZRIndex) + { + EmitLdint(Index); + } + else + { + EmitLdc_I(0); + } + } + + public void EmitStintzr(int Index) + { + if (Index != ARegisters.ZRIndex) + { + EmitStint(Index); + } + else + { + Emit(OpCodes.Pop); + } + } + + public void EmitLoadState(ABlock RetBlk) + { + ILBlock.Add(new AILOpCodeLoad(Array.IndexOf(Graph, RetBlk), AIoType.Fields)); + } + + public void EmitStoreState() + { + ILBlock.Add(new AILOpCodeStore(Array.IndexOf(Graph, CurrBlock), AIoType.Fields)); + } + + public void EmitLdtmp() => EmitLdint(Tmp1Index); + public void EmitSttmp() => EmitStint(Tmp1Index); + + public void EmitLdint(int Index) => Ldloc(Index, AIoType.Int); + public void EmitStint(int Index) => Stloc(Index, AIoType.Int); + + public void EmitLdvec(int Index) => Ldloc(Index, AIoType.Vector); + public void EmitStvec(int Index) => Stloc(Index, AIoType.Vector); + + public void EmitLdvecsi(int Index) => Ldloc(Index, AIoType.VectorI); + public void EmitStvecsi(int Index) => Stloc(Index, AIoType.VectorI); + + public void EmitLdvecsf(int Index) => Ldloc(Index, AIoType.VectorF); + public void EmitStvecsf(int Index) => Stloc(Index, AIoType.VectorF); + + public void EmitLdflg(int Index) => Ldloc(Index, AIoType.Flag); + public void EmitStflg(int Index) + { + LastFlagOp = CurrOp; + + Stloc(Index, AIoType.Flag); + } + + private void Ldloc(int Index, AIoType IoType) + { + ILBlock.Add(new AILOpCodeLoad(Index, IoType, GetOperType(IoType))); + } + + private void Ldloc(int Index, AIoType IoType, Type Type) + { + ILBlock.Add(new AILOpCodeLoad(Index, IoType, Type)); + } + + private void Stloc(int Index, AIoType IoType) + { + ILBlock.Add(new AILOpCodeStore(Index, IoType, GetOutOperType(IoType))); + } + + private Type GetOutOperType(AIoType IoType) + { + //This instruction is used to convert between floating point + //types, so the input and output types are different. + if (CurrOp.Emitter == AInstEmit.Fcvt_S) + { + return GetFloatType(((AOpCodeSimd)CurrOp).Opc); + } + else + { + return GetOperType(IoType); + } + } + + private Type GetOperType(AIoType IoType) + { + switch (IoType & AIoType.Mask) + { + case AIoType.Flag: return typeof(bool); + case AIoType.Int: return GetIntType(CurrOp); + case AIoType.Vector: return GetVecType(CurrOp, IoType); + } + + throw new ArgumentException(nameof(IoType)); + } + + private Type GetIntType(AOpCode OpCode) + { + //Always default to 64-bits. + return OpCode.RegisterSize == ARegisterSize.Int32 + ? typeof(uint) + : typeof(ulong); + } + + private Type GetVecType(AOpCode OpCode, AIoType IoType) + { + if (!(OpCode is IAOpCodeSimd Op)) + { + return typeof(AVec); + } + + int Size = Op.Size; + + if (Op.Emitter == AInstEmit.Fmov_Ftoi || + Op.Emitter == AInstEmit.Fmov_Itof) + { + Size |= 2; + } + + if (Op is AOpCodeMem || Op is IAOpCodeLit) + { + return Size < 4 ? typeof(ulong) : typeof(AVec); + } + else if (IoType == AIoType.VectorI) + { + return GetIntType(Size); + } + else if (IoType == AIoType.VectorF) + { + return GetFloatType(Size); + } + + return typeof(AVec); + } + + private static Type GetIntType(int Size) + { + switch (Size) + { + case 0: return typeof(byte); + case 1: return typeof(ushort); + case 2: return typeof(uint); + case 3: return typeof(ulong); + } + + throw new ArgumentOutOfRangeException(nameof(Size)); + } + + private static Type GetFloatType(int Size) + { + switch (Size) + { + case 0: return typeof(float); + case 1: return typeof(double); + } + + throw new ArgumentOutOfRangeException(nameof(Size)); + } + + public void EmitCall(Type MthdType, string MthdName) + { + if (MthdType == null) + { + throw new ArgumentNullException(nameof(MthdType)); + } + + if (MthdName == null) + { + throw new ArgumentNullException(nameof(MthdName)); + } + + EmitCall(MthdType.GetMethod(MthdName)); + } + + public void EmitCall(MethodInfo MthdInfo) + { + if (MthdInfo == null) + { + throw new ArgumentNullException(nameof(MthdInfo)); + } + + ILBlock.Add(new AILOpCodeCall(MthdInfo)); + } + + public void EmitLdc_I(long Value) + { + if (CurrOp.RegisterSize == ARegisterSize.Int32) + { + EmitLdc_I4((int)Value); + } + else + { + EmitLdc_I8(Value); + } + } + + public void EmitLdc_I4(int Value) + { + ILBlock.Add(new AILOpCodeConst(Value)); + } + + public void EmitLdc_I8(long Value) + { + ILBlock.Add(new AILOpCodeConst(Value)); + } + + public void EmitLdc_R4(float Value) + { + ILBlock.Add(new AILOpCodeConst(Value)); + } + + public void EmitLdc_R8(double Value) + { + ILBlock.Add(new AILOpCodeConst(Value)); + } + + public void EmitZNFlagCheck() + { + EmitZNCheck(OpCodes.Ceq, (int)APState.ZBit); + EmitZNCheck(OpCodes.Clt, (int)APState.NBit); + } + + private void EmitZNCheck(OpCode ILCmpOp, int Flag) + { + Emit(OpCodes.Dup); + Emit(OpCodes.Ldc_I4_0); + + if (CurrOp.RegisterSize != ARegisterSize.Int32) + { + Emit(OpCodes.Conv_I8); + } + + Emit(ILCmpOp); + + EmitStflg(Flag); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Translation/AILLabel.cs b/Ryujinx/Cpu/Translation/AILLabel.cs new file mode 100644 index 000000000..0ee39ad7e --- /dev/null +++ b/Ryujinx/Cpu/Translation/AILLabel.cs @@ -0,0 +1,28 @@ +using System.Reflection.Emit; + +namespace ChocolArm64.Translation +{ + class AILLabel : IAILEmit + { + private bool HasLabel; + + private Label Lbl; + + public void Emit(AILEmitter Context) + { + Context.Generator.MarkLabel(GetLabel(Context)); + } + + public Label GetLabel(AILEmitter Context) + { + if (!HasLabel) + { + Lbl = Context.Generator.DefineLabel(); + + HasLabel = true; + } + + return Lbl; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Translation/AILOpCode.cs b/Ryujinx/Cpu/Translation/AILOpCode.cs new file mode 100644 index 000000000..a4bc93a06 --- /dev/null +++ b/Ryujinx/Cpu/Translation/AILOpCode.cs @@ -0,0 +1,19 @@ +using System.Reflection.Emit; + +namespace ChocolArm64.Translation +{ + struct AILOpCode : IAILEmit + { + private OpCode ILOp; + + public AILOpCode(OpCode ILOp) + { + this.ILOp = ILOp; + } + + public void Emit(AILEmitter Context) + { + Context.Generator.Emit(ILOp); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Translation/AILOpCodeBranch.cs b/Ryujinx/Cpu/Translation/AILOpCodeBranch.cs new file mode 100644 index 000000000..e4caad1ff --- /dev/null +++ b/Ryujinx/Cpu/Translation/AILOpCodeBranch.cs @@ -0,0 +1,21 @@ +using System.Reflection.Emit; + +namespace ChocolArm64.Translation +{ + struct AILOpCodeBranch : IAILEmit + { + private OpCode ILOp; + private AILLabel Label; + + public AILOpCodeBranch(OpCode ILOp, AILLabel Label) + { + this.ILOp = ILOp; + this.Label = Label; + } + + public void Emit(AILEmitter Context) + { + Context.Generator.Emit(ILOp, Label.GetLabel(Context)); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Translation/AILOpCodeCall.cs b/Ryujinx/Cpu/Translation/AILOpCodeCall.cs new file mode 100644 index 000000000..8cd944eb0 --- /dev/null +++ b/Ryujinx/Cpu/Translation/AILOpCodeCall.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Reflection.Emit; + +namespace ChocolArm64.Translation +{ + struct AILOpCodeCall : IAILEmit + { + private MethodInfo MthdInfo; + + public AILOpCodeCall(MethodInfo MthdInfo) + { + this.MthdInfo = MthdInfo; + } + + public void Emit(AILEmitter Context) + { + Context.Generator.Emit(OpCodes.Call, MthdInfo); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Translation/AILOpCodeConst.cs b/Ryujinx/Cpu/Translation/AILOpCodeConst.cs new file mode 100644 index 000000000..80150ec5a --- /dev/null +++ b/Ryujinx/Cpu/Translation/AILOpCodeConst.cs @@ -0,0 +1,81 @@ +using System.Reflection.Emit; +using System.Runtime.InteropServices; + +namespace ChocolArm64.Translation +{ + class AILOpCodeConst : IAILEmit + { + [StructLayout(LayoutKind.Explicit, Size = 8)] + private struct ImmVal + { + [FieldOffset(0)] public int I4; + [FieldOffset(0)] public long I8; + [FieldOffset(0)] public float R4; + [FieldOffset(0)] public double R8; + } + + private ImmVal Value; + + private enum ConstType + { + Int32, + Int64, + Single, + Double + } + + private ConstType Type; + + private AILOpCodeConst(ConstType Type) + { + this.Type = Type; + } + + public AILOpCodeConst(int Value) : this(ConstType.Int32) + { + this.Value = new ImmVal { I4 = Value }; + } + + public AILOpCodeConst(long Value) : this(ConstType.Int64) + { + this.Value = new ImmVal { I8 = Value }; + } + + public AILOpCodeConst(float Value) : this(ConstType.Single) + { + this.Value = new ImmVal { R4 = Value }; + } + + public AILOpCodeConst(double Value) : this(ConstType.Double) + { + this.Value = new ImmVal { R8 = Value }; + } + + public void Emit(AILEmitter Context) + { + switch (Type) + { + case ConstType.Int32: Context.Generator.EmitLdc_I4(Value.I4); break; + + case ConstType.Int64: + { + if (Value.I8 >= int.MinValue && + Value.I8 <= int.MaxValue) + { + Context.Generator.EmitLdc_I4(Value.I4); + + Context.Generator.Emit(OpCodes.Conv_I8); + } + else + { + Context.Generator.Emit(OpCodes.Ldc_I8, Value.I8); + } + break; + } + + case ConstType.Single: Context.Generator.Emit(OpCodes.Ldc_R4, Value.R4); break; + case ConstType.Double: Context.Generator.Emit(OpCodes.Ldc_R8, Value.R8); break; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Translation/AILOpCodeLoad.cs b/Ryujinx/Cpu/Translation/AILOpCodeLoad.cs new file mode 100644 index 000000000..2169cc777 --- /dev/null +++ b/Ryujinx/Cpu/Translation/AILOpCodeLoad.cs @@ -0,0 +1,82 @@ +using ChocolArm64.State; +using System; +using System.Reflection.Emit; + +namespace ChocolArm64.Translation +{ + struct AILOpCodeLoad : IAILEmit + { + public int Index { get; private set; } + + public AIoType IoType { get; private set; } + + public Type OperType { get; private set; } + + public AILOpCodeLoad(int Index, AIoType IoType) : this(Index, IoType, null) { } + + public AILOpCodeLoad(int Index, AIoType IoType, Type OperType) + { + this.IoType = IoType; + this.Index = Index; + this.OperType = OperType; + } + + public void Emit(AILEmitter Context) + { + switch (IoType & AIoType.Mask) + { + case AIoType.Arg: EmitLdarg(Context, Index); break; + case AIoType.Fields: EmitLdfld(Context, Index); break; + case AIoType.Flag: EmitLdloc(Context, Index, ARegisterType.Flag); break; + case AIoType.Int: EmitLdloc(Context, Index, ARegisterType.Int); break; + case AIoType.Vector: EmitLdloc(Context, Index, ARegisterType.Vector); break; + } + } + + private void EmitLdarg(AILEmitter Context, int Index) + { + Context.Generator.EmitLdarg(Index); + } + + private void EmitLdfld(AILEmitter Context, int Index) + { + long IntInputs = Context.LocalAlloc.GetIntInputs(Context.GetILBlock(Index)); + long VecInputs = Context.LocalAlloc.GetVecInputs(Context.GetILBlock(Index)); + + LoadLocals(Context, IntInputs, ARegisterType.Int); + LoadLocals(Context, VecInputs, ARegisterType.Vector); + } + + private void LoadLocals(AILEmitter Context, long Inputs, ARegisterType BaseType) + { + for (int Bit = 0; Bit < 64; Bit++) + { + long Mask = 1L << Bit; + + if ((Inputs & Mask) != 0) + { + ARegister Reg = AILEmitter.GetRegFromBit(Bit, BaseType); + + Context.Generator.EmitLdarg(ATranslatedSub.RegistersArgIdx); + Context.Generator.Emit(OpCodes.Ldfld, Reg.GetField()); + + AILConv.EmitConv( + Context, + Context.GetFieldType(Reg.Type), + Context.GetLocalType(Reg)); + + Context.Generator.EmitStloc(Context.GetLocalIndex(Reg)); + } + } + } + + private void EmitLdloc(AILEmitter Context, int Index, ARegisterType Type) + { + ARegister Reg = new ARegister(Index, Type); + + Context.Generator.EmitLdloc(Context.GetLocalIndex(Reg)); + + AILConv.EmitConv(Context, Context.GetLocalType(Reg), OperType); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Translation/AILOpCodeLog.cs b/Ryujinx/Cpu/Translation/AILOpCodeLog.cs new file mode 100644 index 000000000..1338ca1f3 --- /dev/null +++ b/Ryujinx/Cpu/Translation/AILOpCodeLog.cs @@ -0,0 +1,17 @@ +namespace ChocolArm64.Translation +{ + struct AILOpCodeLog : IAILEmit + { + private string Text; + + public AILOpCodeLog(string Text) + { + this.Text = Text; + } + + public void Emit(AILEmitter Context) + { + Context.Generator.EmitWriteLine(Text); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Translation/AILOpCodeStore.cs b/Ryujinx/Cpu/Translation/AILOpCodeStore.cs new file mode 100644 index 000000000..012e24aec --- /dev/null +++ b/Ryujinx/Cpu/Translation/AILOpCodeStore.cs @@ -0,0 +1,82 @@ +using ChocolArm64.State; +using System; +using System.Reflection.Emit; + +namespace ChocolArm64.Translation +{ + struct AILOpCodeStore : IAILEmit + { + public AIoType IoType { get; private set; } + + public Type OperType { get; private set; } + + public int Index { get; private set; } + + public AILOpCodeStore(int Index, AIoType IoType) : this(Index, IoType, null) { } + + public AILOpCodeStore(int Index, AIoType IoType, Type OperType) + { + this.IoType = IoType; + this.Index = Index; + this.OperType = OperType; + } + + public void Emit(AILEmitter Context) + { + switch (IoType & AIoType.Mask) + { + case AIoType.Arg: EmitStarg(Context, Index); break; + case AIoType.Fields: EmitStfld(Context, Index); break; + case AIoType.Flag: EmitStloc(Context, Index, ARegisterType.Flag); break; + case AIoType.Int: EmitStloc(Context, Index, ARegisterType.Int); break; + case AIoType.Vector: EmitStloc(Context, Index, ARegisterType.Vector); break; + } + } + + private void EmitStarg(AILEmitter Context, int Index) + { + Context.Generator.EmitStarg(Index); + } + + private void EmitStfld(AILEmitter Context, int Index) + { + long IntOutputs = Context.LocalAlloc.GetIntOutputs(Context.GetILBlock(Index)); + long VecOutputs = Context.LocalAlloc.GetVecOutputs(Context.GetILBlock(Index)); + + StoreLocals(Context, IntOutputs, ARegisterType.Int); + StoreLocals(Context, VecOutputs, ARegisterType.Vector); + } + + private void StoreLocals(AILEmitter Context, long Outputs, ARegisterType BaseType) + { + for (int Bit = 0; Bit < 64; Bit++) + { + long Mask = 1L << Bit; + + if ((Outputs & Mask) != 0) + { + ARegister Reg = AILEmitter.GetRegFromBit(Bit, BaseType); + + Context.Generator.EmitLdarg(ATranslatedSub.RegistersArgIdx); + Context.Generator.EmitLdloc(Context.GetLocalIndex(Reg)); + + AILConv.EmitConv( + Context, + Context.GetLocalType(Reg), + Context.GetFieldType(Reg.Type)); + + Context.Generator.Emit(OpCodes.Stfld, Reg.GetField()); + } + } + } + + private void EmitStloc(AILEmitter Context, int Index, ARegisterType Type) + { + ARegister Reg = new ARegister(Index, Type); + + AILConv.EmitConv(Context, OperType, Context.GetLocalType(Reg)); + + Context.Generator.EmitStloc(Context.GetLocalIndex(Reg)); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Translation/AIoType.cs b/Ryujinx/Cpu/Translation/AIoType.cs new file mode 100644 index 000000000..34aa224e5 --- /dev/null +++ b/Ryujinx/Cpu/Translation/AIoType.cs @@ -0,0 +1,18 @@ +using System; + +namespace ChocolArm64.Translation +{ + [Flags] + enum AIoType + { + Arg, + Fields, + Flag, + Int, + Float, + Vector, + Mask = 0xff, + VectorI = Vector | 1 << 8, + VectorF = Vector | 1 << 9 + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Translation/ALocalAlloc.cs b/Ryujinx/Cpu/Translation/ALocalAlloc.cs new file mode 100644 index 000000000..0661ddc8d --- /dev/null +++ b/Ryujinx/Cpu/Translation/ALocalAlloc.cs @@ -0,0 +1,231 @@ +using System.Collections.Generic; + +namespace ChocolArm64.Translation +{ + class ALocalAlloc + { + private class PathIo + { + private Dictionary AllInputs; + private Dictionary CmnOutputs; + + private long AllOutputs; + + public PathIo() + { + AllInputs = new Dictionary(); + CmnOutputs = new Dictionary(); + } + + public PathIo(AILBlock Root, long Inputs, long Outputs) : this() + { + Set(Root, Inputs, Outputs); + } + + public void Set(AILBlock Root, long Inputs, long Outputs) + { + if (!AllInputs.TryAdd(Root, Inputs)) + { + AllInputs[Root] |= Inputs; + } + + if (!CmnOutputs.TryAdd(Root, Outputs)) + { + CmnOutputs[Root] &= Outputs; + } + + AllOutputs |= Outputs; + } + + public long GetInputs(AILBlock Root) + { + if (AllInputs.TryGetValue(Root, out long Inputs)) + { + return Inputs | (AllOutputs & ~CmnOutputs[Root]); + } + + return 0; + } + + public long GetOutputs() + { + return AllOutputs; + } + } + + private Dictionary IntPaths; + private Dictionary VecPaths; + + private struct BlockIo + { + public AILBlock Block; + public AILBlock Entry; + + public long IntInputs; + public long VecInputs; + public long IntOutputs; + public long VecOutputs; + } + + private const int MaxOptGraphLength = 120; + + public ALocalAlloc(AILBlock[] Graph, AILBlock Root) + { + IntPaths = new Dictionary(); + VecPaths = new Dictionary(); + + if (Graph.Length < MaxOptGraphLength) + { + InitializeOptimal(Graph, Root); + } + else + { + InitializeFast(Graph); + } + } + + private void InitializeOptimal(AILBlock[] Graph, AILBlock Root) + { + //This will go through all possible paths on the graph, + //and store all inputs/outputs for each block. A register + //that was previously written to already is not considered an input. + //When a block can be reached by more than one path, then the + //output from all paths needs to be set for this block, and + //only outputs present in all of the parent blocks can be considered + //when doing input elimination. Each block chain have a root, that's where + //the code starts executing. They are present on the subroutine start point, + //and on call return points too (address written to X30 by BL). + HashSet Visited = new HashSet(); + + Queue Unvisited = new Queue(); + + void Enqueue(BlockIo Block) + { + if (!Visited.Contains(Block)) + { + Unvisited.Enqueue(Block); + + Visited.Add(Block); + } + } + + Enqueue(new BlockIo() + { + Block = Root, + Entry = Root + }); + + while (Unvisited.Count > 0) + { + BlockIo Current = Unvisited.Dequeue(); + + Current.IntInputs |= Current.Block.IntInputs & ~Current.IntOutputs; + Current.VecInputs |= Current.Block.VecInputs & ~Current.VecOutputs; + Current.IntOutputs |= Current.Block.IntOutputs; + Current.VecOutputs |= Current.Block.VecOutputs; + + //Check if this is a exit block + //(a block that returns or calls another sub). + if ((Current.Block.Next == null && + Current.Block.Branch == null) || Current.Block.HasStateStore) + { + if (!IntPaths.TryGetValue(Current.Block, out PathIo IntPath)) + { + IntPaths.Add(Current.Block, IntPath = new PathIo()); + } + + if (!VecPaths.TryGetValue(Current.Block, out PathIo VecPath)) + { + VecPaths.Add(Current.Block, VecPath = new PathIo()); + } + + IntPath.Set(Current.Entry, Current.IntInputs, Current.IntOutputs); + VecPath.Set(Current.Entry, Current.VecInputs, Current.VecOutputs); + } + + void EnqueueFromCurrent(AILBlock Block, bool RetTarget) + { + BlockIo BlkIO = new BlockIo() { Block = Block }; + + if (RetTarget) + { + BlkIO.Entry = Block; + BlkIO.IntInputs = 0; + BlkIO.VecInputs = 0; + BlkIO.IntOutputs = 0; + BlkIO.VecOutputs = 0; + } + else + { + BlkIO.Entry = Current.Entry; + BlkIO.IntInputs = Current.IntInputs; + BlkIO.VecInputs = Current.VecInputs; + BlkIO.IntOutputs = Current.IntOutputs; + BlkIO.VecOutputs = Current.VecOutputs; + } + + Enqueue(BlkIO); + } + + if (Current.Block.Next != null) + { + EnqueueFromCurrent(Current.Block.Next, Current.Block.HasStateStore); + } + + if (Current.Block.Branch != null) + { + EnqueueFromCurrent(Current.Block.Branch, false); + } + } + } + + private void InitializeFast(AILBlock[] Graph) + { + //This is WAY faster than InitializeOptimal, but results in + //uneeded loads and stores, so the resulting code will be slower. + long IntInputs = 0; + long IntOutputs = 0; + long VecInputs = 0; + long VecOutputs = 0; + + foreach (AILBlock Block in Graph) + { + IntInputs |= Block.IntInputs; + IntOutputs |= Block.IntOutputs; + VecInputs |= Block.VecInputs; + VecOutputs |= Block.VecOutputs; + } + + //It's possible that not all code paths writes to those output registers, + //in those cases if we attempt to write an output registers that was + //not written, we will be just writing zero and messing up the old register value. + //So we just need to ensure that all outputs are loaded. + IntInputs |= IntOutputs; + VecInputs |= VecOutputs; + + foreach (AILBlock Block in Graph) + { + IntPaths.Add(Block, new PathIo(Block, IntInputs, IntOutputs)); + VecPaths.Add(Block, new PathIo(Block, VecInputs, VecOutputs)); + } + } + + public long GetIntInputs(AILBlock Root) => GetInputsImpl(Root, IntPaths.Values); + public long GetVecInputs(AILBlock Root) => GetInputsImpl(Root, VecPaths.Values); + + private long GetInputsImpl(AILBlock Root, IEnumerable Values) + { + long Inputs = 0; + + foreach (PathIo Path in Values) + { + Inputs |= Path.GetInputs(Root); + } + + return Inputs; + } + + public long GetIntOutputs(AILBlock Block) => IntPaths[Block].GetOutputs(); + public long GetVecOutputs(AILBlock Block) => VecPaths[Block].GetOutputs(); + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Translation/IAILEmit.cs b/Ryujinx/Cpu/Translation/IAILEmit.cs new file mode 100644 index 000000000..6e4e9a785 --- /dev/null +++ b/Ryujinx/Cpu/Translation/IAILEmit.cs @@ -0,0 +1,7 @@ +namespace ChocolArm64.Translation +{ + interface IAILEmit + { + void Emit(AILEmitter Context); + } +} \ No newline at end of file diff --git a/Ryujinx/Cpu/Translation/ILGeneratorEx.cs b/Ryujinx/Cpu/Translation/ILGeneratorEx.cs new file mode 100644 index 000000000..abb35ec3f --- /dev/null +++ b/Ryujinx/Cpu/Translation/ILGeneratorEx.cs @@ -0,0 +1,129 @@ +using System; + +namespace ChocolArm64 +{ + using System.Reflection.Emit; + + static class ILGeneratorEx + { + public static void EmitLdc_I4(this ILGenerator Generator,int Value) + { + switch (Value) + { + case 0: Generator.Emit(OpCodes.Ldc_I4_0); break; + case 1: Generator.Emit(OpCodes.Ldc_I4_1); break; + case 2: Generator.Emit(OpCodes.Ldc_I4_2); break; + case 3: Generator.Emit(OpCodes.Ldc_I4_3); break; + case 4: Generator.Emit(OpCodes.Ldc_I4_4); break; + case 5: Generator.Emit(OpCodes.Ldc_I4_5); break; + case 6: Generator.Emit(OpCodes.Ldc_I4_6); break; + case 7: Generator.Emit(OpCodes.Ldc_I4_7); break; + case 8: Generator.Emit(OpCodes.Ldc_I4_8); break; + case -1: Generator.Emit(OpCodes.Ldc_I4_M1); break; + default: Generator.Emit(OpCodes.Ldc_I4, Value); break; + } + } + + public static void EmitLdarg(this ILGenerator Generator, int Index) + { + switch (Index) + { + case 0: Generator.Emit(OpCodes.Ldarg_0); break; + case 1: Generator.Emit(OpCodes.Ldarg_1); break; + case 2: Generator.Emit(OpCodes.Ldarg_2); break; + case 3: Generator.Emit(OpCodes.Ldarg_3); break; + + default: + if ((uint)Index <= byte.MaxValue) + { + Generator.Emit(OpCodes.Ldarg_S, (byte)Index); + } + else if ((uint)Index < ushort.MaxValue) + { + Generator.Emit(OpCodes.Ldarg, (short)Index); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Index)); + } + break; + } + } + + public static void EmitStarg(this ILGenerator Generator, int Index) + { + if ((uint)Index <= byte.MaxValue) + { + Generator.Emit(OpCodes.Starg_S, (byte)Index); + } + else if ((uint)Index < ushort.MaxValue) + { + Generator.Emit(OpCodes.Starg, (short)Index); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Index)); + } + } + + public static void EmitLdloc(this ILGenerator Generator, int Index) + { + switch (Index) + { + case 0: Generator.Emit(OpCodes.Ldloc_0); break; + case 1: Generator.Emit(OpCodes.Ldloc_1); break; + case 2: Generator.Emit(OpCodes.Ldloc_2); break; + case 3: Generator.Emit(OpCodes.Ldloc_3); break; + + default: + if ((uint)Index <= byte.MaxValue) + { + Generator.Emit(OpCodes.Ldloc_S, (byte)Index); + } + else if ((uint)Index < ushort.MaxValue) + { + Generator.Emit(OpCodes.Ldloc, (short)Index); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Index)); + } + break; + } + } + + public static void EmitStloc(this ILGenerator Generator, int Index) + { + switch (Index) + { + case 0: Generator.Emit(OpCodes.Stloc_0); break; + case 1: Generator.Emit(OpCodes.Stloc_1); break; + case 2: Generator.Emit(OpCodes.Stloc_2); break; + case 3: Generator.Emit(OpCodes.Stloc_3); break; + + default: + if ((uint)Index <= byte.MaxValue) + { + Generator.Emit(OpCodes.Stloc_S, (byte)Index); + } + else if ((uint)Index < ushort.MaxValue) + { + Generator.Emit(OpCodes.Stloc, (short)Index); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Index)); + } + break; + } + } + + public static void EmitLdargSeq(this ILGenerator Generator, int Count) + { + for (int Index = 0; Index < Count; Index++) + { + Generator.EmitLdarg(Index); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/Gal/GalPrimitiveType.cs b/Ryujinx/Gal/GalPrimitiveType.cs new file mode 100644 index 000000000..7b6d99a0b --- /dev/null +++ b/Ryujinx/Gal/GalPrimitiveType.cs @@ -0,0 +1,21 @@ +namespace Gal +{ + public enum GalPrimitiveType + { + Points = 0x0, + Lines = 0x1, + LineLoop = 0x2, + LineStrip = 0x3, + Triangles = 0x4, + TriangleStrip = 0x5, + TriangleFan = 0x6, + Quads = 0x7, + QuadStrip = 0x8, + Polygon = 0x9, + LinesAdjacency = 0xa, + LineStripAdjacency = 0xb, + TrianglesAdjacency = 0xc, + TriangleStripAdjacency = 0xd, + Patches = 0xe + } +} \ No newline at end of file diff --git a/Ryujinx/Gal/GalVertexAttrib.cs b/Ryujinx/Gal/GalVertexAttrib.cs new file mode 100644 index 000000000..bbc326337 --- /dev/null +++ b/Ryujinx/Gal/GalVertexAttrib.cs @@ -0,0 +1,33 @@ +namespace Gal +{ + public struct GalVertexAttrib + { + public int Index { get; private set; } + public int Buffer { get; private set; } + public bool IsConst { get; private set; } + public int Offset { get; private set; } + + public GalVertexAttribSize Size { get; private set; } + public GalVertexAttribType Type { get; private set; } + + public bool IsBgra { get; private set; } + + public GalVertexAttrib( + int Index, + int Buffer, + bool IsConst, + int Offset, + GalVertexAttribSize Size, + GalVertexAttribType Type, + bool IsBgra) + { + this.Index = Index; + this.Buffer = Buffer; + this.IsConst = IsConst; + this.Offset = Offset; + this.Size = Size; + this.Type = Type; + this.IsBgra = IsBgra; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Gal/GalVertexAttribSize.cs b/Ryujinx/Gal/GalVertexAttribSize.cs new file mode 100644 index 000000000..11f0470c2 --- /dev/null +++ b/Ryujinx/Gal/GalVertexAttribSize.cs @@ -0,0 +1,20 @@ +namespace Gal +{ + public enum GalVertexAttribSize + { + _32_32_32_32 = 0x1, + _32_32_32 = 0x2, + _16_16_16_16 = 0x3, + _32_32 = 0x4, + _16_16_16 = 0x5, + _8_8_8_8 = 0xa, + _16_16 = 0xf, + _32 = 0x12, + _8_8_8 = 0x13, + _8_8 = 0x18, + _16 = 0x1b, + _8 = 0x1d, + _10_10_10_2 = 0x30, + _11_11_10 = 0x31 + } +} \ No newline at end of file diff --git a/Ryujinx/Gal/GalVertexAttribType.cs b/Ryujinx/Gal/GalVertexAttribType.cs new file mode 100644 index 000000000..c0ed59fb2 --- /dev/null +++ b/Ryujinx/Gal/GalVertexAttribType.cs @@ -0,0 +1,13 @@ +namespace Gal +{ + public enum GalVertexAttribType + { + Snorm = 1, + Unorm = 2, + Sint = 3, + Uint = 4, + Uscaled = 5, + Sscaled = 6, + Float = 7 + } +} \ No newline at end of file diff --git a/Ryujinx/Gal/IGalRenderer.cs b/Ryujinx/Gal/IGalRenderer.cs new file mode 100644 index 000000000..306d0d511 --- /dev/null +++ b/Ryujinx/Gal/IGalRenderer.cs @@ -0,0 +1,17 @@ +using System; + +namespace Gal +{ + public interface IGalRenderer + { + long FrameBufferPtr { get; set; } + + void QueueAction(Action ActionMthd); + void RunActions(); + + void Render(); + void SendVertexBuffer(int Index, byte[] Buffer, int Stride, GalVertexAttrib[] Attribs); + void SendR8G8B8A8Texture(int Index, byte[] Buffer, int Width, int Height); + void BindTexture(int Index); + } +} \ No newline at end of file diff --git a/Ryujinx/Gal/OpenGL/OpenGLRenderer.cs b/Ryujinx/Gal/OpenGL/OpenGLRenderer.cs new file mode 100644 index 000000000..72ad6f706 --- /dev/null +++ b/Ryujinx/Gal/OpenGL/OpenGLRenderer.cs @@ -0,0 +1,282 @@ +using OpenTK.Graphics.OpenGL; +using System; +using System.Collections.Generic; + +namespace Gal.OpenGL +{ + public class OpenGLRenderer : IGalRenderer + { + private struct VertexBuffer + { + public int VaoHandle; + public int VboHandle; + + public int PrimCount; + } + + private struct Texture + { + public int Handle; + } + + private List VertexBuffers; + + private Texture[] Textures; + + private Queue ActionsQueue; + + public long FrameBufferPtr { get; set; } + + public OpenGLRenderer() + { + VertexBuffers = new List(); + + Textures = new Texture[8]; + + ActionsQueue = new Queue(); + } + + public void QueueAction(Action ActionMthd) + { + ActionsQueue.Enqueue(ActionMthd); + } + + public void RunActions() + { + while (ActionsQueue.Count > 0) + { + ActionsQueue.Dequeue()(); + } + } + + public void Render() + { + for (int Index = 0; Index < VertexBuffers.Count; Index++) + { + VertexBuffer Vb = VertexBuffers[Index]; + + if (Vb.VaoHandle != 0 && + Vb.PrimCount != 0) + { + GL.BindVertexArray(Vb.VaoHandle); + GL.DrawArrays(PrimitiveType.TriangleStrip, 0, Vb.PrimCount); + } + } + + } + + public void SendVertexBuffer(int Index, byte[] Buffer, int Stride, GalVertexAttrib[] Attribs) + { + if (Index < 0) + { + throw new ArgumentOutOfRangeException(nameof(Index)); + } + + if (Buffer.Length == 0 || Stride == 0) + { + return; + } + + EnsureVbInitialized(Index); + + VertexBuffer Vb = VertexBuffers[Index]; + + Vb.PrimCount = Buffer.Length / Stride; + + VertexBuffers[Index] = Vb; + + IntPtr Length = new IntPtr(Buffer.Length); + + GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle); + GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); + GL.BindBuffer(BufferTarget.ArrayBuffer, 0); + + GL.BindVertexArray(Vb.VaoHandle); + + for (int Attr = 0; Attr < 16; Attr++) + { + GL.DisableVertexAttribArray(Attr); + } + + foreach (GalVertexAttrib Attrib in Attribs) + { + if (Attrib.Index >= 3) break; + + GL.EnableVertexAttribArray(Attrib.Index); + + GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle); + + int Size = 0; + + switch (Attrib.Size) + { + case GalVertexAttribSize._8: + case GalVertexAttribSize._16: + case GalVertexAttribSize._32: + Size = 1; + break; + case GalVertexAttribSize._8_8: + case GalVertexAttribSize._16_16: + case GalVertexAttribSize._32_32: + Size = 2; + break; + case GalVertexAttribSize._8_8_8: + case GalVertexAttribSize._11_11_10: + case GalVertexAttribSize._16_16_16: + case GalVertexAttribSize._32_32_32: + Size = 3; + break; + case GalVertexAttribSize._8_8_8_8: + case GalVertexAttribSize._10_10_10_2: + case GalVertexAttribSize._16_16_16_16: + case GalVertexAttribSize._32_32_32_32: + Size = 4; + break; + } + + bool Signed = + Attrib.Type == GalVertexAttribType.Snorm || + Attrib.Type == GalVertexAttribType.Sint || + Attrib.Type == GalVertexAttribType.Sscaled; + + bool Normalize = + Attrib.Type == GalVertexAttribType.Snorm || + Attrib.Type == GalVertexAttribType.Unorm; + + VertexAttribPointerType Type = 0; + + switch (Attrib.Type) + { + case GalVertexAttribType.Snorm: + case GalVertexAttribType.Unorm: + case GalVertexAttribType.Sint: + case GalVertexAttribType.Uint: + case GalVertexAttribType.Uscaled: + case GalVertexAttribType.Sscaled: + { + switch (Attrib.Size) + { + case GalVertexAttribSize._8: + case GalVertexAttribSize._8_8: + case GalVertexAttribSize._8_8_8: + case GalVertexAttribSize._8_8_8_8: + { + Type = Signed + ? VertexAttribPointerType.Byte + : VertexAttribPointerType.UnsignedByte; + + break; + } + + case GalVertexAttribSize._16: + case GalVertexAttribSize._16_16: + case GalVertexAttribSize._16_16_16: + case GalVertexAttribSize._16_16_16_16: + { + Type = Signed + ? VertexAttribPointerType.Short + : VertexAttribPointerType.UnsignedShort; + + break; + } + + case GalVertexAttribSize._10_10_10_2: + case GalVertexAttribSize._11_11_10: + case GalVertexAttribSize._32: + case GalVertexAttribSize._32_32: + case GalVertexAttribSize._32_32_32: + case GalVertexAttribSize._32_32_32_32: + { + Type = Signed + ? VertexAttribPointerType.Int + : VertexAttribPointerType.UnsignedInt; + + break; + } + } + + break; + } + + case GalVertexAttribType.Float: + { + Type = VertexAttribPointerType.Float; + + break; + } + } + + GL.VertexAttribPointer( + Attrib.Index, + Size, + Type, + Normalize, + Stride, + Attrib.Offset); + } + + GL.BindVertexArray(0); + } + + public void SendR8G8B8A8Texture(int Index, byte[] Buffer, int Width, int Height) + { + EnsureTexInitialized(Index); + + GL.BindTexture(TextureTarget.Texture2D, Textures[Index].Handle); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + GL.TexImage2D(TextureTarget.Texture2D, + 0, + PixelInternalFormat.Rgba, + Width, + Height, + 0, + PixelFormat.Rgba, + PixelType.UnsignedByte, + Buffer); + } + + public void BindTexture(int Index) + { + GL.ActiveTexture(TextureUnit.Texture0 + Index); + + GL.BindTexture(TextureTarget.Texture2D, Textures[Index].Handle); + } + + private void EnsureVbInitialized(int VbIndex) + { + while (VbIndex >= VertexBuffers.Count) + { + VertexBuffers.Add(new VertexBuffer()); + } + + VertexBuffer Vb = VertexBuffers[VbIndex]; + + if (Vb.VaoHandle == 0) + { + Vb.VaoHandle = GL.GenVertexArray(); + } + + if (Vb.VboHandle == 0) + { + Vb.VboHandle = GL.GenBuffer(); + } + + VertexBuffers[VbIndex] = Vb; + } + + private void EnsureTexInitialized(int TexIndex) + { + Texture Tex = Textures[TexIndex]; + + if (Tex.Handle == 0) + { + Tex.Handle = GL.GenTexture(); + } + + Textures[TexIndex] = Tex; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Gpu/BCn.cs b/Ryujinx/Gpu/BCn.cs new file mode 100644 index 000000000..bf782d167 --- /dev/null +++ b/Ryujinx/Gpu/BCn.cs @@ -0,0 +1,468 @@ +using System; +using System.Drawing; + +namespace Ryujinx.Gpu +{ + static class BCn + { + public static byte[] DecodeBC1(NsGpuTexture Tex, int Offset) + { + int W = (Tex.Width + 3) / 4; + int H = (Tex.Height + 3) / 4; + + byte[] Output = new byte[W * H * 64]; + + SwizzleAddr Swizzle = new SwizzleAddr(W, H, 8); + + for (int Y = 0; Y < H; Y++) + { + for (int X = 0; X < W; X++) + { + int IOffs = Offset + Swizzle.GetSwizzledAddress64(X, Y) * 8; + + byte[] Tile = BCnDecodeTile(Tex.Data, IOffs, true); + + int TOffset = 0; + + for (int TY = 0; TY < 4; TY++) + { + for (int TX = 0; TX < 4; TX++) + { + int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; + + Output[OOffset + 0] = Tile[TOffset + 0]; + Output[OOffset + 1] = Tile[TOffset + 1]; + Output[OOffset + 2] = Tile[TOffset + 2]; + Output[OOffset + 3] = Tile[TOffset + 3]; + + TOffset += 4; + } + } + } + } + + return Output; + } + + public static byte[] DecodeBC2(NsGpuTexture Tex, int Offset) + { + int W = (Tex.Width + 3) / 4; + int H = (Tex.Height + 3) / 4; + + byte[] Output = new byte[W * H * 64]; + + SwizzleAddr Swizzle = new SwizzleAddr(W, H, 4); + + for (int Y = 0; Y < H; Y++) + { + for (int X = 0; X < W; X++) + { + int IOffs = Offset + Swizzle.GetSwizzledAddress128(X, Y) * 16; + + byte[] Tile = BCnDecodeTile(Tex.Data, IOffs + 8, false); + + int AlphaLow = Get32(Tex.Data, IOffs + 0); + int AlphaHigh = Get32(Tex.Data, IOffs + 4); + + ulong AlphaCh = (uint)AlphaLow | (ulong)AlphaHigh << 32; + + int TOffset = 0; + + for (int TY = 0; TY < 4; TY++) + { + for (int TX = 0; TX < 4; TX++) + { + ulong Alpha = (AlphaCh >> (TY * 16 + TX * 4)) & 0xf; + + int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; + + Output[OOffset + 0] = Tile[TOffset + 0]; + Output[OOffset + 1] = Tile[TOffset + 1]; + Output[OOffset + 2] = Tile[TOffset + 2]; + Output[OOffset + 3] = (byte)(Alpha | (Alpha << 4)); + + TOffset += 4; + } + } + } + } + + return Output; + } + + public static byte[] DecodeBC3(NsGpuTexture Tex, int Offset) + { + int W = (Tex.Width + 3) / 4; + int H = (Tex.Height + 3) / 4; + + byte[] Output = new byte[W * H * 64]; + + SwizzleAddr Swizzle = new SwizzleAddr(W, H, 4); + + for (int Y = 0; Y < H; Y++) + { + for (int X = 0; X < W; X++) + { + int IOffs = Offset + Swizzle.GetSwizzledAddress128(X, Y) * 16; + + byte[] Tile = BCnDecodeTile(Tex.Data, IOffs + 8, false); + + byte[] Alpha = new byte[8]; + + Alpha[0] = Tex.Data[IOffs + 0]; + Alpha[1] = Tex.Data[IOffs + 1]; + + CalculateBC3Alpha(Alpha); + + int AlphaLow = Get32(Tex.Data, IOffs + 2); + int AlphaHigh = Get16(Tex.Data, IOffs + 6); + + ulong AlphaCh = (uint)AlphaLow | (ulong)AlphaHigh << 32; + + int TOffset = 0; + + for (int TY = 0; TY < 4; TY++) + { + for (int TX = 0; TX < 4; TX++) + { + int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; + + byte AlphaPx = Alpha[(AlphaCh >> (TY * 12 + TX * 3)) & 7]; + + Output[OOffset + 0] = Tile[TOffset + 0]; + Output[OOffset + 1] = Tile[TOffset + 1]; + Output[OOffset + 2] = Tile[TOffset + 2]; + Output[OOffset + 3] = AlphaPx; + + TOffset += 4; + } + } + } + } + + return Output; + } + + public static byte[] DecodeBC4(NsGpuTexture Tex, int Offset) + { + int W = (Tex.Width + 3) / 4; + int H = (Tex.Height + 3) / 4; + + byte[] Output = new byte[W * H * 64]; + + SwizzleAddr Swizzle = new SwizzleAddr(W, H, 8); + + for (int Y = 0; Y < H; Y++) + { + for (int X = 0; X < W; X++) + { + int IOffs = Swizzle.GetSwizzledAddress64(X, Y) * 8; + + byte[] Red = new byte[8]; + + Red[0] = Tex.Data[IOffs + 0]; + Red[1] = Tex.Data[IOffs + 1]; + + CalculateBC3Alpha(Red); + + int RedLow = Get32(Tex.Data, IOffs + 2); + int RedHigh = Get16(Tex.Data, IOffs + 6); + + ulong RedCh = (uint)RedLow | (ulong)RedHigh << 32; + + int TOffset = 0; + + for (int TY = 0; TY < 4; TY++) + { + for (int TX = 0; TX < 4; TX++) + { + int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; + + byte RedPx = Red[(RedCh >> (TY * 12 + TX * 3)) & 7]; + + Output[OOffset + 0] = RedPx; + Output[OOffset + 1] = RedPx; + Output[OOffset + 2] = RedPx; + Output[OOffset + 3] = 0xff; + + TOffset += 4; + } + } + } + } + + return Output; + } + + public static byte[] DecodeBC5(NsGpuTexture Tex, int Offset, bool SNorm) + { + int W = (Tex.Width + 3) / 4; + int H = (Tex.Height + 3) / 4; + + byte[] Output = new byte[W * H * 64]; + + SwizzleAddr Swizzle = new SwizzleAddr(W, H, 4); + + for (int Y = 0; Y < H; Y++) + { + for (int X = 0; X < W; X++) + { + int IOffs = Swizzle.GetSwizzledAddress128(X, Y) * 16; + + byte[] Red = new byte[8]; + byte[] Green = new byte[8]; + + Red[0] = Tex.Data[IOffs + 0]; + Red[1] = Tex.Data[IOffs + 1]; + + Green[0] = Tex.Data[IOffs + 8]; + Green[1] = Tex.Data[IOffs + 9]; + + if (SNorm) + { + CalculateBC3AlphaS(Red); + CalculateBC3AlphaS(Green); + } + else + { + CalculateBC3Alpha(Red); + CalculateBC3Alpha(Green); + } + + int RedLow = Get32(Tex.Data, IOffs + 2); + int RedHigh = Get16(Tex.Data, IOffs + 6); + + int GreenLow = Get32(Tex.Data, IOffs + 10); + int GreenHigh = Get16(Tex.Data, IOffs + 14); + + ulong RedCh = (uint)RedLow | (ulong)RedHigh << 32; + ulong GreenCh = (uint)GreenLow | (ulong)GreenHigh << 32; + + int TOffset = 0; + + if (SNorm) + { + for (int TY = 0; TY < 4; TY++) + { + for (int TX = 0; TX < 4; TX++) + { + int Shift = TY * 12 + TX * 3; + + int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; + + byte RedPx = Red [(RedCh >> Shift) & 7]; + byte GreenPx = Green[(GreenCh >> Shift) & 7]; + + RedPx += 0x80; + GreenPx += 0x80; + + float NX = (RedPx / 255f) * 2 - 1; + float NY = (GreenPx / 255f) * 2 - 1; + + float NZ = (float)Math.Sqrt(1 - (NX * NX + NY * NY)); + + Output[OOffset + 0] = Clamp((NZ + 1) * 0.5f); + Output[OOffset + 1] = Clamp((NY + 1) * 0.5f); + Output[OOffset + 2] = Clamp((NX + 1) * 0.5f); + Output[OOffset + 3] = 0xff; + + TOffset += 4; + } + } + } + else + { + for (int TY = 0; TY < 4; TY++) + { + for (int TX = 0; TX < 4; TX++) + { + int Shift = TY * 12 + TX * 3; + + int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; + + byte RedPx = Red [(RedCh >> Shift) & 7]; + byte GreenPx = Green[(GreenCh >> Shift) & 7]; + + Output[OOffset + 0] = RedPx; + Output[OOffset + 1] = RedPx; + Output[OOffset + 2] = RedPx; + Output[OOffset + 3] = GreenPx; + + TOffset += 4; + } + } + } + } + } + + return Output; + } + + private static byte Clamp(float Value) + { + if (Value > 1) + { + return 0xff; + } + else if (Value < 0) + { + return 0; + } + else + { + return (byte)(Value * 0xff); + } + } + + private static void CalculateBC3Alpha(byte[] Alpha) + { + for (int i = 2; i < 8; i++) + { + if (Alpha[0] > Alpha[1]) + { + Alpha[i] = (byte)(((8 - i) * Alpha[0] + (i - 1) * Alpha[1]) / 7); + } + else if (i < 6) + { + Alpha[i] = (byte)(((6 - i) * Alpha[0] + (i - 1) * Alpha[1]) / 7); + } + else if (i == 6) + { + Alpha[i] = 0; + } + else /* i == 7 */ + { + Alpha[i] = 0xff; + } + } + } + + private static void CalculateBC3AlphaS(byte[] Alpha) + { + for (int i = 2; i < 8; i++) + { + if ((sbyte)Alpha[0] > (sbyte)Alpha[1]) + { + Alpha[i] = (byte)(((8 - i) * (sbyte)Alpha[0] + (i - 1) * (sbyte)Alpha[1]) / 7); + } + else if (i < 6) + { + Alpha[i] = (byte)(((6 - i) * (sbyte)Alpha[0] + (i - 1) * (sbyte)Alpha[1]) / 7); + } + else if (i == 6) + { + Alpha[i] = 0x80; + } + else /* i == 7 */ + { + Alpha[i] = 0x7f; + } + } + } + + private static byte[] BCnDecodeTile( + byte[] Input, + int Offset, + bool IsBC1) + { + Color[] CLUT = new Color[4]; + + int c0 = Get16(Input, Offset + 0); + int c1 = Get16(Input, Offset + 2); + + CLUT[0] = DecodeRGB565(c0); + CLUT[1] = DecodeRGB565(c1); + CLUT[2] = CalculateCLUT2(CLUT[0], CLUT[1], c0, c1, IsBC1); + CLUT[3] = CalculateCLUT3(CLUT[0], CLUT[1], c0, c1, IsBC1); + + int Indices = Get32(Input, Offset + 4); + + int IdxShift = 0; + + byte[] Output = new byte[4 * 4 * 4]; + + int OOffset = 0; + + for (int TY = 0; TY < 4; TY++) + { + for (int TX = 0; TX < 4; TX++) + { + int Idx = (Indices >> IdxShift) & 3; + + IdxShift += 2; + + Color Pixel = CLUT[Idx]; + + Output[OOffset + 0] = Pixel.R; + Output[OOffset + 1] = Pixel.G; + Output[OOffset + 2] = Pixel.B; + Output[OOffset + 3] = Pixel.A; + + OOffset += 4; + } + } + + return Output; + } + + private static Color CalculateCLUT2(Color C0, Color C1, int c0, int c1, bool IsBC1) + { + if (c0 > c1 || !IsBC1) + { + return Color.FromArgb( + (2 * C0.R + C1.R) / 3, + (2 * C0.G + C1.G) / 3, + (2 * C0.B + C1.B) / 3); + } + else + { + return Color.FromArgb( + (C0.R + C1.R) / 2, + (C0.G + C1.G) / 2, + (C0.B + C1.B) / 2); + } + } + + private static Color CalculateCLUT3(Color C0, Color C1, int c0, int c1, bool IsBC1) + { + if (c0 > c1 || !IsBC1) + { + return + Color.FromArgb( + (2 * C1.R + C0.R) / 3, + (2 * C1.G + C0.G) / 3, + (2 * C1.B + C0.B) / 3); + } + + return Color.Transparent; + } + + private static Color DecodeRGB565(int Value) + { + int B = ((Value >> 0) & 0x1f) << 3; + int G = ((Value >> 5) & 0x3f) << 2; + int R = ((Value >> 11) & 0x1f) << 3; + + return Color.FromArgb( + R | (R >> 5), + G | (G >> 6), + B | (B >> 5)); + } + + private static int Get16(byte[] Data, int Address) + { + return + Data[Address + 0] << 0 | + Data[Address + 1] << 8; + } + + private static int Get32(byte[] Data, int Address) + { + return + Data[Address + 0] << 0 | + Data[Address + 1] << 8 | + Data[Address + 2] << 16 | + Data[Address + 3] << 24; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Gpu/NsGpu.cs b/Ryujinx/Gpu/NsGpu.cs new file mode 100644 index 000000000..6aa7332cd --- /dev/null +++ b/Ryujinx/Gpu/NsGpu.cs @@ -0,0 +1,22 @@ +using Gal; + +namespace Ryujinx.Gpu +{ + class NsGpu + { + public IGalRenderer Renderer { get; private set; } + + public NsGpuMemoryMgr MemoryMgr { get; private set; } + + public NsGpuPGraph PGraph { get; private set; } + + public NsGpu(IGalRenderer Renderer) + { + this.Renderer = Renderer; + + MemoryMgr = new NsGpuMemoryMgr(); + + PGraph = new NsGpuPGraph(this); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Gpu/NsGpuEngine.cs b/Ryujinx/Gpu/NsGpuEngine.cs new file mode 100644 index 000000000..bf1045696 --- /dev/null +++ b/Ryujinx/Gpu/NsGpuEngine.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Gpu +{ + enum NsGpuEngine + { + None = 0, + _2d = 0x902d, + _3d = 0xb197, + Compute = 0xb1c0, + Kepler = 0xa140, + Dma = 0xb0b5, + GpFifo = 0xb06f + } +} \ No newline at end of file diff --git a/Ryujinx/Gpu/NsGpuMemoryMgr.cs b/Ryujinx/Gpu/NsGpuMemoryMgr.cs new file mode 100644 index 000000000..e555f2af3 --- /dev/null +++ b/Ryujinx/Gpu/NsGpuMemoryMgr.cs @@ -0,0 +1,204 @@ +using System.Runtime.CompilerServices; + +namespace Ryujinx.Gpu +{ + class NsGpuMemoryMgr + { + private const long AddrSize = 1L << 40; + + private const int PTLvl0Bits = 14; + private const int PTLvl1Bits = 14; + private const int PTPageBits = 12; + + private const int PTLvl0Size = 1 << PTLvl0Bits; + private const int PTLvl1Size = 1 << PTLvl1Bits; + private const int PageSize = 1 << PTPageBits; + + private const int PTLvl0Mask = PTLvl0Size - 1; + private const int PTLvl1Mask = PTLvl1Size - 1; + private const int PageMask = PageSize - 1; + + private const int PTLvl0Bit = PTPageBits + PTLvl0Bits; + private const int PTLvl1Bit = PTPageBits; + + private const long PteUnmapped = -1; + private const long PteReserved = -2; + + private long[][] PageTable; + + public NsGpuMemoryMgr() + { + PageTable = new long[PTLvl0Size][]; + } + + public long Map(long CpuAddr, long GpuAddr, long Size) + { + CpuAddr &= ~PageMask; + GpuAddr &= ~PageMask; + + for (long Offset = 0; Offset < Size; Offset += PageSize) + { + if (GetPTAddr(GpuAddr + Offset) != PteReserved) + { + return Map(CpuAddr, Size); + } + } + + for (long Offset = 0; Offset < Size; Offset += PageSize) + { + SetPTAddr(GpuAddr + Offset, CpuAddr + Offset); + } + + return GpuAddr; + } + + public long Map(long CpuAddr, long Size) + { + CpuAddr &= ~PageMask; + + long Position = GetFreePosition(Size); + + if (Position != -1) + { + for (long Offset = 0; Offset < Size; Offset += PageSize) + { + SetPTAddr(Position + Offset, CpuAddr + Offset); + } + } + + return Position; + } + + public long Reserve(long GpuAddr, long Size, long Align) + { + for (long Offset = 0; Offset < Size; Offset += PageSize) + { + if (HasPTAddr(GpuAddr + Offset)) + { + return Reserve(Size, Align); + } + } + + for (long Offset = 0; Offset < Size; Offset += PageSize) + { + SetPTAddr(GpuAddr + Offset, PteReserved); + } + + return GpuAddr; + } + + public long Reserve(long Size, long Align) + { + long Position = GetFreePosition(Size, Align); + + if (Position != -1) + { + for (long Offset = 0; Offset < Size; Offset += PageSize) + { + SetPTAddr(Position + Offset, PteReserved); + } + } + + return Position; + } + + private long GetFreePosition(long Size, long Align = 1) + { + long Position = 0; + long FreeSize = 0; + + Align = (Align + PageMask) & ~PageMask; + + while (Position + FreeSize < AddrSize) + { + if (!HasPTAddr(Position + FreeSize)) + { + FreeSize += PageSize; + + if (FreeSize >= Size) + { + return Position; + } + } + else + { + Position += FreeSize + PageSize; + FreeSize = 0; + + long Remainder = Position % Align; + + if (Remainder != 0) + { + Position = (Position - Remainder) + Align; + } + } + } + + return -1; + } + + public long GetCpuAddr(long Position) + { + long BasePos = GetPTAddr(Position); + + if (BasePos < 0) + { + return -1; + } + + return BasePos + (Position & PageMask); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool HasPTAddr(long Position) + { + if (Position >> PTLvl0Bits + PTLvl1Bits + PTPageBits != 0) + { + return false; + } + + long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask; + long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask; + + if (PageTable[L0] == null) + { + return false; + } + + return PageTable[L0][L1] != PteUnmapped; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private long GetPTAddr(long Position) + { + long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask; + long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask; + + if (PageTable[L0] == null) + { + return -1; + } + + return PageTable[L0][L1]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetPTAddr(long Position, long TgtAddr) + { + long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask; + long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask; + + if (PageTable[L0] == null) + { + PageTable[L0] = new long[PTLvl1Size]; + + for (int Index = 0; Index < PTLvl1Size; Index++) + { + PageTable[L0][Index] = PteUnmapped; + } + } + + PageTable[L0][L1] = TgtAddr; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Gpu/NsGpuPBEntry.cs b/Ryujinx/Gpu/NsGpuPBEntry.cs new file mode 100644 index 000000000..226a7f61d --- /dev/null +++ b/Ryujinx/Gpu/NsGpuPBEntry.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; + +namespace Ryujinx.Gpu +{ + struct NsGpuPBEntry + { + public NsGpuRegister Register { get; private set; } + + public int SubChannel { get; private set; } + + private int[] m_Arguments; + + public ReadOnlyCollection Arguments => Array.AsReadOnly(m_Arguments); + + public NsGpuPBEntry(NsGpuRegister Register, int SubChannel, params int[] Arguments) + { + this.Register = Register; + this.SubChannel = SubChannel; + this.m_Arguments = Arguments; + } + + public static NsGpuPBEntry[] DecodePushBuffer(byte[] Data) + { + using (MemoryStream MS = new MemoryStream(Data)) + { + BinaryReader Reader = new BinaryReader(MS); + + List GpFifos = new List(); + + bool CanRead() => MS.Position + 4 <= MS.Length; + + while (CanRead()) + { + int Packed = Reader.ReadInt32(); + + int Reg = (Packed << 2) & 0x7ffc; + int SubC = (Packed >> 13) & 7; + int Args = (Packed >> 16) & 0x1fff; + int Mode = (Packed >> 29) & 7; + + if (Mode == 4) + { + //Inline Mode. + GpFifos.Add(new NsGpuPBEntry((NsGpuRegister)Reg, SubC, Args)); + } + else + { + //Word mode. + if (Mode == 1) + { + //Sequential Mode. + for (int Index = 0; Index < Args && CanRead(); Index++, Reg += 4) + { + GpFifos.Add(new NsGpuPBEntry((NsGpuRegister)Reg, SubC, Reader.ReadInt32())); + } + } + else + { + //Non-Sequential Mode. + int[] Arguments = new int[Args]; + + for (int Index = 0; Index < Args && CanRead(); Index++) + { + Arguments[Index] = Reader.ReadInt32(); + } + + GpFifos.Add(new NsGpuPBEntry((NsGpuRegister)Reg, SubC, Arguments)); + } + } + } + + return GpFifos.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/Gpu/NsGpuPGraph.cs b/Ryujinx/Gpu/NsGpuPGraph.cs new file mode 100644 index 000000000..e40b6283e --- /dev/null +++ b/Ryujinx/Gpu/NsGpuPGraph.cs @@ -0,0 +1,276 @@ +using ChocolArm64.Memory; +using Gal; +using System.Collections.Generic; + +namespace Ryujinx.Gpu +{ + class NsGpuPGraph + { + private NsGpu Gpu; + + private int[] Registers; + + public NsGpuEngine[] SubChannels; + + private Dictionary CurrentVertexBuffers; + + public NsGpuPGraph(NsGpu Gpu) + { + this.Gpu = Gpu; + + Registers = new int[0x1000]; + + SubChannels = new NsGpuEngine[8]; + + CurrentVertexBuffers = new Dictionary(); + } + + public void ProcessPushBuffer(NsGpuPBEntry[] PushBuffer, AMemory Memory) + { + bool HasQuery = false; + + foreach (NsGpuPBEntry Entry in PushBuffer) + { + if (Entry.Arguments.Count == 1) + { + SetRegister(Entry.Register, Entry.Arguments[0]); + } + + switch (Entry.Register) + { + case NsGpuRegister.BindChannel: + if (Entry.Arguments.Count > 0) + { + SubChannels[Entry.SubChannel] = (NsGpuEngine)Entry.Arguments[0]; + } + break; + + case NsGpuRegister._3dVertexArray0Fetch: + SendVertexBuffers(Memory); + break; + + case NsGpuRegister._3dCbData0: + if (GetRegister(NsGpuRegister._3dCbPos) == 0x20) + { + SendTexture(Memory); + } + break; + + case NsGpuRegister._3dQueryAddressHigh: + case NsGpuRegister._3dQueryAddressLow: + case NsGpuRegister._3dQuerySequence: + case NsGpuRegister._3dQueryGet: + HasQuery = true; + break; + } + } + + if (HasQuery) + { + long Position = + (long)GetRegister(NsGpuRegister._3dQueryAddressHigh) << 32 | + (long)GetRegister(NsGpuRegister._3dQueryAddressLow) << 0; + + int Seq = GetRegister(NsGpuRegister._3dQuerySequence); + int Get = GetRegister(NsGpuRegister._3dQueryGet); + + int Mode = Get & 3; + + if (Mode == 0) + { + //Write + Position = Gpu.MemoryMgr.GetCpuAddr(Position); + + if (Position != -1) + { + Gpu.Renderer.QueueAction(delegate() + { + Memory.WriteInt32(Position, Seq); + }); + } + } + } + } + + private void SendVertexBuffers(AMemory Memory) + { + long Position = + (long)GetRegister(NsGpuRegister._3dVertexArray0StartHigh) << 32 | + (long)GetRegister(NsGpuRegister._3dVertexArray0StartLow) << 0; + + long Limit = + (long)GetRegister(NsGpuRegister._3dVertexArray0LimitHigh) << 32 | + (long)GetRegister(NsGpuRegister._3dVertexArray0LimitLow) << 0; + + int VbIndex = CurrentVertexBuffers.Count; + + if (!CurrentVertexBuffers.TryAdd(Position, VbIndex)) + { + VbIndex = CurrentVertexBuffers[Position]; + } + + if (Limit != 0) + { + long Size = (Limit - Position) + 1; + + Position = Gpu.MemoryMgr.GetCpuAddr(Position); + + if (Position != -1) + { + byte[] Buffer = AMemoryHelper.ReadBytes(Memory, Position, (int)Size); + + int Stride = GetRegister(NsGpuRegister._3dVertexArray0Fetch) & 0xfff; + + List Attribs = new List(); + + for (int Attr = 0; Attr < 16; Attr++) + { + int Packed = GetRegister(NsGpuRegister._3dVertexAttrib0Format + Attr * 4); + + GalVertexAttrib Attrib = new GalVertexAttrib(Attr, + (Packed >> 0) & 0x1f, + ((Packed >> 6) & 0x1) != 0, + (Packed >> 7) & 0x3fff, + (GalVertexAttribSize)((Packed >> 21) & 0x3f), + (GalVertexAttribType)((Packed >> 27) & 0x7), + ((Packed >> 31) & 0x1) != 0); + + if (Attrib.Offset < Stride) + { + Attribs.Add(Attrib); + } + } + + Gpu.Renderer.QueueAction(delegate() + { + Gpu.Renderer.SendVertexBuffer(VbIndex, Buffer, Stride, Attribs.ToArray()); + }); + } + } + } + + private void SendTexture(AMemory Memory) + { + long TicPos = (long)GetRegister(NsGpuRegister._3dTicAddressHigh) << 32 | + (long)GetRegister(NsGpuRegister._3dTicAddressLow) << 0; + + int CbData = GetRegister(NsGpuRegister._3dCbData0); + + int TicIndex = (CbData >> 0) & 0xfffff; + int TscIndex = (CbData >> 20) & 0xfff; //I guess? + + TicPos = Gpu.MemoryMgr.GetCpuAddr(TicPos + TicIndex * 0x20); + + if (TicPos != -1) + { + int Word0 = Memory.ReadInt32(TicPos + 0x0); + int Word1 = Memory.ReadInt32(TicPos + 0x4); + int Word2 = Memory.ReadInt32(TicPos + 0x8); + int Word3 = Memory.ReadInt32(TicPos + 0xc); + int Word4 = Memory.ReadInt32(TicPos + 0x10); + int Word5 = Memory.ReadInt32(TicPos + 0x14); + int Word6 = Memory.ReadInt32(TicPos + 0x18); + int Word7 = Memory.ReadInt32(TicPos + 0x1c); + + long TexAddress = Word1; + + TexAddress |= (long)(Word2 & 0xff) << 32; + + TexAddress = Gpu.MemoryMgr.GetCpuAddr(TexAddress); + + if (TexAddress != -1) + { + NsGpuTextureFormat Format = (NsGpuTextureFormat)(Word0 & 0x7f); + + int Width = (Word4 & 0xffff) + 1; + int Height = (Word5 & 0xffff) + 1; + + byte[] Buffer = GetDecodedTexture(Memory, Format, TexAddress, Width, Height); + + if (Buffer != null) + { + Gpu.Renderer.QueueAction(delegate() + { + Gpu.Renderer.SendR8G8B8A8Texture(0, Buffer, Width, Height); + }); + } + } + } + } + + private static byte[] GetDecodedTexture( + AMemory Memory, + NsGpuTextureFormat Format, + long Position, + int Width, + int Height) + { + byte[] Data = null; + + switch (Format) + { + case NsGpuTextureFormat.BC1: + { + int Size = (Width * Height) >> 1; + + Data = AMemoryHelper.ReadBytes(Memory, Position, Size); + + Data = BCn.DecodeBC1(new NsGpuTexture() + { + Width = Width, + Height = Height, + Data = Data + }, 0); + + break; + } + + case NsGpuTextureFormat.BC2: + { + int Size = Width * Height; + + Data = AMemoryHelper.ReadBytes(Memory, Position, Size); + + Data = BCn.DecodeBC2(new NsGpuTexture() + { + Width = Width, + Height = Height, + Data = Data + }, 0); + + break; + } + + case NsGpuTextureFormat.BC3: + { + int Size = Width * Height; + + Data = AMemoryHelper.ReadBytes(Memory, Position, Size); + + Data = BCn.DecodeBC3(new NsGpuTexture() + { + Width = Width, + Height = Height, + Data = Data + }, 0); + + break; + } + + //default: throw new NotImplementedException(Format.ToString()); + } + + return Data; + } + + public int GetRegister(NsGpuRegister Register) + { + return Registers[((int)Register >> 2) & 0xfff]; + } + + public void SetRegister(NsGpuRegister Register, int Value) + { + Registers[((int)Register >> 2) & 0xfff] = Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Gpu/NsGpuRegister.cs b/Ryujinx/Gpu/NsGpuRegister.cs new file mode 100644 index 000000000..740ca9feb --- /dev/null +++ b/Ryujinx/Gpu/NsGpuRegister.cs @@ -0,0 +1,93 @@ +namespace Ryujinx.Gpu +{ + enum NsGpuRegister + { + BindChannel = 0, + + _2dClipEnable = 0x0290, + _2dOperation = 0x02ac, + + _3dGlobalBase = 0x02c8, + _3dRt0AddressHigh = 0x0800, + _3dRt0AddressLow = 0x0804, + _3dRt0Horiz = 0x0808, + _3dRt0Vert = 0x080c, + _3dRt0Format = 0x0810, + _3dRt0BlockDimensions = 0x0814, + _3dRt0ArrayMode = 0x0818, + _3dRt0LayerStride = 0x081c, + _3dRt0BaseLayer = 0x0820, + _3dViewportScaleX = 0x0a00, + _3dViewportScaleY = 0x0a04, + _3dViewportScaleZ = 0x0a08, + _3dViewportTranslateX = 0x0a0c, + _3dViewportTranslateY = 0x0a10, + _3dViewportTranslateZ = 0x0a14, + _3dViewportHoriz = 0x0c00, + _3dViewportVert = 0x0c04, + _3dDepthRangeNear = 0x0c08, + _3dDepthRangeFar = 0x0c0c, + _3dClearColorR = 0x0d80, + _3dClearColorG = 0x0d84, + _3dClearColorB = 0x0d88, + _3dClearColorA = 0x0d8c, + _3dScreenScissorHoriz = 0x0ff4, + _3dScreenScissorVert = 0x0ff8, + _3dVertexAttrib0Format = 0x1160, + _3dVertexAttrib1Format = 0x1164, + _3dVertexAttrib2Format = 0x1168, + _3dVertexAttrib3Format = 0x116c, + _3dVertexAttrib4Format = 0x1170, + _3dVertexAttrib5Format = 0x1174, + _3dVertexAttrib6Format = 0x1178, + _3dVertexAttrib7Format = 0x117c, + _3dVertexAttrib8Format = 0x1180, + _3dVertexAttrib9Format = 0x1184, + _3dVertexAttrib10Format = 0x1188, + _3dVertexAttrib11Format = 0x118c, + _3dVertexAttrib12Format = 0x1190, + _3dVertexAttrib13Format = 0x1194, + _3dVertexAttrib14Format = 0x1198, + _3dVertexAttrib15Format = 0x119c, + _3dScreenYControl = 0x13ac, + _3dTscAddressHigh = 0x155c, + _3dTscAddressLow = 0x1560, + _3dTscLimit = 0x1564, + _3dTicAddressHigh = 0x1574, + _3dTicAddressLow = 0x1578, + _3dTicLimit = 0x157c, + _3dMultiSampleMode = 0x15d0, + _3dVertexEndGl = 0x1614, + _3dVertexBeginGl = 0x1618, + _3dQueryAddressHigh = 0x1b00, + _3dQueryAddressLow = 0x1b04, + _3dQuerySequence = 0x1b08, + _3dQueryGet = 0x1b0c, + _3dVertexArray0Fetch = 0x1c00, + _3dVertexArray0StartHigh = 0x1c04, + _3dVertexArray0StartLow = 0x1c08, + _3dVertexArray1Fetch = 0x1c10, //todo: the rest + _3dVertexArray0LimitHigh = 0x1f00, + _3dVertexArray0LimitLow = 0x1f04, + _3dCbSize = 0x2380, + _3dCbAddressHigh = 0x2384, + _3dCbAddressLow = 0x2388, + _3dCbPos = 0x238c, + _3dCbData0 = 0x2390, + _3dCbData1 = 0x2394, + _3dCbData2 = 0x2398, + _3dCbData3 = 0x239c, + _3dCbData4 = 0x23a0, + _3dCbData5 = 0x23a4, + _3dCbData6 = 0x23a8, + _3dCbData7 = 0x23ac, + _3dCbData8 = 0x23b0, + _3dCbData9 = 0x23b4, + _3dCbData10 = 0x23b8, + _3dCbData11 = 0x23bc, + _3dCbData12 = 0x23c0, + _3dCbData13 = 0x23c4, + _3dCbData14 = 0x23c8, + _3dCbData15 = 0x23cc, + } +} \ No newline at end of file diff --git a/Ryujinx/Gpu/NsGpuTexture.cs b/Ryujinx/Gpu/NsGpuTexture.cs new file mode 100644 index 000000000..26500c04a --- /dev/null +++ b/Ryujinx/Gpu/NsGpuTexture.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Gpu +{ + struct NsGpuTexture + { + public int Width; + public int Height; + + public byte[] Data; + } +} \ No newline at end of file diff --git a/Ryujinx/Gpu/NsGpuTextureFormat.cs b/Ryujinx/Gpu/NsGpuTextureFormat.cs new file mode 100644 index 000000000..9bb122812 --- /dev/null +++ b/Ryujinx/Gpu/NsGpuTextureFormat.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Gpu +{ + enum NsGpuTextureFormat + { + BC1 = 0x24, + BC2 = 0x25, + BC3 = 0x26 + } +} \ No newline at end of file diff --git a/Ryujinx/Gpu/SwizzleAddr.cs b/Ryujinx/Gpu/SwizzleAddr.cs new file mode 100644 index 000000000..5ad35a538 --- /dev/null +++ b/Ryujinx/Gpu/SwizzleAddr.cs @@ -0,0 +1,144 @@ +using System; + +namespace Ryujinx.Gpu +{ + class SwizzleAddr + { + private int Width; + + private int XB; + private int YB; + + public SwizzleAddr(int Width, int Height, int Pad) + { + int W = Pow2RoundUp(Width); + int H = Pow2RoundUp(Height); + + XB = CountZeros(W); + YB = CountZeros(H); + + int HH = H >> 1; + + if (!IsPow2(Height) && Height <= HH + HH / 3 && YB > 3) + { + YB--; + } + + this.Width = RoundSize(Width, Pad); + } + + private static int Pow2RoundUp(int Value) + { + Value--; + + Value |= (Value >> 1); + Value |= (Value >> 2); + Value |= (Value >> 4); + Value |= (Value >> 8); + Value |= (Value >> 16); + + return ++Value; + } + + private static bool IsPow2(int Value) + { + return Value != 0 && (Value & (Value - 1)) == 0; + } + + private static int CountZeros(int Value) + { + int Count = 0; + + for (int i = 0; i < 32; i++) + { + if ((Value & (1 << i)) != 0) + { + break; + } + + Count++; + } + + return Count; + } + + private static int RoundSize(int Size, int Pad) + { + int Mask = Pad - 1; + + if ((Size & Mask) != 0) + { + Size &= ~Mask; + Size += Pad; + } + + return Size; + } + + public int GetSwizzledAddress8(int X, int Y) + { + return GetSwizzledAddress(X, Y, 4); + } + + public int GetSwizzledAddress16(int X, int Y) + { + return GetSwizzledAddress(X, Y, 3); + } + + public int GetSwizzledAddress32(int X, int Y) + { + return GetSwizzledAddress(X, Y, 2); + } + + public int GetSwizzledAddress64(int X, int Y) + { + return GetSwizzledAddress(X, Y, 1); + } + + public int GetSwizzledAddress128(int X, int Y) + { + return GetSwizzledAddress(X, Y, 0); + } + + private int GetSwizzledAddress(int X, int Y, int XBase) + { + /* + * Examples of patterns: + * x x y x y y x y 0 0 0 0 64 x 64 dxt5 + * x x x x x y y y y x y y x y 0 0 0 0 512 x 512 dxt5 + * y x x x x x x y y y y x y y x y 0 0 0 0 1024 x 1024 dxt5 + * y y x x x x x x y y y y x y y x y x 0 0 0 2048 x 2048 dxt1 + * y y y x x x x x x y y y y x y y x y x x 0 0 1024 x 1024 rgba8888 + * + * Read from right to left, LSB first. + */ + int XCnt = XBase; + int YCnt = 1; + int XUsed = 0; + int YUsed = 0; + int Address = 0; + + while (XUsed < XBase + 2 && XUsed + XCnt < XB) + { + int XMask = (1 << XCnt) - 1; + int YMask = (1 << YCnt) - 1; + + Address |= (X & XMask) << XUsed + YUsed; + Address |= (Y & YMask) << XUsed + YUsed + XCnt; + + X >>= XCnt; + Y >>= YCnt; + + XUsed += XCnt; + YUsed += YCnt; + + XCnt = Math.Min(XB - XUsed, 1); + YCnt = Math.Min(YB - YUsed, YCnt << 1); + } + + Address |= (X + Y * (Width >> XUsed)) << (XUsed + YUsed); + + return Address; + } + } +} diff --git a/Ryujinx/Loaders/Compression/Lz4.cs b/Ryujinx/Loaders/Compression/Lz4.cs new file mode 100644 index 000000000..aace200cc --- /dev/null +++ b/Ryujinx/Loaders/Compression/Lz4.cs @@ -0,0 +1,78 @@ +using System; + +namespace Ryujinx.Loaders.Compression +{ + static class Lz4 + { + public static byte[] Decompress(byte[] Cmp, int DecLength) + { + byte[] Dec = new byte[DecLength]; + + int CmpPos = 0; + int DecPos = 0; + + int GetLength(int Length) + { + byte Sum; + + if (Length == 0xf) + { + do + { + Length += (Sum = Cmp[CmpPos++]); + } + while (Sum == 0xff); + } + + return Length; + } + + do + { + byte Token = Cmp[CmpPos++]; + + int EncCount = (Token >> 0) & 0xf; + int LitCount = (Token >> 4) & 0xf; + + //Copy literal chunck + LitCount = GetLength(LitCount); + + Buffer.BlockCopy(Cmp, CmpPos, Dec, DecPos, LitCount); + + CmpPos += LitCount; + DecPos += LitCount; + + if (CmpPos >= Cmp.Length) + { + break; + } + + //Copy compressed chunck + int Back = Cmp[CmpPos++] << 0 | + Cmp[CmpPos++] << 8; + + EncCount = GetLength(EncCount) + 4; + + int EncPos = DecPos - Back; + + if (EncCount <= Back) + { + Buffer.BlockCopy(Dec, EncPos, Dec, DecPos, EncCount); + + DecPos += EncCount; + } + else + { + while (EncCount-- > 0) + { + Dec[DecPos++] = Dec[EncPos++]; + } + } + } + while (CmpPos < Cmp.Length && + DecPos < Dec.Length); + + return Dec; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Loaders/ElfDyn.cs b/Ryujinx/Loaders/ElfDyn.cs new file mode 100644 index 000000000..595d6cfb5 --- /dev/null +++ b/Ryujinx/Loaders/ElfDyn.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Loaders +{ + struct ElfDyn + { + public ElfDynTag Tag { get; private set; } + + public long Value { get; private set; } + + public ElfDyn(ElfDynTag Tag, long Value) + { + this.Tag = Tag; + this.Value = Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Loaders/ElfDynTag.cs b/Ryujinx/Loaders/ElfDynTag.cs new file mode 100644 index 000000000..fb6cab3fb --- /dev/null +++ b/Ryujinx/Loaders/ElfDynTag.cs @@ -0,0 +1,72 @@ +namespace Ryujinx.Loaders +{ + enum ElfDynTag + { + DT_NULL = 0, + DT_NEEDED = 1, + DT_PLTRELSZ = 2, + DT_PLTGOT = 3, + DT_HASH = 4, + DT_STRTAB = 5, + DT_SYMTAB = 6, + DT_RELA = 7, + DT_RELASZ = 8, + DT_RELAENT = 9, + DT_STRSZ = 10, + DT_SYMENT = 11, + DT_INIT = 12, + DT_FINI = 13, + DT_SONAME = 14, + DT_RPATH = 15, + DT_SYMBOLIC = 16, + DT_REL = 17, + DT_RELSZ = 18, + DT_RELENT = 19, + DT_PLTREL = 20, + DT_DEBUG = 21, + DT_TEXTREL = 22, + DT_JMPREL = 23, + DT_BIND_NOW = 24, + DT_INIT_ARRAY = 25, + DT_FINI_ARRAY = 26, + DT_INIT_ARRAYSZ = 27, + DT_FINI_ARRAYSZ = 28, + DT_RUNPATH = 29, + DT_FLAGS = 30, + DT_ENCODING = 32, + DT_PREINIT_ARRAY = 32, + DT_PREINIT_ARRAYSZ = 33, + DT_GNU_PRELINKED = 0x6ffffdf5, + DT_GNU_CONFLICTSZ = 0x6ffffdf6, + DT_GNU_LIBLISTSZ = 0x6ffffdf7, + DT_CHECKSUM = 0x6ffffdf8, + DT_PLTPADSZ = 0x6ffffdf9, + DT_MOVEENT = 0x6ffffdfa, + DT_MOVESZ = 0x6ffffdfb, + DT_FEATURE_1 = 0x6ffffdfc, + DT_POSFLAG_1 = 0x6ffffdfd, + DT_SYMINSZ = 0x6ffffdfe, + DT_SYMINENT = 0x6ffffdff, + DT_GNU_HASH = 0x6ffffef5, + DT_TLSDESC_PLT = 0x6ffffef6, + DT_TLSDESC_GOT = 0x6ffffef7, + DT_GNU_CONFLICT = 0x6ffffef8, + DT_GNU_LIBLIST = 0x6ffffef9, + DT_CONFIG = 0x6ffffefa, + DT_DEPAUDIT = 0x6ffffefb, + DT_AUDIT = 0x6ffffefc, + DT_PLTPAD = 0x6ffffefd, + DT_MOVETAB = 0x6ffffefe, + DT_SYMINFO = 0x6ffffeff, + DT_VERSYM = 0x6ffffff0, + DT_RELACOUNT = 0x6ffffff9, + DT_RELCOUNT = 0x6ffffffa, + DT_FLAGS_1 = 0x6ffffffb, + DT_VERDEF = 0x6ffffffc, + DT_VERDEFNUM = 0x6ffffffd, + DT_VERNEED = 0x6ffffffe, + DT_VERNEEDNUM = 0x6fffffff, + DT_AUXILIARY = 0x7ffffffd, + DT_FILTER = 0x7fffffff + } +} \ No newline at end of file diff --git a/Ryujinx/Loaders/ElfRel.cs b/Ryujinx/Loaders/ElfRel.cs new file mode 100644 index 000000000..8b691d99c --- /dev/null +++ b/Ryujinx/Loaders/ElfRel.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.Loaders +{ + struct ElfRel + { + public long Offset { get; private set; } + public long Addend { get; private set; } + + public ElfSym Symbol { get; private set; } + public ElfRelType Type { get; private set; } + + public ElfRel(long Offset, long Addend, ElfSym Symbol, ElfRelType Type) + { + this.Offset = Offset; + this.Addend = Addend; + this.Symbol = Symbol; + this.Type = Type; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Loaders/ElfRelType.cs b/Ryujinx/Loaders/ElfRelType.cs new file mode 100644 index 000000000..cc638b19d --- /dev/null +++ b/Ryujinx/Loaders/ElfRelType.cs @@ -0,0 +1,128 @@ +namespace Ryujinx.Loaders +{ + enum ElfRelType + { + R_AARCH64_NONE = 0, + R_AARCH64_ABS64 = 257, + R_AARCH64_ABS32 = 258, + R_AARCH64_ABS16 = 259, + R_AARCH64_PREL64 = 260, + R_AARCH64_PREL32 = 261, + R_AARCH64_PREL16 = 262, + R_AARCH64_MOVW_UABS_G0 = 263, + R_AARCH64_MOVW_UABS_G0_NC = 264, + R_AARCH64_MOVW_UABS_G1 = 265, + R_AARCH64_MOVW_UABS_G1_NC = 266, + R_AARCH64_MOVW_UABS_G2 = 267, + R_AARCH64_MOVW_UABS_G2_NC = 268, + R_AARCH64_MOVW_UABS_G3 = 269, + R_AARCH64_MOVW_SABS_G0 = 270, + R_AARCH64_MOVW_SABS_G1 = 271, + R_AARCH64_MOVW_SABS_G2 = 272, + R_AARCH64_LD_PREL_LO19 = 273, + R_AARCH64_ADR_PREL_LO21 = 274, + R_AARCH64_ADR_PREL_PG_HI21 = 275, + R_AARCH64_ADR_PREL_PG_HI21_NC = 276, + R_AARCH64_ADD_ABS_LO12_NC = 277, + R_AARCH64_LDST8_ABS_LO12_NC = 278, + R_AARCH64_TSTBR14 = 279, + R_AARCH64_CONDBR19 = 280, + R_AARCH64_JUMP26 = 282, + R_AARCH64_CALL26 = 283, + R_AARCH64_LDST16_ABS_LO12_NC = 284, + R_AARCH64_LDST32_ABS_LO12_NC = 285, + R_AARCH64_LDST64_ABS_LO12_NC = 286, + R_AARCH64_MOVW_PREL_G0 = 287, + R_AARCH64_MOVW_PREL_G0_NC = 288, + R_AARCH64_MOVW_PREL_G1 = 289, + R_AARCH64_MOVW_PREL_G1_NC = 290, + R_AARCH64_MOVW_PREL_G2 = 291, + R_AARCH64_MOVW_PREL_G2_NC = 292, + R_AARCH64_MOVW_PREL_G3 = 293, + R_AARCH64_LDST128_ABS_LO12_NC = 299, + R_AARCH64_MOVW_GOTOFF_G0 = 300, + R_AARCH64_MOVW_GOTOFF_G0_NC = 301, + R_AARCH64_MOVW_GOTOFF_G1 = 302, + R_AARCH64_MOVW_GOTOFF_G1_NC = 303, + R_AARCH64_MOVW_GOTOFF_G2 = 304, + R_AARCH64_MOVW_GOTOFF_G2_NC = 305, + R_AARCH64_MOVW_GOTOFF_G3 = 306, + R_AARCH64_GOTREL64 = 307, + R_AARCH64_GOTREL32 = 308, + R_AARCH64_GOT_LD_PREL19 = 309, + R_AARCH64_LD64_GOTOFF_LO15 = 310, + R_AARCH64_ADR_GOT_PAGE = 311, + R_AARCH64_LD64_GOT_LO12_NC = 312, + R_AARCH64_LD64_GOTPAGE_LO15 = 313, + R_AARCH64_TLSGD_ADR_PREL21 = 512, + R_AARCH64_TLSGD_ADR_PAGE21 = 513, + R_AARCH64_TLSGD_ADD_LO12_NC = 514, + R_AARCH64_TLSGD_MOVW_G1 = 515, + R_AARCH64_TLSGD_MOVW_G0_NC = 516, + R_AARCH64_TLSLD_ADR_PREL21 = 517, + R_AARCH64_TLSLD_ADR_PAGE21 = 518, + R_AARCH64_TLSLD_ADD_LO12_NC = 519, + R_AARCH64_TLSLD_MOVW_G1 = 520, + R_AARCH64_TLSLD_MOVW_G0_NC = 521, + R_AARCH64_TLSLD_LD_PREL19 = 522, + R_AARCH64_TLSLD_MOVW_DTPREL_G2 = 523, + R_AARCH64_TLSLD_MOVW_DTPREL_G1 = 524, + R_AARCH64_TLSLD_MOVW_DTPREL_G1_NC = 525, + R_AARCH64_TLSLD_MOVW_DTPREL_G0 = 526, + R_AARCH64_TLSLD_MOVW_DTPREL_G0_NC = 527, + R_AARCH64_TLSLD_ADD_DTPREL_HI12 = 528, + R_AARCH64_TLSLD_ADD_DTPREL_LO12 = 529, + R_AARCH64_TLSLD_ADD_DTPREL_LO12_NC = 530, + R_AARCH64_TLSLD_LDST8_DTPREL_LO12 = 531, + R_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC = 532, + R_AARCH64_TLSLD_LDST16_DTPREL_LO12 = 533, + R_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC = 534, + R_AARCH64_TLSLD_LDST32_DTPREL_LO12 = 535, + R_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC = 536, + R_AARCH64_TLSLD_LDST64_DTPREL_LO12 = 537, + R_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC = 538, + R_AARCH64_TLSIE_MOVW_GOTTPREL_G1 = 539, + R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC = 540, + R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21 = 541, + R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC = 542, + R_AARCH64_TLSIE_LD_GOTTPREL_PREL19 = 543, + R_AARCH64_TLSLE_MOVW_TPREL_G2 = 544, + R_AARCH64_TLSLE_MOVW_TPREL_G1 = 545, + R_AARCH64_TLSLE_MOVW_TPREL_G1_NC = 546, + R_AARCH64_TLSLE_MOVW_TPREL_G0 = 547, + R_AARCH64_TLSLE_MOVW_TPREL_G0_NC = 548, + R_AARCH64_TLSLE_ADD_TPREL_HI12 = 549, + R_AARCH64_TLSLE_ADD_TPREL_LO12 = 550, + R_AARCH64_TLSLE_ADD_TPREL_LO12_NC = 551, + R_AARCH64_TLSLE_LDST8_TPREL_LO12 = 552, + R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC = 553, + R_AARCH64_TLSLE_LDST16_TPREL_LO12 = 554, + R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC = 555, + R_AARCH64_TLSLE_LDST32_TPREL_LO12 = 556, + R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC = 557, + R_AARCH64_TLSLE_LDST64_TPREL_LO12 = 558, + R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC = 559, + R_AARCH64_TLSDESC_LD_PREL19 = 560, + R_AARCH64_TLSDESC_ADR_PREL21 = 561, + R_AARCH64_TLSDESC_ADR_PAGE21 = 562, + R_AARCH64_TLSDESC_LD64_LO12 = 563, + R_AARCH64_TLSDESC_ADD_LO12 = 564, + R_AARCH64_TLSDESC_OFF_G1 = 565, + R_AARCH64_TLSDESC_OFF_G0_NC = 566, + R_AARCH64_TLSDESC_LDR = 567, + R_AARCH64_TLSDESC_ADD = 568, + R_AARCH64_TLSDESC_CALL = 569, + R_AARCH64_TLSLE_LDST128_TPREL_LO12 = 570, + R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC = 571, + R_AARCH64_TLSLD_LDST128_DTPREL_LO12 = 572, + R_AARCH64_TLSLD_LDST128_DTPREL_LO12_NC = 573, + R_AARCH64_COPY = 1024, + R_AARCH64_GLOB_DAT = 1025, + R_AARCH64_JUMP_SLOT = 1026, + R_AARCH64_RELATIVE = 1027, + R_AARCH64_TLS_DTPMOD64 = 1028, + R_AARCH64_TLS_DTPREL64 = 1029, + R_AARCH64_TLS_TPREL64 = 1030, + R_AARCH64_TLSDESC = 1031 + } +} \ No newline at end of file diff --git a/Ryujinx/Loaders/ElfSym.cs b/Ryujinx/Loaders/ElfSym.cs new file mode 100644 index 000000000..c4ed810ce --- /dev/null +++ b/Ryujinx/Loaders/ElfSym.cs @@ -0,0 +1,43 @@ +namespace Ryujinx.Loaders +{ + struct ElfSym + { + public string Name { get; private set; } + + public ElfSymType Type { get; private set; } + public ElfSymBinding Binding { get; private set; } + public ElfSymVisibility Visibility { get; private set; } + + public bool IsFuncOrObject => + Type == ElfSymType.STT_FUNC || + Type == ElfSymType.STT_OBJECT; + + public bool IsGlobalOrWeak => + Binding == ElfSymBinding.STB_GLOBAL || + Binding == ElfSymBinding.STB_WEAK; + + public int SHIdx { get; private set; } + public long ValueAbs { get; private set; } + public long Value { get; private set; } + public long Size { get; private set; } + + public ElfSym( + string Name, + int Info, + int Other, + int SHIdx, + long ImageBase, + long Value, + long Size) + { + this.Name = Name; + this.Type = (ElfSymType)(Info & 0xf); + this.Binding = (ElfSymBinding)(Info >> 4); + this.Visibility = (ElfSymVisibility)Other; + this.SHIdx = SHIdx; + this.ValueAbs = Value + ImageBase; + this.Value = Value; + this.Size = Size; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Loaders/ElfSymBinding.cs b/Ryujinx/Loaders/ElfSymBinding.cs new file mode 100644 index 000000000..8bbc6d4e0 --- /dev/null +++ b/Ryujinx/Loaders/ElfSymBinding.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Loaders +{ + enum ElfSymBinding + { + STB_LOCAL = 0, + STB_GLOBAL = 1, + STB_WEAK = 2 + } +} \ No newline at end of file diff --git a/Ryujinx/Loaders/ElfSymType.cs b/Ryujinx/Loaders/ElfSymType.cs new file mode 100644 index 000000000..e504411e1 --- /dev/null +++ b/Ryujinx/Loaders/ElfSymType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Loaders +{ + enum ElfSymType + { + STT_NOTYPE = 0, + STT_OBJECT = 1, + STT_FUNC = 2, + STT_SECTION = 3, + STT_FILE = 4, + STT_COMMON = 5, + STT_TLS = 6 + } +} \ No newline at end of file diff --git a/Ryujinx/Loaders/ElfSymVisibility.cs b/Ryujinx/Loaders/ElfSymVisibility.cs new file mode 100644 index 000000000..a308ef795 --- /dev/null +++ b/Ryujinx/Loaders/ElfSymVisibility.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Loaders +{ + enum ElfSymVisibility + { + STV_DEFAULT = 0, + STV_INTERNAL = 1, + STV_HIDDEN = 2, + STV_PROTECTED = 3 + } +} \ No newline at end of file diff --git a/Ryujinx/Loaders/Executable.cs b/Ryujinx/Loaders/Executable.cs new file mode 100644 index 000000000..31caf2946 --- /dev/null +++ b/Ryujinx/Loaders/Executable.cs @@ -0,0 +1,144 @@ +using ChocolArm64.Memory; +using Ryujinx.Loaders.Executables; +using Ryujinx.OsHle; +using System.Collections.Generic; + +namespace Ryujinx.Loaders +{ + class Executable + { + private IElf NsoData; + private AMemory Memory; + + private ElfDyn[] Dynamic; + + public long ImageBase { get; private set; } + public long ImageEnd { get; private set; } + + public Executable(IElf NsoData, AMemory Memory, long ImageBase) + { + this.NsoData = NsoData; + this.Memory = Memory; + this.ImageBase = ImageBase; + this.ImageEnd = ImageBase; + + WriteData(ImageBase + NsoData.TextOffset, NsoData.Text, MemoryType.CodeStatic, AMemoryPerm.RX); + WriteData(ImageBase + NsoData.ROOffset, NsoData.RO, MemoryType.Normal, AMemoryPerm.Read); + WriteData(ImageBase + NsoData.DataOffset, NsoData.Data, MemoryType.Normal, AMemoryPerm.RW); + + if (NsoData.Text.Count == 0) + { + return; + } + + long Mod0Offset = ImageBase + NsoData.Mod0Offset; + + int Mod0Magic = Memory.ReadInt32(Mod0Offset + 0x0); + long DynamicOffset = Memory.ReadInt32(Mod0Offset + 0x4) + Mod0Offset; + long BssStartOffset = Memory.ReadInt32(Mod0Offset + 0x8) + Mod0Offset; + long BssEndOffset = Memory.ReadInt32(Mod0Offset + 0xc) + Mod0Offset; + long EhHdrStartOffset = Memory.ReadInt32(Mod0Offset + 0x10) + Mod0Offset; + long EhHdrEndOffset = Memory.ReadInt32(Mod0Offset + 0x14) + Mod0Offset; + long ModObjOffset = Memory.ReadInt32(Mod0Offset + 0x18) + Mod0Offset; + + long BssSize = BssEndOffset - BssStartOffset; + + Memory.Manager.MapPhys(BssStartOffset, BssSize, (int)MemoryType.Normal, AMemoryPerm.RW); + + ImageEnd = BssEndOffset; + + List Dynamic = new List(); + + while (true) + { + long TagVal = Memory.ReadInt64(DynamicOffset + 0); + long Value = Memory.ReadInt64(DynamicOffset + 8); + + DynamicOffset += 0x10; + + ElfDynTag Tag = (ElfDynTag)TagVal; + + if (Tag == ElfDynTag.DT_NULL) + { + break; + } + + Dynamic.Add(new ElfDyn(Tag, Value)); + } + + this.Dynamic = Dynamic.ToArray(); + } + + private void WriteData( + long Position, + IList Data, + MemoryType Type, + AMemoryPerm Perm) + { + Memory.Manager.MapPhys(Position, Data.Count, (int)Type, Perm); + + for (int Index = 0; Index < Data.Count; Index++) + { + Memory.WriteByte(Position + Index, Data[Index]); + } + } + + private ElfRel GetRelocation(long Position) + { + long Offset = Memory.ReadInt64(Position + 0); + long Info = Memory.ReadInt64(Position + 8); + long Addend = Memory.ReadInt64(Position + 16); + + int RelType = (int)(Info >> 0); + int SymIdx = (int)(Info >> 32); + + ElfSym Symbol = GetSymbol(SymIdx); + + return new ElfRel(Offset, Addend, Symbol, (ElfRelType)RelType); + } + + private ElfSym GetSymbol(int Index) + { + long StrTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_STRTAB); + long SymTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_SYMTAB); + + long SymEntSize = GetFirstValue(ElfDynTag.DT_SYMENT); + + long Position = SymTblAddr + Index * SymEntSize; + + return GetSymbol(Position, StrTblAddr); + } + + private ElfSym GetSymbol(long Position, long StrTblAddr) + { + int NameIndex = Memory.ReadInt32(Position + 0); + int Info = Memory.ReadByte(Position + 4); + int Other = Memory.ReadByte(Position + 5); + int SHIdx = Memory.ReadInt16(Position + 6); + long Value = Memory.ReadInt64(Position + 8); + long Size = Memory.ReadInt64(Position + 16); + + string Name = string.Empty; + + for (int Chr; (Chr = Memory.ReadByte(StrTblAddr + NameIndex++)) != 0;) + { + Name += (char)Chr; + } + + return new ElfSym(Name, Info, Other, SHIdx, ImageBase, Value, Size); + } + + private long GetFirstValue(ElfDynTag Tag) + { + foreach (ElfDyn Entry in Dynamic) + { + if (Entry.Tag == Tag) + { + return Entry.Value; + } + } + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Loaders/Executables/IElf.cs b/Ryujinx/Loaders/Executables/IElf.cs new file mode 100644 index 000000000..bc8eb1bcf --- /dev/null +++ b/Ryujinx/Loaders/Executables/IElf.cs @@ -0,0 +1,17 @@ +using System.Collections.ObjectModel; + +namespace Ryujinx.Loaders.Executables +{ + interface IElf + { + ReadOnlyCollection Text { get; } + ReadOnlyCollection RO { get; } + ReadOnlyCollection Data { get; } + + int Mod0Offset { get; } + int TextOffset { get; } + int ROOffset { get; } + int DataOffset { get; } + int BssSize { get; } + } +} \ No newline at end of file diff --git a/Ryujinx/Loaders/Executables/Nro.cs b/Ryujinx/Loaders/Executables/Nro.cs new file mode 100644 index 000000000..5067ba12e --- /dev/null +++ b/Ryujinx/Loaders/Executables/Nro.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.ObjectModel; +using System.IO; + +namespace Ryujinx.Loaders.Executables +{ + class Nro : IElf + { + private byte[] m_Text; + private byte[] m_RO; + private byte[] m_Data; + + public ReadOnlyCollection Text => Array.AsReadOnly(m_Text); + public ReadOnlyCollection RO => Array.AsReadOnly(m_RO); + public ReadOnlyCollection Data => Array.AsReadOnly(m_Data); + + public int Mod0Offset { get; private set; } + public int TextOffset { get; private set; } + public int ROOffset { get; private set; } + public int DataOffset { get; private set; } + public int BssSize { get; private set; } + + public Nro(Stream Input) + { + BinaryReader Reader = new BinaryReader(Input); + + Input.Seek(4, SeekOrigin.Begin); + + int Mod0Offset = Reader.ReadInt32(); + int Padding8 = Reader.ReadInt32(); + int Paddingc = Reader.ReadInt32(); + int NroMagic = Reader.ReadInt32(); + int Unknown14 = Reader.ReadInt32(); + int FileSize = Reader.ReadInt32(); + int Unknown1c = Reader.ReadInt32(); + int TextOffset = Reader.ReadInt32(); + int TextSize = Reader.ReadInt32(); + int ROOffset = Reader.ReadInt32(); + int ROSize = Reader.ReadInt32(); + int DataOffset = Reader.ReadInt32(); + int DataSize = Reader.ReadInt32(); + int BssSize = Reader.ReadInt32(); + + this.Mod0Offset = Mod0Offset; + this.TextOffset = TextOffset; + this.ROOffset = ROOffset; + this.DataOffset = DataOffset; + this.BssSize = BssSize; + + byte[] Read(long Position, int Size) + { + Input.Seek(Position, SeekOrigin.Begin); + + return Reader.ReadBytes(Size); + } + + m_Text = Read(TextOffset, TextSize); + m_RO = Read(ROOffset, ROSize); + m_Data = Read(DataOffset, DataSize); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Loaders/Executables/Nso.cs b/Ryujinx/Loaders/Executables/Nso.cs new file mode 100644 index 000000000..ae9a9af62 --- /dev/null +++ b/Ryujinx/Loaders/Executables/Nso.cs @@ -0,0 +1,122 @@ +using Ryujinx.Loaders.Compression; +using System; +using System.Collections.ObjectModel; +using System.IO; + +namespace Ryujinx.Loaders.Executables +{ + class Nso : IElf + { + private byte[] m_Text; + private byte[] m_RO; + private byte[] m_Data; + + public ReadOnlyCollection Text => Array.AsReadOnly(m_Text); + public ReadOnlyCollection RO => Array.AsReadOnly(m_RO); + public ReadOnlyCollection Data => Array.AsReadOnly(m_Data); + + public int Mod0Offset { get; private set; } + public int TextOffset { get; private set; } + public int ROOffset { get; private set; } + public int DataOffset { get; private set; } + public int BssSize { get; private set; } + + [Flags] + private enum NsoFlags + { + IsTextCompressed = 1 << 0, + IsROCompressed = 1 << 1, + IsDataCompressed = 1 << 2, + HasTextHash = 1 << 3, + HasROHash = 1 << 4, + HasDataHash = 1 << 5 + } + + public Nso(Stream Input) + { + BinaryReader Reader = new BinaryReader(Input); + + Input.Seek(0, SeekOrigin.Begin); + + int NsoMagic = Reader.ReadInt32(); + int Version = Reader.ReadInt32(); + int Reserved = Reader.ReadInt32(); + int FlagsMsk = Reader.ReadInt32(); + int TextOffset = Reader.ReadInt32(); + int TextMemOffset = Reader.ReadInt32(); + int TextDecSize = Reader.ReadInt32(); + int ModNameOffset = Reader.ReadInt32(); + int ROOffset = Reader.ReadInt32(); + int ROMemOffset = Reader.ReadInt32(); + int RODecSize = Reader.ReadInt32(); + int ModNameSize = Reader.ReadInt32(); + int DataOffset = Reader.ReadInt32(); + int DataMemOffset = Reader.ReadInt32(); + int DataDecSize = Reader.ReadInt32(); + int BssSize = Reader.ReadInt32(); + + byte[] BuildId = Reader.ReadBytes(0x20); + + int TextSize = Reader.ReadInt32(); + int ROSize = Reader.ReadInt32(); + int DataSize = Reader.ReadInt32(); + + Input.Seek(0x24, SeekOrigin.Current); + + int DynStrOffset = Reader.ReadInt32(); + int DynStrSize = Reader.ReadInt32(); + int DynSymOffset = Reader.ReadInt32(); + int DynSymSize = Reader.ReadInt32(); + + byte[] TextHash = Reader.ReadBytes(0x20); + byte[] ROHash = Reader.ReadBytes(0x20); + byte[] DataHash = Reader.ReadBytes(0x20); + + NsoFlags Flags = (NsoFlags)FlagsMsk; + + this.TextOffset = TextMemOffset; + this.ROOffset = ROMemOffset; + this.DataOffset = DataMemOffset; + this.BssSize = BssSize; + + //Text segment + Input.Seek(TextOffset, SeekOrigin.Begin); + + m_Text = Reader.ReadBytes(TextSize); + + if (Flags.HasFlag(NsoFlags.IsTextCompressed) || true) + { + m_Text = Lz4.Decompress(m_Text, TextDecSize); + } + + //Read-only data segment + Input.Seek(ROOffset, SeekOrigin.Begin); + + m_RO = Reader.ReadBytes(ROSize); + + if (Flags.HasFlag(NsoFlags.IsROCompressed) || true) + { + m_RO = Lz4.Decompress(m_RO, RODecSize); + } + + //Data segment + Input.Seek(DataOffset, SeekOrigin.Begin); + + m_Data = Reader.ReadBytes(DataSize); + + if (Flags.HasFlag(NsoFlags.IsDataCompressed) || true) + { + m_Data = Lz4.Decompress(m_Data, DataDecSize); + } + + using (MemoryStream Text = new MemoryStream(m_Text)) + { + BinaryReader TextReader = new BinaryReader(Text); + + Text.Seek(4, SeekOrigin.Begin); + + Mod0Offset = TextReader.ReadInt32(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/CondVar.cs b/Ryujinx/OsHle/CondVar.cs new file mode 100644 index 000000000..02fb8ba37 --- /dev/null +++ b/Ryujinx/OsHle/CondVar.cs @@ -0,0 +1,86 @@ +using ChocolArm64.Memory; +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.OsHle +{ + class CondVar + { + private AMemory Memory; + + private long CondVarAddress; + private long Timeout; + + private class WaitingThread + { + public int Handle; + + public ManualResetEvent Event; + + public WaitingThread(int Handle, ManualResetEvent Event) + { + this.Handle = Handle; + this.Event = Event; + } + } + + private ConcurrentQueue WaitingThreads; + + public CondVar(AMemory Memory, long CondVarAddress, long Timeout) + { + this.Memory = Memory; + this.CondVarAddress = CondVarAddress; + this.Timeout = Timeout; + + WaitingThreads = new ConcurrentQueue(); + } + + public void WaitForSignal(int ThreadHandle) + { + int Count = Memory.ReadInt32(CondVarAddress); + + if (Count <= 0) + { + return; + } + + Memory.WriteInt32(CondVarAddress, Count - 1); + + ManualResetEvent Event = new ManualResetEvent(false); + + WaitingThreads.Enqueue(new WaitingThread(ThreadHandle, Event)); + + if (Timeout != -1) + { + Event.WaitOne((int)(Timeout / 1000000)); + } + else + { + Event.WaitOne(); + } + } + + public void SetSignal(int Count) + { + if (Count == -1) + { + while (WaitingThreads.TryDequeue(out WaitingThread Thread)) + { + Thread.Event.Set(); + } + + Memory.WriteInt32(CondVarAddress, WaitingThreads.Count); + } + else + { + //TODO: Threads with the highest priority needs to be signaled first. + if (WaitingThreads.TryDequeue(out WaitingThread Thread)) + { + Thread.Event.Set(); + } + + Memory.WriteInt32(CondVarAddress, Count); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Display.cs b/Ryujinx/OsHle/Display.cs new file mode 100644 index 000000000..f62430fa8 --- /dev/null +++ b/Ryujinx/OsHle/Display.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.OsHle +{ + class Display + { + public string Name { get; private set; } + + public Display(string Name) + { + this.Name = Name; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/FileDesc.cs b/Ryujinx/OsHle/FileDesc.cs new file mode 100644 index 000000000..2a21f5007 --- /dev/null +++ b/Ryujinx/OsHle/FileDesc.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.OsHle +{ + class FileDesc + { + public string Name { get; private set; } + + public FileDesc(string Name) + { + this.Name = Name; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Handles/HDomain.cs b/Ryujinx/OsHle/Handles/HDomain.cs new file mode 100644 index 000000000..fd252f5d7 --- /dev/null +++ b/Ryujinx/OsHle/Handles/HDomain.cs @@ -0,0 +1,58 @@ +using Ryujinx.OsHle.Utilities; +using System; +using System.Collections.Generic; + +namespace Ryujinx.OsHle.Handles +{ + class HDomain : HSession + { + private Dictionary Objects; + + private IdPool ObjIds; + + public HDomain(HSession Session) : base(Session) + { + Objects = new Dictionary(); + + ObjIds = new IdPool(); + } + + public int GenertateObjectId(object Obj) + { + int Id = ObjIds.GenerateId(); + + if (Id == -1) + { + throw new InvalidOperationException(); + } + + Objects.Add(Id, Obj); + + return Id; + } + + public void DeleteObject(int Id) + { + if (Objects.TryGetValue(Id, out object Obj)) + { + if (Obj is IDisposable DisposableObj) + { + DisposableObj.Dispose(); + } + + ObjIds.DeleteId(Id); + Objects.Remove(Id); + } + } + + public object GetObject(int Id) + { + if (Objects.TryGetValue(Id, out object Obj)) + { + return Obj; + } + + return null; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Handles/HEvent.cs b/Ryujinx/OsHle/Handles/HEvent.cs new file mode 100644 index 000000000..d9d0ff4c2 --- /dev/null +++ b/Ryujinx/OsHle/Handles/HEvent.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.OsHle.Handles +{ + class HEvent + { + + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Handles/HNvMap.cs b/Ryujinx/OsHle/Handles/HNvMap.cs new file mode 100644 index 000000000..3e15eda38 --- /dev/null +++ b/Ryujinx/OsHle/Handles/HNvMap.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.OsHle.Handles +{ + class HNvMap + { + public int Id { get; private set; } + public int Size { get; private set; } + + public int Align { get; set; } + public int Kind { get; set; } + public long Address { get; set; } + + public HNvMap(int Id, int Size) + { + this.Id = Id; + this.Size = Size; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Handles/HSession.cs b/Ryujinx/OsHle/Handles/HSession.cs new file mode 100644 index 000000000..6b9016591 --- /dev/null +++ b/Ryujinx/OsHle/Handles/HSession.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.OsHle.Handles +{ + class HSession + { + public string ServiceName { get; private set; } + + public bool IsInitialized { get; private set; } + + public int State { get; set; } + + public HSession(string ServiceName) + { + this.ServiceName = ServiceName; + } + + public HSession(HSession Session) + { + ServiceName = Session.ServiceName; + IsInitialized = Session.IsInitialized; + } + + public void Initialize() + { + IsInitialized = true; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Handles/HSessionObj.cs b/Ryujinx/OsHle/Handles/HSessionObj.cs new file mode 100644 index 000000000..c1e5e41a4 --- /dev/null +++ b/Ryujinx/OsHle/Handles/HSessionObj.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.OsHle.Handles +{ + class HSessionObj : HSession + { + public object Obj { get; private set; } + + public HSessionObj(HSession Session, object Obj) : base(Session) + { + this.Obj = Obj; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Handles/HSharedMem.cs b/Ryujinx/OsHle/Handles/HSharedMem.cs new file mode 100644 index 000000000..acc1e7ebd --- /dev/null +++ b/Ryujinx/OsHle/Handles/HSharedMem.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.OsHle.Handles +{ + class HSharedMem + { + public long PhysPos { get; private set; } + + public HSharedMem(long PhysPos) + { + this.PhysPos = PhysPos; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Handles/HThread.cs b/Ryujinx/OsHle/Handles/HThread.cs new file mode 100644 index 000000000..9fb0b57be --- /dev/null +++ b/Ryujinx/OsHle/Handles/HThread.cs @@ -0,0 +1,14 @@ +using ChocolArm64; + +namespace Ryujinx.OsHle.Handles +{ + class HThread + { + public AThread Thread { get; private set; } + + public HThread(AThread Thread) + { + this.Thread = Thread; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Handles/HTransferMem.cs b/Ryujinx/OsHle/Handles/HTransferMem.cs new file mode 100644 index 000000000..962d1b664 --- /dev/null +++ b/Ryujinx/OsHle/Handles/HTransferMem.cs @@ -0,0 +1,23 @@ +using ChocolArm64.Memory; + +namespace Ryujinx.OsHle.Handles +{ + class HTransferMem + { + public AMemory Memory { get; private set; } + public AMemoryPerm Perm { get; private set; } + + public long Position { get; private set; } + public long Size { get; private set; } + public long PhysPos { get; private set; } + + public HTransferMem(AMemory Memory, AMemoryPerm Perm, long Position, long Size, long PhysPos) + { + this.Memory = Memory; + this.Perm = Perm; + this.Position = Position; + this.Size = Size; + this.PhysPos = PhysPos; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Horizon.cs b/Ryujinx/OsHle/Horizon.cs new file mode 100644 index 000000000..bae33f8eb --- /dev/null +++ b/Ryujinx/OsHle/Horizon.cs @@ -0,0 +1,163 @@ +using ChocolArm64.Memory; +using Ryujinx.Loaders.Executables; +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Utilities; +using System.Collections.Concurrent; +using System.IO; + +namespace Ryujinx.OsHle +{ + class Horizon + { + internal const int HidSize = 0x40000; + internal const int FontSize = 0x50; + + internal int HidHandle { get; private set; } + internal int FontHandle { get; private set; } + + public long HidOffset { get; private set; } + public long FontOffset { get; private set; } + + internal IdPool IdGen { get; private set; } + internal IdPool NvMapIds { get; private set; } + + internal IdPoolWithObj Handles { get; private set; } + internal IdPoolWithObj Fds { get; private set; } + internal IdPoolWithObj Displays { get; private set; } + + public ConcurrentDictionary Mutexes { get; private set; } + public ConcurrentDictionary CondVars { get; private set; } + + private ConcurrentDictionary Processes; + + private AMemoryAlloc Allocator; + + private Switch Ns; + + public Horizon(Switch Ns) + { + this.Ns = Ns; + + IdGen = new IdPool(); + NvMapIds = new IdPool(); + + Handles = new IdPoolWithObj(); + Fds = new IdPoolWithObj(); + Displays = new IdPoolWithObj(); + + Mutexes = new ConcurrentDictionary(); + CondVars = new ConcurrentDictionary(); + + Processes = new ConcurrentDictionary(); + + Allocator = new AMemoryAlloc(); + + HidOffset = Allocator.Alloc(HidSize); + FontOffset = Allocator.Alloc(FontSize); + + HidHandle = Handles.GenerateId(new HSharedMem(HidOffset)); + FontHandle = Handles.GenerateId(new HSharedMem(FontOffset)); + } + + public void LoadCart(string ExeFsDir, string RomFsFile = null) + { + if (RomFsFile != null) + { + Ns.VFs.LoadRomFs(RomFsFile); + } + + int ProcessId = IdGen.GenerateId(); + + Process MainProcess = new Process(Ns, Allocator, ProcessId); + + void LoadNso(string FileName) + { + foreach (string File in Directory.GetFiles(ExeFsDir, FileName)) + { + if (Path.GetExtension(File) != string.Empty) + { + continue; + } + + using (FileStream Input = new FileStream(File, FileMode.Open)) + { + Nso Program = new Nso(Input); + + MainProcess.LoadProgram(Program); + } + } + } + + LoadNso("rtld"); + + MainProcess.SetEmptyArgs(); + + LoadNso("main"); + LoadNso("subsdk*"); + LoadNso("sdk"); + + MainProcess.InitializeHeap(); + MainProcess.Run(); + + Processes.TryAdd(ProcessId, MainProcess); + } + + public void LoadProgram(string FileName) + { + int ProcessId = IdGen.GenerateId(); + + Process MainProcess = new Process(Ns, Allocator, ProcessId); + + using (FileStream Input = new FileStream(FileName, FileMode.Open)) + { + if (Path.GetExtension(FileName).ToLower() == ".nro") + { + MainProcess.LoadProgram(new Nro(Input)); + } + else + { + MainProcess.LoadProgram(new Nso(Input)); + } + } + + MainProcess.SetEmptyArgs(); + MainProcess.InitializeHeap(); + MainProcess.Run(); + + Processes.TryAdd(ProcessId, MainProcess); + } + + public void StopAllProcesses() + { + foreach (Process Process in Processes.Values) + { + Process.StopAllThreads(); + } + } + + internal bool TryGetProcess(int ProcessId, out Process Process) + { + if (!Processes.TryGetValue(ProcessId, out Process)) + { + return false; + } + + return true; + } + + internal void CloseHandle(int Handle) + { + object HndData = Handles.GetData(Handle); + + if (HndData is HTransferMem TransferMem) + { + TransferMem.Memory.Manager.Reprotect( + TransferMem.Position, + TransferMem.Size, + TransferMem.Perm); + } + + Handles.Delete(Handle); + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Ipc/IpcBuffDesc.cs b/Ryujinx/OsHle/Ipc/IpcBuffDesc.cs new file mode 100644 index 000000000..41b1efe03 --- /dev/null +++ b/Ryujinx/OsHle/Ipc/IpcBuffDesc.cs @@ -0,0 +1,27 @@ +using System.IO; + +namespace Ryujinx.OsHle.Ipc +{ + struct IpcBuffDesc + { + public long Position { get; private set; } + public long Size { get; private set; } + public int Flags { get; private set; } + + public IpcBuffDesc(BinaryReader Reader) + { + long Word0 = Reader.ReadUInt32(); + long Word1 = Reader.ReadUInt32(); + long Word2 = Reader.ReadUInt32(); + + Position = Word1; + Position |= (Word2 << 4) & 0x0f00000000; + Position |= (Word2 << 34) & 0x7000000000; + + Size = Word0; + Size |= (Word2 << 8) & 0xf00000000; + + Flags = (int)Word2 & 3; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Ipc/IpcDomCmd.cs b/Ryujinx/OsHle/Ipc/IpcDomCmd.cs new file mode 100644 index 000000000..035671859 --- /dev/null +++ b/Ryujinx/OsHle/Ipc/IpcDomCmd.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.OsHle.Ipc +{ + enum IpcDomCmd + { + SendMsg = 1, + DeleteObj = 2 + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Ipc/IpcHandleDesc.cs b/Ryujinx/OsHle/Ipc/IpcHandleDesc.cs new file mode 100644 index 000000000..fa5d7e1de --- /dev/null +++ b/Ryujinx/OsHle/Ipc/IpcHandleDesc.cs @@ -0,0 +1,90 @@ +using System; +using System.IO; + +namespace Ryujinx.OsHle.Ipc +{ + class IpcHandleDesc + { + public bool HasPId { get; private set; } + + public long PId { get; private set; } + + public int[] ToCopy { get; private set; } + public int[] ToMove { get; private set; } + + public IpcHandleDesc(BinaryReader Reader) + { + int Word = Reader.ReadInt32(); + + HasPId = (Word & 1) != 0; + + ToCopy = new int[(Word >> 1) & 0xf]; + ToMove = new int[(Word >> 5) & 0xf]; + + PId = HasPId ? Reader.ReadInt64() : 0; + + for (int Index = 0; Index < ToCopy.Length; Index++) + { + ToCopy[Index] = Reader.ReadInt32(); + } + + for (int Index = 0; Index < ToMove.Length; Index++) + { + ToMove[Index] = Reader.ReadInt32(); + } + } + + public IpcHandleDesc(int[] Copy, int[] Move) + { + ToCopy = Copy ?? throw new ArgumentNullException(nameof(Copy)); + ToMove = Move ?? throw new ArgumentNullException(nameof(Move)); + } + + public IpcHandleDesc(int[] Copy, int[] Move, long PId) : this(Copy, Move) + { + this.PId = PId; + + HasPId = true; + } + + public static IpcHandleDesc MakeCopy(int Handle) => new IpcHandleDesc( + new int[] { Handle }, + new int[0]); + + public static IpcHandleDesc MakeMove(int Handle) => new IpcHandleDesc( + new int[0], + new int[] { Handle }); + + public byte[] GetBytes() + { + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + int Word = HasPId ? 1 : 0; + + Word |= (ToCopy.Length & 0xf) << 1; + Word |= (ToMove.Length & 0xf) << 5; + + Writer.Write(Word); + + if (HasPId) + { + Writer.Write((long)PId); + } + + foreach (int Handle in ToCopy) + { + Writer.Write(Handle); + } + + foreach (int Handle in ToMove) + { + Writer.Write(Handle); + } + + return MS.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Ipc/IpcHandler.cs b/Ryujinx/OsHle/Ipc/IpcHandler.cs new file mode 100644 index 000000000..444b1022b --- /dev/null +++ b/Ryujinx/OsHle/Ipc/IpcHandler.cs @@ -0,0 +1,358 @@ +using ChocolArm64.Memory; +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Objects; +using Ryujinx.OsHle.Services; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.OsHle.Ipc +{ + static class IpcHandler + { + private delegate long ServiceProcessRequest(ServiceCtx Context); + + private static Dictionary<(string, int), ServiceProcessRequest> ServiceCmds = + new Dictionary<(string, int), ServiceProcessRequest>() + { + { ( "acc:u0", 3), Service.AccU0ListOpenUsers }, + { ( "acc:u0", 5), Service.AccU0GetProfile }, + { ( "acc:u0", 100), Service.AccU0InitializeApplicationInfo }, + { ( "acc:u0", 101), Service.AccU0GetBaasAccountManagerForApplication }, + { ( "apm", 0), Service.ApmOpenSession }, + { ( "appletOE", 0), Service.AppletOpenApplicationProxy }, + { ( "audout:u", 0), Service.AudOutListAudioOuts }, + { ( "audout:u", 1), Service.AudOutOpenAudioOut }, + { ( "audren:u", 0), Service.AudRenOpenAudioRenderer }, + { ( "audren:u", 1), Service.AudRenGetAudioRendererWorkBufferSize }, + { ( "friend:a", 0), Service.FriendCreateFriendService }, + { ( "fsp-srv", 1), Service.FspSrvInitialize }, + { ( "fsp-srv", 51), Service.FspSrvMountSaveData }, + { ( "fsp-srv", 200), Service.FspSrvOpenDataStorageByCurrentProcess }, + { ( "fsp-srv", 203), Service.FspSrvOpenRomStorage }, + { ( "fsp-srv", 1005), Service.FspSrvGetGlobalAccessLogMode }, + { ( "hid", 0), Service.HidCreateAppletResource }, + { ( "hid", 11), Service.HidActivateTouchScreen }, + { ( "hid", 100), Service.HidSetSupportedNpadStyleSet }, + { ( "hid", 102), Service.HidSetSupportedNpadIdType }, + { ( "hid", 103), Service.HidActivateNpad }, + { ( "hid", 120), Service.HidSetNpadJoyHoldType }, + { ( "lm", 0), Service.LmInitialize }, + { ( "nvdrv", 0), Service.NvDrvOpen }, + { ( "nvdrv", 1), Service.NvDrvIoctl }, + { ( "nvdrv", 2), Service.NvDrvClose }, + { ( "nvdrv", 3), Service.NvDrvInitialize }, + { ( "nvdrv", 4), Service.NvDrvQueryEvent }, + { ( "nvdrv:a", 0), Service.NvDrvOpen }, + { ( "nvdrv:a", 1), Service.NvDrvIoctl }, + { ( "nvdrv:a", 2), Service.NvDrvClose }, + { ( "nvdrv:a", 3), Service.NvDrvInitialize }, + { ( "nvdrv:a", 4), Service.NvDrvQueryEvent }, + { ( "pctl:a", 0), Service.PctlCreateService }, + { ( "pl:u", 1), Service.PlGetLoadState }, + { ( "pl:u", 2), Service.PlGetFontSize }, + { ( "pl:u", 3), Service.PlGetSharedMemoryAddressOffset }, + { ( "pl:u", 4), Service.PlGetSharedMemoryNativeHandle }, + { ( "set", 1), Service.SetGetAvailableLanguageCodes }, + { ( "sm:", 0), Service.SmInitialize }, + { ( "sm:", 1), Service.SmGetService }, + { ( "time:u", 0), Service.TimeGetStandardUserSystemClock }, + { ( "time:u", 1), Service.TimeGetStandardNetworkSystemClock }, + { ( "time:u", 2), Service.TimeGetStandardSteadyClock }, + { ( "time:u", 3), Service.TimeGetTimeZoneService }, + { ( "time:s", 0), Service.TimeGetStandardUserSystemClock }, + { ( "time:s", 1), Service.TimeGetStandardNetworkSystemClock }, + { ( "time:s", 2), Service.TimeGetStandardSteadyClock }, + { ( "time:s", 3), Service.TimeGetTimeZoneService }, + { ( "vi:m", 2), Service.ViGetDisplayService }, + }; + + private static Dictionary<(Type, int), ServiceProcessRequest> ObjectCmds = + new Dictionary<(Type, int), ServiceProcessRequest>() + { + //IManagerForApplication + { (typeof(AccIManagerForApplication), 0), AccIManagerForApplication.CheckAvailability }, + { (typeof(AccIManagerForApplication), 1), AccIManagerForApplication.GetAccountId }, + + //IProfile + { (typeof(AccIProfile), 1), AccIProfile.GetBase }, + + //IApplicationFunctions + { (typeof(AmIApplicationFunctions), 1), AmIApplicationFunctions.PopLaunchParameter }, + { (typeof(AmIApplicationFunctions), 20), AmIApplicationFunctions.EnsureSaveData }, + { (typeof(AmIApplicationFunctions), 21), AmIApplicationFunctions.GetDesiredLanguage }, + + //IApplicationProxy + { (typeof(AmIApplicationProxy), 0), AmIApplicationProxy.GetCommonStateGetter }, + { (typeof(AmIApplicationProxy), 1), AmIApplicationProxy.GetSelfController }, + { (typeof(AmIApplicationProxy), 2), AmIApplicationProxy.GetWindowController }, + { (typeof(AmIApplicationProxy), 3), AmIApplicationProxy.GetAudioController }, + { (typeof(AmIApplicationProxy), 4), AmIApplicationProxy.GetDisplayController }, + { (typeof(AmIApplicationProxy), 11), AmIApplicationProxy.GetLibraryAppletCreator }, + { (typeof(AmIApplicationProxy), 20), AmIApplicationProxy.GetApplicationFunctions }, + { (typeof(AmIApplicationProxy), 1000), AmIApplicationProxy.GetDebugFunctions }, + + //ICommonStateGetter + { (typeof(AmICommonStateGetter), 0), AmICommonStateGetter.GetEventHandle }, + { (typeof(AmICommonStateGetter), 1), AmICommonStateGetter.ReceiveMessage }, + { (typeof(AmICommonStateGetter), 5), AmICommonStateGetter.GetOperationMode }, + { (typeof(AmICommonStateGetter), 6), AmICommonStateGetter.GetPerformanceMode }, + { (typeof(AmICommonStateGetter), 9), AmICommonStateGetter.GetCurrentFocusState }, + + //ISelfController + { (typeof(AmISelfController), 11), AmISelfController.SetOperationModeChangedNotification }, + { (typeof(AmISelfController), 12), AmISelfController.SetPerformanceModeChangedNotification }, + { (typeof(AmISelfController), 13), AmISelfController.SetFocusHandlingMode }, + + //IStorage + { (typeof(AmIStorage), 0), AmIStorage.Open }, + + //IStorageAccessor + { (typeof(AmIStorageAccessor), 0), AmIStorageAccessor.GetSize }, + { (typeof(AmIStorageAccessor), 11), AmIStorageAccessor.Read }, + + //IWindowController + { (typeof(AmIWindowController), 1), AmIWindowController.GetAppletResourceUserId }, + { (typeof(AmIWindowController), 10), AmIWindowController.AcquireForegroundRights }, + + //ISession + { (typeof(ApmISession), 0), ApmISession.SetPerformanceConfiguration }, + + //IAudioRenderer + { (typeof(AudIAudioRenderer), 4), AudIAudioRenderer.RequestUpdateAudioRenderer }, + { (typeof(AudIAudioRenderer), 5), AudIAudioRenderer.StartAudioRenderer }, + { (typeof(AudIAudioRenderer), 7), AudIAudioRenderer.QuerySystemEvent }, + + //IFile + { (typeof(FspSrvIFile), 0), FspSrvIFile.Read }, + { (typeof(FspSrvIFile), 1), FspSrvIFile.Write }, + + //IFileSystem + { (typeof(FspSrvIFileSystem), 7), FspSrvIFileSystem.GetEntryType }, + { (typeof(FspSrvIFileSystem), 8), FspSrvIFileSystem.OpenFile }, + { (typeof(FspSrvIFileSystem), 10), FspSrvIFileSystem.Commit }, + + //IStorage + { (typeof(FspSrvIStorage), 0), FspSrvIStorage.Read }, + + //IAppletResource + { (typeof(HidIAppletResource), 0), HidIAppletResource.GetSharedMemoryHandle }, + + //ISystemClock + { (typeof(TimeISystemClock), 0), TimeISystemClock.GetCurrentTime }, + + //IApplicationDisplayService + { (typeof(ViIApplicationDisplayService), 100), ViIApplicationDisplayService.GetRelayService }, + { (typeof(ViIApplicationDisplayService), 101), ViIApplicationDisplayService.GetSystemDisplayService }, + { (typeof(ViIApplicationDisplayService), 102), ViIApplicationDisplayService.GetManagerDisplayService }, + { (typeof(ViIApplicationDisplayService), 1010), ViIApplicationDisplayService.OpenDisplay }, + { (typeof(ViIApplicationDisplayService), 2020), ViIApplicationDisplayService.OpenLayer }, + { (typeof(ViIApplicationDisplayService), 2030), ViIApplicationDisplayService.CreateStrayLayer }, + { (typeof(ViIApplicationDisplayService), 2101), ViIApplicationDisplayService.SetLayerScalingMode }, + { (typeof(ViIApplicationDisplayService), 5202), ViIApplicationDisplayService.GetDisplayVSyncEvent }, + + //IHOSBinderDriver + { (typeof(ViIHOSBinderDriver), 0), ViIHOSBinderDriver.TransactParcel }, + { (typeof(ViIHOSBinderDriver), 1), ViIHOSBinderDriver.AdjustRefcount }, + { (typeof(ViIHOSBinderDriver), 2), ViIHOSBinderDriver.GetNativeHandle }, + + //IManagerDisplayService + { (typeof(ViIManagerDisplayService), 2010), ViIManagerDisplayService.CreateManagedLayer }, + { (typeof(ViIManagerDisplayService), 6000), ViIManagerDisplayService.AddToLayerStack }, + + //ISystemDisplayService + { (typeof(ViISystemDisplayService), 2205), ViISystemDisplayService.SetLayerZ }, + }; + + private const long SfciMagic = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'I' << 24; + private const long SfcoMagic = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'O' << 24; + + public static void ProcessRequest( + Switch Ns, + AMemory Memory, + HSession Session, + IpcMessage Request, + long CmdPtr, + int HndId) + { + IpcMessage Response = new IpcMessage(Request.IsDomain); + + using (MemoryStream Raw = new MemoryStream(Request.RawData)) + { + BinaryReader ReqReader = new BinaryReader(Raw); + + if (Request.Type == IpcMessageType.Request) + { + string ServiceName = Session.ServiceName; + + ServiceProcessRequest ProcReq = null; + + bool IgnoreNullPR = false; + + if (Session is HDomain Dom) + { + if (Request.DomCmd == IpcDomCmd.SendMsg) + { + long Magic = ReqReader.ReadInt64(); + int CmdId = (int)ReqReader.ReadInt64(); + + object Obj = Dom.GetObject(Request.DomObjId); + + if (Obj is HDomain) + { + ServiceCmds.TryGetValue((ServiceName, CmdId), out ProcReq); + } + else if (Obj != null) + { + ObjectCmds.TryGetValue((Obj.GetType(), CmdId), out ProcReq); + } + } + else if (Request.DomCmd == IpcDomCmd.DeleteObj) + { + Dom.DeleteObject(Request.DomObjId); + + Response = FillResponse(Response, 0); + + IgnoreNullPR = true; + } + } + else + { + long Magic = ReqReader.ReadInt64(); + int CmdId = (int)ReqReader.ReadInt64(); + + if (Session is HSessionObj) + { + object Obj = ((HSessionObj)Session).Obj; + + ObjectCmds.TryGetValue((Obj.GetType(), CmdId), out ProcReq); + } + else + { + ServiceCmds.TryGetValue((ServiceName, CmdId), out ProcReq); + } + } + + if (ProcReq != null) + { + using (MemoryStream ResMS = new MemoryStream()) + { + BinaryWriter ResWriter = new BinaryWriter(ResMS); + + ServiceCtx Context = new ServiceCtx( + Ns, + Memory, + Session, + Request, + Response, + ReqReader, + ResWriter); + + long Result = ProcReq(Context); + + Response = FillResponse(Response, Result, ResMS.ToArray()); + } + } + else if (!IgnoreNullPR) + { + throw new NotImplementedException(ServiceName); + } + } + else if (Request.Type == IpcMessageType.Control) + { + long Magic = ReqReader.ReadInt64(); + long CmdId = ReqReader.ReadInt64(); + + switch (CmdId) + { + case 0: Request = IpcConvertSessionToDomain(Ns, Session, Response, HndId); break; + case 3: Request = IpcQueryBufferPointerSize(Response); break; + case 4: Request = IpcDuplicateSessionEx(Ns, Session, Response, ReqReader); break; + + default: throw new NotImplementedException(CmdId.ToString()); + } + } + else if (Request.Type == IpcMessageType.Unknown2) + { + //TODO + } + else + { + throw new NotImplementedException(Request.Type.ToString()); + } + + AMemoryHelper.WriteBytes(Memory, CmdPtr, Response.GetBytes(CmdPtr)); + } + } + + private static IpcMessage IpcConvertSessionToDomain( + Switch Ns, + HSession Session, + IpcMessage Response, + int HndId) + { + HDomain Dom = new HDomain(Session); + + Ns.Os.Handles.ReplaceData(HndId, Dom); + + return FillResponse(Response, 0, Dom.GenertateObjectId(Dom)); + } + + private static IpcMessage IpcDuplicateSessionEx( + Switch Ns, + HSession Session, + IpcMessage Response, + BinaryReader ReqReader) + { + int Unknown = ReqReader.ReadInt32(); + + int Handle = Ns.Os.Handles.GenerateId(Session); + + Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); + + return FillResponse(Response, 0); + } + + private static IpcMessage IpcQueryBufferPointerSize(IpcMessage Response) + { + return FillResponse(Response, 0, 0x500); + } + + private static IpcMessage FillResponse(IpcMessage Response, long Result, params int[] Values) + { + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + foreach (int Value in Values) + { + Writer.Write(Value); + } + + return FillResponse(Response, Result, MS.ToArray()); + } + } + + private static IpcMessage FillResponse(IpcMessage Response, long Result, byte[] Data = null) + { + Response.Type = IpcMessageType.Response; + + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + Writer.Write(SfcoMagic); + Writer.Write(Result); + + if (Data != null) + { + Writer.Write(Data); + } + + Response.RawData = MS.ToArray(); + } + + return Response; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Ipc/IpcMessage.cs b/Ryujinx/OsHle/Ipc/IpcMessage.cs new file mode 100644 index 000000000..407fd65f9 --- /dev/null +++ b/Ryujinx/OsHle/Ipc/IpcMessage.cs @@ -0,0 +1,231 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.OsHle.Ipc +{ + class IpcMessage + { + public IpcMessageType Type { get; set; } + + public IpcHandleDesc HandleDesc { get; set; } + + public List PtrBuff { get; private set; } + public List SendBuff { get; private set; } + public List ReceiveBuff { get; private set; } + public List ExchangeBuff { get; private set; } + public List RecvListBuff { get; private set; } + + public List ResponseObjIds { get; private set; } + + public bool IsDomain { get; private set; } + public IpcDomCmd DomCmd { get; private set; } + public int DomObjId { get; private set; } + + public byte[] RawData { get; set; } + + public IpcMessage() + { + PtrBuff = new List(); + SendBuff = new List(); + ReceiveBuff = new List(); + ExchangeBuff = new List(); + RecvListBuff = new List(); + + ResponseObjIds = new List(); + } + + public IpcMessage(bool Domain) : this() + { + IsDomain = Domain; + } + + public IpcMessage(byte[] Data, long CmdPtr, bool Domain) : this() + { + using (MemoryStream MS = new MemoryStream(Data)) + { + BinaryReader Reader = new BinaryReader(MS); + + Initialize(Reader, CmdPtr, Domain); + } + } + + private void Initialize(BinaryReader Reader, long CmdPtr, bool Domain) + { + IsDomain = Domain; + + int Word0 = Reader.ReadInt32(); + int Word1 = Reader.ReadInt32(); + + Type = (IpcMessageType)(Word0 & 0xffff); + + int PtrBuffCount = (Word0 >> 16) & 0xf; + int SendBuffCount = (Word0 >> 20) & 0xf; + int RecvBuffCount = (Word0 >> 24) & 0xf; + int XchgBuffCount = (Word0 >> 28) & 0xf; + + int RawDataSize = (Word1 >> 0) & 0x3ff; + int RecvListFlags = (Word1 >> 10) & 0xf; + bool HndDescEnable = ((Word1 >> 31) & 0x1) != 0; + + if (HndDescEnable) + { + HandleDesc = new IpcHandleDesc(Reader); + } + + for (int Index = 0; Index < PtrBuffCount; Index++) + { + PtrBuff.Add(new IpcPtrBuffDesc(Reader)); + } + + void ReadBuff(List Buff, int Count) + { + for (int Index = 0; Index < Count; Index++) + { + Buff.Add(new IpcBuffDesc(Reader)); + } + } + + ReadBuff(SendBuff, SendBuffCount); + ReadBuff(ReceiveBuff, RecvBuffCount); + ReadBuff(ExchangeBuff, XchgBuffCount); + + RawDataSize *= 4; + + long RecvListPos = Reader.BaseStream.Position + RawDataSize; + + long Pad0 = GetPadSize16(Reader.BaseStream.Position + CmdPtr); + + Reader.BaseStream.Seek(Pad0, SeekOrigin.Current); + + int RecvListCount = RecvListFlags - 2; + + if (RecvListCount == 0) + { + RecvListCount = 1; + } + else if (RecvListCount < 0) + { + RecvListCount = 0; + } + + if (Domain) + { + int DomWord0 = Reader.ReadInt32(); + + DomCmd = (IpcDomCmd)(DomWord0 & 0xff); + + RawDataSize = (DomWord0 >> 16) & 0xffff; + + DomObjId = Reader.ReadInt32(); + + Reader.ReadInt64(); //Padding + } + + RawData = Reader.ReadBytes(RawDataSize); + + Reader.BaseStream.Seek(RecvListPos, SeekOrigin.Begin); + + for (int Index = 0; Index < RecvListCount; Index++) + { + RecvListBuff.Add(new IpcRecvListBuffDesc(Reader)); + } + } + + public byte[] GetBytes(long CmdPtr) + { + //todo + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + int Word0; + int Word1; + + Word0 = (int)Type; + Word0 |= (PtrBuff.Count & 0xf) << 16; + Word0 |= (SendBuff.Count & 0xf) << 20; + Word0 |= (ReceiveBuff.Count & 0xf) << 24; + Word0 |= (ExchangeBuff.Count & 0xf) << 28; + + byte[] HandleData = new byte[0]; + + if (HandleDesc != null) + { + HandleData = HandleDesc.GetBytes(); + } + + int DataLength = RawData?.Length ?? 0; + + int Pad0 = (int)GetPadSize16(CmdPtr + 8 + HandleData.Length); + + //Apparently, padding after Raw Data is 16 bytes, however when there is + //padding before Raw Data too, we need to subtract the size of this padding. + //This is the weirdest padding I've seen so far... + int Pad1 = 0x10 - Pad0; + + DataLength = (DataLength + Pad0 + Pad1 + (IsDomain ? 0x10 : 0)) / 4; + + DataLength += ResponseObjIds.Count; + + Word1 = DataLength & 0x3ff; + + if (HandleDesc != null) + { + Word1 |= 1 << 31; + } + + Writer.Write(Word0); + Writer.Write(Word1); + Writer.Write(HandleData); + + MS.Seek(Pad0, SeekOrigin.Current); + + if (IsDomain) + { + Writer.Write(ResponseObjIds.Count); + Writer.Write(0); + Writer.Write(0L); + } + + if (RawData != null) + { + Writer.Write(RawData); + } + + foreach (int Id in ResponseObjIds) + { + Writer.Write(Id); + } + + Writer.Write(new byte[Pad1]); + + return MS.ToArray(); + } + } + + private long GetPadSize16(long Position) + { + if ((Position & 0xf) != 0) + { + return 0x10 - (Position & 0xf); + } + + return 0; + } + + public long GetSendBuffPtr() + { + if (SendBuff.Count > 0 && SendBuff[0].Position != 0) + { + return SendBuff[0].Position; + } + + if (PtrBuff.Count > 0 && PtrBuff[0].Position != 0) + { + return PtrBuff[0].Position; + } + + return -1; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Ipc/IpcMessageType.cs b/Ryujinx/OsHle/Ipc/IpcMessageType.cs new file mode 100644 index 000000000..b0e283de7 --- /dev/null +++ b/Ryujinx/OsHle/Ipc/IpcMessageType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.OsHle.Ipc +{ + enum IpcMessageType + { + Response = 0, + Unknown2 = 2, + Request = 4, + Control = 5 + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Ipc/IpcPtrBuffDesc.cs b/Ryujinx/OsHle/Ipc/IpcPtrBuffDesc.cs new file mode 100644 index 000000000..414d71f45 --- /dev/null +++ b/Ryujinx/OsHle/Ipc/IpcPtrBuffDesc.cs @@ -0,0 +1,26 @@ +using System.IO; + +namespace Ryujinx.OsHle.Ipc +{ + struct IpcPtrBuffDesc + { + public long Position { get; private set; } + public int Index { get; private set; } + public short Size { get; private set; } + + public IpcPtrBuffDesc(BinaryReader Reader) + { + long Word0 = Reader.ReadUInt32(); + long Word1 = Reader.ReadUInt32(); + + Position = Word1; + Position |= (Word0 << 20) & 0x0f00000000; + Position |= (Word0 << 30) & 0x7000000000; + + Index = ((int)Word0 >> 0) & 0x03f; + Index |= ((int)Word0 >> 3) & 0x1c0; + + Size = (short)(Word0 >> 16); + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Ipc/IpcRecvListBuffDesc.cs b/Ryujinx/OsHle/Ipc/IpcRecvListBuffDesc.cs new file mode 100644 index 000000000..8180b8dd3 --- /dev/null +++ b/Ryujinx/OsHle/Ipc/IpcRecvListBuffDesc.cs @@ -0,0 +1,19 @@ +using System.IO; + +namespace Ryujinx.OsHle.Ipc +{ + struct IpcRecvListBuffDesc + { + public long Position { get; private set; } + public short Size { get; private set; } + + public IpcRecvListBuffDesc(BinaryReader Reader) + { + long Value = Reader.ReadInt64(); + + Position = Value & 0xffffffffffff; + + Size = (short)(Value >> 48); + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/MemoryInfo.cs b/Ryujinx/OsHle/MemoryInfo.cs new file mode 100644 index 000000000..adf863b27 --- /dev/null +++ b/Ryujinx/OsHle/MemoryInfo.cs @@ -0,0 +1,28 @@ +using ChocolArm64.Memory; + +namespace Ryujinx.OsHle +{ + struct MemoryInfo + { + public long BaseAddress; + public long Size; + public int MemType; + public int MemAttr; + public int MemPerm; + public int IpcRefCount; + public int DeviceRefCount; + public int Padding; //SBZ + + public MemoryInfo(AMemoryMapInfo MapInfo) + { + BaseAddress = MapInfo.Position; + Size = MapInfo.Size; + MemType = MapInfo.Type; + MemAttr = 0; + MemPerm = (int)MapInfo.Perm; + IpcRefCount = 0; + DeviceRefCount = 0; + Padding = 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/MemoryType.cs b/Ryujinx/OsHle/MemoryType.cs new file mode 100644 index 000000000..b1ac330ae --- /dev/null +++ b/Ryujinx/OsHle/MemoryType.cs @@ -0,0 +1,25 @@ +namespace Ryujinx.OsHle +{ + enum MemoryType + { + Unmapped = 0, + Io = 1, + Normal = 2, + CodeStatic = 3, + CodeMutable = 4, + Heap = 5, + SharedMemory = 6, + ModCodeStatic = 8, + ModCodeMutable = 9, + IpcBuffer0 = 10, + MappedMemory = 11, + ThreadLocal = 12, + TransferMemoryIsolated = 13, + TransferMemory = 14, + ProcessMemory = 15, + Reserved = 16, + IpcBuffer1 = 17, + IpcBuffer3 = 18, + KernelStack = 19 + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Mutex.cs b/Ryujinx/OsHle/Mutex.cs new file mode 100644 index 000000000..99d12b28e --- /dev/null +++ b/Ryujinx/OsHle/Mutex.cs @@ -0,0 +1,89 @@ +using ChocolArm64; +using ChocolArm64.Memory; +using System.Threading; + +namespace Ryujinx.OsHle +{ + class Mutex + { + private const int MutexHasListenersMask = 0x40000000; + + private AMemory Memory; + + private long MutexAddress; + + private int CurrRequestingThreadHandle; + + private int HighestPriority; + + private ManualResetEvent ThreadEvent; + + private object EnterWaitLock; + + public Mutex(AMemory Memory, long MutexAddress) + { + this.Memory = Memory; + this.MutexAddress = MutexAddress; + + ThreadEvent = new ManualResetEvent(false); + + EnterWaitLock = new object(); + } + + public void WaitForLock(AThread RequestingThread, int RequestingThreadHandle) + { + lock (EnterWaitLock) + { + int CurrentThreadHandle = Memory.ReadInt32(MutexAddress) & ~MutexHasListenersMask; + + if (CurrentThreadHandle == RequestingThreadHandle || + CurrentThreadHandle == 0) + { + return; + } + + if (CurrRequestingThreadHandle == 0 || RequestingThread.Priority < HighestPriority) + { + CurrRequestingThreadHandle = RequestingThreadHandle; + + HighestPriority = RequestingThread.Priority; + } + } + + ThreadEvent.Reset(); + ThreadEvent.WaitOne(); + } + + public void GiveUpLock(int ThreadHandle) + { + lock (EnterWaitLock) + { + int CurrentThread = Memory.ReadInt32(MutexAddress) & ~MutexHasListenersMask; + + if (CurrentThread == ThreadHandle) + { + Unlock(); + } + } + } + + public void Unlock() + { + lock (EnterWaitLock) + { + if (CurrRequestingThreadHandle != 0) + { + Memory.WriteInt32(MutexAddress, CurrRequestingThreadHandle); + } + else + { + Memory.WriteInt32(MutexAddress, 0); + } + + CurrRequestingThreadHandle = 0; + + ThreadEvent.Set(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AccIManagerForApplication.cs b/Ryujinx/OsHle/Objects/AccIManagerForApplication.cs new file mode 100644 index 000000000..8e2a002ba --- /dev/null +++ b/Ryujinx/OsHle/Objects/AccIManagerForApplication.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.OsHle.Objects +{ + class AccIManagerForApplication + { + public static long CheckAvailability(ServiceCtx Context) + { + return 0; + } + + public static long GetAccountId(ServiceCtx Context) + { + Context.ResponseData.Write(0xcafeL); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AccIProfile.cs b/Ryujinx/OsHle/Objects/AccIProfile.cs new file mode 100644 index 000000000..2dbe189d7 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AccIProfile.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.OsHle.Objects +{ + class AccIProfile + { + public static long GetBase(ServiceCtx Context) + { + Context.ResponseData.Write(0L); + Context.ResponseData.Write(0L); + Context.ResponseData.Write(0L); + Context.ResponseData.Write(0L); + Context.ResponseData.Write(0L); + Context.ResponseData.Write(0L); + Context.ResponseData.Write(0L); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIApplicationFunctions.cs b/Ryujinx/OsHle/Objects/AmIApplicationFunctions.cs new file mode 100644 index 000000000..e174f9433 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIApplicationFunctions.cs @@ -0,0 +1,56 @@ +using System.IO; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Objects +{ + class AmIApplicationFunctions + { + private const uint LaunchParamsMagic = 0xc79497ca; + + public static long PopLaunchParameter(ServiceCtx Context) + { + //Only the first 0x18 bytes of the Data seems to be actually used. + MakeObject(Context, new AmIStorage(MakeLaunchParams())); + + return 0; + } + + public static long EnsureSaveData(ServiceCtx Context) + { + long UIdLow = Context.RequestData.ReadInt64(); + long UIdHigh = Context.RequestData.ReadInt64(); + + Context.ResponseData.Write(0L); + + return 0; + } + + public static long GetDesiredLanguage(ServiceCtx Context) + { + //This is an enumerator where each number is a differnet language. + //0 is Japanese and 1 is English, need to figure out the other codes. + Context.ResponseData.Write(1L); + + return 0; + } + + private static byte[] MakeLaunchParams() + { + //Size needs to be at least 0x88 bytes otherwise application errors. + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + MS.SetLength(0x88); + + Writer.Write(LaunchParamsMagic); + Writer.Write(1); //IsAccountSelected? Only lower 8 bits actually used. + Writer.Write(1L); //User Id Low (note: User Id needs to be != 0) + Writer.Write(0L); //User Id High + + return MS.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIApplicationProxy.cs b/Ryujinx/OsHle/Objects/AmIApplicationProxy.cs new file mode 100644 index 000000000..2b0bc346d --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIApplicationProxy.cs @@ -0,0 +1,63 @@ +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Objects +{ + class AmIApplicationProxy + { + public static long GetCommonStateGetter(ServiceCtx Context) + { + MakeObject(Context, new AmICommonStateGetter()); + + return 0; + } + + public static long GetSelfController(ServiceCtx Context) + { + MakeObject(Context, new AmISelfController()); + + return 0; + } + + public static long GetWindowController(ServiceCtx Context) + { + MakeObject(Context, new AmIWindowController()); + + return 0; + } + + public static long GetAudioController(ServiceCtx Context) + { + MakeObject(Context, new AmIAudioController()); + + return 0; + } + + public static long GetDisplayController(ServiceCtx Context) + { + MakeObject(Context, new AmIDisplayController()); + + return 0; + } + + public static long GetLibraryAppletCreator(ServiceCtx Context) + { + MakeObject(Context, new AmILibraryAppletCreator()); + + return 0; + } + + public static long GetApplicationFunctions(ServiceCtx Context) + { + MakeObject(Context, new AmIApplicationFunctions()); + + return 0; + } + + public static long GetDebugFunctions(ServiceCtx Context) + { + MakeObject(Context, new AmIDebugFunctions()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIAudioController.cs b/Ryujinx/OsHle/Objects/AmIAudioController.cs new file mode 100644 index 000000000..59d94bda4 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIAudioController.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.OsHle.Objects +{ + class AmIAudioController + { + + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmICommonStateGetter.cs b/Ryujinx/OsHle/Objects/AmICommonStateGetter.cs new file mode 100644 index 000000000..32f065c81 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmICommonStateGetter.cs @@ -0,0 +1,55 @@ +namespace Ryujinx.OsHle.Objects +{ + class AmICommonStateGetter + { + private enum FocusState + { + InFocus = 1, + OutOfFocus = 2 + } + + private enum OperationMode + { + Handheld = 0, + Docked = 1 + } + + public static long GetEventHandle(ServiceCtx Context) + { + Context.ResponseData.Write(0L); + + return 0; + } + + public static long ReceiveMessage(ServiceCtx Context) + { + //Program expects 0xF at 0x17ae70 on puyo sdk, + //otherwise runs on a infinite loop until it reads said value. + //What it means is still unknown. + Context.ResponseData.Write(0xfL); + + return 0; //0x680; + } + + public static long GetOperationMode(ServiceCtx Context) + { + Context.ResponseData.Write((byte)OperationMode.Handheld); + + return 0; + } + + public static long GetPerformanceMode(ServiceCtx Context) + { + Context.ResponseData.Write((byte)0); + + return 0; + } + + public static long GetCurrentFocusState(ServiceCtx Context) + { + Context.ResponseData.Write((byte)FocusState.InFocus); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIDebugFunctions.cs b/Ryujinx/OsHle/Objects/AmIDebugFunctions.cs new file mode 100644 index 000000000..9794c43f4 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIDebugFunctions.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.OsHle.Objects +{ + class AmIDebugFunctions + { + + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIDisplayController.cs b/Ryujinx/OsHle/Objects/AmIDisplayController.cs new file mode 100644 index 000000000..be36ca0ca --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIDisplayController.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.OsHle.Objects +{ + class AmIDisplayController + { + + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmILibraryAppletCreator.cs b/Ryujinx/OsHle/Objects/AmILibraryAppletCreator.cs new file mode 100644 index 000000000..585df9e99 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmILibraryAppletCreator.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.OsHle.Objects +{ + class AmILibraryAppletCreator + { + + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIParentalControlService.cs b/Ryujinx/OsHle/Objects/AmIParentalControlService.cs new file mode 100644 index 000000000..12c3c2a96 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIParentalControlService.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.OsHle.Objects +{ + class AmIParentalControlService + { + + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmISelfController.cs b/Ryujinx/OsHle/Objects/AmISelfController.cs new file mode 100644 index 000000000..33cde0958 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmISelfController.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.OsHle.Objects +{ + class AmISelfController + { + public static long SetOperationModeChangedNotification(ServiceCtx Context) + { + bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; + + return 0; + } + + public static long SetPerformanceModeChangedNotification(ServiceCtx Context) + { + bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; + + return 0; + } + + public static long SetFocusHandlingMode(ServiceCtx Context) + { + bool Flag1 = Context.RequestData.ReadByte() != 0 ? true : false; + bool Flag2 = Context.RequestData.ReadByte() != 0 ? true : false; + bool Flag3 = Context.RequestData.ReadByte() != 0 ? true : false; + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIStorage.cs b/Ryujinx/OsHle/Objects/AmIStorage.cs new file mode 100644 index 000000000..7e608ac8c --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIStorage.cs @@ -0,0 +1,23 @@ +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Objects +{ + class AmIStorage + { + public byte[] Data { get; private set; } + + public AmIStorage(byte[] Data) + { + this.Data = Data; + } + + public static long Open(ServiceCtx Context) + { + AmIStorage Storage = Context.GetObject(); + + MakeObject(Context, new AmIStorageAccessor(Storage)); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIStorageAccessor.cs b/Ryujinx/OsHle/Objects/AmIStorageAccessor.cs new file mode 100644 index 000000000..62007ef81 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIStorageAccessor.cs @@ -0,0 +1,56 @@ +using ChocolArm64.Memory; +using System; + +namespace Ryujinx.OsHle.Objects +{ + class AmIStorageAccessor + { + public AmIStorage Storage { get; private set; } + + public AmIStorageAccessor(AmIStorage Storage) + { + this.Storage = Storage; + } + + public static long GetSize(ServiceCtx Context) + { + AmIStorageAccessor Accessor = Context.GetObject(); + + Context.ResponseData.Write((long)Accessor.Storage.Data.Length); + + return 0; + } + + public static long Read(ServiceCtx Context) + { + AmIStorageAccessor Accessor = Context.GetObject(); + + AmIStorage Storage = Accessor.Storage; + + long ReadPosition = Context.RequestData.ReadInt64(); + + if (Context.Request.RecvListBuff.Count > 0) + { + long Position = Context.Request.RecvListBuff[0].Position; + short Size = Context.Request.RecvListBuff[0].Size; + + byte[] Data; + + if (Storage.Data.Length > Size) + { + Data = new byte[Size]; + + Buffer.BlockCopy(Storage.Data, 0, Data, 0, Size); + } + else + { + Data = Storage.Data; + } + + AMemoryHelper.WriteBytes(Context.Memory, Position, Data); + } + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIWindowController.cs b/Ryujinx/OsHle/Objects/AmIWindowController.cs new file mode 100644 index 000000000..ea967ae84 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIWindowController.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.OsHle.Objects +{ + class AmIWindowController + { + public static long GetAppletResourceUserId(ServiceCtx Context) + { + Context.ResponseData.Write(0L); + + return 0; + } + + public static long AcquireForegroundRights(ServiceCtx Context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/ApmISession.cs b/Ryujinx/OsHle/Objects/ApmISession.cs new file mode 100644 index 000000000..2b2c03515 --- /dev/null +++ b/Ryujinx/OsHle/Objects/ApmISession.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.OsHle.Objects +{ + class ApmISession + { + public static long SetPerformanceConfiguration(ServiceCtx Context) + { + int PerfMode = Context.RequestData.ReadInt32(); + int PerfConfig = Context.RequestData.ReadInt32(); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AudIAudioRenderer.cs b/Ryujinx/OsHle/Objects/AudIAudioRenderer.cs new file mode 100644 index 000000000..b451d07ba --- /dev/null +++ b/Ryujinx/OsHle/Objects/AudIAudioRenderer.cs @@ -0,0 +1,36 @@ +namespace Ryujinx.OsHle.Objects +{ + class AudIAudioRenderer + { + public static long RequestUpdateAudioRenderer(ServiceCtx Context) + { + long Position = Context.Request.ReceiveBuff[0].Position; + + //0x40 bytes header + Context.Memory.WriteInt32(Position + 0x4, 0xb0); //Behavior Out State Size? (note: this is the last section) + Context.Memory.WriteInt32(Position + 0x8, 0x18e0); //Memory Pool Out State Size? + Context.Memory.WriteInt32(Position + 0xc, 0x600); //Voice Out State Size? + Context.Memory.WriteInt32(Position + 0x14, 0xe0); //Effect Out State Size? + Context.Memory.WriteInt32(Position + 0x1c, 0x20); //Sink Out State Size? + Context.Memory.WriteInt32(Position + 0x20, 0x10); //Performance Out State Size? + Context.Memory.WriteInt32(Position + 0x3c, 0x20e0); //Total Size (including 0x40 bytes header) + + for (int Offset = 0x40; Offset < 0x40 + 0x18e0; Offset += 0x10) + { + Context.Memory.WriteInt32(Position + Offset, 5); + } + + return 0; + } + + public static long StartAudioRenderer(ServiceCtx Context) + { + return 0; + } + + public static long QuerySystemEvent(ServiceCtx Context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/FriendIFriendService.cs b/Ryujinx/OsHle/Objects/FriendIFriendService.cs new file mode 100644 index 000000000..9a39380a5 --- /dev/null +++ b/Ryujinx/OsHle/Objects/FriendIFriendService.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.OsHle.Objects +{ + class FriendIFriendService + { + + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/FspSrvIFile.cs b/Ryujinx/OsHle/Objects/FspSrvIFile.cs new file mode 100644 index 000000000..4b9f8c37b --- /dev/null +++ b/Ryujinx/OsHle/Objects/FspSrvIFile.cs @@ -0,0 +1,72 @@ +using ChocolArm64.Memory; +using System; + +using System.IO; + +namespace Ryujinx.OsHle.Objects +{ + class FspSrvIFile : IDisposable + { + public Stream BaseStream { get; private set; } + + public FspSrvIFile(Stream BaseStream) + { + this.BaseStream = BaseStream; + } + + public static long Read(ServiceCtx Context) + { + FspSrvIFile File = Context.GetObject(); + + long Position = Context.Request.ReceiveBuff[0].Position; + + long Zero = Context.RequestData.ReadInt64(); + long Offset = Context.RequestData.ReadInt64(); + long Size = Context.RequestData.ReadInt64(); + + byte[] Data = new byte[Size]; + + int ReadSize = File.BaseStream.Read(Data, 0, (int)Size); + + AMemoryHelper.WriteBytes(Context.Memory, Position, Data); + + //TODO: Use ReadSize, we need to return the size that was REALLY read from the file. + //This is a workaround because we are doing something wrong and the game expects to read + //data from a file that doesn't yet exists -- and breaks if it can't read anything. + Context.ResponseData.Write((long)Size); + + return 0; + } + + public static long Write(ServiceCtx Context) + { + FspSrvIFile File = Context.GetObject(); + + long Position = Context.Request.SendBuff[0].Position; + + long Zero = Context.RequestData.ReadInt64(); + long Offset = Context.RequestData.ReadInt64(); + long Size = Context.RequestData.ReadInt64(); + + byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, Position, (int)Size); + + File.BaseStream.Seek(Offset, SeekOrigin.Begin); + File.BaseStream.Write(Data, 0, (int)Size); + + return 0; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && BaseStream != null) + { + BaseStream.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/FspSrvIFileSystem.cs b/Ryujinx/OsHle/Objects/FspSrvIFileSystem.cs new file mode 100644 index 000000000..fed1c55ac --- /dev/null +++ b/Ryujinx/OsHle/Objects/FspSrvIFileSystem.cs @@ -0,0 +1,70 @@ +using ChocolArm64.Memory; +using System.IO; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Objects +{ + class FspSrvIFileSystem + { + public string FilePath { get; private set; } + + public FspSrvIFileSystem(string Path) + { + this.FilePath = Path; + } + + public static long GetEntryType(ServiceCtx Context) + { + FspSrvIFileSystem FileSystem = Context.GetObject(); + + long Position = Context.Request.PtrBuff[0].Position; + + string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position); + + string FileName = Context.Ns.VFs.GetFullPath(FileSystem.FilePath, Name); + + if (FileName == null) + { + //TODO: Correct error code. + return -1; + } + + bool IsFile = File.Exists(FileName); + + Context.ResponseData.Write(IsFile ? 1 : 0); + + return 0; + } + + public static long OpenFile(ServiceCtx Context) + { + FspSrvIFileSystem FileSystem = Context.GetObject(); + + long Position = Context.Request.PtrBuff[0].Position; + + int FilterFlags = Context.RequestData.ReadInt32(); + + string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position); + + string FileName = Context.Ns.VFs.GetFullPath(FileSystem.FilePath, Name); + + if (FileName == null) + { + //TODO: Correct error code. + return -1; + } + + FileStream Stream = new FileStream(FileName, FileMode.OpenOrCreate); + + MakeObject(Context, new FspSrvIFile(Stream)); + + return 0; + } + + public static long Commit(ServiceCtx Context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/FspSrvIStorage.cs b/Ryujinx/OsHle/Objects/FspSrvIStorage.cs new file mode 100644 index 000000000..76ad8917a --- /dev/null +++ b/Ryujinx/OsHle/Objects/FspSrvIStorage.cs @@ -0,0 +1,44 @@ +using ChocolArm64.Memory; +using Ryujinx.OsHle.Ipc; +using System.IO; + +namespace Ryujinx.OsHle.Objects +{ + class FspSrvIStorage + { + public Stream BaseStream { get; private set; } + + public FspSrvIStorage(Stream BaseStream) + { + this.BaseStream = BaseStream; + } + + public static long Read(ServiceCtx Context) + { + FspSrvIStorage Storage = Context.GetObject(); + + long Offset = Context.RequestData.ReadInt64(); + long Size = Context.RequestData.ReadInt64(); + + if (Context.Request.ReceiveBuff.Count > 0) + { + IpcBuffDesc BuffDesc = Context.Request.ReceiveBuff[0]; + + //Use smaller length to avoid overflows. + if (Size > BuffDesc.Size) + { + Size = BuffDesc.Size; + } + + byte[] Data = new byte[Size]; + + Storage.BaseStream.Seek(Offset, SeekOrigin.Begin); + Storage.BaseStream.Read(Data, 0, Data.Length); + + AMemoryHelper.WriteBytes(Context.Memory, BuffDesc.Position, Data); + } + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/HidIAppletResource.cs b/Ryujinx/OsHle/Objects/HidIAppletResource.cs new file mode 100644 index 000000000..73b948df7 --- /dev/null +++ b/Ryujinx/OsHle/Objects/HidIAppletResource.cs @@ -0,0 +1,22 @@ +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; + +namespace Ryujinx.OsHle.Objects +{ + class HidIAppletResource + { + public HSharedMem Handle; + + public HidIAppletResource(HSharedMem Handle) + { + this.Handle = Handle; + } + + public static long GetSharedMemoryHandle(ServiceCtx Context) + { + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Context.Ns.Os.HidHandle); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/ObjHelper.cs b/Ryujinx/OsHle/Objects/ObjHelper.cs new file mode 100644 index 000000000..e37f5320e --- /dev/null +++ b/Ryujinx/OsHle/Objects/ObjHelper.cs @@ -0,0 +1,24 @@ +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; + +namespace Ryujinx.OsHle.Objects +{ + static class ObjHelper + { + public static void MakeObject(ServiceCtx Context, object Obj) + { + if (Context.Session is HDomain Dom) + { + Context.Response.ResponseObjIds.Add(Dom.GenertateObjectId(Obj)); + } + else + { + HSessionObj HndData = new HSessionObj(Context.Session, Obj); + + int VHandle = Context.Ns.Os.Handles.GenerateId(HndData); + + Context.Response.HandleDesc = IpcHandleDesc.MakeMove(VHandle); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/Parcel.cs b/Ryujinx/OsHle/Objects/Parcel.cs new file mode 100644 index 000000000..0d322bab8 --- /dev/null +++ b/Ryujinx/OsHle/Objects/Parcel.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; + +namespace Ryujinx.OsHle.Objects.Android +{ + static class Parcel + { + public static byte[] GetParcelData(byte[] Parcel) + { + if (Parcel == null) + { + throw new ArgumentNullException(nameof(Parcel)); + } + + using (MemoryStream MS = new MemoryStream(Parcel)) + { + BinaryReader Reader = new BinaryReader(MS); + + int DataSize = Reader.ReadInt32(); + int DataOffset = Reader.ReadInt32(); + int ObjsSize = Reader.ReadInt32(); + int ObjsOffset = Reader.ReadInt32(); + + MS.Seek(DataOffset - 0x10, SeekOrigin.Current); + + return Reader.ReadBytes(DataSize); + } + } + + public static byte[] MakeParcel(byte[] Data, byte[] Objs) + { + if (Data == null) + { + throw new ArgumentNullException(nameof(Data)); + } + + if (Objs == null) + { + throw new ArgumentNullException(nameof(Objs)); + } + + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + Writer.Write(Data.Length); + Writer.Write(0x10); + Writer.Write(Objs.Length); + Writer.Write(Data.Length + 0x10); + + Writer.Write(Data); + Writer.Write(Objs); + + return MS.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/TimeISteadyClock.cs b/Ryujinx/OsHle/Objects/TimeISteadyClock.cs new file mode 100644 index 000000000..ead8c41a1 --- /dev/null +++ b/Ryujinx/OsHle/Objects/TimeISteadyClock.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.OsHle.Objects +{ + class TimeISteadyClock + { + + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/TimeISystemClock.cs b/Ryujinx/OsHle/Objects/TimeISystemClock.cs new file mode 100644 index 000000000..d9a3a073e --- /dev/null +++ b/Ryujinx/OsHle/Objects/TimeISystemClock.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.OsHle.Objects +{ + class TimeISystemClock + { + private static DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public static long GetCurrentTime(ServiceCtx Context) + { + Context.ResponseData.Write((long)(DateTime.Now - Epoch).TotalSeconds); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/TimeITimeZoneService.cs b/Ryujinx/OsHle/Objects/TimeITimeZoneService.cs new file mode 100644 index 000000000..af5490a61 --- /dev/null +++ b/Ryujinx/OsHle/Objects/TimeITimeZoneService.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.OsHle.Objects +{ + class TimeITimeZoneService + { + + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/ViIApplicationDisplayService.cs b/Ryujinx/OsHle/Objects/ViIApplicationDisplayService.cs new file mode 100644 index 000000000..81616ae11 --- /dev/null +++ b/Ryujinx/OsHle/Objects/ViIApplicationDisplayService.cs @@ -0,0 +1,148 @@ +using ChocolArm64.Memory; +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; +using System.IO; + +using static Ryujinx.OsHle.Objects.Android.Parcel; +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Objects +{ + class ViIApplicationDisplayService + { + public static long GetRelayService(ServiceCtx Context) + { + MakeObject(Context, new ViIHOSBinderDriver()); + + return 0; + } + + public static long GetSystemDisplayService(ServiceCtx Context) + { + MakeObject(Context, new ViISystemDisplayService()); + + return 0; + } + + public static long GetManagerDisplayService(ServiceCtx Context) + { + MakeObject(Context, new ViIManagerDisplayService()); + + return 0; + } + + public static long OpenDisplay(ServiceCtx Context) + { + string Name = GetDisplayName(Context); + + long DisplayId = Context.Ns.Os.Displays.GenerateId(new Display(Name)); + + Context.ResponseData.Write(DisplayId); + + return 0; + } + + public static long OpenLayer(ServiceCtx Context) + { + long LayerId = Context.RequestData.ReadInt64(); + long UserId = Context.RequestData.ReadInt64(); + + long ParcelPtr = Context.Request.ReceiveBuff[0].Position; + + byte[] Parcel = MakeIGraphicsBufferProducer(ParcelPtr); + + AMemoryHelper.WriteBytes(Context.Memory, ParcelPtr, Parcel); + + Context.ResponseData.Write((long)Parcel.Length); + + return 0; + } + + public static long CreateStrayLayer(ServiceCtx Context) + { + long LayerFlags = Context.RequestData.ReadInt64(); + long DisplayId = Context.RequestData.ReadInt64(); + + long ParcelPtr = Context.Request.ReceiveBuff[0].Position; + + Display Disp = Context.Ns.Os.Displays.GetData((int)DisplayId); + + byte[] Parcel = MakeIGraphicsBufferProducer(ParcelPtr); + + AMemoryHelper.WriteBytes(Context.Memory, ParcelPtr, Parcel); + + Context.ResponseData.Write(0L); + Context.ResponseData.Write((long)Parcel.Length); + + return 0; + } + + public static long SetLayerScalingMode(ServiceCtx Context) + { + int ScalingMode = Context.RequestData.ReadInt32(); + long Unknown = Context.RequestData.ReadInt64(); + + return 0; + } + + public static long GetDisplayVSyncEvent(ServiceCtx Context) + { + string Name = GetDisplayName(Context); + + int Handle = Context.Ns.Os.Handles.GenerateId(new HEvent()); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + return 0; + } + + private static byte[] MakeIGraphicsBufferProducer(long BasePtr) + { + long Id = 0x20; + long CookiePtr = 0L; + + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + //flat_binder_object (size is 0x28) + Writer.Write(2); //Type (BINDER_TYPE_WEAK_BINDER) + Writer.Write(0); //Flags + Writer.Write((int)(Id >> 0)); + Writer.Write((int)(Id >> 32)); + Writer.Write((int)(CookiePtr >> 0)); + Writer.Write((int)(CookiePtr >> 32)); + Writer.Write((byte)'d'); + Writer.Write((byte)'i'); + Writer.Write((byte)'s'); + Writer.Write((byte)'p'); + Writer.Write((byte)'d'); + Writer.Write((byte)'r'); + Writer.Write((byte)'v'); + Writer.Write((byte)'\0'); + Writer.Write(0L); //Pad + + return MakeParcel(MS.ToArray(), new byte[] { 0, 0, 0, 0 }); + } + } + + private static string GetDisplayName(ServiceCtx Context) + { + string Name = string.Empty; + + for (int Index = 0; Index < 8 && + Context.RequestData.BaseStream.Position < + Context.RequestData.BaseStream.Length; Index++) + { + byte Chr = Context.RequestData.ReadByte(); + + if (Chr >= 0x20 && Chr < 0x7f) + { + Name += (char)Chr; + } + } + + return Name; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/ViIHOSBinderDriver.cs b/Ryujinx/OsHle/Objects/ViIHOSBinderDriver.cs new file mode 100644 index 000000000..72a472ae5 --- /dev/null +++ b/Ryujinx/OsHle/Objects/ViIHOSBinderDriver.cs @@ -0,0 +1,211 @@ +using ChocolArm64.Memory; +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; +using Ryujinx.OsHle.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +using static Ryujinx.OsHle.Objects.Android.Parcel; + +namespace Ryujinx.OsHle.Objects +{ + class ViIHOSBinderDriver + { + private delegate long ServiceProcessRequest(ServiceCtx Context, byte[] ParcelData); + + private static Dictionary<(string, int), ServiceProcessRequest> InterfaceMthd = + new Dictionary<(string, int), ServiceProcessRequest>() + { + { ("android.gui.IGraphicBufferProducer", 0x1), GraphicBufferProducerRequestBuffer }, + { ("android.gui.IGraphicBufferProducer", 0x3), GraphicBufferProducerDequeueBuffer }, + { ("android.gui.IGraphicBufferProducer", 0x7), GraphicBufferProducerQueueBuffer }, + //{ ("android.gui.IGraphicBufferProducer", 0x8), GraphicBufferProducerCancelBuffer }, + { ("android.gui.IGraphicBufferProducer", 0x9), GraphicBufferProducerQuery }, + { ("android.gui.IGraphicBufferProducer", 0xa), GraphicBufferProducerConnect }, + { ("android.gui.IGraphicBufferProducer", 0xe), GraphicBufferPreallocateBuffer }, + }; + + private class BufferObj + { + + } + + public IdPoolWithObj BufferSlots { get; private set; } + + public byte[] Gbfr; + + public ViIHOSBinderDriver() + { + BufferSlots = new IdPoolWithObj(); + } + + public static long TransactParcel(ServiceCtx Context) + { + int Id = Context.RequestData.ReadInt32(); + int Code = Context.RequestData.ReadInt32(); + + long DataPos = Context.Request.SendBuff[0].Position; + long DataSize = Context.Request.SendBuff[0].Size; + + byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, DataPos, (int)DataSize); + + Data = GetParcelData(Data); + + using (MemoryStream MS = new MemoryStream(Data)) + { + BinaryReader Reader = new BinaryReader(MS); + + MS.Seek(4, SeekOrigin.Current); + + int StrSize = Reader.ReadInt32(); + + string InterfaceName = Encoding.Unicode.GetString(Data, 8, StrSize * 2); + + if (InterfaceMthd.TryGetValue((InterfaceName, Code), out ServiceProcessRequest ProcReq)) + { + return ProcReq(Context, Data); + } + else + { + throw new NotImplementedException($"{InterfaceName} {Code}"); + } + } + } + + private static long GraphicBufferProducerRequestBuffer(ServiceCtx Context, byte[] ParcelData) + { + ViIHOSBinderDriver BinderDriver = Context.GetObject(); + + int GbfrSize = BinderDriver.Gbfr?.Length ?? 0; + + byte[] Data = new byte[GbfrSize + 4]; + + if (BinderDriver.Gbfr != null) + { + Buffer.BlockCopy(BinderDriver.Gbfr, 0, Data, 0, GbfrSize); + } + + return MakeReplyParcel(Context, Data); + } + + private static long GraphicBufferProducerDequeueBuffer(ServiceCtx Context, byte[] ParcelData) + { + ViIHOSBinderDriver BinderDriver = Context.GetObject(); + + //Note: It seems that the maximum number of slots is 64, because if we return + //a Slot number > 63, it seems to cause a buffer overrun and it reads garbage. + //Note 2: The size of each object associated with the slot is 0x30. + int Slot = BinderDriver.BufferSlots.GenerateId(new BufferObj()); + + return MakeReplyParcel(Context, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + } + + private static long GraphicBufferProducerQueueBuffer(ServiceCtx Context, byte[] ParcelData) + { + return MakeReplyParcel(Context, 1280, 720, 0, 0, 0); + } + + private static long GraphicBufferProducerCancelBuffer(ServiceCtx Context, byte[] ParcelData) + { + ViIHOSBinderDriver BinderDriver = Context.GetObject(); + + using (MemoryStream MS = new MemoryStream(ParcelData)) + { + BinaryReader Reader = new BinaryReader(MS); + + MS.Seek(0x50, SeekOrigin.Begin); + + int Slot = Reader.ReadInt32(); + + BinderDriver.BufferSlots.Delete(Slot); + + return MakeReplyParcel(Context, 0); + } + } + + private static long GraphicBufferProducerQuery(ServiceCtx Context, byte[] ParcelData) + { + return MakeReplyParcel(Context, 0, 0); + } + + private static long GraphicBufferProducerConnect(ServiceCtx Context, byte[] ParcelData) + { + return MakeReplyParcel(Context, 1280, 720, 0, 0, 0); + } + + private static long GraphicBufferPreallocateBuffer(ServiceCtx Context, byte[] ParcelData) + { + ViIHOSBinderDriver BinderDriver = Context.GetObject(); + + int GbfrSize = ParcelData.Length - 0x54; + + BinderDriver.Gbfr = new byte[GbfrSize]; + + Buffer.BlockCopy(ParcelData, 0x54, BinderDriver.Gbfr, 0, GbfrSize); + + using (MemoryStream MS = new MemoryStream(ParcelData)) + { + BinaryReader Reader = new BinaryReader(MS); + + MS.Seek(0xd4, SeekOrigin.Begin); + + int Handle = Reader.ReadInt32(); + + HNvMap NvMap = Context.Ns.Os.Handles.GetData(Handle); + + Context.Ns.Gpu.Renderer.FrameBufferPtr = + Context.Memory.Manager.GetPhys(NvMap.Address, AMemoryPerm.Read); + } + + return MakeReplyParcel(Context, 0); + } + + private static long MakeReplyParcel(ServiceCtx Context, params int[] Ints) + { + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + foreach (int Int in Ints) + { + Writer.Write(Int); + } + + return MakeReplyParcel(Context, MS.ToArray()); + } + } + + private static long MakeReplyParcel(ServiceCtx Context, byte[] Data) + { + long ReplyPos = Context.Request.ReceiveBuff[0].Position; + long ReplySize = Context.Request.ReceiveBuff[0].Position; + + byte[] Reply = MakeParcel(Data, new byte[0]); + + AMemoryHelper.WriteBytes(Context.Memory, ReplyPos, Reply); + + return 0; + } + + public static long AdjustRefcount(ServiceCtx Context) + { + int Id = Context.RequestData.ReadInt32(); + int AddVal = Context.RequestData.ReadInt32(); + int Type = Context.RequestData.ReadInt32(); + + return 0; + } + + public static long GetNativeHandle(ServiceCtx Context) + { + int Id = Context.RequestData.ReadInt32(); + uint Unk = Context.RequestData.ReadUInt32(); + + Context.Response.HandleDesc = IpcHandleDesc.MakeMove(0xbadcafe); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/ViIManagerDisplayService.cs b/Ryujinx/OsHle/Objects/ViIManagerDisplayService.cs new file mode 100644 index 000000000..0fdca3bac --- /dev/null +++ b/Ryujinx/OsHle/Objects/ViIManagerDisplayService.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.OsHle.Objects +{ + class ViIManagerDisplayService + { + public static long CreateManagedLayer(ServiceCtx Context) + { + Context.ResponseData.Write(0L); //LayerId + + return 0; + } + + public static long AddToLayerStack(ServiceCtx Context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/ViISystemDisplayService.cs b/Ryujinx/OsHle/Objects/ViISystemDisplayService.cs new file mode 100644 index 000000000..8d3a51f4e --- /dev/null +++ b/Ryujinx/OsHle/Objects/ViISystemDisplayService.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.OsHle.Objects +{ + class ViISystemDisplayService + { + public static long SetLayerZ(ServiceCtx Context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Process.cs b/Ryujinx/OsHle/Process.cs new file mode 100644 index 000000000..1cfae9293 --- /dev/null +++ b/Ryujinx/OsHle/Process.cs @@ -0,0 +1,179 @@ +using ChocolArm64; +using ChocolArm64.Memory; +using Ryujinx.Loaders; +using Ryujinx.Loaders.Executables; +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Svc; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Ryujinx.OsHle +{ + class Process + { + private const int MaxStackSize = 8 * 1024 * 1024; + + private const int TlsSize = 0x200; + private const int TotalTlsSlots = 32; + private const int TlsTotalSize = TotalTlsSlots * TlsSize; + private const long TlsPageAddr = (AMemoryMgr.AddrSize - TlsTotalSize) & ~AMemoryMgr.PageMask; + + private Switch Ns; + + public int ProcessId { get; private set; } + + public AMemory Memory { get; private set; } + + private SvcHandler SvcHandler; + + private AThread MainThread; + + private ConcurrentDictionary TlsSlots; + + private List Executables; + + private long ImageBase; + + public Process(Switch Ns, AMemoryAlloc Allocator, int ProcessId) + { + this.Ns = Ns; + this.ProcessId = ProcessId; + + Memory = new AMemory(Ns.Ram, Allocator); + SvcHandler = new SvcHandler(Ns, Memory); + TlsSlots = new ConcurrentDictionary(); + Executables = new List(); + + ImageBase = 0x8000000; + + Memory.Manager.MapPhys( + TlsPageAddr, + TlsTotalSize, + (int)MemoryType.ThreadLocal, + AMemoryPerm.RW); + } + + public void LoadProgram(IElf Program) + { + Executable Executable = new Executable(Program, Memory, ImageBase); + + Executables.Add(Executable); + + ImageBase = AMemoryHelper.PageRoundUp(Executable.ImageEnd); + } + + public void SetEmptyArgs() + { + ImageBase += AMemoryMgr.PageSize; + } + + public void InitializeHeap() + { + Memory.Manager.SetHeapAddr((ImageBase + 0x3fffffff) & ~0x3fffffff); + } + + public bool Run() + { + if (Executables.Count == 0) + { + return false; + } + + long StackBot = TlsPageAddr - MaxStackSize; + + Memory.Manager.MapPhys(StackBot, MaxStackSize, (int)MemoryType.Normal, AMemoryPerm.RW); + + int Handle = MakeThread(Executables[0].ImageBase, TlsPageAddr, 0, 48, 0); + + if (Handle == -1) + { + return false; + } + + MainThread = Ns.Os.Handles.GetData(Handle).Thread; + + MainThread.Execute(); + + return true; + } + + public void StopAllThreads() + { + if (MainThread != null) + { + while (MainThread.IsAlive) + { + MainThread.StopExecution(); + } + } + + foreach (AThread Thread in TlsSlots.Values) + { + while (Thread.IsAlive) + { + Thread.StopExecution(); + } + } + } + + public int MakeThread( + long EntryPoint, + long StackTop, + long ArgsPtr, + int Priority, + int ProcessorId) + { + AThread Thread = new AThread(Memory, EntryPoint, Priority); + + int TlsSlot = GetFreeTlsSlot(Thread); + + int Handle = Ns.Os.Handles.GenerateId(new HThread(Thread)); + + if (TlsSlot == -1 || Handle == -1) + { + return -1; + } + + Thread.Registers.SvcCall += SvcHandler.SvcCall; + Thread.Registers.ProcessId = ProcessId; + Thread.Registers.ThreadId = Ns.Os.IdGen.GenerateId(); + Thread.Registers.TlsAddr = TlsPageAddr + TlsSlot * TlsSize; + Thread.Registers.X0 = (ulong)ArgsPtr; + Thread.Registers.X1 = (ulong)Handle; + Thread.Registers.X31 = (ulong)StackTop; + + Thread.WorkFinished += ThreadFinished; + + return Handle; + } + + private int GetFreeTlsSlot(AThread Thread) + { + for (int Index = 1; Index < TotalTlsSlots; Index++) + { + if (TlsSlots.TryAdd(Index, Thread)) + { + return Index; + } + } + + return -1; + } + + private void ThreadFinished(object sender, EventArgs e) + { + if (sender is AThread Thread) + { + TlsSlots.TryRemove(GetTlsSlot(Thread.Registers.TlsAddr), out _); + + Ns.Os.IdGen.DeleteId(Thread.ThreadId); + } + } + + private int GetTlsSlot(long Position) + { + return (int)((Position - TlsPageAddr) / TlsSize); + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/ServiceCtx.cs b/Ryujinx/OsHle/ServiceCtx.cs new file mode 100644 index 000000000..88ddcdb77 --- /dev/null +++ b/Ryujinx/OsHle/ServiceCtx.cs @@ -0,0 +1,52 @@ +using ChocolArm64.Memory; +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; +using System.IO; + +namespace Ryujinx.OsHle +{ + class ServiceCtx + { + public Switch Ns { get; private set; } + public AMemory Memory { get; private set; } + public HSession Session { get; private set; } + public IpcMessage Request { get; private set; } + public IpcMessage Response { get; private set; } + public BinaryReader RequestData { get; private set; } + public BinaryWriter ResponseData { get; private set; } + + public ServiceCtx( + Switch Ns, + AMemory Memory, + HSession Session, + IpcMessage Request, + IpcMessage Response, + BinaryReader RequestData, + BinaryWriter ResponseData) + { + this.Ns = Ns; + this.Memory = Memory; + this.Session = Session; + this.Request = Request; + this.Response = Response; + this.RequestData = RequestData; + this.ResponseData = ResponseData; + } + + public T GetObject() + { + object Obj = null; + + if (Session is HSessionObj SessionObj) + { + Obj = SessionObj.Obj; + } + if (Session is HDomain Dom) + { + Obj = Dom.GetObject(Request.DomObjId); + } + + return Obj is T ? (T)Obj : default(T); + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceAcc.cs b/Ryujinx/OsHle/Services/ServiceAcc.cs new file mode 100644 index 000000000..14a3e83ea --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceAcc.cs @@ -0,0 +1,33 @@ +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long AccU0ListOpenUsers(ServiceCtx Context) + { + return 0; + } + + public static long AccU0GetProfile(ServiceCtx Context) + { + MakeObject(Context, new AccIProfile()); + + return 0; + } + + public static long AccU0InitializeApplicationInfo(ServiceCtx Context) + { + return 0; + } + + public static long AccU0GetBaasAccountManagerForApplication(ServiceCtx Context) + { + MakeObject(Context, new AccIManagerForApplication()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceApm.cs b/Ryujinx/OsHle/Services/ServiceApm.cs new file mode 100644 index 000000000..f0df462b7 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceApm.cs @@ -0,0 +1,16 @@ +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long ApmOpenSession(ServiceCtx Context) + { + MakeObject(Context, new ApmISession()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceAppletOE.cs b/Ryujinx/OsHle/Services/ServiceAppletOE.cs new file mode 100644 index 000000000..2f98a2014 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceAppletOE.cs @@ -0,0 +1,16 @@ +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long AppletOpenApplicationProxy(ServiceCtx Context) + { + MakeObject(Context, new AmIApplicationProxy()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceAud.cs b/Ryujinx/OsHle/Services/ServiceAud.cs new file mode 100644 index 000000000..96e7e5481 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceAud.cs @@ -0,0 +1,60 @@ +using ChocolArm64.Memory; +using Ryujinx.OsHle.Objects; +using System.Text; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long AudOutListAudioOuts(ServiceCtx Context) + { + long Position = Context.Request.ReceiveBuff[0].Position; + + AMemoryHelper.WriteBytes(Context.Memory, Position, Encoding.ASCII.GetBytes("iface")); + + Context.ResponseData.Write(1); + + return 0; + } + + public static long AudOutOpenAudioOut(ServiceCtx Context) + { + Context.ResponseData.Write(48000); + Context.ResponseData.Write(2); + Context.ResponseData.Write(2); + Context.ResponseData.Write(0); + + return 0; + } + + public static long AudRenOpenAudioRenderer(ServiceCtx Context) + { + MakeObject(Context, new AudIAudioRenderer()); + + return 0; + } + + public static long AudRenGetAudioRendererWorkBufferSize(ServiceCtx Context) + { + int SampleRate = Context.RequestData.ReadInt32(); + int Unknown4 = Context.RequestData.ReadInt32(); + int Unknown8 = Context.RequestData.ReadInt32(); + int UnknownC = Context.RequestData.ReadInt32(); + int Unknown10 = Context.RequestData.ReadInt32(); + int Unknown14 = Context.RequestData.ReadInt32(); + int Unknown18 = Context.RequestData.ReadInt32(); + int Unknown1c = Context.RequestData.ReadInt32(); + int Unknown20 = Context.RequestData.ReadInt32(); + int Unknown24 = Context.RequestData.ReadInt32(); + int Unknown28 = Context.RequestData.ReadInt32(); + int Unknown2c = Context.RequestData.ReadInt32(); + int Rev1Magic = Context.RequestData.ReadInt32(); + + Context.ResponseData.Write(0x400L); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceFriend.cs b/Ryujinx/OsHle/Services/ServiceFriend.cs new file mode 100644 index 000000000..980d42196 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceFriend.cs @@ -0,0 +1,16 @@ +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long FriendCreateFriendService(ServiceCtx Context) + { + MakeObject(Context, new FriendIFriendService()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceFspSrv.cs b/Ryujinx/OsHle/Services/ServiceFspSrv.cs new file mode 100644 index 000000000..e2ceb0aed --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceFspSrv.cs @@ -0,0 +1,42 @@ +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long FspSrvInitialize(ServiceCtx Context) + { + return 0; + } + + public static long FspSrvMountSaveData(ServiceCtx Context) + { + MakeObject(Context, new FspSrvIFileSystem(Context.Ns.VFs.GetGameSavesPath())); + + return 0; + } + + public static long FspSrvOpenDataStorageByCurrentProcess(ServiceCtx Context) + { + MakeObject(Context, new FspSrvIStorage(Context.Ns.VFs.RomFs)); + + return 0; + } + + public static long FspSrvOpenRomStorage(ServiceCtx Context) + { + MakeObject(Context, new FspSrvIStorage(Context.Ns.VFs.RomFs)); + + return 0; + } + + public static long FspSrvGetGlobalAccessLogMode(ServiceCtx Context) + { + Context.ResponseData.Write(0); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceHid.cs b/Ryujinx/OsHle/Services/ServiceHid.cs new file mode 100644 index 000000000..30093f49f --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceHid.cs @@ -0,0 +1,56 @@ +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long HidCreateAppletResource(ServiceCtx Context) + { + HSharedMem HidHndData = Context.Ns.Os.Handles.GetData(Context.Ns.Os.HidHandle); + + MakeObject(Context, new HidIAppletResource(HidHndData)); + + return 0; + } + + public static long HidActivateTouchScreen(ServiceCtx Context) + { + long Unknown = Context.RequestData.ReadInt64(); + + return 0; + } + + public static long HidSetSupportedNpadStyleSet(ServiceCtx Context) + { + long Unknown0 = Context.RequestData.ReadInt64(); + long Unknown8 = Context.RequestData.ReadInt64(); + + return 0; + } + + public static long HidSetSupportedNpadIdType(ServiceCtx Context) + { + long Unknown = Context.RequestData.ReadInt64(); + + return 0; + } + + public static long HidActivateNpad(ServiceCtx Context) + { + long Unknown = Context.RequestData.ReadInt64(); + + return 0; + } + + public static long HidSetNpadJoyHoldType(ServiceCtx Context) + { + long Unknown0 = Context.RequestData.ReadInt64(); + long Unknown8 = Context.RequestData.ReadInt64(); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceLm.cs b/Ryujinx/OsHle/Services/ServiceLm.cs new file mode 100644 index 000000000..dc6acad95 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceLm.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long LmInitialize(ServiceCtx Context) + { + Context.Session.Initialize(); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceNvDrv.cs b/Ryujinx/OsHle/Services/ServiceNvDrv.cs new file mode 100644 index 000000000..f7c0d3022 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceNvDrv.cs @@ -0,0 +1,601 @@ +using ChocolArm64.Memory; +using Ryujinx.Gpu; +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; +using Ryujinx.OsHle.Utilities; +using System; +using System.Collections.Generic; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + private delegate long ServiceProcessRequest(ServiceCtx Context); + + private static Dictionary<(string, int), ServiceProcessRequest> IoctlCmds = + new Dictionary<(string, int), ServiceProcessRequest>() + { + { ("/dev/nvhost-as-gpu", 0x4101), NvGpuAsIoctlBindChannel }, + { ("/dev/nvhost-as-gpu", 0x4102), NvGpuAsIoctlAllocSpace }, + { ("/dev/nvhost-as-gpu", 0x4106), NvGpuAsIoctlMapBufferEx }, + { ("/dev/nvhost-as-gpu", 0x4108), NvGpuAsIoctlGetVaRegions }, + { ("/dev/nvhost-as-gpu", 0x4109), NvGpuAsIoctlInitializeEx }, + { ("/dev/nvhost-ctrl", 0x001b), NvHostIoctlCtrlGetConfig }, + { ("/dev/nvhost-ctrl", 0x001d), NvHostIoctlCtrlEventWait }, + { ("/dev/nvhost-ctrl-gpu", 0x4701), NvGpuIoctlZcullGetCtxSize }, + { ("/dev/nvhost-ctrl-gpu", 0x4702), NvGpuIoctlZcullGetInfo }, + { ("/dev/nvhost-ctrl-gpu", 0x4705), NvGpuIoctlGetCharacteristics }, + { ("/dev/nvhost-ctrl-gpu", 0x4706), NvGpuIoctlGetTpcMasks }, + { ("/dev/nvhost-ctrl-gpu", 0x4714), NvGpuIoctlZbcGetActiveSlotMask }, + { ("/dev/nvhost-gpu", 0x4714), NvMapIoctlChannelSetUserData }, + { ("/dev/nvhost-gpu", 0x4801), NvMapIoctlChannelSetNvMap }, + { ("/dev/nvhost-gpu", 0x4808), NvMapIoctlChannelSubmitGpFifo }, + { ("/dev/nvhost-gpu", 0x4809), NvMapIoctlChannelAllocObjCtx }, + { ("/dev/nvhost-gpu", 0x480b), NvMapIoctlChannelZcullBind }, + { ("/dev/nvhost-gpu", 0x480c), NvMapIoctlChannelSetErrorNotifier }, + { ("/dev/nvhost-gpu", 0x480d), NvMapIoctlChannelSetPriority }, + { ("/dev/nvhost-gpu", 0x481a), NvMapIoctlChannelAllocGpFifoEx2 }, + { ("/dev/nvmap", 0x0101), NvMapIocCreate }, + { ("/dev/nvmap", 0x0103), NvMapIocFromId }, + { ("/dev/nvmap", 0x0104), NvMapIocAlloc }, + { ("/dev/nvmap", 0x0109), NvMapIocParam }, + { ("/dev/nvmap", 0x010e), NvMapIocGetId }, + }; + + public static long NvDrvOpen(ServiceCtx Context) + { + long NamePtr = Context.Request.SendBuff[0].Position; + + string Name = AMemoryHelper.ReadAsciiString(Context.Memory, NamePtr); + + int Fd = Context.Ns.Os.Fds.GenerateId(new FileDesc(Name)); + + Context.ResponseData.Write(Fd); + Context.ResponseData.Write(0); + + return 0; + } + + public static long NvDrvIoctl(ServiceCtx Context) + { + int Fd = Context.RequestData.ReadInt32(); + int Cmd = Context.RequestData.ReadInt32() & 0xffff; + + FileDesc FdData = Context.Ns.Os.Fds.GetData(Fd); + + long Position = Context.Request.PtrBuff[0].Position; + + Context.ResponseData.Write(0); + + if (IoctlCmds.TryGetValue((FdData.Name, Cmd), out ServiceProcessRequest ProcReq)) + { + return ProcReq(Context); + } + else + { + throw new NotImplementedException($"{FdData.Name} {Cmd:x4}"); + } + } + + public static long NvDrvClose(ServiceCtx Context) + { + int Fd = Context.RequestData.ReadInt32(); + + Context.Ns.Os.Fds.Delete(Fd); + + Context.ResponseData.Write(0); + + return 0; + } + + public static long NvDrvInitialize(ServiceCtx Context) + { + long TransferMemSize = Context.RequestData.ReadInt64(); + int TransferMemHandle = Context.Request.HandleDesc.ToCopy[0]; + + Context.ResponseData.Write(0); + + return 0; + } + + public static long NvDrvQueryEvent(ServiceCtx Context) + { + int Fd = Context.RequestData.ReadInt32(); + int EventId = Context.RequestData.ReadInt32(); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(0xcafe); + + Context.ResponseData.Write(0); + + return 0; + } + + private static long NvGpuAsIoctlBindChannel(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + int Fd = Context.Memory.ReadInt32(Position); + + return 0; + } + + private static long NvGpuAsIoctlAllocSpace(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + + int Pages = Reader.ReadInt32(); + int PageSize = Reader.ReadInt32(); + int Flags = Reader.ReadInt32(); + int Padding = Reader.ReadInt32(); + long Align = Reader.ReadInt64(); + + if ((Flags & 1) != 0) + { + Align = Context.Ns.Gpu.MemoryMgr.Reserve(Align, (long)Pages * PageSize, 1); + } + else + { + Align = Context.Ns.Gpu.MemoryMgr.Reserve((long)Pages * PageSize, Align); + } + + Context.Memory.WriteInt64(Position + 0x10, Align); + + return 0; + } + + private static long NvGpuAsIoctlMapBufferEx(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + + int Flags = Reader.ReadInt32(); + int Kind = Reader.ReadInt32(); + int Handle = Reader.ReadInt32(); + int PageSize = Reader.ReadInt32(); + long BuffAddr = Reader.ReadInt64(); + long MapSize = Reader.ReadInt64(); + long Offset = Reader.ReadInt64(); + + HNvMap NvMap = Context.Ns.Os.Handles.GetData(Handle); + + if (NvMap != null) + { + if ((Flags & 1) != 0) + { + Offset = Context.Ns.Gpu.MemoryMgr.Map(NvMap.Address, Offset, NvMap.Size); + } + else + { + Offset = Context.Ns.Gpu.MemoryMgr.Map(NvMap.Address, NvMap.Size); + } + } + + Context.Memory.WriteInt64(Position + 0x20, Offset); + + return 0; + } + + private static long NvGpuAsIoctlGetVaRegions(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + MemWriter Writer = new MemWriter(Context.Memory, Position); + + long Unused = Reader.ReadInt64(); + int BuffSize = Reader.ReadInt32(); + int Padding = Reader.ReadInt32(); + + BuffSize = 0x30; + + Writer.WriteInt64(Unused); + Writer.WriteInt32(BuffSize); + Writer.WriteInt32(Padding); + + Writer.WriteInt64(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt64(0); + + Writer.WriteInt64(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt64(0); + + return 0; + } + + private static long NvGpuAsIoctlInitializeEx(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + + int BigPageSize = Reader.ReadInt32(); + int AsFd = Reader.ReadInt32(); + int Flags = Reader.ReadInt32(); + int Reserved = Reader.ReadInt32(); + long Unknown10 = Reader.ReadInt64(); + long Unknown18 = Reader.ReadInt64(); + long Unknown20 = Reader.ReadInt64(); + + return 0; + } + + private static long NvHostIoctlCtrlGetConfig(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + MemWriter Writer = new MemWriter(Context.Memory, Position + 0x82); + + for (int Index = 0; Index < 0x101; Index++) + { + Writer.WriteByte(0); + } + + return 0; + } + + private static long NvHostIoctlCtrlEventWait(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + + int SyncPtId = Reader.ReadInt32(); + int Threshold = Reader.ReadInt32(); + int Timeout = Reader.ReadInt32(); + int Value = Reader.ReadInt32(); + + Context.Memory.WriteInt32(Position + 0xc, 0xcafe); + + return 0; + } + + private static long NvGpuIoctlZcullGetCtxSize(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + Context.Memory.WriteInt32(Position, 1); + + return 0; + } + + private static long NvGpuIoctlZcullGetInfo(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemWriter Writer = new MemWriter(Context.Memory, Position); + + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + + return 0; + } + + private static long NvGpuIoctlGetCharacteristics(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + MemWriter Writer = new MemWriter(Context.Memory, Position); + + //Note: We should just ignore the BuffAddr, because official code + //does __memcpy_device from Position + 0x10 to BuffAddr. + long BuffSize = Reader.ReadInt64(); + long BuffAddr = Reader.ReadInt64(); + + BuffSize = 0xa0; + + Writer.WriteInt64(BuffSize); + Writer.WriteInt64(BuffAddr); + Writer.WriteInt32(0x120); //NVGPU_GPU_ARCH_GM200 + Writer.WriteInt32(0xb); //NVGPU_GPU_IMPL_GM20B + Writer.WriteInt32(0xa1); + Writer.WriteInt32(1); + Writer.WriteInt64(0x40000); + Writer.WriteInt64(0); + Writer.WriteInt32(2); + Writer.WriteInt32(0x20); //NVGPU_GPU_BUS_TYPE_AXI + Writer.WriteInt32(0x20000); + Writer.WriteInt32(0x20000); + Writer.WriteInt32(0x1b); + Writer.WriteInt32(0x30000); + Writer.WriteInt32(1); + Writer.WriteInt32(0x503); + Writer.WriteInt32(0x503); + Writer.WriteInt32(0x80); + Writer.WriteInt32(0x28); + Writer.WriteInt32(0); + Writer.WriteInt64(0x55); + Writer.WriteInt32(0x902d); //FERMI_TWOD_A + Writer.WriteInt32(0xb197); //MAXWELL_B + Writer.WriteInt32(0xb1c0); //MAXWELL_COMPUTE_B + Writer.WriteInt32(0xb06f); //MAXWELL_CHANNEL_GPFIFO_A + Writer.WriteInt32(0xa140); //KEPLER_INLINE_TO_MEMORY_B + Writer.WriteInt32(0xb0b5); //MAXWELL_DMA_COPY_A + Writer.WriteInt32(1); + Writer.WriteInt32(0); + Writer.WriteInt32(2); + Writer.WriteInt32(1); + Writer.WriteInt32(0); + Writer.WriteInt32(1); + Writer.WriteInt32(0x21d70); + Writer.WriteInt32(0); + Writer.WriteByte((byte)'g'); + Writer.WriteByte((byte)'m'); + Writer.WriteByte((byte)'2'); + Writer.WriteByte((byte)'0'); + Writer.WriteByte((byte)'b'); + Writer.WriteByte((byte)'\0'); + Writer.WriteByte((byte)'\0'); + Writer.WriteByte((byte)'\0'); + Writer.WriteInt64(0); + + return 0; + } + + private static long NvGpuIoctlGetTpcMasks(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + + int MaskBuffSize = Reader.ReadInt32(); + int Reserved = Reader.ReadInt32(); + long MaskBuffAddr = Reader.ReadInt64(); + long Unknown = Reader.ReadInt64(); + + return 0; + } + + private static long NvGpuIoctlZbcGetActiveSlotMask(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + Context.Memory.WriteInt32(Position + 0, 7); + Context.Memory.WriteInt32(Position + 4, 1); + + return 0; + } + + private static long NvMapIoctlChannelSetUserData(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + return 0; + } + + private static long NvMapIoctlChannelSetNvMap(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + int Fd = Context.Memory.ReadInt32(Position); + + return 0; + } + + private static long NvMapIoctlChannelSubmitGpFifo(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + MemWriter Writer = new MemWriter(Context.Memory, Position + 0x10); + + long GpFifo = Reader.ReadInt64(); + int Count = Reader.ReadInt32(); + int Flags = Reader.ReadInt32(); + int FenceId = Reader.ReadInt32(); + int FenceVal = Reader.ReadInt32(); + + for (int Index = 0; Index < Count; Index++) + { + long GpFifoHdr = Reader.ReadInt64(); + + long GpuAddr = GpFifoHdr & 0xffffffffff; + + int Size = (int)(GpFifoHdr >> 40) & 0x7ffffc; + + long CpuAddr = Context.Ns.Gpu.MemoryMgr.GetCpuAddr(GpuAddr); + + if (CpuAddr != -1) + { + byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, CpuAddr, Size); + + NsGpuPBEntry[] PushBuffer = NsGpuPBEntry.DecodePushBuffer(Data); + + Context.Ns.Gpu.PGraph.ProcessPushBuffer(PushBuffer, Context.Memory); + } + } + + Writer.WriteInt32(0); + Writer.WriteInt32(0); + + return 0; + } + + private static long NvMapIoctlChannelAllocObjCtx(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + int ClassNum = Context.Memory.ReadInt32(Position + 0); + int Flags = Context.Memory.ReadInt32(Position + 4); + + Context.Memory.WriteInt32(Position + 8, 0); + + return 0; + } + + private static long NvMapIoctlChannelZcullBind(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + + long GpuVa = Reader.ReadInt64(); + int Mode = Reader.ReadInt32(); + int Padding = Reader.ReadInt32(); + + return 0; + } + + private static long NvMapIoctlChannelSetErrorNotifier(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + + long Offset = Reader.ReadInt64(); + long Size = Reader.ReadInt64(); + int Mem = Reader.ReadInt32(); + int Padding = Reader.ReadInt32(); + + return 0; + } + + private static long NvMapIoctlChannelSetPriority(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + int Priority = Context.Memory.ReadInt32(Position); + + return 0; + } + + private static long NvMapIoctlChannelAllocGpFifoEx2(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + MemWriter Writer = new MemWriter(Context.Memory, Position + 0xc); + + int Count = Reader.ReadInt32(); + int Flags = Reader.ReadInt32(); + int Unknown8 = Reader.ReadInt32(); + long Fence = Reader.ReadInt64(); + int Unknown14 = Reader.ReadInt32(); + int Unknown18 = Reader.ReadInt32(); + + Writer.WriteInt32(0); + Writer.WriteInt32(0); + + return 0; + } + + private static long NvMapIocCreate(ServiceCtx Context) + { + long Position = Context.Request.GetSendBuffPtr(); + + int Size = Context.Memory.ReadInt32(Position); + + int Id = Context.Ns.Os.NvMapIds.GenerateId(); + + int Handle = Context.Ns.Os.Handles.GenerateId(new HNvMap(Id, Size)); + + Context.Memory.WriteInt32(Position + 4, Handle); + + return 0; + } + + private static long NvMapIocFromId(ServiceCtx Context) + { + long Position = Context.Request.GetSendBuffPtr(); + + int Id = Context.Memory.ReadInt32(Position); + + int Handle = -1; + + foreach (KeyValuePair KV in Context.Ns.Os.Handles) + { + if (KV.Value is HNvMap NvMap && NvMap.Id == Id) + { + Handle = KV.Key; + + break; + } + } + + Context.Memory.WriteInt32(Position + 4, Handle); + + return 0; + } + + private static long NvMapIocAlloc(ServiceCtx Context) + { + long Position = Context.Request.GetSendBuffPtr(); + + MemReader Reader = new MemReader(Context.Memory, Position); + + int Handle = Reader.ReadInt32(); + int HeapMask = Reader.ReadInt32(); + int Flags = Reader.ReadInt32(); + int Align = Reader.ReadInt32(); + byte Kind = (byte)Reader.ReadInt64(); + long Addr = Reader.ReadInt64(); + + HNvMap NvMap = Context.Ns.Os.Handles.GetData(Handle); + + if (NvMap != null) + { + NvMap.Address = Addr; + NvMap.Align = Align; + NvMap.Kind = Kind; + } + + Console.WriteLine($"NvMapIocAlloc at {NvMap.Address:x16}"); + + return 0; + } + + private static long NvMapIocParam(ServiceCtx Context) + { + long Position = Context.Request.GetSendBuffPtr(); + + MemReader Reader = new MemReader(Context.Memory, Position); + + int Handle = Reader.ReadInt32(); + int Param = Reader.ReadInt32(); + + HNvMap NvMap = Context.Ns.Os.Handles.GetData(Handle); + + int Response = 0; + + switch (Param) + { + case 1: Response = NvMap.Size; break; + case 2: Response = NvMap.Align; break; + case 4: Response = 0x40000000; break; + case 5: Response = NvMap.Kind; break; + } + + Context.Memory.WriteInt32(Position + 8, Response); + + return 0; + } + + private static long NvMapIocGetId(ServiceCtx Context) + { + long Position = Context.Request.GetSendBuffPtr(); + + int Handle = Context.Memory.ReadInt32(Position + 4); + + HNvMap NvMap = Context.Ns.Os.Handles.GetData(Handle); + + Context.Memory.WriteInt32(Position, NvMap.Id); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServicePctl.cs b/Ryujinx/OsHle/Services/ServicePctl.cs new file mode 100644 index 000000000..62537f287 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServicePctl.cs @@ -0,0 +1,16 @@ +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long PctlCreateService(ServiceCtx Context) + { + MakeObject(Context, new AmIParentalControlService()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServicePl.cs b/Ryujinx/OsHle/Services/ServicePl.cs new file mode 100644 index 000000000..6981637f9 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServicePl.cs @@ -0,0 +1,35 @@ +using Ryujinx.OsHle.Ipc; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long PlGetLoadState(ServiceCtx Context) + { + Context.ResponseData.Write(1); //Loaded + + return 0; + } + + public static long PlGetFontSize(ServiceCtx Context) + { + Context.ResponseData.Write(Horizon.FontSize); + + return 0; + } + + public static long PlGetSharedMemoryAddressOffset(ServiceCtx Context) + { + Context.ResponseData.Write(0); + + return 0; + } + + public static long PlGetSharedMemoryNativeHandle(ServiceCtx Context) + { + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Context.Ns.Os.FontHandle); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceSet.cs b/Ryujinx/OsHle/Services/ServiceSet.cs new file mode 100644 index 000000000..f98e8f0de --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceSet.cs @@ -0,0 +1,32 @@ +using ChocolArm64.Memory; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + private const int LangCodesCount = 13; + + public static long SetGetAvailableLanguageCodes(ServiceCtx Context) + { + int PtrBuffSize = Context.RequestData.ReadInt32(); + + if (Context.Request.RecvListBuff.Count > 0) + { + long Position = Context.Request.RecvListBuff[0].Position; + short Size = Context.Request.RecvListBuff[0].Size; + + //This should return an array of ints with values matching the LanguageCode enum. + byte[] Data = new byte[Size]; + + Data[0] = 0; + Data[1] = 1; + + AMemoryHelper.WriteBytes(Context.Memory, Position, Data); + } + + Context.ResponseData.Write(LangCodesCount); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceSm.cs b/Ryujinx/OsHle/Services/ServiceSm.cs new file mode 100644 index 000000000..8af3ed5e4 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceSm.cs @@ -0,0 +1,48 @@ +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + private const int SmNotInitialized = 0x415; + + public static long SmInitialize(ServiceCtx Context) + { + Context.Session.Initialize(); + + return 0; + } + + public static long SmGetService(ServiceCtx Context) + { + //Only for kernel version > 3.0.0. + if (!Context.Session.IsInitialized) + { + //return SmNotInitialized; + } + + string Name = string.Empty; + + for (int Index = 0; Index < 8 && + Context.RequestData.BaseStream.Position < + Context.RequestData.BaseStream.Length; Index++) + { + byte Chr = Context.RequestData.ReadByte(); + + if (Chr >= 0x20 && Chr < 0x7f) + { + Name += (char)Chr; + } + } + + HSession Session = new HSession(Name); + + int Handle = Context.Ns.Os.Handles.GenerateId(Session); + + Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceTime.cs b/Ryujinx/OsHle/Services/ServiceTime.cs new file mode 100644 index 000000000..2b93e3db8 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceTime.cs @@ -0,0 +1,37 @@ +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long TimeGetStandardUserSystemClock(ServiceCtx Context) + { + MakeObject(Context, new TimeISystemClock()); + + return 0; + } + + public static long TimeGetStandardNetworkSystemClock(ServiceCtx Context) + { + MakeObject(Context, new TimeISystemClock()); + + return 0; + } + + public static long TimeGetStandardSteadyClock(ServiceCtx Context) + { + MakeObject(Context, new TimeISteadyClock()); + + return 0; + } + + public static long TimeGetTimeZoneService(ServiceCtx Context) + { + MakeObject(Context, new TimeITimeZoneService()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceVi.cs b/Ryujinx/OsHle/Services/ServiceVi.cs new file mode 100644 index 000000000..d5c86bfc2 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceVi.cs @@ -0,0 +1,18 @@ +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long ViGetDisplayService(ServiceCtx Context) + { + int Unknown = Context.RequestData.ReadInt32(); + + MakeObject(Context, new ViIApplicationDisplayService()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Svc/SvcHandler.cs b/Ryujinx/OsHle/Svc/SvcHandler.cs new file mode 100644 index 000000000..245d24389 --- /dev/null +++ b/Ryujinx/OsHle/Svc/SvcHandler.cs @@ -0,0 +1,79 @@ +using ChocolArm64.Memory; +using ChocolArm64.State; +using System; +using System.Collections.Generic; + +namespace Ryujinx.OsHle.Svc +{ + partial class SvcHandler + { + private delegate void SvcFunc(Switch Ns, ARegisters Registers, AMemory Memory); + + private Dictionary SvcFuncs = new Dictionary() + { + { 0x01, SvcSetHeapSize }, + { 0x03, SvcSetMemoryAttribute }, + { 0x04, SvcMapMemory }, + { 0x06, SvcQueryMemory }, + { 0x08, SvcCreateThread }, + { 0x09, SvcStartThread }, + { 0x0b, SvcSleepThread }, + { 0x0c, SvcGetThreadPriority }, + { 0x13, SvcMapSharedMemory }, + { 0x14, SvcUnmapSharedMemory }, + { 0x15, SvcCreateTransferMemory }, + { 0x16, SvcCloseHandle }, + { 0x17, SvcResetSignal }, + { 0x18, SvcWaitSynchronization }, + { 0x1a, SvcArbitrateLock }, + { 0x1b, SvcArbitrateUnlock }, + { 0x1c, SvcWaitProcessWideKeyAtomic }, + { 0x1d, SvcSignalProcessWideKey }, + { 0x1e, SvcGetSystemTick }, + { 0x1f, SvcConnectToNamedPort }, + { 0x21, SvcSendSyncRequest }, + { 0x22, SvcSendSyncRequestWithUserBuffer }, + { 0x26, SvcBreak }, + { 0x27, SvcOutputDebugString }, + { 0x29, SvcGetInfo } + }; + + enum SvcResult + { + Success = 0, + ErrBadHandle = 0xe401, + ErrTimeout = 0xea01, + ErrBadIpcReq = 0xf601, + } + + private Switch Ns; + private AMemory Memory; + + private static Random Rng; + + public SvcHandler(Switch Ns, AMemory Memory) + { + this.Ns = Ns; + this.Memory = Memory; + } + + static SvcHandler() + { + Rng = new Random(); + } + + public void SvcCall(object sender, SvcEventArgs e) + { + ARegisters Registers = (ARegisters)sender; + + if (SvcFuncs.TryGetValue(e.Id, out SvcFunc Func)) + { + Func(Ns, Registers, Memory); + } + else + { + throw new NotImplementedException(e.Id.ToString("x3")); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Svc/SvcMemory.cs b/Ryujinx/OsHle/Svc/SvcMemory.cs new file mode 100644 index 000000000..c1249b403 --- /dev/null +++ b/Ryujinx/OsHle/Svc/SvcMemory.cs @@ -0,0 +1,126 @@ +using ChocolArm64.Memory; +using ChocolArm64.State; +using Ryujinx.OsHle.Handles; + +namespace Ryujinx.OsHle.Svc +{ + partial class SvcHandler + { + private static void SvcSetHeapSize(Switch Ns, ARegisters Registers, AMemory Memory) + { + int Size = (int)Registers.X1; + + Memory.Manager.SetHeapSize(Size, (int)MemoryType.Heap); + + Registers.X0 = (int)SvcResult.Success; + Registers.X1 = (ulong)Memory.Manager.HeapAddr; + } + + private static void SvcSetMemoryAttribute(Switch Ns, ARegisters Registers, AMemory Memory) + { + long Position = (long)Registers.X0; + long Size = (long)Registers.X1; + int State0 = (int)Registers.X2; + int State1 = (int)Registers.X3; + + //TODO + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcMapMemory(Switch Ns, ARegisters Registers, AMemory Memory) + { + long Src = (long)Registers.X0; + long Dst = (long)Registers.X1; + long Size = (long)Registers.X2; + + Memory.Manager.MapMirror(Src, Dst, Size, (int)MemoryType.MappedMemory); + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcQueryMemory(Switch Ns, ARegisters Registers, AMemory Memory) + { + long InfoPtr = (long)Registers.X0; + long Position = (long)Registers.X2; + + AMemoryMapInfo MapInfo = Memory.Manager.GetMapInfo(Position); + + MemoryInfo Info = new MemoryInfo(MapInfo); + + Memory.WriteInt64(InfoPtr + 0x00, Info.BaseAddress); + Memory.WriteInt64(InfoPtr + 0x08, Info.Size); + Memory.WriteInt32(InfoPtr + 0x10, Info.MemType); + Memory.WriteInt32(InfoPtr + 0x14, Info.MemAttr); + Memory.WriteInt32(InfoPtr + 0x18, Info.MemPerm); + Memory.WriteInt32(InfoPtr + 0x1c, Info.IpcRefCount); + Memory.WriteInt32(InfoPtr + 0x20, Info.DeviceRefCount); + Memory.WriteInt32(InfoPtr + 0x24, Info.Padding); + + //TODO: X1. + + Registers.X0 = (int)SvcResult.Success; + Registers.X1 = 0; + } + + private static void SvcMapSharedMemory(Switch Ns, ARegisters Registers, AMemory Memory) + { + int Handle = (int)Registers.X0; + long Position = (long)Registers.X1; + long Size = (long)Registers.X2; + int Perm = (int)Registers.X3; + + HSharedMem HndData = Ns.Os.Handles.GetData(Handle); + + if (HndData != null) + { + long Src = Position; + long Dst = HndData.PhysPos; + + if (Memory.Manager.MapPhys(Src, Dst, Size, + (int)MemoryType.SharedMemory, (AMemoryPerm)Perm)) + { + Registers.X0 = (int)SvcResult.Success; + } + } + + //TODO: Error codes. + } + + private static void SvcUnmapSharedMemory(Switch Ns, ARegisters Registers, AMemory Memory) + { + int Handle = (int)Registers.X0; + long Position = (long)Registers.X1; + long Size = (long)Registers.X2; + + HSharedMem HndData = Ns.Os.Handles.GetData(Handle); + + if (HndData != null) + { + Registers.X0 = (int)SvcResult.Success; + } + + //TODO: Error codes. + } + + private static void SvcCreateTransferMemory(Switch Ns, ARegisters Registers, AMemory Memory) + { + long Position = (long)Registers.X1; + long Size = (long)Registers.X2; + int Perm = (int)Registers.X3; + + AMemoryMapInfo MapInfo = Memory.Manager.GetMapInfo(Position); + + Memory.Manager.Reprotect(Position, Size, (AMemoryPerm)Perm); + + long PhysPos = Memory.Manager.GetPhys(Position, AMemoryPerm.None); + + HTransferMem HndData = new HTransferMem(Memory, MapInfo.Perm, Position, Size, PhysPos); + + int Handle = Ns.Os.Handles.GenerateId(HndData); + + Registers.X1 = (ulong)Handle; + Registers.X0 = (int)SvcResult.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Svc/SvcSystem.cs b/Ryujinx/OsHle/Svc/SvcSystem.cs new file mode 100644 index 000000000..6f614d162 --- /dev/null +++ b/Ryujinx/OsHle/Svc/SvcSystem.cs @@ -0,0 +1,163 @@ +using ChocolArm64.Memory; +using ChocolArm64.State; +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; +using System; + +namespace Ryujinx.OsHle.Svc +{ + partial class SvcHandler + { + private static void SvcCloseHandle(Switch Ns, ARegisters Registers, AMemory Memory) + { + int Handle = (int)Registers.X0; + + Ns.Os.CloseHandle(Handle); + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcResetSignal(Switch Ns, ARegisters Registers, AMemory Memory) + { + int Handle = (int)Registers.X0; + + //TODO: Implement events. + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcWaitSynchronization(Switch Ns, ARegisters Registers, AMemory Memory) + { + long HandlesPtr = (long)Registers.X0; + int HandlesCount = (int)Registers.X2; + long Timeout = (long)Registers.X3; + + //TODO: Implement events. + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcGetSystemTick(Switch Ns, ARegisters Registers, AMemory Memory) + { + Registers.X0 = (ulong)Registers.GetSystemReg(3, 3, 14, 0, 1); + } + + private static void SvcConnectToNamedPort(Switch Ns, ARegisters Registers, AMemory Memory) + { + long StackPtr = (long)Registers.X0; + long NamePtr = (long)Registers.X1; + + string Name = AMemoryHelper.ReadAsciiString(Memory, NamePtr, 8); + + //TODO: Validate that app has perms to access the service, and that the service + //actually exists, return error codes otherwise. + + HSession Session = new HSession(Name); + + Registers.X1 = (ulong)Ns.Os.Handles.GenerateId(Session); + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcSendSyncRequest(Switch Ns, ARegisters Registers, AMemory Memory) + { + SendSyncRequest(Ns, Registers, Memory, false); + } + + private static void SvcSendSyncRequestWithUserBuffer(Switch Ns, ARegisters Registers, AMemory Memory) + { + SendSyncRequest(Ns, Registers, Memory, true); + } + + private static void SendSyncRequest(Switch Ns, ARegisters Registers, AMemory Memory, bool IsUser) + { + long CmdPtr = Registers.TlsAddr; + long Size = 0x100; + int Handle = 0; + + if (IsUser) + { + CmdPtr = (long)Registers.X0; + Size = (long)Registers.X1; + Handle = (int)Registers.X2; + } + else + { + Handle = (int)Registers.X0; + } + + byte[] CmdData = AMemoryHelper.ReadBytes(Memory, CmdPtr, (int)Size); + + HSession Session = Ns.Os.Handles.GetData(Handle); + + IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr, Session is HDomain); + + if (Session != null) + { + IpcHandler.ProcessRequest(Ns, Memory, Session, Cmd, CmdPtr, Handle); + + byte[] Response = AMemoryHelper.ReadBytes(Memory, CmdPtr, (int)Size); + + Registers.X0 = (int)SvcResult.Success; + } + else + { + Registers.X0 = (int)SvcResult.ErrBadIpcReq; + } + } + + private static void SvcBreak(Switch Ns, ARegisters Registers, AMemory Memory) + { + long Reason = (long)Registers.X0; + long Unknown = (long)Registers.X1; + long Info = (long)Registers.X2; + + throw new Exception($"SvcBreak: {Reason} {Unknown} {Info}"); + } + + private static void SvcOutputDebugString(Switch Ns, ARegisters Registers, AMemory Memory) + { + long Position = (long)Registers.X0; + long Size = (long)Registers.X1; + + string Str = AMemoryHelper.ReadAsciiString(Memory, Position, (int)Size); + + Console.WriteLine($"SvcOutputDebugString: {Str}"); + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcGetInfo(Switch Ns, ARegisters Registers, AMemory Memory) + { + long StackPtr = (long)Registers.X0; + int InfoType = (int)Registers.X1; + long Handle = (long)Registers.X2; + int InfoId = (int)Registers.X3; + + switch (InfoType) + { + case 6: Registers.X1 = GetTotalMem(Memory); break; + case 7: Registers.X1 = GetUsedMem(Memory); break; + case 11: Registers.X1 = GetRnd64(); break; + + default: throw new NotImplementedException($"SvcGetInfo: {InfoType} {Handle} {InfoId}"); + } + + Registers.X0 = (int)SvcResult.Success; + } + + private static ulong GetTotalMem(AMemory Memory) + { + return (ulong)Memory.Manager.GetTotalMemorySize(); + } + + private static ulong GetUsedMem(AMemory Memory) + { + return (ulong)Memory.Manager.GetUsedMemorySize(); + } + + private static ulong GetRnd64() + { + return (ulong)Rng.Next() + ((ulong)Rng.Next() << 32); + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Svc/SvcThread.cs b/Ryujinx/OsHle/Svc/SvcThread.cs new file mode 100644 index 000000000..895cb247f --- /dev/null +++ b/Ryujinx/OsHle/Svc/SvcThread.cs @@ -0,0 +1,79 @@ +using ChocolArm64.Memory; +using ChocolArm64.State; +using Ryujinx.OsHle.Handles; +using System.Threading; + +namespace Ryujinx.OsHle.Svc +{ + partial class SvcHandler + { + private static void SvcCreateThread(Switch Ns, ARegisters Registers, AMemory Memory) + { + long EntryPoint = (long)Registers.X1; + long ArgsPtr = (long)Registers.X2; + long StackTop = (long)Registers.X3; + int Priority = (int)Registers.X4; + int ProcessorId = (int)Registers.X5; + + if (Ns.Os.TryGetProcess(Registers.ProcessId, out Process Process)) + { + int Handle = Process.MakeThread( + EntryPoint, + StackTop, + ArgsPtr, + Priority, + ProcessorId); + + Registers.X0 = (int)SvcResult.Success; + Registers.X1 = (ulong)Handle; + } + + //TODO: Error codes. + } + + private static void SvcStartThread(Switch Ns, ARegisters Registers, AMemory Memory) + { + int Handle = (int)Registers.X0; + + HThread HndData = Ns.Os.Handles.GetData(Handle); + + if (HndData != null) + { + HndData.Thread.Execute(); + + Registers.X0 = (int)SvcResult.Success; + } + + //TODO: Error codes. + } + + private static void SvcSleepThread(Switch Ns, ARegisters Registers, AMemory Memory) + { + ulong NanoSecs = Registers.X0; + + if (NanoSecs == 0) + { + Thread.Yield(); + } + else + { + Thread.Sleep((int)(NanoSecs / 1000000)); + } + } + + private static void SvcGetThreadPriority(Switch Ns, ARegisters Registers, AMemory Memory) + { + int Handle = (int)Registers.X1; + + HThread HndData = Ns.Os.Handles.GetData(Handle); + + if (HndData != null) + { + Registers.X1 = (ulong)HndData.Thread.Priority; + Registers.X0 = (int)SvcResult.Success; + } + + //TODO: Error codes. + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Svc/SvcThreadSync.cs b/Ryujinx/OsHle/Svc/SvcThreadSync.cs new file mode 100644 index 000000000..1df75625e --- /dev/null +++ b/Ryujinx/OsHle/Svc/SvcThreadSync.cs @@ -0,0 +1,87 @@ +using ChocolArm64; +using ChocolArm64.Memory; +using ChocolArm64.State; +using Ryujinx.OsHle.Handles; + +namespace Ryujinx.OsHle.Svc +{ + partial class SvcHandler + { + private static void SvcArbitrateLock(Switch Ns, ARegisters Registers, AMemory Memory) + { + int OwnerThreadHandle = (int)Registers.X0; + long MutexAddress = (long)Registers.X1; + int RequestingThreadHandle = (int)Registers.X2; + + AThread RequestingThread = Ns.Os.Handles.GetData(RequestingThreadHandle).Thread; + + Mutex M = new Mutex(Memory, MutexAddress); + + M = Ns.Os.Mutexes.GetOrAdd(MutexAddress, M); + + //FIXME + //M.WaitForLock(RequestingThread, RequestingThreadHandle); + + Memory.WriteInt32(MutexAddress, 0); + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcArbitrateUnlock(Switch Ns, ARegisters Registers, AMemory Memory) + { + long MutexAddress = (long)Registers.X0; + + if (Ns.Os.Mutexes.TryGetValue(MutexAddress, out Mutex M)) + { + M.Unlock(); + } + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcWaitProcessWideKeyAtomic(Switch Ns, ARegisters Registers, AMemory Memory) + { + long MutexAddress = (long)Registers.X0; + long CondVarAddress = (long)Registers.X1; + int ThreadHandle = (int)Registers.X2; + long Timeout = (long)Registers.X3; + + AThread Thread = Ns.Os.Handles.GetData(ThreadHandle).Thread; + + if (Ns.Os.Mutexes.TryGetValue(MutexAddress, out Mutex M)) + { + M.GiveUpLock(ThreadHandle); + } + + CondVar Signal = new CondVar(Memory, CondVarAddress, Timeout); + + Signal = Ns.Os.CondVars.GetOrAdd(CondVarAddress, Signal); + + Signal.WaitForSignal(ThreadHandle); + + M = new Mutex(Memory, MutexAddress); + + M = Ns.Os.Mutexes.GetOrAdd(MutexAddress, M); + + //FIXME + //M.WaitForLock(Thread, ThreadHandle); + + Memory.WriteInt32(MutexAddress, 0); + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcSignalProcessWideKey(Switch Ns, ARegisters Registers, AMemory Memory) + { + long CondVarAddress = (long)Registers.X0; + int Count = (int)Registers.X1; + + if (Ns.Os.CondVars.TryGetValue(CondVarAddress, out CondVar Cv)) + { + Cv.SetSignal(Count); + } + + Registers.X0 = (int)SvcResult.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Utilities/IdPool.cs b/Ryujinx/OsHle/Utilities/IdPool.cs new file mode 100644 index 000000000..836d6310e --- /dev/null +++ b/Ryujinx/OsHle/Utilities/IdPool.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; + +namespace Ryujinx.OsHle.Utilities +{ + class IdPool + { + private HashSet Ids; + + private int CurrId; + private int MinId; + private int MaxId; + + public IdPool(int Min, int Max) + { + Ids = new HashSet(); + + CurrId = Min; + MinId = Min; + MaxId = Max; + } + + public IdPool() : this(1, int.MaxValue) { } + + public int GenerateId() + { + lock (Ids) + { + for (int Cnt = MinId; Cnt < MaxId; Cnt++) + { + if (Ids.Add(CurrId)) + { + return CurrId; + } + + if (CurrId++ == MaxId) + { + CurrId = MinId; + } + } + + return -1; + } + } + + public bool DeleteId(int Id) + { + lock (Ids) + { + return Ids.Remove(Id); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Utilities/IdPoolWithObj.cs b/Ryujinx/OsHle/Utilities/IdPoolWithObj.cs new file mode 100644 index 000000000..621466a82 --- /dev/null +++ b/Ryujinx/OsHle/Utilities/IdPoolWithObj.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Ryujinx.OsHle.Utilities +{ + class IdPoolWithObj : IEnumerable> + { + private IdPool Ids; + + private ConcurrentDictionary Objs; + + public IdPoolWithObj() + { + Ids = new IdPool(); + + Objs = new ConcurrentDictionary(); + } + + public int GenerateId(object Data) + { + int Id = Ids.GenerateId(); + + if (Id == -1 || !Objs.TryAdd(Id, Data)) + { + throw new InvalidOperationException(); + } + + return Id; + } + + public bool ReplaceData(int Id, object Data) + { + if (Objs.ContainsKey(Id)) + { + Objs[Id] = Data; + + return true; + } + + return false; + } + + public T GetData(int Id) + { + if (Objs.TryGetValue(Id, out object Data) && Data is T) + { + return (T)Data; + } + + return default(T); + } + + public void Delete(int Id) + { + if (Objs.TryRemove(Id, out object Obj)) + { + if (Obj is IDisposable DisposableObj) + { + DisposableObj.Dispose(); + } + + Ids.DeleteId(Id); + } + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return Objs.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return Objs.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Utilities/MemReader.cs b/Ryujinx/OsHle/Utilities/MemReader.cs new file mode 100644 index 000000000..9868293a0 --- /dev/null +++ b/Ryujinx/OsHle/Utilities/MemReader.cs @@ -0,0 +1,44 @@ +using ChocolArm64.Memory; + +namespace Ryujinx.OsHle.Utilities +{ + class MemReader + { + private AMemory Memory; + + public long Position { get; private set; } + + public MemReader(AMemory Memory, long Position) + { + this.Memory = Memory; + this.Position = Position; + } + + public byte ReadByte() + { + byte Value = Memory.ReadByte(Position); + + Position++; + + return Value; + } + + public int ReadInt32() + { + int Value = Memory.ReadInt32(Position); + + Position += 4; + + return Value; + } + + public long ReadInt64() + { + long Value = Memory.ReadInt64(Position); + + Position += 8; + + return Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Utilities/MemWriter.cs b/Ryujinx/OsHle/Utilities/MemWriter.cs new file mode 100644 index 000000000..041b0a970 --- /dev/null +++ b/Ryujinx/OsHle/Utilities/MemWriter.cs @@ -0,0 +1,38 @@ +using ChocolArm64.Memory; + +namespace Ryujinx.OsHle.Utilities +{ + class MemWriter + { + private AMemory Memory; + + public long Position { get; private set; } + + public MemWriter(AMemory Memory, long Position) + { + this.Memory = Memory; + this.Position = Position; + } + + public void WriteByte(byte Value) + { + Memory.WriteByte(Position, Value); + + Position++; + } + + public void WriteInt32(int Value) + { + Memory.WriteInt32(Position, Value); + + Position += 4; + } + + public void WriteInt64(long Value) + { + Memory.WriteInt64(Position, Value); + + Position += 8; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Switch.cs b/Ryujinx/Switch.cs new file mode 100644 index 000000000..9e5ea7d06 --- /dev/null +++ b/Ryujinx/Switch.cs @@ -0,0 +1,42 @@ +using ChocolArm64.Memory; +using Gal; +using Ryujinx.Gpu; +using Ryujinx.OsHle; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx +{ + public class Switch : IDisposable + { + public IntPtr Ram {get; private set; } + + internal NsGpu Gpu { get; private set; } + internal Horizon Os { get; private set; } + internal VirtualFs VFs { get; private set; } + + public Switch(IGalRenderer Renderer) + { + Ram = Marshal.AllocHGlobal((IntPtr)AMemoryMgr.RamSize); + + Gpu = new NsGpu(Renderer); + Os = new Horizon(this); + VFs = new VirtualFs(); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + VFs.Dispose(); + } + + Marshal.FreeHGlobal(Ram); + } + } +} \ No newline at end of file diff --git a/Ryujinx/VirtualFs.cs b/Ryujinx/VirtualFs.cs new file mode 100644 index 000000000..03317f4a1 --- /dev/null +++ b/Ryujinx/VirtualFs.cs @@ -0,0 +1,65 @@ +using System; +using System.IO; + +namespace Ryujinx +{ + class VirtualFs : IDisposable + { + private const string BasePath = "Fs"; + private const string SavesPath = "Saves"; + + public Stream RomFs { get; private set; } + + public void LoadRomFs(string FileName) + { + RomFs = new FileStream(FileName, FileMode.Open, FileAccess.Read); + } + + internal string GetFullPath(string BasePath, string FileName) + { + if (FileName.StartsWith('/')) + { + FileName = FileName.Substring(1); + } + + string FullPath = Path.GetFullPath(Path.Combine(BasePath, FileName)); + + if (!FullPath.StartsWith(GetBasePath())) + { + return null; + } + + return FullPath; + } + + internal string GetGameSavesPath() + { + string SavesDir = Path.Combine(GetBasePath(), SavesPath); + + if (!Directory.Exists(SavesDir)) + { + Directory.CreateDirectory(SavesDir); + } + + return SavesDir; + } + + internal string GetBasePath() + { + return Path.Combine(Directory.GetCurrentDirectory(), BasePath); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && RomFs != null) + { + RomFs.Dispose(); + } + } + } +} \ No newline at end of file