emulator: Speccy type: standalone core_classification: other source: "https://fms.komkon.org/Speccy/" upstream: closed-source version_source: url: "https://play.google.com/store/apps/details?id=com.fms.speccy&hl=en" pattern: '\[\[\["([^"]+)"\]\],\[\[\[\d+' author: "Marat Fayzullin (Garage Research)" profiled_date: "2026-10-20" core_version: "5.9.13" display_name: "Sinclair - ZX Spectrum and Sam Coupe (Speccy)" verification: existence cores: - speccy - SPECCY - com.fms.speccy.deluxe - com.fms.speccy systems: - sinclair-zx-spectrum - timex-tc2048 - timex-ts2068 - sam-coupe mode: standalone notes: | Sinclair emulator by Marat Fayzullin, covering ZX Spectrum 16kB, 48kB and 128kB, Spectrum +2, +2A and +3, Timex TC2048 and TS2068, the Slovak Didaktik Gama, the Russian Pentagon and Scorpion clones, and the Sam Coupe. The build read here is the free Android package com.fms.speccy, versionName 5.9.13 versionCode 5913, taken as the Play app bundle drop, xapk sha256 02ba433a0dbebf33742381ef3d126a2e0853bbd820d9b8fa93c52f61a4762300, base apk sha256 d87a8f042d57840e8c773404bcd7011a717cba698b2434e253a0bc7852772999, native split config.arm64_v8a sha256 b86c8c044032c7cdb7accb5a1530b7664271825b13469b0d20984dfd24250adf. Both splits carry no v1 signature and are signed through the APK Signing Block alone, the v2 and v3 signer being the author's own key, CN=Marat Fayzullin O=Garage Research issued 2011, fingerprint d09469e9b550cbc9005d3bd089f3df5f0ba89cba71cdd3b33736a0382d6f9848, beside a Play distribution source stamp. The paid package com.fms.speccy.deluxe, last published as 5.9.3 versionCode 5903 in December 2021, is carried by no mirror that answers: the author's site links only to Play for it, apkcombo bounces its download page back to the app page, apkpure redirects to its generic downloader, apkvision holds no copy and archive.org none either. The drop carries one native split, arm64-v8a, whose libmain.so keeps its dynamic symbol table, so the readings below cite offsets in that slice and Java lines in a jadx 1.5.6 decompile of the base apk. Against the 5.9.11 arm64-v8a library only OriginalDebugZ80 changed size, every loader function keeping its code at a shifted address. The free Speccy 5.9 builds for Windows and Ubuntu were read beside it and open the same names with the same lengths and the same order. No source has ever been published: every download on the site, from 1.7 for MSDOS through 5.9, is a binary package, while ColEm and fMSX on the same site ship source archives. ref: com.fms.speccy.apk APK Signing Block, https://fms.komkon.org/Speccy/, Speccy59-Windows-bin.zip, Speccy59-Ubuntu-x86-bin.tgz One directory holds everything. MainActivity hands the home directory to jniStart, which copies it into the buffer GetHomeDir returns and passes that same buffer to SetPrivateDir, and Application stores what GetHomeDir returns as the directory LoadBIOS changes into before it opens the first ROM and back out of when it is done. That directory is external storage joined with the application label up to its first space, so /sdcard/Speccy for the package read here, falling back to getExternalFilesDirs(null) when it is not a writable directory. The author's own instructions give the same placement. Every open goes through mopen, which calls OpenRealFile first: the name is tried as given, and again as /, keeping the larger of the two when both open. mopen then layers gzdopen over the descriptor, so any of these files also opens gzipped, and a content:// name is routed to the Android storage framework. The desktop builds have no home directory of their own and open every name where they stand, which -home changes for the system ROMs alone. ref: MainActivity.java:1091-1104, EMULib.java:178, 315-361, 363-373, libmain.so jniStart 0x30ad8-0x310e3 (0x30eec-0x30efc, 0x30f58), GetHomeDir 0x2e73c, SetPrivateDir 0x509e0, Application 0x57784-0x577a4, OpenRealFile 0x50bd8-0x50dff, mopen 0x50e00-0x50f8f, LoadBIOS 0x5b0c4-0x5b0e8, 0x5b3d4-0x5b3e0, Speccy59-Ubuntu-x86-bin speccy 0x3dae5-0x3db17, Speccy.html "-home ", EmuAndroid "System ROMs" LoadBIOS drives the whole set from one word of hardware flags, calling TryLoadROM once per name in a fixed order with the length to read and the bit that asks for it. TryLoadROM does nothing when the bit is clear, and clears the bit when mopen fails, which is the only check the emulator makes: the count mread returns is compared against the requested length solely to pick the word it logs, OK or FAILED, and a short read leaves the bit set. Nothing else about a file is examined and no hash is compared. A machine ROM that loads clears every other machine bit and a peripheral ROM that loads clears every other peripheral bit, so one machine and one disk interface stand at a time. StartZXS gives up when no machine bit survives, logging FAILED TO LOAD SYSTEM ROM! and returning zero without starting the emulation, which makes the ROM of the selected model the one file the emulator cannot run without. ref: libmain.so LoadBIOS 0x5b090-0x5b43b, TryLoadROM 0x5c64c-0x5c777 (mopen 0x5c680, mread 0x5c6ec, bit cleared 0x5c75c-0x5c760, masks 0x5c728-0x5c744), StartZXS 0x5ad74-0x5b08f (LoadBIOS 0x5ae9c, mask 0x5aea0-0x5aeac, message 0x5b058-0x5b06c), Speccy59-Ubuntu-x86-bin speccy 0x3dac0-0x3ddaa, 0x3d970-0x3da04, 0x3f43c-0x3f469, 0x3f640 Two names are reached only through another. ZXS128TR.ROM is tried in place of ZXS128.ROM when the 128kB model and the TR-DOS interface are both asked for, and ZXS128.ROM is loaded after it when it fails. The Multiface bit picks its image from the machine already loaded, MFPLUS3.ROM behind a +2A or +3, MF128.ROM behind a 128kB or +2 and MF1.ROM behind a 16kB or 48kB, and the 8192 bytes at offset 8192 of what it read are copied into the page below the machine ROM. ref: libmain.so LoadBIOS 0x5b29c-0x5b2c4, 0x5b32c-0x5b348, 0x5b2fc-0x5b3b4, Speccy59-Ubuntu-x86-bin speccy 0x3dc79-0x3dca1, 0x3ddf0-0x3de10, 0x3dcd9-0x3dd64, Speccy.html "Now trying to load ZXS128TR.ROM BIOS file" Thirteen of the nineteen ROMs ship inside the Android package. MainActivity reads assets/memfs.mp3 whole and jniStart mounts it through SetMemoryFS as an in-memory filesystem, which mopen consults only after OpenRealFile has failed, so a copy in the home directory is served ahead of the packaged one. The directory is a run of 32 byte entries, a length then a name, closed by an entry with an empty name whose first word is the key: rotated left by three times the entry count, XORed sixteen bits at a time into each length and rotated right by three after each, it yields the thirteen lengths, which run consecutively from the end of the directory and account for the file exactly. Eleven of the thirteen are the same bytes the Windows and Ubuntu distributions ship, and TC2048.ROM and TS2068.ROM appear in no distribution at all. ref: MainActivity.java:1091-1104, libmain.so jniStart 0x30c28-0x30c50, InitMemoryFS 0x50998, SetMemoryFS 0x50a08-0x50bd7 (key 0x50a74-0x50aac, lengths 0x50b74-0x50bb0), mopen 0x50ecc-0x50f74 Companion files come from the loaded program's path. LoadFile derives .sta, .pok, .cht and .pal from it and reads each when it is there, and the startup palette is a name of its own, /Speccy.pal on Android and Speccy.pal beside the binary on the desktop, which -palette redirects. ref: libmain.so LoadFile 0x5c100, 0x5c1e8, 0x5c210, 0x5c234, Application 0x57874-0x578d0, StartZXS 0x5af00-0x5af10, LoadPAL 0x5b5f4, Speccy59-Ubuntu-x86-bin speccy 0x3f4ac-0x3f4bd, 0x3e450-0x3e494, Speccy.html "-palette " files: - name: ZXS48.ROM system: sinclair-zx-spectrum required: true bundled: true size: 16384 md5: 4c42a2f075212361c3117015b107ff68 sha1: 5ea7c2b824672e914525d1d5c419d71b84a426a2 crc32: "ddee531f" description: "ZX Spectrum 48kB BASIC ROM" note: "Read as 16384 bytes for the 48kB model, which is the model the emulator starts in when nothing else is asked for. Present in the packaged filesystem and in the Windows and Ubuntu distributions." source_ref: "libmain.so LoadBIOS 0x5b2e4-0x5b2f4, Speccy59-Ubuntu-x86-bin speccy 0x3dcbe-0x3dcd1" - name: ZXS16.ROM aliases: ["ZXS48.ROM", "48.rom"] system: sinclair-zx-spectrum required: true max_size: 16384 description: "ZX Spectrum 16kB BASIC ROM" note: "Read for the 16kB model under a name of its own, with no fallback to ZXS48.ROM, so the model refuses to start without it however many other ROMs are present. Neither the package nor the desktop distributions carry a copy." source_ref: "libmain.so LoadBIOS 0x5b2cc-0x5b2dc, Speccy59-Ubuntu-x86-bin speccy 0x3dca8-0x3dcbb" - name: ZXS128.ROM system: sinclair-zx-spectrum required: true bundled: true size: 32768 md5: 85fede415f4294cc777517d7eada482e sha1: 16375d42ea109b47edded7a16028de7fdb3013a1 crc32: "2cbe8995" description: "ZX Spectrum 128kB ROM pair" note: "Read as 32768 bytes, two pages. Loaded after ZXS128TR.ROM has been tried when the TR-DOS interface is on as well." source_ref: "libmain.so LoadBIOS 0x5b2b0-0x5b2c4, Speccy59-Ubuntu-x86-bin speccy 0x3dc85-0x3dca1" - name: ZXS128TR.ROM system: sinclair-zx-spectrum required: false bundled: true size: 32768 md5: 44082607f78a3b643f260b0c7e443bbb sha1: 9b8bc3190086774fe6887e975c81e9976e2711ef crc32: "ce98811c" description: "ZX Spectrum 128kB ROM pair carrying TR-DOS" note: "Tried in place of ZXS128.ROM when the 128kB model and the TR-DOS interface are both asked for, and ZXS128.ROM is loaded in its place when it fails." source_ref: "libmain.so LoadBIOS 0x5b32c-0x5b348, Speccy59-Ubuntu-x86-bin speccy 0x3ddf0-0x3de10" - name: ZXSPLUS2.ROM system: sinclair-zx-spectrum required: true bundled: true size: 32768 md5: 238f77692156a5c49d20c0aa2862e8bb sha1: 8cafb292af58617907b9e6b9093d3588a75849b8 crc32: "e7a517dc" description: "ZX Spectrum +2 ROM pair" note: "Read as 32768 bytes for the +2. The +2A bit is cleared ahead of the call whenever the +3 bit is set, so the pair that asks for ZXSPLUS3.ROM cannot leave a second machine standing." source_ref: "libmain.so LoadBIOS 0x5b278-0x5b294, Speccy59-Ubuntu-x86-bin speccy 0x3dc56-0x3dc72" - name: ZXSPLUS3.ROM system: sinclair-zx-spectrum required: true bundled: true size: 65536 md5: 7e00ed3562abfd188d0d4da03e80bc0a sha1: 500c0945760abeefcbd08bc22c0d07b14b336cf0 crc32: "be0d9ec4" description: "ZX Spectrum +2A and +3 ROM set" note: "Read as 65536 bytes, four pages covering both the +2A and the +3, which the loader asks for under one bit pair." source_ref: "libmain.so LoadBIOS 0x5b264-0x5b274, Speccy59-Ubuntu-x86-bin speccy 0x3dc3e-0x3dc51" - name: TC2048.ROM system: timex-tc2048 required: true bundled: true size: 16384 md5: 9dd7ecf784a6c04265c073c236f5fadb sha1: febb2d495b6eda7cdcb4074935d6e9d9f328972d crc32: "f1b5fa67" description: "Timex Computer 2048 ROM" note: "Read as 16384 bytes for the TC2048. Carried in the packaged filesystem only: the Windows and Ubuntu distributions ship no copy." source_ref: "libmain.so LoadBIOS 0x5b21c-0x5b22c, Speccy59-Ubuntu-x86-bin speccy 0x3dbf6-0x3dc09" - name: TS2068.ROM system: timex-ts2068 required: true bundled: true size: 24576 md5: 9194283503a105f3f3dfba13e61e993f sha1: 34cd8113bf75b65a4da6eea355f9d46ad635f7e4 crc32: "48004230" description: "Timex Sinclair 2068 ROM set" note: "The loader asks for 49152 bytes while the author's own image is 24576, so the read stops short of what was asked and the machine starts anyway. Carried in the packaged filesystem only." source_ref: "libmain.so LoadBIOS 0x5b200-0x5b214, Speccy59-Ubuntu-x86-bin speccy 0x3dbdd-0x3dbf1" - name: DIDAKTIK.ROM system: sinclair-zx-spectrum required: true bundled: true size: 16384 md5: 28287c397defff765b39bd0660da6d01 sha1: 8466a9da0169666210ccff5d43376d70bae0ae9b crc32: "45c29401" description: "Didaktik Gama ROM" note: "Read as 16384 bytes for the Slovak Didaktik Gama clone." source_ref: "libmain.so LoadBIOS 0x5b234-0x5b244, Speccy59-Ubuntu-x86-bin speccy 0x3dc0e-0x3dc21" - name: PENTAGON.ROM system: sinclair-zx-spectrum required: true bundled: true size: 49152 md5: 36e48cd8984702fa9628121f83264ed0 sha1: 582254842091d9f6263652f06c6a86bd54dcf736 crc32: "e7bbb552" description: "Pentagon 128kB ROM set" note: "Read as 49152 bytes, three pages. The third is copied into the disk interface window once it has loaded, so the Pentagon carries its own TR-DOS and does not need TRDOS.ROM." source_ref: "libmain.so LoadBIOS 0x5b1cc-0x5b1fc, Speccy59-Ubuntu-x86-bin speccy 0x3dbb8-0x3dbd7, 0x3de18-0x3de37" - name: SCORPION.ROM system: sinclair-zx-spectrum required: true bundled: true size: 65536 md5: b26cba495108d1227aa5124cd9a1456d sha1: 66cecdadf992d8adb9c66deee929eb56600dc9bc crc32: "fef73c28" description: "Scorpion 256kB ROM set" note: "Read as 65536 bytes, four pages, the last copied into the disk interface window. The Scorpion bit also drops every peripheral bit before the first ROM is opened, so its own service ROM stands in place of the separate interfaces." source_ref: "libmain.so LoadBIOS 0x5b18c-0x5b1c8, 0x5b11c-0x5b134, Speccy59-Ubuntu-x86-bin speccy 0x3db94-0x3dbb2, 0x3de40-0x3de5f" - name: COUPE.ROM system: sam-coupe required: true bundled: true size: 32768 md5: 9f40c46cce4008e0548db44f39527914 sha1: 58186fd34a8b9c1929913e2b1d7dc4efa507c160 crc32: "39ff160a" description: "Sam Coupe ROM" note: "Read as 32768 bytes for the Sam Coupe, which is a machine of its own in the same flag word, so selecting it clears every Spectrum bit and the trapdoor devices are switched off with it." source_ref: "libmain.so LoadBIOS 0x5b24c-0x5b25c, TryLoadROM 0x5c728-0x5c744, Speccy59-Ubuntu-x86-bin speccy 0x3dc26-0x3dc39, Speccy.html \"Now disabling all trapdoor-based devices in Sam Coupe mode\"" - name: TRDOS.ROM system: sinclair-zx-spectrum required: false bundled: true size: 16384 md5: 53e2f417c6996df9af170e147df8e369 sha1: 282eb7bc819aad2a12fd954e76f7838a4e1a7929 crc32: "d8882a8c" description: "Beta Disk TR-DOS interface ROM" note: "Opened first of all, before any machine ROM, and read as 16384 bytes. A failed open drops the interface and leaves the emulation running without it. Its presence is also what makes the 128kB model reach for ZXS128TR.ROM." source_ref: "libmain.so LoadBIOS 0x5b11c-0x5b13c, Speccy59-Ubuntu-x86-bin speccy 0x3db20-0x3db47" - name: IF1.ROM system: sinclair-zx-spectrum required: false bundled: true size: 8192 md5: 31b704ae925305e74f50699271fddd9a sha1: 5cfb6bca4177c45fefd571734576b55e3a127c08 crc32: "bb66dd1e" description: "Sinclair Interface I ROM" note: "Read as 8192 bytes when the Interface I is on, and dropped silently when it is missing. Snapshots that page it in need it to restore." source_ref: "libmain.so LoadBIOS 0x5b144-0x5b154, Speccy59-Ubuntu-x86-bin speccy 0x3db4c-0x3db5f, Speccy.html \"Fixed some .Z80 snapshots not loading when IF1.ROM is missing\"" - name: DISCIPLE.ROM system: sinclair-zx-spectrum required: false max_size: 16384 description: "DISCiPLE disk interface ROM" note: "Read when the DISCiPLE interface is on, up to 16384 bytes, and dropped silently when it is missing. Neither the package nor the desktop distributions carry a copy." source_ref: "libmain.so LoadBIOS 0x5b15c-0x5b16c, Speccy59-Ubuntu-x86-bin speccy 0x3db64-0x3db77" - name: PLUSD.ROM system: sinclair-zx-spectrum required: false max_size: 8192 description: "MGT +D disk interface ROM" note: "Read when the +D interface is on, up to 8192 bytes, and dropped silently when it is missing. Neither the package nor the desktop distributions carry a copy." source_ref: "libmain.so LoadBIOS 0x5b174-0x5b184, Speccy59-Ubuntu-x86-bin speccy 0x3db7c-0x3db8f" - name: MF1.ROM system: sinclair-zx-spectrum required: false max_size: 16384 description: "Multiface One ROM" note: "Read up to 16384 bytes when the Multiface is on behind a 16kB or 48kB machine, and the 8192 bytes at offset 8192 of what it read are copied into the page below the machine ROM. Dropped silently when it is missing." source_ref: "libmain.so LoadBIOS 0x5b378-0x5b3b4, Speccy59-Ubuntu-x86-bin speccy 0x3dea8-0x3deb7, 0x3dcf9-0x3dd0b" - name: MF128.ROM system: sinclair-zx-spectrum required: false max_size: 16384 description: "Multiface 128 ROM" note: "Read up to 16384 bytes when the Multiface is on behind a 128kB or +2 machine, and the 8192 bytes at offset 8192 of what it read are copied into the page below the machine ROM. Dropped silently when it is missing." source_ref: "libmain.so LoadBIOS 0x5b34c-0x5b3b4, Speccy59-Ubuntu-x86-bin speccy 0x3dcea-0x3dd0b" - name: MFPLUS3.ROM system: sinclair-zx-spectrum required: false max_size: 16384 description: "Multiface +3 ROM" note: "Read up to 16384 bytes when the Multiface is on behind a +2A or +3 machine, and the 8192 bytes at offset 8192 of what it read are copied into the page below the machine ROM. Dropped silently when it is missing. Neither the package nor the desktop distributions carry a copy." source_ref: "libmain.so LoadBIOS 0x5b300-0x5b3b4, Speccy59-Ubuntu-x86-bin speccy 0x3dee0-0x3dee7, 0x3dcf9-0x3dd0b" - name: Speccy.pal required: false unsourceable: "any palette the user writes, in the text format the parser reads" category: game_data description: "startup colour palette" note: "Built as /Speccy.pal on Android and taken as the bare name beside the binary on the desktop, loaded once the machine has started and left alone when it is absent. The parser reads the file line by line, skipping blanks and comments, so a palette is text rather than a dump, and -palette points the desktop builds at another name or at one of four built-in palettes." source_ref: "libmain.so Application 0x57874-0x578d0, StartZXS 0x5af00-0x5af10, LoadPAL 0x5b5f4-0x5b90b, Speccy59-Ubuntu-x86-bin speccy 0x3f4ac-0x3f4bd, 0x3e450-0x3e560" - name: ROOT.chts required: false category: game_data size: 1652893 md5: b41d9ebf814806a25438a6d937804e54 sha1: 0b53d51635a2ce3d4e12c2077df9aeffeac02517 crc32: "b58e81f1" description: "cheat database read by the Cheatopedia browser" note: "The package ships no cheat set. Cheatopedia looks for the file in the package assets, then in the home directory and its subdirectories, and takes it from Speccy-Cheats.zip in the Downloads directory when that is newer, unpacking it into /Cheats. The author publishes that archive at fms.komkon.org/EmuAndroid/Speccy-Cheats.zip and states it is what re-enables the cheat features Google Play made him disable in March 2019. The copy read here holds this one file." source_ref: "CheatHelper.java:16-49, Cheatopedia.java:181-188, 256-290, 455, EmuAndroid \"Cheats support\"" - name: Controls.png required: false category: game_data size: 22587 md5: d10635cb6f101ed733f3d1133bc78f61 sha1: 14c4ab8ccc8ecb81ebf99fec336a3e872c13aabc crc32: "dc9da322" description: "virtual joystick skin" note: "Read as /Controls.png by the overlay button loader and by the layout editor. The author publishes the template to draw over at fms.komkon.org/EmuAndroid/Controls.png." source_ref: "MainActivity.java:1592-1603, LayoutEditor.java:227" - name: Backdrop.png required: false category: game_data unsourceable: "any picture the user places behind the emulated screen" description: "backdrop drawn around the emulated screen" note: "Read as /Backdrop.png. A built-in drawable stands in when the file is absent." source_ref: "MainActivity.java:1723-1748" - name: ".back.png" required: false category: game_data unsourceable: "any picture the user places behind one title" description: "backdrop for one title" note: "Named from the loaded program's path and preferred over the shared backdrop for that game." source_ref: "MainActivity.java:1737-1739" - name: ".pok" required: false category: game_data unsourceable: "POKEs the user writes or collects per title" description: "POKE list for one title" note: "Named from the loaded program's path and read by LoadPOK when present." source_ref: "libmain.so LoadFile 0x5c1e8-0x5c200, LoadPOK 0x5fd98" - name: ".cht" required: false category: game_data unsourceable: "cheat codes the user writes or collects per title" description: "cheat codes for one title" note: "Named from the loaded program's path and read by LoadCHT when present. Distinct from the .chts sets of the Cheatopedia browser." source_ref: "libmain.so LoadFile 0x5c210-0x5c224, LoadCHT 0x600f4" - name: ".pal" required: false category: game_data unsourceable: "any palette the user writes for one title" description: "colour palette for one title" note: "Named from the loaded program's path and read by LoadPAL when present, taking effect over the startup palette for that program." source_ref: "libmain.so LoadFile 0x5c234-0x5c248, LoadPAL 0x5b5f4" exclusion_note: > Left out are the files Speccy writes and reads back itself, which are emulator state rather than anything a user obtains: .sta holding saved emulation state, which LoadFile restores ahead of everything else it derives from the program path, LOG.MID holding the MIDI soundtrack the recorder writes, /PrinterOutput.txt holding what the ZX and Timex printers print, and the replay files of the instant replay recorder. Snapshots, tapes, disk images and screens are the user's own content: .z80, .sna, .szx, .scs, .tap, .tzx, .trd, .scl, .fdi, .dsk, .mgt, .sad and .scr all reach LoadFile, and the emulator writes most of them back. DEFAULT.Z80 is only the name the desktop builds fall back to when they are started with no file at all. names.dat is left out because the path is dead in this build: FileInfo opens assets/names.dat and /names.dat inside one try block, and no split of the package carries that asset, so the failed asset read takes the home directory copy down with it. changelog.html is left out as the release history shown from the menu, read from the package assets and from nowhere else. The Android audio and graphics libraries the loader opens by name are provided by the system. ref: libmain.so LoadFile 0x5c004-0x5c64b (0x5c100-0x5c148), STAName 0x8f170, SndName 0x8b048, StartZXS 0x5aeb0-0x5aec0, Application 0x57848-0x57874, LoadRPL 0x77b64, SaveRPL 0x777f8, SaveSCR 0x60838, bi.java:189-218, EMULib.java:779, 809, Speccy59-Ubuntu-x86-bin speccy 0xbf50, Speccy.html "[filename] = Name of the file to load [DEFAULT.Z80]"