Emulación del servidor de updates (d4c-emu)

⚠️ Esto es una prueba de concepto, no me hago responsable de los daños que puedas provocar a tu consola.


Hace un par de días me puse a trabajar en una API para NightFall porque actualmente es simplemente un servidor http y quería hacer algo más “profesional”.

La idea que siempre he tenido de NightFall ha sido emular el proceso de actualización de la Nintendo Switch, así que me puse a investigar como actualiza el firmware vía red la nintendo switch.

Investigando el proceso de update

Si queremos ver que llamadas realiza la nintendo switch a la hora de actualizar deberemos de usar algo que esté entre medias del servidor y la nintendo switch para poder capturas los paquetes que manda y recibe, así que me decante a usar un proxy.

Los servidores de nintendo mayormente usa una autentificación vía certificado SSL así que tuve que añadir el certificado único de mi consola para poder acceder a los servidores de nintendo, para esto lo que hice fue seguir la guía que viene con el programa NxCertDump de shadowninja108, este programa te genera un certificado p12 que es compatible con el programa que voy a usar de proxy.

img

En el proxy configuré el certificado generado nx_tls_client_cert.pfx como certificado cliente y forzando que lo usé en cualquier llamada.

(A la hora de importar el certificado puede que te pregunte por una contraseña esta es: switch)

img.png

También para que la nintendo switch pueda realizar las llamadas correctamente deberemos de parchear el módulo ssl que permite recibir respuestas sin verificar el certificado usé los parches que hay públicos en el repositorio de misson20000

Una vez hecho esto conecté mi nintendo al proxy y entonces pude ver las llamadas

img

Según la switchbrew-wiki las llamadas que nos interesaban vienen de sun.hac.lp1.d4c.nintendo.net, aqua.hac.lp1.d4c.nintendo.net y atumn.hac.lp1.d4c.nintendo.net.

Estas tres direcciones son las que llama al hacer el proceso de update completo. Las funciones de cada una son las siguientes:

  • sun: Contiene el nº de versión más reciente en la cdn.
  • aqua: Contiene el nº requerido para usar los servicios online de nintendo.
  • atumn: es la cdn donde se aloja todos los ncas.

Ya hemos identificado las llamadas que necesitamos ahora necesitamos saber el orden al que llama a estas direcciones y que llamadas hace.

aqua suele ser llamada al encender la consola o de forma aleatoria así que queda fuera del proceso al hacer update.

sun es siempre llamado para verificar que la versión del title_id: 0100000000000816 es superior (y no inferior, que en este caso siempre va a devolver una pantalla de que estás en la última versión) a la versión que tiene actualmente el sistema. En caso de que responda con una versión superior al del sistema automáticamente llamará a atumn solicitando mediante un HEAD el id nca de la versión que indica sun

img

Una vez obtenido el id vuelve a solicitar a atumn en una solicitud del tipo GET el NCA para descargarlo y parsear los title_ids que necesita actualizar para solicitar los ncaid y descargar estos.

img

una vez ya la consola tiene los ncas necesarios descargados la consola solicitará al usuario si desea realizar la actualización y comprobará en aqua si dispones de la versión necesaria para conectarte a los servicios y te mostrara un mensaje de que hay una actualización de firmware disponible y te permitirá saltar la advertencia y continuar, pero en el caso contrario te forzara a actualizar si quieres acceder a ese servicio.

y aquí finaliza el proceso de investigación así que me puse a programar el emulador.

Programando d4c-emu

Para programar el emulador decidi usar ASP.NET, ya que me permite usar la librería LibHac que me facilita bastante la lectura de NCAs

Implementando aqua y sun

Primero programe un controlador para aqua y sun ya que estos son dos simples llamadas GET

/* aqua */
[ApiController]
[Produces("application/json")]
public class AquaController : ControllerBase
{
    [HttpGet("required_system_update_meta")]
    public ActionResult<string> 
        GetLastestMandatoryUpdate(string device_id = "DEADCAFEBABEBEEF")
        /* ... */
        
/* sun */
[ApiController]
[Route("v1/")]
[Produces("application/json")]
public class SunController : ControllerBase
{
    [HttpGet("system_update_meta")]
    public ActionResult<string> 
        GetLastestUpdate(string device_id = "DEADCAFEBABEBEEF")
        /* ... */

Ambas respuestas son del tipo GET y responden con un application/json estos como he explicado antes responde con la versión de firmware más actual así que lo único que hice fue cargar al inicio del programa en un fssystem con todos los ncas y los ordeno según su versión para obtener siempre la ultima versión entre los NCAs.

foreach (var nca in Horizon.hos.ncafolder.Titles.
                     Values.OrderByDescending(x => x.Version.Version))
            if (nca.Id == 0x0100000000000816ul)
            {
            /* ... */

Una vez que obtengo la última versión devuelvo un json como respuesta con los datos necesarios:

img

img

Y con esto ya está tendríamos implementado tanto sun como aqua, ahora nos queda atumn que tiene su chicha.

Implementando atumn

atumn es el encargado de devolver los ncas a la nintendo este tiene una estructura dependiendo de lo que solicite por ejemplo llamar a t/s/{contentid}/{version} te devuelve el id del nca según el titleid que solicite y su versión y los demás hacen lo siguiente:

  • HEAD t/s/{contentid}/{version}: devuelve el id del nca de tipo NCA0 (actualmente solo 0100000000000816) según el titleid que solicite y su versión.
  • HEAD t/a/{contentid}/{version}: devuelve el id del meta nca según el titleid que solicite y su versión.
  • GET c/s/{NcaId}: devuelve el nca de tipo NCA0 (actualmente solo 0100000000000816) según el ncaid que solicite.
  • GET c/a/{NcaId}: devuelve el meta nca de según el ncaid que solicite.
  • GET c/c/{NcaId}: devuelve el program (o otro) nca de según el ncaid que solicite.

Realmente estas cinco llamadas son iguales o similares a las de aqua o sun y solo cambian una sola cosa así lo que hice fue algo así:

foreach (var nca in Horizon.hos.ncafolder.Titles.Values.OrderBy(x => x.Id))
            if (nca.Id.Equals(Convert.ToUInt64($"0x{contentid}", 16)) &&
                nca.Version.Version.Equals(Convert.ToUInt32($"{version}")))
            {
                HttpContext.Response.Headers.Add("X-Nintendo-Content-ID", nca.MetaNca.NcaId);
                HttpContext.Response.Headers.Add("X-Nintendo-Content-Hash",
                    Horizon.SHA256CheckSum(Path.Combine(Horizon.hos.NcaFolderPath, 
                        nca.MetaNca.Filename)));

y con todo esto ya tenemos el servicio montado y podemos conectar nuestra switch al emulador y poder testear el sistema de updates.

Podéis ver el resultado en el siguiente video: https://youtu.be/bhru9z-hDMc

Enlaces

  • d4c-emu - Nintendo Content Delivery server emulator