
Reverse engineering of the engine powering the PriPara games on arcade.
PriPara (internal name Prineo) is an arcade rythm game developed by SynSophia.
The goal of this project was to reverse the model format of the arcade series in order to port exclusive characters to the newer version of the game on Nintendo Switch.
Some interesting screenshots taken during the RE process.
Early version of the Noesis addon — first partial model import (14-09-2025)
C# model reader used to test deserialization implementations (06-12-2025)
Running a decrypted dump in a VM and hooking the hashing function via DLL injection using MinHook and Xenos, using the ReturnAddress intrinsic to track hash function calls (08-12-2025)
Imported scene from the game via the Noesis addon (27-12-2025)
Imported skinned model from encrypted resource via Noesis addon

Ported dress from PriPara arcade to NintendoSwitch version, model exported using Noesis plugin
The game implements modified versions of standard cryptographic primitives.
Implementation: https://github.com/ExIfDev/Prineo_RE/blob/main/Noesis/fmt_prineo.py#L1130-L1140
Implementation: https://github.com/ExIfDev/Prineo_RE/blob/main/Noesis/fmt_prineo.py#L1074-L1128
The following documentation refers to files after SSZL decompression.
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; //absolute offset
uint32 DATA_SIZE;
};
//the MDJ file can contain a variety of buffers related to geometry and scenes, it does
//not have a header, its assumed to start with one of the following buffers
//and always terminate with the "end/0/" marker
struct Pose //pose applied to a skeleton (=restPose in a model container)
//usually this pose is the same as in the "base" buffer.
//if AnimParams are present then what follows is the animation curves.
//AnimParams can be null when there are no keyframes.
{
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;
//Base transform
float sclX, sclY, sclZ;
float rotX, rotY, rotZ;
float tslX, tslY, tslZ;
//for all channels (9) "Sx", "Sy", "Sz", "Rx", "Ry", "Rz", "Tx", "Ty", "Tz"
struct AnimationParams //6 bytes
{
uint16 unk2; //possibly an uint32 since unk3 is always 0
uint16 unk3;
uint16 CHANNEL_MASK;
};
//if anim params are not null then....
uint32 KEYFRAME_COUNT;
struct Keyframe[KEYFRAME_COUNT]
{
float VALUE;//radians
float TIME;//centiseconds
}:
};
};
struct TextureDefs //define texture name and id bindings
{
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 //defines parameters of materials and shaders
{
string[4] ident ="mate";
uint16 MAT_COUNT;
struct Material[MAT_COUNT]
{
uint16 serializer_id;//1024
uint16 strLen;
string[strLen] MatName;
uint32 TYPE;//..maybe hash? looked up in a registry
//Texture slots
//ambient
uint16 aCount;
for (int i = 0; i < aCount; i++)
{
uint16 serializer_id;
if (serializer_id == 1029)
{
uint32 unk;
}
else
{
uint32 unk2;
}
}
//diffuse
uint16 dCount;
for (int i = 0; i < dCount; i++)
{
uint16 serializer_id;
if (serializer_id == 1029)
{
uint32 unk;
}
else
{
uint32 unk2;
}
}
//specular
uint16 sCount;
for (int i = 0; i < sCount; i++)
{
uint16 serializer_id;
if (serializer_id == 1029)
{
uint32 unk;
}
else
{
uint32 unk2;
}
}
//possibly parameter slots
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;
};
//two possible chunks could follow:
struct MPSS_Subchunk //clear usage unknown
{
string header = "MPSS";
uint16 count;
struct MPSS_Entry[count]
{
uint16 unk32;
byte[16] payload;
}
}
struct MPDS_Subchunk //string to value shader parameter chunk
{
string header = "MPDS";
uint16 count;
struct MPDS_Entry[count]
{
uint16 strLen;
string[strLen] Name;
byte[32] value;
}
}
};
struct LightChunk //defines parameters of lights in a scene
{
char[4] ident = "ligh";
int16 LIGHT_COUNT;
struct light[LIGHT_COUNT]
{
uint16 serializer_id;
uint16 strLen;
char[strLen] light_name;
byte[49] unk40; //transform matrix along with color vals
}
}
struct ModeBuffer//marks the start of a submesh list and its buffers
{