GR Mod
A small desktop tool for the Ricoh GR IV / GR IV HDF (firmware 1.11). Not a Ricoh product.
core/platform-independent TypeScript: firmware container codec and patcher with built-in self-check (src/fw), added aspect ratios (src/fw/aspect), preset conversion from .xmp Look profiles and .cube LUTs (src/color), exact-length baseline JPEG encoder (src/jpeg), start-up script generator and simulator (src/wallpaper), memory-card layout planning (src/card), and theEnginefacade (src/app).ui/React UI (Vite). Talks to the platform throughsrc/host.tsonly.shell/desktop shell in Go for Windows and macOS (a WebView2 window on Windows, a Chromium "app" window on macOS, plus a local HTTP API for drives and files, and for fetching the official firmware from Ricoh's site and updates of GR Mod itself). Seeshell/README.md.
npm install && npm -w ui run build && (cd shell && ./build.sh windows) → shell/dist/GRMod.exe; ./build.sh mac → shell/dist/GR Mod.app and shell/dist/GRMod-mac.zip (Intel Macs with macOS 10.13 or later, Apple silicon with macOS 11 or later), plus GRMod--mac.dmg when the disk image tools are installed.
On a Mac: unzip GRMod-mac.zip and move GR Mod.app to Applications. The app is signed ad hoc, not with an Apple Developer ID, so the first start is blocked: open System Settings › Privacy & Security and click "Open Anyway" (or run xattr -dr com.apple.quarantine "/Applications/GR Mod.app" once). The UI opens in a window of Chrome, Edge, Brave, Chromium or Vivaldi when one is installed, otherwise in the default browser; GR Mod quits by itself about 20 seconds after that window is closed. macOS asks once for access to removable volumes; allow it, or the card cannot be written.
Test: npm -w core test (the byte-identity tests need the official firmware in core/testdata/private/, which is not distributed), cd shell && go test ./....
An Android build would reuse core/ and ui/ unchanged and replace shell/ with a WebView wrapper implementing the same host interface (volumes via the Storage Access Framework).
No firmware file is included here. The user picks the official 1.11 file, or lets the program fetch it: the shell reads Ricoh's own download pages for the chosen model and downloads the current update from there (shell/internal/ricoh; Ricoh's page says that downloading means accepting its software licence agreement, and the dialog says so too and links to it). The before/after preview and the frame of an added aspect ratio are drawn on a sample picture, ui/src/assets/preview.jpg: DPReview's studio test scene, included with DPReview's permission (it is not covered by the licences of the code). The user can replace it in the UI, or by keeping a preview.jpg next to the program (on macOS: next to GR Mod.app; served by GET /api/sidecar/preview.jpg). The two card files that open the camera's factory menu (needed once, to switch Script on) are not stored here: core/src/fw/factory.ts reads their names, keyword and key out of the user's firmware. core/src/color/data.ts holds measurements (the camera's base tone curve and paired calibration colours). The power-off image method builds on the public research in radium-wang/ricoh-gr4-firmware-analysis-and-feature-expansion.
Added aspect ratios
core/src/fw/aspect adds aspect ratios to the camera next to the four factory ones (up to 8; e.g. 65:24 gives 6192×2272 JPEGs). It is a TypeScript port of the crop compiler of DoYitNow/gr-custom-tool and is checked against it: core/test/aspect-oracle.test.ts builds 439 configurations (every possible screen geometry, and random lists of up to 8 ratios) and compares them byte for byte with what the reference produced (tools/aspect/gen_oracle.py). tools/aspect/verify_file.py FILE --boot runs the firmware's own functions of a built file in an emulator. The RTOS and ICONBIN sections of such a file are longer than the official ones; the version stays 1.11.
One thing is added on top of the port (build revision 2, installPlaybackDecode in build.ts; the end of the RTOS section says GRMODAR2, files of GR Mod 0.2.x say GRMODAR1 and are still recognised). Magnifying a photo in playback decodes the whole JPEG into a buffer the firmware sizes for align16(width) × height rows while its decoder asks for align8(height); every factory size has a height that is a multiple of 8, the sizes of added ratios are multiples of 4, and for those the decode step gave up and the playback screen stayed in its magnifying state. The byte count of that allocation is now taken for the height rounded up to 8. tools/aspect/playback_check.py FILE runs the firmware's own image creation for every photo size on the official firmware and on FILE.
On a camera: a user reports that a 0.2.x build installs and shoots in the added ratios, and that magnifying such photos got stuck (the reason for revision 2). With revision 2 the user reports that magnifying 65:24 photos works. See third_party/gr-custom-tool/NOTICE.md for what is derived from the reference.
Soft focus
Two generations:
- GR Mod 0.4.x (
core/src/fw/clarity.ts) rewrote rows of the camera's clarity table (RTOS 0x53b4c860, 9 rows for clarity −4…+4 of 11 int16 gains in Q10, finest band first) so that clarity −2 / −3 / −4 became weak / medium / strong soft focus. Such files are still recognised and verified; the program no longer writes them. - GR Mod 0.5.2 (
core/src/fw/aspect/softfocus.ts) puts soft focus on the ADJ lever with four values (off / weak / medium / strong) and leaves the clarity table official. The ADJ controller is table-driven (21 records of member-function pointers at 0x53d9f714, addressed from 23 places). Soft focus takes over function 0x0A (Flash Exposure Comp.), which the GR IV never offers on the ADJ lever (not in the ADJ mode setting list, not in the fixed layout of any camera mode); its record is overwritten in place with Crop's, carrying four new handlers, so the table does not move. The ADJ mode setting list gets 0x0A after Image Control; its name is text 840 in all 21 languages and its icon is icon 720 (bounds never above what added ratios use). The value is kept in byte 0x90F of the user settings, alignment padding that the factory-defaults initialiser never writes; the structure is saved, loaded and copied to U1–U3 as a whole, so the value survives power-off. Whether a picture goes through the digital-filter step at all (0x537150fc) depends on Clarity being non-neutral for most image controls; while soft focus is on it counts as non-neutral. DigitalFilterProcess::SetEffectParameter passes clarity 5 / 6 / 7 while soft focus is on, and the clarity function takes rows 9–11 from a table of the three soft-focus rows. It acts on the recorded picture only: live view does not go through this step. The five ADJ slot getters read a function above 0x14 as off. Built files are revision 3 (GRMODAR3): the record at the end of RTOS may list no ratio and carries one more byte for these additions.tools/softfocus/check.py RTOS.bin official-RTOS.binruns the patched functions in an emulator. - GR Mod 0.5.0 / 0.5.1 used function 0x15 and copied the table to the appended area, moving 22 of its 23 references; the one left (0x531bd0f0, a check over the five slots when ADJ is pressed) read past the official table once 0x15 was on a slot, and the camera froze. A camera that still holds 0x15 in a slot has it read as off from 0.5.2 on; the official firmware would freeze, so take soft focus off the ADJ slots before going back to it.
clarity + 4 and multiplies each band of detail by its gain through the first diagonal entry of a CSC matrix (the other entries stay 1.0, so luma only, by inference). Lower gains on the fine and middle bands with the coarse ones kept give roughly g × picture + (1 − g) × strongly blurred picture, the usual digital soft focus. core/src/color/clarity.ts is a model of the pyramid (bilinear scaling assumed). It is linear and works after gamma on the 8-bit picture, so highlights do not bloom. Tried on a camera with 0.5.2: the three strengths are clearly distinct on the recorded picture.
Date imprint
core/src/fw/aspect/datestamp.ts hooks JpegEncodeMacroProcess::Execute (0x537034b4) and runs the position-independent C module tools/datestamp/datestamp.c (compiled by tools/datestamp/gen.py; GRMOD_ZIG=1 uses python3 -m ziglang cc, the compiler of the committed bytes) on every picture the JPEG encoder is given: main picture, 720x480 screennail, 160x120 thumbnail. It draws the date bottom right (pictures anywhere in 0x40000000..0xBFFFFFFF: with RAW+JPEG the scaled main picture of the smaller JPEG sizes is above 0xA0000000) in orange seven-segment digits, '26 10 08 or 2026.10.08 17:34, light only on black-and-white photos. The setting is byte 0x94B of the user settings (padding the factory-defaults initialiser never writes, like 0x90F for soft focus): bit 0 on, bit 1 the long style, anything above 3 is off.
core/src/fw/aspect/datestamp-menu.ts puts it in the camera menu: a line after Horizon Correction on the Shooting Assist page of the still menu, opening a submenu (On/Off, Style) built like D-Range Correction's, each line a list built like Horizon Correction's. The still menu already has the maximum of 38 lines, so two descriptors are appended, one without Red Filter (GR IV Monochrome only) and one without HDF (GR IV HDF only), and the menu builder picks the one for the camera's model; every model sees the official menu plus the new line. The submenu and list record tables are relocated with the new records (including the end pointer the list constructor scans to, 0x531d6dac). Texts 841..845, icons 721/722. tools/datestamp/menu_check.py BUILT-RTOS OFFICIAL-RTOS runs the patched firmware functions in an emulator (menu builder for all three models, controller factory, SetCurrentMenu, the handlers for every value of the byte, the controllers drawing the new records, the list constructor's table scan, texts and icons); tools/datestamp/render_check.py runs the module on synthetic pictures.
Monochrome looks
core/src/fw/aspect/monounlock.ts makes the six looks of the GR IV Monochrome selectable on the colour models, after lemonadesaltbagel/GR4-MonoUnlock (no code copied): the check at 0x533cc6e0 becomes cmp r1, #0x18 / bhi, and rows 3..8 of the look availability table at 0x53dbf36c (7 bytes per row, one column per camera model, column 1 = Monochrome) get every column except 1. The reference sets column 0 only (the standard GR IV); with every colour column the GR IV HDF has them too. 13 words in all.
Second matrix
Every imported preset (.cube / .xmp) is fitted as a colour matrix, three tone curves and a post-curve ("second") matrix (core/src/color/fit.ts): the plain fit first, then all three together on the same CIE76 objective. The camera applies the second matrix after the tone curves, together with the saturation setting; each slot gets its nine saturation matrices multiplied by it (Q9 rows of 512), so the saturation setting keeps working. On nine test presets the mean error drops on all of them (e.g. Provia 100F ΔE2000 1.32 → 0.43).
Added Image Control slots
core/src/fw/aspect/slots.ts adds up to six looks (styles 34..39) after Cinema (Green), each with its own matrix, curves, second matrix, ASCII name (text 846..) and icon (768..). A new look's records are complete copies of Cinema Yellow's with its own colour data and Standard's multi-axis blocks; the four style tables are copied with the new styles appended to each camera variant's row, and everything else that knows the style numbers (menu order and counts, names, icons, the ADJ list's own icon lookup, the shooting overlay, RAW development, look visibility, the live-view parameter generator) is extended. Tried on a GR IV HDF as test firmwares 022..034 (022 copied incomplete multi-axis records and the camera stopped on the first ADJ highlight of a new slot). Since 0.7.1 in-camera RAW development keeps an added look's parameters in Cinema Yellow's row of its own parameter table (with the look's own row, row 0, every parameter was "absent": the screen showed -4 and developing stopped the camera); as in the settings, that row has one owner per custom base and the module keeps the other looks' values, so every added look starts from its own saved values and keeps its own changes; and pictures name the added look in the maker note (ImageTone 0x8110, 0x8111, ...; before, Standard) with Cinema Yellow's parameters, which playback and RAW development read back: the playback information shows the slot's icon and parameters.
Halation (core/src/fw/aspect/halation.ts, module tools/halation/halation.c): an added slot can have the red glow of CineStill 800T (cinema film without its anti-halation layer) around its highlights; GR Mod only switches it on per slot, the look is fixed (HALATION, matched to CineStill 800T pictures of street lamps, platform tubes and LED boards). The camera's parameter screen shows Cinema's Toning (parameter 8, values 0..2, no effect on an added look) as "Halation" (Off / Weak / Strong) for the added looks: its name (text 852), list icon (780) and slider scale (781) are swapped on the shooting and RAW development screens. The development step that reads the frame's record (0x539113DC) leaves its style and Toning level in a word; the module runs at the start of JpegEncodeMacroProcess::Execute (before the date imprint) on every picture of the frame: light above the slot's own threshold (of the brighter of Y and red; halationLight: the level its look gives to the grey Standard renders at 235, and the range up to its white, so that a look whose white is lowered still spreads the same light), averaged down to at most 1024 px, spread twice (a narrow fringe and a wider glow, three box passes each, relative to the width), then a table turns the spread light into how far red, green and blue are lifted towards white (red first: red near the light becomes orange, white highlights stay white). GR Mod's preview runs the same integer computation (halateYcc), bit for bit; the module was checked against it in an emulator. Live view does not show it; RAW files are not changed.
Adjustable parameters: the firmware keeps them per factory style, and whatever reads them for the selected look takes the new styles as Cinema Yellow (18). Each new look still has its own values, kept per photo/movie set and across power-off: Cinema Yellow's fields hold the values of the look selected in a set, the other looks' values wait in a table of a native module (tools/slotparams/slot_params.c, pre-compiled per slot count by gen_slotparams.py into slotparams-native.ts) saved as its own record (HTData 12) after the settings. The module takes over the 13 getters and setters of Cinema Yellow's parameters and the 8 "style of set" setters, and chains to the settings load/save. The 88 change functors of those fields (which give the value to the camera model behind the parameter screen), the parameter screens' list choice (grain), the main menu's Image Control row and the digital-filter step (grain, Clarity) take the new looks as Cinema Yellow as well. New looks start at the neutral values, which the parameter screen also shows as their defaults. U1–U3 share the table; a settings reset does not reset it.
Updates
The program can update itself (shell/internal/update, UI in ui/src/updates.tsx). At start (switchable off in the dialog) and from "Check for updates" in the ? menu it reads update.json and update.json.sig from https://github.com/Maxwooow/Ricoh-GRMod/releases/latest/download/ (GRMOD_UPDATE_SOURCE replaces that address for tests). The manifest is accepted only if its Ed25519 signature verifies with the public key in shell/updates.go; the package (Windows: GRMod-x.y.z.exe, macOS: GRMod-x.y.z-mac.zip) only if its size and SHA-256 match the manifest. Windows renames the running GRMod.exe to GRMod.exe.old and puts the new one in its place; macOS moves the running GR Mod.app to .GR Mod.app.old and the unpacked new bundle into its place. The shell then closes its window, starts the new program with --after-update=PID, which waits for the old one to end and removes the .old copy. An app that macOS runs translocated (never moved out of the download folder) or a folder that cannot be written is not replaced; the dialog offers the download page instead.
Publishing a release: build, then
go run ./tools/updatesign -key PRIVATE.key -version X.Y.Z -notes-zh "…" -notes-en "…" \ -windows dist/GRMod.exe -mac dist/GRMod-mac.zip -out dist/release
(in shell/) and upload everything in dist/release (plus the .dmg for new users) to the GitHub release vX.Y.Z, which must be the latest release. The private key is not in the repository.
The UI
The Image Control page has one card of switches under the two slots: soft focus (ADJ lever), date imprint (with its menu) and monochrome looks (not shown for the Monochrome model). They are saved with the project (soft, dateStamp, monoUnlock in project.json) and passed to Engine.buildFirmware; the firmware copies page reads them back from a file through Engine.inspect.
Going back to the factory firmware ("Write factory firmware" on the Image Control page, or an official copy on the firmware copies page) asks first, in red, whether the camera's settings were reset (Setup › Reset › Reset to Factory Default): settings made with GR Mod's firmware (an added slot or ratio selected, soft focus on the ADJ lever, the date imprint) are values the official firmware does not know. Cancel has the focus; only the red button writes.
Combinations
Anything that appends code or icons (ratios, ADJ soft focus, date imprint) gives the grown layout: RTOS grows, and ICONBIN too when icons are added (a build that only appends code, like the date imprint of a test build with a fixed setting, leaves ICONBIN as it is; growPayload, grownRanges, Engine.inspect and the self-check accept either). The monochrome looks (core/src/fw/aspect/monounlock.ts, 13 words) on their own are written in place, like Image Control edits: official layout plus the checksum compensation word; Engine.inspect recognises them when all 13 words are there. With any of the other additions they go into the grown build and its feature byte (bit 2). core/test/combinations.test.ts builds every combination, checks the self-check and what inspect reads back, that soft focus alone is unchanged, and that all additions with the contents of the earlier test firmware (core/test/fw-helpers/test-firmware.ts) give test firmware 017 byte for byte. tools/testfw/build.ts builds such test files.
Licence
GR Mod is free software, licensed under the GNU General Public License version 2 only (SPDX: GPL-2.0-only); the full text is in LICENSE. A build given to anyone else has to come with its complete source under the same licence.
- The aspect-ratio compiler under
core/src/fw/aspect/andtools/aspect/is a port of DoYitNow/gr-custom-tool (GPL-2.0-only);third_party/gr-custom-tool/NOTICE.mdlists what is derived from it, and the C sources inthird_party/gr-custom-tool/native/keep their own copyright lines. - The monochrome looks follow the method of lemonadesaltbagel/GR4-MonoUnlock; no code of that project is included.
- Not covered by the licence: the sample picture
ui/src/assets/preview.jpg, and Ricoh's firmware, which is not part of this repository (the program reads it from the user or downloads it from Ricoh's site under Ricoh's own licence terms).