#!/ebrmain/bin/run_script -clear_screen # # KOReader Rescue — restores KOReader after a PocketBook firmware update # # PocketBook firmware updates frequently delete /applications/koreader.app, # the small launcher stub, while leaving the KOReader folder itself intact. # This tool keeps a copy of YOUR stub and puts it back when it disappears. # # Install: copy this file to /applications/ on the reader, eject, tap the icon # once while KOReader still works ("Armed"). Tap it again whenever # KOReader stops launching. # # A local backup is always preferred. Only when there is no backup at all does # it fall back to downloading a launcher over Wi-Fi, verified against a # manifest. It never ships a KOReader binary of its own. # # Screens are drawn with fbink (shipped by KOReader) rather than printed to # PocketBook's text console: on current firmware that console renders nothing # at all, so echo output is invisible to the reader. # # Free and unencumbered. Made by Inkstream. VERSION="1.2" STAMP="$(date '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo 'unknown time')" # ---------------------------------------------------------------- helpers --- LOG="" log() { [ -n "$LOG" ] && echo "$1" >>"$LOG" 2>/dev/null return 0 } size_of() { [ -f "$1" ] || return 1 wc -c <"$1" 2>/dev/null | tr -d ' \t' } sum_of() { [ -f "$1" ] || return 1 if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" 2>/dev/null | cut -d' ' -f1 elif command -v md5sum >/dev/null 2>&1; then md5sum "$1" 2>/dev/null | cut -d' ' -f1 else echo "size:$(size_of "$1")" fi } touched_recently() { [ -f "$1" ] || return 1 command -v find >/dev/null 2>&1 || return 1 [ -n "$(find "$1" -mmin "-$2" 2>/dev/null)" ] } # ------------------------------------------------------------ find storage --- ROOT="" for candidate in /mnt/ext1 /mnt/ext2; do if [ -d "$candidate/applications" ]; then if [ -d "$candidate/applications/koreader" ]; then ROOT="$candidate" break fi [ -z "$ROOT" ] && ROOT="$candidate" fi done if [ -z "$ROOT" ]; then echo "KOReader Rescue $VERSION" echo "Could not find an applications folder." echo "This tool only runs on PocketBook devices." sleep 12 exit 1 fi APPS="$ROOT/applications" KODIR="$APPS/koreader" STUB="$APPS/koreader.app" VAULT="$APPS/.koreader-rescue" BACKUP="$VAULT/koreader.app" META="$VAULT/stub.info" LOG="$VAULT/rescue.log" mkdir -p "$VAULT" 2>/dev/null { echo "" echo "=== $STAMP — KOReader Rescue $VERSION ===" } >>"$LOG" 2>/dev/null # ------------------------------------------------------------------ screen --- # # One screen per outcome: a kicker, one headline, a short paragraph, and a # quiet meta line. Drawn in a single pass, refreshed once, so the e-ink panel # flashes exactly one time. FBINK="$KODIR/fbink" FONT_R="$KODIR/fonts/noto/NotoSans-Regular.ttf" FONT_B="$KODIR/fonts/noto/NotoSans-Bold.ttf" can_draw() { [ -x "$FBINK" ] && [ -f "$FONT_R" ] && [ -f "$FONT_B" ] } # draw [line2] [line3] # # Body lines are passed separately instead of as one wrapped string so that # each sentence keeps its own line; fbink would otherwise break them wherever # the column happens to end. draw() { headline="$1" b1="$2" b2="$3" b3="$4" log "screen: $headline / $b1 $b2 $b3" if ! can_draw; then echo "KOReader Rescue $VERSION" echo "$headline" [ -n "$b1" ] && echo "$b1" [ -n "$b2" ] && echo "$b2" [ -n "$b3" ] && echo "$b3" return 0 fi "$FBINK" -q -k >/dev/null 2>&1 # kicker "$FBINK" -q -b -m -t regular="$FONT_B",px=22,top=120,left=70,right=70 \ "KOREADER RESCUE" >/dev/null 2>&1 # headline "$FBINK" -q -b -m -t regular="$FONT_B",px=54,top=300,left=60,right=60 \ "$headline" >/dev/null 2>&1 # body, one line at a time row=430 for line in "$b1" "$b2" "$b3"; do [ -z "$line" ] && continue "$FBINK" -q -b -m -t regular="$FONT_R",px=30,top=$row,left=80,right=80 \ "$line" >/dev/null 2>&1 row=$((row + 46)) done # meta, measured back from the bottom edge "$FBINK" -q -b -m -t regular="$FONT_R",px=22,top=-190,left=70,right=70 \ "KOReader $KOREV" >/dev/null 2>&1 "$FBINK" -q -b -m -t regular="$FONT_R",px=20,top=-130,left=70,right=70 \ "made by Inkstream" >/dev/null 2>&1 # one flashing refresh for the whole page "$FBINK" -q -f -s >/dev/null 2>&1 return 0 } # Hold the screen long enough to be read, and show that the wait is finite # rather than leaving the reader looking frozen. hold() { if ! can_draw; then sleep 12 return 0 fi step=0 while [ "$step" -le 10 ]; do "$FBINK" -q -y -2 -P $((step * 10)) >/dev/null 2>&1 sleep 1 step=$((step + 1)) done return 0 } # --------------------------------------------------------------- network ---- # # Overridable: put BASE_URL="https://your.server/path" in # /applications/.koreader-rescue/config to point at your own mirror. BASE_URL="https://inkstream.tukew.com/fixkoreader" [ -f "$VAULT/config" ] && . "$VAULT/config" 2>/dev/null download() { command -v wget >/dev/null 2>&1 || return 1 rm -f "$2" 2>/dev/null wget -q -T 25 -O "$2" "$1" 2>/dev/null && [ -s "$2" ] && return 0 wget -q -T 25 --no-check-certificate -O "$2" "$1" 2>/dev/null && [ -s "$2" ] && return 0 case "$1" in https://*) wget -q -T 25 -O "$2" "http://${1#https://}" 2>/dev/null && [ -s "$2" ] && return 0 ;; esac rm -f "$2" 2>/dev/null return 1 } fetch_stub() { dest="$1" mf="$VAULT/manifest.tmp" tmp="$VAULT/stub.tmp" download "$BASE_URL/manifest.txt" "$mf" || { log "manifest download failed" return 1 } kover="$(echo "$KOREV" | sed 's/^v//; s/[-_].*//' 2>/dev/null)" line="" [ -n "$kover" ] && line="$(awk -v v="$kover" '$1==v {print; exit}' "$mf" 2>/dev/null)" [ -z "$line" ] && line="$(awk '$1=="latest" {print; exit}' "$mf" 2>/dev/null)" rm -f "$mf" 2>/dev/null [ -z "$line" ] && return 1 url="$(echo "$line" | awk '{print $2}')" want_size="$(echo "$line" | awk '{print $3}')" want_sum="$(echo "$line" | awk '{print $4}')" [ -z "$url" ] && return 1 download "$url" "$tmp" || return 1 got_size="$(size_of "$tmp")" if [ -n "$want_size" ] && [ "$got_size" != "$want_size" ]; then log "size mismatch: got $got_size want $want_size" rm -f "$tmp" 2>/dev/null return 1 fi if command -v sha256sum >/dev/null 2>&1 && [ -n "$want_sum" ]; then got_sum="$(sha256sum "$tmp" 2>/dev/null | cut -d' ' -f1)" if [ "$got_sum" != "$want_sum" ]; then log "checksum mismatch" rm -f "$tmp" 2>/dev/null return 1 fi else log "warning: checksum not verified" fi mv "$tmp" "$dest" 2>/dev/null || return 1 sync { echo "KOReader $KOREV — stub downloaded $STAMP" echo "size=$(size_of "$dest")" echo "sum=$(sum_of "$dest")" echo "from=$url" } >"$META" 2>/dev/null return 0 } # ------------------------------------------------------- read installed rev --- # # KOReader writes this file as git-rev; older trees used git_rev. Read whichever # is actually there instead of silently reporting "unknown". KOREV="unknown" for revfile in "$KODIR/git-rev" "$KODIR/git_rev"; do if [ -f "$revfile" ]; then KOREV="$(head -n 1 "$revfile" 2>/dev/null)" break fi done [ -z "$KOREV" ] && KOREV="unknown" STUB_SIZE="$(size_of "$STUB" 2>/dev/null)" [ -z "$STUB_SIZE" ] && STUB_SIZE=0 # --------------------------------------------------------------- no KOReader -- if [ ! -d "$KODIR" ]; then log "koreader directory missing" draw "Nothing to protect" \ "KOReader is not installed on this reader." \ "Install it first, then open this tool" \ "once to arm it." hold exit 1 fi # ------------------------------------------------------------ stub is gone ---- if [ "$STUB_SIZE" -lt 1024 ]; then log "stub missing or too small: $STUB_SIZE bytes" if [ ! -f "$BACKUP" ]; then draw "Looking for a launcher" \ "There is no backup on this reader yet," \ "so this tool is asking over Wi-Fi." \ "One moment." if fetch_stub "$BACKUP"; then log "downloaded stub" else draw "No launcher found" \ "There is no backup and Wi-Fi did not help." \ "Connect to Wi-Fi and open this tool again," \ "or copy koreader.app over USB." hold exit 1 fi fi rm -f "$STUB" 2>/dev/null if cp "$BACKUP" "$STUB" 2>/dev/null; then chmod 755 "$STUB" 2>/dev/null sync NEW_SIZE="$(size_of "$STUB")" if [ -n "$NEW_SIZE" ] && [ "$NEW_SIZE" -gt 1024 ]; then log "restored stub, $NEW_SIZE bytes" draw "KOReader is back" \ "The launcher was missing, and has been" \ "put back from your backup." \ "Restart the reader, then open KOReader." else log "restore landed wrong: $NEW_SIZE" draw "That did not land" \ "The launcher was copied but came out wrong." \ "Disconnect USB, restart the reader," \ "and open this tool again." fi else log "could not write stub" draw "Could not write" \ "The applications folder is not writable" \ "right now. Disconnect USB, restart the" \ "reader, and open this tool again." fi hold exit 0 fi # ------------------------------------------------- stub is present and sane --- CURRENT_SUM="$(sum_of "$STUB")" BACKUP_SUM="" [ -f "$BACKUP" ] && BACKUP_SUM="$(sum_of "$BACKUP")" if [ -z "$BACKUP_SUM" ]; then HEADLINE="Armed" BODY1="Your launcher is saved." elif [ "$CURRENT_SUM" != "$BACKUP_SUM" ]; then HEADLINE="Backup updated" BODY1="KOReader changed, so the saved copy did too." else HEADLINE="All good" BODY1="Your launcher is safe and matches the backup." fi if [ "$CURRENT_SUM" != "$BACKUP_SUM" ]; then cp "$STUB" "$BACKUP.tmp" 2>/dev/null && mv "$BACKUP.tmp" "$BACKUP" 2>/dev/null sync { echo "KOReader $KOREV — stub saved $STAMP" echo "size=$STUB_SIZE" echo "sum=$CURRENT_SUM" } >"$META" 2>/dev/null fi log "$HEADLINE (stub $STUB_SIZE bytes, rev $KOREV)" # -------------------------------------------------- diagnose the other case --- # # A healthy launcher and a fresh crash log mean the launcher is not the # problem, and this tool would otherwise send the reader away reassured while # KOReader still refuses to open. CRASH="$KODIR/crash.log" if touched_recently "$CRASH" 120; then log "recent crash detected" draw "Launcher is fine" \ "KOReader itself is crashing, which is a" \ "different problem this tool cannot repair." \ "A newer KOReader build usually fixes it." else draw "$HEADLINE" \ "$BODY1" \ "If KOReader disappears after a firmware" \ "update, open this tool again." fi hold exit 0