A PSP Emulator for >= .NET Core 3.0 made in C# by soywiz - Carlos Ballesteros Velasco
Work In Progress emulator
- Cpu, Fpu and VFpu are almost fully implemented
- Audio is implemented
- Gpu still requires lots of works
- Hle emulation has implemented lots of APIs (still lot of work left)
- There are lot of homebrew games running
- There are a growing number of commercial games running at full speed
Being fully managed (thought it will need unsafe access) will allow to port to lots of platforms in the future: AKA: Linux, Mac, PS3, Android, future devices...
The quality of the generated code is fantastic.
Lots of MIPs instructions encodes in a single X86/64 instruction.
The registers are inlined in the CpuThreadState class, so access
to a register is as fast as accessing a non-virtual field.
Using VirtualAlloc/mmap, allows all the PSP Memory Segments to be in a virtual space that fits the PSP Memory.
And since there is no DMA access on a HLE emulation, we can get a pointer just making an ADD operation between the base and the PSP address.
This is as fast as possible.
MainPtr = VirtualAlloc(Base + MainOffset, MainSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
Detect and access to a function cache features O(1),
adding a function cache O(log(n)),
and flushing a range or the whole Function Cache is as fast as possible too.
This is achieved with a Array that contains all the possible memory addresses and a SortedSet
that allows to know which functions have been generated and should be purged when invalidating instruction cache.
Each PSP Thread is a native Green Thread / Fiber. Since the PSP has a single core, and some games requres
thread scheduling, we simulate thread scheduling with fibers. Each PspThread have its own registers in an object,
so instead of restoring the registers, it just resumes the thread execution. Skiping completely the register restoring.
This model also allows to pause the execution of the code at any time very easy, even inside an HleFunction or even
in the future, if we optimize the function generation to detect JAL instructions and call delegates directly, inside
nested function calls.
LINQ + functional-programming helps to make the code very clean, simple and easy to understand.
/// <summary>
/// Get the size of the largest free memory block.
/// </summary>
/// <returns>The size of the largest free memory block, in bytes.</returns>
[HlePspFunction(NID = 0xA291F107, FirmwareVersion = 150)]
public int sceKernelMaxFreeMemSize()
{
return HleState.MemoryManager.GetPartition(HleMemoryManager.Partitions.User).ChildPartitions
.Where(Partition => !Partition.Allocated)
.OrderByDescending(Partition => Partition.Size)
.First()
.Size
;
}
C# support of structs and pointers, allow the HLE functions to use pointers directly and seamlessly.
/// <summary>
/// Reads an entry from an opened file descriptor.
/// </summary>
/// <param name="FileHandle">Already opened file descriptor (using sceIoDopen)</param>
/// <param name="IoDirent">Pointer to an io_dirent_t structure to hold the file information</param>
/// <returns>
/// Read status
/// Equal to 0 - No more directory entries left
/// Great than 0 - More directory entired to go
/// Less than 0 - Error
/// </returns>
[HlePspFunction(NID = 0xE3EB004C, FirmwareVersion = 150)]
public int sceIoDread(int FileHandle, HleIoDirent* IoDirent)
{
var HleIoDrvFileArg = GetFileArgFromHandle(FileHandle);
return HleIoDrvFileArg.HleIoDriver.IoDread(HleIoDrvFileArg, IoDirent);
}
Most CPU/FPU/VFPU operations are something like 8x times faster than the original PSP on a modern computer.
Still the GPU implementation is still limited because of the Cpu <-> Gpu memory BUS. But probably will be enough in most cases.
With the "Keep It Simple Stupid" and "Don't Repeat Yourself" philosophies always in mind,
the quality of the core is a priority always over new features.
CPU/Gpu Instruction decoding, assembler and disassembler share a single table with the instruction descriptions that will allow generating the required code on the fly.
// Arithmetic operations.
ID("add", VM("000000:rs:rt:rd:00000:100000"), "%d, %s, %t", ADDR_TYPE_NONE, 0),
ID("addu", VM("000000:rs:rt:rd:00000:100001"), "%d, %s, %t", ADDR_TYPE_NONE, 0),
ID("addi", VM("001000:rs:rt:imm16" ), "%t, %s, %i", ADDR_TYPE_NONE, 0),
The idea is to create a full set of tests, testing the emulator interface, code generation, and HLE APIs.
UnitTests are integrated in the code, and HLE APIs are external integration tests in form of PSP executables than can be executed on any emulator to test APIs.
There is a project called pspautotests used by all the active emulators to test regressions and new APIs.
Project is on GitHub, so anyone can branch the project and perform Pull Requests helping to improve the code.
I'm the creator of the D PSP Emulator. That emulator had 4 versions, every one was created almost from the scratch with different approaches and trying
to improve the quality of the code in each iteration.
The lack of a good IDE, the complicated structure of the D language, the horrible compilation times, caused that it taked too much time for everything,
and made it impossible to refactoring the code without days or weeks of work.
In this time I have learned lot of things, and since the .NET platform is fast enough, have "mono", and solves all my problems with D, it will be my final choice.
The first emulator made by Noxa was a C# emulator too. It was for .NET 2.0. .NET and C# have evolved a lot since that 2.0 version. And now it is probably the best
platform/language out there. It has the right balance between ease, power and speed. Of course it lacks some stuff, but it is the language where less programming features I have missed.
I didn't used the pspplayer code as base because I wanted to make things with all the stuff I have learned this time and with a different approach.
I started the first version of my emulator using Noxa's pspplayer as base. I didn't managed to compile his code that time because of a mixed DLL x86/x64, and I wanted to learn to make an emulator.
I want to acknowledge all the great work of Noxa, because without all his first hard work, I hadn't learnt this all stuff.