Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
Prineo_RE — Reverse engineering del motore che alimenta i giochi PriPara su arcade. | Kitploit
Strumenti/GitHubGitHub/exifdev/prineo_re
Sicurezza Sistemi EmbeddedReverse EngineeringCrittografiaAnalisi di BinariApprendimento e FormazioneAnalisi del Firmware
GitHubexifdev/prineo_re

Prineo_RE

Reverse engineering del motore che alimenta i giochi PriPara su arcade.

Vedi Repository
54 mesi faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Informazioni

PriPara (nome interno Prineo) è un gioco ritmico arcade sviluppato da SynSophia.

Obiettivo

L'obiettivo di questo progetto è stato quello di fare reverse engineering del formato dei modelli della serie arcade per poter portare i personaggi esclusivi sulla versione più recente del gioco per Nintendo Switch.

Screenshot

Alcuni screenshot interessanti scattati durante il processo di reverse engineering.


Early Noesis addon import Versione iniziale del plugin Noesis — primo import parziale del modello (14-09-2025)


C# model reader Lettore di modelli in C# utilizzato per testare le implementazioni di deserializzazione (06-12-2025)


Hooking hashing function Esecuzione di un dump decrittato in una VM e hooking della funzione di hashing tramite DLL injection usando MinHook e Xenos, sfruttando l'intrinseco ReturnAddress per tracciare le chiamate alla funzione di hash (08-12-2025)


Imported scene Scena importata dal gioco tramite il plugin Noesis (27-12-2025)


Imported model Modello skinnato importato da una risorsa crittata tramite il plugin Noesis


Ported model Ported model2

Vestito portato da PriPara arcade alla versione Nintendo Switch, modello esportato utilizzando il plugin Noesis


Implementazioni Crittografiche Personalizzate

Il gioco implementa versioni modificate di primitive crittografiche standard.


1. FNV-1

  • Ordine delle operazioni invertito
  • Base iniziale offset mancante

Implementazione: https://github.com/ExIfDev/Prineo_RE/blob/main/Noesis/fmt_prineo.py#L1130-L1140


2. Mersenne Twister

  • Sottrae invece di aggiungere
  • Moltiplicatore personalizzato 0x13F8769B
  • mascherato a 31 bit
  • Operazione twist mutata con costante personalizzata
  • Costanti di maschera modificate

Implementazione: https://github.com/ExIfDev/Prineo_RE/blob/main/Noesis/fmt_prineo.py#L1074-L1128

RICERCA SUL FORMATO DEI FILE

  • Gioco: PriPara (Prineo)
  • Mano: destrorso
  • Endianness: LE
  • Avvolgimento triangoli: CW
  • Tipo: chunk sparsi
  • MaxPolyC: 65535
  • MaxBoneC: 255

La documentazione seguente si riferisce ai file dopo la decompressione SSZL.

.BIN (CONTENITORE GENERICO)

root@kitploit:~
const byte[] ident = {0x00, 0x13, 0x10, 0x09};
struct Header
{
    uint32 VERSION;//1
    uint32 FILE_SIZE;
    byte[4] Ident;
    int32 unk1;
    int32 unk2;
    int32 unk3;
    uint32 DATA_OFFSET; //offset assoluto
    uint32 DATA_SIZE;
};

.MDJ

root@kitploit:~
//il file MDJ può contenere vari buffer relativi a geometria e scene; non
//ha un header, si assume che inizi con uno dei buffer seguenti
//e termina sempre con il marcatore "end/0/"

struct Pose //posa applicata a uno scheletro (=restPose in un contenitore di modello)
            //di solito questa posa è uguale a quella nel buffer "base".
            //se sono presenti AnimParams, ciò che segue sono le curve di animazione.
            //AnimParams può essere nullo quando non ci sono keyframe.
{
    char[4] ident = "pose";
    uint16 strLen;
    char[strLen] POSE_NAME;
    byte[2] unk;
    uint16 BONE_COUNT;
    byte[8] unkdata;

    struct Bone[BONE_COUNT]
    {
        uint16 serializer_id;//518
        uint16 strLen;
        string[strLen] BONE_NAME;
        //Trasformazione base
        float sclX, sclY, sclZ;
        float rotX, rotY, rotZ;
        float tslX, tslY, tslZ;

        //per tutti i canali (9) "Sx", "Sy", "Sz", "Rx", "Ry", "Rz", "Tx", "Ty", "Tz"

        struct AnimationParams //6 byte
        {
            uint16 unk2; //possibilmente un uint32 dato che unk3 è sempre 0
            uint16 unk3;
            uint16 CHANNEL_MASK;
        };
        //se i parametri di animazione non sono nulli allora....
        uint32 KEYFRAME_COUNT;
        struct Keyframe[KEYFRAME_COUNT]
        {
            float VALUE;//radianti
            float TIME;//centisecondi
        };
    };
};

