A small C++17 library that parses and encodes Rhythia map files.
- Single header + one .cpp (plus vendored miniz for ZIP/DEFLATE). Drop into any project as a submodule.
- No external dependencies beyond the C++17 standard library and the vendored miniz amalgamation.
- Round-trip-safe. Bytes that parse will re-encode with identical metadata; audio and cover payloads are preserved verbatim.
include/rhmParse/rhmParse.h # public API
src/rhmParse.cpp # implementation
src/miniz.h, src/miniz.c # vendored ZIP/DEFLATE (public domain)
examples/dump.cpp # print a map's contents
examples/roundtrip.cpp # parse + re-encode and verify
examples/convert.cpp # convert between .rhm and .sspm
CMakeLists.txt # optional, for add_subdirectory() users
git submodule add https://github.com/yo-ru/rhmParse third_party/rhmParseThen add third_party/rhmParse/src/rhmParse.cpp and third_party/rhmParse/src/miniz.c to your build's source list and put third_party/rhmParse/include/ and third_party/rhmParse/src/ on the include path.
add_subdirectory(third_party/rhmParse)
target_link_libraries(my_app PRIVATE rhmParse::rhmParse)g++ -std=c++17 -I include -I src src/rhmParse.cpp src/miniz.c my_code.cpp -o my_app#include <rhmParse/rhmParse.h>
#include <fstream>
#include <iterator>
std::ifstream f("map.rhm", std::ios::binary);
std::vector<uint8_t> bytes((std::istreambuf_iterator<char>(f)),
std::istreambuf_iterator<char>());
rhm::Rhm rhm;
if (!rhm::Parse(bytes, rhm)) {
fprintf(stderr, "invalid map\n");
return 1;
}
printf("Title: %s\n", rhm.map.title.c_str());
printf("Notes: %zu\n", rhm.map.notes.size());
printf("Audio: %zu bytes\n", rhm.audio.size());
// Re-encode (for editing or repackaging):
auto encoded = rhm::Encode(rhm);See examples/dump.cpp for a complete CLI utility.
| Symbol | Purpose |
|---|---|
rhm::Rhm |
Top-level record: map, audio, cover. |
rhm::Map |
Decoded map JSON: metadata + the note list. Nullable JSON fields use std::optional. |
rhm::Note |
One playable note (time, x, y). |
rhm::Parse(bytes, out) |
Decode bytes into an Rhm. Returns false on truncation/malformed zip/invalid JSON. |
rhm::Encode(rhm) |
Encode an Rhm to std::vector<uint8_t>. Writes map always; writes audio and cover only when they're populated. |
rhm::FromSspm(bytes, out) |
Decode an SSPM v2 file into an Rhm. Non-ssp_note markers, requires_mod and the sha1 hash are dropped. |
rhm::ToSspm(rhm) |
Encode an Rhm as SSPM v2 bytes. The difficulty_name custom field, audio, and cover are preserved; RHM-only metadata (onlineId, onlineStatus, audioFileName, imagePath path strings) is dropped. |
All API surface is documented inline in rhmParse.h.
A .rhm is a ZIP archive containing:
| Entry | Required | Contents |
|---|---|---|
map |
yes | UTF-8 JSON: metadata + the note list |
audio |
no | raw audio bytes (typically MP3); omitted when the map ships without audio |
cover |
no | raw image bytes (typically PNG); omitted when the map has no cover |
The map JSON, in the canonical field order the game emits:
Note coordinates lie on the standard Sound-Space-style 3x3 grid (typically 0..2); some maps use fractional offsets, so Note.x and Note.y are floats.
MIT. Use freely; attribution appreciated but not required.
The vendored miniz (src/miniz.{h,c}) is in the public domain.
The .rhm container and map JSON layout were reverse-engineered from maps exported by the official client. This library is an independent reimplementation; no game code is reused.
{ "OnlineId": null, // or int - ranked / uploaded map id "OnlineStatus": null, // or string "LegacyId": "string", // stable SSPM-style id "SongName": "string", "Mappers": ["string", ...], "Title": "string", "Duration": 0, // ms "Difficulty": 0, // preset slot "CustomDifficultyName": "string", "StarRating": 0.0, "Notes": [ { "Time": 0, "X": 0, "Y": 0 }, ... ], "AudioFileName": "string", "ImagePath": null // or string - null means no cover }