mirror of
https://github.com/pumpitupdev/pumptools.git
synced 2024-12-26 21:24:08 +01:00
141 lines
4.6 KiB
Markdown
141 lines
4.6 KiB
Markdown
|
# Notes about how NX2 handles saving and loading usb profile data
|
||
|
Executable of v1.54.
|
||
|
|
||
|
This document outlines the relevant parts regarding how NX2 loads and stores profiles and how this had an impact on the
|
||
|
design of the patch module `net-profile`, that hooks up usb profile loading over socket to a remote server.
|
||
|
|
||
|
## Syscall flow
|
||
|
The game calls the following system calls in order per player, here player 1 (id 0), when loading usb profile data
|
||
|
(assume all calls are successful here):
|
||
|
```c
|
||
|
// Simplified pseudo-like C code omitting non relevant parameters
|
||
|
fopen("/mnt/0/nx2save.bin")
|
||
|
fread(fd_save) // stats area
|
||
|
fread(fd_save) // encrypted area
|
||
|
fclose(fd_save)
|
||
|
|
||
|
fopen("/mnt/0/nx2rank.bin")
|
||
|
fread(fd_rank) // encrypted area
|
||
|
fclose(fd_rank)
|
||
|
```
|
||
|
|
||
|
For saving the profile, this is the call flow:
|
||
|
```c
|
||
|
// Simplified pseudo-like C code omitting non relevant parameters
|
||
|
fopen("/mnt/0/nx2save.bin")
|
||
|
fseek(fd_save) // beginning
|
||
|
fwrite(fd_save) // stats area
|
||
|
fwrite(fd_save) // encrypted area
|
||
|
fclose(fd_save)
|
||
|
|
||
|
fopen("/mnt/0/nx2rank.bin")
|
||
|
fseek(fd_rank) // beginning
|
||
|
fwrite(fd_rank) // encrypted area
|
||
|
fclose(fd_rank)
|
||
|
```
|
||
|
|
||
|
We can see that in both scenarios, for loading and saving, the game first takes care of a single file before moving on
|
||
|
to the next one.
|
||
|
|
||
|
There is another flow that is slightly different when playing World Max. After playing a song in World Max, the game
|
||
|
writes the changes of the `nx2save.bin` file back:
|
||
|
```c
|
||
|
// Simplified pseudo-like C code omitting non relevant parameters
|
||
|
fopen("/mnt/0/nx2save.bin")
|
||
|
fseek(fd_save) // beginning
|
||
|
fwrite(fd_save) // stats area
|
||
|
fwrite(fd_save) // encrypted area
|
||
|
fclose(fd_save)
|
||
|
```
|
||
|
|
||
|
## Pumpnet RPCs
|
||
|
The above set the requirements for how pumpnet needs to handle file loading and storing.
|
||
|
|
||
|
### First solution
|
||
|
The initial solution was combining the loading and storing process of both files, `nx2save.bin` and `nx2rank.bin`
|
||
|
into a single download/upload call.
|
||
|
|
||
|
Load phase:
|
||
|
```c
|
||
|
// Simplified pseudo-like C code omitting non relevant parameters
|
||
|
fopen("/mnt/0/nx2save.bin")
|
||
|
pumpnet_download_profiles()
|
||
|
fread(fd_save) // read from buffered data
|
||
|
fread(fd_save) // read from buffered data
|
||
|
fclose(fd_save)
|
||
|
|
||
|
fopen("/mnt/0/nx2rank.bin") // skip, data already buffered
|
||
|
fread(fd_rank) // read from buffered data
|
||
|
fclose(fd_rank) // clear all buffers and reset internal state
|
||
|
```
|
||
|
|
||
|
Save phase:
|
||
|
```c
|
||
|
// Simplified pseudo-like C code omitting non relevant parameters
|
||
|
fopen("/mnt/0/nx2save.bin")
|
||
|
pumpnet_download_profiles()
|
||
|
fseek(fd_save) // seek for buffers
|
||
|
fwrite(fd_save) // write to buffers
|
||
|
fwrite(fd_save) // write to buffers
|
||
|
fclose(fd_save) // skip, do nothing
|
||
|
|
||
|
fopen("/mnt/0/nx2rank.bin") // skip, data already buffered
|
||
|
fseek(fd_rank) // seek for buffers
|
||
|
fwrite(fd_rank) // write to buffers
|
||
|
pumpnet_upload_profiles()
|
||
|
fclose(fd_rank) // clear all buffers and reset internal state
|
||
|
```
|
||
|
|
||
|
However, with the additional flow during World Max, this solution didn't work and broke internal state handling.
|
||
|
Modifying this solution to incorporate saving just the `nx2save.bin` file wasn't feasible with the above architecture.
|
||
|
|
||
|
### Second solution
|
||
|
Therefore, the pumpnet backend was re-designed to operate on on a single file level and separate for saving and
|
||
|
loading.
|
||
|
|
||
|
Load phase:
|
||
|
```c
|
||
|
// Simplified pseudo-like C code omitting non relevant parameters
|
||
|
fopen("/mnt/0/nx2save.bin")
|
||
|
pumpnet_download_save()
|
||
|
fread(fd_save) // read from buffered data
|
||
|
fread(fd_save) // read from buffered data
|
||
|
fclose(fd_save)
|
||
|
pumpnet_clear_save_cache()
|
||
|
|
||
|
fopen("/mnt/0/nx2rank.bin")
|
||
|
pumpnet_download_rank()
|
||
|
fread(fd_rank) // read from buffered data
|
||
|
fclose(fd_rank)
|
||
|
pumpnet_clear_rank_cache()
|
||
|
```
|
||
|
|
||
|
Save phase:
|
||
|
```c
|
||
|
// Simplified pseudo-like C code omitting non relevant parameters
|
||
|
fopen("/mnt/0/nx2save.bin")
|
||
|
pumpnet_download_save()
|
||
|
fseek(fd_save) // seek for buffers
|
||
|
fwrite(fd_save) // write to buffers
|
||
|
fwrite(fd_save) // write to buffers
|
||
|
fclose(fd_save)
|
||
|
pumpnet_upload_save()
|
||
|
pumpnet_clear_save_cache()
|
||
|
|
||
|
fopen("/mnt/0/nx2rank.bin")
|
||
|
fseek(fd_rank) // seek for buffers
|
||
|
fwrite(fd_rank) // write to buffers
|
||
|
fclose(fd_rank)
|
||
|
pumpnet_upload_rank()
|
||
|
pumpnet_clear_rank_cache()
|
||
|
```
|
||
|
|
||
|
Buffer modifications are detected when `fwrite` is called and flagged accordingly that the module is aware of having
|
||
|
to write back dirty data on the call to `fclose`.
|
||
|
|
||
|
State handling is also simplified as it only needs to be taken care of on a file level between `fopen` and `fclose`
|
||
|
calls and not on a multi-file level.
|
||
|
|
||
|
This solution is definitely more robust and flexible than the previous one that operated on a full profile level.
|
||
|
However, the call overhead to the remote server is four times higher compared to the previous solution. Considering
|
||
|
that these calls are not being issued on a high frequency, server load is unlikely to be an issue.
|