mirror · freeslapmac-silentbyte-identical

Every file in v1.0.0, exactly as shipped

These are copies, not re-exports: each row's sha-256 is the hash of the file served here and of the same file inside the zip. Both are checked against the package before publication, and the live site is re-hashed afterwards.

⬇ Download the zip (7,529 B) Read the analysis

zip sha-256 f937fce517256ca847d9506ec0684689cbc3d21b4d8c7b1440d23644545521fc

filebyteslinessha-256 of the served copy
build.sh3,82780ea56b6d768ecca9059270e857365e385036e1ec7d3c2674f96f5916152ff7602
verify.sh3,808781c9eb94a91617f366f55bde35f1f3bec7463b4d915135e467f9bcbfdc01cb8ab
switch.sh1,10631da038ea7e2faad2c91f1cd339c620d0401aab4fedd0d56462b46cc3db38d482f
README.md4,31110573200526b52ebf46e26b3ba1e327ac66c8c4f755607de96bb3fc2eb5468ad989
LICENSE1,067215d963dad33771425d4e62016aff764d49296198333f3c104ace6ca710208feb4
build.sh · 3,827 B · 80 lines · sha256 ea56b6d768ecca90…
#!/bin/bash
# freeslapmac-silent — build a silent copy of FreeSlapMac.
#
# FreeSlapMac has no mute: its slap/tap sounds are MP3s inside
# Contents/Resources/Sounds/, played by the bundled `spank` process. This script copies
# the app and overwrites every MP3 with a hand-built silent MPEG frame of the same
# duration, then ad-hoc re-signs the copy (the original is ad-hoc signed too).
# Detection, screen flash, combos, counters and settings are untouched.
#
# Usage:  bash build.sh [SOURCE_APP] [DEST_APP]
# Default: bash build.sh "/Applications/FreeSlapMac.app" "/Applications/FreeSlapMac Silent.app"
# Needs:  macOS only. No python, no brew, no ffmpeg — afinfo/afconvert/codesign ship with macOS.

set -euo pipefail

SRC="${1:-/Applications/FreeSlapMac.app}"
DEST="${2:-/Applications/FreeSlapMac Silent.app}"
SOUNDS_REL="Contents/Resources/Sounds"

die() { printf 'error: %s\n' "$*" >&2; exit 1; }

[ -d "$SRC" ] || die "source app not found: $SRC"
[ -d "$SRC/$SOUNDS_REL" ] || die "not a FreeSlapMac bundle (no $SOUNDS_REL): $SRC"
[ "$(cd "$SRC" && pwd -P)" != "$(cd "$DEST" 2>/dev/null && pwd -P || echo -)" ] || die "source and destination are the same app"
if [ -e "$DEST" ] && [ "${FORCE:-0}" != "1" ]; then
  die "destination already exists: $DEST  (re-run with FORCE=1 to replace it)"
fi

TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT

# One silent MPEG-1 Layer III frame: 44.1 kHz, mono, 128 kb/s = 417 bytes, 1152 samples
# (26.122 ms). Zeroed side-info + zeroed main data decode to exact digital silence.
printf '\xff\xfb\x90\xc0' > "$TMP/frame"
head -c 413 /dev/zero >> "$TMP/frame"
[ "$(wc -c < "$TMP/frame" | tr -d ' ')" = "417" ] || die "frame build failed"
FRAME_MS=26.122448979591837
# 3000 frames (78 s) covers every clip in the shipped library with room to spare.
: > "$TMP/silence.mp3"
args=(); for ((i = 0; i < 3000; i++)); do args+=("$TMP/frame"); done
cat "${args[@]}" > "$TMP/silence.mp3"

printf 'copying %s\n     -> %s\n' "$SRC" "$DEST"
rm -rf "$DEST"
ditto "$SRC" "$DEST"

total=0 replaced=0
while IFS= read -r -d '' f; do
  total=$((total + 1))
  dur="$(afinfo "$f" 2>/dev/null | awk -F': ' '/estimated duration/ {print $2; exit}' | awk '{print $1}')"
  if [ -z "$dur" ]; then
    printf 'warn: no duration for %s — using 1.0 s\n' "$f" >&2
    dur=1.0
  fi
  frames="$(awk -v d="$dur" -v m="$FRAME_MS" 'BEGIN { n = int(d * 1000 / m + 0.5); if (n < 1) n = 1; print n }')"
  head -c $((frames * 417)) "$TMP/silence.mp3" > "$f.new" && mv "$f.new" "$f"
  replaced=$((replaced + 1))
