Skip to content

Commit 1465e60

Browse files
committed
[RAHasher] CLI improvements
1 parent 45a7468 commit 1465e60

5 files changed

Lines changed: 211 additions & 58 deletions

File tree

.github/workflows/c-cpp.yml

Lines changed: 32 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,17 @@ on:
1313
- develop
1414

1515
jobs:
16-
RALibretro-Win32:
17-
runs-on: windows-2019
16+
Prepare-release: # preventing concurrent releases creation
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Create release
20+
uses: softprops/action-gh-release@v1
21+
if: startsWith(github.ref, 'refs/tags/')
22+
with:
23+
draft: true
24+
25+
RAHasher-Win32:
26+
runs-on: windows-2025
1827
steps:
1928
- name: Checkout
2029
uses: actions/checkout@v2
@@ -25,10 +34,19 @@ jobs:
2534
- name: Install Windows SDK 8.1
2635
run: choco install windows-sdk-8.1
2736
- name: Build
28-
run: msbuild.exe RALibretro.sln -p:Configuration=Release -p:Platform=x86
37+
run: msbuild.exe RALibretro.sln -t:RAHasher -p:Configuration=Release -p:Platform=x86
38+
- name: Zip
39+
if: startsWith(github.ref, 'refs/tags/')
40+
run: 7z a -mx=9 RAHasher-x86-Windows-${{ github.ref_name }}.zip .\obj\Release\Win32\RAHasher.exe
41+
- name: Release
42+
uses: softprops/action-gh-release@v1
43+
if: startsWith(github.ref, 'refs/tags/')
44+
with:
45+
draft: true
46+
files: RAHasher*.zip
2947

30-
RALibretro-Win64:
31-
runs-on: windows-2019
48+
RAHasher-Win64:
49+
runs-on: windows-2025
3250
steps:
3351
- name: Checkout
3452
uses: actions/checkout@v2
@@ -39,39 +57,16 @@ jobs:
3957
- name: Install Windows SDK 8.1
4058
run: choco install windows-sdk-8.1
4159
- name: Build
42-
run: msbuild.exe RALibretro.sln -p:Configuration=Release -p:Platform=x64
43-
44-
RALibretro-Win32-cross-compile:
45-
runs-on: ubuntu-latest
46-
steps:
47-
- name: Checkout
48-
uses: actions/checkout@v2
49-
with:
50-
submodules: recursive
51-
- name: Install dependencies
52-
run: |
53-
sudo apt-get update
54-
sudo apt-get install gcc-multilib # bits/libc-header-start.h
55-
- name: Install MinGW
56-
uses: egor-tensin/setup-mingw@v2
57-
- name: Build
58-
run: make ARCH=x86
59-
60-
RALibretro-Win64-cross-compile:
61-
runs-on: ubuntu-latest
62-
steps:
63-
- name: Checkout
64-
uses: actions/checkout@v2
60+
run: msbuild.exe RALibretro.sln -t:RAHasher -p:Configuration=Release -p:Platform=x64
61+
- name: Zip
62+
if: startsWith(github.ref, 'refs/tags/')
63+
run: 7z a -mx=9 RAHasher-x64-Windows-${{ github.ref_name }}.zip .\obj\Release\x64\RAHasher.exe
64+
- name: Release
65+
uses: softprops/action-gh-release@v1
66+
if: startsWith(github.ref, 'refs/tags/')
6567
with:
66-
submodules: recursive
67-
- name: Install dependencies
68-
run: |
69-
sudo apt-get update
70-
sudo apt-get install gcc-multilib # bits/libc-header-start.h
71-
- name: Install MinGW
72-
uses: egor-tensin/setup-mingw@v2
73-
- name: Build
74-
run: make ARCH=x64
68+
draft: true
69+
files: RAHasher*.zip
7570

7671
RAHasher-linux-x86:
7772
runs-on: ubuntu-latest

Makefile.RAHasher

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ src/Git.cpp: etc/Git.cpp.template FORCE
6060

