1
0
Fork 0
mirror of https://github.com/Luzifer/scs-extract.git synced 2024-12-20 21:41:16 +00:00
scs-extract/b0rkhash/hash.go

194 lines
4.9 KiB
Go

// Package b0rkhash contains a broken implementation of the Google
// CityHash algorithm to access the SCS archive files of ETS2
//
//nolint:mnd
package b0rkhash
import (
"encoding/binary"
)
// Some primes between 2^63 and 2^64 for various uses.
const (
k0 uint64 = 0xc3a5c85c97cb3127
k1 uint64 = 0xb492b66fbe98f273
k2 uint64 = 0x9ae16a3b2f90404f
k3 uint64 = 0xc949d7c7509e6557
)
func fetch32(p []byte) uint32 {
return binary.LittleEndian.Uint32(p)
}
func fetch64(p []byte) uint64 {
r := binary.LittleEndian.Uint64(p)
return r
}
// Bitwise right rotate
func rotate(val uint64, shift uint) uint64 {
// Avoid shifting by 64: doing so yields an undefined result.
if shift == 0 {
return val
}
return (val >> shift) | val<<(64-shift)
}
func rotateByAtleast1(val uint64, shift uint) uint64 {
return (val >> shift) | (val << (64 - shift))
}
func shiftMix(val uint64) uint64 {
return val ^ (val >> 47)
}
func hash128to64(x Uint128) uint64 {
const mul = uint64(0x9ddfea08eb382d69)
a := (x.Low64() ^ x.High64()) * mul
a ^= (a >> 47)
b := (x.High64() ^ a) * mul
b ^= (b >> 47)
b *= mul
return b
}
func hashLen16(u, v uint64) uint64 {
return hash128to64(Uint128{u, v})
}
func hashLen0to16(s []byte, length int) uint64 {
if length > 8 {
a := fetch64(s)
b := fetch64(s[length-8:])
return hashLen16(a, rotateByAtleast1(b+uint64(length), uint(length))) ^ b
}
if length >= 4 {
a := uint64(fetch32(s))
return hashLen16(uint64(length)+(a<<3), uint64(fetch32(s[length-4:])))
}
if length > 0 {
a := s[0]
b := s[length>>1]
c := s[length-1]
y := uint32(a) + (uint32(b) << 8)
z := uint32(length) + (uint32(c) << 2)
return shiftMix(uint64(y)*k2^uint64(z)*k3) * k2
}
return k2
}
// This probably works well for 16-byte strings as well, but is may be overkill
// in that case
func hashLen17to32(s []byte, length int) uint64 {
a := fetch64(s) * k1
b := fetch64(s[8:])
c := fetch64(s[length-8:]) * k2
d := fetch64(s[length-16:]) * k0
return hashLen16(rotate(a-b, 43)+rotate(c, 30)+d,
a+rotate(b^k3, 20)-c+uint64(length)) //#nosec:G115 // Should never be negative
}
// Return a 16-byte hash for 48 bytes. Quick and dirty.
// callers do best to use "random-looking" values for a and b.
func weakHashLen32WithSeeds(w, x, y, z, a, b uint64) Uint128 {
a += w
b = rotate(b+a+z, 21)
c := a
a += x
a += y
b += rotate(a, 44)
return Uint128{a + z, b + c}
}
// Return a 16-byte hash for s[0] ... s[31], a, and b. Quick and dirty.
func weakHashLen32WithSeedsByte(s []byte, a, b uint64) Uint128 {
return weakHashLen32WithSeeds(
fetch64(s),
fetch64(s[8:]),
fetch64(s[16:]),
fetch64(s[24:]),
a,
b)
}
// Return an 8-byte hash for 33 to 64 bytes.
func hashLen33to64(s []byte, length int) uint64 {
z := fetch64(s[24:])
a := fetch64(s) + (uint64(length)+fetch64(s[length-16:]))*k0 //#nosec:G115 // Should never be negative
b := rotate(a+z, 52)
c := rotate(a, 37)
a += fetch64(s[8:])
c += rotate(a, 7)
a += fetch64(s[16:])
vf := a + z
vs := b + rotate(a, 31) + c
a = fetch64(s[16:]) + fetch64(s[length-32:])
z = fetch64(s[length-8:])
b = rotate(a+z, 52)
c = rotate(a, 37)
a += fetch64(s[length-24:])
c += rotate(a, 7)
a += fetch64(s[length-16:])
wf := a + z
ws := b + rotate(a, 31) + c
r := shiftMix((vf+ws)*k2 + (wf+vs)*k0)
return shiftMix(r*k0+vs) * k2
}
// CityHash64 return a 64-bit hash.
func CityHash64(s []byte) uint64 {
length := len(s)
if length <= 32 {
if length <= 16 {
return hashLen0to16(s, length)
}
return hashLen17to32(s, length)
} else if length <= 64 {
return hashLen33to64(s, length)
}
// For string over 64 bytes we hash the end first, and then as we
// loop we keep 56 bytes of state: v, w, x, y and z.
x := fetch64(s[length-40:])
y := fetch64(s[length-16:]) + fetch64(s[length-56:])
z := hashLen16(fetch64(s[length-48:])+uint64(length), fetch64(s[length-24:]))
v := weakHashLen32WithSeedsByte(s[length-64:], uint64(length), z)
w := weakHashLen32WithSeedsByte(s[length-32:], y+k1, x)
x = x*k1 + fetch64(s)
// Decrease len to the nearest multiple of 64, and operate on 64-byte chunks.
tmpLength := uint32(length) //#nosec:G115 // Should never be negative
tmpLength -= 1 & ^uint32(63)
for {
x = rotate(x+y+v.Low64()+fetch64(s[8:]), 37) * k1
y = rotate(y+v.High64()+fetch64(s[48:]), 42) * k1
x ^= w.High64()
y += v.Low64() + fetch64(s[40:])
z = rotate(z+w.Low64(), 33) * k1
v = weakHashLen32WithSeedsByte(s, v.High64()*k1, x+w.Low64())
w = weakHashLen32WithSeedsByte(s[32:], z+w.High64(), y+fetch64(s[16:]))
z, x = x, z
s = s[64:]
tmpLength -= 64
if tmpLength == 0 {
break
}
}
return hashLen16(
hashLen16(v.Low64(), w.Low64())+shiftMix(y)*k1+z,
hashLen16(v.High64(), w.High64())+x)
}
// CityHash64WithSeed return a 64-bit hash with a seed.
func CityHash64WithSeed(s []byte, seed uint64) uint64 {
return CityHash64WithSeeds(s, k2, seed)
}
// CityHash64WithSeeds return a 64-bit hash with two seeds.
func CityHash64WithSeeds(s []byte, seed0, seed1 uint64) uint64 {
return hashLen16(CityHash64(s)-seed0, seed1)
}