done < <(find "$DEST/$SOUNDS_REL" -name '*.mp3' -print0)
[ "$replaced" -gt 0 ] || die "no MP3s found to silence under $DEST/$SOUNDS_REL"

# Distinguish it in the menu bar / Dock. Bundle id is deliberately preserved so the
# silent build inherits your sensitivity, mode, cooldown and creak preferences.
PB=/usr/libexec/PlistBuddy
INFO="$DEST/Contents/Info.plist"
$PB -c "Set :CFBundleName FreeSlapMac Silent" "$INFO" 2>/dev/null || $PB -c "Add :CFBundleName string FreeSlapMac Silent" "$INFO"
$PB -c "Set :CFBundleDisplayName FreeSlapMac Silent" "$INFO" 2>/dev/null || $PB -c "Add :CFBundleDisplayName string FreeSlapMac Silent" "$INFO"

# Modifying resources breaks the original seal — re-sign ad-hoc, exactly like the original.
codesign --force --deep --sign - "$DEST" >/dev/null 2>&1 || die "codesign failed"
xattr -cr "$DEST" 2>/dev/null || true

printf 'silenced %d/%d MP3s, re-signed ad-hoc\n' "$replaced" "$total"

if [ -x "$(dirname "$0")/verify.sh" ] || [ -f "$(dirname "$0")/verify.sh" ]; then
  printf '\nverifying…\n'
  bash "$(dirname "$0")/verify.sh" "${VERIFY_SRC:-$SRC}" "$DEST"
else
  printf '\n(verify.sh not found next to build.sh — skipping the proof step)\n'
fi

printf '\nDone. Launch with:\n  open -a "%s"\nRun only one build at a time — both write /tmp/freeslapmac_cmd.\n' "$DEST"
verify.sh · 3,808 B · 78 lines · sha256 1c9eb94a91617f36…
#!/bin/bash
# freeslapmac-silent — prove the silent build really is silent.
#
# For every MP3 in the destination bundle this decodes the file with macOS's own
# AudioToolbox (afconvert) — the same decoder `spank` uses to play it — and asserts the
# PCM payload contains no non-zero byte at all, and that its duration still matches the
# original clip. Also checks the signature, the bundle id and the executable.
#
# Usage: bash verify.sh [SOURCE_APP] [DEST_APP]

set -uo pipefail

SRC="${1:-/Applications/FreeSlapMac.app}"
DEST="${2:-/Applications/FreeSlapMac Silent.app}"
SOUNDS_REL="Contents/Resources/Sounds"
fail=0
say() { printf '%s\n' "$*"; }
bad() { printf '  ✗ %s\n' "$*"; fail=$((fail + 1)); }

[ -d "$SRC" ] || { echo "no source app: $SRC" >&2; exit 2; }
[ -d "$DEST" ] || { echo "no destination app: $DEST" >&2; exit 2; }

TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT

dur() { afinfo "$1" 2>/dev/null | awk -F': ' '/estimated duration/ {print $2; exit}' | awk '{print $1}'; }

say "== files =="
srcfiles=(); while IFS= read -r l; do srcfiles+=("$l"); done < <(find "$SRC/$SOUNDS_REL" -name '*.mp3' | sort)
dstfiles=(); while IFS= read -r l; do dstfiles+=("$l"); done < <(find "$DEST/$SOUNDS_REL" -name '*.mp3' | sort)
say "  original: ${#srcfiles[@]} mp3   silent: ${#dstfiles[@]} mp3"
[ "${#srcfiles[@]}" -eq "${#dstfiles[@]}" ] && [ "${#dstfiles[@]}" -gt 0 ] || bad "file count differs"