6161
zip: $(OUTDIR)/RAHasher$(EXE)
6262
rm -f $(OUTDIR)/RAHasher-*.zip RAHasher-*.zip
63-
zip -9 RAHasher-$(ARCH)-$(KERNEL)-`git describe --tags | sed s/\-.*//g | tr -d "\n"`.zip $(OUTDIR)/RAHasher$(EXE)
63+
zip -9 --junk-paths RAHasher-$(ARCH)-$(KERNEL)-`git describe --tags | sed s/\-.*//g | tr -d "\n"`.zip $(OUTDIR)/RAHasher$(EXE)
6464

6565
clean:
6666
rm -f $(OUTDIR)/RAHasher$(EXE) $(OBJS) $(OUTDIR)/RAHasher*.zip RAHasher*.zip

README.md

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
# RALibretro
1+
# RAHasher
22

3-
RALibretro is a multi-emulator that can be used to develop RetroAchievements and, of course, earn them.
3+
RAHasher is a CLI utility for verifying ROM checksums [with hashing methods used by RetroAchievements](https://docs.retroachievements.org/developer-docs/game-identification.html).
44

5-
The "multi-emulation" feature is only possible because it uses [libretro](https://github.com/libretro/) cores to do the actual emulation. What RALibretro does is to connect the emulation to the tools used to create RetroAchievements.
5+
_(It's a copy of the same utility provided by [RALibretro](https://github.com/RetroAchievements/RALibretro), but with a bit more convenient CLI.)_
66

7-
The integration with RetroAchievements.org site is done by the [RAIntegration](https://github.com/RetroAchievements/RAIntegration).
8-
9-
10-
## Building RALibretro with MSYS2/Makefile
7+
## Building RAHasher with MSYS2/Makefile
118

129
### Install MSYS2
1310

@@ -25,14 +22,14 @@ $ pacman -S make git zip mingw-w64-i686-gcc mingw-w64-i686-SDL2 mingw-w64-i686-g
2522
### Clone the repo
2623

2724
```
28-
$ git clone --recursive --depth 1 https://github.com/RetroAchievements/RALibretro.git
25+
$ git clone --recursive --depth 1 https://github.com/LeXofLeviafan/RAHasher.git
2926
```
3027

3128
### Build
3229

3330
```
34-
$ cd RALibretro
35-
$ make
31+
$ cd RAHasher
32+
$ make -f Makefile.RAHasher HAVE_CHD=1
3633
```
3734

3835
**NOTE**: use `make` for a release build or `make DEBUG=1` for a debug build. Don't forget to run `make clean` first if switching between a release build and a debug build.
@@ -42,21 +39,32 @@ $ make
4239
### Clone the repo
4340

4441
```
45-
> git clone --recursive --depth 1 https://github.com/RetroAchievements/RALibretro.git
42+
> git clone --recursive --depth 1 https://github.com/LeXofLeviafan/RAHasher.git
4643
```
4744

4845
### Build
4946

50-
Load `RALibretro.sln` in Visual Studio and build it.
47+
Load `RALibretro.sln` in Visual Studio and build it (specifically the `RAHasher` target).
5148

5249
## Command Line Arguments
5350

5451
Argument|Description
5552
-|-
56-
-c [--core]|the core's name, e.g. `--core picodrive_libretro`
57-
-s [--system]|the system id, see ConsoleID in [RAInterface/RA_Consoles.h](https://github.com/RetroAchievements/RAInterface/blob/master/RA_Consoles.h), e.g. `--system 1`
58-
-g [--game]|full path to the game's file, e.g. `--game "C:\ROMS\GEN\Demons Of Asteborg Demo.gen"`
53+
-v|(optional) enables verbose messages for debugging
54+
-s systempath|(optional) specifies where supplementary files are stored (typically a path to RetroArch/system)
55+
system|specifies the system key or id associated to the game (which hash algorithm to use)
56+
filepath|specifies the path to the game file (file may include wildcards, path may not)
57+
58+
I.e., in order to find out what checksum RA would assign to a NES game file, you can invoke the program like this:
59+
```bat
60+
RAHasher.exe NES "C:\ROMS\NES\Alwa's Awakening 8-bit edition.nes"
61+
```
62+
You can also pass `?` as the system key; in which case RAHasher will attempt to detect the system based on ROM file extension. (Note: equivalent "system ID" number is `91`.)
5963

60-
In order to run a game on startup, provide the core, system and game, e.g.:
64+
Additionally, you can pass multiple filenames and/or specify it a glob template (with `*`/`?` wildcards). Note that wildcards are only allowed in _filename itself_ (not in the path), and that system detection is not allowed in multiple files mode.
6165

62-
`RALibretro.exe --core picodrive_libretro --system 1 --game "C:\ROMS\GEN\Demons Of Asteborg Demo.gen"`
66+
Finally, the full list of valid console keys/IDs will be printed along with usage info if you run RAHasher without arguments:
67+
```bat
68+
RAHasher.exe
69+
```
70+
The list ordering matches RetroAchievements website menu. (Also, system keys match short names from [RetroAchievents game lists](https://retroachievements.org/games), sans whitespace.)

src/RAHasher.cpp

Lines changed: 141 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#ifdef _WIN32
1414
#define WIN32_LEAN_AND_MEAN
1515
#include <Windows.h>
16+
#ifndef strcasecmp
17+
#define strcasecmp _stricmp
18+
#endif
1619
#elif defined(__unix__)
1720
#include <glob.h>
1821
#include <sys/stat.h>
@@ -28,16 +31,150 @@ void rc_hash_init_chd_cdreader(); /* in HashCHD.cpp */
2831

2932
void initHash3DS(const std::string& systemDir); /* in Hash3DS.cpp */
3033

34+
static const char* _NINTENDO = "Nintendo";
35+
static const char* _SONY = "Sony";
36+
static const char* _ATARI = "Atari";
37+
static const char* _SEGA = "Sega";
38+
static const char* _NEC = "NEC";
39+
static const char* _SNK = "SNK";
40+
static const char* _OTHERS = "Others";
41+
42+
typedef struct console_t {
43+
const uint32_t id;
44+
const char* key;
45+
const char* group;
46+
const char* name;
47+
} console_t;
48+
49+
static const console_t CONSOLES[] = {
50+
{RC_CONSOLE_GAMEBOY, "GB", _NINTENDO, "Game Boy"},
51+
{RC_CONSOLE_GAMEBOY_ADVANCE, "GBA", _NINTENDO, "Game Boy Advance"},
52+
{RC_CONSOLE_GAMEBOY_COLOR, "GBC", _NINTENDO, "Game Boy Color"},
53+
{RC_CONSOLE_NINTENDO, "NES", _NINTENDO, "NES/Famicom"},
54+
{RC_CONSOLE_SUPER_NINTENDO, "SNES", _NINTENDO, "SNES/Super Famicom"},
55+
{RC_CONSOLE_NINTENDO_64, "N64", _NINTENDO, "Nintendo 64"},
56+
{RC_CONSOLE_GAMECUBE, "GC", _NINTENDO, "GameCube"},
57+
{RC_CONSOLE_NINTENDO_DS, "DS", _NINTENDO, "Nintendo DS"},
58+
{RC_CONSOLE_NINTENDO_DSI, "DSi", _NINTENDO, "Nintendo DSi"},
59+
{RC_CONSOLE_POKEMON_MINI, "MINI", _NINTENDO, "Pokemon Mini"},
60+
{RC_CONSOLE_VIRTUAL_BOY, "VB", _NINTENDO, "Virtual Boy"},
61+
{RC_CONSOLE_GAME_AND_WATCH, "G&W", NULL, "Game & Watch"},
62+
{RC_CONSOLE_NINTENDO_3DS, "3DS", NULL, "Nintendo 3DS"},
63+
{RC_CONSOLE_WII, "Wii", NULL, "Nintendo Wii"},
64+
{RC_CONSOLE_WII_U, "WiiU", NULL, "Nintendo Wii U"},
65+
66+
{RC_CONSOLE_PLAYSTATION, "PS1", _SONY, "PlayStation"},
67+
{RC_CONSOLE_PLAYSTATION_2, "PS2", _SONY, "PlayStation 2"},
68+
{RC_CONSOLE_PSP, "PSP", _SONY, "PlayStation Portable"},
69+
70+
{RC_CONSOLE_ATARI_2600, "2600", _ATARI, "Atari 2600"},
71+
{RC_CONSOLE_ATARI_7800, "7800", _ATARI, "Atari 7800"},
72+
{RC_CONSOLE_ATARI_JAGUAR, "JAG", _ATARI, "Atari Jaguar"},
73+
{RC_CONSOLE_ATARI_JAGUAR_CD, "JCD", _ATARI, "Atari Jaguar CD"},
74+
{RC_CONSOLE_ATARI_LYNX, "Lynx", _ATARI, "Atari Lynx"},
75+
{RC_CONSOLE_ATARI_5200, "5200", NULL, "Atari 5200"},
76+
{RC_CONSOLE_ATARI_ST, "AST", NULL, "Atari ST"},
77+
78+
{RC_CONSOLE_SG1000, "SG1K", _SEGA, "SG-1000"},
79+
{RC_CONSOLE_MASTER_SYSTEM, "SMS", _SEGA, "Master System"},
80+
{RC_CONSOLE_GAME_GEAR, "GG", _SEGA, "Game Gear"},
81+
{RC_CONSOLE_MEGA_DRIVE, "MD", _SEGA, "Genesis/Mega Drive"},
82+
{RC_CONSOLE_SEGA_CD, "SCD", _SEGA, "Sega CD"},
83+
{RC_CONSOLE_SEGA_32X, "32X", _SEGA, "32X"},
84+
{RC_CONSOLE_SATURN, "SAT", _SEGA, "Saturn"},
85+
{RC_CONSOLE_DREAMCAST, "DC", _SEGA, "Dreamcast"},
86+
{RC_CONSOLE_PICO, "Pico", NULL, "Sega Pico"},
87+
88+
{RC_CONSOLE_PC_ENGINE, "PCE", _NEC, "PC Engine/TurboGrafx-16"},
89+
{RC_CONSOLE_PC_ENGINE_CD, "PCCD", _NEC, "PC Engine CD/TurboGrafx-CD"},
90+
{RC_CONSOLE_PC8800, "80/88", _NEC, "PC-8000/8800"},
91+
{RC_CONSOLE_PCFX, "PC-FX", _NEC, "PC-FX"},
92+
{RC_CONSOLE_PC6000, "PC-6000", NULL, "PC-6000"},
93+
{RC_CONSOLE_PC9800, "9800", NULL, "PC-9800"},
94+
95+
{RC_CONSOLE_NEO_GEO_CD, "NGCD", _SNK, "Neo Geo CD"},
96+
{RC_CONSOLE_NEOGEO_POCKET, "NGP", _SNK, "Neo Geo Pocket"},
97+
98+
{RC_CONSOLE_3DO, "3DO", _OTHERS, "3DO Interactive Multiplayer"},
99+
{RC_CONSOLE_AMSTRAD_PC, "CPC", _OTHERS, "Amstrad CPC"},
100+
{RC_CONSOLE_APPLE_II, "A2", _OTHERS, "Apple II"},
101+
{RC_CONSOLE_ARCADE, "ARC", _OTHERS, "Arcade"},
102+
{RC_CONSOLE_ARCADIA_2001, "A2001", _OTHERS, "Arcadia 2001"},
103+
{RC_CONSOLE_ARDUBOY, "ARD", _OTHERS, "Arduboy"},
104+
{RC_CONSOLE_COLECOVISION, "CV", _OTHERS, "ColecoVision"},
105+
{RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER, "ELEK", _OTHERS, "Elektor TV Games Computer"},
106+
{RC_CONSOLE_FAIRCHILD_CHANNEL_F, "CHF", _OTHERS, "Fairchild Channel F"},
107+
{RC_CONSOLE_INTELLIVISION, "INTV", _OTHERS, "Intellivision"},
108+
{RC_CONSOLE_INTERTON_VC_4000, "VC4000", _OTHERS, "Interton VC 4000"},
109+
{RC_CONSOLE_MAGNAVOX_ODYSSEY2, "MO2", _OTHERS, "Magnavox Odyssey 2"},
110+
{RC_CONSOLE_MEGADUCK, "DUCK", _OTHERS, "Mega Duck"},
111+
{RC_CONSOLE_MSX, "MSX", _OTHERS, "MSX"},
112+
//{RC_CONSOLE_STANDALONE, "EXE", _OTHERS, "Standalone"}, /* >90, not usable */
113+
{RC_CONSOLE_UZEBOX, "UZE", _OTHERS, "Uzebox"},
114+
{RC_CONSOLE_VECTREX, "VECT", _OTHERS, "Vectrex"},
115+
{RC_CONSOLE_SUPERVISION, "WSV", _OTHERS, "Watara Supervision"},
116+
{RC_CONSOLE_WASM4, "WASM4", _OTHERS, "WASM-4"},
117+
{RC_CONSOLE_WONDERSWAN, "WS", _OTHERS, "WonderSwan"},
118+
{RC_CONSOLE_AMIGA, "Amiga", NULL, "Amiga"},
119+
{RC_CONSOLE_CASSETTEVISION, "ECV", NULL, "Cassette Vision"},
120+
{RC_CONSOLE_SUPER_CASSETTEVISION, "ESCV", NULL, "Super Cassette Vision"},
121+
{RC_CONSOLE_COMMODORE_64, "C64", NULL, "Commodore 64"},
122+
{RC_CONSOLE_FM_TOWNS, "FMTowns", NULL, "FM Towns"},
123+
{RC_CONSOLE_NOKIA_NGAGE, "N-Gage", NULL, "Nokia N-Gage"},
124+
{RC_CONSOLE_ORIC, "Oric", NULL, "Oric"},
125+
{RC_CONSOLE_CDI, "CD-i", NULL, "Philips CD-i"},
126+
{RC_CONSOLE_SHARPX1, "X1", NULL, "Sharp X1"},
127+
{RC_CONSOLE_X68K, "X68K", NULL, "Sharp X68000"},
128+
{RC_CONSOLE_THOMSONTO8, "TO8", NULL, "Thomson TO8"},
129+
{RC_CONSOLE_TI83, "TI83", NULL, "TI-83"},
130+
{RC_CONSOLE_TIC80, "TIC-80", NULL, "TIC-80"},
131+
{RC_CONSOLE_VIC20, "VIC-20", NULL, "VIC-20"},
132+
{RC_CONSOLE_ZEEBO, "Zeebo", NULL, "Zeebo"},
133+
{RC_CONSOLE_ZX81, "ZX81", NULL, "ZX81"},
134+
{RC_CONSOLE_ZX_SPECTRUM, "ZXS", NULL, "ZX Spectrum"},
135+
{RC_CONSOLE_MS_DOS, "DOS", NULL, "DOS"},
136+
{RC_CONSOLE_XBOX, "Xbox", NULL, "Xbox"},
137+
};
138+
static const long CONSOLES_NUM = sizeof(CONSOLES) / sizeof(console_t);
139+
140+
static int32_t find_console_id (const char* key) {
141+
for (int i = 0; i < CONSOLES_NUM; i++)
142+
if ((CONSOLES[i].group != NULL) && (strcasecmp(key, CONSOLES[i].key) == 0))
143+
return CONSOLES[i].id;
144+
return atoi(key); // if no matching key found, falling back to original behaviour
145+
}
146+
31147
static void usage(const char* appname)
32148
{
33149
printf("RAHasher %s\n====================\n", git::getReleaseVersion());
34150

35-
printf("Usage: %s [-v] [-s systempath] systemid filepath\n", util::fileName(appname).c_str());
151+
printf("Usage: %s [-v] [-s systempath] system filepath...\n", util::fileName(appname).c_str());
36152
printf("\n");
37153
printf(" -v (optional) enables verbose messages for debugging\n");
38154
printf(" -s systempath (optional) specifies where supplementary files are stored (typically a path to RetroArch/system)\n");
39-
printf(" systemid specifies the system id associated to the game (which hash algorithm to use)\n");
155+
printf(" system specifies the system key or id associated to the game (which hash algorithm to use)\n");
40156
printf(" filepath specifies the path to the game file (file may include wildcards, path may not)\n");
157+
158+
printf("\n");
159+
printf(" ID Key Group Name\n");
160+
printf(" -- ------- -------- ---------------------------\n");
161+
const char* last_group = NULL;
162+
for (int i = 0; i < CONSOLES_NUM; i++) {
163+
#ifdef HIDE_UNSUPPORTED_CONSOLES
164+
if (CONSOLES[i].group == NULL)
165+
continue;
166+
#endif
167+
if ((CONSOLES[i].group != NULL) && (last_group != NULL) && (last_group != CONSOLES[i].group))
168+
printf("\n");
169+
printf(" %2d %-7s %-8s %s\n", CONSOLES[i].id, CONSOLES[i].key,
170+
(CONSOLES[i].group == NULL ? "" : CONSOLES[i].group), CONSOLES[i].name);
171+
if (CONSOLES[i].group != NULL)
172+
last_group = CONSOLES[i].group;
173+
}
174+
printf("\nFor a single file, console ID can be specified as '?' (to attempt guessing by extension)\n");
175+
#ifndef HIDE_UNSUPPORTED_CONSOLES
176+
printf("Warning: consoles with a 'blank' group are currently not supported by RA!\n");
177+
#endif
41178
}
42179

43180
class StdErrLogger : public Logger
@@ -267,7 +404,8 @@ int main(int argc, char* argv[])
267404
return 1;
268405
}
269406

270-
consoleId = atoi(argv[argi++]);
407+
const char* consoleKey = argv[argi++];
408+
consoleId = (strcmp(consoleKey, "?") == 0 ? 1+RC_CONSOLE_MAX : find_console_id(consoleKey));
271409
if (consoleId == 0)
272410
{
273411
usage(argv[0]);

src/RAHasher.vcxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@
6363
<SubSystem>Console</SubSystem>
6464
<GenerateDebugInformation>true</GenerateDebugInformation>
6565
</Link>
66+
<PreBuildEvent>
67+
<Command>$(ProjectDir)..\etc\MakeGitCpp.bat</Command>
68+
</PreBuildEvent>
6669
<CustomBuildStep>
6770
<Command>copy /b/y $(TargetPath) $(SolutionDir)\bin</Command>
6871
</CustomBuildStep>
@@ -88,6 +91,9 @@
8891
<SubSystem>Console</SubSystem>
8992
<GenerateDebugInformation>true</GenerateDebugInformation>
9093
</Link>
94+
<PreBuildEvent>
95+
<Command>$(ProjectDir)..\etc\MakeGitCpp.bat</Command>
96+
</PreBuildEvent>
9197
<CustomBuildStep>
9298
<Command>copy /b/y $(TargetPath) $(SolutionDir)\bin64</Command>
9399
</CustomBuildStep>
@@ -118,6 +124,9 @@
118124
<OptimizeReferences>true</OptimizeReferences>
119125
<GenerateDebugInformation>true</GenerateDebugInformation>
120126
</Link>
127+
<PreBuildEvent>
128+
<Command>$(ProjectDir)..\etc\MakeGitCpp.bat</Command>
129+
</PreBuildEvent>
121130
<CustomBuildStep>
122131
<Command>copy /b/y $(TargetPath) $(SolutionDir)\bin</Command>
123132
</CustomBuildStep>
@@ -147,6 +156,9 @@
147156
<OptimizeReferences>true</OptimizeReferences>
148157
<GenerateDebugInformation>true</GenerateDebugInformation>
149158
</Link>
159+
<PreBuildEvent>
160+
<Command>$(ProjectDir)..\etc\MakeGitCpp.bat</Command>
161+
</PreBuildEvent>
150162
<CustomBuildStep>
151163
<Command>copy /b/y $(TargetPath) $(SolutionDir)\bin64</Command>
152164
</CustomBuildStep>

0 commit comments

Comments
 (0)