struct TextureDefs //definisce i collegamenti nome e id delle texture
{
    string[4] ident ="imag";
    uint16 TEX_COUNT;
    struct TexDef[TEX_COUNT]
    {
        uint16 serializer_id;//1029
        uint16 unk;
        uint16 TEX_ID;
        uint16 strLen;
        string[strLen] texName;
    };
};

struct MaterialChunk //definisce i parametri dei materiali e degli shader
{
    string[4] ident ="mate";
    uint16 MAT_COUNT;

    struct Material[MAT_COUNT]
    {
        uint16 serializer_id;//1024
        uint16 strLen;
        string[strLen] MatName;
        uint32 TYPE;//..forse hash? cercato in un registro

        //Slot texture

        //ambientale
        uint16 aCount;
        for (int i = 0; i < aCount; i++)
        {
            uint16 serializer_id;
            if (serializer_id == 1029)
            {
                uint32 unk;
            }
            else
            {
                uint32 unk2;
            }
        }

        //diffuso
        uint16 dCount;
        for (int i = 0; i < dCount; i++)
        {
            uint16 serializer_id;
            if (serializer_id == 1029)
            {
                uint32 unk;
            }
            else
            {
                uint32 unk2;
            }
        }

        //speculare
        uint16 sCount;
        for (int i = 0; i < sCount; i++)
        {
            uint16 serializer_id;
            if (serializer_id == 1029)
            {
                uint32 unk;
            }
            else
            {
                uint32 unk2;
            }
        }

        //possibili slot di parametri

        uint16 unk1Count;
        for (int i = 0; i < unk1Count; i++)
        {
            uint16 serializer_id;
            float unk12;
        }

        uint16 unk2Count;
        for (int i = 0; i < unk2Count; i++)
        {
            uint16 serializer_id;
            float unk22;
        }

        byte HAS_TEXTURE;

        if (HAS_TEXTURE == 1)
        {
            uint16 TEX_COUNT;
            struct TexureMap[TEX_COUNT]
            {
                uint16 serializer_id;
                uint32 TEX_ID;
            }  
        }

        uint16 strLen;
        string[strLen] unkNullString;    
    };

    //due possibili chunk potrebbero seguire:

    struct MPSS_Subchunk //uso chiaro sconosciuto
    {
        string header = "MPSS";
        uint16 count;
        struct MPSS_Entry[count]
        {
            uint16 unk32;
            byte[16] payload;
        }
    }

    struct MPDS_Subchunk //chunk parametri shader stringa-valore
    {
        string header = "MPDS";
        uint16 count;

        struct MPDS_Entry[count]
        {
            uint16 strLen;
            string[strLen] Name;
            byte[32] value;
        }
    }
};

struct LightChunk //definisce i parametri delle luci in una scena
{
    char[4] ident = "ligh";
    int16 LIGHT_COUNT;

    struct light[LIGHT_COUNT]
    {
        uint16 serializer_id;
        uint16 strLen;
        char[strLen] light_name;
        byte[49] unk40; //matrice di trasformazione insieme a valori di colore
    }
}

struct ModeBuffer//marca l'inizio di una lista di submesh e dei suoi buffer
{
    char[4] ident = "mode";
    uint16 serializer_id;//513
    uint16 strLen;
    char[strLen] unkName; //possibilmente il nodo radice
    byte[4] unk; 
    uint16 unkC;  
};

struct MeshMeta//una submesh inizia con alcuni metadati sulla sua dimensione di bounding, nome e la sua boneMap
{
    uint16 serializer_id;//770 0x0302
    uint16 strLen;
    char[strLen] MESH_NAME; 
    byte[4] unk;  
    uint16 boneMapCount;
    struct HashedBoneMap[boneMapCount]
    {
        uint32 boneHash;//nome osseo hashato con SSFNV1a
    }
    float unk3;
    byte unk2;
    byte[24] AABB;
}
struct MeshBegin //indica l'inizio di una submesh
{
    string[4] ident ="verb";
};
struct FaceBuffer 
{
    string[4] ident = "surf";
    int16 FACE_COUNT;
    struct Face [FACE_COUNT]//stride: 6 //tipo: lista triangoli // avvolgimento: orario
    {
        uint16 A;
        uint16 B;
        uint16 C;
    };
};