say "== decode check (AudioToolbox) =="
n=0 silent=0 loud=0 durdrift=0
for f in "${dstfiles[@]}"; do
  n=$((n + 1))
  wav="$TMP/x.wav"
  if ! afconvert -f WAVE -d LEI16 "$f" "$wav" 2>/dev/null; then bad "cannot decode $f"; continue; fi
  off="$(LC_ALL=C grep -abo 'data' "$wav" | head -1 | cut -d: -f1)"
  [ -n "$off" ] || { bad "no data chunk in $f"; continue; }
  nz="$(tail -c +$((off + 9)) "$wav" | LC_ALL=C tr -d '\000' | wc -c | tr -d ' ')"
  if [ "$nz" = "0" ]; then silent=$((silent + 1)); else loud=$((loud + 1)); bad "audible content in $f ($nz non-zero bytes)"; fi

  rel="${f#"$DEST"/}"
  if [ -f "$SRC/$rel" ]; then
    d1="$(dur "$f")"; d2="$(dur "$SRC/$rel")"
    awk -v a="$d1" -v b="$d2" 'BEGIN { exit (a - b < 0.10 && b - a < 0.10) ? 0 : 1 }' || { durdrift=$((durdrift + 1)); bad "duration drift $rel: $d2 s -> $d1 s"; }
  fi
  rm -f "$wav"
done
say "  decoded: $n   digitally silent: $silent   audible: $loud   duration mismatches: $durdrift"

say "== signature / identity =="
codesign --verify --deep --strict "$DEST" >/dev/null 2>&1 && say "  signature: valid (ad-hoc)" || bad "signature invalid"
src_id="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$SRC/Contents/Info.plist")"
dst_id="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$DEST/Contents/Info.plist")"
[ "$src_id" = "$dst_id" ] && say "  bundle id: $dst_id (settings carry over)" || bad "bundle id changed: $src_id -> $dst_id"
dst_name="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleName' "$DEST/Contents/Info.plist")"
say "  display name: $dst_name"

cp "$SRC/Contents/MacOS/$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$SRC/Contents/Info.plist")" "$TMP/a.bin" 2>/dev/null
cp "$DEST/Contents/MacOS/$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$DEST/Contents/Info.plist")" "$TMP/b.bin" 2>/dev/null
codesign --remove-signature "$TMP/a.bin" 2>/dev/null; codesign --remove-signature "$TMP/b.bin" 2>/dev/null
if cmp -s "$TMP/a.bin" "$TMP/b.bin"; then
  say "  executable: identical code ($(shasum -a 256 "$TMP/b.bin" | cut -c1-16)…)"
else
  bad "executable differs from the original"
fi

say "== size =="
say "  original $(du -sh "$SRC" | cut -f1)   silent $(du -sh "$DEST" | cut -f1)"

if [ "$fail" -eq 0 ]; then
  say "PASS — every clip decodes to digital silence, signature valid, same code and settings."
  exit 0
fi
say "FAIL — $fail problem(s) above."
exit 1
switch.sh · 1,106 B · 31 lines · sha256 da038ea7e2faad2c…
#!/bin/bash
# freeslapmac-silent — switch between the loud and silent builds.
# Only one may run at a time: both write /tmp/freeslapmac_cmd, and each launch kills
# every spank process it can find.
#
# Usage: bash switch.sh silent | loud

set -uo pipefail

LOUD="${LOUD_APP:-/Applications/FreeSlapMac.app}"
SILENT="${SILENT_APP:-/Applications/FreeSlapMac Silent.app}"

case "${1:-}" in
  silent) TARGET="$SILENT" ;;
  loud)   TARGET="$LOUD" ;;
  *)      echo "usage: bash switch.sh silent|loud" >&2; exit 2 ;;
esac

[ -d "$TARGET" ] || { echo "not installed: $TARGET" >&2; echo "build it first: bash build.sh" >&2; exit 1; }

# Quit politely so the app can tear down its root wrapper (a hard kill leaves it running).
osascript -e 'quit app "FreeSlapMac Silent"' >/dev/null 2>&1
osascript -e 'quit app "FreeSlapMac"' >/dev/null 2>&1
for _ in 1 2 3 4 5 6; do pgrep -x FreeSlapMac >/dev/null 2>&1 || break; sleep 0.5; done
if pgrep -x FreeSlapMac >/dev/null 2>&1; then
  echo "FreeSlapMac is still running — quit it manually, then re-run this." >&2
  exit 1
fi

open -a "$TARGET"
echo "launched: $TARGET"
README.md · 4,311 B · 105 lines · sha256 73200526b52ebf46…
# FreeSlapMac Silent

