node-rcheevos 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/bin/rhash.js +65 -0
- package/binding.gyp +39 -0
- package/lib/index.d.ts +23 -0
- package/lib/index.js +5 -0
- package/package.json +50 -0
- package/prebuilds/darwin-arm64/node-rcheevos.node +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 JZimz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# node-rcheevos
|
|
2
|
+
|
|
3
|
+
> **⚠️ Work in Progress** - This works and I'm using it in ROMie, but it's early. Only macOS ARM64 binaries are pre-built right now. Other platforms will compile from source (need build tools) until I set up CI for cross-platform builds.
|
|
4
|
+
|
|
5
|
+
Generate RetroAchievements hashes for ROMs in Node.js. Uses the official [rcheevos](https://github.com/RetroAchievements/rcheevos) C library, so you get the same hashes as RetroArch and other emulators.
|
|
6
|
+
|
|
7
|
+
Built this for better RetroAchievements support in [ROMie](https://github.com/JZimz/romie), but it works anywhere you need to hash ROMs in Node.
|
|
8
|
+
|
|
9
|
+
## Why this exists
|
|
10
|
+
|
|
11
|
+
If you've tried adding RetroAchievements to an Electron app, you've probably hit this: WASM libraries don't work in the main process. And reimplementing the hashing logic in JavaScript is a recipe for subtle bugs when the algorithm changes.
|
|
12
|
+
|
|
13
|
+
This wraps the official C library as a native Node addon, so it just works. Same hashing logic as the source of truth, supports all the systems RetroAchievements does (Game Boy, NES, SNES, PlayStation, PSP, you name it).
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install node-rcheevos
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Comes with pre-built binaries for macOS, Windows, and Linux (both x64 and ARM64). If you're on something else, it'll build from source—just need the usual build tools (node-gyp stuff).
|
|
22
|
+
|
|
23
|
+
## How to use it
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
const { rhash } = require('node-rcheevos');
|
|
27
|
+
|
|
28
|
+
const md5 = rhash(4, '/path/to/game.gb'); // Game Boy
|
|
29
|
+
console.log(md5); // "a1b2c3d4e5f6..."
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
TypeScript works too:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { rhash } from 'node-rcheevos';
|
|
36
|
+
|
|
37
|
+
const md5 = rhash(41, '/path/to/game.iso'); // PSP
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
CLI if you want to test it quick:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx rhash -c 4 /path/to/game.gb
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API
|
|
47
|
+
|
|
48
|
+
Just one function: `rhash(consoleId, path, buffer?)`
|
|
49
|
+
|
|
50
|
+
Pass it a RetroAchievements console ID (see table below) and the path to your ROM. Returns the MD5 hash as a string. Throws an error if the file doesn't exist or can't be hashed.
|
|
51
|
+
|
|
52
|
+
## Console IDs
|
|
53
|
+
|
|
54
|
+
Here are the common ones (full list at [docs.retroachievements.org](https://docs.retroachievements.org/Console-IDs/)):
|
|
55
|
+
|
|
56
|
+
| ID | System |
|
|
57
|
+
|----|--------|
|
|
58
|
+
| 1 | Genesis/Mega Drive |
|
|
59
|
+
| 2 | Nintendo 64 |
|
|
60
|
+
| 3 | Super Nintendo |
|
|
61
|
+
| 4 | Game Boy |
|
|
62
|
+
| 5 | Game Boy Advance |
|
|
63
|
+
| 6 | Game Boy Color |
|
|
64
|
+
| 7 | NES/Famicom |
|
|
65
|
+
| 11 | Game Gear |
|
|
66
|
+
| 12 | PlayStation |
|
|
67
|
+
| 27 | PlayStation Portable |
|
|
68
|
+
| 38 | Nintendo 3DS |
|
|
69
|
+
| 39 | Dreamcast |
|
|
70
|
+
|
|
71
|
+
## Building from source
|
|
72
|
+
|
|
73
|
+
If you want to hack on it:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
git clone --recursive https://github.com/jzimz/node-rcheevos.git
|
|
77
|
+
cd node-rcheevos
|
|
78
|
+
npm install
|
|
79
|
+
npm run build
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The `--recursive` flag pulls in the rcheevos submodule. Without it, you won't have the C library to build against.
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT. The rcheevos library this wraps is also MIT.
|
package/bin/rhash.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { rhash } = require('../lib/index.js');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function printUsage() {
|
|
7
|
+
console.log('Usage: rhash -c <consoleId> <path>');
|
|
8
|
+
console.log('');
|
|
9
|
+
console.log('Options:');
|
|
10
|
+
console.log(' -c, --console <id> RetroAchievements console ID');
|
|
11
|
+
console.log('');
|
|
12
|
+
console.log('Examples:');
|
|
13
|
+
console.log(' rhash -c 4 game.gb # Game Boy');
|
|
14
|
+
console.log(' rhash --console 12 game.bin # PlayStation');
|
|
15
|
+
console.log('');
|
|
16
|
+
console.log('Common console IDs:');
|
|
17
|
+
console.log(' 1 - Sega Genesis/Mega Drive');
|
|
18
|
+
console.log(' 2 - Nintendo 64');
|
|
19
|
+
console.log(' 3 - Super Nintendo');
|
|
20
|
+
console.log(' 4 - Game Boy');
|
|
21
|
+
console.log(' 5 - Game Boy Advance');
|
|
22
|
+
console.log(' 7 - NES/Famicom');
|
|
23
|
+
console.log(' 12 - PlayStation');
|
|
24
|
+
console.log(' 27 - PlayStation Portable');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
|
|
30
|
+
if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
|
|
31
|
+
printUsage();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let consoleId;
|
|
35
|
+
let romPath;
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < args.length; i++) {
|
|
38
|
+
if (args[i] === '-c' || args[i] === '--console') {
|
|
39
|
+
consoleId = parseInt(args[i + 1], 10);
|
|
40
|
+
i++;
|
|
41
|
+
} else if (!romPath) {
|
|
42
|
+
romPath = args[i];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!consoleId || !romPath) {
|
|
47
|
+
console.error('Error: Both console ID and ROM path are required\n');
|
|
48
|
+
printUsage();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (isNaN(consoleId)) {
|
|
52
|
+
console.error('Error: Console ID must be a number\n');
|
|
53
|
+
printUsage();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const absolutePath = path.resolve(romPath);
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const result = rhash(consoleId, absolutePath);
|
|
60
|
+
console.log(result);
|
|
61
|
+
process.exit(0);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(`Error: ${error.message}`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
package/binding.gyp
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"targets": [
|
|
3
|
+
{
|
|
4
|
+
"target_name": "rcheevos",
|
|
5
|
+
"sources": [
|
|
6
|
+
"src/addon.cpp",
|
|
7
|
+
"src/rcheevos/src/rhash/aes.c",
|
|
8
|
+
"src/rcheevos/src/rhash/cdreader.c",
|
|
9
|
+
"src/rcheevos/src/rhash/hash.c",
|
|
10
|
+
"src/rcheevos/src/rhash/hash_disc.c",
|
|
11
|
+
"src/rcheevos/src/rhash/hash_encrypted.c",
|
|
12
|
+
"src/rcheevos/src/rhash/hash_rom.c",
|
|
13
|
+
"src/rcheevos/src/rhash/hash_zip.c",
|
|
14
|
+
"src/rcheevos/src/rhash/md5.c",
|
|
15
|
+
"src/rcheevos/src/rc_util.c"
|
|
16
|
+
],
|
|
17
|
+
"include_dirs": [
|
|
18
|
+
"<!@(node -p \"require('node-addon-api').include\")",
|
|
19
|
+
"src/rcheevos/include",
|
|
20
|
+
"src/rcheevos/src"
|
|
21
|
+
],
|
|
22
|
+
"defines": [
|
|
23
|
+
"NAPI_DISABLE_CPP_EXCEPTIONS"
|
|
24
|
+
],
|
|
25
|
+
"cflags!": [ "-fno-exceptions" ],
|
|
26
|
+
"cflags_cc!": [ "-fno-exceptions" ],
|
|
27
|
+
"xcode_settings": {
|
|
28
|
+
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
|
|
29
|
+
"CLANG_CXX_LIBRARY": "libc++",
|
|
30
|
+
"MACOSX_DEPLOYMENT_TARGET": "10.15"
|
|
31
|
+
},
|
|
32
|
+
"msvs_settings": {
|
|
33
|
+
"VCCLCompilerTool": {
|
|
34
|
+
"ExceptionHandling": 1
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a hash for a ROM file using the RetroAchievements algorithm.
|
|
3
|
+
*
|
|
4
|
+
* @param consoleId - The RetroAchievements console ID (e.g., 41 for PSP)
|
|
5
|
+
* @param path - Path to the ROM file (required even when using buffer, for file extension detection)
|
|
6
|
+
* @param buffer - Optional buffer containing ROM data. If provided, hashes from memory instead of reading file
|
|
7
|
+
* @returns MD5 hash as a 32-character hex string
|
|
8
|
+
* @throws Error if the file/buffer cannot be hashed
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { rhash } from 'node-rcheevos';
|
|
13
|
+
* import { readFileSync } from 'fs';
|
|
14
|
+
*
|
|
15
|
+
* // Hash from file
|
|
16
|
+
* const md5 = rhash(41, '/path/to/game.iso');
|
|
17
|
+
*
|
|
18
|
+
* // Hash from buffer (useful if already in memory)
|
|
19
|
+
* const buffer = readFileSync('/path/to/game.iso');
|
|
20
|
+
* const md5 = rhash(41, '/path/to/game.iso', buffer);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function rhash(consoleId: number, path: string, buffer?: Buffer): string;
|
package/lib/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-rcheevos",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Node.js native bindings for RetroAchievements rcheevos library (hash generation)",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"rhash": "bin/rhash.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"lib/**/*",
|
|
12
|
+
"bin/**/*",
|
|
13
|
+
"prebuilds/**/*",
|
|
14
|
+
"binding.gyp",
|
|
15
|
+
"!**/*.test.*"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"install": "node-gyp-build",
|
|
19
|
+
"build": "node-gyp rebuild",
|
|
20
|
+
"prebuild": "prebuildify --napi --strip",
|
|
21
|
+
"test": "node --test"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"retroachievements",
|
|
25
|
+
"rcheevos",
|
|
26
|
+
"hash",
|
|
27
|
+
"rom",
|
|
28
|
+
"native",
|
|
29
|
+
"addon"
|
|
30
|
+
],
|
|
31
|
+
"author": {
|
|
32
|
+
"name": "JZimz",
|
|
33
|
+
"email": "jzimz.tv@gmail.com"
|
|
34
|
+
},
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"node-addon-api": "^8.0.0",
|
|
38
|
+
"node-gyp-build": "^4.8.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^25.0.3",
|
|
42
|
+
"node-gyp": "^10.0.0",
|
|
43
|
+
"prebuildify": "^6.0.0"
|
|
44
|
+
},
|
|
45
|
+
"gypfile": true,
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/jzimz/node-rcheevos.git"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
Binary file
|