static UInt128 CityHash128WithSeed(byte[] s, int len, UInt128 seed, int off)
        {
            if (len < 128)
            {
                return(CityMurmur(s, len, seed, off));
            }

            // We expect len >= 128 to be the common case.  Keep 56 bytes of state:
            // v, w, x, y, and z.
            UInt128 v = new UInt128(), w = new UInt128();
            ulong   x = seed.Low;
            ulong   y = seed.High;
            ulong   z = (ulong)len * k1;

            v.Low  = Rotate(y ^ k1, 49) * k1 + Fetch64(s, off);
            v.High = Rotate(v.Low, 42) * k1 + Fetch64(s, off + 8);
            w.Low  = Rotate(y + z, 35) * k1 + x;
            w.High = Rotate(x + Fetch64(s, off + 88), 53) * k1;

            // This is the same inner loop as CityHash64(), manually unrolled.
            ulong swp;

            do
            {
                x    = Rotate(x + y + v.Low + Fetch64(s, off + 16), 37) * k1;
                y    = Rotate(y + v.High + Fetch64(s, off + 48), 42) * k1;
                x   ^= w.High;
                y   ^= v.Low;
                z    = Rotate(z ^ w.Low, 33);
                v    = WeakHashLen32WithSeeds(s, v.High * k1, x + w.Low, off);
                w    = WeakHashLen32WithSeeds(s, z + w.High, y, off + 32);
                swp  = z;
                z    = x;
                x    = swp;
                off += 64;
                x    = Rotate(x + y + v.Low + Fetch64(s, off + 16), 37) * k1;
                y    = Rotate(y + v.High + Fetch64(s, off + 48), 42) * k1;
                x   ^= w.High;
                y   ^= v.Low;
                z    = Rotate(z ^ w.Low, 33);
                v    = WeakHashLen32WithSeeds(s, v.High * k1, x + w.Low, off);
                w    = WeakHashLen32WithSeeds(s, z + w.High, y, off + 32);
                swp  = z;
                z    = x;
                x    = swp;
                off += 64;
                len -= 128;
            } while ((len >= 128));
            y += Rotate(w.Low, 37) * k0 + z;
            x += Rotate(v.Low + z, 49) * k0;
            // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s.
            for (int tail_done = 0; tail_done < len;)
            {
                tail_done += 32;
                y          = Rotate(y - x, 42) * k0 + v.High;
                w.Low     += Fetch64(s, off + len - tail_done + 16);
                x          = Rotate(x, 49) * k0 + w.Low;
                w.Low     += v.Low;
                v          = WeakHashLen32WithSeeds(s, v.Low, v.High, off + len - tail_done);
            }
            // At this point our 48 bytes of state should contain more than
            // enough information for a strong 128-bit hash.  We use two
            // different 48-byte-to-8-byte hashes to get a 16-byte final result.
            x = HashLen16(x, v.Low);
            y = HashLen16(y, w.Low);
            return(new UInt128(HashLen16(x + v.High, w.High) + y,
                               HashLen16(x + w.High, y + v.High)));
        }
 protected abstract byte[] Decompress(Stream compressed, out UInt128 compressedHash);