pax_global_header00006660000000000000000000000064137705036030014516gustar00rootroot0000000000000052 comment=17a52d087054f4e8da5fa0453b645860876b0172 icoextract-0.1.2/000077500000000000000000000000001377050360300136635ustar00rootroot00000000000000icoextract-0.1.2/.drone.jsonnet000066400000000000000000000033241377050360300164540ustar00rootroot00000000000000local volumes() = [ # Use this to cache installed Python code between steps { "name": "python_install", "path": "/usr/local/" } ]; # Pipeline template local test_with(version, do_deploy=false) = { kind: "pipeline", type: "docker", name: "py" + version, steps: # std.prune removes skipped pipeline stages, since they evaluate to a null element std.prune([ { name: "install", image: "python:" + version + "-buster", commands: [ "pip install -r requirements.txt", "python setup.py install" ], volumes: volumes() }, { name: "test", image: "python:" + version + "-buster", commands: [ "apt-get update", "apt-get install -yy imagemagick gcc-mingw-w64 make", "cd tests && make", "python -m unittest discover . --verbose" ], volumes: volumes() }, if do_deploy then { name: "pypi_upload", image: "plugins/pypi", settings: { username: "__token__", password: { "from_secret": "pypi_token" } }, when: { event: ["tag"], } } ]), volumes: [ { name: "python_install", temp: {} }, ], }; [ test_with("3.6"), test_with("3.7"), test_with("3.8", do_deploy=true), test_with("3.9") ] icoextract-0.1.2/.gitignore000066400000000000000000000032741377050360300156610ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ icoextract-0.1.2/LICENSE000066400000000000000000000021701377050360300146700ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015-2016 Fadhil Mandaga Copyright (c) 2019 James Lu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. icoextract-0.1.2/README.md000066400000000000000000000027371377050360300151530ustar00rootroot00000000000000# icoextract **icoextract** is an icon extractor for Windows PE files (.exe/.dll), written in Python. It also includes a thumbnailer script (`exe-thumbnailer`) for Linux desktops. This project is inspired by [extract-icon-py](https://github.com/firodj/extract-icon-py), [icoutils](https://www.nongnu.org/icoutils/), and others. icoextract aims to be: - Lightweight - Portable (cross-platform) - Fast on large files ## Installation You can install the project via pip: `pip3 install icoextract` On Linux, you can optionally install the thumbnailer by copying [`exe-thumbnailer.thumbnailer`](/exe-thumbnailer.thumbnailer) into `/usr/local/share/thumbnailers/` ## Usage icoextract ships `icoextract` and `icolist` scripts to extract and list icon resources in an executable: ``` usage: icoextract [-h] [-V] [-n NUM] [-v] input output Windows PE EXE icon extractor. positional arguments: input input filename output output filename optional arguments: -h, --help show this help message and exit -V, --version show program's version number and exit -n NUM, --num NUM index of icon to extract -v, --verbose enables debug logging ``` ``` usage: icolist [-h] [-V] [-v] input Lists group icons present in a program. positional arguments: input input filename optional arguments: -h, --help show this help message and exit -V, --version show program's version number and exit -v, --verbose enables debug logging ``` icoextract-0.1.2/exe-thumbnailer.thumbnailer000066400000000000000000000002211377050360300212030ustar00rootroot00000000000000[Thumbnailer Entry] Exec=exe-thumbnailer -v -s %s %i %o MimeType=application/x-ms-dos-executable;application/x-dosexec;application/x-msdownload icoextract-0.1.2/icoextract/000077500000000000000000000000001377050360300160305ustar00rootroot00000000000000icoextract-0.1.2/icoextract/__init__.py000066400000000000000000000136131377050360300201450ustar00rootroot00000000000000#!/usr/bin/env python3 """ Windows PE EXE icon extractor. """ import io import logging import sys import struct import pefile GRPICONDIRENTRY_FORMAT = ('GRPICONDIRENTRY', ('B,Width', 'B,Height','B,ColorCount','B,Reserved', 'H,Planes','H,BitCount','I,BytesInRes','H,ID')) GRPICONDIR_FORMAT = ('GRPICONDIR', ('H,Reserved', 'H,Type','H,Count')) logger = logging.getLogger("icoextract") logging.basicConfig() try: from .version import __version__ except ImportError: __version__ = 'unknown' logger.info('icoextract: failed to read program version') class IconExtractorError(Exception): pass class NoIconsAvailableError(IconExtractorError): pass class InvalidIconDefinitionError(IconExtractorError): pass class IconExtractor(): def __init__(self, filename): self.filename = filename # Use fast loading and explicitly load the RESOURCE directory entry. This saves a LOT of time # on larger files self._pe = pefile.PE(filename, fast_load=True) self._pe.parse_data_directories(pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE']) if not hasattr(self._pe, 'DIRECTORY_ENTRY_RESOURCE'): raise NoIconsAvailableError(f"{filename} has no resources") # Reverse the list of entries before making the mapping so that earlier values take precedence # When an executable includes multiple icon resources, we should use only the first one. resources = {rsrc.id: rsrc for rsrc in reversed(self._pe.DIRECTORY_ENTRY_RESOURCE.entries)} self.groupiconres = resources.get(pefile.RESOURCE_TYPE["RT_GROUP_ICON"]) if not self.groupiconres: raise NoIconsAvailableError(f"{filename} has no group icon resources") self.rticonres = resources.get(pefile.RESOURCE_TYPE["RT_ICON"]) def list_group_icons(self): """ Returns a list of group icon entries. """ return [(e.struct.Name, e.struct.OffsetToData) for e in self.groupiconres.directory.entries] def _get_group_icon_entries(self, num=0): """ Returns the group icon entries for the specified group icon in the executable. """ groupicon = self.groupiconres.directory.entries[num] if groupicon.struct.DataIsDirectory: # Select the first language from subfolders as needed. groupicon = groupicon.directory.entries[0] # Read the data pointed to by the group icon directory (GRPICONDIR) struct. rva = groupicon.data.struct.OffsetToData size = groupicon.data.struct.Size data = self._pe.get_data(rva, size) file_offset = self._pe.get_offset_from_rva(rva) grp_icon_dir = self._pe.__unpack_data__(GRPICONDIR_FORMAT, data, file_offset) logger.debug(grp_icon_dir) if grp_icon_dir.Reserved: raise InvalidIconDefinitionError("Invalid group icon definition (got Reserved=%s instead of 0)" % hex(grp_icon_dir.Reserved)) # For each group icon entry (GRPICONDIRENTRY) that immediately follows, read its data and save it. grp_icons = [] icon_offset = grp_icon_dir.sizeof() for idx in range(grp_icon_dir.Count): grp_icon = self._pe.__unpack_data__(GRPICONDIRENTRY_FORMAT, data[icon_offset:], file_offset+icon_offset) icon_offset += grp_icon.sizeof() grp_icons.append(grp_icon) logger.debug("Got logical group icon %s", grp_icon) return grp_icons def _get_icon_data(self, icon_ids): """ Return a list of raw icon images corresponding to the icon IDs given. """ icons = [] icon_entry_lists = {icon_entry_list.id: icon_entry_list for icon_entry_list in self.rticonres.directory.entries} for icon_id in icon_ids: icon_entry_list = icon_entry_lists[icon_id] icon_entry = icon_entry_list.directory.entries[0] # Select first language rva = icon_entry.data.struct.OffsetToData size = icon_entry.data.struct.Size data = self._pe.get_data(rva, size) logger.debug(f"Exported icon with ID {icon_entry_list.id}: {icon_entry.struct}") icons.append(data) return icons def _write_ico(self, fd, num=0): """ Writes ICO data to a file descriptor. """ group_icons = self._get_group_icon_entries(num=num) icon_images = self._get_icon_data([g.ID for g in group_icons]) icons = list(zip(group_icons, icon_images)) assert len(group_icons) == len(icon_images) fd.write(b"\x00\x00") # 2 reserved bytes fd.write(struct.pack("= 256) generate_thumbnail(args.inputfile, args.outfile, large_size) icoextract-0.1.2/icoextract/version.py000066400000000000000000000000261377050360300200650ustar00rootroot00000000000000__version__ = '0.1.2' icoextract-0.1.2/requirements.txt000066400000000000000000000000311377050360300171410ustar00rootroot00000000000000pefile Pillow setuptools icoextract-0.1.2/setup.py000066400000000000000000000032141377050360300153750ustar00rootroot00000000000000#!/usr/bin/env python3 import sys from setuptools import setup, find_packages if sys.version_info < (3, 6): raise RuntimeError("icoextract requires Python 3.6 or higher.") with open ('icoextract/version.py') as f: exec(f.read()) with open('README.md', 'r') as f: long_description = f.read() setup( name="icoextract", description="Windows PE EXE icon extractor", version=__version__, long_description=long_description, long_description_content_type='text/markdown', url="https://github.com/jlu5/icoextract", author="James Lu", author_email="james@overdrivenetworks.com", license="MIT/Expat", classifiers=[ # https://pypi.org/classifiers/ 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', 'Intended Audience :: End Users/Desktop', 'Topic :: Software Development :: Libraries :: Python Modules', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Operating System :: POSIX', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', ], packages=find_packages(), install_requires=['pefile'], extras_require={ "thumbnailer": ["Pillow"] }, # Executable scripts entry_points={ 'console_scripts': [ 'icoextract = icoextract.scripts.extract:main', 'icolist = icoextract.scripts.icolist:main', 'exe-thumbnailer = icoextract.scripts.thumbnailer:main [thumbnailer]', ], }, ) icoextract-0.1.2/tests/000077500000000000000000000000001377050360300150255ustar00rootroot00000000000000icoextract-0.1.2/tests/.gitignore000066400000000000000000000000611377050360300170120ustar00rootroot00000000000000tmp*.* testapp.ico testapp*.res *.exe testapp.rc icoextract-0.1.2/tests/Makefile000066400000000000000000000041141377050360300164650ustar00rootroot00000000000000CFLAGS=-mwindows -g PREFIX64=x86_64-w64-mingw32- PREFIX32=i686-w64-mingw32- all: testapp64.exe testapp64-nores.exe testapp64-noicon.exe testapp32.exe testapp32-nores.exe testapp32-noicon.exe testapp.ico: testapp.png convert testapp.png -resize 16x16 tmp-testapp-16.bmp convert testapp.png -resize 32x32 tmp-testapp-32.bmp convert testapp.png -resize 48x48 tmp-testapp-48.bmp convert testapp.png -resize 16x16 -depth 8 -remap netscape: -transparent black tmp-testapp246-16.bmp convert testapp.png -resize 32x32 -depth 8 -remap netscape: -transparent black tmp-testapp256-32.bmp convert testapp.png -resize 48x48 -depth 8 -remap netscape: -transparent black tmp-testapp256-48.bmp convert testapp.png tmp-testapp*.bmp testapp.ico testapp.rc: testapp-base.rc testapp-icon.rc $(shell cat testapp-base.rc > testapp.rc) $(shell cat testapp-icon.rc >> testapp.rc) testapp64.res: testapp.rc testapp.ico $(PREFIX64)windres testapp.rc -O coff -o testapp64.res testapp64-noicon.res: testapp-base.rc $(PREFIX64)windres testapp-base.rc -O coff -o testapp64-noicon.res # Build with icon + version resource testapp64.exe: testapp.c testapp64.res $(PREFIX64)gcc $(CFLAGS) -o testapp64.exe testapp.c testapp64.res # Build with only version resource testapp64-noicon.exe: testapp.c testapp64-noicon.res $(PREFIX64)gcc $(CFLAGS) -o testapp64-noicon.exe testapp.c testapp64-noicon.res # Build with no resource info at all testapp64-nores.exe: testapp.c $(PREFIX64)gcc $(CFLAGS) -o testapp64-nores.exe testapp.c testapp32.res: testapp.rc testapp.ico $(PREFIX32)windres testapp.rc -O coff -o testapp32.res testapp32-noicon.res: testapp-base.rc $(PREFIX32)windres testapp-base.rc -O coff -o testapp32-noicon.res testapp32.exe: testapp.c testapp32.res $(PREFIX32)gcc $(CFLAGS) -o testapp32.exe testapp.c testapp32.res testapp32-noicon.exe: testapp.c testapp32-noicon.res $(PREFIX32)gcc $(CFLAGS) -o testapp32-noicon.exe testapp.c testapp32-noicon.res testapp32-nores.exe: testapp.c $(PREFIX32)gcc $(CFLAGS) -o testapp32-nores.exe testapp.c clean: $(RM) tmp*.bmp testapp.ico testapp*.res *.exe tmp*.ico testapp.rc icoextract-0.1.2/tests/README.md000066400000000000000000000005141377050360300163040ustar00rootroot00000000000000## Tests for icoextract To compile these tests you need MinGW and imagemagick. On Debian/Ubuntu this is `apt install gcc-mingw-w64 imagemagick`. ```bash make python3 test_icoextract.py ``` The icon file (`testapp.png`) is sourced from the public domain [Tango icon theme](http://tango-project.org/) (`internet-web-browser.svg`). icoextract-0.1.2/tests/__init__.py000066400000000000000000000000401377050360300171300ustar00rootroot00000000000000# stub to make tests/ a package icoextract-0.1.2/tests/test_extract.py000077500000000000000000000035531377050360300201210ustar00rootroot00000000000000#!/usr/bin/env python3 import filecmp import os.path import unittest import icoextract class UtilsTestCase(unittest.TestCase): def _test_extract(self, infile, target): # Read/write test files in tests/ folder, regardless of where working directory is tests_dir = os.path.dirname(__file__) inpath = os.path.join(tests_dir, infile) target = os.path.join(tests_dir, target) ie = icoextract.IconExtractor(inpath) outfile = f"tmp-{infile}.ico" outpath = os.path.join(tests_dir, outfile) ie.export_icon(outpath) self.assertTrue(filecmp.cmp(outpath, target), f"{outpath} and {target} should be equal") return ie # App has icon + version resource def test_testapp64(self): ie = self._test_extract("testapp64.exe", "testapp.ico") self.assertEqual(len(ie.list_group_icons()), 1) # App has only version resource def test_testapp64_noicon(self): with self.assertRaises(icoextract.NoIconsAvailableError): self._test_extract("testapp64-noicon.exe", "testapp-noicon.ico") # App has no resource info at all def test_testapp64_nores(self): with self.assertRaises(icoextract.NoIconsAvailableError): self._test_extract("testapp64-nores.exe", "testapp-nores.ico") def test_testapp32(self): ie = self._test_extract("testapp32.exe", "testapp.ico") self.assertEqual(len(ie.list_group_icons()), 1) def test_testapp32_noicon(self): with self.assertRaises(icoextract.NoIconsAvailableError): self._test_extract("testapp32-noicon.exe", "testapp-noicon.ico") def test_testapp32_nores(self): with self.assertRaises(icoextract.NoIconsAvailableError): self._test_extract("testapp32-nores.exe", "testapp-nores.ico") if __name__ == '__main__': unittest.main() icoextract-0.1.2/tests/testapp-base.rc000066400000000000000000000007721377050360300177510ustar00rootroot00000000000000#include 1 VERSIONINFO FILEVERSION 0,1,0,0 PRODUCTVERSION 0,1,0,0 FILEOS VOS_NT FILETYPE VFT_APP { BLOCK "StringFileInfo" { BLOCK "040904b0" { VALUE "CompanyName", "The icoextract authors" VALUE "FileDescription", "icoextract Test Application" VALUE "FileVersion", "0.1.0" VALUE "InternalName", "testapp" VALUE "ProductName", "icoextract" VALUE "OriginalFilename", "testapp.exe" VALUE "ProductVersion", "0.1.0" } } BLOCK "VarFileInfo" { VALUE "Translation", 0x0409, 1252 } } icoextract-0.1.2/tests/testapp-icon.rc000066400000000000000000000000231377050360300177540ustar00rootroot000000000000002 ICON testapp.ico icoextract-0.1.2/tests/testapp.c000066400000000000000000000003251377050360300166510ustar00rootroot00000000000000#include int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, INT nCmdShow) { MessageBoxW(NULL, L"Hello world!", L"Test Application", MB_ICONASTERISK); return 0; } icoextract-0.1.2/tests/testapp.png000066400000000000000000001067251377050360300172260ustar00rootroot00000000000000PNG  IHDR\rfiCCPICC profile(}=H@_SR*"v␡:Yq*BZu0 GbYWWAqquRtZxp܏wwQe5hmfRI1_C#2f%) u_<ܟW-X 30muMOeeY%>'5ď\W<~\rYQ3#V:Mx8j: 9U[j_)K\9 "PA6X~srU1 h]?*N{I$8@hhq'@0Izŏm⺭){0dȦJAB7偁[ 7!0R5wtV?KrbKGD pHYs B(xtIME 8QO#6IDATxw|յǿwfvZu*,nnW0`:@ ) )/텚HWSllnuc쬚"Zwv; HF2d$#HF2d$#HF2d$#HF2d$#HF2d$#HF2d$#HF2d$#HF2d$#HF2d$#HF-tʬYE"j)eP %RB!d!HئN!p)?d#PNN+[V.s3 G˫irr(äBP U[An .WKJHȭʌGK)g 9؏ 5R=#otCkkpbș@N?R B+3dҤ+ d|(z)yRڞ};e |d l6,)y<@*y9+%//lܞ,\D"Z$B{G)5:47LS}3u45 VhBHDw 2in~ jp:SVQDqq.9/ Zӕc9Ko]m?o&LBEE!55e F@U1e (*xw%#h2A dXƎ]u[ǶM; I7IJg!"?[ⷻ2O[>+Պ0WYcɒ3?ISPTD^}!&F4HzN@A&>NK yw+^yo%Dz΍/GMM[Rrѝ}js-1vd5iNWV 6E`SX=]v e_"p;Uӆǥq&AZz F! u-yc^}f{v入`ozki_R 5! @V _33ΨSQYn~;: p1i|_+zg";w>BQ΄k,>,;yTE*~ MJm?w/񰪆o}|y:3pdK]~&!-@vg^9'Pv;Rb;;xY8j: oe+Vkp5!@5F0%b(_ aANIIωӮ@됒V~YJ\jY?]m5}/Y,5Pv7%KdJKk CtAEppe( &v;\O iH=#-&LJ. S$7b_FAR=i$5M㏶cK\n!mV-fњ&t/Z .NpVBRJ6'60{8l1cs9q2}Ή YX8 =L I(p|}6,* =\qjPW`'k%7y'g2iҕv~6ٶ5ןň$HUU*HƦf.7͎(#j)?TAEV;U{n񽸲 Nq%5CEaBb{]MM<#:^@[I'|v\Kۚy3-1㆙la#3r耄ՏD"D!"0Z$iF Av6ij&h6‚vwї((KMZctqfrڂ44!/R/m"+0SJɆ ;xٱ˲MBpwy3V|'n;_u1ӆ(]ζ@!4 [WǮ n~YΜvTwpݍʯ~#.BZOBD4hh D ,R(lFTv΃<}+?䉇BA NU"g3Ӂ*|SDMuq."Mi1oGشG{:hGjx_!c#?NYY9A >͐*N='|E:NC0Y\]UKAII}K ؁Ӯui"jMa[4Mr8TVN_s[;2@F_/?\60i| dcSRJB EQpJo^Bc{بiDK!ۥZ[gLAܹ_0`#Bp#`tr/+O!(?nO)yw).nSPDd&_"ɲQ+o =›^ei Ji|k^r NW-{Ze:X pDbn#@`r@-Z]ߎˮ/t{\L16p8NNl?~Рil߾35(Ӧ]3NQC r,f7v>1E#ז]jkh@MaWE'!'e.TE9+- Wd뭺7qX# i{ &==HJr9\0M$/ʣ>w?L RsVke>#2cƒ @$ݯt.pA"hITVUz~= O^)mŢY.  ;BϚ4D ~k1e r=vz,t1M7NUT0!Ch1̐RK(J+V}g}vUVe̺K)oZkXxV"$H\_=?\.'Ehjh 7?UHGp)~]}=<zq<>z|ދ:_["lСwI|YN]udluS|iD"<b7p0m0a-ߔW{nH׏׭^}_(|dҤ+AtvY8ߚhkniʢ#u1}YI*ni)ƏeZϝMaA۹s9UDX{q8*#?_=k/!7i:sO<ջ0DKZz,-IC6\Se|Tқ !.II2hDhinǗcK; ^^}_S>%2eʵ6)VjfvmCC#:jW~lh1|:>xg%^R[nCqĉ& vCBo,[zu?'1d$W $l?2:#Oqۙ6gާSze6 n5Mݯ&H۲lٖ~.ӧ_;R4j& 凷_A~nK0/-JlY{#cVpe7R^UK d$Ďu+?ƤQ0-ISXal;|^+u?^W^AC3S7iB()sV?`0ˮyo3̢.p&Kcb-*6Ep`vl|3/eQг5{]H~aMPgUmv^^/2;w&30v.% .z71v]~|=&/қF$njG1+eO7o%PZZzlAvA98D?|A6rʊw.4g ((4xٸ\.-g8O>1Aǿq3Qըu6*٩U =ٔT_D%ɹ?enǴ473s<\XuV1my?Ϧ[>f\t9g_p)FÓ"igФ4ɰR7>-opL~1OXUX2]ɟ|o~l۱ٵ֥Ҫr󯿃㡡r )@Q`dӦ cڏOiZyAyUv$DfμnY ϼruYL#$ !55=2{~Gs[s/.p;(8TǡQ#UCח2bēwS=x(7r>_^G*$)#x]lo\u7~6lj{^z'OyF¤|&S̀\,JlۜluYTt t3 sWӮ!;/_OQ57t=>wJt8?kZ3p`%%qz|`HKE۝Ǐeu$UVNYs窏2!1ϗ'1Bpgp3[G6rF{rEqy_bSc#bl& -dteUE. )$ckkߺ^6oL._~uLR>cVo>sΙݥ74pnab)+Ȳ7^uy?O@_~X+shǛkF*+>lՃ<3g“m(qBl#O~ϪWsWo #aaEB?9@hO! N>Ҳ ̃}Ga'# )TC߾R#+sS<@!9s&iX8b;Wn1&f-i)87t.g9KRԳ/pwog߁\| ;MOiar "^R VX rhkoaTTV!loj|Dbz<E.%nK1 wrI1k„GbpąQQ J ۹KK0u-*j̾,RD`a* }a뺬;=(Sf/E硪 sDGcZ=nj lQC!l6[ng z{- gVVN[s~"ԅ׍RY*L! .hAцM{Xn=5pWPZ^s#[RS`OKNGȨSRk/=p8KWi3;q8$^c!P$n~~7.KׇT?9Yfkxr)g1rqz~-Mof7({vSϾʡ8ˑ( |aN3]xBD>_ev6bL[&*qGr%]߿?=pԔx?PcѷܞxC* @ ,t3S$Y_U.+@ZCǃ)|D+<{عòX)3$ʧG4R /\ЩK)y'9Kx?eE􃻘4cQ E/8l},Rhnd^"I^}\eBџ_2c'M]HT»7y.".?'x?gRc 9> m=0tь;_~if$A$aOxeO׎&<<Ճ\D/iZ~afcd{I'v-# R$$}c`ABF XvRxĵ_&O?'.O++۝Ϟ}5if`|Y|~{TA0s0r^~iqv\/J~SA4n()㮻ofS;&O Uu!3//[5rt%&'L|Oٱ8 n~lBːP0 4aB4Ʋay9jW[a)hhNǿ˯sN?؋o,=XWfik2w篙0mmm%hƎ7VmOf\ F9b4Qa;quΚ9ޱTwsOv td!3` 'ABI<!! 1w`t٬}kiH8ƶ0}>vW~G NHM$Y{( 7="H B!ò,?kYK)>}W3pH?MSR͹G1K().bԠRpYy'ׇzCz2Zš _Wt 00Wtg J(l і=QP31N #3/.<F/矏>A8ggȨ\n MRR@j)%---D"ּe񅖙!Xh3},;w[1ϼ2vL?.7\}9ӫ]TdbhT|Ie۶lۺ}{vw{{SMFP >UBQ1B>ƦN;$V|zbl6;abu.# !;+^FҚ5[~|?IQP),W./@ MRΥ*/QPhNmlg c^>~`fq?.Y.UN&t2=7`W^1%6)8zI޳RjRǬPʟ|RBD%Y(t98Pz ."'(rLj3Y`П[& Q~7ؽF^7a,: A@ktgqf>kӧ_(7#KeEgv N "^ ;^kKXca ]Ԋ&S{y.fuؽc+—_@y {t[TT$2nW sB!05Ӣ`PfRC0t x`(e)G|9JJ&eϞ8nal3jdqpNl iWq9"gQ61,b G(zg"tb+PY>u;غ%:tΕȄӯ_$Լ 0a\JxIT%هV9vUkC>V"7IWk#t!lZ[Sz+u" #ʳQ# 8~d!S|\LĚDScw\.kkk#XnwSTditΜ>}EUM;򪪒^]ZR{((pc(`ۓ#0_ڌWOu"N//}w׬+pS'm}ig~wmnn6d@5; H4'A>"ߟ_P?yr  _!Cگw|b Dd<v7u_;ގT9ʋsG`,jO` RVVʕW-DUfe_2]J9S;gaS ,g7qybZz뚌TdQ]gÆMm\KNQ~~]-j}nn.v=9ajl⊟b ltG[ǎaT~!?Ǵ7SOSWWoX^WW;! 1dHξ̟=܍. Uwz.HxPʮ:0>:5_羻~$ƌ_(wOߧ[fowinn&???e}qq!q=bޛg<42k5y(/?Zn>S={ɧAjP;gE%m9L< /ZfW vG'O`KpM XߕDs /ӄZ_ImوC1cd Y#uj=BaץtWnU/[vgǬ?0+:,$ѡO>~^^d΂E|9tiC6YUel޲%,9@EtX/ftDa4/:dѧkSC bTv 1ێ۩UksV~˕l*pʖ5d<DS֚M=ݻyw{8N*R1r yK lp{?NCt:xM79g\Z}ϟqvE0i#)/@Aaaׯw,_v\%(Ц͆hPY )礓syYϝ01#붑j}];G#D/Qc[_<E ̍@}o7?˶;>"Ow|MhiiK/97_QմcMg̸~i^~-1oL?7r o,}ۄU1Esȵ]:ziz&C ?;=bGafcdێ]ЕvluRB99O$43@n 3zcJ~{ٳg/]}#v5 !![tLdчn fl; Mgߖ`ڴ&IIJm﷾u̕PwK}o-O<4'v6CGtIfR5hp!cma."9'@x P HG, :/$ rKN&JttΌ-h Μ98F ٷo?˖kJsy}Dv?Bdɻ#U8@8"iSO_]l;`IQVgQʏR[{pM׃G%ŹǬm6lȦ͟qf6m'?N$UfԢLa_]"8Ay߶}H}}g,6Ni??0>_g.>GaL@OX엓CVVVopd l]o☩D"_̚5ϕ/彵կ KU`j2mΉT LɀjEyQWZ@UO\ Z*oCAEgKKy`=vDUUJʫ9ulr0~hٹ+.:wVV~_~-- yp 1My9`̫m VzTU9W]֟މ]G*֜XA~ϤnJM,Gϣ~gwm̹cXYѭsߝc|>n&@4 |D1|68)יEJ~u]7fT4ޑd"$HBZ}iMxJM'?e&j,rhm|~ϘLAdyص}u Dk*?S$ܹ@~ټkOXg{WYACu`X\I\눠2s!95{U_خ+*:ulDr quЖ幹9,X8!g| O 22vbܹsyo6j '.>kV8cXk +xG4sFⱿJ#MnwHK z}܋1ExSs9iln^i~fH@)I}~1ül}Y֬YC'qʙ%Hn{^@vmMg:ڭTV e",@G&qS0$Bgq3?GCk4~Z;u5&-WBg tttr UV1&#ez`֬'9vN:ok{+s."O_+5__O/Maee'A}rsN<)O_&et$ >2~?&͢5hx.;o/})b'΢Ksɴ0f-H$rd'pN/xG9|[?',J%?` ӾONZݽ'zŸ3T㴱}nP5d4^_>0Oiv#$ J wwvI5⡀H 0L43 > ]XJ枸j^ (MWDf2PW-ߧeZWid6~νr\v²hGvbs$z59bE:>wԔABB S|a2c?3Eaduz{)]Ra^{{=?8x.--9pɳI!x{;hhj5UÌs@nm|:vMN^Qqb}Gjoo4dE>J𨑀N>y1H>,SjIkF0O/K8\%e2$~ǣ/3N΢y1:f;ձ#ڤ7+5##d:ؼ "Fh63tn6l|9^rݿjFGGnq>x/}@<0DN< ^}c9qقE K"Dt{M!A&-nZ#fM@qIsO<)%6a{+Ï8n\xCiWȲ8m,GtZ,J{ {Է`" E0p|Ԝ ) @C{FHOAA.W^rgvaBp)sy 8^&Mի }`릂/5lH)y'cn+$ L)>t )08z V_@SSd$(NGW8i_ S^M*y"vܲ! sE{7$<ar}C|! !%.[l!ۇ ʧL'%oHCX' oSvǢB!"{~q+v4)yb.F v@ 0,/).BzEcTǙ1cu{`(σ7*kX%#D]PD̛?eLSM}}%Ct1: si&CT!a1T_RW:7w>06U0܍&hRO<z|^ [e"ONpr[sf?6y_J|)5 !&XffDCPiARހ)߹!KnK\sbj' a&@ ;O}dc]c;P]{S#%Lt0T 2Nnko:~ ٹKeTt ŕԞnǛT4@ `89>`ͲkR|kuLLlf_LI4!6t+p D2e(6m OP3%@u|3 *m `trƦ^[6%՘o֬kdeWUtm۾ JlC<5M1%Hi .>}vT1!IX3ZFAb/CNr`8$ii)^B, eoIAijj{HV@Ԕ,rvLHӏQO[Cn o$&jhq-1YmWHy1)>ưgVag/_'`G-"=#1R(t챴=@D¦:&b>Xe;0_t^wm۱_aJ[zRNzW_Z)&%m!I$T8%&!-} "6XmzoJ)=WUjDȹe4̟>+2)cG 0`"[>Z%(Rynށ/Ғzvfedij4MZXo j$Yp P"j[@I2LN C\N)~qp" nák5;?N^˶𦵁@w'7K/gDasI" < EH^iX2UQ!KsW#T!(q|ꔿ0wf7$Bw@Km]G$bx(!0}\:ZZRR1,]RwׄYqҥ&' WLM."uOCJ6c@dj eOC%#pQ~u/N!mO>v@B N'pdYW,,ڸqW GG[q`T9~d> sarV\#:쿦IK<Ҩ "%@H~ԁ~uk4 n N(BZ+KRbSUF[Y[kqx5H PGtY*K/CCckhKc{.eO~5WԐ[%0Ή?IͬtIE4ڃZBL->)D@!(!NAz}MAS>7[ߏlٮ .PL6v=Q۾NeU!94#Bڽ܂nyMPHubqBry(,i DQɔ iҍT^Ң,EZwnx o_1O"zsS3m>e9/{76gatv! %ꧥ= 0QRn QUŸ*ngj;{2P2WWߟhEΔA6B!녈_OČ@[DP[rL9#t̓yÒn?ʛI3@dr:wG黫mfOɛw-#a3N,5(5C-x4B7pXOmRUUihdLM1-g|Koko.scAt_jEH ,<4\GJlagSRϛm<4"+os҂~"}mC -E[s-r"@08h>>v!ۄ4hb[_&HgPt@qg1ˮ@7N <eR3'D?"!}@@U6%^ Sb!ʠc1̰,g .țZ8{ra]{ݟE{(GLB!9wOɑL*P%kk%[ҵiwݵ&׶dhddQ$i8pr 3ЍF@*@aAuP͢߁-D6ȲGMRRfsB 4Eΰ{4Eq0_g3xߍLljE$2D@1„ )2qQ$_IBtN- Dnk4SR5>y^|ہS#1~|MEWD$A'@_~!$RVݝ^ esY:=f;zw܃2 Bx5<o{D/ʊк#ݰ!ʏosIqaP%,FD5!,#)iKd1Xrʾ)qt5֕_ 9TXׅc-n7iv 'Y?b`pF 5Qg?0  '@4 +hk{?4urm9|T{D&>oN`8l@6'$A.Q>)h2o\0 $g6ɘn)1Ha(,(O 5IjeOO~=#pa``xoLFhx2koc@HgB \~`- UX4$>of;k1bqJ{3onc۽swoi},z"+$ʡm(RϿX+>ZzrzSGs8Lbf!.$%_f6}X9XLMF ‚b@_u^JJ +eF1$:D|43#[U㰿 y]t?nv;M><ܜXnblЉⵯDR^A8,/(4 _}ODwW~؁B)i5վ?3l8(ȮcP8Hg4aB-Z4t*gǺZ+BpI87zEac%?;A2]ħ ,&>JW']x!afۼuysӂPp^e'Ѻ H&{_d+d^T9!y^nj_k똞 ӹ ^%v8 &,y`YfTz?3_tz-F?Q'Z_ G?A$/JR=_z0p}qAIp^pqLDz 48%i|^Lsr$<$^H@e(*$={uсP8ۣطgfWGFo=Ζ9p nĢq5fQS `EL'@V, ìV%s_ouxm`Wrm߬q&f`|%ڈI[6J-"ZspxaoJmm|^|cSY̅3Әf Dן 7mbM|*pЪ{-G 6wO{InA{k&&Oփ]|GOx$(nCسsۊ@-͈s oۧ~X`bvw;z%)ߞ*^ {=lOgj>qi^?b,ی:}-F s~xhyA4Dzq bu!BB00㧇/+?|gV;u gxٕ3CܼrM__{'ڛVWS+x XM0;ij)+ Q/;/4O#Jϕ$ٗ+f~ӦGl>KKz"mR /ŀЍwuX-Ĉu RT>$BxE#^?v?O}9<̳hَſNoD$C0re>x0Y%l5cy8gLp;튾z\@Y? DΏ&X0'לV֓a˲@xZ\e5&ըCŎ oc570xݲߙޘ4yֆN-._@3y~|No(O»KχS,H3*MS5c&,mo~NgfέvL2 5 ut+lV坝  BoX ,O3F48VхD_DFEN7'@$?@d&Pr Qj]{\YBqX%=+h8B֯pؓoLJJXI2 mx$%鸣cu~)TJxAe4Ha L%Vn[Ձ%JR_$Zv)V#/lowKoÏav6q˽”Ex]Q!󳋴[&a25[ț8)=64x[|Q ױ*ʫJ:\v9bF"thJR52Iyr6TZ|$<î $-|1Xy 'dl20G-?S诹~ q\zl0E:&JnBm`:Vlֿe Cc;J|te ʽ7%Az&+ZM `w&8K*& y$V@4@ \& OcC@5;OڡcPI<㯿}0X\n.?uYe?H"KҴLTʖdXi|B|6'\>{C+r]ö6Epy,t,M *zi2K!D(aJ/`6Yj zW1rg ٤U^[0 ҟf?yz{+j9\ /ױc70sy_V mm=Ќ=]v,(-//) Ӌy@L@&jBФSN&x, aUWU_GsJץh`5`1E p?I1XH_Pȷeݹkf%u-PCsB0IDe٭f<_z{:`PJ! %2T\+ /=&ivj* C 0BHUேWy>f_RpMynh\qx]H01O] sIb@YLޖfƣ;@ :`*yZT@a!Lܸ5bÄI Y2P`a-N8p5o4y p0$0]C3g@ilxmٴZXhR^jt5Z^W^#hnng3 ia$Wg*id(3Q &<"ގ;cxz}Mr_˴^&"=Ij!^z)M񋧮o(MxR;mӱXc[ JI@G AJ0$ikÄO,^(~ W[䔰Zf/BTى_O5Pid* ehOͺՁiy6||w3V.t5:_Tc2A J=o',5- 0ĉsU~5@%__^Z0u"?0T._).CE.f48M(7 Ju#-hv%R[ib^XC%*B/˫T_NP ܝ;)4E E!B0=6G? ˲upȓ@ 8y)ꒃ!hva7snZM^'n&n~>W~(}Dʍk/ u zh6dNgcͮB;qOݙI cS~.-Z"{Uv3Wyh~OQN a0M/[5UXnԱP~-_d txD S HA2{TnnX+d9N no5rhvN)ZFgn$YXnS9*ٹ<%kpm[ȁ4%X ˕ZΧ_OI8,[L4ST0 y%S7O{LNP} ›Ez㬷f1矇aH9B! dYJՄ, _M/)iKy+P\1S@jxh(\&4̉ߺ!B#tgO%B{*}{fP"P۴FV>??G0OOJʃAyTbHǨ2L:KsJ"oM~Au }fO%PP,=2<\u(G(qIz`u7R~e(M{0 !HmѣcPwf@5W~6`Kwk4Q EQ\8{]_>jK^9v-~;w;`E`-k?Uι9|_3hG/Sy ZhsjIfGYӫVNAɗ7A+BK  ԝYģ?d=:A& nOHטIJ8z=,-ifKW O E)N zT@H!ȗsjM?::lk5ijQτ!Br"KY,Ir< y+5#{xt^'S4l':)_99`&ĤLOPr@$,&/7ZX~~Ѿp7uA8'H4%Ha`hЦְ칂C0!y_ }ƪwk%Bh ]m^7v012ZvS4TXm|׎"H$9AGxigxǥ:$~>Bs?P/PM}uLZSPbXlj[W?DoWsk._Z )O˒Q:>/覣۶4s<\.J/ޗs I4aCpҾ9Fs3Ms#8 A'nA)ģIZw]Lдp8WD쀯>Lx?zݪc%7D2N& 矧w,\XWԞ&A@<_| 5f5+ !H'L&ۚ##GKBpWn(0ۚ ryPYYV<#fzдpH#8r4Wj>@M6PT{1cA!x"}ӳ?T$ґIC%CwIgh_9]Z^0=L_h̀nIX *  솎edE?_BQ3ϝ9g%7/CZ? 7'f-'Y6O. 2ZU_"[/HDjvv'Oyh_Pϟww/Fpm736vLZd%f:J&CvQ^-Fe&NlmRTҦs18_ @_7y>$O`Z}$Jv6Bx\WT]Mx0!ȠsiV 8^֞vȋ}4I?]ri,e`gf.OdYvLS_XR I4hDnH{p[:-BBoxڷ,! ^4XF}HP @zvL]BV3;010KsAׁ[KK Fea.s*2 :K{halB0X-hly 6bɬ8@ 2YnU]f hO~ p;>Ez4HuW(Ɂ!=o 'Xuhrj>[H. <ƃLR[Ë?><7&i>BɘWւ (h<L G_=ɀa<F-SRa9Wc d2/Dc|bPKTī %* Cd$ C4p`2)̆[x^xp,;{~%?Mtl_˽n _WN :ҲɲR9r6v^/Eӌ0bL F"N0؊B0rg}p/k|~ٹ< &;C (;@y9CjIwH}))Yn1u0rk׮_V?;qˀy/?r^&…<d5&/?}a)NпgK乷H<4́`1+D_x+囘Oakن.n$vq<_6+W"u0gA<\%!thuqu3r^x̰:FMmhor_ .A7d2d~ֿ81֦ @$C m晅'j >fkМg<iw)Ct)ةiLAEK_F YМ|(\a?ⱻøC0Jsղ-[(l.|kcY>=}aBɯ7[]3 As`}BƓ#5sfP@0>VJPx#^AO~p ؾ4 '@ZAX5!Q&sڬdآ8/bhd =h=AKtBzvzG~wiEj`DssCMM:aGcK7lw:|6bii(T:ÛgVj 0:[@)ȥ@d;ʢM_APHB|!#2\;j4n6Sr3 ܂ Â{􁣀?{W$(~><6޿ jI"M12 :/ $t:q9Lcˮ`Yܳ ~xSA'?WCCo_WM#ЭA_1ߚp:ۂFABΣoGKu5IdyNBǁNc],Cx{?u+lv/ )Jj WӜ{tS" TS`:cyՖHLeqǾCe![t&f _Y&&%2k7}-v޴C዁ !aXh!-ƋP-\1TK !?0.]F}_;C6 i"t jD] >R_xx`[qYYd {%,x=wgN#<+g1=:G__gV5 X- WD33|[}zi  w[oXa1r\m wz@CQ>h! Pyg䪊7p}h/c]:1.?]x/dIiO_7P ȫ|]?аO3|c_a1цR@j3- J?~hm]rNK!4?[(qȉC.eZ@ݧ9HX(Uq8^o}гi ': \:+WXJX J`*yկ#$'=͛8&s,1zs ][a0!#@:ljuSxyPp1g{ ӪBS+Iy)z9Yr>*iv)@ r,XQOSg/C[6=lZܓ|pp?OI`%Tnzn.!ssx;8)RXW$,à`o }ouoMfS HD ءL DT NA=2 ADJ|8'?I%a& #y?lr֭_< =`OO$1qQtоb S'ofܠL&l6dYҀt~E8VWFW"j6!z.p(PPhswv vsE·qU-*ޤDի?F$2 s T Vx> 3!Ż`d>Q\Fk nC B,l6, 2 b8!8,V"'QPV.")=H.]3qXw{:tG_t7`02rm|(4ˁ KW!uC02enXVgׂ?'o Fjt:XV;u. uP+HuT(]*iЫ!wU |.fSAsA\8>ԂJ881qꙑ秐@V`݄2:ht?ew?o|vvoh0;{s¼W03-r:I@:ÁTPMWz]V7*~=n^9իH/ݸ=Wsx!͡_yſsTvvv<fֆ iH462 ENP]h21l@O |Z4YS NB'2I@7DSpAʆ== P 4y߉M]MES /RMkYd~68l""e$0Z0eǦљv{K+ǙF,:8"L!O`tl Lm>;z[]s 2ٜh$r`+:PL gB |߉V/Lx5`z̏ 13WͬL"*J |_j@ ځa"板DzMsؼz<վLL"5)$ckM "eLY Wfs@Cz_@yV4hߝD}^2U#𯆆9jjc]kbS0jufMnlӉ3dZm39L" a:D+h +˃&Áw;,hip A/' 0bA%'b4: ZƺPc]+mAmkں gj+O66`hi2eI aB 9QY_E HD?exV4zhmphڂ?ȵ z}fsSS_<{/W37`Vغjkl-6 nC߶\%.rsQL""KR 5Od^VL:Pzl ,FG9ͦZpR]1֞~h}X)Z \iGn 39cXc1/85^5ӱpMp;88,빢'H~<LܚA:p[px[L.F4PkmMKHu-dlM>p4="+ m شk:z[%Z`'tM&&&KO\zRHAN Z@.<&nM}+Fgώ3%zspVP/ Z6v{!D^vtn@KGXrWbXOxJb2k*%R[ږW;7viGϷeڸW;kLaYXXVX&XlVFz zMz / _JMH3Ȥ2HH'3HHGGضDQgfG )Jh!3V kKfOydٖ}O>ŻL,dFb. %}0\uyv@E_5dӗh\%.Wo5o1]Fg]!|:Fg., Jzjgkk|,kt9dwJ\kT*r'Ocl" zZ&zYMpa;|EЗ\&jڢNbT(JŤS4ʣzh+A$^W-y FD%Y (_xz!֤B՘ OZAϢ2\'Ƞ ԛjsʛzkJ}5U$T J@ @]K" kUݿ @?ʯFZZJ@M0wZ/F/Wc3j́Z ,GݼV -PH@du h*5 jA cjhk5!Z6 *%ށaZ Hzs _PCp%-ZV*_䯅Pi$@K^@}5ȻAk[+X_UeZJoUDXNxVjT_.(X\Cqo5RW4 \_j+6r{^2kW+ۿ0`=C}coQQMk zP:P+m`cm kg  k,!TWBJjT[Pk7nP_M(p9 ɾAe9 \EA6ArF2{k j;6`c|kcm6Xkcm% 2{}IENDB`