A silent build of [FreeSlapMac](https://freeslapmac.app) — same detection, same screen flash, same combo
counter, **no sound**. Plus the reason it needs to exist, which is a one-word answer: FreeSlapMac has no mute.

## Why this is necessary

The slap/tap sounds are not played by the app. They are MP3s in
`FreeSlapMac.app/Contents/Resources/Sounds/` played by the bundled `spank` process, and `spank` exposes no
volume or mute switch:

```
$ /Applications/FreeSlapMac.app/Contents/MacOS/spank --help
  --custom DIR  --min-amplitude F  --cooldown MS  --speed F  --volume-scaling
```

The app itself stores only `sensitivity`, `triggerMode`, `screenFlash`, `launchAtLogin` and
`_creakingEnabled` — no volume, no `soundEnabled`. So "turn the sound off" has no supported answer in 1.1.0.
This does not patch the app or hook its audio: it builds a *copy* whose entire sound library is digital
silence, so there is nothing to play. Detection and visuals never touch the audio path, so they keep working.

## Requirements

macOS. That's it. No python, no Homebrew, no ffmpeg — it uses `afinfo`, `afconvert`, `codesign`, `ditto`
and `PlistBuddy`, all of which ship with the OS. You do need FreeSlapMac installed, since the build starts
from your own copy (the app is not redistributed here).

## Build

```sh
bash build.sh
# → /Applications/FreeSlapMac Silent.app
```

Custom locations:

```sh
bash build.sh "/Applications/FreeSlapMac.app" "$HOME/Applications/FreeSlapMac Silent.app"
FORCE=1 bash build.sh          # replace an existing silent build
```

It copies the bundle, overwrites every MP3 with a hand-built silent MPEG frame of the same duration,
renames the copy so you can tell the two apart, re-signs it ad-hoc (the original is ad-hoc signed too) and
strips quarantine. ~270 files, about a minute.

## Verify it yourself

```sh
bash verify.sh
```

For each clip it decodes the file with macOS's own AudioToolbox — the same decoder `spank` uses — and asserts
the PCM contains no non-zero byte, that the duration still matches the original, that the signature is valid
and that the executable is byte-identical to the original (only the signature blob differs). Expected output:

```
== files ==
  original: 267 mp3   silent: 267 mp3
== decode check (AudioToolbox) ==
  decoded: 267   digitally silent: 267   audible: 0   duration mismatches: 0
== signature / identity ==
  signature: valid (ad-hoc)
  bundle id: com.devesh.freeslapmac (settings carry over)
  display name: FreeSlapMac Silent
  executable: identical code
PASS — every clip decodes to digital silence, signature valid, same code and settings.
```

Zero non-zero bytes is what silence means here. A trimmed file, a sped-up file or a "quiet" file would fail.

## Use

```sh
bash switch.sh silent    # quit the loud build, launch the silent one
bash switch.sh loud      # back to sound
```

One at a time only: both builds write `/tmp/freeslapmac_cmd` and each launch kills every `spank` it finds, so
two instances fight each other. First launch asks for your admin password once, exactly like the original —
`spank` needs it to read the accelerometer.

Settings are shared (same bundle id), so sensitivity, sound mode, cooldown and the lid-creak toggle carry over
untouched. If you want the silent build at login, remove "Open at Login" from the loud one first — login items
resolve by bundle id, and both builds share it.

## Uninstall

```sh
rm -rf "/Applications/FreeSlapMac Silent.app"
```

The original app is never modified.

## Notes

- **Lid creak is not covered.** If you use a lid-creak helper that plays its own audio (mine does), that sound
  does not come from this bundle; disable it in FreeSlapMac's own "Lid Creaking Sound" switch or in the helper.
- **Security, unrelated to this script:** FreeSlapMac starts a *root* wrapper that `eval`s whatever any local
  process writes to the world-writable FIFO `/tmp/freeslapmac_cmd`. While it runs, any user on the machine can
  get root. This build changes none of that — don't leave either build running on a shared or guest account,
  and it is worth reporting upstream.
- Not affiliated with FreeSlapMac. No third-party audio is redistributed: the silence is generated locally and
  the app itself is never shipped.

MIT — see LICENSE.
LICENSE · 1,067 B · 21 lines · sha256 5d963dad33771425…
MIT License

Copyright (c) 2026 Isaac Chan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.