{"id":33118965,"url":"https://github.com/einar-saukas/ZX0","last_synced_at":"2025-11-19T21:01:42.571Z","repository":{"id":46790206,"uuid":"330422703","full_name":"einar-saukas/ZX0","owner":"einar-saukas","description":"Data compressor for 8-bit computers and low-end platforms","archived":false,"fork":false,"pushed_at":"2023-09-24T19:01:41.000Z","size":355,"stargazers_count":126,"open_issues_count":8,"forks_count":16,"subscribers_count":21,"default_branch":"main","last_synced_at":"2023-11-07T20:11:26.810Z","etag":null,"topics":["compression","compressor","data-compression"],"latest_commit_sha":null,"homepage":"","language":"Assembly","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/einar-saukas.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-01-17T15:29:31.000Z","updated_at":"2023-11-04T16:31:42.000Z","dependencies_parsed_at":"2023-01-20T04:48:03.236Z","dependency_job_id":null,"html_url":"https://github.com/einar-saukas/ZX0","commit_stats":null,"previous_names":[],"tags_count":0,"template":null,"template_full_name":null,"purl":"pkg:github/einar-saukas/ZX0","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/einar-saukas%2FZX0","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/einar-saukas%2FZX0/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/einar-saukas%2FZX0/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/einar-saukas%2FZX0/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/einar-saukas","download_url":"https://codeload.github.com/einar-saukas/ZX0/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/einar-saukas%2FZX0/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":285327645,"owners_count":27152947,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-19T02:00:05.673Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compression","compressor","data-compression"],"created_at":"2025-11-15T04:00:23.594Z","updated_at":"2025-11-19T21:01:42.564Z","avatar_url":"https://github.com/einar-saukas.png","language":"Assembly","funding_links":[],"categories":["Software Development"],"sub_categories":["Libraries"],"readme":"# ZX0\n\n**ZX0** is an optimal data compressor for a custom\n[LZ77/LZSS](https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Storer%E2%80%93Szymanski)\nbased compression format, that provides a tradeoff between high compression\nratio, and extremely simple fast decompression. Therefore it's especially\nappropriate for low-end platforms, including 8-bit computers like the ZX\nSpectrum.\n\nA comparison with other compressors (courtesy of **introspec/spke**) can be seen\n[here](https://www.cpcwiki.eu/forum/programming/new-cruncher-zx0/msg197727/#msg197727).\n\n\n_**WARNING**: The ZX0 file format was changed in version 2. This new format allows\ndecompressors to be slightly smaller and run slightly faster. If you need to compress\na file to the old \"classic\" file format from version 1, then execute ZX0 compressor\nusing parameter \"-c\"._\n\n\n## Usage\n\nTo compress a file, use the command-line compressor as follows:\n\n```\nzx0 Cobra.scr\n```\n\nThis will generate a compressed file called \"Cobra.scr.zx0\".\n\nAfterwards you can choose a decompressor routine in assembly Z80, according to\nyour requirements for speed and size:\n\n* \"Standard\" routine: 68 bytes only\n* \"Turbo\" routine: 126 bytes, about 21% faster\n* \"Fast\" routine: 187 bytes, about 25% faster\n* \"Mega\" routine: 673 bytes, about 28% faster\n\nFinally compile the chosen decompressor routine and load the compressed file\nsomewhere in memory. To decompress data, just call the routine specifying the\nsource address of compressed data in HL and the target address in DE.\n\nFor instance, if you compile the decompressor routine to address 65000, load\n\"Cobra.scr.zx0\" at address 51200, and you want to decompress it directly to the\nscreen, then execute the following code:\n\n```\n    LD    HL, 51200  ; source address (put \"Cobra.scr.zx0\" there)\n    LD    DE, 16384  ; target address (screen memory in this case)\n    CALL  65000      ; decompress routine compiled at this address\n```\n\nIt's also possible to decompress data into a memory area that partially overlaps\nthe compressed data itself (only if you won't need to decompress it again later,\nobviously). In this case, the last address of compressed data must be at least\n\"delta\" bytes higher than the last address of decompressed data. The exact value\nof \"delta\" for each case is reported by **ZX0** during compression. See image\nbelow:\n\n```\n                       |------------------|    compressed data\n    |---------------------------------|       decompressed data\n  start \u003e\u003e                            \u003c---\u003e\n                                      delta\n```\n\nFor convenience, there's also a command-line decompressor that works as follows:\n\n```\ndzx0 Cobra.scr.zx0\n```\n\n\n## Performance\n\nThe **ZX0** optimal compressor algorithm is fairly complex, thus compressing\ntypical files can take a few seconds. During development, you can speed up this\nprocess simply using **ZX0** in \"quick\" mode. This will produce a non-optimal\nlarger compressed file but execute almost instantly:\n\n```\nzx0 -q Cobra.scr\n```\n\nThis way, you can repeatedly modify your files, then quickly compress and test\nthem. Later, when you finish changing these files, you can compress them again\nwithout \"quick\" mode for maximum compression. Notice that using \"quick\" mode\nwill only affect the size of the compressed file, not its format. Therefore\nall decompressor routines will continue to work exactly the same way.\n\nFortunately all complexity lies on the compression process only. The **ZX0**\ncompression format itself is very simple and efficient, providing a high\ncompression ratio that can be decompressed quickly and easily. The provided\n**ZX0** decompressor routines in assembly Z80 are small and fast, they only use\nmain registers (BC, DE, HL, AF), consume very little stack space, and do not\nrequire additional decompression buffer.\n\nThe provided **ZX0** decompressor in C writes the output file while reading the\ncompressed file, without keeping it in memory. Therefore it always use the same\namount of memory, regardless of file size. Thus even large compressed files can\nbe decompressed in very small computers with limited memory, even if it took\nconsiderable time and memory to compress it originally. It means decompressing\nwithin asymptotically optimal space and time O(n) only, using storage space O(n)\nfor input and output files, and only memory space O(w) for processing.\n\n\n## File Format\n\nThe **ZX0** compressed format is very simple. There are only 3 types of blocks:\n\n* Literal (copy next N bytes from compressed file)\n```\n    0  Elias(length)  byte[1]  byte[2]  ...  byte[N]\n```\n\n* Copy from last offset (repeat N bytes from last offset)\n```\n    0  Elias(length)\n```\n\n* Copy from new offset (repeat N bytes from new offset)\n```\n    1  Elias(MSB(offset)+1)  LSB(offset)  Elias(length-1)\n```\n\n**ZX0** needs only 1 bit to distinguish between these blocks, because literal\nblocks cannot be consecutive, and reusing last offset can only happen after a\nliteral block. The first block is always a literal, so the first bit is omitted.\n\nThe offset MSB and all lengths are stored using interlaced\n[Elias Gamma Coding](https://en.wikipedia.org/wiki/Elias_gamma_coding). When\noffset MSB equals 256 it means EOF. The offset LSB is stored using 7 bits \ninstead of 8, because it produces better results in most practical cases.\n\n\n## Advanced Features\n\nThe **ZX0** compressor contains a few extra \"hidden\" features, that are slightly\nharder to use properly, and not supported by the **ZX0** decompressor in C. Please\nread carefully these instructions before attempting to use any of them!\n\n\n#### _COMPRESSING BACKWARDS_\n\nWhen using **ZX0** for \"in-place\" decompression (decompressing data to overlap the\nsame memory area storing the compressed data), you must always leave a small\nmargin of \"delta\" bytes of compressed data at the end. However it won't work to\ndecompress some large data that will occupy all the upper memory until the last\nmemory address, since there won't be even a couple bytes left at the end.\n\nA possible workaround is to compress and decompress data backwards, starting at\nthe last memory address. Therefore you will only need to leave a small margin of\n\"delta\" bytes of compressed data at the beginning instead. Technically, it will\nrequire that lowest address of compressed data should be at least \"delta\" bytes\nlower than lowest address of decompressed data. See image below:\n\n     compressed data    |------------------|\n    decompressed data       |---------------------------------|\n                        \u003c---\u003e                            \u003c\u003c start\n                        delta\n\nTo compress a file backwards, use the command-line compressor as follows:\n\n```\nzx0 -b Cobra.scr\n```\n\nTo decompress it later, you must call one of the supplied \"backwards\" variants\nof the Assembly decompressor, specifying last source address of compressed data\nin HL and last target address in DE.\n\nFor instance, if you compile a \"backwards\" Assembly decompressor routine to\naddress 64000, load backwards compressed file \"Cobra.scr.zx0\" (with size 2202\nbytes) to address 51200, and want to decompress it directly to the ZX Spectrum\nscreen (with 6912 bytes), then execute the following code:\n\n```\n    LD    HL, 51200+2202-1  ; source (last address of \"Cobra.scr.zx0\")\n    LD    DE, 16384+6912-1  ; target (last address of screen memory)\n    CALL  64000             ; backwards decompress routine\n```\n\nNotice that compressing backwards may sometimes produce slightly smaller\ncompressed files in certain cases, slightly larger compressed files in others.\nOverall it shouldn't make much difference either way.\n\n\n#### _COMPRESSING WITH PREFIX_\n\nThe LZ77/LZSS compression is achieved by \"abbreviating repetitions\", such that\ncertain sequences of bytes are replaced with much shorter references to previous\noccurrences of these same sequences. For this reason, it's harder to get very\ngood compression ratio on very short files, or in the initial parts of larger\nfiles, due to lack of choices for previous sequences that could be referenced.\n\nA possible improvement is to compress data while also taking into account what\nelse will be already stored in memory during decompression later. Thus the\ncompressed data may even contain shorter references to repetitions stored in\nsome previous \"prefix\" memory area, instead of just repetitions within the\ndecompressed area itself.\n\nAn input file may contain both some prefix data to be referenced only, and the\nactual data to be compressed. An optional parameter can specify how many bytes\nmust be skipped before compression. See below:\n\n```\n                                        compressed data\n                                     |-------------------|\n         prefix             decompressed data\n    |--------------|---------------------------------|\n                 start \u003e\u003e\n    \u003c--------------\u003e                                 \u003c---\u003e\n          skip                                       delta\n```\n\nAs usual, if you want to decompress data into a memory area that partially\noverlaps the compressed data itself, the last address of compressed data must be\nat least \"delta\" bytes higher than the last address of decompressed data.\n\nFor instance, if you want the first 6144 bytes of a certain file to be skipped\n(not compressed but possibly referenced), then use the command-line compressor\nas follows:\n\n```\nzx0 +6144 Cobra.cbr\n```\n\nIn practice, suppose an action game uses a few generic sprites that are common\nfor all levels (such as player graphics), and other sprites are specific for\neach level (such as enemies). All generic sprites must stay always accessible at\na certain memory area, but any level specific data can be only decompressed as\nneeded, to the memory area immediately following it. In this case, the generic\nsprites area could be used as prefix when compressing and decompressing each\nlevel, in an attempt to improve compression. For instance, suppose generic\ngraphics are loaded from file \"generic.gfx\" to address 56000, occupying 2500\nbytes, and level specific graphics will be decompressed immediately afterwards,\nto address 58500. To compress each level using \"generic.gfx\" as a 2500 bytes\nprefix, use the command-line compressor as follows:\n\n```\ncopy /b generic.gfx+level_1.gfx prefixed_level_1.gfx\nzx0 +2500 prefixed_level_1.gfx\n\ncopy /b generic.gfx+level_2.gfx prefixed_level_2.gfx\nzx0 +2500 prefixed_level_2.gfx\n\ncopy /b generic.gfx+level_3.gfx prefixed_level_3.gfx\nzx0 +2500 prefixed_level_3.gfx\n```\n\nTo decompress it later, you simply need to use one of the normal variants of the\nAssembly decompressor, as usual. In this case, if you loaded compressed file\n\"prefixed_level_1.gfx.zx0\" to address 48000 for instance, decompressing it will\nrequire the following code:\n\n```\n    LD    HL, 48000  ; source address (put \"prefixed_level_1.gfx.zx0\" there)\n    LD    DE, 58500  ; target address (level specific memory area in this case)\n    CALL  65000      ; decompress routine compiled at this address\n```\n\nHowever decompression will only work properly if exactly the same prefix data is\npresent in the memory area immediately preceding the decompression address.\nTherefore you must be extremely careful to ensure the prefix area does not store\nvariables, self-modifying code, or anything else that may change prefix content\nbetween compression and decompression. Also don't forget to recompress your\nfiles whenever you modify a prefix!\n\nIn certain cases, compressing with a prefix may considerably help compression.\nIn others, it may not even make any difference. It mostly depends on how much\nsimilarity exists between data to be compressed and its provided prefix.\n\n\n#### _COMPRESSING BACKWARDS WITH SUFFIX_\n\nBoth features above can be used together. A file can be compressed backwards,\nwith an optional parameter to specify how many bytes should be skipped (not\ncompressed but possibly referenced) from the end of the input file instead. See\nbelow:\n\n```\n       compressed data\n    |-------------------|\n                 decompressed data             suffix\n        |---------------------------------|--------------|\n                                     \u003c\u003c start\n    \u003c---\u003e                                 \u003c--------------\u003e\n    delta                                       skip\n```\n\nAs usual, if you want to decompress data into a memory area that partially\noverlaps the compressed data itself, lowest address of compressed data must be\nat least \"delta\" bytes lower than lowest address of decompressed data.\n\nFor instance, if you want to skip the last 768 bytes of a certain input file and\ncompress everything else (possibly referencing this \"suffix\" of 768 bytes), then\nuse the command-line compressor as follows:\n\n```\nzx0 -b +768 Cobra.cbr\n```\n\nIn previous example, suppose the action game now stores level-specific sprites\nin the memory area from address 33000 to 33511 (512 bytes), just before generic\nsprites that are stored from address 33512 to 34535 (1024 bytes). In this case,\nthese generic sprites could be used as suffix when compressing and decompressing\nlevel-specific data as needed, in an attempt to improve compression. To compress\neach level using \"generic.gfx\" as a 1024 bytes suffix, use the command-line\ncompressor as follows:\n\n```\ncopy /b level_1.gfx+generic.gfx level_1_suffixed.gfx\nzx0 -b +1024 level_1_suffixed.gfx\n\ncopy /b level_2.gfx+generic.gfx level_2_suffixed.gfx\nzx0 -b +1024 level_2_suffixed.gfx\n\ncopy /b level_3.gfx+generic.gfx level_3_suffixed.gfx\nzx0 -b +1024 level_3_suffixed.gfx\n```\n\nTo decompress it later, use the backwards variant of the Assembly decompressor.\nIn this case, if you compile a \"backwards\" decompressor routine to address\n64000, and load compressed file \"level_1_suffixed.gfx.zx0\" (with 217 bytes) to\naddress 39000 for instance, decompressing it will require the following code:\n\n```\n    LD    HL, 39000+217-1  ; source (last address of \"level_1_suffixed.gfx.zx0\")\n    LD    DE, 33000+512-1  ; target (last address of level-specific data)\n    CALL  64000            ; backwards decompress routine\n```\n\nAnalogously, decompression will only work properly if exactly the same suffix\ndata is present in the memory area immediately following the decompression area.\nTherefore you must be extremely careful to ensure the suffix area does not store\nvariables, self-modifying code, or anything else that may change suffix content\nbetween compression and decompression. Also don't forget to recompress your\nfiles whenever you modify a suffix!\n\nAlso if you are using \"in-place\" decompression, you must leave a small margin of\n\"delta\" bytes of compressed data just before the decompression area.\n\n\n## License\n\nThe **ZX0** data compression format and algorithm was designed and implemented\nby **Einar Saukas**. Special thanks to **introspec/spke** for several\nsuggestions and improvements, and together with **uniabis** for providing the\n\"Fast\" decompressor. Also special thanks to **Urusergi** for additional ideas\nand improvements.\n\nThe optimal C compressor is available under the \"BSD-3\" license. In practice,\nthis is relevant only if you want to modify its source code and/or incorporate\nthe compressor within your own products. Otherwise, if you just execute it to\ncompress files, you can simply ignore these conditions.\n\nThe decompressors can be used freely within your own programs (either for the\nZX Spectrum or any other platform), even for commercial releases. The only\ncondition is that you must indicate somehow in your documentation that you have\nused **ZX0**.\n\n\n## Links\n\n**ZX0** implemented in other programming languages:\n\n* [ZX0-Java](https://github.com/einar-saukas/ZX0-Java) - Faster\nmulti-thread data compressor for **ZX0** in [Java](https://www.java.com/).\n\n* [ZX0-Kotlin](https://github.com/einar-saukas/ZX0-Kotlin) - Faster\nmulti-thread data compressor for **ZX0** in [Kotlin](https://kotlinlang.org/).\n\n* [Salvador](https://github.com/emmanuel-marty/salvador) - A non-optimal but\nmuch faster data compressor for **ZX0** in C.\n\n**ZX0** ported to other platforms:\n\n* [DEC PDP11](https://github.com/ivagorRetrocomp/DeZX) _(\"classic\" file format v1)_\n\n* [Hitachi 6309](https://github.com/dougmasten/zx0-6x09) _(\"classic\" file format v1)_\n\n* [Intel 8080](https://github.com/ivagorRetrocomp/DeZX) _(\"classic\" file format v1)_\n\n* [Intel 8088/x86](https://github.com/emmanuel-marty/unzx0_x86) _(all formats)_\n\n* [MOS 6502](https://github.com/bboxy/bitfire/tree/master/packer/zx0/6502) _(all formats)_\n\n* [MOS 6502](https://xxl.atari.pl/zx0-decompressor/) (stream) - _(all formats)_\n\n* [Motorola 6809](https://github.com/dougmasten/zx0-6x09) _(\"classic\" file format v1)_\n\n* [Motorola 68000](https://github.com/emmanuel-marty/unzx0_68000) _(all formats)_\n\nTools supporting **ZX0**:\n\n* [z88dk](http://www.z88dk.org/) - The main C compiler for Z80 machines, that\nprovides built-in support for **ZX0**, **ZX1**, **ZX2**, and **ZX7**.\n\n* [ZX Basic](https://zxbasic.readthedocs.io/) - The main BASIC compiler for\nZ80 machines, that provides built-in support for **ZX0**.\n\n* [Mad-Pascal](https://github.com/tebe6502/Mad-Pascal) - The 32-bit Turbo\nPascal compiler for Atari XE/XL, that provides built-in support for **ZX0**.\n\n* [RASM Assembler](https://github.com/EdouardBERGE/rasm/) - A very fast Z80\nassembler, that provides built-in support for **ZX0** and **ZX7**.\n\n* [MSXlib](https://github.com/theNestruo/msx-msxlib) - A set of libraries to\ncreate MSX videogame cartridges, that provides built-in support\nfor **ZX0**, **ZX1**, and **ZX7**.\n\n* [coco-dev](https://github.com/jamieleecho/coco-dev) - A Docker development\nenvironment to create Tandy Color Computer applications, that provides\nbuilt-in support for **ZX0**.\n\n* [Gfx2Next](https://github.com/headkaze/Gfx2Next) - A graphics conversion \nutility for ZX Spectrum Next development, that provides built-in support\nfor **ZX0**.\n\n* [ConvImgCpc](https://github.com/DemoniakLudo/ConvImgCpc) - An image\nconversion utility for Amstrad CPC development, that provides built-in support\nfor **ZX0** and **ZX1**.\n\n* [Vortex2_Player_SJASM](https://github.com/andydansby/Vortex2_Player_SJASM_ver2_compress) -\nA packaging utility to compile Vortex 2 music for a ZX Spectrum, that compresses \nsongs using **ZX0**.\n\nProjects using **ZX0**:\n\n* [Bitfire](https://github.com/bboxy/bitfire) - A disk image loader/generator\nfor Commodore 64, that stores all compressed data using a modified version\nof **ZX0**.\n\n* [Defender CoCo 3](http://www.lcurtisboyle.com/nitros9/defender.html) - A\nconversion of the official Williams Defender game from the arcades for the\nTandy Color Computer 3 that stores all compressed data using **ZX0** to fit\non two 160K floppy disks.\n\n* [NSID_Emu](https://spectrumcomputing.co.uk/forums/viewtopic.php?f=8\u0026t=2786) -\nA SID Player for ZX Spectrum that stores all compressed data using **ZX0**.\n\n* [ZX Interface 2 Cartridges](http://www.fruitcake.plus.com/Sinclair/Interface2/Cartridges/Interface2_RC_New_3rdParty_GameConversions.htm) -\nSeveral ZX Interface 2 conversions were created using either **ZX0** or **ZX7**\nso a full game could fit into a small 16K cartridge.\n\n* [Joust CoCo 3](http://www.lcurtisboyle.com/nitros9/joust.html) - A port of\narcade game Joust for the Tandy Color Computer 3, that stores all compressed\ndata using **ZX0** to fit on a single 160K floppy disk.\n\n* [Sonic GX](http://norecess.cpcscene.net/) - A remake of video game Sonic the \nHedgehog for the GX-4000, that stores all compressed data using **ZX0**.\n\n* [Rit and Tam](http://www.indieretronews.com/2021/02/rit-and-tam-arcade-classic-rodland-is.html) -\nA remake of platform game Rodland for the Amstrad, that stores all compressed\ndata using **ZX0**.\n\n* [others](https://spectrumcomputing.co.uk/entry/36245/ZX-Spectrum/ZX0) -\nA list of Sinclair-related programs using **ZX0** is available at **Spectrum Computing**.\n\nRelated projects (by the same author):\n\n* [RCS](https://github.com/einar-saukas/RCS) - Use **ZX0** and **RCS** together\nto improve compression of ZX Spectrum screens.\n\n* [ZX0](https://github.com/einar-saukas/ZX0) - The official **ZX0** repository.\n\n* [ZX1](https://github.com/einar-saukas/ZX1) - A simpler but faster version\nof **ZX0**, that sacrifices about 1.5% compression to run about 15% faster.\n\n* [ZX2](https://github.com/einar-saukas/ZX2) - A minimalist version of **ZX1**,\nintended for compressing very small files.\n\n* [ZX5](https://github.com/einar-saukas/ZX5) - An experimental, more complex \ncompressor based on **ZX0**.\n\n* [ZX7](https://spectrumcomputing.co.uk/entry/27996/ZX-Spectrum/ZX7) - A widely\npopular predecessor compressor (now superseded by **ZX0**).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feinar-saukas%2FZX0","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feinar-saukas%2FZX0","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feinar-saukas%2FZX0/lists"}