npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@r2o3/rgskin-nodejs

v0.0.9

Published

A library for converting, loading and creating skins for various rhythm games.

Readme

rgskin

crates.io npm (browser) npm (node)

license rust

A library for loading and creating skins for various rhythm games. It supports multi-threading and cross-platform usage including Web and Node.js environments via WebAssembly (WASM).

Table of Contents

Features

Supported Games:

  • Osu!
  • Quaver
  • fluXis

Rust Usage

Installation

Add this to your Cargo.toml:

[dependencies]
rgskin = "0.0.9"

Or run:

cargo add rgskin

API Reference

Importing/Loading Skins

Recommended way of Loading a skin
use rgskin::prelude::*;

// importing a skin from a directory
let osu_skin = import::osu::skin_from_dir("path/to/skin").expect("Failed to import skin!", false);
let fluxis_skin = import::fluxis::skin_from_dir("path/to/skin").expect("Failed to import skin!", false);
let quaver_skin = import::quaver::skin_from_dir("path/to/skin").expect("Failed to import skin!", false);

The second argument is for if you want to import ALL assets for a skin, it will import all textures but leave the unrequired unloaded, usually recommended when merging skins of the same type as you might need all assets; otherwise if false it will only import and load the required assets.

Manually loading a skin
use rgskin::prelude::*;

// create a new texture store, this is where the actual textures will be stored
let mut textures = TextureStore::new();

// you can import textures from a directory like this:
textures = import::all_textures_from_dir("path/to/skin", None)?;

// or alternatively if you parse the skin config first, you can import only the textures you need like this:
let raw_str = import::osu::ini_str_from_dir("path/to/skin");
let skin_config = OsuSkinIni::from_str(&raw_str)?;

// since get_required_texture_paths returns a HashSet, we need to convert it to a Vec<&str> for the import function
let required_texture_paths_set = skin_config.get_required_texture_paths();
let required_texture_paths = required_texture_paths_set.iter().map(|s| s.as_str()).collect::<Vec<_>>();

textures = import::textures_from_dir("path/to/skin", &required_texture_paths)?;

// now you can create a skin from the config and textures

let osu_skin = OsuSkin::new(skin_config, Some(textures), None); // last parameter is the sound samples store, which you can import similarly to textures

Creating Skins

All skins are loaded in their original formats; Any textures go in TextureStore or samples go in SampleStore, etc. The config is also preserved so, this next part will talk about dealing with generic skins as creating skins for a specific game differs from one to another.

Additionally all skins can be converting into a generic version of it.

Examples:

OsuSkin::from_generic_mania(&generic); 
OsuSkin.to_generic_mania(()); // yes, the extra parethesis is required.
FluXisSkin::from_generic_mania(&generic); 
FluXisSkin.to_generic_mania(fluxis_layout); // if you don't have a layout you can just pass None.

Unlike skins from games not all textures are stored in TextureStore. Skin Elements can have their own textures that are shared pointers (Option<Arc<RwLock<Texture>>>). Meaning the texture can be shared anywhere either in a TextureStore or inside a Skin Element.

Exporting Skins

Recommened way of exporting a skin
export::osu::skin_to_dir(&skin, "path/to/export/to")?;
export::fluxis::skin_to_dir(&skin, "path/to/export/to")?;
Manually exporting a skin
export::osu::ini_to_dir(skin.skin_ini, "path/to/export/to"); // export::{game}
export::textures_to_dir(skin.textures, "path/to/export/to");
export::samples_to_dir(skin.samples, "path/to/export/to"); // if you have samples

JavaScript/TypeScript Usage

Installation

For Node.js:

npm install @r2o3/rgskin-nodejs

For web projects:

<script src="https://unpkg.com/@r2o3/rgskin-browser@latest/rgskin.js"></script>

or

npm install @r2o3/rgskin-browser

then use as an ES module

API Reference

Initialization

As of now you can't parse/write using the original structures in JS/TS, will be supported in the near future.

[!IMPORTANT] wasm-bindgen-rayon uses SharedArrayBuffer for thread communication, which requires your page to be cross-origin isolated. Your server must send these headers:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

You can verify isolation is active with self.crossOriginIsolated in the browser console.


ES Modules (bundler or Node.js)

import init, { initThreadPool } from '@r2o3/rgskin';
// or if not installed via npm:
// import init, { initThreadPool } from './path/to/rgskin.js';

await init();
await initThreadPool(navigator.hardwareConcurrency);

Browser (<script type="module">)

import rgskin from './path/to/rgskin.js';

await rgskin.default();
await rgskin.initThreadPool(navigator.hardwareConcurrency);

CommonJS (Node.js or Legacy Environments)

CommonJS (require) is not natively supported because wasm-bindgen-rayon relies on ES Modules and top-level async initialization. However, you can load it using import():

async function loadWasm() {
    const wasm = await import('@r2o3/rgskin');
    
    await wasm.default();
    await wasm.initThreadPool(navigator.hardwareConcurrency);
    
    return wasm;
}

Importing/Loading Skins

