diff --git a/README.md b/README.md
index dbea943..650ec1a 100644
--- a/README.md
+++ b/README.md
@@ -2,11 +2,12 @@
## Status: **WORKING** ✓
-Enrollment and verify confirmed working on real hardware (2026-03-10).
+Enrollment and verify confirmed working on real hardware (2026-05-26).
## Hardware
-[TNP Nano USB Fingerprint Reader](https://www.amazon.com/dp/B07DW62XS7) (Amazon)
+[TNP Nano USB Fingerprint Reader](https://www.amazon.com/dp/B07DW62XS7) (Amazon US)
+[TNP Nano USB Fingerprint Reader](https://www.amazon.co.uk/TNP-Fingerprint-Reader-Windows-Hello/dp/B07DW62XS7/) (Amazon UK)
@@ -45,15 +46,70 @@ See [Reverse Engineering](docs/reverse-engineering.md) for how to reproduce.
- The required number of GenChar samples for RegModel is `DAT_180032020 / 3`clamped to \[3, 6\]. For this device the value is 6.
- Extra GET_IMAGE calls between GenChars corrupt the device's char buffer state. The `waiting_for_lift` approach must minimize GET_IMAGE polling between captures.
-## Build
+## Instructions
+1. Plug in device & update your machine
```bash
-./build.sh
+sudo apt update
```
-Copies `src/microarray.c` into the libfprint source tree (`$LIBFPRINT_SRC`, defaults to `~/libfprint`), builds with ninja, and installs the shared library.
+2. Install all required tools/libraries
+
+```bash
+sudo apt install -y git build-essential meson ninja-build \
+ pkg-config libglib2.0-dev libgusb-dev libgudev-1.0-dev \
+ libpixman-1-dev libnss3-dev libssl-dev libcairo2-dev \
+ libgirepository1.0-dev gtk-doc-tools \
+ fprintd libpam-fprintd
+ ```
+
+3. Clone the official libfprint source code to ~/libfprint directory
+
+```bash
+rm -rf ~/libfprint
+git clone https://gitlab.freedesktop.org/libfprint/libfprint.git ~/libfprint
+```
+
+4. Manually create the microarray directory
+
+```bash
+cd ~/libfprint
+mkdir -p libfprint/drivers/microarray
+meson setup build
+```
+
+5. Copy this repo
+
+```bash
+rm -rf ~/libfprint-microarray/
+git clone https://github.com/jadegamesuk/libfprint-microarray.git ~/libfprint-microarray
+```
+
+6. Make changes to ~/libfprint
+
+```bash
+# copy fixed meson.build file over to other library
+cp ~/libfprint-microarray/meson.build ~/libfprint/libfprint/meson.build
+cp ~/libfprint-microarray/src/microarray.c ~/libfprint/libfprint/drivers/microarray/microarray.c
+
+cd ~/libfprint/build
+meson configure -Ddrivers=all
+ninja
+sudo cp libfprint/libfprint-2.so.2.0.0 /usr/lib/x86_64-linux-gnu/libfprint-2.so.2
+sudo cp libfprint/libfprint-2.so.2.0.0 /usr/lib/libfprint-2.so.2
+sudo ldconfig
+sudo udevadm control --reload-rules && sudo udevadm trigger
+#sudo systemctl enable --now fprintd
+```
## Testing
+```bash
+# Restart daemon
+sudo systemctl stop fprintd
+sudo G_MESSAGES_DEBUG=all /usr/libexec/fprintd -t 2>&1
+```
+
+### Open a new Terminal Window for the following
```bash
# Enroll right index finger (6 press/lift cycles)
@@ -62,10 +118,23 @@ fprintd-enroll -f right-index-finger
# Verify
fprintd-verify -f right-index-finger
-# Debug logging
-sudo G_MESSAGES_DEBUG=all /usr/libexec/fprintd -t 2>&1
+# Options for [finger]:
+left-thumb
+left-index-finger
+left-middle-finger
+left-ring-finger
+left-little-finger
+right-thumb
+right-index-finger
+right-middle-finger
+right-ring-finger
+right-little-finger
```
+**If there is a future update of libfprint, re-running Step (6) above should make everything work again.**
+
+
+
## License
[MIT](LICENSE)
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..32ed7dd
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,425 @@
+spi_sources = []
+spi_headers = []
+
+if enabled_spi_drivers.length() > 0
+ spi_headers = ['fpi-spi-transfer.h']
+ spi_sources = ['fpi-spi-transfer.c']
+endif
+
+libfprint_sources = [
+ 'fp-context.c',
+ 'fp-device.c',
+ 'fp-image.c',
+ 'fp-print.c',
+ 'fp-image-device.c',
+]
+
+libfprint_private_sources = [
+ 'fpi-assembling.c',
+ 'fpi-byte-reader.c',
+ 'fpi-byte-writer.c',
+ 'fpi-device.c',
+ 'fpi-image-device.c',
+ 'fpi-image.c',
+ 'fpi-print.c',
+ 'fpi-ssm.c',
+ 'fpi-usb-transfer.c',
+] + spi_sources
+
+libfprint_public_headers = [
+ 'fp-context.h',
+ 'fp-device.h',
+ 'fp-image-device.h',
+ 'fp-image.h',
+ 'fp-print.h',
+]
+
+libfprint_private_headers = [
+ 'fpi-assembling.h',
+ 'fpi-byte-reader.h',
+ 'fpi-byte-utils.h',
+ 'fpi-byte-writer.h',
+ 'fpi-compat.h',
+ 'fpi-context.h',
+ 'fpi-device.h',
+ 'fpi-image-device.h',
+ 'fpi-image.h',
+ 'fpi-log.h',
+ 'fpi-minutiae.h',
+ 'fpi-print.h',
+ 'fpi-usb-transfer.h',
+ 'fpi-ssm.h',
+] + spi_headers
+
+nbis_sources = [
+ 'nbis/bozorth3/bozorth3.c',
+ 'nbis/bozorth3/bz_alloc.c',
+ 'nbis/bozorth3/bz_drvrs.c',
+ 'nbis/bozorth3/bz_gbls.c',
+ 'nbis/bozorth3/bz_io.c',
+ 'nbis/bozorth3/bz_sort.c',
+ 'nbis/mindtct/binar.c',
+ 'nbis/mindtct/block.c',
+ 'nbis/mindtct/chaincod.c',
+ 'nbis/mindtct/contour.c',
+ 'nbis/mindtct/detect.c',
+ 'nbis/mindtct/dft.c',
+ 'nbis/mindtct/free.c',
+ 'nbis/mindtct/getmin.c',
+ 'nbis/mindtct/globals.c',
+ 'nbis/mindtct/imgutil.c',
+ 'nbis/mindtct/init.c',
+ 'nbis/mindtct/line.c',
+ 'nbis/mindtct/link.c',
+ 'nbis/mindtct/log.c',
+ 'nbis/mindtct/loop.c',
+ 'nbis/mindtct/maps.c',
+ 'nbis/mindtct/matchpat.c',
+ 'nbis/mindtct/minutia.c',
+ 'nbis/mindtct/morph.c',
+ 'nbis/mindtct/quality.c',
+ 'nbis/mindtct/remove.c',
+ 'nbis/mindtct/ridges.c',
+ 'nbis/mindtct/shape.c',
+ 'nbis/mindtct/sort.c',
+ 'nbis/mindtct/util.c',
+ 'nbis/mindtct/xytreps.c',
+]
+
+driver_sources = {
+ 'upekts' :
+ [ 'drivers/upekts.c', 'drivers/upek_proto.c' ],
+ 'upektc' :
+ [ 'drivers/upektc.c' ],
+ 'upeksonly' :
+ [ 'drivers/upeksonly.c' ],
+ 'uru4000' :
+ [ 'drivers/uru4000.c' ],
+ 'aes1610' :
+ [ 'drivers/aes1610.c' ],
+ 'aes1660' :
+ [ 'drivers/aes1660.c' ],
+ 'aes2501' :
+ [ 'drivers/aes2501.c' ],
+ 'aes2550' :
+ [ 'drivers/aes2550.c' ],
+ 'aes2660' :
+ [ 'drivers/aes2660.c' ],
+ 'aes3500' :
+ [ 'drivers/aes3500.c' ],
+ 'aes4000' :
+ [ 'drivers/aes4000.c' ],
+ 'vcom5s' :
+ [ 'drivers/vcom5s.c' ],
+ 'vfs101' :
+ [ 'drivers/vfs101.c' ],
+ 'vfs301' :
+ [ 'drivers/vfs301.c', 'drivers/vfs301_proto.c' ],
+ 'vfs5011' :
+ [ 'drivers/vfs5011.c' ],
+ 'vfs7552' :
+ [ 'drivers/vfs7552.c' ],
+ 'upektc_img' :
+ [ 'drivers/upektc_img.c', 'drivers/upek_proto.c' ],
+ 'etes603' :
+ [ 'drivers/etes603.c' ],
+ 'egis0570' :
+ [ 'drivers/egis0570.c' ],
+ 'egismoc' :
+ [ 'drivers/egismoc/egismoc.c' ],
+ 'vfs0050' :
+ [ 'drivers/vfs0050.c' ],
+ 'elan' :
+ [ 'drivers/elan.c' ],
+ 'elanmoc' :
+ [ 'drivers/elanmoc/elanmoc.c' ],
+ 'elanspi' :
+ [ 'drivers/elanspi.c' ],
+ 'nb1010' :
+ [ 'drivers/nb1010.c' ],
+ 'virtual_image' :
+ [ 'drivers/virtual-image.c' ],
+ 'virtual_device' :
+ [ 'drivers/virtual-device.c' ],
+ 'virtual_device_storage' :
+ [ 'drivers/virtual-device-storage.c' ],
+ 'synaptics' :
+ [ 'drivers/synaptics/synaptics.c', 'drivers/synaptics/bmkt_message.c' ],
+ 'goodixmoc' :
+ [ 'drivers/goodixmoc/goodix.c', 'drivers/goodixmoc/goodix_proto.c' ],
+ 'fpcmoc' :
+ [ 'drivers/fpcmoc/fpc.c' ],
+ 'realtek' :
+ [ 'drivers/realtek/realtek.c' ],
+ 'focaltech_moc' :
+ [ 'drivers/focaltech_moc/focaltech_moc.c' ],
+ 'microarray' :
+ [ 'drivers/microarray/microarray.c' ],
+}
+
+helper_sources = {
+ 'aeslib' :
+ [ 'drivers/aeslib.c' ],
+ 'aesx660' :
+ [ 'drivers/aesx660.c' ],
+ 'aes3k' :
+ [ 'drivers/aes3k.c' ],
+ 'openssl' :
+ [ ],
+ 'udev' :
+ [ ],
+ 'virtual' :
+ [ 'drivers/virtual-device-listener.c' ],
+}
+
+drivers_sources = []
+drivers_cflags = []
+foreach driver: drivers
+ drivers_sources += driver_sources[driver]
+endforeach
+
+drivers_sources += driver_sources['microarray']
+foreach helper : driver_helpers
+ drivers_sources += helper_sources[helper]
+endforeach
+
+
+fp_enums = gnome.mkenums_simple('fp-enums',
+ sources: libfprint_public_headers,
+ install_header: true,
+ install_dir: get_option('includedir') / versioned_libname,
+)
+fp_enums_h = fp_enums[1]
+
+fpi_enums = gnome.mkenums_simple('fpi-enums',
+ sources: libfprint_private_headers,
+ install_header: false,
+)
+fpi_enums_h = fpi_enums[1]
+
+enums_dep = declare_dependency(
+ sources: [ fp_enums_h, fpi_enums_h ]
+)
+
+# Export the drivers' types to the core code
+drivers_type_list = []
+drivers_type_func = []
+drivers_type_list += '#include '
+drivers_type_list += '#include "fpi-context.h"'
+drivers_type_list += ''
+drivers_type_func += 'GArray *'
+drivers_type_func += 'fpi_get_driver_types (void)'
+drivers_type_func += '{'
+drivers_type_func += ' GArray *drivers = g_array_new (TRUE, FALSE, sizeof (GType));'
+drivers_type_func += ' GType t;'
+drivers_type_func += ''
+
+supported_drivers += 'microarray'
+
+foreach driver: supported_drivers
+ drivers_type_list += 'extern GType (fpi_device_' + driver + '_get_type) (void);'
+ drivers_type_func += ' t = fpi_device_' + driver + '_get_type ();'
+ drivers_type_func += ' g_array_append_val (drivers, t);'
+ drivers_type_func += ''
+endforeach
+drivers_type_list += ''
+drivers_type_func += ' return drivers;'
+drivers_type_func += '}'
+
+drivers_sources += configure_file(input: 'empty_file',
+ output: 'fpi-drivers.c',
+ capture: true,
+ command: [
+ 'echo',
+ '\n'.join(drivers_type_list + [] + drivers_type_func)
+ ])
+
+deps = [
+ enums_dep,
+ gio_dep,
+ glib_dep,
+ gobject_dep,
+ gusb_dep,
+ mathlib_dep,
+] + optional_deps
+
+# These are empty and only exist so that the include directories are created
+# in the build tree. This silences a build time warning.
+subdir('nbis/include')
+subdir('nbis/libfprint-include')
+deps += declare_dependency(include_directories: [
+ root_inc,
+ include_directories('nbis/include'),
+ include_directories('nbis/libfprint-include'),
+])
+
+libnbis = static_library('nbis',
+ nbis_sources,
+ dependencies: deps,
+ c_args: cc.get_supported_arguments([
+ '-Wno-error=redundant-decls',
+ '-Wno-redundant-decls',
+ '-Wno-discarded-qualifiers',
+ '-Wno-array-bounds',
+ '-Wno-array-parameter',
+ '-Wno-unused-but-set-variable',
+ ]),
+ install: false)
+
+libfprint_private = static_library('fprint-private',
+ sources: [
+ fpi_enums,
+ libfprint_private_sources,
+ ],
+ dependencies: deps,
+ link_with: libnbis,
+ install: false)
+
+libfprint_drivers = static_library('fprint-drivers',
+ sources: drivers_sources,
+ c_args: drivers_cflags,
+ dependencies: deps,
+ link_with: libfprint_private,
+ install: false)
+
+mapfile = files('libfprint.ver')[0]
+if meson.version().version_compare('>=1.4')
+ mapfile_path = mapfile.full_path()
+else
+ mapfile_path = meson.project_source_root() / '@0@'.format(mapfile)
+endif
+vflag = '-Wl,--version-script,@0@'.format(mapfile_path)
+
+libfprint = shared_library(versioned_libname.split('lib')[1],
+ sources: [
+ fp_enums,
+ libfprint_sources,
+ ],
+ soversion: soversion,
+ version: libversion,
+ link_args : vflag,
+ link_depends : mapfile,
+ link_with: [libfprint_drivers, libfprint_private],
+ dependencies: deps,
+ install: true)
+
+libfprint_dep = declare_dependency(link_with: libfprint,
+ include_directories: root_inc,
+ dependencies: [
+ enums_dep,
+ gio_dep,
+ glib_dep,
+ gobject_dep,
+ gusb_dep,
+ ])
+
+install_headers(['fprint.h'] + libfprint_public_headers,
+ subdir: versioned_libname
+)
+
+libfprint_private_dep = declare_dependency(
+ include_directories: include_directories('.'),
+ link_with: libfprint_private,
+ dependencies: [
+ deps,
+ libfprint_dep,
+ ]
+)
+
+udev_hwdb = executable('fprint-list-udev-hwdb',
+ 'fprint-list-udev-hwdb.c',
+ dependencies: libfprint_private_dep,
+ link_with: libfprint_drivers,
+ install: false)
+
+udev_hwdb_generator = custom_target('udev-hwdb',
+ output: 'autosuspend.hwdb',
+ depend_files: drivers_sources,
+ capture: true,
+ command: [ udev_hwdb ],
+ install: false,
+)
+
+metainfo = executable('fprint-list-metainfo',
+ 'fprint-list-metainfo.c',
+ dependencies: libfprint_private_dep,
+ link_with: libfprint_drivers,
+ install: false)
+
+metainfo_generator = custom_target('metainfo',
+ output: 'org.freedesktop.libfprint.metainfo.xml',
+ depend_files: drivers_sources,
+ capture: true,
+ command: [ metainfo ],
+ install: true,
+ install_dir: datadir / 'metainfo'
+)
+
+if install_udev_rules
+ udev_rules = executable('fprint-list-udev-rules',
+ 'fprint-list-udev-rules.c',
+ dependencies: libfprint_private_dep,
+ link_with: libfprint_drivers,
+ install: false)
+
+ custom_target('udev-rules',
+ output: '70-@0@.rules'.format(versioned_libname),
+ depend_files: drivers_sources,
+ capture: true,
+ command: [ udev_rules ],
+ install: true,
+ install_dir: udev_rules_dir,
+ )
+endif
+
+sync_udev_udb = custom_target('sync-udev-hwdb',
+ depends: udev_hwdb_generator,
+ output: 'sync-udev-hwdb',
+ install: false,
+ command: [
+ 'cp', '-v',
+ udev_hwdb_generator.full_path(),
+ meson.project_source_root() / 'data'
+ ]
+)
+
+alias_target('sync-udev-hwdb', sync_udev_udb)
+
+supported_devices = executable('fprint-list-supported-devices',
+ 'fprint-list-supported-devices.c',
+ dependencies: libfprint_private_dep,
+ link_with: libfprint_drivers,
+ install: false)
+
+
+if get_option('introspection')
+ # We do *not* include the private header here
+ libfprint_girtarget = gnome.generate_gir(libfprint,
+ sources : fp_enums + [
+ libfprint_public_headers,
+ libfprint_sources,
+ ],
+ nsversion : '@0@.0'.format(soversion),
+ namespace : 'FPrint',
+ symbol_prefix : 'fp_',
+ identifier_prefix : 'Fp',
+ export_packages : 'fprint',
+ extra_args : [
+ '--c-include=fprint.h',
+ ],
+ link_with : libfprint,
+ dependencies : [
+ gio_dep,
+ gobject_dep,
+ gusb_dep,
+ ],
+ includes : [
+ 'Gio-2.0',
+ 'GObject-2.0',
+ 'GUsb-1.0',
+ ],
+ fatal_warnings: true,
+ install : true)
+ libfprint_gir = libfprint_girtarget[0]
+ libfprint_typelib = libfprint_girtarget[1]
+endif
diff --git a/src/microarray.c b/src/microarray.c
index d85c6c2..7f962fd 100644
--- a/src/microarray.c
+++ b/src/microarray.c
@@ -420,25 +420,38 @@ enroll_run_state (FpiSsm *ssm, FpDevice *device)
break;
case ENROLL_EMPTY: {
- /* Check bitmap from pre-enrollment ReadIndex */
+ /* Check bitmap from pre-enrollment ReadIndex to find a free slot */
const guint8 *resp = self->resp_buf + MA_OVERHEAD;
+ self->fid = -1;
+
if (resp[0] == 0x00) {
for (int byte = 0; byte < 4 && self->fid < 0; byte++) {
for (int bit = 0; bit < 8; bit++) {
+ int candidate_slot = byte * 8 + bit;
+
+ /* STRICT CAP: Your chip only supports up to slot 9 (10 fingers total) */
+ if (candidate_slot > 9) {
+ break;
+ }
+
+ /* If this bit is 0, the slot is empty! Let's claim it. */
if (!(resp[1 + byte] & (1 << bit))) {
- self->fid = byte * 8 + bit;
+ self->fid = candidate_slot;
break;
}
}
}
}
- if (self->fid >= 0) {
- fp_dbg ("FID slot %d free, skipping Empty", self->fid);
+
+ /* If we found a valid free slot (0-9), skip erasing and proceed to scan */
+ if (self->fid >= 0 && self->fid <= 9) {
+ fp_dbg ("Found free FID slot %d. Proceeding to image capture.", self->fid);
fpi_ssm_jump_to_state (ssm, ENROLL_GET_IMAGE);
return;
}
- /* No free slots — erase all templates, use slot 0 */
- fp_dbg ("no free FID slots, clearing all templates (CMD 0x0D)");
+
+ /* FALLBACK: If slots 0-9 are completely full, nuke the chip to start fresh */
+ fp_dbg ("Storage slots 0-9 are completely full! Clearing all templates.");
self->fid = 0;
cmd[0] = MA_CMD_EMPTY;
ma_submit_cmd (ssm, device, cmd, 1);