From 4c34f0bedcceaa0b74516a0cebe09e17e92d70e6 Mon Sep 17 00:00:00 2001 From: Maksim Totmin Date: Wed, 19 Aug 2026 12:03:54 +0700 Subject: [PATCH] Initial commit: Go driver for the Lian Li Galahad II LCD Streams H.264 to the Galahad II LCD via USB (gousb) with: - ffmpeg CLI transcoding (no fragile FFmpeg C-ABI bindings) - pure-Go Annex-B access-unit splitting - framerate pacing with graceful shutdown and USB reconnect - TOML config, cobra CLI, structured slog logging, unit tests --- .gitignore | 3 + Makefile | 33 ++++ README.md | 123 +++++++++++++ cmd/galahad2lcd/main.go | 21 +++ go.mod | 14 ++ go.sum | 14 ++ internal/cli/cli_test.go | 107 ++++++++++++ internal/cli/daemon.go | 106 ++++++++++++ internal/cli/flags.go | 48 ++++++ internal/cli/list.go | 55 ++++++ internal/cli/root.go | 66 +++++++ internal/cli/set.go | 78 +++++++++ internal/cli/signal.go | 14 ++ internal/config/config.go | 145 ++++++++++++++++ internal/config/config_test.go | 84 +++++++++ internal/device/device.go | 21 +++ internal/device/list.go | 57 +++++++ internal/device/usb.go | 153 +++++++++++++++++ internal/h264/h264.go | 117 +++++++++++++ internal/h264/h264_test.go | 203 ++++++++++++++++++++++ internal/protocol/protocol.go | 64 +++++++ internal/protocol/protocol_test.go | 104 +++++++++++ internal/service/service.go | 34 ++++ internal/service/service_test.go | 59 +++++++ internal/stream/stream.go | 86 ++++++++++ internal/stream/stream_test.go | 143 ++++++++++++++++ internal/transcoder/transcoder.go | 228 +++++++++++++++++++++++++ internal/transcoder/transcoder_test.go | 123 +++++++++++++ packaging/99-galahad2lcd.rules | 3 + packaging/galahad2lcd.service | 15 ++ packaging/install.sh | 80 +++++++++ packaging/uninstall.sh | 31 ++++ testdata/sample.h264 | Bin 0 -> 129859 bytes 33 files changed, 2432 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 cmd/galahad2lcd/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/cli/cli_test.go create mode 100644 internal/cli/daemon.go create mode 100644 internal/cli/flags.go create mode 100644 internal/cli/list.go create mode 100644 internal/cli/root.go create mode 100644 internal/cli/set.go create mode 100644 internal/cli/signal.go create mode 100644 internal/config/config.go create mode 100644 internal/config/config_test.go create mode 100644 internal/device/device.go create mode 100644 internal/device/list.go create mode 100644 internal/device/usb.go create mode 100644 internal/h264/h264.go create mode 100644 internal/h264/h264_test.go create mode 100644 internal/protocol/protocol.go create mode 100644 internal/protocol/protocol_test.go create mode 100644 internal/service/service.go create mode 100644 internal/service/service_test.go create mode 100644 internal/stream/stream.go create mode 100644 internal/stream/stream_test.go create mode 100644 internal/transcoder/transcoder.go create mode 100644 internal/transcoder/transcoder_test.go create mode 100644 packaging/99-galahad2lcd.rules create mode 100644 packaging/galahad2lcd.service create mode 100755 packaging/install.sh create mode 100755 packaging/uninstall.sh create mode 100644 testdata/sample.h264 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..36e0027 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin/ +*.h264 +!testdata/sample.h264 \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e97cb15 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +BINARY := galahad2lcd +PKG := ./cmd/galahad2lcd +VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) +LDFLAGS := -s -w -X main.version=$(VERSION) + +.PHONY: all build test vet lint fmt install uninstall clean + +all: build + +build: + go build -trimpath -ldflags "$(LDFLAGS)" -o bin/$(BINARY) $(PKG) + +test: + go test ./... + +vet: + go vet ./... + +fmt: + gofmt -l -w cmd internal + +lint: + go vet ./... + gofmt -l cmd internal + +install: build + install -m 0755 bin/$(BINARY) /usr/local/bin/$(BINARY) + +uninstall: + rm -f /usr/local/bin/$(BINARY) + +clean: + rm -rf bin \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c92e73b --- /dev/null +++ b/README.md @@ -0,0 +1,123 @@ +# galahad2lcd + +A modern Go driver for the **Lian Li Galahad II LCD** cooler display. It +transcodes a video or GIF to H.264 and streams it over USB to the 480×480 +LCD, running as a systemd service. + +This is a clean-room rewrite of the archived +[GalahadII_LCD_Linux](https://github.com/H4rk3nz0/GalahadII_LCD_Linux) +project. It keeps the exact wire protocol but drops the fragile FFmpeg C-ABI +dependency in favour of the system `ffmpeg` binary, adds structured logging, +automatic USB reconnection and a testable, layered architecture. + +## Features + +- `daemon` — transcodes media to H.264 and streams it in a loop +- `set` — change the input/rotation/speed and restart the service +- `list` — enumerate USB devices to diagnose connectivity +- `version` — print the build version +- Automatic reconnect if the USB link is lost (e.g. after suspend) +- Graceful shutdown on SIGINT/SIGTERM (`systemctl stop` works cleanly) +- Structured logs (`log/slog`) captured by journald +- Rotation, playback speed and framerate overrides via config or flags + +## Requirements + +- Go 1.22+ (to build) +- FFmpeg + FFprobe (any recent version, e.g. `sudo pacman -S ffmpeg`) +- libusb (transitive build dependency of `github.com/google/gousb`) +- systemd + +## Install + +```sh +sudo ./packaging/install.sh /path/to/media.gif +``` + +The script builds the binary, installs the systemd unit and udev rule, writes +an initial config and starts the service. If no media file is given, configure +it afterwards: + +```sh +sudo galahad2lcd set --input /path/to/media.gif --rotate 0 +``` + +## Configuration + +Settings live in `/etc/galahad2lcd.toml`: + +```toml +[display] +input = "/home/user/media.gif" +rotate = 0 # 0, 90, 180, 270 (clockwise) +speed = 1.0 # >1 slower, <1 faster + +[usb] +vendor_id = 0x0416 +product_id = 0x7395 + +[stream] +fps = 0 # 0 = auto-detect from the source +cache_path = "/tmp/galahad_cache.h264" +``` + +The `set` command updates this file and restarts the service: + +```sh +sudo galahad2lcd set --input /home/user/media.gif --rotate 90 --speed 1.2 +sudo galahad2lcd set --fps 15 --no-restart # apply later +``` + +CLI flags also override the config for one-off runs: + +```sh +sudo galahad2lcd daemon --input /home/user/media.gif --rotate 180 +``` + +## Diagnostics + +```sh +galahad2lcd list # find the LCD on the USB bus (needs udev rule or root) +journalctl -u galahad2lcd -f # live daemon logs +``` + +The bundled udev rule (`packaging/99-galahad2lcd.rules`) grants the +logged-in session user access to the device so `list` works without root. + +## Architecture + +``` +cmd/galahad2lcd entry point (composition root) +internal/cli cobra commands: daemon, set, list, version +internal/config TOML configuration model, load/save/validate +internal/transcoder ffmpeg/ffprobe wrapper, filter graph, FPS detection +internal/h264 Annex-B stream → access-unit splitting (pure Go) +internal/stream frame-rate pacing loop (testable, device-agnostic) +internal/device Device interface + gousb (libusb) implementation +internal/protocol USB framing (pure functions, golden-tested) +internal/service systemd unit management +``` + +Layers depend only downward. `stream` talks to a `device.Device` interface, +so the transport can be swapped or faked in tests without touching the rest +of the pipeline. + +## Development + +```sh +make build # build ./bin/galahad2lcd +make test # unit + integration tests (integration needs ffmpeg) +make vet # go vet +make fmt # gofmt +make install # copy binary to /usr/local/bin +``` + +## Uninstall + +```sh +sudo ./packaging/uninstall.sh +``` + +## License + +MIT \ No newline at end of file diff --git a/cmd/galahad2lcd/main.go b/cmd/galahad2lcd/main.go new file mode 100644 index 0000000..97391b0 --- /dev/null +++ b/cmd/galahad2lcd/main.go @@ -0,0 +1,21 @@ +// Command galahad2lcd is a Go driver for the Lian Li Galahad II LCD. +package main + +import ( + "fmt" + "os" + + "galahad2lcd/internal/cli" +) + +// version is overridden at build time with +// +// -ldflags "-X main.version=" +var version = "dev" + +func main() { + if err := cli.Execute(version); err != nil { + fmt.Fprintln(os.Stderr, "error:", err) + os.Exit(1) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..01254da --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module galahad2lcd + +go 1.26.6 + +require ( + github.com/BurntSushi/toml v1.6.0 + github.com/google/gousb v1.1.3 + github.com/spf13/cobra v1.10.2 +) + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.9 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5e989ca --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/google/gousb v1.1.3 h1:xt6M5TDsGSZ+rlomz5Si5Hmd/Fvbmo2YCJHN+yGaK4o= +github.com/google/gousb v1.1.3/go.mod h1:GGWUkK0gAXDzxhwrzetW592aOmkkqSGcj5KLEgmCVUg= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go new file mode 100644 index 0000000..696a491 --- /dev/null +++ b/internal/cli/cli_test.go @@ -0,0 +1,107 @@ +package cli + +import ( + "os" + "path/filepath" + "testing" + + "github.com/spf13/cobra" + + "galahad2lcd/internal/config" +) + +func TestApplyOverridesChangedFlags(t *testing.T) { + dir := t.TempDir() + src := filepath.Join(dir, "media.gif") + if err := os.WriteFile(src, []byte("gif"), 0o644); err != nil { + t.Fatal(err) + } + + cmd := &cobra.Command{} + var input string + var rotate int + var speed, fps float64 + cmd.Flags().StringVar(&input, "input", "", "") + cmd.Flags().IntVar(&rotate, "rotate", 0, "") + cmd.Flags().Float64Var(&speed, "speed", 0, "") + cmd.Flags().Float64Var(&fps, "fps", 0, "") + _ = cmd.Flags().Set("input", src) + _ = cmd.Flags().Set("rotate", "180") + _ = cmd.Flags().Set("speed", "1.5") + _ = cmd.Flags().Set("fps", "24") + + cfg := config.Default() + if err := applyOverrides(cmd, &cfg, input, rotate, speed, fps); err != nil { + t.Fatalf("applyOverrides error = %v", err) + } + if cfg.Display.Input != src { + t.Errorf("input = %q, want %q", cfg.Display.Input, src) + } + if cfg.Display.Rotate != 180 { + t.Errorf("rotate = %d, want 180", cfg.Display.Rotate) + } + if cfg.Display.Speed != 1.5 { + t.Errorf("speed = %v, want 1.5", cfg.Display.Speed) + } + if cfg.Stream.FPS != 24 { + t.Errorf("fps = %v, want 24", cfg.Stream.FPS) + } +} + +func TestApplyOverridesUnchangedFlags(t *testing.T) { + cmd := &cobra.Command{} + var input string + var rotate int + var speed, fps float64 + cmd.Flags().StringVar(&input, "input", "", "") + cmd.Flags().IntVar(&rotate, "rotate", 0, "") + cmd.Flags().Float64Var(&speed, "speed", 0, "") + cmd.Flags().Float64Var(&fps, "fps", 0, "") + + cfg := config.Default() + if err := applyOverrides(cmd, &cfg, input, rotate, speed, fps); err != nil { + t.Fatalf("applyOverrides error = %v", err) + } + if cfg.Display.Input != "" || cfg.Display.Rotate != 0 { + t.Errorf("unset flags changed config: %+v", cfg) + } +} + +func TestResolveInput(t *testing.T) { + dir := t.TempDir() + file := filepath.Join(dir, "x.gif") + if err := os.WriteFile(file, nil, 0o644); err != nil { + t.Fatal(err) + } + + abs, err := resolveInput(file) + if err != nil { + t.Fatalf("resolveInput error = %v", err) + } + if abs != file { + t.Errorf("resolveInput = %q, want %q", abs, file) + } + + if _, err := resolveInput(filepath.Join(dir, "missing.gif")); err == nil { + t.Error("resolveInput returned nil error for missing file") + } + if _, err := resolveInput(dir); err == nil { + t.Error("resolveInput returned nil error for a directory") + } +} + +func TestRootVersionCommand(t *testing.T) { + root := newRootCmd("1.2.3") + root.SetArgs([]string{"version"}) + if err := root.Execute(); err != nil { + t.Fatalf("version command error = %v", err) + } +} + +func TestRootSetCommandRequiresFlags(t *testing.T) { + root := newRootCmd("dev") + root.SetArgs([]string{"set"}) + if err := root.Execute(); err == nil { + t.Error("set with no flags returned nil error") + } +} diff --git a/internal/cli/daemon.go b/internal/cli/daemon.go new file mode 100644 index 0000000..9723d98 --- /dev/null +++ b/internal/cli/daemon.go @@ -0,0 +1,106 @@ +package cli + +import ( + "errors" + "fmt" + "log/slog" + "time" + + "github.com/spf13/cobra" + + "galahad2lcd/internal/config" + "galahad2lcd/internal/device" + "galahad2lcd/internal/stream" + "galahad2lcd/internal/transcoder" +) + +// openTimeout bounds how long the daemon waits for the USB device to appear. +const openTimeout = 30 * time.Second + +func newDaemonCmd() *cobra.Command { + var ( + configPath string + input string + rotate int + speed float64 + fps float64 + ) + + cmd := &cobra.Command{ + Use: "daemon", + Short: "Run the display streaming daemon", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + cfg, err := config.Load(configPath) + if err != nil { + return err + } + if err := applyOverrides(cmd, &cfg, input, rotate, speed, fps); err != nil { + return err + } + if err := cfg.Validate(); err != nil { + return fmt.Errorf("invalid configuration: %w", err) + } + return runDaemon(cfg) + }, + } + + cmd.Flags().StringVar(&configPath, "config", config.DefaultConfigPath, "path to the configuration file") + cmd.Flags().StringVar(&input, "input", "", "override the input media file") + cmd.Flags().IntVar(&rotate, "rotate", 0, "override the rotation in degrees (0, 90, 180, 270)") + cmd.Flags().Float64Var(&speed, "speed", 0, "override the playback speed (>1 slower, <1 faster)") + cmd.Flags().Float64Var(&fps, "fps", 0, "override the output framerate (0 = auto)") + return cmd +} + +// runDaemon transcodes the configured media, loads the resulting frames and +// streams them to the display, reconnecting automatically if the USB link is +// lost (e.g. after system suspend). +func runDaemon(cfg config.Config) error { + ctx, stop := newSignalContext() + defer stop() + + tc, err := transcoder.New() + if err != nil { + return err + } + + slog.Info("transcoding input to H.264", "input", cfg.Display.Input, "rotate", cfg.Display.Rotate) + fps, err := tc.Transcode(ctx, cfg) + if err != nil { + return err + } + slog.Info("transcoding complete", "fps", fps, "cache", cfg.Stream.CachePath) + + packets, err := stream.LoadPackets(cfg.Stream.CachePath) + if err != nil { + return err + } + if len(packets) == 0 { + return errors.New("no video frames found in input") + } + slog.Info("frames buffered", "count", len(packets)) + + dev, err := device.OpenWithRetry(ctx, cfg.USB.VendorID, cfg.USB.ProductID, openTimeout) + if err != nil { + return err + } + slog.Info("device connected") + + for { + err := stream.New(dev, packets, fps, cfg.Display.Speed).Run(ctx) + if err == nil || ctx.Err() != nil { + _ = dev.Close() + return nil // clean shutdown + } + + slog.Error("stream interrupted; reconnecting", "error", err) + _ = dev.Close() + + dev, err = device.OpenWithRetry(ctx, cfg.USB.VendorID, cfg.USB.ProductID, openTimeout) + if err != nil { + return err + } + slog.Info("device reconnected") + } +} diff --git a/internal/cli/flags.go b/internal/cli/flags.go new file mode 100644 index 0000000..8b3a9c2 --- /dev/null +++ b/internal/cli/flags.go @@ -0,0 +1,48 @@ +package cli + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + + "galahad2lcd/internal/config" +) + +// applyOverrides merges explicitly provided flags into cfg. Zero-valued +// flags are ignored unless the user actually changed them. +func applyOverrides(cmd *cobra.Command, cfg *config.Config, input string, rotate int, speed, fps float64) error { + if cmd.Flags().Changed("input") { + abs, err := resolveInput(input) + if err != nil { + return err + } + cfg.Display.Input = abs + } + if cmd.Flags().Changed("rotate") { + cfg.Display.Rotate = rotate + } + if cmd.Flags().Changed("speed") { + cfg.Display.Speed = speed + } + if cmd.Flags().Changed("fps") { + cfg.Stream.FPS = fps + } + return nil +} + +// resolveInput converts a relative path to an absolute one and verifies the +// file exists, returning a helpful error otherwise. +func resolveInput(input string) (string, error) { + abs, err := filepath.Abs(input) + if err != nil { + return "", fmt.Errorf("resolve input %q: %w", input, err) + } + if st, err := os.Stat(abs); err != nil { + return "", fmt.Errorf("input file %q does not exist", abs) + } else if st.IsDir() { + return "", fmt.Errorf("input %q is a directory, expected a media file", abs) + } + return abs, nil +} diff --git a/internal/cli/list.go b/internal/cli/list.go new file mode 100644 index 0000000..80852a2 --- /dev/null +++ b/internal/cli/list.go @@ -0,0 +1,55 @@ +package cli + +import ( + "fmt" + "sort" + "text/tabwriter" + + "github.com/spf13/cobra" + + "galahad2lcd/internal/device" +) + +func newListCmd() *cobra.Command { + return &cobra.Command{ + Use: "list", + Short: "List USB devices visible to the driver", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + ctx, stop := newSignalContext() + defer stop() + + infos, err := device.ListDevices(ctx) + if err != nil { + return err + } + + if len(infos) == 0 { + fmt.Fprintln(cmd.OutOrStdout(), "no USB devices found (are permissions configured?)") + return nil + } + + sort.Slice(infos, func(i, j int) bool { + return infos[i].Bus < infos[j].Bus || + (infos[i].Bus == infos[j].Bus && infos[i].Address < infos[j].Address) + }) + + w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 4, 2, ' ', 0) + fmt.Fprintln(w, "BUS\tADDR\tVID:PID\tPRODUCT\t") + for _, info := range infos { + product := info.Product + if product == "" { + product = "-" + } + marker := " " + if info.IsLCD { + marker = "*" + } + fmt.Fprintf(w, "%s%d\t%d\t%04x:%04x\t%s\t\n", + marker, info.Bus, info.Address, info.VendorID, info.ProductID, product) + } + fmt.Fprintln(w, "* Lian Li Galahad II LCD (0416:7395)") + return w.Flush() + }, + } +} diff --git a/internal/cli/root.go b/internal/cli/root.go new file mode 100644 index 0000000..0288241 --- /dev/null +++ b/internal/cli/root.go @@ -0,0 +1,66 @@ +// Package cli implements the galahad2lcd command-line interface. +package cli + +import ( + "context" + "fmt" + "log/slog" + "os" + + "github.com/spf13/cobra" +) + +// Execute runs the CLI and returns the first error encountered. The version +// is injected by the caller (typically main via -ldflags). +func Execute(version string) error { + root := newRootCmd(version) + return root.Execute() +} + +func newRootCmd(version string) *cobra.Command { + var verbose bool + + root := &cobra.Command{ + Use: "galahad2lcd", + Short: "Driver for the Lian Li Galahad II LCD", + Long: `galahad2lcd streams H.264 video to the Lian Li Galahad II LCD +display over USB.`, + SilenceUsage: true, + SilenceErrors: true, + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + level := slog.LevelInfo + if verbose { + level = slog.LevelDebug + } + slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: level}))) + }, + } + + root.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "enable debug logging") + + root.AddCommand( + newDaemonCmd(), + newSetCmd(), + newListCmd(), + newVersionCmd(version), + ) + return root +} + +func newVersionCmd(version string) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Print the version and exit", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + fmt.Fprintln(cmd.OutOrStdout(), version) + return nil + }, + } +} + +// signalContext returns a context cancelled on SIGINT and SIGTERM so that +// systemctl stop and Ctrl-C both trigger a clean shutdown. +func signalContext() (context.Context, context.CancelFunc) { + return newSignalContext() +} diff --git a/internal/cli/set.go b/internal/cli/set.go new file mode 100644 index 0000000..82abba1 --- /dev/null +++ b/internal/cli/set.go @@ -0,0 +1,78 @@ +package cli + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "galahad2lcd/internal/config" + "galahad2lcd/internal/service" +) + +func newSetCmd() *cobra.Command { + var ( + configPath string + input string + rotate int + speed float64 + fps float64 + noRestart bool + ) + + cmd := &cobra.Command{ + Use: "set", + Short: "Update display settings and restart the service", + Long: `Update the galahad2lcd configuration file and restart the systemd +service. Provide at least one flag to change a setting. Writing the default +configuration path requires root.`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + if !cmd.Flags().Changed("input") && + !cmd.Flags().Changed("rotate") && + !cmd.Flags().Changed("speed") && + !cmd.Flags().Changed("fps") { + return fmt.Errorf("nothing to set; provide at least one of --input, --rotate, --speed, --fps") + } + + cfg, err := config.Load(configPath) + if err != nil { + return err + } + if err := applyOverrides(cmd, &cfg, input, rotate, speed, fps); err != nil { + return err + } + if err := cfg.Validate(); err != nil { + return fmt.Errorf("invalid configuration: %w", err) + } + + if configPath == config.DefaultConfigPath && os.Geteuid() != 0 { + return fmt.Errorf("writing %s requires root (run with sudo)", configPath) + } + if err := cfg.Save(configPath); err != nil { + return err + } + fmt.Printf("configuration saved to %s\n", configPath) + + if noRestart { + return nil + } + + ctx, stop := newSignalContext() + defer stop() + if err := service.New(config.ServiceName).Restart(ctx); err != nil { + return err + } + fmt.Println("service restarted") + return nil + }, + } + + cmd.Flags().StringVar(&configPath, "config", config.DefaultConfigPath, "path to the configuration file") + cmd.Flags().StringVar(&input, "input", "", "path to the media file") + cmd.Flags().IntVar(&rotate, "rotate", 0, "rotation in degrees (0, 90, 180, 270)") + cmd.Flags().Float64Var(&speed, "speed", 0, "playback speed (>1 slower, <1 faster)") + cmd.Flags().Float64Var(&fps, "fps", 0, "output framerate (0 = auto)") + cmd.Flags().BoolVar(&noRestart, "no-restart", false, "do not restart the service after saving") + return cmd +} diff --git a/internal/cli/signal.go b/internal/cli/signal.go new file mode 100644 index 0000000..d0e369b --- /dev/null +++ b/internal/cli/signal.go @@ -0,0 +1,14 @@ +package cli + +import ( + "context" + "os" + "os/signal" + "syscall" +) + +// newSignalContext returns a context that is cancelled on SIGINT or SIGTERM, +// so that Ctrl-C and systemctl stop both trigger a clean shutdown. +func newSignalContext() (context.Context, context.CancelFunc) { + return signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..7cfd616 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,145 @@ +// Package config defines the runtime configuration for the galahad2lcd +// daemon and handles loading and saving it as a TOML file. +package config + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/BurntSushi/toml" +) + +// Default paths used across the project. They mirror the historical Rust +// driver so that migration is painless. +const ( + DefaultConfigPath = "/etc/galahad2lcd.toml" + DefaultCachePath = "/tmp/galahad_cache.h264" + ServiceName = "galahad2lcd" +) + +// Default hardware identifiers for the Lian Li Galahad II LCD. +const ( + DefaultVendorID = 0x0416 + DefaultProductID = 0x7395 +) + +// Display holds the media-related settings. +type Display struct { + // Input is the path to the source video or GIF file. + Input string `toml:"input"` + // Rotate is the clockwise rotation in degrees: 0, 90, 180 or 270. + Rotate int `toml:"rotate"` + // Speed scales playback: >1 plays slower, <1 plays faster. 1.0 is normal. + Speed float64 `toml:"speed"` +} + +// USB holds the device identifiers used to locate the LCD. +type USB struct { + VendorID int `toml:"vendor_id"` + ProductID int `toml:"product_id"` +} + +// Stream holds playback behaviour. +type Stream struct { + // FPS forces a fixed output framerate. 0 (the default) means auto-detect + // from the source file. + FPS float64 `toml:"fps"` + // CachePath is where the transcoded H.264 stream is written. + CachePath string `toml:"cache_path"` +} + +// Config is the top-level configuration model. +type Config struct { + Display Display `toml:"display"` + USB USB `toml:"usb"` + Stream Stream `toml:"stream"` +} + +// Default returns a configuration populated with sensible defaults. +func Default() Config { + return Config{ + Display: Display{ + Input: "", + Rotate: 0, + Speed: 1.0, + }, + USB: USB{ + VendorID: DefaultVendorID, + ProductID: DefaultProductID, + }, + Stream: Stream{ + FPS: 0, + CachePath: DefaultCachePath, + }, + } +} + +// Load reads the configuration from path. A missing file results in a +// configuration with defaults; any other read or parse error is returned. +func Load(path string) (Config, error) { + cfg := Default() + if path == "" { + path = DefaultConfigPath + } + + data, err := os.ReadFile(path) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return cfg, nil + } + return cfg, fmt.Errorf("read config %q: %w", path, err) + } + + if err := toml.Unmarshal(data, &cfg); err != nil { + return cfg, fmt.Errorf("parse config %q: %w", path, err) + } + return cfg, nil +} + +// Save writes the configuration to path, creating parent directories. +func (c Config) Save(path string) error { + if path == "" { + path = DefaultConfigPath + } + if dir := filepath.Dir(path); dir != "" { + if err := os.MkdirAll(dir, 0o755); err != nil { + return fmt.Errorf("create config dir %q: %w", dir, err) + } + } + + var b strings.Builder + enc := toml.NewEncoder(&b) + if err := enc.Encode(c); err != nil { + return fmt.Errorf("encode config: %w", err) + } + if err := os.WriteFile(path, []byte(b.String()), 0o644); err != nil { + return fmt.Errorf("write config %q: %w", path, err) + } + return nil +} + +// Validate checks the configuration for correctness and returns a +// human-readable description of the first problem found, if any. +func (c Config) Validate() error { + if c.Display.Input == "" { + return errors.New("display.input must not be empty") + } + switch c.Display.Rotate { + case 0, 90, 180, 270: + default: + return fmt.Errorf("display.rotate must be 0, 90, 180 or 270, got %d", c.Display.Rotate) + } + if c.Display.Speed <= 0 { + return fmt.Errorf("display.speed must be > 0, got %v", c.Display.Speed) + } + if c.Stream.FPS < 0 { + return fmt.Errorf("stream.fps must be >= 0, got %v", c.Stream.FPS) + } + if c.Stream.CachePath == "" { + return errors.New("stream.cache_path must not be empty") + } + return nil +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..9ba44be --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,84 @@ +package config + +import ( + "path/filepath" + "testing" +) + +func TestDefault(t *testing.T) { + cfg := Default() + if cfg.Display.Speed != 1.0 { + t.Errorf("default speed = %v, want 1.0", cfg.Display.Speed) + } + if cfg.USB.VendorID != DefaultVendorID || cfg.USB.ProductID != DefaultProductID { + t.Errorf("default usb ids = %x:%x, want %x:%x", + cfg.USB.VendorID, cfg.USB.ProductID, DefaultVendorID, DefaultProductID) + } + if cfg.Stream.CachePath == "" { + t.Error("default cache_path must not be empty") + } +} + +func TestLoadMissingFileReturnsDefaults(t *testing.T) { + cfg, err := Load(filepath.Join(t.TempDir(), "nope.toml")) + if err != nil { + t.Fatalf("Load(missing) error = %v", err) + } + if cfg.Display.Input != "" { + t.Errorf("input = %q, want empty", cfg.Display.Input) + } +} + +func TestSaveAndLoadRoundTrip(t *testing.T) { + path := filepath.Join(t.TempDir(), "sub", "galahad2lcd.toml") + orig := Default() + orig.Display.Input = "/tmp/demo.gif" + orig.Display.Rotate = 180 + orig.Display.Speed = 1.5 + orig.USB.VendorID = 0x1234 + orig.Stream.FPS = 15 + + if err := orig.Save(path); err != nil { + t.Fatalf("Save error = %v", err) + } + + got, err := Load(path) + if err != nil { + t.Fatalf("Load error = %v", err) + } + if got.Display != orig.Display { + t.Errorf("display = %+v, want %+v", got.Display, orig.Display) + } + if got.USB != orig.USB { + t.Errorf("usb = %+v, want %+v", got.USB, orig.USB) + } + if got.Stream != orig.Stream { + t.Errorf("stream = %+v, want %+v", got.Stream, orig.Stream) + } +} + +func TestValidate(t *testing.T) { + tests := []struct { + name string + mutate func(*Config) + wantErr bool + }{ + {"valid", func(c *Config) { c.Display.Input = "/tmp/x.gif" }, false}, + {"empty input", func(c *Config) {}, true}, + {"bad rotate", func(c *Config) { c.Display.Input = "/tmp/x.gif"; c.Display.Rotate = 45 }, true}, + {"negative speed", func(c *Config) { c.Display.Input = "/tmp/x.gif"; c.Display.Speed = -1 }, true}, + {"negative fps", func(c *Config) { c.Display.Input = "/tmp/x.gif"; c.Stream.FPS = -2 }, true}, + {"empty cache", func(c *Config) { c.Display.Input = "/tmp/x.gif"; c.Stream.CachePath = "" }, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := Default() + tt.mutate(&cfg) + err := cfg.Validate() + if (err != nil) != tt.wantErr { + t.Errorf("Validate() error = %v, wantErr = %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/device/device.go b/internal/device/device.go new file mode 100644 index 0000000..1d7dcbc --- /dev/null +++ b/internal/device/device.go @@ -0,0 +1,21 @@ +// Package device abstracts the Lian Li Galahad II LCD hardware. The streamer +// depends only on the Device interface, so tests can substitute a fake and +// future hardware can be supported by adding new implementations. +package device + +import ( + "context" + "errors" +) + +// Device sends H.264 frames to a display. Implementations must be safe for +// sequential use by a single goroutine. +type Device interface { + // WriteFrame transmits one encoded access unit to the display. + WriteFrame(ctx context.Context, frame []byte) error + // Close releases all hardware resources. + Close() error +} + +// ErrNotFound is returned when no matching USB device is present. +var ErrNotFound = errors.New("device not found") diff --git a/internal/device/list.go b/internal/device/list.go new file mode 100644 index 0000000..a2adf5b --- /dev/null +++ b/internal/device/list.go @@ -0,0 +1,57 @@ +package device + +import ( + "context" + "fmt" + + "github.com/google/gousb" +) + +// Info describes a USB device for the diagnostics listing. +type Info struct { + VendorID int + ProductID int + Manufacturer string + Product string + Serial string + Bus int + Address int + IsLCD bool +} + +// ListDevices enumerates all USB devices visible to libusb. Descriptor +// strings are best-effort: reading them requires device access, which is +// why failures are ignored. +func ListDevices(ctx context.Context) ([]Info, error) { + gctx := gousb.NewContext() + defer gctx.Close() + + devs, err := gctx.OpenDevices(func(*gousb.DeviceDesc) bool { return true }) + if err != nil && len(devs) == 0 { + return nil, fmt.Errorf("enumerate USB devices (run as root or install the udev rule): %w", err) + } + defer func() { + for _, d := range devs { + _ = d.Close() + } + }() + + infos := make([]Info, 0, len(devs)) + for _, d := range devs { + info := Info{ + VendorID: int(d.Desc.Vendor), + ProductID: int(d.Desc.Product), + Bus: d.Desc.Bus, + Address: d.Desc.Address, + IsLCD: d.Desc.Vendor == gousb.ID(0x0416) && d.Desc.Product == gousb.ID(0x7395), + } + if info.IsLCD { + // Only read strings for the device we care about. + info.Manufacturer, _ = d.Manufacturer() + info.Product, _ = d.Product() + info.Serial, _ = d.SerialNumber() + } + infos = append(infos, info) + } + return infos, err +} diff --git a/internal/device/usb.go b/internal/device/usb.go new file mode 100644 index 0000000..f9d79f7 --- /dev/null +++ b/internal/device/usb.go @@ -0,0 +1,153 @@ +package device + +import ( + "context" + "fmt" + "time" + + "github.com/google/gousb" + + "galahad2lcd/internal/protocol" +) + +// Lian Li Galahad II LCD USB layout (from the reference driver and +// protocol reversals). +const ( + interfaceNumber = 1 + endpointNumber = 2 + configNumber = 1 + writeTimeout = time.Second +) + +// USBDevice is the gousb-backed implementation of Device. +type USBDevice struct { + gctx *gousb.Context + dev *gousb.Device + intf *gousb.Interface + ep *gousb.OutEndpoint +} + +// Open locates the display by vendor/product ID and claims its video +// interface. The kernel driver is detached automatically and reattached on +// Close. +func Open(ctx context.Context, vendorID, productID int) (*USBDevice, error) { + gctx := gousb.NewContext() + + dev, err := gctx.OpenDeviceWithVIDPID(gousb.ID(vendorID), gousb.ID(productID)) + if err != nil { + gctx.Close() + return nil, fmt.Errorf("open USB device %04x:%04x: %w", vendorID, productID, err) + } + if dev == nil { + gctx.Close() + return nil, fmt.Errorf("USB device %04x:%04x: %w", vendorID, productID, ErrNotFound) + } + + // Auto-detach releases any kernel driver (e.g. usbhid) so we can claim + // the interface, and reattaches it when we are done. + if err := dev.SetAutoDetach(true); err != nil { + _ = dev.Close() + gctx.Close() + return nil, fmt.Errorf("enable auto-detach: %w", err) + } + + cfg, err := dev.Config(configNumber) + if err != nil { + _ = dev.Close() + gctx.Close() + return nil, fmt.Errorf("select USB config %d: %w", configNumber, err) + } + + intf, err := cfg.Interface(interfaceNumber, 0) + if err != nil { + _ = cfg.Close() + _ = dev.Close() + gctx.Close() + return nil, fmt.Errorf("claim interface %d: %w", interfaceNumber, err) + } + + ep, err := intf.OutEndpoint(endpointNumber) + if err != nil { + intf.Close() + _ = dev.Close() + gctx.Close() + return nil, fmt.Errorf("open out endpoint 0x0%x: %w", endpointNumber, err) + } + + return &USBDevice{gctx: gctx, dev: dev, intf: intf, ep: ep}, nil +} + +// OpenWithRetry keeps trying to open the device until ctx is done or +// timeout elapses, with exponential backoff. This lets the service start +// before the USB device has finished enumerating at boot. +func OpenWithRetry(ctx context.Context, vendorID, productID int, timeout time.Duration) (*USBDevice, error) { + deadline := time.Now().Add(timeout) + backoff := 500 * time.Millisecond + + var lastErr error + for { + d, err := Open(ctx, vendorID, productID) + if err == nil { + return d, nil + } + if ctx.Err() != nil { + return nil, ctx.Err() + } + lastErr = err + if time.Now().After(deadline) { + return nil, fmt.Errorf("device %04x:%04x unavailable for %s: %w", + vendorID, productID, timeout, lastErr) + } + + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-time.After(backoff): + if backoff < 5*time.Second { + backoff *= 2 + } + } + } +} + +// WriteFrame encodes the frame into USB packets and transmits them with a +// bounded timeout per packet. +func (d *USBDevice) WriteFrame(ctx context.Context, frame []byte) error { + packets, err := protocol.EncodePackets(frame) + if err != nil { + return fmt.Errorf("encode frame: %w", err) + } + + for _, pkt := range packets { + writeCtx, cancel := context.WithTimeout(ctx, writeTimeout) + n, err := d.ep.WriteContext(writeCtx, pkt) + cancel() + + if err != nil { + return fmt.Errorf("USB write: %w", err) + } + if n != len(pkt) { + return fmt.Errorf("USB short write: %d of %d bytes", n, len(pkt)) + } + } + return nil +} + +// Close releases the interface, device and libusb context. +func (d *USBDevice) Close() error { + var firstErr error + if d.intf != nil { + d.intf.Close() + } + if d.dev != nil { + if err := d.dev.Close(); err != nil && firstErr == nil { + firstErr = err + } + } + if d.gctx != nil { + if err := d.gctx.Close(); err != nil && firstErr == nil { + firstErr = err + } + } + return firstErr +} diff --git a/internal/h264/h264.go b/internal/h264/h264.go new file mode 100644 index 0000000..3d960de --- /dev/null +++ b/internal/h264/h264.go @@ -0,0 +1,117 @@ +// Package h264 parses Annex-B H.264 byte streams (as produced by libx264 +// with annexb=1) and groups the raw NAL units into access units (AUs), where +// each AU corresponds to one decoded video frame. This matches the packet +// boundaries the reference driver receives from FFmpeg's demuxer. +// +// AU grouping rules: +// +// - A VCL NAL (slice: types 1-5) begins a new AU. +// - Non-VCL NALs (SPS/PPS/SEI, ...) are attached as a prefix to the AU +// that contains the next VCL NAL, so a keyframe AU naturally becomes +// SPS + PPS + IDR slice. +// - An access unit delimiter (type 9) is an explicit boundary. +package h264 + +// NAL unit types relevant for AU assembly. +const ( + nalTypeSlice = 1 + nalTypeIDRSlice = 5 + nalTypeSEI = 6 + nalTypeSPS = 7 + nalTypePPS = 8 + nalTypeAUD = 9 +) + +// isVCL reports whether a NAL unit type carries slice data. +func isVCL(t byte) bool { + return t >= nalTypeSlice && t <= nalTypeIDRSlice +} + +// nalType extracts the NAL unit type from the first payload byte. +func nalType(payloadStart []byte) byte { + if len(payloadStart) == 0 { + return 0 + } + return payloadStart[0] & 0x1F +} + +// payloadStarts returns the index of the byte immediately following each +// start code (00 00 01, optionally prefixed with an extra 00). +func payloadStarts(data []byte) []int { + var starts []int + for i := 0; i+2 < len(data); i++ { + if data[i] == 0 && data[i+1] == 0 && data[i+2] == 1 { + starts = append(starts, i+3) + i = i + 2 + } + } + return starts +} + +// startCodeLen determines the length of the start code that immediately +// precedes the payload at index pay in data. +func startCodeLen(data []byte, pay int) int { + if pay >= 4 && data[pay-4] == 0 && data[pay-3] == 0 && data[pay-2] == 0 && data[pay-1] == 1 { + return 4 + } + return 3 +} + +// SplitAUs splits an Annex-B stream into access units. The returned slices +// alias the input buffer, so callers must not modify data while they are in +// use. +func SplitAUs(data []byte) [][]byte { + starts := payloadStarts(data) + if len(starts) == 0 { + if len(data) == 0 { + return nil + } + return [][]byte{data} + } + + // Pre-compute the byte index where each NAL (including its start code) + // begins. + codeStart := make([]int, len(starts)) + for i, pay := range starts { + codeStart[i] = pay - startCodeLen(data, pay) + } + + var aus [][]byte + auStart := codeStart[0] + hasVCL := false + + // flush ends the current AU at the start of NAL i and begins a new one. + flush := func(i int) { + end := codeStart[i] + if end > auStart { + aus = append(aus, data[auStart:end]) + } + auStart = end + hasVCL = false + } + + for i, pay := range starts { + switch t := nalType(data[pay:]); { + case t == nalTypeAUD: + // An access unit delimiter is an explicit boundary; it becomes + // the prefix of the AU that follows it. + if hasVCL { + flush(i) + } + case isVCL(t): + // A slice always starts a new AU once the current one already + // contains slice data. + if hasVCL { + flush(i) + } + hasVCL = true + default: + // Non-VCL prefix: SPS, PPS, SEI, ... attaches to the current AU. + } + } + + if len(data) > auStart { + aus = append(aus, data[auStart:]) + } + return aus +} diff --git a/internal/h264/h264_test.go b/internal/h264/h264_test.go new file mode 100644 index 0000000..dba19d8 --- /dev/null +++ b/internal/h264/h264_test.go @@ -0,0 +1,203 @@ +package h264 + +import ( + "bytes" + "os" + "path/filepath" + "testing" +) + +// streamNALs builds an Annex-B byte stream from a list of NAL unit types. +// Each NAL gets a minimal one-byte payload so the type is easy to assert. +func streamNALs(types ...byte) []byte { + var out []byte + for _, t := range types { + out = append(out, 0x00, 0x00, 0x01) // 3-byte start code + out = append(out, 0x60|t) // nal_ref_idc=3, type in low bits + out = append(out, 0x00) + } + return out +} + +// firstNALType extracts the type of the first NAL in a byte slice. +func firstNALType(au []byte) byte { + if len(au) < 4 { + return 0 + } + // Skip the start code: 3 or 4 bytes. + pay := 3 + if au[0] == 0 && au[1] == 0 && au[2] == 0 { + pay = 4 + } + return au[pay] & 0x1F +} + +func typesOf(aus [][]byte) [][]byte { + var types [][]byte + for _, au := range aus { + var ts []byte + rest := au + for len(rest) > 0 { + ts = append(ts, firstNALType(rest)) + // advance past this NAL (start code + payload of >=1 byte) + skip := 4 + if rest[0] == 0 && rest[1] == 0 && rest[2] == 0 { + skip = 5 + } + // find next start code + next := bytes.Index(rest[skip:], []byte{0x00, 0x00, 0x01}) + if next < 0 { + break + } + rest = rest[skip+next:] + } + types = append(types, ts) + } + return types +} + +func TestSplitAUsEmpty(t *testing.T) { + if got := SplitAUs(nil); got != nil { + t.Errorf("SplitAUs(nil) = %v, want nil", got) + } +} + +func TestSplitAUsNoStartCode(t *testing.T) { + data := []byte{0x67, 0x42, 0x00} + got := SplitAUs(data) + if len(got) != 1 || !bytes.Equal(got[0], data) { + t.Errorf("SplitAUs passthrough = %v, want [% x]", got, data) + } +} + +func TestSplitAUsKeyframeThenP(t *testing.T) { + // SPS(7), PPS(8), IDR(5), P(1), P(1) + stream := streamNALs(7, 8, 5, 1, 1) + aus := SplitAUs(stream) + if len(aus) != 3 { + t.Fatalf("got %d AUs, want 3", len(aus)) + } + types := typesOf(aus) + want := [][]byte{{7, 8, 5}, {1}, {1}} + for i := range want { + if !bytes.Equal(types[i], want[i]) { + t.Errorf("AU %d types = %v, want %v", i, types[i], want[i]) + } + } +} + +func TestSplitAUsWithAUD(t *testing.T) { + // AUD(9), IDR(5), P(1) + stream := streamNALs(9, 5, 1) + aus := SplitAUs(stream) + if len(aus) != 2 { + t.Fatalf("got %d AUs, want 2", len(aus)) + } + types := typesOf(aus) + want := [][]byte{{9, 5}, {1}} + for i := range want { + if !bytes.Equal(types[i], want[i]) { + t.Errorf("AU %d types = %v, want %v", i, types[i], want[i]) + } + } +} + +func TestSplitAUsAUDThenPrefix(t *testing.T) { + // AUD(9), SPS(7), PPS(8), IDR(5), P(1) + stream := streamNALs(9, 7, 8, 5, 1) + aus := SplitAUs(stream) + if len(aus) != 2 { + t.Fatalf("got %d AUs, want 2", len(aus)) + } + types := typesOf(aus) + want := [][]byte{{9, 7, 8, 5}, {1}} + for i := range want { + if !bytes.Equal(types[i], want[i]) { + t.Errorf("AU %d types = %v, want %v", i, types[i], want[i]) + } + } +} + +func TestSplitAUsFourByteCodes(t *testing.T) { + var stream []byte + for _, t := range []byte{7, 8, 5, 1} { + stream = append(stream, 0x00, 0x00, 0x00, 0x01) // 4-byte + stream = append(stream, 0x60|t, 0x00) + } + aus := SplitAUs(stream) + if len(aus) != 2 { + t.Fatalf("got %d AUs, want 2", len(aus)) + } +} + +func TestSplitAUsMixedCodes(t *testing.T) { + var stream []byte + stream = append(stream, 0x00, 0x00, 0x01, 0x67, 0x00) // 3-byte SPS + stream = append(stream, 0x00, 0x00, 0x00, 0x01, 0x68, 0x00) // 4-byte PPS + stream = append(stream, 0x00, 0x00, 0x00, 0x01, 0x65, 0x00) // 4-byte IDR + stream = append(stream, 0x00, 0x00, 0x01, 0x41, 0x00) // 3-byte P + + aus := SplitAUs(stream) + if len(aus) != 2 { + t.Fatalf("got %d AUs, want 2", len(aus)) + } + types := typesOf(aus) + want := [][]byte{{7, 8, 5}, {1}} + for i := range want { + if !bytes.Equal(types[i], want[i]) { + t.Errorf("AU %d types = %v, want %v", i, types[i], want[i]) + } + } +} + +func TestSplitAUsReassemblesStream(t *testing.T) { + stream := streamNALs(7, 8, 5, 1, 1, 7, 8, 5, 1) + aus := SplitAUs(stream) + var rebuilt []byte + for _, au := range aus { + rebuilt = append(rebuilt, au...) + } + if !bytes.Equal(rebuilt, stream) { + t.Error("reassembled stream differs from input") + } +} + +// TestSplitAUsRealFile validates against a real libx264 Annex-B stream. +func TestSplitAUsRealFile(t *testing.T) { + path := filepath.Join("..", "..", "testdata", "sample.h264") + data, err := os.ReadFile(path) + if err != nil { + t.Skipf("testdata not available: %v", err) + } + + aus := SplitAUs(data) + if len(aus) < 2 { + t.Fatalf("got %d AUs, want >= 2", len(aus)) + } + + // First AU must be a keyframe: SPS + PPS + optional prefix NALs (SEI) + // followed by an IDR slice. + first := typesOf(aus[:1])[0] + if len(first) < 3 || first[0] != 7 || first[1] != 8 || first[len(first)-1] != 5 { + t.Errorf("first AU types = %v, want prefix [7 8 ... 5]", first) + } + + // All AUs must be non-empty and start with a valid start code. + for i, au := range aus { + if len(au) < 4 { + t.Fatalf("AU %d too short: %d bytes", i, len(au)) + } + if !(au[0] == 0 && au[1] == 0 && (au[2] == 0 || au[2] == 1)) { + t.Errorf("AU %d does not start with a start code: % x", i, au[:4]) + } + } + + // Reassembly must reproduce the input exactly. + var rebuilt []byte + for _, au := range aus { + rebuilt = append(rebuilt, au...) + } + if !bytes.Equal(rebuilt, data) { + t.Error("reassembled stream differs from input") + } +} diff --git a/internal/protocol/protocol.go b/internal/protocol/protocol.go new file mode 100644 index 0000000..9bcdb06 --- /dev/null +++ b/internal/protocol/protocol.go @@ -0,0 +1,64 @@ +// Package protocol builds the USB packets that the Lian Li Galahad II LCD +// expects. It is a pure, dependency-free implementation of the framing used +// by the reference Rust/Python drivers. +// +// Each H.264 frame is split into one or more 512-byte USB packets. Every +// packet carries an 11-byte header: +// +// [0] report_id (0x02) +// [1] command (0x0D = send H.264) +// [2:6] total payload size (big-endian) +// [6:9] packet index, 3 bytes (big-endian, wraps) +// [9:11] chunk length in this packet (big-endian) +// +// followed by up to 501 bytes of frame data and zero padding. +package protocol + +import ( + "encoding/binary" + "fmt" +) + +// Wire-level constants shared with the reference implementation. +const ( + ReportIDVideo = 0x02 + CmdSendH264 = 0x0D + HeaderSize = 11 + PacketSize = 512 + MaxPayloadSize = 501 +) + +// EncodePackets splits frame data into USB packets. It returns an error only +// when frame is empty, which would otherwise produce a malformed stream. +func EncodePackets(frame []byte) ([][]byte, error) { + if len(frame) == 0 { + return nil, fmt.Errorf("cannot encode empty frame") + } + + var packets [][]byte + for offset, idx := 0, uint32(0); offset < len(frame); offset += MaxPayloadSize { + end := min(offset+MaxPayloadSize, len(frame)) + chunk := frame[offset:end] + + pkt := make([]byte, PacketSize) + pkt[0] = ReportIDVideo + pkt[1] = CmdSendH264 + binary.BigEndian.PutUint32(pkt[2:6], uint32(len(frame))) + pkt[6] = byte(idx >> 16) + pkt[7] = byte(idx >> 8) + pkt[8] = byte(idx) + binary.BigEndian.PutUint16(pkt[9:11], uint16(len(chunk))) + copy(pkt[HeaderSize:HeaderSize+len(chunk)], chunk) + + packets = append(packets, pkt) + idx++ + } + return packets, nil +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/internal/protocol/protocol_test.go b/internal/protocol/protocol_test.go new file mode 100644 index 0000000..8805942 --- /dev/null +++ b/internal/protocol/protocol_test.go @@ -0,0 +1,104 @@ +package protocol + +import ( + "bytes" + "encoding/binary" + "reflect" + "testing" +) + +func TestEncodePacketsSingleChunk(t *testing.T) { + frame := []byte{0x00, 0x00, 0x01, 0x67, 0x42} + packets, err := EncodePackets(frame) + if err != nil { + t.Fatalf("EncodePackets error = %v", err) + } + if len(packets) != 1 { + t.Fatalf("got %d packets, want 1", len(packets)) + } + + pkt := packets[0] + if len(pkt) != PacketSize { + t.Errorf("packet size = %d, want %d", len(pkt), PacketSize) + } + + // Header golden bytes. + want := []byte{ + 0x02, // report id + 0x0D, // command + 0x00, 0x00, 0x00, 0x05, // total size = 5 + 0x00, 0x00, 0x00, // idx = 0 + 0x00, 0x05, // chunk len = 5 + } + if !bytes.Equal(pkt[:HeaderSize], want) { + t.Errorf("header = % x, want % x", pkt[:HeaderSize], want) + } + if !bytes.Equal(pkt[HeaderSize:HeaderSize+len(frame)], frame) { + t.Errorf("payload = % x, want % x", pkt[HeaderSize:HeaderSize+len(frame)], frame) + } +} + +func TestEncodePacketsMultiChunk(t *testing.T) { + frame := bytes.Repeat([]byte{0xAB}, MaxPayloadSize+17) + packets, err := EncodePackets(frame) + if err != nil { + t.Fatalf("EncodePackets error = %v", err) + } + if len(packets) != 2 { + t.Fatalf("got %d packets, want 2", len(packets)) + } + + // First packet carries a full 501-byte chunk. + if got := binary.BigEndian.Uint16(packets[0][9:11]); int(got) != MaxPayloadSize { + t.Errorf("first chunk len = %d, want %d", got, MaxPayloadSize) + } + // Second packet carries the remainder (17 bytes) and index 1. + if got := binary.BigEndian.Uint16(packets[1][9:11]); int(got) != 17 { + t.Errorf("second chunk len = %d, want 17", got) + } + if got := (int(packets[1][6]) << 16) | (int(packets[1][7]) << 8) | int(packets[1][8]); got != 1 { + t.Errorf("second packet idx = %d, want 1", got) + } + // Both packets declare the full frame size. + for i, pkt := range packets { + if got := binary.BigEndian.Uint32(pkt[2:6]); int(got) != len(frame) { + t.Errorf("packet %d total size = %d, want %d", i, got, len(frame)) + } + } +} + +func TestEncodePacketsReassemblesFrame(t *testing.T) { + frame := make([]byte, 2000) + for i := range frame { + frame[i] = byte(i) + } + + packets, err := EncodePackets(frame) + if err != nil { + t.Fatalf("EncodePackets error = %v", err) + } + + var got []byte + for _, pkt := range packets { + n := binary.BigEndian.Uint16(pkt[9:11]) + got = append(got, pkt[HeaderSize:HeaderSize+int(n)]...) + } + if !reflect.DeepEqual(got, frame) { + t.Errorf("reassembled frame differs from input") + } +} + +func TestEncodePacketsEmptyFrame(t *testing.T) { + if _, err := EncodePackets(nil); err == nil { + t.Error("expected error for empty frame, got nil") + } +} + +func TestEncodePacketsIdxWrapsAt24Bits(t *testing.T) { + frame := bytes.Repeat([]byte{0x01}, 10) + packets, err := EncodePackets(frame) + if err != nil { + t.Fatalf("EncodePackets error = %v", err) + } + _ = packets // idx wrapping needs > 16M packets; covered by construction. +} diff --git a/internal/service/service.go b/internal/service/service.go new file mode 100644 index 0000000..aa3bd2a --- /dev/null +++ b/internal/service/service.go @@ -0,0 +1,34 @@ +// Package service manages the galahad2lcd systemd unit. +package service + +import ( + "context" + "fmt" + "os/exec" +) + +// Service represents the installed systemd unit. +type Service struct { + name string + // runCmd is the command constructor; overridable for tests. + runCmd func(ctx context.Context, name string, arg ...string) *exec.Cmd +} + +// New returns a Service wrapper for the given unit name. +func New(name string) *Service { + return &Service{name: name, runCmd: exec.CommandContext} +} + +// Restart tells systemd to restart the service. It fails early when the +// caller lacks privileges. +func (s *Service) Restart(ctx context.Context) error { + cmd := s.runCmd(ctx, "systemctl", "restart", s.name) + out, err := cmd.CombinedOutput() + if err != nil { + if len(out) > 0 { + return fmt.Errorf("systemctl restart %s: %w (%s)", s.name, err, string(out)) + } + return fmt.Errorf("systemctl restart %s: %w", s.name, err) + } + return nil +} diff --git a/internal/service/service_test.go b/internal/service/service_test.go new file mode 100644 index 0000000..af274f0 --- /dev/null +++ b/internal/service/service_test.go @@ -0,0 +1,59 @@ +package service + +import ( + "context" + "os" + "path/filepath" + "testing" +) + +// fakeSystemctl installs a fake systemctl on PATH and returns the path to its +// directory. +func fakeSystemctl(t *testing.T, script string) string { + t.Helper() + dir := t.TempDir() + path := filepath.Join(dir, "systemctl") + if err := os.WriteFile(path, []byte("#!/bin/sh\n"+script), 0o755); err != nil { + t.Fatal(err) + } + t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH")) + return path +} + +func TestRestartSuccess(t *testing.T) { + fakeSystemctl(t, "exit 0\n") + if err := New("galahad2lcd").Restart(context.Background()); err != nil { + t.Fatalf("Restart error = %v", err) + } +} + +func TestRestartFailure(t *testing.T) { + fakeSystemctl(t, "echo 'permission denied' >&2; exit 1\n") + err := New("galahad2lcd").Restart(context.Background()) + if err == nil { + t.Fatal("Restart returned nil error, want failure") + } +} + +func TestRestartCancellation(t *testing.T) { + fakeSystemctl(t, "sleep 5\n") + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + err := New("galahad2lcd").Restart(ctx) + if err == nil { + t.Fatal("Restart returned nil error for cancelled context") + } +} + +// Ensure the default constructor wires up the real exec.CommandContext. +func TestNewDefaultRunCmd(t *testing.T) { + s := New("x") + if s.runCmd == nil { + t.Fatal("runCmd not initialised") + } + cmd := s.runCmd(context.Background(), "true") + if cmd == nil { + t.Fatal("runCmd returned nil command") + } +} diff --git a/internal/stream/stream.go b/internal/stream/stream.go new file mode 100644 index 0000000..926e6be --- /dev/null +++ b/internal/stream/stream.go @@ -0,0 +1,86 @@ +// Package stream plays back pre-encoded H.264 access units to a Device at a +// constant frame rate. Pacing accounts for the time spent writing so that +// slow USB transfers do not accumulate drift. +package stream + +import ( + "context" + "errors" + "fmt" + "os" + "time" + + "galahad2lcd/internal/device" + "galahad2lcd/internal/h264" +) + +const defaultFPS = 30.0 + +// LoadPackets reads an Annex-B H.264 file from disk and splits it into +// access units (one per video frame). +func LoadPackets(path string) ([][]byte, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read cache %q: %w", path, err) + } + return h264.SplitAUs(data), nil +} + +// Streamer plays a fixed set of frames in a loop until cancelled or until a +// write fails. +type Streamer struct { + device device.Device + packets [][]byte + frameTime time.Duration +} + +// New builds a Streamer. speed > 1 slows playback, speed < 1 speeds it up. +func New(dev device.Device, packets [][]byte, fps, speed float64) *Streamer { + effective := clampFPS(fps) + if speed <= 0 { + speed = 1 + } + frameTime := time.Duration(float64(time.Second) / effective * speed) + return &Streamer{device: dev, packets: packets, frameTime: frameTime} +} + +// Run streams packets forever until ctx is cancelled or the device fails. +// A nil error means ctx was cancelled and shutdown is clean. +func (s *Streamer) Run(ctx context.Context) error { + if len(s.packets) == 0 { + return errors.New("no frames to stream") + } + + for { + for _, pkt := range s.packets { + if err := ctx.Err(); err != nil { + return nil // clean shutdown + } + + start := time.Now() + if err := s.device.WriteFrame(ctx, pkt); err != nil { + return fmt.Errorf("write frame: %w", err) + } + + elapsed := time.Since(start) + if wait := s.frameTime - elapsed; wait > 0 { + select { + case <-ctx.Done(): + return nil + case <-time.After(wait): + } + } + } + } +} + +func clampFPS(fps float64) float64 { + switch { + case fps <= 0: + return defaultFPS + case fps > 120: + return 120 + default: + return fps + } +} diff --git a/internal/stream/stream_test.go b/internal/stream/stream_test.go new file mode 100644 index 0000000..7d57c9c --- /dev/null +++ b/internal/stream/stream_test.go @@ -0,0 +1,143 @@ +package stream + +import ( + "context" + "errors" + "sync" + "testing" + "time" +) + +type fakeDevice struct { + mu sync.Mutex + wrote [][]byte + failAt int // fail after this many writes; <0 = never fail + failErr error + // onWrite is invoked after each successful write with the running count. + onWrite func(n int) +} + +func (f *fakeDevice) WriteFrame(_ context.Context, frame []byte) error { + f.mu.Lock() + defer f.mu.Unlock() + if f.failAt >= 0 && f.failAt == len(f.wrote) { + if f.failErr == nil { + f.failErr = errors.New("boom") + } + return f.failErr + } + cp := make([]byte, len(frame)) + copy(cp, frame) + f.wrote = append(f.wrote, cp) + if f.onWrite != nil { + f.onWrite(len(f.wrote)) + } + return nil +} + +func (f *fakeDevice) Close() error { return nil } + +func (f *fakeDevice) count() int { + f.mu.Lock() + defer f.mu.Unlock() + return len(f.wrote) +} + +func TestStreamerPreservesOrder(t *testing.T) { + frames := [][]byte{{1, 2, 3}, {4, 5}, {6}} + ctx, cancel := context.WithCancel(context.Background()) + f := &fakeDevice{ + failAt: -1, + onWrite: func(n int) { + if n == len(frames) { + cancel() + } + }, + } + s := New(f, frames, 1000, 1) + + if err := s.Run(ctx); err != nil { + t.Fatalf("Run error = %v", err) + } + + if f.count() != 3 { + t.Fatalf("wrote %d frames, want 3", f.count()) + } + f.mu.Lock() + defer f.mu.Unlock() + for i, want := range frames { + if len(f.wrote[i]) != len(want) { + t.Errorf("frame %d length = %d, want %d", i, len(f.wrote[i]), len(want)) + } + } +} + +func TestStreamerStopsOnCancel(t *testing.T) { + f := &fakeDevice{failAt: -1} + s := New(f, [][]byte{{1}}, 100, 1) + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + if err := s.Run(ctx); err != nil { + t.Fatalf("Run error = %v, want nil on clean shutdown", err) + } +} + +func TestStreamerPropagatesDeviceError(t *testing.T) { + f := &fakeDevice{failAt: 1} + s := New(f, [][]byte{{1}, {2}}, 1000, 1) + + ctx := context.Background() + if err := s.Run(ctx); err == nil { + t.Fatal("Run returned nil, want device error") + } + if f.count() != 1 { + t.Errorf("wrote %d frames before error, want 1", f.count()) + } +} + +func TestStreamerNoFrames(t *testing.T) { + f := &fakeDevice{failAt: -1} + s := New(f, nil, 30, 1) + if err := s.Run(context.Background()); err == nil { + t.Error("Run returned nil, want error for empty packet list") + } +} + +func TestStreamerPacing(t *testing.T) { + // 10 fps => 100ms per frame. Two frames should take ~200ms. + f := &fakeDevice{failAt: -1} + s := New(f, [][]byte{{1}, {2}}, 10, 1) + + ctx, cancel := context.WithCancel(context.Background()) + start := time.Now() + go func() { + time.Sleep(400 * time.Millisecond) + cancel() + }() + _ = s.Run(ctx) + elapsed := time.Since(start) + + // At 10fps each frame takes 100ms; after 400ms we expect ~4 frames. + if f.count() < 2 { + t.Errorf("too few frames streamed: %d", f.count()) + } + if elapsed < 150*time.Millisecond { + t.Errorf("elapsed = %v, pacing too fast", elapsed) + } +} + +func TestLoadPacketsMissingFile(t *testing.T) { + if _, err := LoadPackets("/nonexistent/cache.h264"); err == nil { + t.Error("LoadPackets returned nil error for missing file") + } +} + +func TestClampFPS(t *testing.T) { + if got := clampFPS(0); got != defaultFPS { + t.Errorf("clampFPS(0) = %v, want %v", got, defaultFPS) + } + if got := clampFPS(300); got != 120 { + t.Errorf("clampFPS(300) = %v, want 120", got) + } +} diff --git a/internal/transcoder/transcoder.go b/internal/transcoder/transcoder.go new file mode 100644 index 0000000..c3af362 --- /dev/null +++ b/internal/transcoder/transcoder.go @@ -0,0 +1,228 @@ +// Package transcoder converts a source video or GIF into a raw Annex-B +// H.264 stream using the system ffmpeg/ffprobe binaries. Shelling out to +// ffmpeg avoids the fragile C-ABI bindings that plague in-process encoders +// and lets this driver work with any recent FFmpeg release. +package transcoder + +import ( + "context" + "errors" + "fmt" + "os" + "os/exec" + "strconv" + "strings" + + "galahad2lcd/internal/config" +) + +// Screen dimensions of the Lian Li Galahad II LCD. +const ( + screenWidth = 480 + screenHeight = 480 +) + +// encoderParameters mirrors the reference driver: constrained VBR, low +// latency and a stream that the display firmware accepts. +const ( + bitRate = 2000 // kbit/s + preset = "veryfast" + profile = "baseline" + defaultFPS = 30.0 +) + +// Transcoder invokes ffmpeg and ffprobe. Binary paths are configurable so +// tests can substitute fakes. +type Transcoder struct { + ffmpeg string + ffprobe string +} + +// New returns a Transcoder that resolves ffmpeg and ffprobe from PATH. +func New() (*Transcoder, error) { + ffmpeg, err := exec.LookPath("ffmpeg") + if err != nil { + return nil, fmt.Errorf("ffmpeg not found: %w (install ffmpeg)", err) + } + ffprobe, err := exec.LookPath("ffprobe") + if err != nil { + return nil, fmt.Errorf("ffprobe not found: %w (install ffmpeg)", err) + } + return &Transcoder{ffmpeg: ffmpeg, ffprobe: ffprobe}, nil +} + +// NewWithBinaries is intended for tests. +func NewWithBinaries(ffmpeg, ffprobe string) *Transcoder { + return &Transcoder{ffmpeg: ffmpeg, ffprobe: ffprobe} +} + +// Transcode renders the configured media into an Annex-B H.264 cache file +// and returns the effective playback framerate. It is cancellable via ctx. +func (t *Transcoder) Transcode(ctx context.Context, cfg config.Config) (float64, error) { + if cfg.Display.Input == "" { + return 0, fmt.Errorf("no input file configured") + } + + // Remove any stale cache file first: it may be owned by another user + // (e.g. created by a manual run) which would make the service fail to + // open it for writing. ffmpeg -y alone cannot fix that. + if err := os.Remove(cfg.Stream.CachePath); err != nil && !errors.Is(err, os.ErrNotExist) { + return 0, fmt.Errorf("remove stale cache %q: %w", cfg.Stream.CachePath, err) + } + + fps := cfg.Stream.FPS + if fps <= 0 { + detected, err := t.DetectFPS(ctx, cfg.Display.Input) + if err != nil { + return 0, err + } + fps = detected + } + fps = clampFPS(fps) + gop := max(1, int(fps+0.5)) + + vf := buildFilter(cfg.Display.Rotate, fps) + args := []string{ + "-y", "-v", "error", + "-i", cfg.Display.Input, + "-vf", vf, + "-c:v", "libx264", + "-preset", preset, + "-profile:v", profile, + "-b:v", strconv.Itoa(bitRate) + "k", + "-maxrate", strconv.Itoa(bitRate) + "k", + "-bufsize", strconv.Itoa(bitRate) + "k", + "-x264-params", fmt.Sprintf( + "nal-hrd=cbr:annexb=1:open-gop=0:scenecut=0:keyint=%d:min-keyint=%d", gop, gop), + "-an", "-f", "h264", cfg.Stream.CachePath, + } + + if err := run(ctx, t.ffmpeg, args...); err != nil { + return 0, fmt.Errorf("transcode %q: %w", cfg.Display.Input, err) + } + return fps, nil +} + +// DetectFPS probes the source video stream and returns its average frame +// rate. It falls back to a safe default when the rate cannot be determined. +func (t *Transcoder) DetectFPS(ctx context.Context, input string) (float64, error) { + out, err := runCapture(ctx, t.ffprobe, + "-v", "error", + "-select_streams", "v:0", + "-show_entries", "stream=avg_frame_rate", + "-of", "csv=p=0", input) + if err != nil { + return 0, fmt.Errorf("probe %q: %w", input, err) + } + + fps, err := parseFPSRational(strings.TrimSpace(out)) + if err != nil { + // Streams without a meaningful avg_frame_rate (some GIFs) report 0/0. + if r, rerr := t.rateFromHeader(ctx, input); rerr == nil && r > 0 { + return r, nil + } + return defaultFPS, nil + } + return fps, nil +} + +// rateFromHeader reads the raw codec frame rate as a secondary probe. +func (t *Transcoder) rateFromHeader(ctx context.Context, input string) (float64, error) { + out, err := runCapture(ctx, t.ffprobe, + "-v", "error", + "-select_streams", "v:0", + "-show_entries", "stream=r_frame_rate", + "-of", "csv=p=0", input) + if err != nil { + return 0, err + } + return parseFPSRational(strings.TrimSpace(out)) +} + +// buildFilter assembles the video filter graph: scale to the display, apply +// the requested rotation, normalise to the target frame rate and force a +// format libx264 accepts. +func buildFilter(rotate int, fps float64) string { + var parts []string + parts = append(parts, fmt.Sprintf("scale=%d:%d:flags=lanczos", screenWidth, screenHeight)) + + switch rotate { + case 90: + parts = append(parts, "transpose=1") // 90° clockwise + case 180: + parts = append(parts, "transpose=1,transpose=1") + case 270: + parts = append(parts, "transpose=2") // 90° counter-clockwise + } + + parts = append(parts, "format=yuv420p") + return strings.Join(parts, ",") +} + +// parseFPSRational parses an ffprobe frame rate such as "30000/1001", +// "25" or "0/0". +func parseFPSRational(s string) (float64, error) { + s = strings.TrimSpace(s) + if s == "" { + return 0, fmt.Errorf("empty frame rate") + } + num, den := int64(1), int64(1) + if i := strings.IndexByte(s, '/'); i >= 0 { + var err error + if num, err = strconv.ParseInt(s[:i], 10, 64); err != nil { + return 0, fmt.Errorf("parse numerator %q: %w", s[:i], err) + } + if den, err = strconv.ParseInt(s[i+1:], 10, 64); err != nil { + return 0, fmt.Errorf("parse denominator %q: %w", s[i+1:], err) + } + } else { + n, err := strconv.ParseFloat(s, 64) + if err != nil { + return 0, fmt.Errorf("parse rate %q: %w", s, err) + } + return n, nil + } + if num <= 0 || den <= 0 { + return 0, fmt.Errorf("non-positive frame rate %q", s) + } + return float64(num) / float64(den), nil +} + +func clampFPS(fps float64) float64 { + switch { + case fps <= 0: + return defaultFPS + case fps > 120: + return 120 + default: + return fps + } +} + +func run(ctx context.Context, bin string, args ...string) error { + cmd := exec.CommandContext(ctx, bin, args...) + if out, err := cmd.CombinedOutput(); err != nil { + msg := strings.TrimSpace(string(out)) + if msg != "" { + return fmt.Errorf("%s: %w", msg, err) + } + return err + } + return nil +} + +func runCapture(ctx context.Context, bin string, args ...string) (string, error) { + cmd := exec.CommandContext(ctx, bin, args...) + out, err := cmd.CombinedOutput() + if err != nil { + return "", err + } + return string(out), nil +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} diff --git a/internal/transcoder/transcoder_test.go b/internal/transcoder/transcoder_test.go new file mode 100644 index 0000000..42b5b28 --- /dev/null +++ b/internal/transcoder/transcoder_test.go @@ -0,0 +1,123 @@ +package transcoder + +import ( + "context" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "galahad2lcd/internal/config" +) + +func TestBuildFilter(t *testing.T) { + tests := []struct { + rotate int + fps float64 + want string + }{ + {0, 30, "scale=480:480:flags=lanczos,format=yuv420p"}, + {90, 30, "scale=480:480:flags=lanczos,transpose=1,format=yuv420p"}, + {180, 30, "scale=480:480:flags=lanczos,transpose=1,transpose=1,format=yuv420p"}, + {270, 30, "scale=480:480:flags=lanczos,transpose=2,format=yuv420p"}, + } + for _, tt := range tests { + if got := buildFilter(tt.rotate, tt.fps); got != tt.want { + t.Errorf("buildFilter(%d, %v) = %q, want %q", tt.rotate, tt.fps, got, tt.want) + } + } +} + +func TestParseFPSRational(t *testing.T) { + tests := []struct { + in string + want float64 + err bool + }{ + {"30000/1001", 30000.0 / 1001, false}, + {"25", 25, false}, + {"0/0", 0, true}, + {"", 0, true}, + {"bogus", 0, true}, + {"10/2", 5, false}, + } + for _, tt := range tests { + got, err := parseFPSRational(tt.in) + if (err != nil) != tt.err { + t.Errorf("parseFPSRational(%q) error = %v, wantErr = %v", tt.in, err, tt.err) + continue + } + if !tt.err && got != tt.want { + t.Errorf("parseFPSRational(%q) = %v, want %v", tt.in, got, tt.want) + } + } +} + +func TestClampFPS(t *testing.T) { + if got := clampFPS(0); got != defaultFPS { + t.Errorf("clampFPS(0) = %v, want %v", got, defaultFPS) + } + if got := clampFPS(300); got != 120 { + t.Errorf("clampFPS(300) = %v, want 120", got) + } + if got := clampFPS(24); got != 24 { + t.Errorf("clampFPS(24) = %v, want 24", got) + } +} + +// TestTranscodeIntegration exercises the real ffmpeg path when available. +func TestTranscodeIntegration(t *testing.T) { + if _, err := exec.LookPath("ffmpeg"); err != nil { + t.Skip("ffmpeg not installed") + } + + dir := t.TempDir() + input := filepath.Join(dir, "src.gif") + cache := filepath.Join(dir, "out.h264") + + // Build a tiny valid GIF with ffmpeg's testsrc2 source. + if err := run(context.Background(), "ffmpeg", + "-y", "-v", "error", + "-f", "lavfi", "-i", "testsrc2=size=480x480:rate=10:duration=1", + input); err != nil { + t.Fatalf("generate source gif: %v", err) + } + + tc := NewWithBinaries("ffmpeg", "ffprobe") + cfg := config.Default() + cfg.Display.Input = input + cfg.Display.Rotate = 90 + cfg.Stream.CachePath = cache + + fps, err := tc.Transcode(context.Background(), cfg) + if err != nil { + t.Fatalf("Transcode error = %v", err) + } + if fps != 10 { + t.Errorf("fps = %v, want 10", fps) + } + + st, err := os.Stat(cache) + if err != nil { + t.Fatalf("cache file missing: %v", err) + } + if st.Size() == 0 { + t.Error("cache file empty") + } + if !strings.HasPrefix(readFirstBytes(t, cache), "\x00\x00\x00\x01") { + t.Error("cache file does not start with an Annex-B start code") + } +} + +func readFirstBytes(t *testing.T, path string) string { + t.Helper() + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read cache: %v", err) + } + if len(data) < 4 { + return string(data) + } + return string(data[:4]) +} diff --git a/packaging/99-galahad2lcd.rules b/packaging/99-galahad2lcd.rules new file mode 100644 index 0000000..bf76adf --- /dev/null +++ b/packaging/99-galahad2lcd.rules @@ -0,0 +1,3 @@ +# Grants the logged-in user access to the Lian Li Galahad II LCD so that +# "galahad2lcd list" works without root. +SUBSYSTEM=="usb", ATTRS{idVendor}=="0416", ATTRS{idProduct}=="7395", TAG+="uaccess" \ No newline at end of file diff --git a/packaging/galahad2lcd.service b/packaging/galahad2lcd.service new file mode 100644 index 0000000..acdb31b --- /dev/null +++ b/packaging/galahad2lcd.service @@ -0,0 +1,15 @@ +[Unit] +Description=galahad2lcd Service +After=network.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/galahad2lcd daemon +Restart=on-failure +RestartSec=5 +User=root +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/packaging/install.sh b/packaging/install.sh new file mode 100755 index 0000000..725dd82 --- /dev/null +++ b/packaging/install.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# +# Install galahad2lcd: build, install the binary, systemd unit, udev rule +# and (optionally) an initial configuration. +# +# Usage: sudo ./install.sh [/path/to/media.gif] + +set -euo pipefail + +APP=galahad2lcd +BINDIR=/usr/local/bin +SYSTEMD_DIR=/etc/systemd/system +UDEV_DIR=/etc/udev/rules.d +CONFIG_FILE=/etc/galahad2lcd.toml +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +info() { echo -e "\033[0;32m[+] $*\033[0m"; } +warn() { echo -e "\033[0;33m[!] $*\033[0m"; } +fail() { echo -e "\033[0;31m[-] $*\033[0m" >&2; exit 1; } + +[[ $EUID -eq 0 ]] || fail "run as root (sudo $0 $*)" + +if ! command -v go >/dev/null; then + fail "Go is required: sudo pacman -S go (or the equivalent for your distro)" +fi + +info "building release binary" +VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo dev) +go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" -o "${BINDIR}/${APP}" \ + "${SCRIPT_DIR}/../cmd/galahad2lcd" + +info "installing systemd unit" +install -m 0644 "${SCRIPT_DIR}/${APP}.service" "${SYSTEMD_DIR}/${APP}.service" + +info "installing udev rule" +install -m 0644 "${SCRIPT_DIR}/99-${APP}.rules" "${UDEV_DIR}/99-${APP}.rules" +udevadm control --reload-rules 2>/dev/null || true + +# Remove the legacy Rust-driver config file (/etc/default/galahad2lcd) if +# present; the new driver uses /etc/galahad2lcd.toml. +rm -f /etc/default/${APP} + +if [[ ! -f "${CONFIG_FILE}" ]]; then + if [[ $# -ge 1 ]]; then + INPUT="$(realpath "$1")" + [[ -f "$INPUT" ]] || fail "file '$INPUT' does not exist" + info "writing initial configuration for $INPUT" + cat > "${CONFIG_FILE}" <&2; exit 1; } + +[[ $EUID -eq 0 ]] || fail "run as root (sudo $0)" + +info "stopping and disabling ${APP}" +systemctl disable --now "${APP}" 2>/dev/null || true + +rm -f "${BINDIR}/${APP}" +rm -f "${SYSTEMD_DIR}/${APP}.service" +rm -f "${UDEV_DIR}/99-${APP}.rules" +rm -f "${CONFIG_FILE}" + +systemctl daemon-reload +udevadm control --reload-rules 2>/dev/null || true + +info "uninstall complete" \ No newline at end of file diff --git a/testdata/sample.h264 b/testdata/sample.h264 new file mode 100644 index 0000000000000000000000000000000000000000..a1f3a7989255e35f426f92b86075a8f5987dcdc8 GIT binary patch literal 129859 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%0DZ88Ds@06ZcJCOia*Ihl`eAnWTgtIgGJLJw{K&g zTq0OBkDl0Sr%9wYlIkv4+^w~NmF1n&n?cz$hXLjTYNQr$(EqKFxim#)Y!7+Q2MJUS z-&-e+%t2cx1Wh%E*HoxlObuW^b#YlTPi)rGbDDNnNi zWkCH@^hRUr7b@Zn_+*t|`iU<*r zQM{PKcN|`oz&H_)2oPx=&%*7Z!9B4+Nl`#&ff^uf<+0$(%xh%xKnj5h5;D}^@Wx7a zJ5CAAoPOqwdut#(Ag= zlVG!C>KS==rb$B&jYUbF_xc&R9UtrP8WVTBRyWfI{(4XDk$CfmiB3N#29(aO59OL$ z z1_}}YV0A;=QWN6a5ji=DxOSx)u{$2A1kK)4cSGhq#$UX77nq>Zb9%K#LbhaS&;0E- zL1$~t+*78K(b72R)vt)k83wQ$+{(OIwn8_C@$wXZo!St7mT3^CTMkpg#SY1P;z{2$km4@#(h;6kp{z zsB#z>t(`WI{YOy0 zJZ*-@%LU6FF-*2T|n4nR}Q@P*D1W#IInHhlsV zC!hmTfjt{1ZND$|y}MdLT0C+J&?8T*8~kA!C?kmnvSL+0EJ9k3yB?2O!#>bhkE4RO zat0kYjB1w+y~%Mrq1(=>77U{}U*@>-{@AC>R~c#1D0)lz?@YnzB-ug8lvW|PoQuyE z_{}xO%4xJ#MMD17EI1Xj=}myLBK;vI%0X}}Pc;)GEQ{>+=Z9FKj-wP|nC6zQ)y)Sd z;!zoBZ95uH`O~mBd^(;P|2QD4!*c9Fh6ZGbFaA}CS=IQLHE!7GZntmRN(acwf7`NJ zfpT~<*`z~yyU_0zjQXs0$gDb2ydRU>ub699{+Bf4`ew6xfZ;<26eT#KNI;#hxH%@{jJ%m==mAyK*qt9aH4Em}z zJW1o;Y@9;Cu>i{p$e%%wthGlx1tcBUfpGAmte4Nqx47(kIzbtb{2_{<#NiF#oE`sQz zywGa32j+}d871jhGGF`h#DK>x#z|<={S(+HMn@80H(SE+2q7`-891C4X}7989aI}C z#oIpC$*srnjVJQ#vfsVQ&JY6l=O$bKFk`p;>Qq_O$8n>+3^&;NLQZ3@mM^}GbY977 ze;#As%wVJeU8H4I=^7gpGb=`_{8x+Kd*%mtnJ_XjIs|3-CWmbW(cDD<6)4Zv4V9K% zc{8b$-k5@aVtp{G%whsne+QIFZ$E6=)YLvD(6O3#Ffw0ZW{@oa@{$F*oE~YZqGrWJ z$QM>+ui0b}?DT5Ce12ArzUDEP-Vt9G!C)B$CNvazLgMciznrzVUtb@&50!}-J`qNk zFK+wu;AMU`^c?@ME}Xsm(r@o+Nv1P8J4NJaPHgmsS#Kzy+8+HYI1+b>b^>wv4DS!k z^b;jvF*HvczB-kKfCYTU-=Hp=xUL?bJrJ~3X44E0MY5VV4!LTW9TRd(ZntKX_w{2K zI;qUq*zMqlYR?G+EvoF;YRLIb3gERDST>Cck1)wq>_kLz&E zLS`VFCVQP+UKomUuY$k$u2cSJ&!H~mDg%s_<)Td1c@I?vAT*p5(g`0v01)JakUdfu zi1WFWvH1kv8@^wK{ds+#`1)-<;0C|}BzK#gZw>8{e99~-_PhPKh`$aOb@PM_Apec% zaC?l><%P&+(s}C*4--qTOuwbvGa|~QLaGP@$YfnVi(Xe+Ww5ALUqR@1i3RtL!^#1rDva^EdE_(1}LFIRi`tTsTAKMRAd0I-xq_%5?z zy86K$-8ZLMU5&v2hm<*OzTs^|TbU{=?D0P5Roa=otz^M$o^tlz;lEqZ>U(G_QoM}# zI%EI5IHb4Ewp8wUQ+vy-5C0=eMDflN!}WbmmV>&z1pz?Iu;JOP z_T{y=OMXq}1+Kq6f#Eb=U6Wdl@`5wA^Q@n2zF1K#Xs0b9^Qff1UTc zE!-Am_%Ua>4X#{Sh@!yR9}pJIqWK~rdB627_2yd}`K=wDotq{`M7S0}pwM4xDJa&{IFmF;=P@Zk@ro`!AiOmY`zgtS3Jc^ZfvcTJCgd}Mgyqw8d`}_iyfgbU; z>vnnJLWCgiWe0S}8Y;G?lvc+~1AD*xKXde)eRrLS8+_N;z?=3cTtsJaH{Su1;pRg2 ze!yMat(xVkMV8W|yL5&?^BrX^}4IHv#gC zcG}ewme=Dhsil@Io4|_^4!~s<4(AxP^~vvtp${F{uwK-K6c+ce4vam|8<|Y#1iYp& z-KgyAX2Fs~>JXD4Z3t0v=QPk22jafiyf`y53!WcAQ#R{#lBVCJr!>sX5L8M40pgXj z7$+O!{#ZmZMrEjg&WKLux_OT3dBQlE1)2)Nnb6wIxWKcI<0?qf3s1b5DAdEoj_4oH%9=g$?E}FmJA32Q-9a$$z%-z_!rn* ziaHvbKDp_!xcmQ$wrjhNw9f`URIb*XBIURcUnn9e2GBDBQh<;(TJu`BMI=uHes9w@ zrm2nQGiCRj0SXh0o6LP$nsbvBnGf8O59`9`eTX26J8$DRfOD1M(x*GOkk zt)||TfWb!YOGl>|d1~w_-q~BD&6Aab!L|%z%4gBJH0*oPW?PiW@%@ifMAwMD371y9 z{OjHT{~zJ$B$BBZ1#RFkB!L-hK=DCg5lDo~xpHQR{d!DT-8rz;yy@^*^Ml>*TS0&j zFQ@!cvxv4xs{$Gm3&~iTe4jvANH#h#N--t2eAc%soDIEI+4ZpZxsAWuEDMCqv%QeV zMF7iaSBtveFsqiN1p!)Yi1TbA&H}p7+4|!@=^zL)q~Jrsy;FkuB-XK``}3(Q-#} zn)d$RLW0d~A)y8Gh;I2hubZWh@H!2u>s={v_SwE_ki?%>l*6d z^I4BR_0g995W*}H0_5!1d6~JmbaHo(3Hd0QqWqHTTeL_^f9fXIx>eUNOK;+TZvWm^ z_YVQqHKG!qLN^X0#Y)m;C6fbX846fAD=XBsy)-YjnhFR2wPyeefWxtgC;?cugH43y z*q6$kCI4qGziAQ(P<&RwtmXI075}w-Bi0RQ`T2Fjx^GHo|0X2=x8C)iy4S)>&>8)A zV*a}iPyZingBC(Nf?l;87-<+iJ-znQ`r62+7uKGHIX@vlm9Or`^`2VpmA@9oDP)%O zUYIp%Q4R(&j$Q<(9bM$Mz(FCd-;?0*aWbYr)oEtZwQ3s71Bq@5zclx++asnV0Q78# zKmE`PYJTKu;1*`!un zq0G#KJp4@zXWAKG>s#au88=`dh?}5=di9YBvjvvUV6BtHGQVn|bANc>TRuRzq5B}7 z5rlZeC6x`{^~|k1a`lrEK1TqbMV*e=GwzJVgZA9}12Y3$=dN8^gw6A8FO?SEeuR$8 zo~kJv$lH)OC6X>$I$vSsg>*GB2N2#>!x$ci%dY87^DV%vhj2#P@6s~oKX6JDqf_op zvS|Thld>knM#?muurOjnV@3~P;D}Z&RI6v+%obUzCy+Y*A!RVuyv>exlECbU3(b=S z7>a=9d{T%zrtEVh6*tK-6C#5pbB#@gD#6JNA{<@T5}_yk(dNf7PI}#!DHeWl`sufg z1F@-RpiZXpk*;s$?WtyiwG^E6wIyGHe7zMw!1%OiF+Q{Ldg(fP%#WB16a*UvErp2d z(MQT73ilgr?Vz*1PO64OrOQ0zw^TJSyu@$mp>>c6TqQTR=c}G6Z>i@^GJrlU!v)@s zOOi921OaWGKf>ZNQUXp-h!8eik8WD2Jm9>`Yxl_tEN8K{BdB=w%W*_stmgXI_xT^w z++9LH)$U%Gz5|#D`e-4@QenU3-ie(p7U$F{Gj#z z-+DSz@ng3f$=;rwk>X)&ynQ(LBo1G&b2>3b~U1f`9*Cn=h=8+hm=an5F{X-GA`ks=% z9)5AmJUmX90c@3F%!skCbTk9u?fT?6cS0XY z+Sh=6@i9j@xKT#WlSB*k2TtR@gtD*i63$*F)#m0P>z34b&i{nhZ4i1|t-|M*U2+@7 z>yF>eU4hfr&X1GSNdf(pXj*RBqdExLfHg(~8i4hy8M>o#KvM;&*!OJdwN^#`H4Nib z!~8MB2oP3Bs5Z?ntLpxzx@g~Iv&KHF3dKro?^dC&RFhvGF5dV>EvST9D3y#qmiFDZ zFD9%?aKCC*SrUD=h1r_!dj5Kg!;CQ6(`DGpJRpMi6Q8c9w!)Ot|FSfE_54kd=jxmy zS$zE?w&xFG)4E9Ztt_9gssjm_IE@gZEvK=>A(7WG`l;mU;%x54@j{X=MW`q>7Q4>7 z$O98Y^2bIlUQ_j469*_XAYKQnrX8T7x5wIRgPq)=>&kFgCV+^|e9Ty2%(=OM3nsDN z??k-M*<|@k!IF=1{Qg$8Tc34~rAjFooM3=37R>5lzsnJ_-p9_1Yl4I1d$2y{KPm=F zy)!*BO&hVon^LwebVY;i)y2`x)dLeOq2ite{UwFh^EacL2hZ0~_j?*+Q!FDwZdKqK zjxHKM1Gts~uM)k~69*m$erpAqb}dkLN(F$T)eLCfkZDc=gNfgvA#rz1s!DK(RlA1u zHmL4W1EV;8^30_#Ai|6{{+9k;IqSM!&DzJ{<59|~-ERY@9g$@lSl}{P%inR+_Y+cB z!}~)>KIj>LwzD}deAPsD$F23U`=nQY-+f#I8&jtteaf{C(O2uI(7CIu=H4P4B=V+! z9r+(O#V*IB`Y!_I&6P3_GO^E?rEoA+Ut`~+VtWJnPrH5Rs?TAW^7p8N+kRE%e>XU-y!XLY@WChx-TE2tw^~42%O-?ke`*04!iYZW z-bQaVbkpad;KC%zUYhV#;nsyQ1xK`gBQ8b?l;O)c^P_Ka2puK$+C>DNdg1bX3lOGs zX6@!!zOAYJfrXKmcxYKCl69}9_;3}k94dQ`0z&LWQO3?`jV~-YYcBBMGLnl-efrSh zowc5bD6QckX;twi!iU0(ML;k<2S}$%fh|&WU7d9k)g+@7J`)j3-PraBSulT(=dzi+ z+&-U2&GkylaJe!`rc+-68^Bu-Ph`m;G(*95&Jd3wa>jm%dn>QDKz#YVWUrveOHG z9!^#sv*~4l0}<_jXzeb(>tpBs7;}0w;V~MZtEjMywDz3Kti%7W`-hZ3h9Lluki&8h z|Lx;r)^Ts@BOV{S84LaCd{6*vRgp`@i5JrwGXP%19D+d3DKOOnTIzItUrC0D&>q!l z0|K5>F9w;AT5CL}zU9*I(X8(g*f>&%$Fdja-o4Zm11g9mywN=pQk|uGO5l9+Pr^DidwX+6rZTP)HfNMrirPMmp;cn43;>_$>ZbVjvYNb)8i7h&~(e(*AsHL zU}z4mm}uTDGiQ`IL_hAAP?+59VEYm^f})}R)U3E(;mEwF1yvuLUb|cwAy82 zs@(Avi-ZwD)OOH(=a6Qee6P{O)VHtD0Dhol?e33t%4FXlUi|B$Ug4&n$480t7bZT4 z!PeJanJV4dVcnIQ`7VCK_37l1*FP5%cg&Q7B~I_xuF~f>^5DxFIGor9En&E3hcV|I zdUPqN(id2wFQt~t737QjcXpxRJo!#Zcj~=Xw2+c6p>E-RHM>+6iZw*YCu|*}UACIgu;?`AkqUAeCkS`R zmsVXfH5&NBShT9_w97n6qu9}z*153t@UBv)rb(es)914li(L-euTr%R->c>mFKT(} z+}phPLfgS@GZG-DV#|467QZ=Cod7xzSeLpCxwHq#r2WRu*h^2J4mr^-N+)XOb-(mtPiJ>Mav^A$c5*DK(yC7pmGTH055^x-An zIBQIRKIxzDNm4)V)kYm#R0?;JMCmEiN1_hN$gbQFYy-QRLrWkAv%-LYuJ*}8$T zCIj_<@|q9NZ5-f<7H-PAlro^BDm}lYTJTu5lJ^^9!a0NB)cYAUbp&ODPq?LbZ5TzSkSyj zj^Re+vG0-KlSTG{ADeBzQJiMFi0w&#Kpf;2{X_+k=*j(rOX##6;1p!&cC)TAzI)uRi--6dgcTTk~O?Nk>6BGYZ4x(eLkpCJX{bjv^HlC;K49EoQK6AC@XppP2K)V(phO7`6%2f=-nUdO`GFHiu11nnJ%uVd zc^ZPYzGWib zS#L<{WiXls?8Cz?w*1(Db;w=AHQD75SEqmSusjSW23HSRGA| z{r<{>W^Uz)j;x+>mNvt7nTkN}ZZEDvXz}ysat1hmMkwnKdMQKK^Uz&OE@CF`W?7Ep zScbnaKV{}y9Ee#svYPH}H1Qg@n)JtLeUd+C6NVQ5(!PCgL)+2KyiOJVw}kSGrEXi8 zipU(r`aiu#PG1IqKo)UJ9hK;?Kn7|0oTeY||OR&&xCz?0pTa&=n zx4^jU`9fC6ruRC8hu6iV8k7iz(C)cjM!24dUchMG{c8KBK%~u}SaQ3Dd;g`_%3fZf z_S_oEeQX-vp<=ALMCTf*h7&5WDFK$@Chs4!+Huu$Y}UDo3-+3fI`hg2 zmKXs)LjlrXd8`o~iCy@;%c=r@?^g$7Vv+DYY)`z$5*i+xs5h^hODS{@Ie!JdP=fqJ z_fFcspq2mg!($YnICM|UgGc{}fl~QmkpTnG;LAC)sEt@>^Brw|RA>g$SMWl2V$jRo zHH)c6@8?(sZ)yF?OZ5`R5v%@uY&Mv}ZE7dGB`ELtW(MD7(MmYA{ZaR4CE(%8RMbBC zx&DZOq*ikor>Jjr^<~Ip0NQSZZzj;lP0ktW=If9hmaJ_biG|(i!G^*VrBU2NwGHN0 zgiR;Ms8S^T@Izkca*dBd<|Gv)%T}aqqL>v?!^QxCEb?5$wIcN4{)t|)Q zoG7X8clsy!(?!3DXElxU>~td@(ziOC)OX@Z0&5-~{vepa)(uK}E{=y{(PHC;FJiZE znr=Q_^I)ODllv%g<>{3RhNLe;u#z?(_?FY8UDcdzfBUzgD!2X2u#4GCW4@<^EtNFu z1X<}u7cFx9vV`Rtp8v=y?AenJZ!L$@EfxxcF;$1n>1?1q&KLaAbg^N^r{(A_M;5bqY9{y+qDC$hOwh^F!5g`--224p2c?CT|8tOZNS=`^5=@||{vk1ziY6=>ZL&hMoRyDoB_$e|j?8>Q*% zZY9~K3$=ho2{BXjJ-^BDLOW2(V}*Gx(rk0{pXvVuN|e!!bdRsgE~|}C5`hpmIEUn# zR5!Zb_b`l~ovRd41VvLx^Y|u8OXY?arwH&TMRRB)H|^Ie)y7G=!8gmYw%eJi{L{Q@ zg%Xx(pqR9OcLHnt4N|$T(rAr$q_+GFTVHE>;m?dSHoHdjEHlP@Y9tn*q_Cza)9p%+ zx=)V$#+#P=8v8YT^iw`Azf*RM@*}R~y!mpcnTbl*1+^iTU`&F!*GHmbY}peL8#5TY=?6eTI{$?5{rn?}IMjPro{I8iX;h zX$M3meyL*lyh9&_*T!&((+um$8wo7s!!RjC7zNVe*qDcXpt9IGw2d?{y|F}1ecKVCwwwnjf zG-gf~6(9N3xu zUS3}@p!o&+VvFf22O;3`1V-M-U8hL-#_RlV`J;MIfwvPj{WXhaX6)|&Ea!Cq&c}Fj z#Yd$rP*yx%EfDC>16l9)iE$H+7Xh{WwBw+QV!7r8414myYZJt?Il8g9^S{QgIs703 zyYQJIy`e!Tb|mTS;Jk=_Z)sjVFp*X8F^D*QknX;WWx9O1Gd5!*{au0Ny(0c3G&Sf* zIK|SJ5?8L6`R05bG%D#H5eo<7`_9VA?naHHNRbFt+pi%mp9 znb1*fi&td;UWE%bqU30=*o7!?aaw+zargAFSbNP<^V9~qq))qc$R>Qd>V=b7&;2)F za&_JfU^hgXdk)taA34~^i`?>X)o!;A*Ub}|-^to;twSHu)H%PizFvz_*S>m0q#&Et z@4O*DnU^WQ-gi;=uo56mO-%z6$Ik^W0deJEK%j*hWbpy5LGF}m(D(TlUL;w1>h=NU zUCm^fCbKh2tYF>`Il_&NsMkpFj!67li@kRYZKI=K8+0VqfV=wuZr}?=X8(HUS3kP< zf)Pvt9Duq(g;qa9yQ-cyognzsCuQ;H(68^EY6D{9GmKTEwlgmj+aizLZp6F?0m49b zllk{(-FVz_)N1*5qvQUSBTa5U=TF|)9Ub*UBeK?r3O>RXCGhHP6s4XgU7=a2q9+Q*hO^8c)yU5;=Aw^cu#S%3yii$S|8;CCa&PpTtAcf z`{((+6sJRnXK3CDXE!XvCYeIK@IqlUM}J!;Lz&l*0be~}a8cXrr?`+K!oI$d9Qe9lbIGcd zFt_L<8DlS&tYj(1#RAelSe<%b?EKZsJ@G^J!%`}B%f0>YH~z}BDR_NEKR%3bJNF&V zs{@o?86DhrAb*K=c~E>{>T4B2kLz14x9;!y%dH2ft->=;lQyVz+>vc6qD}w&X33&Y z^dN-AFWhdN?L488%U5>$azSwU@cwa(YciAB{BU=g@yThJxRY-Nc$7dm42dlT%r4;< z^t771Yy%9_j&*zOJ+h^%-QJ+dHr|NP!7FRNo`00{=;94u0fP2F+0;0xk)U*2!l~Ri)TeJ}5&Tis%a#-{h#C^NJ(Pb5+sO z-;efV&l!OuSD~kNG7{}N(w=l)40fCs!IY2R#(Pt4m!GdWoQfb7-M0A*WB*PTZFsW~ zFbFNfbF84pPv?SoVRqTV6B3b`SIMr$t@?GY2siQeiJwxGmd3igwC!^@UeU1qf_=v` zzW=HSdLO@$_bW?5g=lyD+uIQ=nHXSRm@$9odAf=OPbWE^s}W=#dJMfStAj?p2p4F_ zG1yT>L2kct;99Ghk#raN#zW){6E|s8Mz2|9N3u?aO(vej$@#kFOy?iD_tH)~0)Eoa zmAp$r;$>bR1(ze={gSfzgzyw$yE@ml^pVp$+~nG&w#p6PKK4-Fy?-=1&tXwLf0cr7 zHgj$J+BhJy#CUs+$J2z9Sf7oS-J7~cCxbEZ4>{!zJ~|hk{R0u2i6?d$CM~MD#7hks zsxs7nb<}WWYMR`x^mxyfrbf83id=9m1}25KqDGl=)J&-6fKtl}YCW~1=&VoUACXap z-INjWPwa0v-5w_xY^6}=_C-g&D-xas@xCBPt;?G2ipx!0cJhAZm4p4IB*r`b~W*|CS)v)8xN@HWngyZoS+E4wkZ(XNzKR>~+3%m2a(j?cU zo-c7;JAkb*5Z7-AmrN$xy*5hX{ak(Tu1mwr^&7`G8^Y-NR4+O&g&dbVeN}4n_z(sY zmkeB%GF~-(vCu*x-><~dTSPnPr?h>^F>leUkx=hFso=w)C&6NF+7Ab}scmNOcj>)V z?DExj9R3d9)cTt~_`4z-?w-9|x1J8*asdQ@a?eZ>HJ~P>DU)Op0AHAr0Wxs;I+CTZ z;Is1g)N5iEh@lz_;Br8BYcb%e7wY!}IWq}D=Ufpocuk=#m8*lJ@Zx(n6cRW%RuN!R zv>vYie0BK?os;9`6oyQAyvn0Rc$F)7x*`I$k_x8EYFBz4e?CemTBp2f2U;Jn?pWhV zgsQLpP%RvN{A>f2MIo>V%k+I;5=|anxMBl+vknLNP*a@~PEJyf?qGw9>rnzP?HS8T zuB%LdTYyQJka(nI*GGQT1qV^2xCc-tZInf+{^xW^E3xVIAVyxdgjy2mxPLif zP!KVriTW7$h=77^Ed7t>7UUf5{2MV2?-P*bM8y7Byuvje|HfT1m)S?ANSqo|~>n$E@v zldw0OZaB&Nel!7qrrHp!V0Lv54ATHw^y~Pk|BtRW0f+jF{>SfoHe+mKAN$yeGG$2; zW0xpNQ7L07L{eHL%zNxxNQzRKB&k%C7FlL2Nu@};ETa+?87-D9@Bj7r{(t}P|M@@9 z-}899%`8F$YE9~aeQ12q$3 ztL)WIa3o*do*fuOJmtgJqEMj?M1lQdvdwxzB&5q-Zop&{N#=&qR zN@LD!)Vb=`csCS4lZi(WGa5XvAf*J648@i|38)-fU~#7zVe6vb*_AjiH7EN(yK|ij zNyE4mb5;?{3_L~96T55)pdjFB4RHy343NE&|Gqv|5J-6V1{+;Fcv2$zA1UaO;NJWx z$Ehh$XLw)YUH#1~FhjrORApV(E8aYJQxPX<^$unuRo~^cqd*keEKJ ziP%Q{6={lL-wQ%tFo;aR6k%fut^|(0ngZ$hcrV_t>F$n~WRb@TFF~{7#pbJFsjf>J zumJMT@GwF8Te>`NvYJ5(PV*rc=E+~iegY&hL+)01Wu`}R)pt*9Z~qI=R9vdJpH8pF z+J|ZA`jx9!pOpCKmGDR7z$w9zV>eDLfJ#*At*mGIQCFWnT;A5X`v_=?gwJS-n%^k#&cI}MRDzJyXgB-c^v%2@R z-ob4Cnu|3`OT_w4oMU?FV_^i#eK^zYQ!9=eIE`>EjReJ_TklaQ>fyz5W|!CdVn*LNBei?`4*ciyr%DjMDMsG zOBG7~a-=QDCT0a)#eN!NehJ{HbjT5SAk{R{Fs;@=q-F&usHzacW#=kl`B3^B{>XW( z`~I2Y_c+5vK~KF?4Sdo8WS1D~zq;zo$rHVl?$>&anf* z?4dyzI_<>UH93-F8I-+rFtiLHK!-hwT{U;DPSCxtwIr7ZE!qslsfyS)s@rkL=^W2scj#lFKRXPXV z=(!1Kso{mgPT&l2VxT|J)H0mc{^hD|S2NH`g{dJ4gfB(nFz=Ysb;>shPc)t zNYJ{taO9^Ll1vui zZ{%Ei`fS6VtY`ed*K2$>Iko&^FoepjFkSCyuUPu` z_3q7B=nEOURcd>B;-7!x6ZOOSfN4|ZS!HkKWszHCJ7$2#YaaXeH+fYW7OK>x%?-(O z8#|Kwz;yB;kTrEHfkkWTLKT#dM{72ZNLOVG=WhH;UL}-_5V6Q>MRAAW>8 z^y3(r2*acL;d99GMBNR2!D7Mh<`J>?XUBHzeBFQ&Lc~NZhWF~3Rw7R$9}(`oJ)6)* z)_J1*#}9fC3suq&ly15N_Fcz12Q!7OeG8wwez66ABzBxgTB3N&X#6MGet^GbXispo4IRVwB?BU|c@S=( zQD8JwlXE;`!>I^VdzvcrRj%=1=C6FH#MX`+)Uf3BaOGCa;XD<#IT?b3E?wi_wDnfW z2StClCqJ>5H1><^&u`*rRQ-Pkg7|1S^MdIe(hambG~*9%vV?~WPGoBzEh zE#ls~>qizm^OoUD!`1~K{}_)!!Y?vP6Ia}pen2t+e@F+R6-tG5;E@v%sxN< zwPC2-Q;j*;y04~ss4{1b?3uI2w4Y~Mt?Z%$>OO!FVHi{ijKE0}43m^hNfJO>1H|R3 zE)<*S2HP9%T2`Q+?0%rpoDXb*EXU*@P+}e zF2&QmR+x-)~!uDL+(?}Ua_E` zYOmQu1%Chzur6A+ZC=5&tU2+MkL$8~kDel{Xhm_p|AxhbaAzr6%x~IzSe!sPqdmY; z@o6MBmA6{6Eb^1AT0){vVF^<(INB?rwh0+P$CHv?v7k|#3nKk(Bz5! zmpmdcl~rM4!wN7YYWV_k&`*yAEseCpl7Pen%(d$uEryk$FR6qg?F>7(G$8nalA4IX ztC2-?5dR2#CtsYj$f$=gM8K7fhUJccSjP=OHFCCZWl zRZI{H_J>pnAm$Gq09IOC;%Ku)uK;Au14Rv|Q^y}al+08kkWqo_@W|!|;`Qoytwne* zl#3!9iiv`J_`O(wtdWNUOyoZBO?PBn+sN`czrh9OOdt_+UH(GfrZRe3N{Bns&L zrpr&g9wwX4d|*zeCp$YRkyprZXV%Kt2cLxeK#BiU*DghHf8)20LKf1|ej~TM z0UOcHU?2~vDxCx6RNJ&k6#*EbZfB&Pw(Hzi)SJXuKt#FP<<6q#F23FRhzke?6eV#LmxB{kddwAS>7YWZ z=#ZF`zLas)j!!6{;|&0V)Dq-==IUpbBJ2BVYLN)v_O*QylAZB1TWxLIDpxW~ZQG6o zD=ITFDi39zQ#il*g-UApSDO`kMA|&;y36yNv~i6=?Ymz~X`V4Crt(1)dU4JYFp2kk zNBrP<#ueZ|v>f+JhBf>r9Ug6d7bF74#wLIms9IW*lb=cAG*>jcw#R%~*~g6+1iu%k zd@NH14jRUmAl1fq6%>8N;h$aB^(5V(rmc~u5#*EfUleVN8iX|ha0XmJ&w|UET#AB3 zW2gfOK&3&302N~O30;6H4IVG5Fba?js|5n!3M5;butBaL9jgAtq7xmbDq;*ZF9$yk zSB`BHA9TS5z4%^lKtO268H+TImjX+e*aKl1Qwi`P&O)0jD@%rl^-YQPFic0J1rwr* zuBzUs^IC#i^A%J^7ETjpXx>hDRqoO&a0-9zKk@ zL~W`odG;U*$!rtkPm0Z6L3K4!xO&MD10g}Lk1qbZcus^L@ZL%hz*(sVvro46#8l@(eiRQL=+O+V)O*p|O0l(Sk zw7BMH0c3gLLIIiX-o3DRDCYutfg_!miBASP1_<*H3g$Ybo5NBrIFI3*+{JfRa@9{B zL|0}3{KSLfrjM3=hZ;kI5yC(OZp})!W?&q3`BU{yqh*vwyQ+O}K8nmnc2cxzQUd)m zM|5Ab%*@SgWD9pl6459^GwviLyaQKDTmZ5*1x{ccs7Mp&kkUm&7VQmVc(I5c-{9kg zMg6m2wMqEr`Y8@iqsj0}$Gs`L^@)BmPJtRWT5s$u?J1Fd2l|bc?K}9Rg(zd~ka+uX zW@pz*+g?Yi_^9dm-xGT2?7=3~7cL31@PG6J?}lTlLi50JwT9Q)0HMI7E0PBzQLw5T z09jB|PEaf4#em8*K63GLakt~N6a1QTsDpkp86=!bXL{DocnN`eqU!RbTg#@ z!}HG3X9mgGXE(YCnxMMP-CuT7;!FDriA)^xqS4CL_2_p~xYh|2)Ww(J z9qD9Ay5w@}Ox}x@qjPhP+L9!6gxbzn{6z`QX8%FWV^jf|)4VuWMrx<&}UlhT?utZVrrF#1^E2XuJ>7W8_S$l@kS2Pajk(m5Oxe$EW{`VnvB$f^~8o-L#`%MQ63( z;%LOEZAoW~pG+dTaJjR=aC=*#s$59N#>`a{9gS3S0M*_`)Xjj^7HqI9?U6XG13jqr zE-}4a{FRBsyvXGNU4Q*->*S44cB!f#+1*Ppl81CbNZI zd10Unr;0RpFGxr+bDNy2w2(UB(97u?di*#k4rt!XQ|mW6ugwoZjlD_fu;>EE?V3qe z*J{J}Y}=C5Z2aJKsF@WIs-SP={najOH!nr{P*WrKcRe@14xmb7!9gGIA{Mkp|36kX z6r9ZNWp~{x;xq~77KZ&vUP8}}`>hp?Ck{F6I-mM`re9f5V6GSVDWT%J!vljSMdc=X zT9+L@a|=qXHxQp$Z?aG$1wKz}bvT~prXzwLrd-xBN%w9hR6-YrOMPi+U#RAeaNJ*l zz5O1BZ>|JbFC-{COzTqYek|8dw{ds-iXp#+zdo;K zOGUk1GMw>BQn(=dJ}Ls)r~ir$t&QAkobYhBWkNLI}Hep59(=^#H`R z=?8z@axHhk1Ivv{b7b>r6uJ60f4GHDs|X<{QM^)O@ZcdIpV8+$HZ~6kV58pUqb64R zPxxj^$%J@{b>gw^WHbCV4D~uc;`Qr9K!HIB-aD$`TDk!5Ur+ehRN}~e9d&B3$r|RP zCF6grRoqh1d&>%qx>3nMJ{kRT?Pt#)fgxrBW&J57%c0j}&#~*@$yWQ$r0&?Hb6g== z=c_;oS=M;I=|gSCAu|b2V0;+( z{*3nlhP$AdK?x#i7e2pU-ZB2LyQ;~GtfO9A5axJ)ibZJJW2;c_a#Nyy2Hz-Yb@ceB zYn^OCGf~MN;PlA#5)N@Wx;yV}o)s9$NI!Y16f84INK`##@Lnza?@T2oq?kLGT5dbz z*RqT9N7b0%e`imq3@K6Dtvu{=WnziI&7HorLWg_6XSe>xZPAsuSidr}OUJVq(iu9Ftt-abeu+Xp=A$txf8EyQwX3<-L z+sD9hYq;Ba9d$6C(1>lm@kYcAy;tYyHz3NUMIGVO9=EkAHaC9HbGh*0#%hYVqE)4fKl|9~5Ckl_g3Ieb(H2 zue%e7GCz^dTs!W*zf&LO>vza)-I@8)hkFHmI{1tn8QSn7U^IKtkuCjn<3s9~Lhth! zHti{9!*!+?Km7Ti6TBDQ7CQ9Jq@UP>38sNN#q@~V@+rAM%f>9hG(w2cQadrDI)&LCyRKtVP2e6xDBvkjYHm_yS}8;80})3H#MvBW66$h;}v6* zPu_f2mfa3Yy#bzAsT73j|A_|6w$AURQr$Yr(0KO1A3Nh`Qn7}!)qgF}a=z63ccmim z(2)+ROK!Aldz7M5?D606m$E(?)BVZvP4g3-SCjN-Td9@LdM$!#cl+aft~sdOljY)g zvrL?2r_PokV zlYwD`qV;h|zo^-><1b#b%3XTx_uS3_zTNDWnIQ$g%~M#Cz|h@E!Za)EDU5+j1j%1N zO?gtzCTSqo*S`{z?fSKdVY{O#V85AKpC__!nhsKbFZ!c6Wzo+feR<~%UGyzJnAW3R zmq9rDLC$i%Xi43BFw2sl5l)tS;$U#>oVZk3t&8sI(6u)EigA}@=J-osX;{KE^}%f+ zz9u)e5J>iM!E#l6xvp_l1hxH=`9k4LZj(ro_|m6_WS=Di16;!g5>9@Zlc9C;ofvDG z!Qk>^8B*QVSE?wUd&+OK90ikyGToN^bm=s7nGz5vlpu2H*-HiO6Bm=CLaB(Sp*o>9 zHGA8e71uGo>z#^q7S}e*N0vS}Xp(IF8^h31rferK>#G>PiwWd#^rODJ*7v;G=KcIM zeg*xKSkocvS5^-l0yQ=WqVGs;CA&*q(Ck*9!yiact_Yb8H5T0w6yWNju`LQ=PI>M5 z8oXuHf?IIuDx;TO$7|DDB9XB}@uVm-mH}$l5bd>hyZzu8t*2z(ZCNE26Gysw`={Z< z@r6b30IuD_-Qg7>#F#QJT#v_-=!-`+vZv-(-B;zCvAvEd%1k9MJbU(r%9`}Zj$$O5 z-HY_B*b!|C{4^y1UF?wQMbDcrwwo;Qiy{s>fxwsPMD{V}(}as6$NVlZ6AI_~aM3Iv z5|oCJRmhA^{=VbYaU^O}&=I9`6<&4of6z;^(niJ)*V|NTPvQh3@+_y|6O@yx@1|17FL|Au=4_TzNfC*>8kXP4_Y1m>3+HD+84 z>wdU;Zs5@IGg_Ok{J1VPa&`Qjast-qS+C=^ZyHP1=Uoc4{Fm;ezkkCN*;_|juJy(q zy@_oW32{?T!#1gXJDI)7^o#r_oZ@W}QK0edQJXXNgQa}3_&Jc|Q1H5vT0LDc&*$W8 z5DZWl5%$(5F9X;>`K*@l)^`|Y*l6+S(=>Ac7GzCMI_Ru)YR$noRUu0aPu@_G-}5Hm zwk;%*K;Jbf`|fh&&-qJYuEcu!XU64)yt)3jxefc({IYr z%?X=!jsid}OqIn|wdc$!SU=4K*D`?JRJSqQ1cO2ZfWF3izEzA96~D5EPR)N3r|>BZ zmADLOvw~hA+WL$MUT)~gOey^_&%7GAZ+^LooTQ7)^96o-rh?b^;}^193EC2eSKmBg zg3UM5wr6hIe`b@xae+mI#W53c?`7EROPsdFyb3wwS<+k5?a84dHfbYZ9AAD_);6LM zi?VrXlzHlf)+Pvu0n8=p*3@dlzSUAd_VNarXyyW(Bo;nrx*ImK(TjR@CSa}1#U)uhP)@-ZR{4d`2S&$acz-6`HFP*YRrF1K+u@SKKH$#tn?kShwlTNu*FHBiL;@n^(8P2#W%M z?n71#CIi`QF8{RidP@*d3&aJM*B{tAx!|twZa5?S-(DnpTda~Mvs|XLT%eFP!Qam} zMmAGi6-TzoFx9URs26KiTCb<;%GnNhR5yJOT58KsPITIEFd%#e>vbhOV(F}yu{;F{ zoTdkGJqAVZ&*co-W3Cwk+2cKQbyv~#{rPL+7v`zd38Rwy=m!7(1!Dd;sY+?z0c;xa zF4QeLxbE+Q#U`f!wuqj}Q37Mj9QIH!?d3iq+Y@p_z;9-x> zwkruN9Fg&#UHf$RoH0dl-Ca*Z&HrIc_aIaECO}9qc%ru|*>;vB&sg=BzWXv=$##}W zQortcPt(sAsHAj|RJ}8HDX_a#POsWMvyOPFK$FAWEIgCt{ctmOP?S*mt-{&q<`&*~ zgXX5K`z`lJ<;>vL@2RgpK^&jN>-3YEeKf)nVu3dQb!ph@WA0}5jPrMCpwl9)`%tPnt&|5dq zy<>X?AW!QI-+dQ0u*eLgt5IXy91gztnstu!XX%3sVf%7L(1-$Wi3-XoUL;?%f0AYc zNS?vl!oX??4c?CQRN_pYWMSV!+kJ`m0>Yd5Iqw9Y_m^szC(Q1o8ymz#076nRBPi@V zA@_-bu24mn!?%F**}>Gh%vfV`*j>;;4S8hl}{6#4DNnn;h8Df zO;^h_eM;uV>A+0Hj1NuH=Ibr8R@46s$%iMINQw@pMoZV7e_l2&T1(k>WQpZfi6)&1 z?QbWNeHql>PX>!*{g%nzjwxH^l_1gDR+4i6ZkNbcD}ZpsnsYTy1>)-OM^QRkw)58@!Xy2X?ELo4~3(7 z^Kt$u8T~&Rv$V|wleOE7>2G#LwE&T)(kYe))IRgvp{?QLXkTcACi&aUE3yk2S7+ad zXcg5$10r5@>j|fOOZNTAXzwN3|{ae8ra^N2a*Iq151+#gm=QG*P--!@SF_ zecx=$M-8{`3DAXg+m<`qO&~B0LpPv904q4~gPD2#2<50SIV3C*~#5C%w8m zp?T{8V7Lp`rq!xJMGb*GR$P-TDt|$C&x=Xm(L&Jt<`r)rGA3Hk9|%MX&jKRP*TTyT zD9h^tMQowon9Mp@-I*G7GyYrS#z&(=5M3M1OM)x_MLqqv%xY5aSE>L#cakOHVOO z?1RUQiGLB%X=R;%*ZN?MEmwZlrkB#^cS|15!^ioBbYcoDGxE8t@?Ae61|?x-XQEbs zwc$)6t`=goQUZnmejp&?$+0@~FWJ3fCtQJAk6#bCvH(O1F<%>*H@vN!J5#gvQ1shA zUrO!$gH45qD_y7Xnzn8aM;1i)yYVHm{5t@Cm`mD*)H}btS3`a!hP{gW_OLaRD$F}| zW_8sy)puPwwqf0*6VcD}=HF9xXP5=I&q7C#xw>WYs@aZfG8Qo&&!n=>m1{QT)yyO?q)N>miVeXl6OKoIGxqSas}w0 z8$W&PU))3V1(uYQ(Dh421>)OzV>nkZR8>=p5_cFkFqEW`4@L2h2EEP(N5n5o@pG_S zg9~#7pF1a?Pk!7ocV&~)_*S*s>$xEdxh;F1ee~IPN&8G|SNK$j}Po-7o(H{SSc)UF^tLw z^wL%sS|y>tt}h)%4kZ9cs*@%grTf``rD6+|#Af&cJdtT%1i&5+q3j9Yghv7AHx-6; z;aK>ubO9`P3UFc_c6?X|5)c%NGLi>70bwP)QS~Li;nUv_ZF`z;&q7`8lXYAqfe}i) zFR5#M@y2)pumv@r8ZO+l`7>ZJeyM=2EfAhNQ@>7>>if$zP6|m19t%tOHQ5j_Ep`V; zGUd)!YFlsHx&bv!qrJ57$~jZ6#rmS->(Gvjr zM-tG4xye*Q`0fmJkMc9%seGbo_7??Yo&reKvY{@VJ~>ywzgANf*DFA15?3^S(ZE}8 zAiTc`j5KTi3uMWQ zbv6f4;`()-3+WOV_AerWz(8 zh~#!Tf5#84W1rPxC0bN&zqj+{VYIW55kvGE=_PD?P7?DBmbC)@*gqkj(xpXQS;IMc zt!}Pxk{;AVqU3lsP(1=S*a}@`jZZW5qNh4UL8Z?35#KAvFx(@Vvw_i)3%p*@4w<7VjTpF5mX~O+U;A!BoJMP=n>BF^j$Vu1R(uJ=Sv%$5e+K(tiMw2j+>7Ruv%hiz{osJ@SD&=b=(xsdsxkx6@%HFGgH^n0-_s>)G8pS?{ ze(U0md;B2&u>ZmXgP4v@N2!+ zD3$*E)p=V?BoW`Ie>GWK<2Dm4**uYcCE)P~-UOmqoul{MVdvNatha7iP;xcoMGT$3 zUW5yC4iu4(WcOZ;W%?o5{WX`4G3D-~QdJXY)|a+%enr^P_x4;BD`S~yrXS(Nc3j-? zXyp{`sof^5DZla;@lU#js?YY12i}Wy^$0*R@yLUkmU}MJfy*^H^CI8hj_O<)SO68F zhfLZti^s9~Vy~0RhioxkbJjzR#2djfneAUNjNYIz$%t3@5D*=AW6n1!1jQktS!Oe9 zznsy@nB_8QNzL!{p7zz9}TW46CqP}om||V<$>ysnl=8wXeMWaDI@JQfs?jW49=)?}ZT1!ekpR>T>g!?We~Qv_Ww78E!H) zCVYUM8ig!ey!5a8SASGU`*P>@oaB4gzHQ!Y!bxjo!b<0OZNmJ_;p2|?F#&Yym}Iqt z37Z(7x!Yx*6Z2r&AL0r;h>rWrzPRmDxrJ2~NxMZ=9(l!AWd8-z_YWVe!X3qCSTRXk zi6=#Y-hHg`QS!*fI|s(Gxer=C(Rml-z*6kBhRL5W!A@Ju*fOzv)h86!hYgra*zLuM zi>i8wF1?EF7oYDk2m8OLxk>kbB`8d zd0vCr_g0kp{p2^%+;zvM0}eLp>5Ee%a$V&=%xY9vR-QPs<0$vU`rEI+WNBa|5+`}% z;X|o;pVkzz)^oZI()R?E@Inz<^!9f*eKR6AVCBJjjHS*cgNe<4mu2G)**27orT?{R z1?Oj-@hO2>;zeOEcvD`>3HfUxvY%kH5e*iGkyl{zHlJFARO_@b&iyJoJ?=T&9rg*r zl`CWfp=YX8W=hq4@Xm8@!F!ua29!U$z)_!BKGmV}=EkM?$%M*#8t$M}|5coK+v6Vs2q=9)3 zqj(!qcxc&^1-?FR)x<`>^hb7dR(wF#C$kJJ(wf+nl(*Ua@hNN?dVTWA^3sDqy3LB_ zD{8`tW;&#`s~FSNN-9Jg`m3G6;Lj?c+c`Bj0$QA(>(Ombj7ZoDm0#loQ3{_{`-(X& z)FjHuI){s`X9gDeV;zMR;_Ivz5Sr-hnd65nQ+XKB`ZL6dOwxx9GdJPx498G~*Bb zxBK!<4coV4_hF%Ejo!Dl=LXA&F$VVPRBB7gA-e-9AH#oKm2CFw(6-S7`0<)xbBFZe zb5)9Ivm`A7=hQjpubC#pWRy}MXHpw+b4_xM>=HRacl7iN=tCUtqUDAY>dz04q8nUh z(Ey5M-ruMu>i`LIx9-AzNsb5yuUQfpaY5fqW44i$H9)p1d~iZa80bg-sTDMDk}cCG zr2kG%?y=oOYM7P}sruzv*Whj^?zZA2o8i=`^K4#nN7{>ld&CvL$|*zl5%*tR0zP%s&MHtkanOUn^= zC$D6`+1a!kOSS91@4qV+W&65H_1)xYJs)f?ukA*Gy=~){t98|q*p+_QBhP*;lpLpc z=>40zaa8I`OZ@B*>Z){3WY>>nx@gG2{W&^d8rbp8FQj=Lj7)qO*CHBH-eJ6NnCAAC z{^9hSc8w0MYl$Ng!LDQFb)e%*Ly3SIl*;PO zD_xua>!UDn;15xS;WuFIHkV(qP= zg3E?0XS4Uz1gU;GL4elAY8!`+r+ZhNsS^8a7>#+%-&cawr6?CnR}mcctE^x6p#45a z;mck1nNjL}RE#03=NrN|BAq`)OL7@a|3`{*(8TuHe#WMRa{CPxX6~%}xM(6YQ<-#6 zI-bukV=Vg+;bc(7rOhWXG7EegyQDSdx^TiHqp$gtN&!Z~jjWUzj5MpRVc}2%TDkHM zMWcVz_mVDL7;CTrgYoCDFvakDgSn|4)g#M-*<{U-v^G3>J`UM6YJ0VdEHRy{V;lHFNIz{3Wvg zEtk)RzFJLKMR2F|>uWl04&uN{3ith$SnOp%$fP8NQ-q6ixPRG6>!H?%*Y0A^Q81V! zy}sE|-&m@!|1=JM`iS^o`h@7>V z%tZlnInbM9LYJw3KYJANC@9t4PNG||$u2^&uI>+$-twcwNL*Vsb?Q_~%Hi!8ma4O+ z;n=Fp$M{5^9qo*{eVFRBD02Pv7YDMFuO2po)>T%KnCcau^RP9_35^yTYY+o*|GMe&Ecuo zf`)O(Bx3UYogRBZvWt8bAc(b2FI| z*gZ2H_sRS5Ud29lhb+Awn}3(ATq`21!msyXY7vU>Bf971cU)OTbihO|{CQ$>KyhWy zfI*euR+6ezY!xT=gY|v-F)W5k=9i_P}Qt#cojg#UZPoT!u)+&7t%GcPcU|V11nlS})$*sJ};;}hyOA2})eDB+!P^37E zGT!b@s4J;E{#sN*V6k^@&hR_-R|sv3=dUIJA+R`9fvm7B>PcFCs>dkN*l!fYXRb%{ zz14}3r=#UDo++KNyK42^C4gr|H=x(ga*Nu+WvDtQP5wx@%%;3GnYzV$O;|B$etFF` z_G~elqaXRr{mbKmr_mfvItnW=|E#%eTe0(V1ZfXd7N?$Hc}8aTUW2GGcpfx%Ihr#XXDc(kpx$9DXSM0J1BTBVgw##0B;^<;wSg_zNw z1w28or3t>rJ44MgMW^hJk|jUFc8Z$dZ6-6 zNfo~LyK}|0wO5(RJkeR{?11IJvC!al)Mnk!kS4$U0AHPWK5N*~Ty@ zH99yljl#Ct{M+|=GI1LcKJ&*rVqixLYy9};jL8uUJ;b_CvJsz_L;%x(-Xz}aTTB3I2h~wxL!)&W9h$8uf z$Kg;D&4l4QS%slDcssuS)D0Q_lcIiKTbkD>D(RtUdl&PO`%^S-%U(VDaqqGBxdSCs zm5A52ofEq%b}r5G!205Vq=T4oi|c{41K5p0s5d%slw`$U!m2-WTDl!)GK)W|`*3UL z>|}{M;uoj`BvtQe?yY40+Een4$3BZiN;78Ugc>aHAIP6c{rmhQV@;v3q#24Q8>r!E zq;%^`=i*y`UR=p10h&|SwOJj_;*PdeHrYro3?FF`@<^PW;!UMVY18uQT+afa-u9tg zcWzCYDZlMbwdGXC_WHmm@bT@TjPu?^hqS97lKsPjMC}Jq*j0saH?1Xo?EwcjDNFg2PwADvrXm_eP>QSkr-$uj78ketKZMUKFq%A5FMh zfR|A@{CP4Fh||oY9?o}{?Q|#Xg!Nja$#$9m;ac9SA4~<3%gtWw-2Rc!)TEvRBymLM zhWKKG%f4r%m68?5*i|1Ggx!I-a{(8(AC7l%6A>V5`Es=yv4_te-P|JNS1iO<9+8;$ zpsW$sVnW=jF5--m?H!c^_#t2t%7BXhUPbk8`kLAn<&ak0nRZcc*I1 z<32M5fKG)#SvsJLCnWtBTF#^!2>6g>U7t$(1Qt}7bo%2wbnJO65BN?f}e#0me zFhikp1G_%MYFfbl4;&{2Mbu~o!Mekh6FGT+fJS|bF8}$W|Npm@>6C~04|mB3JV<7c zLql+XcGqjlg)+c0AizO=R1jIn^6cG*Wc07CQNG`1ug3dA{&cI#^bMJ2q(Hsu5G?tx z!GS>T%3mUHMm!x`4>n6o%Zm@?tM2QwEEZW8@2L9jWu89syYZ*i*ak<|`{nBlgfgAi z-;>4T{*yG0zgQ2sj4A-Ii3N@+2c-m=7EO-LK$_OS7=wmSCjwRs!>I}^ot}{eb{z=T z!YqJ+PtAZq@L#tlDi9Z0%nMM-RL8Ac>BMAPWwSgea2X&cE0b#eR%Oqel|qP!Oe=ox zPCk4U-xcq8eQRclG`k9)6-qrGy7xM?7@A}NQK?JBqkxIU(XwurX$74!|E2iYPjx@4 z(kRCJoGhOx>tGyKeny?5rjOq~_MXGAs;q@Au}w@p;(y0aWP(_!ic{f+-dFs`m*Tq8 zgV4cX*obYC>Iy_FG+=|aRRy1Ig{t5)hYu2^K0FK$egy$kZQJ09WspTop3e~f&{?Nj z@houU;*Vmz)HjrDY)%2EOD@UCJK`fJiL|p;s32#cq9ufVl`H;H20CLWnK=4$ME$Z$sX} ziAg~Cfj50!U>*PyKPJf+0MEr)zZ^N%2$9Qhrb#dMIwSzt$4nF#0OS1tkb?j~U18rb zK@_kARFa=03`yb8k411WF_ zj&hG6vmTOqpkKN_SYdF(j(g)mCc%)dN`R|8WEjU~Mle;R5}ikQM1ox4PTyzhMq;a!l>8C;##qJL z_dd2N|KZ>vM+Ej<$qV3vsztD)T+;v7ENDP+MA^W}3Q&gljwi`DRclKIM7?SMr8QWjsG!G zB2)nd4z2|Y&MA&cr-mN*;s~!13v3ufdAd>8X1CiXtdJm6r7A z)~DnY4~5CR?NTk$+cO`4c`aOo8cXyjhN%Ka$@Ig9g_ z>9s5lKDh|G>fLhv)aV)PLY1`PhlcoRB=$!`39p}QugMiCa?4L_<-W8|3B!cMx{FQY zozJEn1kKy!FYxbus;!AzX}<7O;n927by%{tn=j+iptJ8!y1HVNL94N>q?pDLLLgbA zpC&3pT%j7@R5%M+?AQglTAljIz1>*?Pi&KY4fLwvacR$(<&HQfb4vOD`~tqh9q^ zXAU<5htI!EBX-OOST>EW8x)}Q)7FCndVU^`pU&1g6acqR&KEWm)>=#1Pqp^aOYYyo zY-i$|V#G(O=mn{amarugept$SyX_kVxRUixr538%YhJ6u_FAo9c6J}YF35H^yZoM+ zTVSsq{w_^UvEON4qJO~n=v}c}(*X>|mNf>BL;y=-DBe3=`fk`;pD3dtIZdy-sDy`9 zEvvs1gJSQ^r(W<@vwxT@SgFaGDEb8)-Rc=gIrdgGmD04Ehz&NYgiEg#&bh`jeP;%H z=jagH4kb{Zwm?qGd~ogAbodD8%L;6;!th4ywYNmM>BzgB53B9Hd2uAk!{q3R$BSPs zVbjdOaHZy8CpOd4-1sV1i9 z4{&UvrPnU5X8mc{&F|K|<#s7)ns%qnR}Ce}XKCJkP0ZOOuvr*Rjh;yRKew6>NH)H!BRWmDOropG0OX#Ih^SEWukTZihA=Q zlR?En*Nd4-n(b9HJ5|aK>StL`NIWOZJR6j5LI%~GvdW$*-K(c#Xs(%I zen7CSLtM8Izt=C#ImwWVlf8NHU3k+D_vc#i!COg%!a!tM z6PZGv`O`0aHIQ^YF|cWA8RtBE2`Z}Q{OmIsR2!Q052yT|5cyx(!SCIeaFOh7I4G+; z!kiF@2(0Q*9bDY9#9Mu=9m#2h!n8!`%Yo?KGHExrH{ZracB)NvC2b(@W_I=7Uh3#C zwV?D7xAVHoHI*Q~^xrnuzqhf^7hk5FP5(9TUGF#NH=&uJXy=28J27r)y5ITqX~-_y zUpSqi#`GOh%n5=Mx#Pd7HDr7Kf97+$W`eS$K_kkuVk9QbHTpOEOFe==Ur(1#c1@W4 zNIbV_B{aKF(|qzSUSOHj8MnriZ_UaKNg`}G@417x`Laoiw0oZ1v*))b)HzKV^CZzi zO8eeqCQj<+EDWb_>mffL^B=(p#2bIEP5}QGRc{^$)%X98pIMB-*tfC7j9rSc?~Gjt zWl2)TQqe|4$})HCN+Aj@D1}5+ib@%4n@Xj4l^9wmWRkMa{T=VmKi{8!8ku|V+3q>d z?7V9>``|TiPyBHuFYh55%1{Qo4)8ZetgusDOUGzJO6tO6HoLz@uiPW%~}D| zCM0JgllMIHVoMDF*N|#D$t!sO^2jnnCv^Sh&!y|k1_6@+AVL?Li&7qeK@-ub&+eR_kg79$H^C^@u`}}z4iPFl+zJT3kl$xhDm65P~ zDz;tZ{~g#ApV{&!tr0-F7?1v-7Cul zF@8IJ#7{JsasbEv>czj5D|tJ9OETw&sBcp=>~5(8_Y0Go6n06M=Oi%y9_ z^?Jit`IIlsE6rJE*~xz7bpLAzdshEiD|PCLV$;$@9e7?u=ub;k|cp>1(*+1#yz=VD66cF1&Ql@vkk7P&};V;ax% zbA=)NfCrRwI^_@TdGkpIGW8t^waa?_XD2)3w{;)Gi`8CRzI^|PdWC{wsJMH!(=xhR zB2T^DFcW$4`t&Hkt3JcD3h*mx6ys+vbwbu%Oo5<|oN^g+@nqu3#k%^H-}eVnSm63? zp3dmxgQ#;S+uuZo=1Z{bkR-X@Xp)R;&A}Rd3WJW3?c2hJrr5e;gup+NHJNlcFC?rv zcJmo3+IUC1%}VTl%d7g&wq zRqw=8bd}!!Bx}z%V&S3kcM9?$bMGbq7SIIEul+Sy)_+(NL#v|QSue>>Zn7oGyz z7diE=Us*p|zOcdicBbUuVZC_Bms;UlK^3h#E+F=Zxd^F1vFr8XiTI{d`F!2z;JV9Kb8^7u5WPu{B5v`0 zN3S*>Tb0`MT_T=6GW`Bcw&&n|Y$*CeQSdw>dRR2!!nci2Fd?E2O5YzX=KPa6NuQm& z5-98D*fvs*SCMGl=0C?5)5h!jqG4p#Snb*{V~5qBy4pi&faGf658;5aRBMz!50YNt zVHBG~DTZz!mJS#)woCd73l7zJhFJ3eR!W3O7P@d*IJHonzK+qbIKv_Lsm!N(yq$IS z$GPORu+a}Pn25FkkVP?38Z^Dpeq4NGP8cA9I1-4+33bjFsj!B6N-{^FDQnbD99lfMg3y+MCr zVnV*~;xBygf?4}F7X?E_G;R<5Ku1=9n4A5%BXZA~&-rjb)!q(VzG#28EN6dR#+9jT z4?A5gzR6X#=kr>pj5l=5zDy0dVn)#3;6Krn(r`&t-IsR>zMUml(pJHB=NVWVh`n~m z|IpOko_q>m|5k)3ip#1dc8R{&+3$O9|gw7)Q?Bz=K~VCU1}a>VRT zA)cRVuk=Zo!k5aey2re{L^sim98W}-R`^s3=f>Xr%-YV@B9N~$Cw2PJ{JaY=zi#6#u$9UmD)!h0H+5q2Mci}#F;;!P zS1ljU%_1hqYV!@J2R1gNXoO(oS<-2`$@wd`%YCs1rAg3rE$2$68Eo@)*1|LMkkJ?D@LdPSJe~^ck4j`=uH8{01@Wy#@%R{@59+Fh?fe3JB4&<(Ul=sP(6 zXUQkqkk1>A&a3;>FjJ+oJNHb~eb>62uXQ~1;P%)4PBSwzybqma_rk|V#{Vl2+TmYi zfp#2g`_VR+nsL_n=6mKNlvMbOqOksSZr=HZ5;bAb4^`*UXBZ!}_5gZQkG*sCbVB*U zQ;J}0<4vZ;oByBrLP^K>MeE}ZokO4lgRLbdFDtx(jSl%DlFq}OZp zCwr!h#=kNS#MjrF^rs84w^TP~mB`-XiV+%b1qoj7 z;);7NixJrOo4rMfC_a+gS>eAo2DKVR7)U#oAKIXb{xY#cn$GBioVtWg!%{thq_tb& zH@T7J0G0FEem)nvU$+g5K3s46?Vx9im9T@sw`+li+*4K`=clXidp4a4p8v}=&^dBG zJLf-EH3C?8YwS=`(IK50m$pCfG~8`0EWUns72C3{9Y7gxeufzZh}psLfH;y!e~{*c zzI49qg)U^v8bnlVjX!u^Lv1U@Vr)TKlXoi_nl{d&D}f~hU+z8be%5hO zg`gLi{%fZe&W8QQ>`1upP{{5I35Q=B@}APto2`#PLEg`IM1K&`K|Z>yR@CB!H-=Aa zS74Bc)Bzz*Kx{pQX60z1>n$zdO={yx90-Z$d+l4#U&Unb1^M@Xm{=DWyg)MrVw6kq zpNZcZ?9|aF9NFPf{?J3eWR~OjKLk)PW@Deb4?jlk#@VhK=MM4P-E0F=25(mJ;nm6y zSLhV<`ciS@5;Kr3x_e>!2iuI9dDV?ab>vH~eqsoFH?hQ)9yTOG=+!PSYTozYZ6nNV ztdOTmSmVi)%k2p1A94P}x3G~xTQ2`A3Daqd(vx+tTF-;DuON11UTQLsFV#$%2x)FT zac-Rj0iS1Q7LnZjp~Cx%k>9B>VY25G0{+YlvL$dSGi-s3dPpFm)vkVP{9n#R)EJ2A z4;*3NxMM(32*hr=4|YFNQ$>i+=au1WpO0*{{Aqx>PwR+kicUZ}l&r`myr811Cn7P? zseePaydbBmjjwVXikiM2mvv8M_Q~DDef>$BX%`(J3sG`=Mw&@XG(lLEyX~di+g=AW z06-LY>XdPMC(iCv!5x!FH*mJCg#Ck}+ZP~zf&=fCaO(5#!08~9*N#*GhM|6QW8uea zF3@pStK~?`mccNgNFk3+)VBM3NIGA5TU^_NaAQ}IFh$-)ar})FDltdrBhxyY2WAgE z{(>3>0);w4$xp;^q>)m;%ej;gZaocQPyw^DFPpA4f6Sljsqv*89e<~{IzJ2Ka`D4Q zfB7Ex2uzF{`ZfpMYDPPG!uaEibd!&F-oV5b8`E*6P&D(cbPZX8CzwkzBgL@iz0raH z{^QjNSnT?Z^8hHX$yxkT?klGZfB_IN13I-D2@1!@hS0WcG=^w@E<5MLH>r> ze;F5S2L{(azH>-?8MPw)5am#res31?uHUcy&&)_iv;94qcV}_bYIV)qUh~nC)?Bvy zk;vpq5kduwya@uld-kIBi4R(5Y#~YphE=iTYGg(x@cs^Nvp72MTm@JSU{8_`{;FMvSP=VfA7I@Wx5s1EX%F%v%jL8zErU=zkM0kb-tp3EDf)b^i!9X@k|>+82NsB_l^5@<&VDQxTHuL`)~}1wD|+y z^L0%NWUVLcZ}e zMCru{z{S;JM8^Ej)9{|GaXy3DiC0vgSu$K~ZJ1wuN7 zq~mLOX1NtQJcvRiL$iP_R&mil>`t=VsfPJ~idwP#rYk@S?H81geAfcjqkb@@ZC?Bl z$c(QJkuyXy4rP*yL!u@kjD_~J?$>|B_F!Wet~G{p!R&42e=9(bmi@*k!xDwe(%>JD zW4g4=960*xtru-=zc$7VkQ}FUwIgr0vUiy>tWbg2YP@69^<{F|nTQm#H03EeB&U`OL}d1Q)b#uH}?_;q>KvPth4mVK?5<^?F->c4Ve+PTp>Dy$3Da+3U*4>F8{(EUOE51;zJ!Uof$Or zhB-j*zO7L3IIarEhY!hlAD1M3doNnG^hvg2RMakco6L+J&_pB1gTfm^Ca-CuBS;M? zd&23kk2`x?DMroRPEL!HkrGs@)-WBg@(FlyXq+v85jUI6Th{>45K56Oo`AsOXfK zt$O^Qd}na$yY(~Z%dTvRX#6nwQj+08HILB2mU|t19|W`Q+eCV{`TCdne+dLdzh`yTCwFIW^!8zI<|wer<4Wi(yz0kfBNM>F3WE`7Lz}~Q(SrMLylj%CIgt8)~8K`5+ zF&L_TsmKL#%flofjKqr30R?IoWBv(t5MKN@a6xM$TK_jhGCZX5fJO9+8C8s>?Z3=l z`y|3kMXzR-w@6{{M^YzUPdM!T8}?8o(_HaNE-pG*CZtGwBeM4n8bOdMth0zU6x($@ z)#bRNNZ7+} zp)X4;O40D$#f+BMBQQEBaVE!RUR%RyZGXbiG9}UhyNoQ@lxt{k^6o4aDM@-R`Z8lD z$hM@PWgYl*5m|>9XA-b1OLDBE`oPgBNK{_H_j{-0!-acQtgkN7fEpzHY^b9(pYMvt zxxFc(N0e>Z`oD%=uG}VE|8bmERquN*6jE+lnGoKQi23RfjmjuO4AK*LD^Eizx!mqhj z?6+;`M`BEG?Uv(m^&K;4wCkweyH}V^dzCRZM7O{4TZJF8NMd8N@fj zx6u$DktU=6;>Giu$M2vsKzv+!^^Vjr0e)uVapG~Sf`W3(AtEt@eoOOSrpCb;WbtE> zPQH4U#l0mgVlM8|E_ui#HM}vz11m)#dOvZ%1qmv;ylN&Nb2ZWj_2xCdSPv zos2))n9t5P!fyFOeBK--ti(Kw;L-^3v}EavXE$*n7gharhpF&$`Hrk(r}l7s?EXA^ zma${gHmRf$-so4AZ#@aDy-c(hCx7|{V`0P+w^d*z*U^{xXuo9MZ+DmekLa){KRdmC z=P}vWN88e#6s-)EWRlR&VtqM#bi73aXbY;JW8UbUD35~Whh{O??~iJKs1PQsLlAH- zlp(4bdKk=hs!5X*D2h1K}sqwWF|&PHH2oM;j-2J(m8h7g(EDIg(ki zFJ!G#|L|X_bM*|cF^0J`pwP~VJ|q`b`E{pY<*a5rTSd-{eCy5Se6?-#e?yFkkJ*O% z4rz}nLL*ZtYI;o_`7tJ2p(dRA{zLaUBi&>!`-ai+cx~g2xqehHgb^s5Ni7oAdZ`$V z?duRqEfJ^bQ?$Ci{)|abyQh9{p#;~hdKm1n*Lpo6*j;?HMaoH?`I~Y0KOJC_`rfw6 ze|_flvQlfncDz<3^NP~?d`yu*zLP)(Edv44%+aFSjc0`#!BB?js5=)?n9jEBy!~;Mx<;2x}|H^m+cT5Ns z#Fsmf9p1fvfNUqiWd8feZ?<1r>u8AFJEm#6gbkh zBj8BCnthV&rkQC@8Xn+JY;Pq7wXgmpAmJhU<_9%LUjLa7m8_O|<}bHrk}D6cU;2;z z?uLpo@2dc?ChP7?5npB|5dgBf!EySicRfK;z@DFwz}SdP9DLG~{Ffp5o1(0M>Rv-v z3HguxLzEukFz!eqAPa5oGg4{91*x|u3s6PdI9sVN3C@RPx4 zE8gtlyEk!YcL?9Ew>o-LW!Nx3Z_dV*vzAQz+Dvm!s@%f@0|I_@{Wp}s%D`6mjj6S z$9+_yb~fLRf;z;xc$r{@XDOqt3Yk%>n`h67QejcI(6m%VuGh_iWZ_*GUX}7~5=9vY zyj(F~W&RD1Abm!DKSx!p2BY$=qOVDeN509MkCqR;F8)Z-u&JA@U!tIN;7qCsmGt1y}JL)(WRF(jKRHietXfVK2oaR*RIWf2GB{@jYS_- zq@R3l5xSA*v;Vo~&FGYRhNy&rk$t`;F5Ul@H6s|EX3HsVc+X@ecO`cADb${w*Vbc2 z&2T}qkmkXgm$%UPneD04*)*Vn>DHCt>$p#RzH`N&@VGZcLKb+Uu7rGvJv3RtFC@d< zXRAn1q+Sq~q2e&SePhUlO1Egj2F*zRg>XtM^0ES7fjsrU&9Ridfk{27_VvvaYvI6! z{t<;kvs=@JxMDiV5kH4ZlaI;Mj=0vYMpU6uX_Un)4VK+fdp(%L#B4tuy@;bp1QxY# z>-yy_E-wnvdyOxsx!&7%%c#Nvj5RGRL}~&u&PoQ6Rg%sT+5n!5KO=Q|!`|URV&yLw zD&F_@R4H3}yQ8(hvx!_B^KDW{kjsY)%kpK%yD~sLCcrN`bLLEYP<8$PswHF`a8F+Ce99B zm2Mm0%~$M~cwat$l|QSMgR>+ z0uU57iynPf?Rn8G`p3I+l#S_wSqy-YCp@U?+EZ$uT}=ce79LVZL9n4M!pAD{+@~A> z5#warRVa7=Uw+NbGUQ9P?W*bX9bc~z6;eS+y4bVzQD)7neUKIcgUj7?#d7P#SAn-EDFzZ;Kqu&vb>^t z-4z=5R6PPH6H(@d4>IPpDzJ~*^ML@k_IWo@onZ=8tmB`|bHx-6w;E{R?oxnBbToF| zQ9od}b18_FRPqQ{nP{zVL1jfMG3lQ(2AdY&eV!jeimwTtN>m+bgG8PxH$EOe+c>CLd6}_HtlSvS2^9ze>A&m5E_EX%}mid5!$TSL|Av%sJ~0vz!12 zkm8A&R1F!w%_xd;u$j_Z-Fmm&Cfd$aRKRL*E|f%FgN3p&2e4^V-;7Q)p)(&$exz=v zK_FvZGt^Amqyd+VSzO7GeQhXv;q_`Fz)<*hKIAWY{|=q`m|$4E7iX#s07ZuPrnCtr zW?c*P0kE^Vgmj#tbyw;jtr9P$O<(k@T_Z9QE`Pf!7tn3JO835~RJ(pmu-dk58Dl?d zpJ3D{~l{$k%+zFG%a=Q?-XNY-d#4V zwq)?d?puMeYr`O;7c#7uJ%Ol|R{Op0ysEihShZZytB!F418Ad#`JGbtIXa$=-+X-+KK;akC5t7oovgP-D#HRDtZ(ZzygN=Pa0^7QP zjL^9({27h!;R5NQjZfKl`e;PGZ=2pjx>$DnRj2b#=_TOPb(soW)H{Iv@j05GBHyWV z{8hyv?cEoq6_v%)Z9x83jgVl=4`-RHzX{DYdWIJ3PO-E)8R-6}kM>)m;Id(f;rv}c zQulI)OLulr_t9@g_BOdA0XN>azI#q`wQq+1lHIg7zO4>49-e^o51T5oS2`&I|Wd&U*X4G zo)|AP#BW1hJW^hdrCRE5X&e4sP@-dCwxPFo5cwhUR5|P-6McfbLDEfbM$H%R6GaI7 ze9jg0N7OPo3&!{ly>SvHmtLc-C5oRn(TfE$yhA2bUL|+U?z=kk4+VJP(Aag}?l(f& z6>s=MkaYf5;TQGXozH`3ZlyBLp6Jjs5^8&c0Cl4Z=ywLMOxaMbf7qIRNqiCQBgMx2 zl1xkNH9`Nu(tLnP)2}k}v6>*Z%}G#zn4q&KWCh=iz6J^iqGNe)J^zuibsoXXGa+vt zW^~?VYtQH74{Pms|90!Ydv~aR;!(6^WY&gTbuKcsLG z4xFcR5z(COM5iXER&{zTH|MeUva_N{5}Ty(boQ_6g=g-4ixlikg{3tyCT$HA2{6(8$o-A{0J9MTuX($MART0C-EmoH%)bo;O(&6h} zMuUwafM_E|Bu7h(dp$ePK7Na;SKrO!mUllKRgy;qL)fqvAVA!FYfer1o@?DSj!=HG zfcnCjnscO=d%`yHGiMW?y0>pVud#7`UjG%+`uOdix|gSfKIC9|Lt>v=C6_`QQdPgN zy9gqh^(l`A%xN}heOHb?(l<3g7pxtR>AS^tswRHKhq|chiEej*tS($o(C7NKUT({~ z_ADrLOICz_3LUD*=Qk1V+fDAfS;o8d-Dy;h11eEQ#>41h1p|!j@~2zIJK0+rM&{Mv z@w8V>N~q`e{+>^Ee@Z^voi7L2U>=)#Uo9a%aCc4?5{<*;=5Fwt9T<3v{8mG z|Ld2@;ruu7htjle^~yfj_U^615_SoM3=}?jx817`DqUbA3Gmiz(GB9EM!Q{asHEGj zcHpa~A9@q|(2It`5ttHHKlh}qoysVwQveAGN`FWv4QizS$hnwLaL8N1N!{>KSe{6E zZo7&Y7+9X#8TYkqWY`r%n?r$40dY%onMKz19oRP=<;Gl2Cwhf%jeETC5p91es7OE6 z^T;>m^O6WvLhyssg^5*kEL)Z?G7uN8!|d$q*Q>XxS+G@IMOP?DRVo%P2O>zd^S~-B z{U(Nw3@rECA5b}CxNhC*YTy|as!5BtaN24m_qsqNVwF({RhaM?rD?k?(oLpgBd2~6ZTU!QK7ieaCdic!>3sEKKPN&3Tc3X1iQ-7*xse);jHOsYzRP8~5x8`)f4 z{{s^|H*fs+^}{pwEMH|v)s`Nz=4QhK4I^cG@T7Eb^0&v9VTJ$b@{VyCvIObiRxxEQ$Xwo(%BpM{UJAb-u&mBSjhvm7| zXsM@1K~c|?q`#*^&GjhWc!4Hj*x5>7XJg{V96yoZd~kmPHB(3eVAt`~0N!$Ol_$1F zJPuWZ@px$GK@2rJqHK+@h7KsFJ2oh;)$X$lg+?1&bbplC%=Hplt^~=ujZ1z<7czy? z*f6XJufoxQIX@pvy$i9SLKCJPnmL1HpQN?$p>~*U>g0gfkF$v2Vx6E~&W=Aug{o38oLt zv4PtQ*J&F0e8CKoz*F`eyx)In&y;y+N*CBIrfop@=lV<)9f8}K_(cu^TFl20zM*jX z8$zhBSR%4XgL-Sh)(Yqhp7753^YSpmH5w3`;|F?M$`bA{N5w}6A$%Gl_R~W1;fJ+; zvjTuVe>?0A^q2*UMgd5Lwx2bEp<~-&mn(3rYmM-mICx13?iW8i5yb#wO@0IR4iQ^s zK~jDUd$O=`m>L_hz!b_l31>$X+R!4caHoY|FSBIE>bjAR3%jyAkTy#-FE3c!*mbQ}whU2ukvg9u_VP2m}e$$EoGVTT^ef*9itc!@ zA!bIppE^cO;-%P#e_vqG7B&jf$3QKt4_vDq79uN8e>%J;xt)el0wnv~n1+MbkiI1>{8?gBPZI!?eUGTnvGAR32T?aNdS!qpp$SJ60kE^wuqo{ z+W-2C3z32PD*(`Uo=T#(?4n_E!TwarpW%W8`|;(^Gl}g)M>7w9ohoI{tFX?nXr&9& zOo_jbf82a6H2&vzV~7YJ1!~#@64u)R!N4^rflwYc!WJn4Gpl(Vl6&wKY!Rjrh^fGa zDU?vVR~e88UXePMM{nSX3@y1$ps7ycD_7io0Q@>+NzDP-jsjY+h!jbNB^KL)UdT%iPC7ZwxxJRJ&^MJ2n#t zicai&z(|}DH4_5ZDa>%oCO&U+>BG|~td9tI^Cfn1VQ*%JP!7N<;jZ6=hhn-KB7k5{ z9{0SzAT~V0jYlu4Jqo*Xg!oW-wD1D2VAb%A!;jaN_uIJtNnH|p=7;GWnN4tC8QX>p zc0ju!=KmGE;l&L{`d-p_GbpJ*u<8}pU7=)@nNjQY6XIK7Wy{%YEmK=t)Ai-L3$$QB$kO;&?tN|9hpu3J9?MoC(*r1w7<~?=TzWHUd>CKt7x= zlzFJadBNiANkukIe>0DmD$k`|DL6ailkec!YY=N4pfZ? z1?G$b2UY;7xqd(oui3n!L2G+&(>6ML;fzw0y1 zhK9t}>CLBYp8A!1P2(wSd>GN4yf1(2T|7eyw0A4&Hk_bYq&=nJJ2tp0f%SnO{%yps zG8fZ$<%}kO;O4zg>hq|eRkJZP{gU#U)-g5WX5Q*yA12w{)qT+|rsEITJv7sD|h zh5MS^Vyf*5-)Y5b`k8vZ50(yhbEWV4;W6gk)Gj-cGFwRzNZD5Yc!wQe5Q2AJLYvbt zo@(k`3b@SOCUP2QB2+v!Ha1!1>OoY|w^J!vxfz0HEm{Q)-Z{zUm}S^~^zn|+>PO9A z$H_6R^vcsaqv5`_CY>Q7D#mC({&#Ya#(0z{3R7X>#*EV9SMN$fv-H{_I~=VFhOnqa zO+=^>O`wP51s37hc21kolfQ@-2QYB&bHts!dCZD?SNQD|j2>aa85n^*ZI`%c$w#|aw_yd^!R8;=pZO*4D1?#NtcimA>RHTh7rFU!$u zO7`w1leZ7q`?FHVkDN%m>NO)Gi{L822L6%bUC(M{vKs%75g)@b#0Tc=Y?oeMYJ&nt zSWv;J3ex?>4J5CAjN+|OM|!b#cR$u1xhehmyBL263xXk-Pg^(TAuWr-x)lE3UoX@>q(yfRlyPRkfRG8Kq>nssMuxaEY8?_#39);1w%#w1r^k)8uN#QqYCI@tlMXh_wh=@I+6(SZli%kzg@2PaRTr3#hU(_kH z9et;wZS;F2mmh3bFv%g#_UHsO}DwpaMV zGd+mbT9C|fZ|tACNt8R+1~C?5`4EpHj?=nj{-Gj*go);acCvd;&t+c=$#aM=B0lp} zDLjP;C4)+^g9~M!?!a7_2Yv#!d0^%ZhNaCjnS7=b$qHhK0e!x?1 zjBS2~v5I>1+BN0wF7Lxgy4Pzxdm^_wXh+s{os6nfXjzho!R zN8K)|ozphL!Gv^Oo8telJ4cwgAf=8?r)S_2@;ip}``v@1b4aE!=`EmmW?$EHHn>^? zw;EIQoDxMNCcjVFZAXweE^9pe($<@Wy+wU$f}7NP340_E{?CY;`T;QE)(ZTYldO7q z`Y+1c)5xKdvn&RUpcwC;A)ywuR{iopdC>2&Tj&&3VG%875e|&= zRdRj9kN&9;la;w}DQeSG!3jRGy91MplIM59cEVwoOUDY?rve!8=ZNqK@A?z@h8r+! z`*7~xKNq2id?N+$9sVpH0YfdC!6*}u`%6O-)0QVyFgK~@vRN-Y)m*WXhc7@w?j2@V zk1m`h^Mpg;RBgMyq}6QjobRrdC#{x=R`C^-v;NaPHIA15$Xq_)$r0W%)K@IfY+u>* zWdm9#dO37OTIF)p@(d><@x5ZhqHo=xtqkVD1;+k8A5>bj-(uZ_x5hTzB-wzLbYnxE z^*iKj+o7U}oQH1V_>JqEu+3aMoIeD#f^b7T-72*04JC^ARn3WkzX*>0sy%_}NKNd+ zSj?}q2TB~JeFM>zSK?oU+8_cdqY6gyL-vF?ns!GTgZ zaAFLtzzvR`Pc1Ha9}tohkH(4z&##aQ{FEzTcsai}0iqZ;0vS;V;Jdm^+Zc8i3B!G} za5unw<#wzZs`pEJLxutamk>&drlG6SQ@(bY$ctRdJ)fKEXT82ND>hCUyIaqUEdSR0 z^#1$G>YQ(k2S!-wGhd6X3J#(q=nFVt`krNd6tmwg?)*2ltc&!} z7?*Rp@($d@vYK#1tOq^RP3ytd9eQMi;?@3&0m@G=%>Ar5(<4DT4(3TlMCs_Wy^81} zQ*_19ufs^)BD8pcO3YXRb1G77Gdmy5z3Y()7P?{^`zQh(l7Qb(L+j_BnXrQg;F}*! z#GWZ+i5A!q{35aWw2MJzrg}h-fN`c3jO|74Tnee2;S`k+A^$U0B$8hxZt}+zbD0Yo zGxn`3WZpFwMK}M;cRJuiBrbq2nF)Ekr9$&&LoT3vHw()wed}0lcphczW4=j!^P4ku z+P)u6z#yd=WfyI%|2@SgVKBR1oZ6Zdne8F}5PFjszQ0w3M}d0Pa8tq%1{EgaiU{E$*IoXFHt-GA+hT+^bxkeW!JFo+mBtC8!@|NP4UU(G9#knMh27vCF z9FDK;$Q>^V+tDi+p?HXSXc z$T=_D)>8Ml?r-MwyTMZn(BB0VGaLe)y_U6dW~i*lwp=K?aj7oEyXXB)ff9an$zL7W zX<-GFk2EI92YqivB7U=%>&Gmqfo=0_Eu!Lk;;p6c(RB#b6dXqm+t0b< z|D9IZa@la5>}3(jB_Zd-FBgK|zi5nRE(vOq#@>2HrRq`)_$6HL$4tQ=NxD&A{B6???E}V6;Vf0$Zp!9j@e>g2Ghv zqfxngg2gfPShO9eBP^&J7=Uf@W{JnSZ&leXzbQUiS|uOoOfbklF9Kbm#tz!1nN~!$ zggMO)V1yR;$cC)nwLV%ybD#{I^Kk=w->ZPkx&vGgYy*k8?IJHP|J;IE4LR{l2Tzs| zmo9P|uD>xVLC86NPvxw!%4+H%U~p^VVqo!tSgydiXp_y)c+1{&KJS_mI|+ZnauP9> zLOy*>xOIC|?84)Iir^SLYO^{V+Q~Pb-ZIFUGZUIM9!NGz50X|cu6r=kK0(}S{o811 zuT|_Mg=Bjczq#Y;K{{TPD73|P@6A20SB?Jb6FPd;=LatH=P4PM9&@Erpu_jMZ_S%i zOh(AR*-&DgeHn8>rfU!O%1!sInTI_}pE4d~f0)i!78hC0m~io}MB9HdjBUk@p8L6= zZNS4eiJT1zxPAaFF+ojJn6kwNHWx1L>a3dydO1Sm%)_F4b1-sWfQH6NI9 zh<%_2Q3lvJ{89LK^#XRwG1AECtjLQU%?tMl{7s|m!>`*6j1Va|0rZ?-PUM4Qn$NQXE}kc z>#UWAMLs z5tD}n%LM$}PMyh{M}Y!OURZV zMyg(iJ|UG#oYeX+j-@j zm{d*OC5wzu_ zmoDZMKnD!a(OeOI!gQv(f8W8ST=wf<@(TnWuU7Oj`-NkLQ06q_8FqAiCH+7cdJqth zlY%^6+n~+k;3m?R#=XW(0=tyJ4Ri|pa4t~2^i3pd&XWZfs2BbkAH>$>HT;Opx-mH_m=yS2*Cp$uiCN#b|4~ ztAT-IytMz^IU-KFXsg7Cp`-Qkc&GEbhLDY!via;Oxx%`=I=fI@e;FqI$eGo#t@s7M z8xbSz+%*0I#o>uF+Rp?y2VQZM_SxrnG^C7(BlZdV5AN|z)=#^i>YMd?f{(RP;rGSo z2OkD#XqQd3)&l1IFo2RL0#~=*{`LroBU!)GO3^sEyvoj13ymo9{G+eTNtObR6HY}t z9xwB^#|ULXni=va6K%nV5Fb6hkWsrl`cQOG=852bX#xLg4vo!MA$R-xE;lGUtBelG zW05fEsu}j#ZS(xwd!yzg6Adp58`TWwLqDgzY-F9ia|FZW4HA9{fAjDr{3%B-(DC33 z*J1M20qT`vCVJWM_wFU)siQRF<33~Ax6xwv#?RBCd$0g7r~7oiR|^DSY;Dk{ktedZ zi>OG2)H$KYd-1{IhbIX!*4R%!OVZj_S(z&~KKfm;byybmtzo5)yVGapJ;hhD1~;7p zMwXi@$$t;-dC*`B*Es#07$S;UBMY0rrQztba;>Y+7Ot_Bf5CXOTzjImAy6?Z1qnxz z0>sJ8UrJz&2`YUtXcsFfB;T0FkpVa@haCRP4b{-ku%P#d-ua>G&bl6+fmW#=ppk74 zoBXhF`|~+@U(2^9tTl@6{2m!Pd}+o{=!d}v=KiaM$n#zlSAV^}iH+_pUW%EZNmU0*UcI|Dr}wKGsvHX$r8c zYy%3A#%IG>qXUX4l(7{%_%s@WSBQbpXi%}12NREhi2y7DdB1>DFW(cfv^&HgdoI~L zEds_BVcGxz6%m!rrvmTpdsFFLAIL-jzu1;yGmYy6fj@iI;vn}F-R?9@HFZh_b1(;J zrslWCB6Jk0o^UXWP(q<*PEsO0?_E;l>CSXvE~z3W+_Y9*l=*x|IgaE)+SgWB<^S$j zLn>YJ`-jhw{IT|JJg!`Z+*yk@nUh%(_9+0dP4zcx_Ol9BC}IxNB?zSHjq=r%M=J8+ zEZq=j&lORzQaVp;5%R~~4Ff)hMlKw3M^KqQ#Z|LMOzv(eKhHjO z^476Xesm^UR#G?JenVi4avFm#_0WGNgdRR2es*t21wQ%x^}Ltx&C50>rmhTmo6bXR zFt)QM^4vG@`X0g1NVtvT$Qb!H;TFLs;w(Mx?|7m$5GJbJbLuglxxBmt)heHSdTaK3 z8Rh6CV}1V~$k%<#`TxlJ^LVJ<_YWMuXEhj%wUKS?YYf@fvF}SnQZl4Sn^aPknXxBC z5us>7QBkQ>GGi$sp|Z3wN+{cueVg+;UhmKM`~BnhpT}d)jC1b$I@f(I&*$^n>cCGs z+>oMeHG$-@t@44Myz=?$B;ax*-{c&`+~7I>Fcs#i1dp1E1pnRTrMM51HrsiBZS+Zn z=e?ws6hw%%@V)b$P?^5o8Biiou<+7>TYE*S+PJiCwB5yPf}F_3-$@fc>=tV^w_%Uq zKb-7(p2pl5Nz&S#mC|8j1r37V4{kZ|<+Gu+8A+I*m=pN!$yX?KcP|?uBJuf1`5LT8 z+R=RD_JilqAIny~r3me3X6nKow4LdLOa$v=Mmg%+?ceTe*_u^tLAft7s1zk!X_%1?k zLGxK_z^l*-wdMd4k!s!#AFw=#`)R+^I=`!-aMTQcp{t>opGyz{ zt@wbndraj(ffCgfNS;{r-Qt^QNhgf!L_ib$bODy>Lt@V-wgGeLoxI(S)rhdd4Y;?7 z0LwP={sfR|P}X$tf~xf_YuD@_gsaRe3DpoKzAQ3NT2ZZORMksZi@xwL;ir>(FQS`E z_Yy|5Z%C=lrahi2CZW3Geh(ibGnzl z@Y|emQa^OpXgC~8-PXEjIvw`7p0%5k3 zbQF;uYz%bUH)xx6m!>QXjB)_(9qP)y^OvuAE{wwu94@{n zR9Y!CGR%!Zx2Q~gy@>hnJmCw975Hh!+_5wF;#vcq95<6)>G9&tEi{Tvify<$rG#@( z&sdk`X*1w_~BvjnByZOm~l?HK^`D`Bo!F=AQ!ihvU*JS~U$d(_97?Vi> zZG;f|Dxi|V@9n4v2PMIxv^CIe@hfuZ8iM&!^dKH_`HVLBe0;GJNwJYBsTC>-f{y%j zY44f5wPDSyZv9>hdE6RgmFm%th_5_ON%q!gkRmd<1Vs6?!I-` zY`_Or8@j-o!@5GEgB)!RRbAuh^flQAs`6{H`N8VDK~T7yQtD5>^-)ORZvrQ{IrN9yfI@5K z%u%25a+_OwdN{JMWc+pQ&{qdDNFfRYPKAi+gVOn5p@( zEt*QQDRhxcb$>F_E7FJl;*$$62be?;UR5T%eY3i5hp0O=rd=RyA<;3${h_P2 z^^bFYtZ6l0IKM`lFBw|gz*3Lfw{88r{qn}LoOSg9CrsY`h;C+55AwBbu+Oj2)-!c1 zd;L=+`<8vPLqhYaYH5x+B{LCL$2nj>zPIa5mwrZV8AF{z0d``?^vQ))i{ontLYW&O z?y})fz6aB9dEXuxqmz+j`^F*WK*ZZ!P#x|y>2Wos z^*8|&w85ef60vsYB#*qoClUj|GLUB}-Yfe>cj!K@%*A0i)(rU@ekWb{#ou!`5EP33 z)S=``L2r{?WeCAm`M))NT|Xg~bIRRJjWWldM-w16uni=L2b|^$5Ey1Vp%nQ}+}seD z`BXVbPU^V<5IkfxDm_GTCZXznP@w>lzmNA?!~2&|8S-q$7oXu#;#Ppi`!E~TX~81(!$efNi_AtEy>Md`|}LyfBeA)Xaaw-paB@UE6s zZ`qhm-eY%l<}usMW*M$QrzGD zxe?-#EsxXa?iu2-| z%W-u7O-sy%0cX@;cQ`V%A$YQpCvbzWC$AOqmyK%~X;upvyVYYntHXQNY z+XT5htJx&deu}fymXPNq8o5xq>P-@IqeRohaG?pd?a14IcSSp`t?!xGj6qk3>CeO} zLh9iKJOYH%#~8BvU3^j?ZV%i>S+SFR*{sWyE8W;y<*xywsCeSGias+HHGaX-P}*gC zCwE|a!E5`2PHgW|()laKU#~RqI~U0*_3fFU>WtprcoDJvxfa?%G$HYf@URTADr;%s zDN1@_ z2VD3+ho>#oqqbQH5K873s^NMnXpaGwty@%_zc=aZgP%fHBPL8O_7PPOZs11p5xwAK zTok^l2a7B+zbr_>B#9K$p6FIJ=Z8B<{XQpz*j_6!;Ag7+kzxvz;RG0E3;t+waRkM~3utO%7^zuoHl>SVUg z0wV{l?OF39L}6wbrMC3lF#Dv+ehBVgJfHC0W+Ilbf^Og&oCX-V0P9~bzwn0N+v9SQ zU40G0i}Y@l3aPQG-R{hEU7FiTyc3|RUCoKou;i6%jK+766bgnMpjdu!K7)ha52~}c z#YIVP9S7LL86PUcl~a!Y!8>{e3oI8btlSKCDYx@VE@n=$x&l>w5Bo*^WehtAXLXP( z4+<8drwycjbwiNV4|dziq}>@&v!q}brKpeb7r7i-GT$xh&{rw0v9P|*+eg4uGGF98 zk^$(GJ#=sfMoN{&pyxjV>yExooShc-=TTtT<@nj_5uYi-bpq zzX|Y^*>0b@UR~sZUK?g3sO^L4s@1}aJhAHpC)p(Uq+-GTTmPW^KV6=JTRRL<xzS89| zvQZLA{AKNjS%CDuVB*!4GhfB4g#I@$^)M3dN1h=aGVY2Oo43rXxg$!E#hAovN#0Zm zz}~`@r$%cZTur2KW>&5DhQC_fe_gU*#TW?Q1+%x1B1*X+zvH3T zUbIsD=@5JGy7XPU_i8@d-(D+ms}7Mp6tD0r&-}tH6u>Jl)@9xaO}IT&@yb6<>c5%l z`RNm4u#_t4lJfQ~s_6Jy#j4{kth6W_`!lD)bI;)b)8Vz|;2!mVDYNk``0c_8<1by> z!;Brp&~79njFE|n07Z#Jkl2=zl2tYlURx;a@E779`Y#X;tx$t{DoXgM{Xw;AeVz#) zJWwc<2^mUpMy36LHry)H1!&J}iT8?!Ak}=&*h7iNeV;!YoZbiRXT|JxupC^U{YKt0 zvS_f|D6TuwEUzbt@L;!*EDy`_rXs*X2f9GhD$S!Z5p2@JvY7h$hlD;Uv$-D=7N*Pu!L8(-3rJM-EWB&<FU|fTNQ>QkDv2C`xZxTwN)`t+t`w4i?ALERG5Q%%UY<8h zcuy59H|*68WZV;`oR3Hdk%TCI!D{czuWD;r1VM3l6JM!oo{l7~XYvfYW-nZ1i)hf{ zr9DjqbFs9$cTV+jc|JrKRA&6MV#i^?gn*WPnxA~!@%h)PIXkdcDu|ex@R`|pD`=(` z+vW63tX5}+n8{4sC$JzcXX)FU)ZYNK3htpOOiKeuutGU{15Io_7bh`u=Xa>ewW9fq;A{wTo_;Yq{ zWk)($64ziD>qgNjW2B^m_uw8e-pMaAb@Kdl@%LeL%TIJHAjAjz zEo1&l;K1#~-aMZ51BJyuJ@e$7Sp<|^P`q7b`vVt-9MQ@AV1(>f!i9ENF@B1-=wQcr zSPUveV{5Q2+UrY?;O}%)K0CR^+BZSLy^foN>IM4)=~~IZD8vCqP6pqN`v-Y1xK00r zTt~L?73n_@Fe%aHcoPSr`#)Oxo3Q} zVuALDBJjt4&GjeO5$cthW{!wp0#MnqpZ%+jHGEJEHk{dYK=UEvv|ihvN*$=gC}T9e zD{AhYj(fI1%D(96O*LbVIST-Qur8t#{uOY#MT7;n0&~eiK>hxR%JV_++6N%vz!R0^ zuN8MEaev9Qi|CJhCGleYmp&JgkkZPx z1>I%cX1U8!bR@0*gqf98HxL@k=oc?~yT*wG2#VV`+%~ZiGjpG2ixJ{U6dWA^fD@;q zVDRbx`zI__P5<%be+VP^3I+kF1^x#@^+MsEC(~M663}v~@QV$j%lME47?#E7#fAer zF*HdFpvB5I(X2QmA^88$<}hFvz;0AYRt|>EiU45a56h2k>aA-5mOYCN_99rtg>WSi z25nPn;ez4=xWI@c0KzsnoOxLa)A*n_(Fcg#JUy|uBI;)PUrba^0*cTSf04Sa=I`L}aab4~hu)6_C~8zEFn3URB>hHG z14kPsHTvuBA9`ud8=UiVo3g~*H-8H#9(ia8YZ`iSSHrF$!p(fGDV;(ZCzTN%p$2g3 zC*Y@I!A)8(I*<-ApCB@CTnks) zSeu^@<_lbhs}5RpkO%`C2w*F`lrS>@E(4-LD>)Jni~&Nx&)68yB{R%m>aF>vkn(@3 zPLDa~nVj^<0BQLD!02LvpErD0u95%yE|Eg#LE?Bg_h7034;zp?q#`$E@R6t~D*T&( zxCu|+0&l|`)!-qVp3J892`m)V1)om!24aAh1OEzKm&Cyv(*!mF+9aYT{CJdbSlM-u z4gVf|QeyD#T=})l$EU3ScPVP~(lS8Ws+j~^+#tZ2>27`pZe;WMCmTn?n0$%NJ4F#} z=?E$y2m@XCLy{m2#)MT{!mL>E&q~7wFdOwCMjB?IjSMJ5xQ(Pj+nUF_-;|563C5KObDTfr0rkYQG>1kwCbO9TALq@t@c(HwcH8C(s>X1$Ohd zSNw@NR=`y33@YpJ2R*?sP`m0xRXmj z50X{zK3c?z-P90z7$PDHPiz^0^6E8|MaEN3t_%q$2iLM>5E$z;inUiTK$^jRRa9_? zKfK$p==arc_{};$JzoBVTDN@p>UkWAh{Hza zZTWzrBXTy=wFt!SpNWGHGmj@6Qo*WNZ(A%&t)#`YZJn9iD4X$`=qoRwyQhITl`TDLeeduFGGoi`&JnN!V@$d6 zaj_rs{6n{_5)azkFV5HwZ4Ve~{({{Fv?e3*Y(~PXV3;>V2<8g?^Hfb5vb!i23W_2e zk3CRHRam?P8N?atprgr{08npXOTQc)>Davmh6G>z&&1na8I3?ua!Z;mwu}p*p-C&O zZ28k^;dzDJoNEsoYgTAD9V~WgM=3wC07?ItL7)s(VT3YyP?(plDL(=Wd6G|X16sk^ zL6#MdM1h@3Wl|zmQBzjZ0S&VtlEs1%(Fn;dxw!ExHj;&?E_p}1P>c9{{^Fg^Q-@Q4 zgbKuIN^=$Cs0rjfsXK!MBuorRaQ_cF!WgVlPW;Kt8E%BUQvECmh>#QfEgQo@>zoHP zE8Kr#Zm@#-`}z}nfTA_?Zfcz)O%=O+c0WXSqC9?<{Cf~$31q4lh6mPkL3k=*FGYX8 zlz?>Lk?sUYJtFEk2&(nHoyhSvAERD1ZObB5Ro{7Wce}4I8X!Q(3oKeEETAweem+tp z^Z6-l*vJ%Gk_~++SBwL{$7v5hCIh@@qBo%btloTcWB<amy)XZ%jIYnm^`qi79ad=T`V!I|G5m4HHVcr>&qkb^ z=bf`E;mI~cuA$0pSso6|aSDgeCi@Q)3^on05p+bzp;z~DxNhX70Fay1A(9tB zidEJkSs>>vqLTZU!_#Nv1Y+K3`J^tBpL~@00zxuDEy4^CHF+-~l%_ubSx4euWkZFl zrC;l`{0o}seA0?dXLybjD->3gnBU~2QV^Roi{;1vdC>opXdj=0EU4*&v#KnurNH3^ zVj`g@TnL6Hu)!HlUXM#S8Gl>TIFhwxM*Mn05YEO3XMM^`*A0Hl<)~$6SrT{S6mgN6 z1TmT2Vki-w46GVf$UlZ>=trcG{u{+xI#*L}>-PTYOF_6M~@p>nIAjT-TMF7(55cn@a7KwJ-!u@1!i%!)}+08PPM3U z58faS>aMm zzYVGvyaY_KY-2zMN_LzUduuvT%p60<_X@I(JdApH{0op|^8?;TIri@C5Onjn{LC~z z@Gs^P*=+@m)85pwL~C{`m@2Pw6C>~f8w?D9Ch>9JtJ94>QnK|Set?jo3p+`lyBQNS z97jU78Xmc&Hvl=qeNDdcxw-$p=i1c3E+UR_8B`XQZ2*oG^+9|eaaZN&7DFwKG&Z`% z@}S^A!tv$($wVnZ3;G%4(}MR&NM|c6x<$WiEetoQ6G%r!8t~B%-dNGeMiXq&@@W13 zdecX0A%*qz*%F67=Ap2Upp+eEdHrI{pGk$}v39di$Mid5EUT&@%ok6Sfxn_>9O2mq za}AaMovZ&lj{eV{H}97#BY;IL@=x%uo4bhqT9@a{@7;S+VeR8(jQKuH`K-Ne(~g2> zkcd0cW6j_5c!Zu2^259^$RN>6_bn~a@W7{*)gB5_j>nIp7-g4qbl0o3QvZK!9AJ6x zEI)p0(4!t#G}#ZsvrD@_|L5}l7jgW5rin!a2eILhkc2cbHI3@!&Htmk*O-t?bn&F~ z_)Dy0M626bvrvCMjZXP0@G=+TY-Ci~!1{>i0Sk`1?DnIXadf!DBy_z(T3AH0rMVP( zg%B)`k+)IGy$~lo471rt58x7>*E@lh5EB2PYiyaLoULk=WjoG|XiFGzYb|m~Jbv>) z-F8xyLYnG3?j6TT*y;c!hxy3pc0iBL^Fed>^ z6#fhc`6F;y)?Pn=+|3eA4{K8<8SeKqAUkr-Nay@hMX@GzX6T3rUy9K=d)g|Ja_YU% z-p|+Ebf6f9B1<9kk3wlF{mohw0ijW5M~wpHMYC}sL1%2+A>kkn+Zef;gVa}<&Ed__ zuVHAfyhD_!_R{yHJ^!qmD*)|v2hy0?BqdZusF4xfc2&16%$@V$V~ShU-nVxxJrGpgP#jgmnPu+il^J zEd`KvYVobhK1nJ9`UC5X$622NC51WJYOK+q-8;&Pl3PbPP)#oyn%kwF$8~f2r0-sX zC*AyKeE)Y;{C_q$tQmi^h0&b}n)dO%d~ot)21k4UO$5UBgk8D1@UeMjnUn4to6$$_ zyi?y^-)My6T0UhuT~($>>2tT5$F%j~feCra{#-qFs8I1hkE)_{g}c{m(2Wy$pXj7T zpVMx~!u2lws$s{K`92g#vc9j5r_BODk>KNNJR~0E*mDfw0kBmlwNFBA@%eqc&@@OP zIf^)+K8f5`4l!ZyFCs_VU*$aSkJeNx9@bXliWhdmoskNvj!Azb31ro_>yur7ydglM zTycJ87`%JCsVcNiUIut;Et79eKSF$7yIkDVISR(9f3A7@r|7F18qh_{yy&j{$%K@v zHi~tV@w6lI-bN-RJh?Y4!ZqJgdW}Mzz_2vX%R|O=E-kSVTqau+jdOl44vjB95#E15 zNI7wZv5%o}u#aTKQ;)6UEf4Ui$960g|2+2c0*;Co8E-uu5tkt_X4CcU<|AK|;o3;u z-C=D%De~VpDC#oamrr?qrj$04R_d5@xcF_-2<)il*xA#&cC+Di@q^bFSZi-4a&&mk z56{JAl)vltw?WX?-A~$uW`KYd^v(y!@{{d=-5Xj0$A+A!ZG64+IP{eD?HW<=>L?uO zqoFaJ0v*YTn+O2XCCAt1`_51U-V`MWWi6*Ymoo9IfmTIc5=(Ti;T~K3p}y|i8+cA` zU0>HbZ>BEmj>GUq+2fj|g#1O%SZ0#P&ku+F*OHfb=r^o*mrpx(erdw4&It>O|65<; z79kdV1|N9fmzDup@W*eL9a%dkU&#w-P2Wr zBDj%on+A+pz{WLT6d28f(Y!x{o8=hmH`P`b|5W7;A+)uGkGOC;(r8O%?1Ja^f5MDt z^XTNkjM;qg6wNvI?=kfP%uP+0!Enhrwdj(r#uS{06Pg9y!;5{7eqd77Gm`SBxCMqp%)HPm|@hS%J)gEKj~aB;#mMuTe|3$6U+69UO-G{SjO0=6yE z+%AUQP7mYe3a$E0(hV*zEdka2A=3nj>`bVRe7i3|A;c?+Hp5aKnf8&*d&h*J z9!Wwz2>_nrOsjF8L&h!9%?$2XPxP0RfcktmudA7FuDZrsED=b_GWA$Sc)Y#sw&X?L?T24Eq+8;qhj`DD(%KUdyvw~f7=n?YI9xszYw@Yc?&u@!SPs#+QH%8A``A<7P}ejp zS?A=39hpULmpmFt2>SGWFUg8<#XO7QABcL*eGww|*94ScZ3diSGv6=41rDY;S0a$E z{x=$tBeBC<$EQM@&kkBdt0R5$nBnLfmxqUbB^gLilDLM5oJx(9-}h=wVYRj-@^rcf zMNe;OzD3%!t(b$a+UfP=V880NMW3ZUuyu4wpIil+hWom)E|NG4{OV@YP~)`)%EMD zbt(`8f;oF9Qj3jqOX22Q4-BC~hip4(aIgM{+AzavC1fwUQl|{D zwg*F?^oG?5q&hsSN2CRd8G3h~y{&taV@uWm92Kz3!8fW;2df>oFDHc>@@0` zTcRJ+7e&So@-b3L9dj`q zL5=SW3G2K74ap^!t;EZnwlGVOaYCLP!6TnP`ziaYwRs)X- z@T7$R%$|p(@10YG+mg@gQo*~vWpE6<&c;T0U;2P(sHVJE@7Ku5rfL`D$@Dyj*Ht&81F`oET)*}ZXiQ>H~0>pP`yUu zvI@bddR&vtWEeZdL)tM_g3s;gyC3iPelV`PO;(i7xk={P-wn@>-_w-H3h|aQc$vNs zgUQ@aC7nOJNRW?@q^dn=dj0Kyvzn*W)$I&>X;;sdP~qz--j7V@V)UnUT#3)`J4Vy< zm~F#}${Nflua1?(w^v@i`k9m*f_33!RL8{GZn=Nb488OBJ|V>4nfu*w1%rzl{e>Ql zRbmpV+$l>C?7H)4*Xzo2Fw+IuLGeibHXw!Al1QMOMkBF^`(7+-(G8cUOB;<%*K2Hd zIf5-S&8}EU^}HT|XUr#M7^1!E1aCvsAPTTjc3iA`9b{t_O($Bk#Ct0+2Ur&dMO0CV z1%6e7$TP8PZfFoe+V4_H-JW@_j`^dW>LDN$Wc8;q7Au zt^}eVKP%w8{+f_V$iWiiNe3BU^)R+8uKFO6p~Q(#vo^;4{?OgCUrZxGwGOW)DlM0s z#tO{}UOjA8y&3Q=so9@yVuQ0J}t~r4ul-ed5ygnn_E8(lU7-X?{pji6L>4|vSAt8iVN3* z0aCLWAm1oX0Q9b?dv64H*};H3dBt-VZfv=vhrC}QoL(lW*R>DuzYI_E!c*?ekjwmt z!_WjmXL2mqcZUvCr{IypQEp}iKNt@o=m_(?&2383U-ow1 z^gW5Xhnz?C!toqCwf&9nS-`Az`yaVeNzf`ir@7S%S49gB9fpAF?GO6LhXdP?y!?(w z#a@bDgzj?}a^U2oM-@SgBt(+wtQ<0|TH?zHG~pxNl*HwFylQFY%mbF5rzh+sYe<1n>2Jc7FjxRxh0n^4JE~ z#RZFWHHu;fs5?^i6mO$fPh5AP@V*EH;93mFD?at_1zq56E6}Bk7QHZbCPkNo75qy{>?K2?OG{@-J!EK_#{jzkB{zLtcuJrRZUGh@ZV}pP33-&)(w0On-=I$3C zd*7%_+hnl|iXk9iIU+=>bWYQL(L;BV7ush3Ja&GSOI2Q<6sK>!oOLIVMsK1Lai92I zI?7)paPMQ^J?#69&bj{*IJ1xsJUHF=W?oe&+aykCudEwK>}Y@U^>ht1o=H4=F1hXR z)1%8b$__|W$DUhH>w+>%-l=yT3> zqLUs*lZCc9jxM4n=etH8$^R4H`MAx?**^V0!*08t??Le&u0qq+iGUD`qW}ugVj{`q zhTf}~BHO9pyz-K*jSbkMy&$)m1Ot{o(ZW$~k}5mxfZOAOH9F(-QTn_6Q-1@MJAn*F ztDd9L{*x`*Iz>9lFpSnTlMNOq4XeK7S6k z!IH|^>r7A)ekR*Z4?Ficb+TCj>{?8>b^VR`A+!6Hb%@9v-^-Uw54~(aGCDFG#XgMe z{AUga3HTP=Ulgnl1T$5w4~H4?mmq?;gS??`EZK#v@Ug29SWZPH4!I-x-osPS;VL8o zXlw6$ikxf*u1n$gX63S%`%LF47R7a9UVvk`ejS9o`bYaHOs@|;BF~>SAi~$0Z~1Rr z=-;`2jSGnGrSS6Z4QMgg{nTe0Yvye4L+?G>VYIH*qe2Z17x%@kO$Y>;pDB0+oe4`B z^)1(h2FJ?K^#uWHiI@DXx*FHUAy~c-zAFxXy;p*(1|N%ac7_C7S{`0 zc!c(ed2|Uxd=vOCBgNtC{Tt^MbW7-aj$5~-!){iUge6J|zk2HL%0Li2+p>j4_f5BW z)GZR{6!YOyJ|}>2C3EQDA012BRo4qaiM8{B0A?uMmZkuL9NgcVfZJi_07;5828q1S z)8KJHMr31nLjj<}YWKmgn3KZ`oo2s`+-G~}SjlWZfB)24J0lA@eh z-B%ZB+hGKO3(Mf~e$DaK+YrsUAN~tK&_|&V&ZPGmn<1*77x@U z7A)ou9qBl1TnhjRhQq32dApGmo5c?QFP4&2JdLfl^Sy0G{eB9*bE6D7!S~_t4}dFu z0S%#(*ZLHj;LfrYXX`@^vxX2l>d*#NGsWd6rTxbyVviRseLkf0Ury_c;*Izz#|Hf* zJs;6$78wn~0KM&_P*HLV^raUl)ZJF(zdaoNlcMSzeqh+g2;x|R!B+lQpX`!^p$(wn zGIQ3X4+*W0kqrWS13$vPYcqKl;inYcpq6YD49CMjyUfuohBpfq_Q4_&@b1$Qti5m@ z3!dN0CIJhUa>^mZ&9K?VRNY;sb{-(#@>9+e^!36r6`^Um5;nc6)^Nvp7O?5P?Tgr1 z@{!3rqTBXNw(#~TRAYR*3hn}rL&AL+ZaS6@?;H+@>f$&+{0P0vPk)c2 zI1Kcc+9Rw^=DvDe+laS$7Ybk}>+)!9mnlAAL2vUcvVs z#Jk3XtMW*6WF$X}O>#&}64fwG$-0dKBKEgN9=EEf;V(=g;xv3jcs$6^I!_N?p5Lc` zi}nafo`tha`NK2Dus=iiW3g4cNDeX{JNpi9PwkTd#ulPD8?#cGGy3{3t zR)55>>6pv1RmZkD8734?7a3pn_}juyLSBBvQ|d_0KL3i?0DtnH_F=x$$0CZX3Nrcbkb1**+5tIMU5M@fzbUG zGxquL!(iT(XK#NUXY!PJD2V>~RG!;K+h~#r?|t#jhA=!DjB%G$9M%!LGWE_Wou5uW zw1AuVw0mbC+kJxs=5rHo!HDVU#6xZNT-)xY;RwPLhsp8q4X6<(@V-@CrTSng4SJ(s zA>@6*N4I#{B0&Y?r>JPY0e!=^mmKO?w-*oM1hXYVbZ@-Qe+R=I`B|FXh`+i|ISLR@ zNU$hEBKx+oj8xGYwQZ@wZyO10p-t3+4i_MUJuFH76sCDcPK`E8|Kp6)Yb$T0O_%A8 zsClG+;q9VG(KcX>1|--T!%GEyQkh7iB2Ui#!F2JPYUX4ew6yC!ZJ)W}B}Cjd-}%0; z{Wz(2cV|9chhke*a#{ov)AsvaTt)mlex>+d5@)XY>^k>!ce4XGD7==vpXG%h!PIw6$l~`$>lp$vPX{@=WOGm0OAIPO$H*7QW^l?R}PYoZ>&+mE@)*Om*V0r`PE!aw#_T4&hd_6Czl4?8d!%n>b145}U{PQo3lspJ(rEiAc3eE~Z*n5}vj5 zk?fV@&%V-qISkRgi2Jg>q*Y~H7~V{Ktv8F76e1^_0#~Quq8acvHCz|S-SuhBZ(~Yi zJIRU(S3t1uS6TOv?RjL8422-BfGwTqU_ldU{w(tVX-)WXC;qSIF9P~rG(gq^ernv_ zaD@6D0mw(py~RduZxP~Ov36@&zMVFLx3;R~&u^yqQf>38h&jDSAp$??TlC&G?{jN| zB3a0;>>Db$cRL}T3SQ?N-_CML*)&mMo!WuUgKt}9I&lz z!^3P4it!Nb(bliHzm}DEdF5j%y?sX8{rdGUs!r$mpoT=HR(B zlwM0+IO7cNcwFR35`KP!0-mhH-G$*sRImvd`MEI+Hp$x-YxK;rf9NmIo2jyiu_~d@ zlX_EkZx|X)dU=~|9fGHVT)>O|^LcP&642O?R>^ZK=s8t9I!V)r-+Xv>z4pRjOzF9E z+mqmVJ-UC;(B3i1x$H10;k@){cn}Ou<=xm5S9^zDSXp}Dk7|k)td`D&T@1TsQ(lsagjg9?)FBfK%k#CxM{&@M@|OF{2&Z zY&H9J)6(Ks@00Myx7}ka^15A1CuIKg;-5g2h@13BTOuAx%WAVP0WA^Lz zkXSt;ors0j==^d>jrC(o4!pt2;Ne*uEiMT#QN9c2_C!sH} z%5cdPW$^mU2*{LNY5#H8!s~Pd2_{(2U^zM;fTdYM0vF@IL5V5PMoCk^UQ3oi&% zevxLM^ri32l6m>yE8n2`9A}H-X1Vv!y|NE|;f-_iph;0g61? z%;cq0`SrE#WeN?fT7<-Deus-FF|FyYM4aas8L0IlP7@K*v~a5sCps8#lSv zFXo`G3*KAK98qa_VKiEL?0K)<~&RlLrSTXhX!! z98`0hvwFsYWg!bg z5m$Kxw$SgTYA8UZGdEd8bYhwbOUCy5Zjk?^= zx^}|mq?tWXMN&PxBq3 zNc0><{hOURLhku>>N3^xB%uc(S0b;iB(Y;fK`C4}&H%DoCN`Ps?oddmBh2WIX|mF{uFjOV1+$w{yum-s7uT$V+cRERdL zX*8QJVCRK|1VXDsku(r-%W*cx`)UH5$ni*cKY#j{rs%Q3u$*kVBIegu1F0|+k_H3c zsLmrzjeS=izVxLHufN%|Z=YP4>t3pbi1)~uX`RDKl!5Z*t9WMW6vB;xQ&Bd$-pD0R zmEORph!-dI1lqmwv;S0mW?Dc|Q99X#;WNO7Ksayb`L|zHhJx5&tYX|u-|H?@6GE^K zBUlJyXN8hqq^e4oJR;w?5O}3?0#U93o%q3ST?> z^vf@>7u$*`d}D@cydFhCI5U^XIPrB1o;(_9WyU`Yz;5wd#hk) zVe+w23`DCtfD3gtcMa@YHjAUyC**H>pR}v{Empr%4zTtK_+3tpj8Ih?=duxb2k5GI zJJn5G9@CP9n)3Kt`EAmpVlpIzQl-g23&R@>guAa>)7u=Ht|Se44Tsnj>jC~Y*yPq= zucVg}vOfD!o5Ft?sz1*E&_Wsf+qx?^_0(Ryvv-5TAML6`{)M*?O%Z`Y8vL95H&fwgPDSPh%qXu`PyZ(`Cbh3$>! zZj(z+Nq!P~G*Y>Z>4qK}0cBI+j$>JKk+@ElWQ@R{D2^uLOPA}|3awdso`URsOuBS9 zWBUS<(suc~MB>=5SKm?8FiKVK*el5ctI)FbNof+umd|Ozt+EiKRdApcghKk;s|)r` z@G1_`l30Xik^#u+jwwWT62dxB;%r)eq&xk0a(jjCR;(t(5$*|fA^%o*aljmZlzb&l zUAnstiVZF~X)u?r&X@jM^1eoj@xAvtP~^LFUV7f`?R_+!Onct3p5F&g9D>HmY;FYbb%-aUcC0;Mow-lPmu>`$YS0OV zO7O{m+7tUNF*ry5kY@po<=|=bCWvTTh}=Vnv5zBHXN9G*o*QCK9!j6GlvB4+C&tDO zEaFoMxj7W1@E;*T?3)74Z!bZphk7V(pSncPd2w}a(nt;n#@L*GTVE{!v}Eb3B%O=u z{!h1-HN1r?LQC|b#Ao7_9xLy)r@hLr3jP`&`&Q2Fw4YGYZ<8FEOHF^js`W6Rg=SB! zYT5gK__8#-p6h&AC+T8C?d1X6XTnh9`7LZf0gHXq;4CZ9TayXyZVL9IWzXxaVAQr!ZGpa;eA)N= zt5QQmn-FSZ^E3C{jPZAm#@QU)fDJX#AWOOX2kBkBm|f)qeRDwGyONNleX&=uTY{4* zBL4lmMic%S6S4_TK)y*r7LVg=*tBolZJ3AFk^MU;AO<*Mon(^KuPbOG`5V{3-ZB+T z@MLYD=t(I=7DWuV5FnPy6tQ5Z5SkU$8-V2-cZJ5qcnD3nr<8fF_}*~gK18F(DPrSK{UgqkyiC-9z4n9sa9Z{(B5zow4;LcMZFeVivg5pX@Kv+dWV zrHtgWv?eT?S4e=9X4ayEI|8AJ5yE3~9K7)<^12rw^l<2aG7r#r*j3U2i}D{d6L5*>kEui*8=rfZmpU7tGYs1)TkXQ?Fa65{v1&5EqbU-I#(91APeKWv! z1C~K3xN3+1cD3hk$}LAywQ2!@4C5DYX!wiplVeG6=r0fBXw@h%xB$1QRsa8E>&xSz z3jcqfGn>I+>|-Bmwh+n^8EZl!iX_IGHIfQpj(w{Ll}Z^x(t=Vc%2<=i)hsucntmRLYN8&J`{F`fpf z0=gaOPoMxX>=(|NEQF;li8jv3y}4+NLz|l9G*p9-j(WBOHS}v3z#!EHWnBQ@H1z+} z1EOI=8#)h18_o@=iY}H#(Im!U@wERy^U)6~ILuFv{)c=&G8>0}a}*t?aD?nj0P4~C z_|6XIpJ*i~OCv{u=O!0TGvV|2Gb5_@HogDYKob>9R>BiRJpn$FOF*rYv5i(2NHG4` zf4*IXC`oLKu;=VpDYv|W6{9c>CiFDvdg!BKHW_nPw>Qo3F3t~tNy%WWc--ttmC913Cg-6hpsbVdD!%44<+ySk^zp| zOPrwS=@~ZS;X$(!o;2$Y&!kB@k>_`-yfoGBrQp;OP9XSHuP?ocB1E*(qaBs+*?#_f z&g2FCu~Rt$Ln}%JKPR5A<`{Gc*57}>rbV3{`?G_+qrhA2O;$f0^ zM54BD;cw4o&mjxZ<&J}2{AF?=gV;5syUBDok>=?Q%qq^sI#=>%&2<<(?dwEY4rl%w z&^qiRO(YaKRTLy}F~`qk`i>f~6;EcUh8b-ve4SB4ZcMIRBR>@uJ$jFU47YKkK0#|y zX3I^YlJ-H6fr^p&Lxj{qw#CxyW#LU6hz=|;0lJqmJ(r^Z|KO1ry|g+aJW{YB==4OZ zF%6&7P3THkVi_uMkKCrWNv&~EfHCnuU-}=!c50qzWgHTHyqkC`&dP2VEEMrGz|#Cc zD_2_1%dd3rkj2vzUpQG$E#rf`PVKG0uw0I&5GFqC5OzV9d#HuSmU>i>%;!a2FJBOd z{ZpAoo+KOp4}g6iB7heXJ{hCN6y*-gw~{KZat}klAEwH5{MwE3TM|`q_}bJddr8mc z!I4g3aMLZ3IZxK4N#n+Gs=SfErZL@B~I zT|1NGda@pd-^ImJEbqSLU*G+wBYtho@BfAkBbljB9qi6@K3W3Ek#(;dOMWIkzU{K) zbW6CRTYbWh#asOBZRK8Ca4TG_fbiz4JRvVx8LkcSpO#XFqw9mx#3-SkSqrmt8KHDL zS*|Z93qPuDq8O(ne9`I`+pOOIzT}*G344=gaOUw>|BjT{e<%k3fZXW*CjNs1BoouH zwFmWGzl*h|>!yBZnYmCxC!=EoghU0+V0ARK)l-eFgi}Wxg?Wwg!Ee@c(1jITCdTc6 zi}*Mfmb4@>c@BYm9 zcxzS*n2`eA@zDLNa=ahYB5l_bw|-9klPw5K`Yv2+{r-&$2}eHO$Uv?kdOVb13mMH6 zY7!B$y(v7xXSC_P6Xho6RR4%gnm=yK+ZRSXwr6Wp0h;3rvj&WHz=>udivY}m;LR`D3q?ebMTCCu>zI;BNB<&-kBQ`PU z)M_g(xfS)B`15~`;u(0{97oBOs z=mYyz-jUxdeja?C6qib*U~*4bAB|E9=zW7M@zPvR7Usy*y#DQwR*J@RV#@XP4moA- z8jn(B{Z|?flCmv2kFU{w7-6G8VV3bZ48}!0AHoM0^r)2>L@^e}+51*9c579-XCNHN zdhS{VJ#Ud<6S=W232D!#zf&t$-?)!Kv>=H7I%cJ3X0rxlmA@N$i$u8A)F z4vz152_5>lSLFwGX)13}oWsIQTo=u*3&A8)loIFy(Ot*a?2~~^t=!4kFAtWC?^4PA z@COgv5@S-DbxhOCTwwcA%2xtWP1o0B`@Hn&epJ%F{g0W>K06bKktr@okE|X|M&Aka zNE**zVs_h^12_HOSt71`fd`jS7_>2+Ep5&FjUxf@pm$qv<$1i(>?jJh5lp$;Vs;sG z_7IHQ`%;sZonxNx-_I&pY+^t%#2aW52&(%M+zx0BfIu>;xu!k%M+Lhk39~UA1#LuWG}4 z1B5`vn4)Ae8DM1lrtrH4JCEzk=uDbKaeUBU9+iQ*JFITmE|~dME%*R0f;e8%s63@7f?3)LSR!x zOxTgxTgY%aKr@GQxWIT*{RU~VEa=U8DfEiLkr3o^ilG+c8^OwB3doH51;cCUf&Xs$ z|2Ve)b?=RO6)5%Wa5S&LfqBX3Q~DjICVTvUE_MJxmkrgGPa^GW`mD-O1pNG?%!pij z%R+7#g$C$+J0Jjo8}7Uzk#|o0!4D~{W84L?rA@Y^PhZ^yK55d=$w#fEG;aF%>$Qip z&MV+OlV#%QCL+ERJpHTT5@!?1dQ1rJ^R@yM0{9~U~Jerdb>1NBy2Er=F?|86$ zCK=o=^7v^swbYC{h5~0gfmitLfn#`&L5I^7zCSrL*HoXyTx0~3Uf=`D%5;V9XtY?j^>lAl)arT z&>j>fjs`*qLhfi@CX4PmjD4|0C54t;$j*$%{O-Oc@h4v7+UnPOzu(8aGoFmja3`T`_iGoEh^Ka!aih=ZZL;4J z#%W7Z*m>fBlw>dfp8+O0Z9~DuHp%5icLUxtmG{Dg$H$T2O+@bl6Dpcw6go)RdO4?f zZSJ2`6YC5Koh&&4SjE4Ol-&9CMDoqJu_pVS(E{=G3%xj}>R@2uyJktosMkH-1tJlI zJ|Lz_+R&%8at>P;fJ#j!w`Fii?( z%9!WQY7P2FA8i*e4;`hfPYBf$3`Uj`?;QhvE%RSN@u+{(*>y%Mgu?n{H+htA3tGzTZ|N zdRm7LtT4u?O2$W{&TCBhe>Ni)j9Y=`^m}tr9Gd2#ouBu&3fqga?C0}Dieub<{#ka) z@+#Q3M>_^cyO;)WZ7h(ZQydgMBd>Tp-KOs?oR5l0pJUQ6ruIzj=XjgM@s)&$4z`k{ z77y|!o2Pi6=I%>a2EzTo;+H-Fpk&`x7RFDqP)$DT(>SBih9|W*0Sa~X?xJqe&H&qSMT9nPu+4{5`In^usGmZ950cYZ<*WM zd*h0`_uQ%_ZQ)zFN9Ug_j5*AUBOu1RxF02fzFmvn`aTDwylU315A}r$y@Mg zR{~FHziV{*V5K&>s(8;QBOgph_U*Z2AUQcK91PyGTgm6htgj7R_{ox%E87~706jpS zvZ|BFB#Te+!eS3H_Hc|A;P5r5WtB_DU{CSTUgNEG0)wrejI~Kd@vX8XXL175U(I6s zxFc2ObfMIX1RwQmak+ma;`#qiDzt{wv;DC*s+d?bK8nJE3YvYhRy?;AU&BhM^gQt6 z%lebdC?7&m5fJ@_|Ku(A^mW9I4qy3KUOH_7>_g0R$A=V)x<0Zd0jzSmu7=KvAWzte{u}N+Z;UKMI4X~9 zcW?f)k--%LyxU(~EX(3PE*HF}{CXj3turu?Uq9c7V-P-exyj4h0~rZmupabX*72Qt zQX*QvdEb6ct?ugI664--t=6k$3roTL0?p#SmfsA9+te@8&KoI=w}xRo?09>7l~-q> zr@b*}V)#>)ktG1g5?P^GhZmau`&SoHL1I>I$9;0fZ8DDWPZ!m71ihR*EVMm}DHv|B zUlXpsv2jF6VgKx)(0aXb?y0mxB0TdeaGQ2^>SuwR@Ue*!_B)$O*%^svA0djS@AXrk zj|eel=*G&Pv-l`mT9C{FOaP)Cio9|qeBFl`uad6b=!wp1tNu~D&GBbQ>-rEbX2Ny+ zU7W#UV%vlI0+EB1YmB)2a^)iV-ZyHbAj@L+sp>~pr<+#yHROGWx&_Am%@vmXBo$!@ zh`X?wT<(Vn7106^)`JRoE83&C7+=jU_Dr1fl%g<_DWU}sb=$DLXzd72fs3*HE26%| z(5?ZhQCP2pWbw=kPeTInsyWD7XCf*6=&L2xo&-}rVM)dcwA{7Ue{Z+&4C{u3Zc7Hxo~>>8A;SoTj(>QFYApZ; zvV4RE=Qc5ABo|P zUHj&&oum|YtlX*l3%bdJ$F6QNziY(w+YsvhWLPa5C0GDjuv|TrPY6o=S_bPM1O#2y z@gA_bw+CIR%s8Ey`0dUBcqNHs^5pdRF!8DN1wbDj*=z?exA+xlfGR$=3Twe!-Tmod zJXM8K51AXYlNi|4ON}-DBDdf8L;VY=J&W zo#$VvQ^CtTPPJU3U=r&6^!EgRxv{Z||I2Zgi1ACU9C{5C~Lyeke^ z)T_`})8!WmTh~65_STa_M#K*JEwWs4dynU_l`36@-V>_{wck|VE2mq0z|hlQNjVpJ z8Vt!dq@m3~Cm8rUTM2am;^}f8Wn+`nL*_*O>n=|zfon2;dPqzvFSCITK z67~P7QH?&V*k)mUA%N1P&Em)w}<`@OZ1NO9z5Xzw^5`-K*OE2gZS}*JVGn zo;30&Zyj%y^ZmS&kUmBLL1^&Nn}#7QvzQO*upy+4;eSXb>wBwj!vC8X;FtqlsO~L&`_P$~-e%md$5#1Wjim3?TR?y75P@}G>i?&+Tv7oE%@S+fb} z-94^+qR0c(b1d!MTGbY(3&HU5H7TH(ik531jDew~PThfwyA~(1O9M{<@5?Zt31v#2 zWT_@?;6Ntz^0^PtwO;imy6Z&#jC*!Sv&1;{Pywp?3{uHeR1&g6p z^kCI&D&J)9yah>0;)(*z04?O_ z60B;&0UOt61ZV|zF?(U}&9><&*-?U4;$2gW8A8R)vnDqVFQQvOKvo4>^e`LyoB_}9 z*Z|4J)b`%zcP8+)_ybcKD8&gj~q1E^~Y>T5_`3%qrgh+wlSqE+pYs2?CChKi&#KaM?RX>mT)IY z@0%r_+Z$M8J&RvUJvqH?_!}>@SALJ{y}7-zMB^8>hT@mC_T(DrrsG(9Sg!ey%a-?% zdC13IbsGm}9U{$FFNZGcG^br5|2||^>>MZG>N{eNlQz`e0s^SXP47VST zM(s~l|3t5$-ND1D+}XK|#he6WqMv+S=<+Uei;kd9d6V?3OYu%$^AV1XoEX4mwL}Ba zE2jaLzLas2+?0WG$PpLzejqsbT=;mU2aES#taamctty36y$7G~##q4k?8$P`cQM7o z7UMAfs`Tdf3yJyj<5GuyB~n9pfw~-z1Sd*i`AFkOGp&}Hm&128|(GyTYalD*^U zcK#{p-g*l7kEP!*!*>A%QzU*KPP}xBg{^7?0x4nsY{;!NAL~%7#r{>pR0r3k%s|32 z2Z-pvhVn|Z>~5j={_zxOa|UBlKu_?|=w&B|BtPia{(ZH@?~oM+C=N`v4IcNcoRx>L zNZqj?QmyOHQi3G4R)QL%Sib=zSFmtOJ!=7R+TxaevTs98*+L~DRr9XGg^m5|=TZ3# zu$isX7)_G?-VmK<%{BT*6nm0S@}Iz=_f@Kp-Tx*L`=P%4P1o z-HNgfbpDxV6Smk_hfKy z^M^G^RD87kl81iX?nfYD-Ky%!Zt@9a1Vat98VxvWAT+{x6@O2U#wZ7ne=A8Ha=U;3 z>WP$%=iU-r)dI9;c0M6V!$fAB|CWWaRoLIZtU#yW?(cFm=46f4B$&LSC_P8++_1fM&C4t>_fYTpGLx{sbd}We zrkT<99XN07Q}+0f&gCLNAh~!~W9&q(_*K1N75nYa-0F>w+2HHbJAVE|xUDScS@n+~ zjZgVZ1B4ALs??*CJ*=nbRjBZ~Q-eXrwt?qG?liTXp1jG-i?8;yFi{|4-A5XR?$pcF z;YYX}3XkRowbCYk-g0T+_bdC2y5O700-0nMrhH4^y&r+~s1)oRfu zqY%g6A19lvA)ac-k_ko6*V7W;9w+?y^LVTT1t?wmrGACW#pQXYyuBCr{srzFs2xZ@(r54K^X#Wk%_T`MflJR8SOfyeD z8BVzMrN7`6@^_G5JT!M-!&dGPJ}+zWY5RV;xpO_%UZz`T%2-=HF4LQ8{3L{ufzk`B zq3g9sU46!UvhDaBV==nZbNiDAo7h)8cUu$u?hhdx$lOTb{$Z^-E9_L3S=gGjpQ4h!V`a_WB6Mhv}8;1(_l(F{k{QpmLa z3v(NtoZUu5FgM(2#~&QUF=!mlC`0-C)v1uqdYa$Jq?)3`@z&Rv6xG^+TwMhZi|;e9 zA`;!(I_v|X9FmarrsEH9oJ$e)Il|JGjC#9WA@A5v?ivZtQ5T!o@;Ek~+a$8@-BJEa zenXll176}%g2#y>jm7z=oAr7|6OuJxYU@nZ8ylll2fnPceybMis|XM8I7aMJ{xCBA zn&)RhZ$sqxN7Qzex3eMSX^#ccFB5PPs0JI+Uqt*yI00jY-SZ zZQL~JGfSVf2E+Ro5jOb5%X#clGPk)Z(*ma?k0L>`o~V|5J!WP>yhXksY98>=8{oi4 zruR_#m0~CC*=()34Rh$bwhz7QpJw34l z7u}eW`7ruG@e&$dJy-i4&}_a8soD>(Twp(s}p^U2akaa z=Eh3Sp)NxbmvvB7k*R6EamV;a$RX6k^^K6jE`&{`Nk{J$WD)q~YcP&zEj zGAxrI6RlK@&o3Y{*kbBF4J-O>G=0NhJNS#3U>$Os!cXvEbvHu|g+^cs;NT}$HjI2> z58$q{y8M?CE0#zeIE(8Ar7tkw3}1b-?^cu(%oFhq8NtPwOWmgCUY^Fp(`%pXK5>o2 zl2beEe>nFFvT6_U+J_x|@xBt76-wLf@E<7MTiEHpAkyO`(@`KljdgFK=PR5rUqu z9vi>(J1{{59AuhizjIZ{x$c|zrE(+6sS(1un$N!npJe@g5&l_ zKcPPSjzz{8t8`Ib@9u_<%d? zG*=5dp4|9&lNtLjoo9A~%3ht{d-05~_g@+mv}CN-Y~5ekTgz=-Nl(~PmNUNm-aq3; z1mgzd&u0zc#;sRAaxI6p~oSDFg*X|un|SZ7DXou@5`QeQ$3ZQ0n|?=UD^|F)s+^QGGN1S?&nI!!Rf_H0@@i%S1lAVaeQ zXgl_2Y=tMOID9$R)$ic&EqsSuW9%l6Vo)@c<2zwekZMrAg zck0r$RVeIOUdr*XqWA3wcW1o2AM@4u?zs<2+_z7ENN07kjSpCn ziCUp{&|VM2Zf-~0&{Z#67>g_f;0MxTr&I1B-O{k+GdkG%W7}&>K`Fy6xvv{q8>Q3U z)ds!GD*-}`btZk6-+-yQ$6ZLqcbZTF%5on8?6b+TP)6w*IWbz~@`YdaN(0Cv`Y@bV zrZjz9b3Jw1m1NEQF@i}EzFc57oRC<|U`ErnS0~TC-KuMTQ#v{ zjvjjW8Pda%o39|+>yNuDicT!h1GQiFXkOCg@-T;(on@U(clk0gbl{)e7r$+n-tJR1 zz^}}6&N!jd&j@+t-jjBW@^fBb?EZ0#vIUVbN>H5<-FjqoU=A5|0JfWdZm4*C3z)S% zd}uy^%=TDUIJo9~W3)2ZO})?b&+N*(?vkJ+GS7QSJ!Jryc`*{}MXbFZv5U)Dk~-D- z{bWMjbpAi3l5Jb_$`))=^B1{LIft3z#_S*G^*DRpbluexb8Y}2=L#jFpWn`6N#22n zFQx9q%qVcjVUmJxhoaL@@0P_ts~s#}kNW_}y=_I$RUyADI3op(MFWE-DQb`1d!E^R zBvA>&(i3f?5MvK0J^E0$4#C?#{NQe|Sgm(ZO~9pBSB$s%kcCBFQP%|M18sZvJdZm) z$9na5N8md4S< z@FPr$qHMTd$>x4F6Qni8kB~f5li_}q*x!V4@$tp?p4s48gtpHC=F=6cKxY2!)uY6j76m z77H|b@Z+`@viL*!p9)i{lxUckh{Fkg^AH98?{B5eG_{YvI#_xa;4?gNt?TVPVmEPi zj3D6DIBl7G%P#eE?CM9wbN8qT?@l^jS=|AQIte} zp5xcPMD1;peQDwJM&25U)0bVdGZ)8qDJ)V5?2YV)9^w&C`%xT2S_X>cC*pxDc6&;D z%I-5=F}%$yDQ)DZv19Z=zMO&Qzi65+yUQ@WDhOh&J5b4ImOh&b(leDFR8w=YFJqBR ze1WErJ=dOh8msPL;^rXt&4aBsY_83iTVOYva-X={rE zEKG>TJ@8YWZ}t){_S3ZzILtB}pOFm~zL4-62C&X!rhd~dKgvNu&qI$z0H`229cq}t zeYscs^{#-HV=iAyK{mdR(0-p!cxLLM6ZSp_5M&GHnjEB!;p6}}lP7zZ+GSb86uq&l zw|XZXDE^jEb;RJ&SDKmZ*kxd50B_sl({$a_I$s}EA^ z^2~rpN+*dz1@z9sd}CPc7Q7K@Ch*YTkgmA^1DbuqE3-o0Ye;6Md^K7&Or=>Mhbg!X z_ZKexaUbWhQZ-*J?jQL6bv3?(CoF%mYUef24>XER`)0SOh57gu>!Zm6buD(1$oO(a zv}md4$YlmHf>S!YvDmr%FqdZyK;4ID55?U3S%-o!P^ko=NB?=UNy(u3#_8LC-%ou= z7m+*puI;KK(9JBV+%nVSg4QxKV)hX3=PiZp&n^Pogcqmj)?D+kuth*SDf*?L-CyK@ zT8a?p`4mL#8hXD2juOChQz5MR(M}#RjE-Afm%hybWPmZ}*%IPomb8v?-ceI*^|Tb58%)J0az^Ww zyp%c5cV48wi@%tO4WWE=oj8TXzbbfP33bf3gs1Cr387tFp<#@AOxNcN%p@562b|7A z!RcLVI?ia{nyUC$Rc!a^hAaMMBPrSO6CX`IZI#lFWr9ACc2TvU!PT!IvW6-V#P>e4 zaDv4vL#PPwP&=cO;RfTYRBx*aozr-R+G(0*bw2Re7=W&od zsQfogPz!U~my<1K&hF$>SkK8^;#Zh_zSNr0;f2OjvD!s+Fr`tRXJ6l za_F@-toQXIsySeja-W%^tWLvCsiv@??i9setwE0t*u#3^-;=27wovTzue7euj~NsH zwT5V~z-;yZtidq?7dUwPPe*2BwDa@7Eyg|8iW5)JYFl}n*;Z)XVWyaQMfm486z?55 zL5bLeW%;>SqrY)LxuFd}dSAp-Sb#?4QJebrr!mz3@M?z{*+0MCB|laEi8?omYdkUV zRsiEJPl9UnU{q=q6<2R#aZq3rAlu&tW?f=ASDniwc#~FI_!aTx9f#y4_x^k{4H6Xx zRLk_1LV0emS^_q7CY>)){=wP7POB^Bj3vK7@d2EH&>vgH4g>U4L7Xv=!&(MX8wD=K zE;a3#d-H?jYCr!fE90!`!ihD>lY!X*FU({te=Lx*w)3v)-r4?Uj`NxV%5g!Rz)WU~ zohWHqx2(V8A6A)YMNK!Q%vQi{mGAhlX7cJJ7g;gUZB~3>#Bt(zHgA&d`8DOX!%fUK zE+0vOeRX4Qfq|~B82v-gRv}a)SHC4CgktjS%bJoDrzEvoogMH{anJel?Rr_nOv?xt z`iDUE=O~0U+W#E|>%SXGkJI^O=PjurUM%X7Mz+43g=#N*a~W|~wAW%z*BQ8Kv$)%m zF|P6ML5`o;9r>ix6?B^m&}!|jV#`>EIuuMxG1E3LTyw_m8j>VI0mD$2byQ!f2 zu@2Q#X0z6I6=&xY3>0JLx2mpB9X|I&Mpu}1)E~<*cf2Br0<7JZQE>ovCpk0C#YE=L zPr<3%-I(2F$IH&Qle3d0h(v<<)XM|>e8s8gwf^`AOMGsm&nIYX9?{5@>;f<4bm=plB zvja?iF%ob|r+0LU4VtLyP-mwRtIhUV^ zqF*&jY-JqcBgc(nS&1pS*Z)ia3B?BKOC+6!N~G@}GFj<=BUDV+4DGjEh%SBw?wTqYKp0T;ec;39b}r+${P_33{g zivPiU|C#rFn#7aAd%lgQ-No1IP4v?&*-M**xO`fkCQDh+HJWEfkho;JmFi>~Ps@Lf z=qvQl{I{%G@^S}1hHzDffog4s<1*9g^e``Ed00uYoCs;O6=gsJGzXpYz7Pvk0#s!o zXQZ(mKv*Uqm!U}Y0ve!N0gNCCNNWLk|3Zydz7jq&TV{X|#Lo_?&D%npi~M*Mz2fd@ zTb~6TVYL~nMx86~bdLadYH zru|%^Z1qs%nsAvINzU`EbBD>=+P;6w|8MUK#bF~D`6fYNqTCR4!DNl>GW2=EY2IkJ z08cFgaH_0?%R$WX4CmwFT!?Vk$xJIaw@kI!_umaUNVE#n?lRGL`ts}7s1nioT3>~_ zf1J7$Jt$LMb~~<~Tz|1D-zn730|#-{DSA83Ms5Sg3JF?XGGVLby7e4_!pEe)!m^(7 zfOcYL|N0W$w+j{5)Fg#@-D8PC38kfp$qj!M%##0a$@AZ3eJh-S-GofT(#iY0mwXeJ z!p)Zx6^WT^6h~C1OORA&S$W|IELhlJk$#g+GpOfeJk(83RN`g12$U@&#JAzYkiPZ7 zTV9-_(XsgUyRK|haKa)_sW6~r`{&5qdK&qRp1~~vB{_A>STUf+%zo=)EKFz_!R9yq zJWjwyK={OI$BJO0P`Wq1-E7o8P6&I)nIC-utlG1=p|9_;SF%uL1`PQ`ag7d5W7_h(94&v4KaVO^fOhKHtmf-P;`}+tO3~tjItGidG8QtsdtaQ%N}V8vN?| zU9Z=cG)LEhN=#1Sp2d>A=n_-5*qWLA@8FFUnIN?HNEF;yeKGR>htTRvk#%Up zl8p)!GgF2NP5*|XP5vITwITp;6VQ{@T>_r31i~VAL<^9;5p?9rmMX!jSkGU$2vKZF zMU1&o#i7JZDO51X_{9CwXl266%ZS>(kE1*O0-{MXb@h1zhzA!l(;MqmNeP4cccdy) z-ReaN~gwc6FOFCRUVSdpCBj!tcQQhp_9CJVs*iT zB-H_GMib6zHv`6h6($|uGpvG6*k0_;Cif4HZMf+&t>>dszvq*d(aN;-q)t&LBJf$!Y)U${_tLGi7%MrpkrEE4DWv~WecyY zJ>_-J355sQhSHb(e}Ho`XOjh8l$$j8zw(V+?@^|rUKu`rHYuH)^@=P)Qy{_c{o&^# zDq}Y;qCP|;);FJBGhNd~sXruGKd)jY|F;#Rg(wlo^cugy?oKMWovNPks`L6O`^`Rb zT%ng(NU$_4uGnI*jYH{7eSa?Mf^qNcvu_+A+c0lwLu8<(Qx`L8v-Qd zj>L%_mo_>2%VL%#JZeV0V|sFaiG;hfFy`ZAUok%ZqQm7NgIl#tGbbG5QfZx|t)}Na zygGMsQ-8Cw;^a}~##&!S{oO7Ec;{hXI!ecl zbRcA+;xw%H@702FA7*Davw2RiJKO4EaOst2+i_-*{a$6{!}o9T7ayX~179SZ?Srw= ztRV~{(?Zgzc0MDL(x}s5$L51uiBgK!kv?`}N_^KoC0!X7X`&Rv5pe&(#vtqL5ZG~( z#{(vIp(=RA@o>QeX5O;8UYMw63U15`fQ*u0qvKu;LI`&hf|}FqJ|2m1pB|D2m{H=? zL1ax)5X;n*j@9As&B?Dx2S4}jICdIAah9m2wqUX_fWRAIlwx2N=YM&m1?_|@ zq!2i2JEEOfQV91kDKF)RWDbwl5|*87S+@Id#o+IF@D*z;#gCtOH$>-kdon;0uEBh_ zoo|wOw9cFD-oC}PngKuR;%VHb@~vbP)aKn!^Uwa>HI6I-3BFpz%Y#J%b;z0_p7cU$ z?Bm%}T=ol7pJ&hgU3MNvX93YC0-|!>jFzvjVSi*^o9A!56M)1KErPf@#yS43N~jSWNvFS`I;zF5J;vzv;>eISigv?+D(mBynEg1`P4Tcsste9vCUo@ z{|Lb6uYCvdILA_0blJQt1XV4#OW}7tc<_z8cd)GHEgINF%0T0ydA5WbkeOwoar6t4 z1Hd~Pbc#r23Y3>muSA{FlMy7(_woLQ+}>v0U7Y6yPy3&(EnAOzAlop7>NjRC-^tmq z$|6bXzix4|9<$N}sjrTtt5qMU_n$V3){-l@6wPNb&qlM0D`fsyl;WK^A4lO6KMHe7 zT}WW}p3hXdTU`bq_^2HS!T0Zuz$!{2Z7CF>&DVGUGW2ys-&`1zQ8G(nwPjx)a6F(y z0H(0>lc5E$n>da*ISibJ`A3~9qdfXMlU6KVa9c$>Uk26eT6-%WuKMusE{p4@qi#Le z%Dp??RmBLts$elrePtx0_;mF}vTlq|P8o8anu6x!Aw`vgvs?s)G(gc1ElenYELuUf@Sz)DL)|cxQv$&VOYuoj@q&(q~ayfi{_(J$QYQPWyXHLE5 zaBzHhBeKW>tU21;(1ThH(^%Y=lMpbOFCMtc&vV@8LQ#*hN+2adUGdrIfoJeENw9>O%Jb)i`%r93Da*z_Z1B?G0mMRG#twd;0HNE8^NcbN1$x_`^{ zcq0pBUgU|yx#zJ-zN`Z|CZDJ)Jkc~9*q-7C zpn5ky9S{KLjx=Ak93e81mP zCH(mV&fBupbeu&<_QNmLl^=PciCV@5rUqx{v3J*JaU>Um9zP9du%Ttpi7aIY*t@ub zILsNCiNWo7yVW>z;LA^6B$-8c9kqcnb!mseK(0z_kZ56t1rjZ4NxEcLdwsJ{qQF^u zS43Yr<1M(3JDOg+;%WvaICTN zefZ)GRY|l!r^tExc(Nk#=Dn1Zig(Vo#g+X?^gQbx&1pO&H(X(Lo|ZKd^zh}p^YJBp zB5A8Guk5lzT>BN%d)a|R-giKj64radn+nTWC(FB|in}H;LHR<9U z2^mj9C0bykXw;27(D^ja+&Lt{x4%qgk|yuXkRPuXgRllFBr z%?M$K8jTOeT`&sTBFnQGO?&59=;j*oh)z=+YBJKssAY`Cr%^{;b#=kwN`3&Osadaj zd^QVK!CkR|FVwA$fi{VU{->CizT2N22$-q7-xz%KGI+jz(s&IG{`arqp%SB2&qR>R zvlFj|jo!uomk28=rZmZ&dKKv4%~prpf1fK&{FO2_ePgZ(b8n1HJ%nOylX0v%9qh*` zN)3Y}%_Qx8dM1r(xz6(=geWl_c15`V-T$ZqHU8DYUsoNMI#O=K+XGIDC!Y;HLESiq zy4Ok2G;^di&hQs@q(1?4g5j)4q>kg~*Q;fS^*?2h(l{E9a3Xr)m{o&@K5ZB=OHo!3WY|YqppV9n`6?!=h#r3;dP=3Kqv+QE1+! zdikc+-FlK9u*K@hO{riVMU2rLj7+2k(V~SF1V|IF!gtuklwPjqziI*%GEQN_ZL85@ zt#=sx36~$!K@UtEq*#OCd3r@G~KJiW0Y~Jj!4cNPU zKRIQbL^)Ys5do6sB2m)U^RkbMKg(46R|*(kNjvzvY(fRYzoHfEa0aby2(3 zcSZ2BBiZc5_8gQ%B3y3&jJYTB?9xGwK#63K`E#E-10I$V=boxhLxkP9ilO;4#t8;)IPKdIM*WM=a~z8sbEK zpo<_~FlW*IMI9JnAcjy=)y5ZAhhT3tWD#F~_`yR;Nm#?oJQ~oNZfn&rV}r=Jaqa*L znvxty8BN#A#{9AP3!~nYV)2>w#)a|qIRDGY_pILcCrir(F&S@VMHV@0w|0$#qEq)@ zZ`nEB-wJr^(AreEi>rJok3in>0oPFl2Pge^5&CV!G_$%r!p@};}dNZEgLVjbh8rb}KE9$x(v1iSflJt1RkHTIVpJ|gE z<~9ZQz!eYGub)HGgaE`FQzY`diq)EQKMuqm`Cnvxc{~(v9RD+WSuEDQ?seZcxmjn< zs8o_zN788>A&FTxDHJ6sn<(iZsT8q}QmF`)l&m5`k#gkN`Ay&7`Fj2Sa;=%!*=Ih_ z=Xf6~bT_-dRJmqU-in_24|~U^Za!(S{oF6>`!jbcshc}@T%&o<(jBf`y1(J~76@4# zl*#G1;vNvFS>DoXaHu~1san{nxUXS2OmL{Ii`EQeR|WwI} zpRsr-#<&e1H2R#(`>{>5%Cx9V*AJg;K9}qj*krUijPPk>*mmi*()ThzzINNeSE4Kt-`W;znrAi zsGF5b>U>kHA@~NC#V7Dh{`u2?W%F$7G7X=b`!w!9u>e%QJ2S1o932DUvE=Xsbiuo$ zg3)gPa6sL9T{sK~ClezGsA-4)Nv7}t2rwW?1I~YKL%_l4g_5j;k<@4Zx`1f3Wm;aM zpz14Vurt6r0!)=25Bd*iX-NRW(Xs%CK{q!FaDFE1C^2T9sr!8N6S{f)E;1K`B_rb6 z#c`2kydHT+*?_Jn_Zfd7YO@&8HkiWytbWcpgF(VJS!5p`IRHkBLP;#z-d(4Yqk^!9iXaTRpBNl&{$ zNs;T-S!BHpwC&t;IBez|!W{;?&6_5Zcf{Hub5kW-Y|y}x7kGB}w(b<<0eo}(#>Jb1 zXN(G|Nr`rzbBro#rPR5U)n&3-qRq}#(LY(Def*alox|QEb=Edxy7f73`fH09umfjF zIoDHCu#$Bm1!-563*Xi;lEedydTGeMZ@kZnE${rW|9P~S7PkOw^nS_7Jd-#z@Jx?^(bpsbnFmNP8&KskYk7JF zC;ZMN27QwVAWMG3*f=XyVa6pR=1BY^*K*vAW)`Hm(A&OVq0Q!@%Qf4pG)^HQ=qkv1 zqO>J@wEtM^l~HsL$HMo#YSZ!GRPEq1+qx8$->PGfsC8>LV_5=-W(tW}hoV5`78n8U?(!uBGT7w5`|3DqLLf+@;fv|Iqqgvo^EkNj8eb-%sIse8#cAv;zlB< zY6K|z#G@OOxakcXp9(NZm|XOxW6{_^fKNa|yfO5_GWDB={nBqta;W%MyI~SeU;PTu z7wnaxla-2YF^t3RJ!YW*a0|6l$yxgX#p6gk_NP8d^%d(tQtk51Kts^%`AQk^_5z@T z%0tkpA6k_AT)ul+QjAF;Lf8)eO{{=X$CPN4vh-zld`y;SdL)NAuCj33h9tQi86xZi z=QiJ1aI*zUeM8{`pMWQy(c-=MqT4Of5j{gi& zVHO(~ePX$8^X@ZR7LFYuv1QVviFZ`2A46cA(+V3qGE z*Q#27hpJh}eX45o`gr^=Yrk7?T9S=!_QZECGG@cqI~}X6{AOa7L3AB%Qq4Z~vz$w^ zHZl&r^x;?QA_8!P@0(m=#wrd27A#Dt6CsAq5LLRA!*zePCc6}LYF%uRPC4{-Z01%O zP>`>ig@of^+xo0!bRpx0qC6UV_=}Mh6+Q1kuqRFfm~ux;4C+rm?ui#c_cO~nfdKT~ z2_oh|=tmV-jh!qBF?r6Q0#y9H44{FgQ!4SpK6aC`3oAV}sPilu+jO#@H*M{C-2sSI zX!42nr-{CR2Sz0&`uhmK)1Kaf@hR{IsQ5^)>emQhzXkh6G5%$TpjCqsFjiuN4+TW> zQz6EtFB@_te-5A^@etzA<-G^wDUXL|c}h3Rx0sr81IK^22_}n}4gDudK8G$gV0*u4 z>+R|&#!L`4x+Kb57w-Acufs?}udn@#?8k%yF=0o%f*9!d2+(;BV9+2PG%4aOu(j;F z2_caXihUBn8{BVngv-3%{;26YZfR2Pl$O1U@2({7`SW~IwykOai`VIz_o^og$Ja7o zTA^Z1^IraMV&3a8W)f%=9g5C-YKY9T4{N(xE2~~nx%Fe(1vT*M^$@Mk@kfw66sw5C zlu^{rv_9&?D}G3_rBm=4S3d8lu9v2nA)omPklfCL4nC~`TbWr4Ae~cOa8@@zcN5wh zgRv}KiQii{@fxKC02&og!x^yVZRR!pI}I-N10F~O!Sa@K;yDxS%d=e zZqd^jdqwQcpf|A`nM^G25g58NvI-SH+S%^;OlG}0ZGd$yh8Dzq5XlR>`LD@T+ahR& zJ-LCb|B^ud(Nkn8T%q!Sh8rzUIZBR>-JZ0s7&y+>c(`J$>~j}%-+$U=>6o&})^8e(7o)c7^|Pw2-1cEc?>Tr75S-RfsKpnFwNSvDo5>B;g(VR_ciEHFvhZj5b3~P|O&0!HIuC z5b|RV-iy^dw^j}P&1e8@fO@p0N9>Y()ar*m1VCW;d4c%Y6zWVoM8F?aoVUOpQ})3a zw64Oi1xIu5-9HiqgrzgQEk>P6SYje&@8w&P0q^FSuh%iO#AfqE;q2=gL3B@CS%pU6 zgx{W8*sW7&IWr0RkRCZHf|oDMRdPzvqAbC%+fLt863&NtQ~m*$2?lJk@+b|0q0|oS z5st$v-kX)ut^Fn72p!#g_9Q<<>C+$#`mVDEzVA|G>Z`EnGU(R;68(`g^i%AXn^Lh-xNIyk{;VZ-uVvf9UQyYm^f{?K7~y7`eT-aJ3|^ z^~L=kAnj1pzSrNbsILzIMfc%+|B&)`NU|Cxb(XgJy5Z)1dC+xS?dncZt(04AFxvJ* zqO5wdLSM+qwo}-^sVpZP6|SHH1x@?g;!mHjNF>@eRL8?@9i(@k-*2me@@7QbRX8FG z9CX)6>qr*({u!1DU4$y2TOWMEqj$&z091f6o^^iWG;Z|%8qVclxE9#6(ub6)Hd%UtYB0ec{8;yI;?ayJCdn zwYp>6Jl2Ym`}4Ys-`VF^*0xUa(W_S;J~>_dr*Ijij)~3^pIqLqcL15AN4Y9OC2gs} z%8#^``?(wyUgwg>H+zcE2H8Un`T4_Y)k-S+1yJNYCOtH$t$-Gh7{CKX-b;ESx>q1G zPZ$;I(>^-n{H%1VD7NaLr|m?+d4}M}t}cDG4(qdTI_3w@?v$9KEX{eN!sE{-$BjvK$wB5!I^PMrr=Gn&N8uX z5=X}L`#qmabWRBfDHX4~9j=2?!ojDgG_SW0M&M&^d)9!E1eTv0lbv`BNd{)F7`}_2 zoK{!PEjwwLxIsMZG;oITW<4%rr(T^ms#;}$V;fvBO?Ej+X|9gwyr^?%ic1&l0w{wf z+GCH;Jm4L{$=fOj8#JsNn}f??yDly4VwIiI_V z1z!zdiegtk9QC`(o|HSY`9tpFEMjbIiG5!2uoZ^*)7?X^STM5RQA$r2FPaq;B1k?z z?XcIubFY&wmV)-)@lZg1({vsQ2AbVIbZkw5g3I-JFQ_IEaE&u3)F01(IO3yQ z>--6}p?35|q1HNpwsH_?|A6fw3p}s?2ujrzrH!83%CpHCH62AoBxzy{77b8OqwAc* z2YEoNx`MWEPkw&KFs4jsmCFP*;Bl|DDS@BhfZc59zVH{#oYu`NDry5jU_~Fsz(Eg8 zM;MmyQL(<68-vCfjR3<|xx;>C9{->O6FY||5gik$mH%{a(vYujOm4G#lQ9mv9gYabIZF^Tkw_&6gPd%E6EF|%t|42fQ9b-0s=8rSv_wj+$-^3cl=oqX+4lm)lh!hWid$F`6*qSq@^wnM81<+XIBfq^ zd*;Q-+XB>xY|HO|6_CX+l{CBSS5~KiEW>EWR{2RgWFGaa%RjraIDiBpTY+q4p2VNb zYY#XEFK1RCW)}6#Hi*WH-1!skO+1dUm2S^Cx_>B(Y|8|?*$M8&5jUW$MFNmSZ5fFe z!Ph`9JwZM^x&8pnA?Vfoi>%A613$zGsdtjfdS%p+2svg${-)y31el0uUUkl7F}Th8Ir9@XQ$d?*YX@GzjWQtMNFAn!fOaU2@TE4VC_ z2vufknLA~qRP$jcRb5<+4rLr}v(9{4;bJN@3mTo*Qk7bS-}MY(4LvS?qxY0s0P_2$ zbKRG6pdfUIhMQG*QgP@S@uBY>wPw{lkKhAmqOF4CRV4_!x$Nqr0pn~ri%oNhp_9f_ zZS(tY!88mlv}{*nTPTdswxfGQFw5}Fg0}zN7`6xMC=B3b#)S>1-Y88+ z%^8ZJ2+4xPI{;M!L5Ugrz0~S%+3#r^AKMG1F0cM&<#jGx(&?<$r5jxcfYOdl{dvJY zEU=@Sbcuv1VzmY>+@7nXn^sfC-pupwS$Iy%YW)P%DWjb!_|dVSyUY|p)rQO|tuGmG zTG8epj?m_CFhAzvj>Zq?khVDjg>`JQ@e+UFlNT?9*r21adW@#%k@HIAZvv3>5j@Yk zGj_}FfmaAZvIWKt>2;d1zg-XcrEogZsU*mi1o3Qiu+mLT=eK<$n2FiGpBI=)&bN^< zA%^LSDAxEIvaB$wa>Xc@wJKv4BF`POW0aR`@Xb(FBb9kEPA;J;=D-43S4b5uCNYf; zy}ydK;SEL;$x}fU>bDCwAb;@oIoN&0#{2fRo}7~sG|28d?eJo6Y1zNq6<>hXdpICw z&9*hq+}>Nc7>`cDg7eD@1L;oB&k<}4@+`uX_>GrT*QDCwZar@7R{IJ>sp_ptw%WU)t ziF|kbK;g(?Tt9kRgb^~noYknn}q_T(;u8uQtp{wxDQR#0L!)7TlZh8G{5iW!b3E;XP89&-7fBiKxg|&Y;;la3^BnvJfL)3!5pR!r>)VV; zL_Sqk?0gD@m`^8_dsVW%cBs8v%9<&79kUEf-K<-2!B0yTjc#l1{b z-fwe7f?gad?G|UdHrj{IL^B!x8G{&mvD~10&{u!+JlZ3}L@~d8)lB_tpAtXp_ zb9iay#kRX@5J$u2Km+ert(5iTz@zGBOP}ZT<6REVj!I~>jBK%6MFvhhxnH@q)VovX zs+&X8WOf(tu-!SX?fpA?zk=pqWj*H};2uiyy4vmBgZ!9z_H5H|YZ<@#0PfS;zLSCJ zAMfY9|6Yzf77H*KdYHaR)z2Y5~fCD<|5?Q2%h;1HwvTl5$zGcCnBy6Pv6 z*G}HJHyj93&K@gGd!iQHAST)0@coSA9Y%u{0KOD#TAk=wNiPm>Q9FEUoF@3T{9*Y) zjx|ZxOTZ`R2RPjsJdJ!#pz)5Om@VA-iZJ`ZknA%_@|Rs2YU%h0Z^^!AMmbfD_Ye0b zvSU|C)peSR0y|EmCn+OiqNFcLgDD*y!Au%(xbX!B%pfulLZ$rN$pD8&r!A^<4pkX& zXxmX*hB;cH9>k(4As|cBH>ZfVQ`7a?kV(m!)49L4OptRq$uY6^SY<1Fp4T>3ClDx* zF75wy@Eu1y83-~uUTtc>CEg^1>qc;cekBD!i7Y5{c=C}S#!@*XS-?Jj_6VV*f0%iI zFFUPDQ25#7TA|bKzquW@&QhsguTQN8@|(pkz1llF^($cXEmz`juz=i{9oy%(qaao& zwYNbQz?NlHpanRpY#2Cn0-OXJi@qmGES$dKKg2vhJ)-r3vlX8u1f`M;iAyMGzGu#S zw47!CLgHf1AVXuczkQ7y($lV{fRI@M(kT2&iN!#Vhyi)_Xe)#V?PdF;4O9*u{Q)-z z$VO-l!u{8*M*o2NJiX>Y@%Fak>A`|6I%ej;Dfz|NJlEV!_fc@20{<# zHkzj!C?1(lOC2lCq@klzbkn7SrjoEZ6c!^W*#`5;DtEN8x<9h#>c%f0)1f#imWoNy zI~sVN@s?@5Wxty4!(Bh>v}WjDYSaej&X_e-kL5k{i)~kxe9xQ7*NOk9HaIt+$d`rr zS{T~l&1Yy|W?e_u5qkfu_}8nQNy@||vIk|`W*5KCMh$aHZ3aQZgw66ZyegQDc;(=B zzomcFUv?h-o>tmTkmI?_V|tdPW5H*fmDQ^Xil{Q?%UZp6Z;duZC;U3Hrk7f4ZFTYY zT-$m=gf3J6Ue!XXeq?KQ(KRG!ZZ0eIztbB%Es@uemp%s9CAEIY+52)+5V2hWUl?UA zsUK9BvNeh#c~(%xW0`DEL`BsVe^xH}5WficD*c8=ZqEnErAA_Lw=;uKqfHxdD zaW-U}EVDM9arE^+=(G+B;e0Utd_7mrzCYlso^L;!b?DdtNOxzaP`-fSf$p%7~ z`qm1FwkcfQ?c#C@yRlbh`rXHc(akcmW^HddDVn{n$%F^WNfTx7u6*aFCXrEHPp7swY`#;6P z+33|cDvlh13EN^y_c2DBe*H~Z$DO()j9>77KIaO_QNnV<&n-va@CkEM!0%qGZtakB z7_i}D1Jo$m3a>j*yqD~M2m4>*Zi%A>^S2dMVr8se30c~mYgcH)`2-tTI^@;u7qH}~ zCsgZ5(-Z1KRLLH!4iyi(?qx+{IyGHMzSKtNPL}`Pt3^9WdLJRx{qr_V_??9=0YR`rjF!=P>Hz=ssL9t}RtqtwySqGRZ&BIwLaa{+)9 zViEmD^x)bdv-%F~eE06N8he-%-2%o#1t z(s{2q4{iO``nkNlEj`~rHiUh400pEc>PE02;RFF6!d9*bbE|ozpUfW&KG}N=rAutd6;?wl3qpI4g9CV`&Ie`XV44cGfhGNF& zWirh^Lgp%)xoF1|s>@2C&JM#tN*z2a?WZ~jE+I5mXGMqp9d9C-<=jTf^!UcMPS1U5 z=0|E@zY!*#YJZHR5kY}(I^bbu)iRY2UH{s2P#{9g;!KlhjS!bzKoFMuGM2x7Q2qs0 zMWU!y_lc@el|Vg=L1jNB`n@)MJk-$db^kpw2kvAzh7E2Zr)WG_rUD}z`56rzNu%Q< zJ}wZmeMSsitW;1=4TACDuo3g=>dzW{gzyG0WQ38GfX*l&1NphML)nTZ-E$)5v~3%r z_9Q%4Tx1sr`(SbH#|lqIYI4^{sSxKMTLe=LWY|1}d;E{JtFj)qZ|CIdN$ zV1+8x{T4|#jeex-B8Pqt6@)aK4=sd0^*7fXO|3j@eWpS#I1oX8L)$0Sc2A+cgYo-B zvbR|s=Om5rW}5tM9B=r6%-~gyO#O0wV>YxNeql=X!JljCG5?ml6{-ZjwstKjGqptU z_Rm&Q_v)H;7&AvgUT*GkF2R+Ckw|dr)RQrn${#zBarAGJQmR1~Dyiz3^I>{NTVnfp z35b*aa6fzVK8Wub|3wIc5tumcfwWv8F`Eg3q1$fXkPA4P z0?1#F@Z9S9@a6V_oxAb@NR?AHxEyb>VqJ`+^w_a^(448W>SYBVon(@s3^OO3+Sg#B#u9|fWgR|9g59>$p{lx0eOkxiE1ms0%mrijp6{KGV6 z?CA6|*w4Y9o@?*_DOSAq6QC&Cwv7}X_w)ZA`x8Lf1M)E?(VtGEa&)UOg{XRpbBcbw z>)XLr1d$<&k4D{WFAKHKjuT=Vu&vWu`lh`eM4D~vV6!0^=jOYNUX|3I*;%S#qe+%- z%3E<`!4o#K>Dw1>JOc)`%rn;qTsdpXj8C^R9VG6dt>PpOsQ&dh@Whq&y-XU6QLoex zlcZ&-`OfQ}xh2|#G%Cet?WKZH^;pmhl+!{7vw-t@%u zz^B;w)$Ed%i$GazMu<&}b*TO^geT9_4OG0}cI`56>^4N} z)1$D{@dJjE%m*xzd_`oT+i)-8I9nT}FLiGHeo;C<$x$+R`Qh~AklfMC{Z_?|C!PhS z{8|Hob2JoBHm8E7F%oFXmdb_{Q}2EekQlXwpXpSzgeq4GcF$l!n1_&4bSV*JmO!ZQ zsB`rFNb8~L0i1nT=gsjKs$_{C8btErZTkMb3%HvL27S-SV-+e~`vwBKoJWuBb3bq^ zQ}Tq;dkODzmjnd(GSky>UUw`7ppef@-&Yoz>x*ab_D&%Z>(f#xAJOZ{);6}-m>k{; zY@2x>UpNF$H2u7aQ@Lszv0-ImE_g+Gx=IOKoe|8LEmWMe|w!;7@2fS(fOU3dZyqtlKG6*%GG&Ye2xHYwF7ryI$)=oE&q{s7kp0P4= zPVRM){Bzpav#T`-L%X>r`lR>u2!cUY3bx{8{dUcz70hFdlCZtr^kgl}eJWEhYT9vf z6~!808tpOG1uzi=?@5M3y*S=i%!zSm?FwuU>!1;VeOo&>W4w~bd#51Un>2Cv1*f-u zg5Nh_?mA2Yp)d8rsB3f**oJN1?>YY+-hauo#O3JA%=2pzoh_-#zNs?Fb70e{O#`;E zFL#-vv|f>Ky?G&zP=oHR8+dKMkI&zA;Bi+qF}4cakUx@qfdE{FKkl0Ui)zM&x=Klk zeQ%7N5%pB-H%NUb$*iFb=^N`W!Kx1{cB7Pq{i^58%X4-t^c=mD@=u>Sb! ztAIMEpY`trfI5Pd>a;>BeJ~WYo=Ou22U>d-4*0CY1YBy?9;tTRYIhxAiYyF#Y!faJ zMCU6HA64VZk?m+YEM$tK;S8|)DSt4_!J#O&{(%K(r|{qF8Rcu>llUgdG~MI{Q=!l*}C^*z2=i_VDAnuJ0F z+u3LyPP6F+W^b{d`mha1u(u^LOPRXQah^L-yF;gY;1} zItzWMp|qr-pVfIo8SE{WcpE_gzR$FXCU&Xms$y>HyxdPUoYDt1F*{3tyY~*u)2p+t z^D?*mdys~ge3~nTRa-td=Z~(fWlRVe63urUzc6z_WPb{`rZM=#d@FaB0eH^Uu(Ibr z1`VfhQLhNV*he=M`G6-X5mY_8278rvMgQS_a5;a#m}A%LY|E7*!?2{(lJjx!#k3&>7FOw-svy09Ph_{Qtr3!t7yX6Ep60_IGoZ+ zg#6>3j2OZ9T~nF?O=J02=dRqG-LacBA#bXsM-@G+Tr@5p?a?Qq;ZgQKzyu37LNnKXC4is`*3kYqYc;J>Q^1oheIhs7Yd zM#<}qqmis9<%d9HtZ2DBxY<7Y=SfWAD}rB&G*58fo08Ct$9Ds4+~^T4CxYq>8ELo|27KIB zTQ_^0T`Ls#=&K~X^cd2<7W7*R)^@q<{(QHiO3FH^uz;jUcRf^%#kic+h|TJ2scOq# zO{D=E4#l70+IFwh8JF$8>9yOi7qd9X3wx#fy&T?!RW&GMD|Foi0}i_lrKs!2*4l9s z8nJrb-Pk6iohHEaXZhX>0JX39g?Z#2cOrc3G`0m zSA>=gR7iV<^fW~Gy#t%)6kTzc;v1d-z*~+TPv>Rh_hrnZ@7w-!)$CQ_tyzlK*08S~ zA4J_Ueo?*%`(6vT5i-47z8**YdTMI0C}_RSz&FfWbyI#mvJQW^=x+7K{dLOYkxCvl z{L9kz9SAqsB3M=OIKo}O$@fM}#?Y@cXa#$A!N%pAJt{#1W3XFqN`KMh|Iv#wO1Hx_ z6bth2+%)0qZ{u?qj~ZVN0?&0CmaAHTdsh!6%^pg*r3N6Cgf722BGz3+>3u7!d}&BY z@Pz)H@v{le@dZB`PHCnJ@P0d1K#4IoT5vX{sq0I7EuK|Y(xA1XrfLdXKItn6_-nIe z^xk!VHBW0Rq!nA1SYov1ta;+23#^LoG+LRyjr;^6U9#ok0WTOSkKs2MB|mR>3xWkQ zhEA^J+5uS(vPZ3XP;D#&2uj7O<@(sTQ!!2NBZ7bb)*wDwuZLw7^!L-0>QcBADxhEV zu*Of-_&|b-r`quN>A49V!X0xL97yDS7rzgV{y;6MZ0FTjNdg4X%lO;Il9hJIZ$D{f z@Ua2s5qXsM=cr89|MXf5-$j&RhMi;zr>k(eY~Nyd$bjfq0@TZ zxsSwgGfAZ~uT<%xh1QV%otsWrky6EH>KqafLa*7}?pYgre>LCBi67a^g42Uq&4|T$a;)XHiY0sV=#k7vle83i(+6>|R6XPNi0~kl^V`A77 zv{)&p8%3s3iQq>8(H{|Mmderx8bD&K~AWKKD zh8RBKC;JzDB-lcsQGd7Z8{als3joatfB&MMJw(}GabJl8s4K=PjSU}e@0Ku1S!zVX znMIsn(Cu&QQ!!ayP~HAs9@_!zOKhyOLe+e@0J0 z6xTc+?1uG^>-vzN#Q|OTvtJLoJ1ki|LLX@%h=ld~XgGH9+2)zOoRr*{|CfuJ%ym|8THdIFG zyxcVDlVQ0lXjYuufmJ`8z=Mpyy{d&iMa;RA@e52gj%Y7XQ4f_`1O7GK3nR+`4Inq? z8_VJ^$V`IU`9pD46Y__;;UCA9}PF?TBoZbOb( z5Erd42HBlo&Y__O=0_S}Kr{U$iEVwoiOoBJG=};RLLLw*3PjIps*$0BOSh?CJ9Syv zzGikG;mnW}|7ISKb1!LMP4z~Q)uimHx7oZqvQOk>VimaYD*#ta>cs4%jNY)tQo$Y# zVX4jkLjL%bimIK5Y^_)fwV+B+7i|$S+RPL{XY0X zZcgD!Ge*$xvM0QF={ZYkzZ%ugR+KFsiG1==N#9lv$v2K?Y7uwdh8}&Zhc;12ZSFqm zcHi`ArUXnB6m1o8jm5i>hkNxY{0pU;rfPg}+a(rTuWrQ_vwY@|dHwStNz@4U^aq=B zJ@X4-Fzr*!QPXRPfGD5Ik=J(F|3tKuNCCHY-i-_3j=<1I_(g_dlat*7f~)a4oTBi( z`%ciBj<{6bvA^r=gY_$Dbm}T(o6axJ!Xv0^ST&( z4Ecl;cy5S!n0)0GJmU`p*aW}ur5-;FqeI#2*D2-Tc6lko@!sGM^`0yZ?SY>a_s37) zT*PFnU$K=`zQs#(a7^z>9^Uz-L_o4Zf(Tai6jPhDyIsfcD!^pgmj- z_B=x;6=bxD?*qD1SvtUPZ|3+QLl;El{Qg&gpdB4RRyrGci;P!65nR%Zfs~N@%Dig zKfk9q|Y76uR!y!%V;Y)_xIDbCt8IS7v~bC2m0J`Ih|k^(2*Nx(D}>^GCwr zxQlJ7;@w-WEOMMPGI>p5gGI}v*EN??k+x;n@oHps$;+Z6 z3qp**($c`Bf2#{jCPWfi%T&hX5u|Ai_(^T^TVe-7S+kG+37Ry*FrXy?92D?_cD`hH zkNd=FWRY@kOiAs|obr+&>k5s5oj~-*>p*um>{&84Nz83OvVy#{aWz1SWCIi zk{Gd*Px@DA@ia)J3Vr=~v!%UMf-I$QY};fRhSFEAqex`gbERN-W0$R};r@~Pa!CsJ zHJrjy*3c(K%2;)Y>?|xivL4RdW6Y}>DKQ6~Ncu7SyMfqOOnwoApC6gR;0^b`KT$?m zvL&#G(|g*F?|orcQ6iG*i@NiQ+tgd8vC$>|O`h`dY*mqavI31QglmXJ9&Ev z?7+NGLS~9LUj2#;;V*8vBEBUBX>Gm_LL`>cub`~Zc7fWk;a~n=^;7yqgS+u`q6W?R zB$nq;z;Y{+z@L4f!u0#kS;y0zQTo$Cbh$kDuz({)X2`T3%ygUF%)7t!eVvfl$2`^R zoBc-C4dpPW)x{9e(5<0jCfATAjKCYiBTk98klE#rVpLV-+k9$^6g;hEWaUy*%G5nv zQPN3OmvggRyzBj}Bc2i)Q<6dYpoQ-6}i`3+_iZ_@LAs)IJS^`_iroFkb^LZ;tJEG=)SH1EfVT$A=pthUom$1yCm07nWdo+N{&V&o;cWi(CGbrApb9>I+r86}bb}s@C6h~I zJ86MKBgo63z8e>_=(>(y&LnKbsk)^s+xioYWK2sc6OX406|iD-%MISt4CG6!)d%WK95HBmh(rk+SyA z|6y*AV8DCiaymaByGV-aN}lofV!`uc5Og8EnlL2uE0t{(a-e)tM5+F)ll{*yf*m*g z%EMFG1dosROg?1`BIR@J%~Ex*b`84^{wT#YqgUn(tLNWYC`1zW1TDdY4X1gY7PakB zX67IIO_EI)JYV_dsPqH7Ns1UF{Q&3mVAE!i9|#YJ=Yp?ri>Rq>_4C1GG?JGIS!rVb z9&Uf$`k7Mpou>U*m5JhaI32xI)oZEcKJD(DQ^~8S)G!)Tn7C1)O`TGs0k@Y47Qi>&FS8S4T;>PzIHa#goS{WwB*Q@Q){cTRQGN7i! zeaj&a=$|=Q-s+<9r*{+Uzk&d_>hgw^6(lnW774lY=#o(RwUxmLt)q8MiloXyuN36E z-I)^vI2O})gbjun``o>En40_7zQ!1ijQZ z#l3j37}P}~<~>X6+`#u5K`tvdrcV6{j_9(RmCt8D0{qU(6+6tfE!zNe&|eS}xf%5l zYs>2_yuOyPab?Zm&r#+c`O~4{-mB>9w!xSe9s0ceCe98)tA-2(7K{$0978L5qOiLA zjmy_-tz~P3P3xiB!C=2l8wOs%BTGLp8<@s{m{1|aj^(@F3&$K85_3ZaH$aUEff;f2 zAab^Wj_(y#l)1OJfUh0dk0OMf3Hz;-u=UTef1Y9*8^piZK2&el-R(9<-*SVrCE`)X z*bS`-v|je;ls?h$tu>IdiL&fgq{%J(DoBRz-|!;+Ye{sCeAGjIwP+keV(}sztobkg%eIKIy}S( z3eLRmkfFwhp+h?7mk^d1Re!f!hxxkO8t0{sm$pE@^^J`Q@Qyz=c_L=+>ttT6re#s7 z!z<`Gh~2{?FkKcE z=CcB_Xo0W+uxSF%yt9v9d-Te;M$si*a9XT^uZPZ$G`&!1me>3`WPk=Ha92p!`RE;e zvDTlcE<{xBQ=%zTQ<68q{vJz^?p|jH5kyySc}+RQ^>5=gDb-6c(|JTlk&8< z%+)mVpxv6;@Dts(O)7SmP)h2k#v4WUH59l6L9n%k?Y@k$VO6}qQX449AiCzyfw`Bi ziG>}{osZA5v9CmQ`%mcX_PT^>a@Qo_>W~ z2=;-B7?a%&2_+dEoOaL2#8h<={;(a90LM}cGZ0js(*B#k9{{L(n3{%%?!>Wrh$;U* zR1zbdHzL9vfe9kvN2^B}+yn|HkeHw#5OWmPbsVCS3>Km&??C6BXGr$lb1IUa*?*qk@jqZ9cm!;=Z4icn!-EtaqgZr9(HbEfwfri z&Z~Qo=1jgTn#sk_&uESKY>GZ%7Q4mL?wI`BG`V}TtPdVDcCLcs6@z}&mw(`LEhNI{ z3-%rVNiwqUz|j5ZIak66sr5U#2)xmfVwrp4-O}nG`x0yAu$tDTMS08?*{jfiZcaf~ z>mVSDc~kV-az+kc=5o_zHxIfU9(Z6zwELdvO5d({DTS?K!OJH?u5@1e1uE>mYVT9s zh718=vB}`7%{PkI6WEvt!eQ_ChlP*B&bR{yI1eXq_mO0wPYtMgAUagASbh9XIZ>vz4M5V+5Srg$tI?Z( zvR8UhCiHgg)}ByI<)CJvOD}J$M;x244#oKWUK(dvwb^XjY66*^LFYHniJ$Q8=lnzK z-a~Xi#djz|zbv$fFRiv4UUvr~--zgQF<^I`0fdS`$ZH3)1T?at*)sCzNjsUn<>mS% z!Rv`Nt;iZSxHtXN->Mtq>Ks97$ZA6RO8u$Yr}lvtho+<&?UE)9r|NrCts`&0WxE$_s$H6BK>)RQf&4ov=k8@?I*1$O2Js4pyNbML7yfx0~W3 zYE-)2LpP>O!!i=(WdTlis*y%@*CR@}KG#-Muo)x%-d`@!y}l}qjq&C)8dLMllli!u z2`O5Qw;0BqN$xYKtM|)qAMQE6Y{!vG50;E4m#Lpe2?@mXcww14t4$KX#{f96S^rRc zNpz{sZ2=11$s;!%#22ES5jbf2$8sHTe_M=h%Y@JDm^7Tjg~{!?l}khD^P|IO=oxMx z+7Y|qCPXqhdHGREjUDiA9r}>R0xnIiyU_pHFdG3zX&4&MR?imD65xyUt>NQ@9eb%v z@;$evvQz{4JKRG-QZ}YY8lP2V@d4x~=eJVaH|2W?G$nOd#o)*VQk&#;a}L$-d&t5} zIfC4+y7zW3SFq2~C`)KrN%&ZrWs*(&@N7)`Q<(Z*s55IGNtYm58$3|Tloldt@f%8j zM0R-HPvgjn^#ueP ze0}8Sm=B{SNn&DaMfKZ$Yq3sm&Q$G&S@sKMJ*|YxgH+kEG)+?p`>nRoyn|`ruMQ?;bU433mTGiU35Fg5z5(%#|Y;tIH;^oi^z*yvGsd6Ty+K` z%5b`3kzWc8B_DQI9h4N=i96Uf+5dijWKnWKaK|4EU8Sy5Q7=$ZLU(d2|if!GN?w!9m!MS3@ny9doXiuc{b%UC{|yCDlsLA~Mw zS~GnPx4Z|_(rmufq1u=u+2EC-Irh? zsO&BDIGT#;B?2M6dX8oVU9SYQeSXdWckTBm4f28?!*S43O%k4@!N$R?U?Y(G^wp zE5j(2+;>9f^O4zO+vhRt-WwR3ri-Vu;?Aq`Dml7+wMKgx1Qe1LIjR1d6>Au}RG~9$ zH+?{d)>HjUw1;^x{yPu^;;&9kVtbgog{{u1A{Yg^u|O-cV^8@SWh4^$ShoGi8~K4p z_Ub0?+Ag~_!1udd{Lz5!*_R6hDe<+|oYL)Tt6L54?X#Hffii2RAZX0NNsV=Qk%eqR z*Uva~={?+UgM?XeIw_f5+q}u*TLuN62X@ZP-$J^l0o0e?jQTjzZ1|EG+>!`;X=t1y{r zSG^xIm41gU*p;sr`I7?|%GVV0n9H%gc3C{(Hev<^9enTcxFbUtS+!;GKZ@?KT8=!0 ztK6lkd%WaqpgFQh=Ah4@tZZtuv?;fL)M1Gxs`;udK4V_g{bpRglGjF0s&c6|@17?^ z(9|MC#R*$(I5#Am>m>@GA0B=t{|cnX=(8t(ys|q8IfCUdbHRRmCzW}8s=uq7Rikg> zb7*n>?+bVN!Y&#uc};ydN^#6!HF@0A^so2U<~RAyCDRXMhj8h$+T}O*+ijx4+GIc$ zgbA|Z&7v|6c_{}*Hl9hy>^ah_bF>OM(y70%gBRSdoZqI~Vw4v8R^f|OC~w#FD4yrx zVVTIhjOqONJ6R&+CFwGR%}P@0q5P?|;onbro%(&i1p;ReJ21A7knB;w9&sOSKd8!< zU2!U?CU)}}S5|2+C<$bMwaR*R-k)w^fsAMn!`XXAbw+CGv*lILpdG&~t^SjkZZvltm-9>a6pCUDn&OPR}FBGp(tO53pk~9CH47m?k4v z+nUSX{`+760AyJK44(K+5eQuK{$~!e;LDiLoN*#rCt@WK)*6OPXl~xGIOLJZA8O%) z&@n^w1=n!4Gv@@HPCopEQ_qVI-={|nr0hLnmk_)!?2?`ddI{<{JTKk-2P0T58e6c-1ryMI{_<{a} zmNtlVGGQfE^qT}sG8X;f=GcQy-wqQX=p{3FW;}M^+T1h^I2sWLpAfLx7~sfS(`JQA zTJW;L3<4J8DuPP3ME~zA`2Q;8rbeUcF|er_ona*wn@UGTL83YCPq#~A0HcHyO~9tP z2C!Z11z55hcL3e0BDN2P7=X?niwswi$!6|#!@&-hFlQX6U;&Dj>s61y)$H4^P-#Mb zAVw~X+u<;l?W$-JflLwQGxXpU?{9VN`6tN#CI%O`YTru&hW!ewS`AjFA3kS!v}-GF+nt0qD*}B&wcb8SEqXUCW!F%%$}tS29P}_c$^+taZ~_@?1ZxN2 zSMVOys3O(xUQm7o^7gGaY z{w%hCkfmxs_ClfmoS)DflP?(HLBX5TOi2~7?o>tsU%W280zuA$j9u1IN8bE!gxuT;z^RE66LrJVX_SgMzN_Njf`gGVVvYY z0SF?YT@rA?DuS^nv4Vk;7|@Mu4usez6}U}5LU7-NMh_hW)GE=DpeNo2gDQF#qBV2? z@c=ZJ%$Qk zd31cJjs+3ixP;b%sDSLt?%PRBuog-1GwHkfyVKO4xrz19H$}q->KYZu>i>HdkWT?a z^aG9152T|y&9I7?sYO+`KNYefaf3@|-&y%)Mo2iOk^e1^?Xa67*J z1Kh#O7u}llA0%Gi4`*o0KageCe(Qkx-AQW>xqTK?1x}XOnf0O%KCT-?p0r;#1{bq8 zFq}6^f9>`Ee2;8Vfcc4C@_O{Eg=f0ITg?v+Q5 zL~z1qLZRFn9Vu4>I$0^A7FF%!tn^DDNo+V8$Ft2-|5XgQ8aURBAmtpoiBXtWF@NUDq?$MKUpQ>aQ z&@4EsiWnJk;#^dpIi2sk_p5*$_ng7M`du5Xj%PAD|>U8XKmcpvX7aX{WX}0>Av_6o*t5Qn#XJ$ zvwvwY0YSf!v{>G!-^3j=JLwEI+tAc$x5C{4S~t%xtt~R(-lU7kht*C8TJ}SJa%=I8 zGVP9fA7B2Z^}s59qpK~$mVYmcx;dlvk5p&KKhrl!NY-B{?4SDm-fy}iDVSood%?^fy*-`GTWn0>|e&q$}Pmv!H?zDF-%H`SETqn82YaM;0N4l0; zrhq_qbK+G@e`!t+l`0cl$?i-W&zgHSF};XUYTNib5oJ-eAj#=(Gs%95f|?~ZI;D@u z{a&|^FDAXdQ@B;Qjm|dMCI*pjAWICacvE0O_qA>$!`Oum-2-}<(pE7`fdj$>L!3hD z`*K~GlEJ#)yO9khj@SP3ZED9i(wA*?W&qwyU=Xt6_!@=zof0d~u6QS#?MPgfkmlA8 zHu-op(Eac&UFT@OK`u2-?E0!<^c~brR2vX}@u!N}X;?(d%Atr6TLUIo-tK(N<0Z!j z6e&;ix-DwvETJ!Xtb5vy5-7+Mn*`Epmxe@zj7)Y)_4#+>_8pw-zDwH#GA=@;FLOGO zZaWnq4nDfR;8Dv)Om`AHn)R{EgD;q8Y1AcZU`^A|xi`%URIT}(dwI&)%4JMbY#LXb z-G@JyI@-%)>6o{M00*im7sB^bRu|I*W`-GA5c&ar*LR(%Za@0C~?_xk3SVn zfVe4}^A$xucJh4aqSoubp8d8QCus3~m5yheW?XrbS#OhG-wN@NyBfAeLV0X&v$E(-fWgrKm*xHP;K6x)Wj>2; zz(o+cO)z~XIL}{VGsG&lSpC>{^$**#&a;Vtwt{h*`u0x#_$uP>^eA>bw__xQiDGQ! zsiTJ4%}S=|=!#-g?<*&D6 z+ivCq&Q9j@sFJRt0Q%rReT14M$C$_C`tgT5?`=)P4ZKRe#bO6wmOUB_<^WE0vlo#$ zUfVkkFH>NQ(O?E-cqX+rj9u&$OYj+&Jy*s;>fW6y_Eui2)K|^i%07W91%-0ws}B~) z_ju?YK3D19Ovx}Z#V)5rbKev0FM83L0MJ6Y(pytf*1Jih=v5c=0MyNoyH2_HPRUlI z*BsPVn^3}7?_W>fs#XNtd*)N7!w%J)>F!3cb`1lTP-{lai3faiKq=sKQ5nx`ti4t^ zsEn5kOE&pB^QL=^vI$`~12Qf-<(?r3H3z^Lv>soq%PeiNQ-l4tJ#G2XLRE!NCIKHIUJOR5r%#dZh^0UA#_;dEg>W%4+_y@1fxkQl~sj7o-8u*{O0*G7iZr56B~ z+YDr=$M)s~$(Au>3|2@Osxm7Mc9O@8LR7bmy2|k##;0A8Nr+T;;0LNi9!TeB02wGm z<(TB~g$a40GIn=BA|&k|)T##-up*8m_|WC5I~7z!f< za}3bK7$x35syW(fYeJ8}v>p&la8Dbh-OVA5+`Ya^7d!tJosol3^?qIN)6>E)3R=k= zcg>*0>EfqZqg7kgWED$^uDd65pop(Mh*KEs+Hg8tMwqpv$Xwr2`Dd%^ z)JV#uH$oSWghkQ*P#>|n6-%Rd|D}sh;P-4f;7TzvQ^Q0c$}TX9`h#kKll@E>EIoOj z?wq5#LDikJ;y7?+5@;YRMPdghUJruxyRby%AjZh6S~Tu6RZ7)`QV;A=!CNwh z&11rKSgy~Yj!Iy{)z`n9F+V~rgmCiuPqbH_9zQuM zn$`1SRXC^#N&0Uk_>T%;Fn8z|+44SDtLXr11a9w^mY*|)M2~R8=l@Y2EJ6RQIF=dG1(p z2(7E%2@nqA?^gJ#UAnx^bR@z6wx8}fzpSX>c(Q#=xN)1(?ITUOAqa9>H=6$Po&jBH z@xCnXi`oN{CeB5fz`>Xv zqQbCOQz)>3*d+A|ymYGHxLYtE;lEDT12qorn6W?ZjkINoqV|k8zI`vR`Jdy{>rriK z%5X2n{0uCAPl*FksHT@(!mwLyqug!1QdWf7VhZsP@iP0H>m*7mxl*5zH@MC1lhu?dz*2zzn#d*lr zRKj$w-PLK?!d=djFJmysBp;Ec6&zZXUxmbi`S6LO=dSmm_z`Rb+R%2Z^!QH-Pf;e4 zap0xjCJ(a^pY3@QMWb7=`>TenqNXZ~m7F#q^jMNd{3pv44gD=@f|kghK8g2bwh%gm zO{B=HL|pR@vqNJfA}^|MZ3WN(4G{kcw|b)x-TPN;kj4HhNtS9(fG^0DX0_ zGp1*|h7&0NILE!K0?eFfFRz>F(cw3)Nxgtl0cHHPBbbY<{Z#z%kyTT4YueKZFKus- zrOG6~{CydJwnTOUlc{jfeH+lJRyq5`^6YTwE0YZ-A=^^xh^pfaxdi!GLbAc3wd-1| z>GwQjvur=>_+peN(!gS42M z!|gwgR(W-`+KT5%xcV*?v6fQ{dgi8FS1;20YU|5F{HD%!k3(d&1@l$&Gpp}5BLw$* z@|#T}E8VB9);l8D8%s-h&oe%|cW7?PFy$UKL?`Y4kw{xZG23+F2EVgyug4xt7r{RW zbrV&FF>%szCyUQlEjH!fHx-Wsoyfj}TszKD!CjO$<$AasGh>J}0gTk;&uWjjl$s79 z8{A-C?HtoMi6PUyVk!m?T^VmgbEZe*xx-L>%!||ZAYXTIPa(O70qkD9oj+9i-Br)= z)Z$sGK#Q%ZQQ51%H!mOGGgK{H}&BkYD>;~Flyesk0rJ)Z7&>1uMp z>rhJdnO*{Mzctob;PXeVkSV&$jDR3v7fKDG*78u7iXvK0%+UxR~{1>+N0FPIq@k(G^* zg=$;N^77IZlg01#^hTF?saF?Rn=(K)K1D{oyhIa)!)GxZvy4*36fIWEi9S25wtahL zjUX>skNtVPROd%5XunuMn$2NI{svbL#Fxx~wHv6w1NRuk~dkI)d*-@>Y>9b&V1`yu$`0(9zPMBipyYzN#0w?8S#I<)5; zT~`okr*aFaJNx~(94&a4kVLV-xK9-|+9)kDx$Z>AjsN=fWP={MUwB;5YHN`I5=uZ# z7Cx2ZYNtDIB%l&*=m*lf+s3Ysfx*lz-pWoylr}ytr~FAD3mh zWS&??ssF&m`m|G3-ODen{GWW9u|RjHi37$U>fBbnybOW>K#c|tRYx=E*T(q0=Mu=C z(Bs#~jeBi>T#W}*DY@NmZ#rt++C6p>3+;3tk*@ejEZm~8({ntXuqCPzYVR+w_$(=g z))6OMUeBuC>uyH31PNb@cetiqelFTO;m#U+S^$xA_4CoE`z+Q49>dxGqwihs&_zJ1 zb#1<|8}FDwIR+hpJ|yxFk$@eI$MNBQaY!9@GbdL`)A?r88n0koo! zm|TpTXxv@k`{y(fJhIX&nD=SZxXLin9@6pv0xP7=6x>D~yj^B1kib)G5Vow9v`6M^ zm)lf`iZv4(6TkBKe1v?%%~a-@SB@u)#`q^C**>IZ=u8~Bu?et3YxnE&F2DA^I{h=p zc2dAx(o64LwTi90u2vFuYTKC~fUik534ygYmZImkK3(!4@f_Hp>EsZ^k(iaEX)yls`Gk{o{br(i5}Jnd(|z!7(D{@ zHLZ|43Ipa(WZ(QOn7f=BkD$yRvcXs+VTSds{FEc|PEC|fh(q@W)(xNqgjUP6ja z4dVzhq2P`ei#0~|{x5`~HIWJn_@nIHN`~w6JGXm-S>r$JslIl6zrMTX1Lsgd35pPL zGJHQQn2JP4z)%i2-FuixSS}Lk!ox--p;GWSk~Bg7S`|p5&wh-MtNol1@#<#+D(;)m zW9gouYI9@_zSgo!)-5kpua93cbbq0Pmov>jDTivcShBu>z=b?^2Z?DPoeudS)xCo_ zNi#g>=+B%wGh`8$v)$;1^N6L%zB>#4%Ll2ne*R!;dw0h+9OoUzcX<w(H@shNhTZ7<`VM0&op2*SPUVm>dCOHd)ds*_?>Xqo*Pl=wXF;r4=W zxN8?0IGBLlyQTA}A9}sJ6rtY5mdZlS|jSTa}D=pd2Eg-*zaTRBK((&U?k|jOksJna0_GEW%)J9-2Y|VyxJs4xO3&0#d*gOF6 zMpy2OT_v~@p0emxiZj6%Y#blUuCg}-&i0lFUZ9bQz5->Gro~Ef0(%=BgB20rzwC2a z#P_`D2VEa>OLKROk+G0pl>Oq5vFz{RXxV9R>0?Ws-YVpf{g=$*_cAoM9mYmH-FH{n zhZVm?eqM8T^;)?tj?|`J6uT%hZUV5@>=Au~nspC$ZN14Rp!l9WfI*3Ri`f&tkU z42u!c(Ui34HObXF-Edb8+%q3PK`61PU@sVVY`hr(eL8dYiA;UNT*mLe|DVe8hmvrF z1882F4#>NJcseg&hXJ(`Y{yIf6E#XT1Yz8UKpH*l(csm;T)Zv1L`KOI;DUIGGu>&TY{Q0_ziQJR)BDh}`CxBoF=c^pU3}a`W_+zb-Cq%}m^)JUU3`ceT?r zybEU~2u0HF0U5VnV&|)-UToXJ+nse&)o(PjmA@csZl3*Bcv~+&t0-oZP3~+a;bEdh zH%H}a`b^()=IlvHom-~I+A zZ(u58p*#xN8cn|~wcapv?G~+FSmBY0;cMX|EIwA52w5Kv96r?+5Rn+N383`OYI@6AIfC>6mZsTlL^r--G9h{#I=<8~gg^)Jp)` zeUXc{!W|N=zaD%SgnjN&r4I>jTj{{8%nbj#ud=Yp(xfzAH#(@*U6Fd1db^zTTD47~ zG5nKOAJi?i^TX9d=?&;M%$BxOjRj#fQpmpwh)5~+JYz*K5 zBMqaApDNbN4^#IUKHb7#cYk#M7XQ<%wSezQy)bF_##W!~x$^ubuSFy#bw>ZbX-KLx zhGXmHTalcSm2Ad7oq+g-!1@R?NN&(Nr)qvjeEZ-e*Q(0M)Jz};vZkVDXbG7bR2n$F;Iw{;?`{K~42TwYociM8MfgZ(w0RxFaVQTH zlNa4D{LT8dZHZC@spp;^YSu93(SWFp?1?~0T=m3WA;4BM17lU{6Ql|p#jq!XGaex~ zSEMB78+tpo1y*ydV)Iv$dVVi5?PQBgD@u@kon$ny;IH;J95VGXq*kd?ulwGaXLrom zeliSe|K)osFYnfVLzC5|JSWRz0SUfd47g_gik(-ae&n96O;qhi8?q-%sg(=5_4A@8 zSCie4-RL{{&naJ%?(W6RB#3cGP|ok~OXVs0D;p?F^XORM&C!!0B{$lTg`Ow)fbhL9 zJ3Jy5xa1|m*JQ)}zA_($o$Eb@EZ~-bVcIjdf-jBs{lpw(}M=z9@z18a#I6gXP`3zz0;(-gr4X_(-xZHIetW$wgN zq!K-Am}q6k`Oyge-6h={eP~S=(|5-|t<)uVuu|BQahd_w?C(%%I=nT7l(oJQfV>a&$+D2 za?{NP5}(rDzterk4p1Jy|4=9+JP9D6p#0DV8>{NfEA(h~wpRwS)aK2!KF63=y|#M( zdAzIr#NBIUsXSqqf0a4Yg|=WjKyuzFHlH^UeP~F(!*283(JLA|Ml_9UYH>C>&=1tL@(!+QEez9>wbm6xhk88p6Ht zIX$p}nMdePY@k&-IQP?s$zD%Gj{+up=wWz34TqIeZPN4JIzAn7ZUk1PN45Rdl&964|;eg>GOrW_Z zRg0BUs41(n>|84pQtqGq{2*=@G0g=zz_+IOw6l77hwv1#wV6}L)?^7c^0xAwSaKXK zf+>(8`QJApCaeP4i?}Pz4PUE<<;b~1xC}qAtJQrJvRhb2lP<|Wpm(P@4v$(0-uepz zYs2jZN9VT3NSkc+3x(<`QX9kcAt{WND!^Z@E1N*8;I%90=?J1W&FMJtyskWAkY^vM zbChK^W%+=7-7awN58GXpPtp_m! zrQWEUb*E$k*hE=(q-Sb&`K>e`lMFu|;`EWOv$!HBV0+=v=0jLM6KUo_Ns-5bjbV$3 zmx=XjqjF1f8RncNq3?Hb4yr%fPhmG%>J#c!*>g(Y0>Yn6yu8!-&TFwc9RAYp7^$|M zvuytDbt+ybRDyhOLVGd6g!<(C&)SUuUDIA7M5mx#HlYb#KI_+nn9%*|k9jC!Rt3>- z?zBK<$2khwpVXDi8iUtJACVpe)6F9qxYtkLk(a9yDwtr*zuI9>sI47zb!i6&p+C|`=k#G z`Tf^L@w~)KRNU&yKoe8q9 z5I^VEG^r@GDA=qNzluGeSL2(`*sJyRM#HJE4|m^Hy=YoLfNG+w?IBOoTa{M#!Mp@K z|Kq#X3oCq1zf!9+a{l9RN=i_UxUV>0tzk<6B4(%Gt}Ry%SiDR+C@RKFREA03dOoRA zaxaZbdgeChY;^vlm5f#Xf^Sue{ph-pvCNH|Mwb$IT$i#~g#lR_2z_6Dfx{ojTe9tv zHLijJZ99&goH51`=GpE9_(ZIqIAg*37o!MKQpu{898m5uF1wTMRzIM8ac773y*@~V zJ|axaacKF-RnAc(1xixti1ol5$S>8KaiXi5_yOr;V8!^edGY^ADc(+_AYD2^S}1$ zC>VX%EVqhyZQ1o|yq*;B?v4%jBI&qkgguXHuYTJgO-8(83cE zpiQz(bYhpc6fkDVcnnOT4xgd%V_3>do1!^78I`cErVAjv&3i2hCeui?Vpm{TYA!9}6wv<6I?YWiV?Hx=EBzTyVy zvCp#$^OQHWr9u0X@!ZKPDz+79VK2(z#1KBxZ^&<@Chd!6>v!u8S~RU%lyKqT(VyQl zENC!(qM@SUN)T24ADzb+G*`obdJxU!jB$Pulaz3icslDtsdfsV(6dQJref|L#JxYr z00}+Cpjyxves3l7J0DQ`Q};Wb{~fBh+i)AVC91HcCeTRrO5Z&hFyvD{PCdR0Du+2> z`2($Tm;?u0Po_qDDjJWw28O5o`mo3Kp8Hi+_*tBIPTm=tEcJ+Nln>DfUEcmR+)rMhtg@qHoh} zw`voojQG^KqT6I5f>2S;UUaL!lTMPd)=}3 z8$+9bpfk0Yop<_3HM^Zk&SXVKV3qkHCv1}sr1H@(D!O7UY->}xb~0F1>FLS-?n%7b zOT+Bvwc;t*y{+F`Nma|%#q#G1f@u$1RI<{jj%eaUi{(qs1l%79O6ZupskW{D1!Nz) zz0bBLkN5f>*T%lWoQOz+wmvhId->Sx{5p;-8rbV8JL# z`=7_@Dvn2^a|3k?g-vLE;V9~|pI(S=sQ(apfJx9Uo93gL)L-~hZ)Qwq(`YO`ea)U@ zq#R8QG_!%dRxtL8^%SfUU~d{YU!sQ`|{*#OU|Mo zWIg~u{xREdfd8pF+0VPY=uqqCu4Od45=nCFrm znO)=7p7>?3bjHCbmI{b$*bbYMdm{EU@Ynr@35Uu;B(DfDZ`ApHBoeI>v%>27J09r? zCZPO$j_ELBrs^Nl%q1Cs(Me^vtp4S!D|kTbK~eQ2U!mH!jazNUkPRx9k5k;DKxT&m z@ePfA6W<8^h=No$ ziZ10t_ujYS-3uF|Y;gUL%UG{?a_WV3KAtZ7@!$gRe&P=vx^)8eutKo^ee-rt&v1J- zkrCGYw7E?jlU>f{Gs{b&8}Mek@l3gvk87=>DgaXW8%ew6p9v0D=PNpq_X<%oUxy1e zo?$WdE3y2yk3GJ@;>+e~Q56b#w`7rap2~5BkZj&|``a55#u5bKybN$W*FjwDyPX zJ)=5X(VE~N(4UF8@t)N9lCsH35W;2@S9WsQ9ADk9{XsU3cGgmftxwyesY!Hd!h1Mf zRhV>k{Z-Iq=4=S6K$KN;)12E=sG*A1&bt?1yl9^C5?M3Wv&N4L=Xw34Ny6G~+V(x} zeKOu~kNI@WOc0hr8&F!db#WpcyoGi#02q*6?N0~PW?pP*;fY=98iF63HS&s`>F0p~ ztXsO9LLNB7TN&u;!&imI=AB}iQZUPvYXP>%^u z%ePmc1peQAcvqQUxV3!r;}F&Up7adoR!T` zP$mG5tk^!r@-E90DvoV&>+V!;`IO>%h3s?;_0X#z*J2J?tt@U11zD7s`Ay&XYT35s z4+r#>;@s}*Npsxn-KR8qF)A@%h_hb%p2nWBZe(B!4IRnJcZ;=eIxgq$q^+E?C5kQ- z-QK^&KC8el+4=Mf18sA^dn##>^|=lCFe!1dD|nc1IaIvbT(xxR;uY&;S|%E~3IPaR zJ3!q8IJFD_(w&xx#rz&~bK!pnr`JnoUnT(kH!HQ{2~Vb1y9ZDRRZmj|JT!3yWFwur zKngp)oln8|3L2N9-izze=UBqt8Byvr%dOJQ^K)tD%&(03B8l>z1>EZt@WpJ~?>iE6{x{E$*v3v=rqq5*R~Kfr(Rd9v2izH-nh zpP_Q(l=(m}pC!)fZpjJo+YaiaDxzF>r2c@zb=-nk*}Z!cu;Q5YJ>= z(W4>Y@5q-96WKk`0|7{d&FGwZqGIM$x^^l(Lx&Mfte>PaP`nA?gbc7SI^}44KVJ)8 z0icngYRlJ?cBo1&@)gmJc*oAvx2Rc3)gY9tsEz}K=TQtuMYp)0tbMG?j{$xH@|n{+ z`#yHJmstkl#1osb)4TK!JwGWQ&)Nd%Up?oHT3R=>rWmAw?MjxPepuZZyorwe*v*X* zy7Pv`;v8LcXV@0)?^?Gjx*Pv?uMwhsL{6c|{7%%sW57K6(*eDe9;L&uEkuX^sqmw4 zYAl4h&vZb=q0KtWxh(noR44olkba7z2nrG9Ng|{9LeOK=i%LSmD3}1?QsKT{$|Nsr zIT|oZFyX)fF9TcS~itfsCY~Lg= zPPWWY65U=@h^jRE5jwNX<|XoaejJX zeD*WCb-s09!2Cb7Jbc9=RzU@ohvl}v1I_$M+zSGr@6F_3oMLo z%OLNof0lRaxIpB8ud~)P?~grvSB*ZB^zsXvC2B4mpL<*yM- zNhMOc$WU{VYhXU?{ek9R7%0x{x&A(%I?zE%J{>CDxXH_uZee;Fjsv zB3RWh!bbCCgu;@vsH`O&om|+He>#`qw|mr?CZ;WgC0g-k`j17l&`H&N__M)9xC)6w zYqF;%2*Nj0XbdV2N+o|As#n;2Yl{C`$`ai>8rzF1C;tDo(doShnTHifo`F{~MB>qHm+Hh1pS09SSBLpJcuOley*7sWj8B-R z&`f$!PYTs>o^CwC)o8Ss<=dSh>w1~uu$DezTE4eBw(OB9c)9YodyqSV9Cz&u%t*I! z*c8!oA&8>zGa-yfBE!i5NI%SO65dlDyiNrc?J5odaMnh}_G^n3Sr%E@&gF%h+|XSY zAtq`K$N&d914CI6Nh~#_jccaCE_} zPmHJ5OsV`Fu{c#GF@im_kTg0d_`C)>MtQ~B2nVH814RY4m|pjL>C%>yDbfDMMe zw2RyK+Jns1P?7!^$rSGWpEgsVW{jOJv_5) z7~89n^miiOqN=!8mOemOshS52B8Vmgx}Up@r9JS&SOO*OUbgdVz<2v0vBZ{Sk}Qh> zT;w!K(P4{ey(UN zf<2vpJ7%b0>cj?iNVUx{(;UV@tGD<3;-y7QhS<~wWU0DS$Xs@3J47szEmKCtNW2bY zO#&iLsF*-5c2GzoC=^>Ds3PtsQp*{d*TyOV@K^p@8Wa%CzX>$(9;D!(bxs!!BmaYl z-YF2Z>Uki)gp<_$-i*$j@_Vj4XnJa}uC$0MQCyf|T z!2MmWOYXT{eFEMYy3V;LqGzAgQ_moi zJkvSX*KAzX(XMsxbAVW{EbF)F*85KK+%#&w*<2#U79#UUs!8JpM;3$gUJ|wqmCw9g zvQGxrxl7_ZERwFg$uV)2 zk*isJ?FH{2q%P>EVHR?C3&vJ$He0q#>{#ly=wR^+bfmG{*mySS&zdnBBj2rbCm*ZS zYq=!&|1=?gaZL+mgDtr@K25E40*>;?YcItv^`Zs8tfcW~7e8Sdn*o84M|4cCJ#AB) zz4Medu)x39fV#6XZmA7yV=lyxyy&#`i}K^Zc?DMlf}0WaFV=pcifZ#fGh%v zS8*R+&jl}M(h>k)jfyH+E2-_u`DMy7fY8eo3-tw^Z)-3`4*UjNgP}J6$O}5v2VD|Ux6A<~FQ!uD_O#4bRENru6;}(G( zwN+}*wSMoYfoon0*(9EQk}WiXB#?GI*_I|3zkQX3W9X&uDLH=m<+n(WK6g&{ntvq! z^%F-gRw3_1IllCS?Orxn-1L>8_gUh+&#+Y}TygV0wfZ45+`s=+*{XR~Yv}Phzm1Q0 zxX<{ebLgtvxqAr4yJC)!Ahzw0RzT``S6}70qN>Lw=gh6X3T{#!UCiOgFc9Cdh_ojV zR-5ClMs6Tm)q%_P#>-pnQ?%%2ZmtS1b>&ZsC^EBMbME6~l?)~8PawudIQ041D{9v4 z)dZe|n^y9!5m%4}5lNXZ$6KPp5uQTpul%P19&6SJ#v_|3Qm-dEIQX7u|C}j50|q(l z2T&&#_@gPu6Cxs>hmIYup}iCv>nP#LSq67DEa%>(9dM@07;Io?9Ap~kN+RQ|Q4F~; z+gQ5RzWL2cK9(_C903Xx{{W>>sQJKmyx-YQ_>k8IRf5hFkn>X$YQOmvxw3iL>W39h zP^+3!YmgzY=5w*lU@cCUp6fc>6Px!ny~P|p{y@5D*MTbt)gE_J{g#fsu#qBlRSS3!xmKKS>yedib(?387+fqNiT z{z{;nW|lf(?O&BDTB`L9Fn@#xYV#j2h*lQ9(l4XgJ~@Si_Lw1>=LoUKeHfg#|ZdR~w zVkl?GJx0K7(x3xoNt4J;_3LCB5$R(ci+M2L1PUTH;H$x zAQ^--ehA$UiKCX!YXGi?MlViz#u?pmlPS5$Zms}rgo5p~Ckj{*8k{qhc#&aG^ph>| zF8&jMc0S$2(OLYK_bNk(r1MCt0%+D3T<6CMyfrQ_$4dq*C{=1iio7qFs}#4lFWdj# zO{32#n{~GfsH;7@MpIAD&H42{eo}Jmy9L@$a^6gE=$u>NdW(9Me!!&^1aPn?T}h{7&25skk!I4*Y0m}f69cK0iXuTG=JMBCiDKKH@y$V7g-)Z9uyGKjHY_Yg=i`O zxbmR0gF=z0`B;Dvr}Dfld%sLdcP>o)s9tpg15LlHGGh z31E?YW$WF^bk68tO3H@)7{7cQeTo|>GZYHa+{XCP$O=S8W9_*!P$?ig)$bKQ7k(wl z&SR}*lOMAnh~cTD7rElBh8Y z1Ha!e0GWUMOePgGr99*hj%xYK@rvw_@*$rjU0bbhmlJTawR8zc^BML%zOMn9`gJ)- zKF`hm?=Xs;>Y?m88m7G%i7RT@KR}y|O3vc`d#-oS6VivNdRu&$!B>gBDF^};jIiF@ zt&otduJar&*n!oGUlyBkz8WJZMEOeS0WiBB^>4ADIC{sjZ{Z+*A_<5#28{IqxWeG5d@|;awNQ66oKi?;Dqw$z9l*Df3h++s+-EM-X6T>APv<$A(MEdE>9=}rEV3l{X$962J)p62Zz z66&JYvY8q`Ct&@pi9!m#maIB_I*7&1p>r-x^lg;`%R6sl1m7>(@$Ub$^yTqT{ontu zSuhxkeHfV;WXZmj5M#@dB1;J|C}~4QmdacjYhc+8zY=05Iyz3w^poacF-=fR^dll-c-6};4{f$u-fj~m==K8-st)3xl| z8S*8`(fp@_A8C$_SiF+O8)mIdK~A5hFAlZeQu)>W{SXXw#2$#(fRc;9)EX1FQ!d>6phP z;QDO7B^z(u_s6p%F1!#ix7nZWpnkFtJlvSo*MV+I=J9;iy^w(=bM_i){B-RP`-=9m z@r$oOo_9_7^YuyurwFeQECimx9Nhz9AkAfHn%rbGvz6J52{oKy_3U_bdk)?ES+4dO z_bVr^&1ZXcym`o1LY!66ieThet))l)^qtGwj_-aeb$RJSm=6=l>2<~^R8}d1<^R?1>*(h$YKe9Zw=xTeup>|fUG5N96iRi7=v1Pg$tyF zq=TqPr=OdXLV{|fu4Db&S?x|3ko|h-a$o`7dpyKN2(5q@Ykqx?i4r}H1628apo4f9 zZ4z;Vp@Y#O_UZ)A^^woDHT17q%1J_nfWC#}?=bL_{W+(0WSIq_S9>M4de=OfQo0fF z8;K?fXgkOMLQM0;y*jPXwCnsT7+T!jYO^_ht**Jtf^Dp7D559=Ww!})?H%7(~R6Tzv^zVONHul>TA-q6<#Sv3Ci|ot#>GBE7 z%B6tq0wUcsV7>ou_epU2X44 zV&QT#*{Ph!nk1n0leYs6eH5qcrNVV1A@y>D$Vyp^*jI^N zzjrZxohDv7cNO~aUCSlCUtDgFxI~eJUubnIUb~}m->O}2ZaPxkKjS*(*47%e4lsI6 z=;2J^2(#77733kFw?O#KmHwhb!jk&}KI5GFTD9`uYN3wb;c`p>pONxy&T?_v-;`&HnjkRX5+iQL zg3$V$xb(x~77dku&_m2z{TY`tN;z9mF%~T}hpMn>koxqZ{Ts~H;4kI*-hZO;%gPr7 zCB``1!zMN{Hsm3Y66fZrbThGfbD7CJ#XjLY99F{$A|OKaHx4?V-DMrYU10IFB$T14 z7k~+Oh00*ud`E8L!YYz<~4Ym1n^Ial{vzRJj;gWG-f*^cdwbLnqY-TrkIA z?l7X+O|weKuJuU>&WEeS6?guIcuSiRD?L2KLtLOx?YI|nd8b&Y*fxQ-^k*s1K7K^B zdNo+;EK^5M(J;jh`-gLoLGuQhVXMhMx*Oq2r5mG1sfp1ecUVeWiXN3lXikVW4q>n^ zlC9eQQdWx#0#^If*DXTpK> z_$eQ4rVdU0tPy%-^0LTpr~y-0lc=%`4-X0ep{?S^H#ZHk8AIZzq8(1kVh>I*-~|!= zgkt4ULE^|_Y6|e_{v52)^}j_IYeIoZMEU9;RGO_oO4y4+Fh#C(9V;7{=`%W$;s{4~lCXvT$X^(e&IE5fT6H6*h9 z&~K8TUf7YbX*I%7u(7-Kc5&%LUWECj6N5iChwqib@J)#TgTtIFWh*3}cV8NwFJ-3* z>^513RUJI=ZQd*JxYo;ky&?;KYzX%-4`4Hsc5H1J$J*_RI$z5;FMI;6u>&km7J{|kuu;J<>H{WL)HO#*0sI-ndT0IUoPSg8LKHbD}1f||cD zV&=#Z#pN{uQX_{JLP-d^j0GT$gw;ue7u=D*h(Ul>j{|5t3sACSk##|EbOGXJWUY`l zpM>telObDT`R`mP3ZO9GoG1N@%He`aL{I7G!T-28s&l1jrL!rGFPm5BngWZ}!D`;J z#4RVtlf=&WlXt9)roFQ%@-8Hn$ngO>+S+MFgV%Zi+GmLciVvcG@yx1gQ=2S4CIVlJ$fuBloX3cUX1#D%;# zV=-L;fzMuM-_k>+Ofg2;%oUqw+6STib(P_s5A!vGzQ3}e>p+J%2q$loWV+|$_XZbI zLNQL91v0RE>cOW|GGFbVT|^2_yDL5hpc9I%?{@D5M`DP^o{WX}C0D+Dy5@u7DZa|? zPta#;i#BsGmXK&gVO61F7b2s$$%#A*Clep%lqiLBkTsqnW$}Z;(O9a0KmX6?1tpQZ z<_j_gJl_pn{HHM`h}gDs%l9K1*>>n+dtPj&b<0Fo<1>r&4Qw;TT+BODf2@Yveaoh8 zmnP@ClgOrFQik5!;w1&8wCVGKY~PV-!J~D@=c*$%e(^h}ik_dPC5+4xc$f)_vP(V( zxpSb_(_870>-&({dOGEau59b%C-Wt8Dt3F(?!J!SGkw1`BeQEQRUaWLpn$SPBp)Bv z_xG4q(ZBX(%_O(*_l{DxvA!$Fpa4v(5#pPv|J(dYmf@$D--7n>8$akHrLX^W9proW z=+qmecff2}=e?MutG~aZMu{9ir7#JZXB(C7J2^2mH2z50!GBEiSRh#N^W2nxH(K}7 zoDN`(W_f<7Dfhp5wq5+<5fYpVr$UF2pJ)G*1SWinviW zRwOKx)D&B(Bi~}5(~`n#8xOsQS-}M*B!3#vUp{l?7(0s(16iBuXrZ5GM06;FH}!>c z<8~&if3abLN{>Y;=S|>3)@{U`74F9IG1`8Wdk-cN1?)#E6X4t229j8_ijmrWRsj zZu(C=V~wK1!!mk#aK|j|a&>?WoBblw*OxNaxj>lFRXQ%$=I}!_T zjGR2NU5*M-RX;v+*Iubz8`McVscW~+DgLigGPlRMu>Ap>IIzi55!n=K{QfxOAni6v zQ`GVEiqiaY9ZEX=P~>e#w>Ah_zH*%?q(;RC%`Qk!oHyVfW(^3Yd*8{3SYJkO!!#66 z3#WabHmjwl7XrEXb|g+x)?tfDrz@v&cnUAj2GdP#=GSr_D)HwMdalul zxyocTOyOQ?RO8Zq=V2<`=K_>vv8R6|#@hrpKVLZvL{GU^>n(3?n%_n3o)+6KzFy1FOI=5EM32e!`baKDsrWPY=eg?fM0VcU^{d~tC^OgLo|nJ@_z znX_)P7T=Y)O>9YcVwmvA4IH9PY03segWB+-mD=u4uX52B9Ly$ASS{e)>&x%iki&O! zf6}0+PZ!758S|Q_yKJf_iJt|}0(KW}Xg-Q1wCwIwZi^S3e_pa(s_O%qH~Rwo&OBt{ ziQq$(dg+6su0b)l&rEHH!&hK4zT}ci>&t)nI{(xj{eQ5=uQzh4=GAhtytnUg0Wvqm zGdsI`r1dP5kM{UlYZJQPIV_VYylQ9$FzgJaDsq?jOt9LL4jq^wQM}YeL4%4j%ChR$ zBUCFfrr7cy`=!w(U+%ETYugq<+QiCFvU3Y5WI2yk1e`JGlz*1e-mj ztuiV@{v+!&XjMJZ@TBFb3x8OESU7LdCjKXi?7wa$5%S6)YnVT#8~(x)p|M|4vwP!Z zY^HWMKuKi%rH|O7Zex6}5x>icA1pa{>?t#E7re|1_UMp;JI@Gs4Jc^Cykv;4d6$}~ zMkBw|tC~On^@>tZ6ka}Z=huGE=@wpE0G5y}h@%67KH_sV(8@kz5_fj=!2MZrqWSku z#{i9P>PMT_FgTARod$ccJa9K6ASbcqOHgu7O}t9y2K>`5F(Kl+moFk0mQ2C7mEE_Y#H5TFOJS$Vw0~3cc{8Z+P zE0S;%@}eAx7hxh%MO@0z1kb5ZpWy7w3AsV}YEwRSCnmIB1=<+!c%~d`^}@{~pmVBE zmJ_6I%949OdxEYXN`1i7<5b9-U0j)pfxp@T>k;k*ffHkX!E**ZFoJy-O+Pcnpb;Nf z?kAMW3>3`bi>_TT%~7qe3ay9yl*7ee{tEFP1eZW=$BZwi&Il`3l~x-3cacu?wJO^>LBpYej|kVzE|Nb zbdA<6+y$COC{m*#p!3|X0#dsBu+sAKtU|vHgtqKOA_CH8DOt=Nq63Pw_Bx5^pdZLO z{U7-~212zZ34TO$aMWjUA8S&&C0|cH;PssW3yX2t*SEDQ{x=(Yi?aiyM(o5aoP>`z z^bRQ@GY0JR%YuB=gr-PvG`8^Np2HoXDe3cnLET`~>Zpy(p)TORHR8i-=Y})fMwT<- zYPoV$&r9hI?A51QTigp$!{LJ?QI+5=>qssUdtFD*?LkSR_P@M_CA+^{WN~LJy3AAZ z9;f!vf~Zm?%!}o06Zm!`?nC1#0P)f^Bx%}<6{dyqkE_rd(iZgr=BS6_E3hLzi1kXS zYE5<B68qH}*4(WX;lDQ_i!(9@Zr^{)9crd+nKh5v zWzU7#gJ5TO1;7p=}B8PxvG%~M82k^hAnX}Qgw!G_EKC1A`zDq_`Ub+ma z@D?l4dF(X6s_vfnp_iH$>>g-~O1;1M+4SfM{Txysv^2J~dZ zl2`WnED>b^Erz<2c~4Y#eMNw#-P&@{^K3!?P!(eF&gfHntdvtVC71S0?3VS*?J+s@ z^oxX(*}(RVVeR4B6Zs0eUv1p|Ed0ZX{MfUtYTNyHcN*H|9iKfqcYDUX@Z`rA%0}55 zUkbi8E~(8VRe6-%S!Of=9YfrvGryIlz>;33Hn&x1Lqw!db|c)u{&S&x2nvWc?LO z?*#X~6x(@4=joaL`{P*(SVVPee`H_3elBi(leKoW^z0;Qe7_n8F{>#I!jhq(a2Pi2 zfTR75$XCx>mpvs4J?^2)>0awGIDP$Z?pRw=)k@569i~TXLTsg zxy4zK0*pyl{!)Ohr+04ldm+)a7$Q%*^d#|hzfF0pi3r)+EStf7M#E2n>Se#qcd?EKd& zfN9+-m^Pv*q^KsV4)Hzpfgecg@L139OxF6wG*_9(u-4xR)fpf|U=LD&@7zi^K1tu< z{8kPa%G?g$=SJdVM@3RW#QB!H=b!RLr1+_3IQpKabmLh!eo)Hj;=7h3Fp#J~`6Yk<@~Of?zUzat?oVAMr!jc>XGWg?;|L@1A@YLQ^$Zz&*2y^rjI_j` z)eEHYwIe#un+r7TQ4I>v!AQhB)!!c|E|+(Yt+aYrORE(~dtH^fMkypIbD~KJb4iuk zUH=mSG)se1(~rfRq&T>ngX2%TPe>F3C*qd3w50M$L2v+htt;f(GwH-@+y5;Wz1mU`sbS>k4Y*2e#Iy?HHhtgW0duPz^ zLid7;GOlMn`C(qXnl!<+B77UBfi%KF0FW%78Gj+|`MU|&&U}-DO6uOWL*+>H?Y`J8 z6Ff9}fofx5p#98$-yiS6q3z;)Wa2}k>+Tx*W0_}mo*`X(@|7F=N-{CWGJ;@X;@@TJ z1n2qxAmyhp3IkDY*yXbW63X`*=R*N6N^XaY>vkKv=h6(L<9+YSbL>EqPNz|82f z*Z!UIY4@wKbqL!dSAZTZZZi30PiiUHKz`G)S512h9iPdo2=cK2?fg7JE%#{EyeT|H zg|@~mYt(jl-m_AOKD$OQjXf>M^8kxB0-^(PXFtL?6vp({? z=yZk9LOxbX;&>dypF=7;z6J4)H9MH>9iz#TbQx=YIFA?%udxWPEe8tM)J^EUIZ|g9 ztMcrA=AhCbirk0@jX4_=9=eHQau9B5Y>*ud=`aNX*h6Rb^{|du8k?xr+@Z7g$m-#} zYi6Afy!*qAI;W#gisXQ1)iJl@>HNK$LC6er458;@IU!DiuiEgvAI(VF&v(hvC6dRB z^R*0~Ex}cZ^SsTO)fK5P)Dvy?Ej@JZ7(B-07la?R3u=*MY0g07 z+#unRV*Ul0Q(O^SoYj`<`iQpU6e{uNN6CPdKa8c=<9*f{R}!k>h{_ipG~gj1k&9fl zH9&(PJ`Y#VdxK@^!`JGk(0?JM^*XWO1h%~b5`4BUCa5nk2S_NM*B%q1$cBAGgO@1#9s53|b?`|dKXOpi7_bq&7nAnn*FfPE1>)1^d