struct VertexBuffer 
{
    string[4] ident = "coor";
    uint16 VERTEX_COUNT;
    struct Vertex [VERTEX_COUNT] //stride 12
    {
        float x;
        float y;
        float z;
    };
};

struct NormalBuffer
{
    string[4] ident ="norm";
    uint16 NORM_COUNT; //dovrebbe essere uguale a VERTEX_COUNT
    struct Normal[NORM_COUNT]//stride 12
    {
        float nx;
        float ny;
        float nz;
    };
};

struct BinormalBuffer//buffer binormale (non presente in nessun file)
{
    string[4] ident ="bino";
    uint16 BINO_COUNT; //dovrebbe essere uguale a VERTEX_COUNT
    struct BiNormal[NORM_COUNT]//stride 12
    {
        float bx;
        float by;
        float bz;
    };
};

struct UV0Buffer
{
    string[4] ident ="tex0";
    uint16 UV0_COUNT;    //dovrebbe essere uguale a VERTEX_COUNT
    struct UV[UV0_COUNT] //stride 8
    {
        float u;
        float v;
    };
};

struct UV1Buffer
{
    string[4] ident ="tex1";
    uint16 UV1_COUNT;    //dovrebbe essere uguale a VERTEX_COUNT
    struct UV[UV1_COUNT] //stride 8
    {
        float u;
        float v;
    };
};

struct VertexColors
{
    string[4] ident = "colo";
    uint16 VERTEX_COUNT;   //dovrebbe essere uguale a VERTEX_COUNT
    struct UV[UV1_COUNT] //stride 4
    {
        byte r,g,b,a;
    };
}

struct TangentBuffer //vettore tangente senza segno
{
    char[4] ident ="tan ";
    uint16 TANG_COUNT;      //dovrebbe essere uguale a VERTEX_COUNT
    struct Tang[TANG_COUNT]//stride 12
    {
        float tx;
        float ty;
        float tz;
    };
};

struct WeightBuffer //Valori di peso per ogni osso assegnato a un vertice
{
    char[4] ident ="weig";
    uint16 WEIGHT_COUNT;
    struct VertexWeight[WEIGHT_COUNT] //stride 16 
    {
        float w1;
        float w2;
        float w3;
        float w4;
    };
};

struct VBIBuffer //Indici ossei del vertice, in ordine, indici ossei che influenzano un vertice
                 //questa tabella è locale al buffer del vertice corrente
{
    char[4] ident ="bone";
    uint16 VERTEX_COUNT;
    struct VBI[VERTEX_COUNT]
    {
        byte boneIDX1;
        byte boneIDX2;
        byte boneIDX3;
        byte boneIDX4;
    };
};

//
struct Skeleton //Albero dei nodi ossei, definisce le relazioni tra le ossa
{
    char[4] ident ="skel";
    struct BoneName[BONE_COUNT] //Numero di ossa letto dal chunk POSE
    {
        uint16 serializer_id; //257=radice 258=figlio
        uint16 strLen;
        char[strLen] BONE_NAME;
        uint32 unk2;
        uint16 CHILD_COUNT;
    };
};

struct RestPose //definisce la posa a riposo dello scheletro
{
    char[4] ident ="base";
    uint16 strLen;
    char[strLen] POSE_NAME;
    int16 BONE_COUNT;
    struct BoneTrs[BONE_COUNT]
    {
        uint16 serializer_id;//1537
        uint16 strLen;
        char[strLen] BONE_NAME;
        float sx, sy, sz; //scala
        float rx, ry, rz; //rotazione
        float tx, ty, tz; //traslazione
    };
};

struct MeshEnd //indica la fine di una mesh
{
    string[4] ident ="vere";
    uint16 strLen;
    char[strLen] NAME;
    byte[2] padding;
};

struct EndFlag //indica la fine del file
{
    string[4] ident ="end "
}

_tex.BIN (CONTENITORE TEXTURE)

root@kitploit:~
//le informazioni sono relative al file decompresso
struct Header
{
    uint32 VERSION;
    uint32 FILE_SIZE;
    uint32 tex_ident;
    uint32 unk;
    uint32 TEXTURE_COUNT;
};

struct TextureDecl[TEXTURE_COUNT]
{
    uint32 HASH;//nome texture hashato senza estensione
    uint32 OFFSET;//offset assoluto
    uint32 SIZE;
};
//seguono i buffer delle texture a OFFSET con SIZE
//TGA o DDS

Ringraziamenti

  • @REDxEYE per aver aiutato a fare reverse engineering del buffer materiale
Scarica lo strumento