For Node:

Recommended way of Loading a skin
const OsuSkin = rgskin.osuSkinFromDir("path/to/skin");
const FluXisSkin = rgskin.fluXisSkinFromDir("path/to/skin");
Manually loading a skin
// create a new texture store, this is where the actual textures will be stored
let textures = new rgskin.TextureStore();

// you can import textures from a directory like this:
textures = rgskin.allTexturesFromDir("path/to/skin");

// you can parse configs like this:
let raw_str = rgskin.iniStrFromDir("path/to/skin")
let skin_config = rgskin.OsuSkinIni.fromStr(raw_str);

let osuSKin = new rgskin.OsuSkin(skin_config, textures, null) // last parameter is the sound samples store, which you can import similarly to textures

For Browsers:

Unfortunately you can't automatically import everything using a single function. So you'll have to do a bit of work. Check FilesMap preparation

Recommended way of Loading a skin
const OsuSkin = rgskin.osuSkinFromFiles(filesMap);
const FluXisSkin = rgskin.fluXisSkinFromFiles(filesMap);
Manually loading a skin
// create a new texture store, this is where the actual textures will be stored
let textures = new rgskin.TextureStore();

// you can import textures from files like this:
// filesMap is a Map object with relative path -> Uint8Array pairs
textures = rgskin.allTexturesFromFiles(filesMap);

// you can parse configs like this:
// assuming you have the skin.ini file in your filesMap
let raw_str = filesMap.get("skin.ini"); // get the Uint8Array
let decoder = new TextDecoder();
let ini_string = decoder.decode(raw_str);
let skin_config = rgskin.OsuSkinIni.fromStr(ini_string);

let osuSkin = new rgskin.OsuSkin(skin_config, textures, null); // last parameter is the sound samples store, which you can import similarly to textures
Preparing the filesMap

The filesMap parameter is a Map object where:

  • Keys are relative paths (strings)
  • Values are file contents as Uint8Array

Example: Reading files from an HTML file input

const filesMap = new Map();

// assuming you have an <input type="file" multiple webkitdirectory> element
fileInput.addEventListener('change', async (event) => {
    const files = event.target.files;
    
    for (const file of files) {
        const arrayBuffer = await file.arrayBuffer();
        const uint8Array = new Uint8Array(arrayBuffer);
        const relativePath = file.webkitRelativePath || file.name;
        filesMap.set(relativePath, uint8Array);
    }
    
    // now you can import the skin
    const skin = rgskin.osuSkinFromFiles(filesMap);
});

Creating Skins

Check Rust's Creating Skins for more details

OsuSkin.fromGenericMania(&generic); 
OsuSkin.toGenericMania();
FluXisSkin.fromGenericMania(&generic); 
FluXisSkin.toGenericMania(fluxis_layout); // if you don't have a layout you can just not pass anything or null.

Exporting Skins

For Node:

Recommended way of exporting a skin
rgskin.osuSkinToDir(skin, "path/to/export/to");
rgskin.fluXisSkinToDir(skin, "path/to/export/to");
Manually exporting a skin
rgskin.iniToDir(skin.skin_ini, "path/to/export/to");
rgskin.texturesToDir(skin.textures, "path/to/export/to");
rgskin.samplesToDir(skin.samples, "path/to/export/to"); // if you have samples

For Browsers:

Recommended way of exporting a skin
// Returns a JavaScript Map object with relative path -> Uint8Array pairs
const filesMap = rgskin.osuSkinToFiles(skin);
const filesMap = rgskin.fluXisSkinToFiles(skin);
Manually exporting a skin
const iniString = rgskin.iniToString(skin.skin_ini);
const texturesMap = rgskin.texturesToFiles(skin.textures);
const samplesMap = rgskin.samplesToFiles(skin.samples);

// combine them into a single Map if needed
const filesMap = new Map([
    ['skin.ini', new TextEncoder().encode(iniString)],
    ...texturesMap,
    ...samplesMap
]);

Actually Exporting/Downloading the files will depend on your implementation.

Example using JSZip:

const filesMap = rgskin.osuSkinToFiles(skin);

const zip = new JSZip();
filesMap.forEach((data, path) => {
    zip.file(path, data);
});

const zipBlob = await zip.generateAsync({ type: 'blob' });
const link = document.createElement('a');
link.href = URL.createObjectURL(zipBlob);
link.download = 'skin.zip';
link.click();

Example Downloading each file indiviually:

const filesMap = rgskin.osuSkinToFiles(skin);

filesMap.forEach((data, path) => {
    const blob = new Blob([data]);
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = path;
    link.click();
});

Building

Rust Library

cargo build

WASM Bindings

  1. Install wasm-pack:
cargo install wasm-pack

[!IMPORTANT]
It's really recommended to have wasm-opt installed and added to path for the wasm build.

  1. Build the package:
npm run build # debug build
npm run build-release # release build
  1. This will build it for both node and browser and the output will be in dist-web and dist-node directory.

License

r2o3 uses the MIT License for all its sibiling projects. See LICENSE for more information