From 5ec145e0544bf81d56ab620a80dcabe3507c42b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com> Date: Sun, 19 Jun 2022 15:21:41 +0100 Subject: [PATCH] feat: initial WASM version --- README.md | 9 +- examples/web/chip_ahoyto.d.ts | 57 +++++ examples/web/chip_ahoyto.js | 312 ++++++++++++++++++++++++++ examples/web/chip_ahoyto_bg.wasm | Bin 0 -> 37577 bytes examples/web/chip_ahoyto_bg.wasm.d.ts | 13 ++ examples/web/index.html | 9 + examples/web/index.js | 14 ++ src/chip8_neo.rs | 185 ++++++++------- 8 files changed, 515 insertions(+), 84 deletions(-) create mode 100644 examples/web/chip_ahoyto.d.ts create mode 100644 examples/web/chip_ahoyto.js create mode 100644 examples/web/chip_ahoyto_bg.wasm create mode 100644 examples/web/chip_ahoyto_bg.wasm.d.ts create mode 100644 examples/web/index.html create mode 100644 examples/web/index.js diff --git a/README.md b/README.md index ed27bc0..3c2257f 100644 --- a/README.md +++ b/README.md @@ -25,13 +25,20 @@ The work of this emulator was inspired/started by [jc-chip8](https://github.com/ ## Build -### WASM +### WASM for Node.js ```bash cargo install wasm-pack wasm-pack build --release --target=nodejs -- --features wasm ``` +### WASM for Web + +```bash +cargo install wasm-pack +wasm-pack build --release --target=web -- --features wasm +``` + ## Reason And... yes this is the real inspiration behind the emulator's name: diff --git a/examples/web/chip_ahoyto.d.ts b/examples/web/chip_ahoyto.d.ts new file mode 100644 index 0000000..967df31 --- /dev/null +++ b/examples/web/chip_ahoyto.d.ts @@ -0,0 +1,57 @@ +/* tslint:disable */ +/* eslint-disable */ +/** +*/ +export class Chip8Classic { + free(): void; +/** +*/ + constructor(); +} +/** +*/ +export class Chip8Neo { + free(): void; +/** +*/ + constructor(); +/** +* @param {Uint8Array} rom +*/ + load_rom_ws(rom: Uint8Array): void; +/** +*/ + reset_ws(): void; +/** +*/ + reset_hard_ws(): void; +/** +*/ + clock_ws(): void; +} + +export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +export interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly __wbg_chip8neo_free: (a: number) => void; + readonly chip8neo_new: () => number; + readonly chip8neo_load_rom_ws: (a: number, b: number, c: number) => void; + readonly chip8neo_reset_ws: (a: number) => void; + readonly chip8neo_reset_hard_ws: (a: number) => void; + readonly chip8neo_clock_ws: (a: number) => void; + readonly __wbg_chip8classic_free: (a: number) => void; + readonly chip8classic_new: () => number; + readonly __wbindgen_malloc: (a: number) => number; + readonly __wbindgen_exn_store: (a: number) => void; +} + +/** +* If `module_or_path` is {RequestInfo} or {URL}, makes a request and +* for everything else, calls `WebAssembly.instantiate` directly. +* +* @param {InitInput | Promise<InitInput>} module_or_path +* +* @returns {Promise<InitOutput>} +*/ +export default function init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>; diff --git a/examples/web/chip_ahoyto.js b/examples/web/chip_ahoyto.js new file mode 100644 index 0000000..792feab --- /dev/null +++ b/examples/web/chip_ahoyto.js @@ -0,0 +1,312 @@ + +let wasm; + +const heap = new Array(32).fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +let heap_next = heap.length; + +function dropObject(idx) { + if (idx < 36) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachegetUint8Memory0 = null; +function getUint8Memory0() { + if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) { + cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachegetUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + +let WASM_VECTOR_LEN = 0; + +function passArray8ToWasm0(arg, malloc) { + const ptr = malloc(arg.length * 1); + getUint8Memory0().set(arg, ptr / 1); + WASM_VECTOR_LEN = arg.length; + return ptr; +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } +} + +function getArrayU8FromWasm0(ptr, len) { + return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); +} +/** +*/ +export class Chip8Classic { + + static __wrap(ptr) { + const obj = Object.create(Chip8Classic.prototype); + obj.ptr = ptr; + + return obj; + } + + __destroy_into_raw() { + const ptr = this.ptr; + this.ptr = 0; + + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_chip8classic_free(ptr); + } + /** + */ + constructor() { + const ret = wasm.chip8classic_new(); + return Chip8Classic.__wrap(ret); + } +} +/** +*/ +export class Chip8Neo { + + static __wrap(ptr) { + const obj = Object.create(Chip8Neo.prototype); + obj.ptr = ptr; + + return obj; + } + + __destroy_into_raw() { + const ptr = this.ptr; + this.ptr = 0; + + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_chip8neo_free(ptr); + } + /** + */ + constructor() { + const ret = wasm.chip8neo_new(); + return Chip8Neo.__wrap(ret); + } + /** + * @param {Uint8Array} rom + */ + load_rom_ws(rom) { + const ptr0 = passArray8ToWasm0(rom, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + wasm.chip8neo_load_rom_ws(this.ptr, ptr0, len0); + } + /** + */ + reset_ws() { + wasm.chip8neo_reset_ws(this.ptr); + } + /** + */ + reset_hard_ws() { + wasm.chip8neo_reset_hard_ws(this.ptr); + } + /** + */ + clock_ws() { + wasm.chip8neo_clock_ws(this.ptr); + } +} + +async function load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } + } +} + +async function init(input) { + if (typeof input === 'undefined') { + input = new URL('chip_ahoyto_bg.wasm', import.meta.url); + } + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbg_process_e56fd54cf6319b6c = function(arg0) { + const ret = getObject(arg0).process; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_is_object = function(arg0) { + const val = getObject(arg0); + const ret = typeof(val) === 'object' && val !== null; + return ret; + }; + imports.wbg.__wbg_versions_77e21455908dad33 = function(arg0) { + const ret = getObject(arg0).versions; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); + }; + imports.wbg.__wbg_node_0dd25d832e4785d5 = function(arg0) { + const ret = getObject(arg0).node; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_is_string = function(arg0) { + const ret = typeof(getObject(arg0)) === 'string'; + return ret; + }; + imports.wbg.__wbg_static_accessor_NODE_MODULE_26b231378c1be7dd = function() { + const ret = module; + return addHeapObject(ret); + }; + imports.wbg.__wbg_require_0db1598d9ccecb30 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = getObject(arg0).require(getStringFromWasm0(arg1, arg2)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_crypto_b95d7173266618a9 = function(arg0) { + const ret = getObject(arg0).crypto; + return addHeapObject(ret); + }; + imports.wbg.__wbg_msCrypto_5a86d77a66230f81 = function(arg0) { + const ret = getObject(arg0).msCrypto; + return addHeapObject(ret); + }; + imports.wbg.__wbg_getRandomValues_b14734aa289bc356 = function() { return handleError(function (arg0, arg1) { + getObject(arg0).getRandomValues(getObject(arg1)); + }, arguments) }; + imports.wbg.__wbg_randomFillSync_91e2b39becca6147 = function() { return handleError(function (arg0, arg1, arg2) { + getObject(arg0).randomFillSync(getArrayU8FromWasm0(arg1, arg2)); + }, arguments) }; + imports.wbg.__wbg_newnoargs_e23b458e372830de = function(arg0, arg1) { + const ret = new Function(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_call_ae78342adc33730a = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbindgen_object_clone_ref = function(arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_self_99737b4dcdf6f0d8 = function() { return handleError(function () { + const ret = self.self; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_window_9b61fbbf3564c4fb = function() { return handleError(function () { + const ret = window.window; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_globalThis_8e275ef40caea3a3 = function() { return handleError(function () { + const ret = globalThis.globalThis; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_global_5de1e0f82bddcd27 = function() { return handleError(function () { + const ret = global.global; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbindgen_is_undefined = function(arg0) { + const ret = getObject(arg0) === undefined; + return ret; + }; + imports.wbg.__wbg_buffer_7af23f65f6c64548 = function(arg0) { + const ret = getObject(arg0).buffer; + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_cc9018bd6f283b6f = function(arg0) { + const ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_set_f25e869e4565d2a2 = function(arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0); + }; + imports.wbg.__wbg_length_0acb1cf9bbaf8519 = function(arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_newwithlength_8f0657faca9f1422 = function(arg0) { + const ret = new Uint8Array(arg0 >>> 0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_subarray_da527dbd24eafb6b = function(arg0, arg1, arg2) { + const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + imports.wbg.__wbindgen_memory = function() { + const ret = wasm.memory; + return addHeapObject(ret); + }; + + if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) { + input = fetch(input); + } + + + + const { instance, module } = await load(await input, imports); + + wasm = instance.exports; + init.__wbindgen_wasm_module = module; + + return wasm; +} + +export default init; + diff --git a/examples/web/chip_ahoyto_bg.wasm b/examples/web/chip_ahoyto_bg.wasm new file mode 100644 index 0000000000000000000000000000000000000000..e5178df226f9cd406c380e432a15724c30a377ba GIT binary patch literal 37577 zcmZQbEY4+QU|?XJ$S9h?RA0|npTGzrnCj~p5}50u0uUxc0s~`x0!w{8NRAam)iXf2 zjP(f&3?Pkl2@r9{`UEzx0+3ly76VAG7OJ-fM6=X`xh(Zy9y>$>NC$Hr$YQ3~>@v*d zN$GO&@#RVB@dZWs$*IM~@u{X}X(^^A$!TWBhL%ZY$qbAjMIs<YnRzMcsd@34#qs$` zS*gh-5Gi?(RC;__YEf}!eqM3Bxp}IQp^2%frGZ6CVv4abM3ods71RKT?)a3V{DSzR z)HDVbkPXrx1?lm5`6;RK1}P~<rYRQ2MyV#|7N#kt5F^msP+U@!nU@ZcGJ<F>E=erO zOpZ@X2KgkvDBjQC#Wmj7-zC(?HQvZ9$;jBy*xVx7Fe%kMC53?vWGmG3MX80QnMELr zlMGEQEmAC#lT(wEj13r=L5gG{rY9Fw7L?@2Cs~@Nm>ZfK8=0Az8CoP-LR<h1h1_Ci zh$_=W3$qk+^F%W<BV&U!3qy!1HHb;+sU<;)c`5n1VTn1Vsm1Y0h9>65CW(ng7M4lL z#-?TrOdxAjA&QH@D%~=3a)K-KlH)B6Q;m|0Et67{lM~GhP0SfsL24BsYV%Ud^YRmm z(u?C$jf|5_Of6E4&5bOK4N_7W7(mLPft;L}lM|nqYHnd{Vw9MYY;0_9Y><eg0yUnJ zbMo_2!4U;c3Q#kOQ*+YdEiKKB&67-0l2g*m(hO28Ab|=Euky^il>G8|%Oo?yw4|go zV^cGeWRtWch#EzRgVS^JlM-`6GBS(fEmDolO;giM43ZO56O9v%A?l!JLDa>YrlcCC z8l+hmC8eY!rx=+-)QE%3g1R}gIKDJ5B{eNGFEs@c=1>!oO4HI(i{i}_(~OMM%uLhF zlFdv^O)Ma4z^OAmJ}<RAJ~`Raz|bNo#VpOp!Z^t+4Mjn5YDs*Wk!h-hnPsYpshMet zQKAtf0-(0!q~@iUWW*aJCMOvtr&%T?C8k-J8bZ^E3do&cTgo#_GN4K=(hST@&C?Q- z6D`vWO^l2n_Cb?VacNRwQBh)Ld`hCJk$Fl|ijhfbVp@_}5+q>?fDD9syd<M2zZ{Zq z1d)VuQ*-l+DjC?ACo@iDWMpAyU}0wF<z!}NVqsul73N@NWMUR!WMg1rVPgW7QyfgJ zOiWB{tZWPn3@j|HtbD8-j7$tnOiZi{tgH-73=GViZ0t;oOiV0XOpGiH3@q&I3=B*x zAho<a9Q*=I?97aeObnc$LXU-kiIIVkk&%&!k%57kiHi%&W@2JuVrF1qWaQ@NW?^Av zEMPP-VP$4y5M<+ItY>s=XgI*Y&HjLqn+@V}CI(@UZ_?wFGcpS-@>28T(~4448S;4G z{Jhk11~(BnHzz+aCB7&>H@>`>!Cw$AT$BomyYgZNZ!t8njKrc8kYX={;^dtC<ZO^w zHaKZPEl<u#EH2JWhB`n1Yz#~c<Omm(a8Jz1$xmj;LN2sYEArxtOY)0S8M+uby%`xC z8M%$!*D%`Hu3;?dWGr06m|NvnT~*n@SYKUTQ@e&SXBuP78pil;#^|z@jH?*SEuB+) z82cE5HK#JRGe)doT+OIz9XFlPDwb>V3&x3z+Z34i>ly2m85{+&mAF+Hm>ifK6c`-8 zGWxSPI5srYIC4002(&Odih!vsM*#?(?O0yuSevE9<j4ROa%3vWR$^3ORABO!R$%bu zWl~^pWXS@nR%CHpu;BoM1`~rTFQX%a0!y|cGsqsMdJxC4fytAXLCR5HfyMFv|NrdX zybMwbERJjnERGyy=}HXQWsZ!63Ji{nCE1R4S=>r&j!Y$vYc?HV$kJe9P-0+lP+(DD z6KG;AQDk>yP-J8Vdq7||qY{$>izhFG0+Rwuwi1&9vp+9`0)rx>0+SLmFM}h4A~O#& zw*rHs2-p}V1y%(n1qKCnN7*t*mclGY0kANGyEI5bT9H+O-4SGi0s}7tw*o7Rg957} z0}m6o0wXUIH^^E?Mg_JKfi^~%PYab8TzQ!l7z8FVW}rIQm6w^D*+GFpfl+}0<V;2d z7JmgskRw?Ym^}-Xn4}dM6<8IR6_}(IK?XE3DuFF!bOl+pW-~Y-l^7IQ{2dt-m=!?5 z=`YR8sK5vk6IjZq$fm#`V9w;AzzmHUe-@B!ECQ1m71$J*1)3QZm>nCKK%yY78ORsR zj?86BjE>ADj!QNiV8~KrWvX|a1Bo&vCPzjE#u6pQLXf95m^gSCxxsRvs4g;NVo+e^ z20NI|jEO^mQGv~jiARA=ffYhAnt?SafLzLwB~Zt$z#y=kQHj}=myw5w+fe|LNFa&I zu{ukE!CM-dWWWi;fvFxugY5&wHlqRy$fXQrN=%N-g|48ucZ7zBFE6_S6E8gcLBY?C zCHNTyW`n|7ky(LJ02Hz;4xqSz2!c|H0+Rx>Cnyd;A<GDg15h-9RPr)#b0Y$o6%@$u zAok>C1w{lXh*`lw40Dwdqbo0q0wc)LNWshQxCIi8ifnMN!GqbAmq~#g6wb`tOb!Z6 zj`hXh^q-}~?8scA#NfCF6holEV0GjGCsI&O14o+@gFA}@C>evoo<V^L682yl6c`=r z6qw5k&6q&RR{>;{K%D}sz;Z@KW*&BKCI?4G1qMg?LL~-JA_kdJQV2@Apm0X9lo@I% zNI8<Fj0y}8OQ8`CwiHZgFflmRDKJ5;W>x^FLlCu;QHfQ7$&tAPl=PVdni=6?2FlY6 zpde#Fq)4P7LrR;BAZM2-F@Vx0ILu&alR<$IoHp6H6&V!RL5Y$>fdNE;(k45|scaw@ zb8v%W0+d=Az}ABTjX{AK5-x0L37s93(4mIG6FSr|kiRhuV>V-A;9=y(Nd3Hw+@NHQ zjKQ{pVwFLG#goOsaRVfQfbz6AFOvc{v>aAo$wDuR9UB^01X>sc(iFHs`Z+*FtRgcH zC&V=xObm*kf{i6xi4j^JfQ$#FKSu^LCWx;=iXbeo7%2J$>J*qDp}@%vDnda83q*+m zBS;&_08j`rfC@5DnZg8#S*RusZjfs@6nPx?Y(BsMVzVf5f!P{N3|UHGBMxjnz~BfX zK+XmihYF0|(q>HHI>>PcBmh_(9FJ^1z@W$g&Zbis6*$~LRf`4_2e?!O6(TnvDwP;q z6*v?aJd1ciDO!O+U@jx5#21*ysKDb6Vkt3#EMkS3ti<HYQm??Oz^K8*lBK}p&FaX& z&CAT~2r>>#fK)RmaDWU2J4=B>fh$Xq5$YaL<S{#d3ULi4aK2-JxCvCQfl4_f+ue8> zxnZ^{a>Dh4e5Ap|pup)1Dxd|XFe-3*^D=QOKn-F5)hi6#3Y-cI0#g{3*r4&Q#Gmb$ znWe-CDn=Q=Wu*c`wgO|85`zM)Dpugk@|0HM1w|j90<Qu;B<>&;xgs;v97R@Wa!_Of zyA9+~keQ4SGa0j$7zLIxDzSqi8<ZFoxEwjMK&7JsYgU#5V^+2jD0M5aD=<5PvKk9C zfhw}X%x8j`&j?Z^uoT=jP+)cB$j-_#V`6c<0*d~Ih6Yet0K0NBBxp667!(B*_#8R1 z9M5b%z@W**V9vy#z^5Q!&cxt&0hF3}KyEq#VsU|3p!A`@0b+sFEAW{yffI`(3&`J~ za*ExIiA8}8RCYVEWO;)sa83nYH(quHP6b{C&TJ(Ha2PLT6sS;OfYywlKsV#z05uSp z%aoAHAtoH94-=@wMlXGsK&20p0w<_uWB^e-3Y?%421GG{N@sR(Y%_rBd9V)@7(lVl zqQC_TAq5Ule6WJ!Ly-wwC@Dc=M-h}PnG{&Fvy|8rz}{q605K*rf*S;k*`U<GqQI7w zt;nLl0FGWrA_bRrpftze#>)b>%8|Lyk<p!(71ZhjwMJN>0l*+&4lcPIdEj*nq_$xM z)iI0^Uoe5n0(gS}skmo#WG;i2_$&&nSs+&^u!5q4frk~66%^nJ3s%%Auw*HLN+bn# zP-CwM9A}^;49d-5HJ}<c8&Y(D3I~|CK<zwGN(Jj>P+){4M{t=nnGsfqu|e`RIFP~Z zKCtH@;S4hfYzZiExUvL-c$gvOC&ZngHU_MOMs_Et;A4Y0l9^k9U4cb`L7+l`1saCz z3M>L<3J7^f+XYdlk&~9#A!!L*>###&fgMy1bHMCj;&!|P%31&AK{*0{e%Y|$00X$i z2U5BN!~r!fKpYSampK5E*|OmPD1;qBG`I}{Qg#9)vj-{zqQTN2nF}D9BTyL-?Ff<v z$=m?RoPo-KXh)DVNag`Z<_c5>L_30{K{78uGIyXdAleZm4U+i)l6eA^0nv^iX^_kt zWSJiz4oCzf^97mHu<-zcBS-`!^9LjY<8**zKq4TS7G%x@5C<dzlIcO_%m8sfA|RP5 z$eaZr4oCzfGY6To0>lA{fMk{+b2flDAQ6zv4iIO}Mo27xXh)dL0g%iVs0@g91WAMR zoB+w}fy#hrN02l~<^o9O2vi0{JA$M^GB-dnXP`15+7ToTl6e4<xdN2|(T*T#kjx8^ z%pIr<h;{@?gJeE{WS&4}K(r%B8YJ@rB=ZI;1EL*4(jb|JO`s@&%6vgq)&Y|F1C;^M zjv#4}o(UkCmQ9e*0nv^iX^_kekW3F$21Gl8q(L$ZKr&OHG9cO!Bn^^T0g{;ml>yO? zAZd`y5@Z=@m5Qax1D7eVDivN$D=~lyDp0)%E^8oFDx(4uxJqTn%2r|p^{1E>SRrK? ztV(5qRjIHRoFb?@1u9QLjV}cztW_$g^}r0a2GWjU@#SRzb15iBxfQq_1+r8a6qvbp zi!qASD>FDcWGOL0Mh!p>A_P`o&}URo;&l8k@5|zV5GIeM05S(u?=UEEIx=M`aac1d zfEuX^oLNeYjw)HY3=B%#jvOTljE<^c4oi^|qcx+P0;A)9`9ei51r|sCLPb#Lk)=$L zRe>ACV^-jHWGQpxD0XDb=HusOVP)fC=U`-#m64W`lwjs|Y)}BB#sdrj;SAggERGB6 zvOv8#Sp`N%J~v)D9vM)(p39n1fk&NNfzgrQjh72l`DF9)2?+}D^YQX<fdRKLRGku! zDww6fpv$ntAPb~Xg<FBik->_=LV?NAB1<4eiIG<t)LF=KTw;)=z@*Dyp}<mT#lXO$ z406Dux-32x&`24mN8zZFrO1dqG!<AJE3^3gxfmHaL4#)y%)!XW4rVcdNie|%CP4%% zh`@`P!C~-}!Ht(0<Y<#DM{v81$x#Q+I?)KqeHsc(ASo3ECdUI{0R@PFjRF(cmu5@` zV3u5#8579g3R#XXK!Kde016!jeMTjC2<S2}WGOK?{{PRc046=9tr?YgG(q-rWPw`V z`ix3SED9{3CTNxtW0pXY0+R+4j}o&2qdTu6h|QtE01iq<fhCMmJQ^UK0yuOsg4ByB zF@tnMG%;x~2`E5xs&j+Vvyu`hJ$dsgg0fwfqe>RsrAo}Y3=ED>81k$bz%39^iwC6E zjTht_7O+~d60ST)t^$Z6CWxYc@}8jPKd57;lBEDq1X2M?RV<(+c%uoL1VLRbW^mE~ zS;m?J3Jh?PVo-n!TQO`i;86jGB(mUE15grT&}BGo0O|&U#(qF$$qR(hj9HG`4M2Iv z73@8TfeMVE#HR!@4l2N)%dph|B&Y}qOQKT2ie^x<Ae;&|G#_9PfTRM5WuRQm=*SQ5 z9YI1I>Ox1x5(N%$22ud!JORf8Ag!QWiWCw`*t8;a%0qNsfa?UM6D3e%RhI!&y0U=6 z0@N;d+-#5q%A*VljIO-0jtq_s3XrtGkqt_FjtvUl(uzEeY$b}E3e1l7g^Iii%#Q3? zAQqnjvm+ar#jn8Z$O>i&C@_PDMePd}1wo@QU@;*DW=AG4OBkG#L3%|L7!)`_=|+)D zfdgbGi13tlY)Hsb<W^u*WM+0y;CAJeR^W6zz+b4yqQIrYz@WtHSnmT#O$xk4S&mB- zvXt0B!N~5&=mrvZWVB+~Y{0F+1{%&RwqgJc200!851%k7uqcQStw#Yg;sVklOtcmO zm=+<TwQ#_+2okM@1*S!SXe~@IE&N1l0S|_Pyw3;K!U76qR<uxN0afIXQ08`I1jQSt zqeHeLrvev<$ECpRSf2$NjOON%1!XaCsiOo+!k{F`puh@Bg6@bUD?@aao6tgBmYdN6 z%W|MpjwsC-Km`nY8K|6M0999@!FN!mmjd;NHydO@N>wG06O|Ytj%ChL0_pOG=z$38 zGB9L=v`NCXLCYktQb$Hm)duSEf^1_1*~TLQR|(FJj0#X=6&Mv*v%pqCEdbf20I~&a zfjCIp5`!!yum)yuDGJsCu0cSBm;wXXhm4N@84DE{-9fGrgJ}YVIB4bq;t_DDgCdPb z6psRs(|JVjC@2D10jm1J9syaQ017T$1_qFX5GcMOrJNEZgcVq_KoWuo2?Z9AfB;AU zYszp#qzrzrB52@(yupH$Z1^xFk#zEMgW`7!xK{84H(&%DSAbFls5E7AB&vXaL1b$C z05TU`z=H}V#|8m`#f&^$VAts~Y%u_321YA}Ee4=g8)G3#`R&M9lBLhc0BSRFK??4M zR%orr392JMG(dUa5eWsy53L6n9JLjg92K%0=d>MQaNN;$fI%RDfm?xDfzfdVsM?EU z;8p<5gml0;AcJ;*cp$%ntKJhZ9wW$c3<^973<^A+(x8I5LBVkX%n9Hsza&e6!<rFP zM>9GyW`X^|pa5<jf|_dzoYssAp#1BQt-`=PgOiaJ)Oh~Dkgdd`0xCzCK||6kp#D4u zqEV;70-F9%V03H*&DJp0J2o&WFz7HeFgfn8_X16s{bvO6|1-k)|G@lzaQ<ILZY5R) zMo-Y}kOHeasOiQ68nO`q4OEyh2{@jZe1Jib%@I6E%>o()hs)fa1(spuHe-?jO|Y3U zNhmNWFnTM2W~Laui+DlZ7G`MMUV+IKJkbW4Gz3ixxq)YQ6c`kkvUC_2934D(8Mzgh z6`8mdz+MEk-5HgbLG2zUN5(8AR!0T}CeUmmGicV!TUv?Pla~cFfu+Fe1{%0kV1=}_ zl-RQsLGvu29stPP{q<g;IVs2bEJZfZU=@=BGe|uLD6*K471Y5MutOBoWhtR3s6|%* zn%YD*p$1g}t7CPRA{!_*v1KWO=V_QhA;7Ev4FP6Q2!ICn6qvywu)p4imkpfW+<3u} z3L0($6>f~6TAKk<YcuLHFoLRnR`5&=Xw=>@-WwFoj%+0gtVN1UpqV<*c;0`;LNg{6 zN6<(!s0?Isv@cX(b!00k0)>ua0VrHSGb=nLAU%wrR3}v8*wFBSp%A1T)JTCSFH&N1 zY-l*b;0AIVIA&BF85Niu8yLY8vY_^Qg8-Px?8pKVZ(#A12F-!6C@`BbX(+IOGEkNo zla2z5;}M1|M{w_z#qkY8mZKV|Bg^9W1i`$+kmaZj5xv5Y<){H>I<cBFnSgZ90aaKA z3M`HiW=uW`ED9`+Cm6EKm_T#8ERGUcW=t7iL4hnYrUWocBg>2_0?bm$GGht=vlOz- zm^>6%91XG@-^_v5Y#45Ux(wt739uW$PG<o#ome2wxiFiMb0CfYIS1kh9=Ibo5RUjT z8|ny90l}caDv%HAL_x;TrNIFRwi`4apupk?3LqrML6}a=5Er!&bP>cIAQwRb0~A2e zz?cJb2eSgJz#>KlZpSN=4=^}3fWjyl-0cF58#98O_n)1S13cxbz~IQ7<ycdw#H7HG z4eC*OgBq^hMZBPi8s=;zHb?N}I-4VNA!ObilwZMl03^Zznz>Y9aI7!Ras=md21O=N z4rO+%0ku^b6q&$NC$K!21?nO~Gm00811UH5*Mm8*Eaa61n!R^q$x>ueU~#O;Qepv5 z$#S^zg2ww972u|Ul`(>pfr|nTurg2;tjNIRpui4tC4&N+V@)BV6o5?dg9-;$kU8KI zGz%=q1Q!GsyIG2?+zM=tOl6=MWbn{7Xc|Y6QGppWZ_Dg>092<OVDyw$Vg&^Rn*ysN zQ;8xwlY;`Q;~&OCB@RcX5+w##76(UYkT57PW+`%lcnXZ*SuQ5f&@_V*c;c76Oo7Rf zt*pqgv8jcl5Y+BsfegokW`!{&AtUrSbV3L6vFHRBH3FbH15S5vSTQQFXDNbuL9E$| z%%Jh5GROaof7lBZm>_hPV`BqLj-x=C0t=`U%jOE2Q(;s9`3<Cx4YZEqKQm~?`v3p` z|CvE`IU{JT2y-Em186cFGy@6sK9l1g(4vn*MMh8%FoUPBK?7DeJV8V6D}r)qO_mZP zC<s_U%O@He8X9V{KubzMgH5<x1r9wHkiWsL1h94jVaCs`zz9wPAO<LzFoDCR9+Fu= z8HEv)Q5cy)<sSpI9s<QaBO;S9I3i^dP-cZG15LJoG6}MGz!fyAX<%iHAZIgy>r0TY z6<D}i*%?{u6&M6ofR>Yh*QYRn=53%uO$rPGbHKBzpc!LkP$ecX3FH${nFh*lpsDj^ zj7ki!c_w#I%R_^S1vJaV?9R&so<jk(azO1QCIv=;3VzV+GBaqs4P-qHGj#3*ZT<;V z?LksED`*^$Re=T4U}6Q$qCuzKkmjG5;qy<RfEDOv1a*Ev?H7Rx9@yMFc!(A>-2<7? zLUJ;g37S-aOn8GG4N}MinYadxMF=!A@<S)Gz_TKt8B*}*Dw;aTK&}EjKtSyR(5fNO zR2#DbQ<fqq=3#zjgv_}yLg(B->qa1chPV*sXV6ju*diRPeg-wXKr>TtTTwzDG_wVs z&jLFfG>;FS^yhX2O~`_r&*;w!T2e=7=@$d$(k}*YY0wHU(Ci#^u@`iNoB?A5o5_)Z z39-t71r#?54B&xmB__~FHPj5G>2^>#3w9oOKnOHl&WLTg95(L{2}CI#&@{S$MYcOH zBP@Pk9`)sA1T!7$&6pTK=@7gUjS(DrW{^oV&`cU=0ZTn}YLbDMLBK+apBt>2xeUA{ z2Ruy%8jM9<GR0h?#0(l(U__o6W>R1VFH2$M23H@TIbhI?EhsHBfEwnY4hFLs69@Mv zHbypZJq+m$fT{!zB@RbMcSrCHv49ebBcr>tD|i7EqdPA%sMVdN2%0&CtY>2bWjsX| zP>Yd4gGmA;t-#<Y1FAeUn0N%4q08SG6<I)~f)cX=yBjYPQ@sMSBV(2VGpJF@;t1|z zfQP0OSR4~T?LhE|l>&=n0hq%A>P$Pro0iPF4B)0Evn~UuMG9)PGDBLljyxqrMMco2 zCNroVk;M%fYExj&2Gy}_pz?<eQvQJ4!Jxnbasabqy_W*BV}fUq0<&X*XA#J=%&1nf zK&@m!SjmFLN=S}ZU<DVsiY%b=l~n=Mr2?%21g-B=Vu9pK2w#as0aDIF`YkMqtf28W zZjeF+W@*q&JgD6(t;oo&z~RaZT3`UKTEUe(lcNB5a8-dxKptWf6Oyf<MQe(nsdiA2 z3vL)WGAOcuOb5A%2~_5@!9+mu0*(n#=PnBzF|ZOI94AN|qy`dbESx1vkr~vy$x?y@ zDl4Rc#DXwQ5u}U}q>L35_N?HrXIEhH1~*HXK@-{8N-T<Oki-W{a=Hu(N~{X3j?8Y- zpt77Xn~#Zs5xnw|O@T#`8RUExM@CSih{cgH3&aBTjzGg)ps{;!x&u`>8cd+j1f@t& zQ;Zo@$%2N!z@ezXs=%VbBp}GZ0BTM+g34D$MbKOrq>BQ|m!Li;3ncwQRyl!8P+)Td zIh&sw97doaTU3+4!)yv{p8VV(S2BafO0eb#?!)Yi{GeXBK(-Q>3WFn~A}`4QplJaK zB_2m51tv)TQR3HN5>Vn3WMJS0H77yUAIM;EJ5E4>L6HTd3lw{bf=u<0q|D?9?x`|? zQm-OtDu*!(qy?H*6*(1{9OVlY*%g=^<3WWRJ1FmP7J*eV!Bm3slmaKXmj_Z0?&WbR zu!HgzSRpf5Av+>J!ZHU~PmvNgJpZvcvSe|CMvN7>LG3sJJ&=PzSb+=D#$n9nV+O6l z<^~CHC~)aA@MJkMf>tqs7La=Kf+~J2{asejP!1$5I(YE1a5Fh5fLey2l{b()!RF4( z3i3CD0vog)<HO6s4IWit&H@>ot-ue9U9hc+tY8-?g1Qr28cZxo><~|Y7i}^rvO6*= zGCAI1fUG7~V0UE7=3`=HVBuC^1&1`dBeNotBZDHV<6o#6urJv`Gk&ZrATP3WJF+OU zf{g%Gi_D6wj$c7VBNMkHlOn6*ZxEZ2+Yz)jlS5jO6>K}WnG0Hz>}XJ=$gBYBVmLA= za7Zh1fV{%207@CIybL@n+zg;~zD$aoD9!{sffM8eW=3WPR+w8kvA9(Mtc;bFg@KWY z9mz#3+>UUUG9&o_oFADK*unV`;b;TL>!3A|pw@YrBY&Y1vm=+Mv;s3I`{QyvH>l!Z zW2$##RNzJo2XH$g4F`bI6KFU<i3v0UqQIsgm<{R;uqm)82)Oey@-TCALl)YA`j%{< zzNG>)q$mWJ7N|odpq1E2LnTb0kOXzuKm{!#Q7|ZgvLPsZ6?wT8xFA!}yr2vXQl|j! zz|Lm}Rh;z-j4Te0+K~7HEj(9ZbYv`4VwYCrQeahJ2WJ(Kup*<QE=b6cF^d~qelt3< zfR+`5`Xb<lB4~v#DDi@pU&%v8nLxe(rDA4K!yS||LBsgq!d!t#kr5(OQwYk`;GP4U z0%(i}Qiy{_2I1v4BXR?t(GikyKwVx%HU&md1D;WV6V%J)1eeaxUM`YpU}cP;J~1d3 zl_0%bHc-zEw4N8#HrHU{0EIZCBB<=-0=3Zv62K#Kpfm`=3Jj1|k`kK&yEmwb3>t~& zDCA`Ut!M{L5@jo~fpVDw1E~DxP~bo={~4K>xgAl>W&y44Vo+pPV1on%8>k{+RA7Tv z3GASuTd<3vO%X*7P{W1=v|?R@NkWm63A7j+(u)FB37iUS3LF?!0?0f>l>qk^$Rr5` z76n!iLk3=qFlaEzfO=>k^Fgcr8JX&t9TeCV7(p!!Mk&yYJtznn6xcuoJ*@Uo06BtN zf!!104+c=Z#i_vHXaHK31_}&CsMkRgY6=Pr3NUAYt$>6a6Qo#zEUgBGAG-oOa`-{( zPi9!EVS)q}XhammV#Ww8c973lV4h(^3oJGT@Un5351{oYcsLI{#0PFSVgwLaR0fnl zKmi1bcMh<*%%F9)pdLMAmLdme)E2Z%7Gbs`NUA<df!Px@vdf^rZN&fz1y0bqPEb>t z5tJ!GwJbP3AoV6BO1OU`TDA&Ij+~&(#jL<2prr&_ywAW7TDAfjl_b>rU_|YGu!0uy zdP^%XgBtbFIab830%q8*0??}c6^x+C9#9924b-y*SJ$A-0Kp23&>k(D0;@kSXvqk) zC*sBn8g&A7mo%6_s~{9u1bP{H*%eqoCb289WhrqeuzK>cD{v^VW-EdwSV8+*SQJ<U z%z0S36+rtA6hRY$AW={u$?C|G1=_*EqQIK%*x10~Da{LBi_8F8(h2Slvn#MV%4b2B zcPp|uGAOaS^0Fzgg6v`hjmc*#F*p`vDKUW(C1|DNQbtGcQddxuv?NPlDI=(3#{yn; z4JJUnCl&>E(CSdoQYMsz&Y%r2tf0L+AQ{l2Z1~D-(CTp}(8dhVVs=i@+%apm5({*3 zuP11C17v9@8)(K&pcuRm6+)`>uyDgVT_8uZDX{t?)PqKkSp|wg9#LS;R)Vd00`;Up zGZBzwtDrtF1E^{S^;kjfX9F#HWregs*g*Q=E@1&J#%2>J1~s4^86agfBiL!65Cugw zDA$8*1TB?q2CYhF2MtZs6`C=zKo@(mfqW0L3Suv4!U9@Ca)P!afx1shET91h76ndF zr;QV&jsr_)oE<&@p}+<WDo{#?xChkaR{%K#v~dX>00QcudV^7cL!cP6rHGkZfmuKX z6y2bmK%jv*Q1WrCaI66*PifFZC!+$hx3s_#(C!okP<IqGd#eCiV+|3OQeY4OO*nx@ zm;@$)3Oa%1jNBJk89D1gW3!;b9$Kh@hH|(-MGJ=_J9zUHyDqp1uE6EkGwlF_BZ~qj zB<s5JGJs1J21PanCP*_ER91jSSQOYmdxoIvyg(rZ@+>Q41Q`@bpaD+>7RQP#MP^3^ zMNou-W}rYT(Y&P<L1iXrIRi6f#@mt0jh7YNmRDj{U;qtBg2INy6<jMZE3ks<C@wc% zW(5{do0P>7+(`iS`k;bLZoDjxOrV|vs8<LTWCjT`Lj;*YHh{FVfCO0}f-EpWR*)bo zM35CG$OaN*g9x%Ya=G)eK-xASFDWs5^D=^3dZ77TB@R#%n%l9ySb^KIYs!iX48@NB z|Nbt|DpcTbgbZGCfFwCkC5s#z8k<@wKur(OC<tgM7F3dPgId&}g?pfo5?H_p9;*lS zRY834+HTMYG%Kjv3hoJk=dK;OAPE%Il~-cUf;7cIsRPtb1C7#yvLXX$-i=Lx!Bc@r zfz1;%st@YFfP>bZ7qkryJZa9Pz~;-#tiYxOl7uu|e0V{XJV+PFekKJre_lpzZUuJ8 z5*a4Yq!^n5lM(~Cw*abr7!)9lKQ7RMEm%@waR8+xP(oB-0QGnn6qrCga2Ak%A$@Pq zj2XC92yQQcCn1<X4IxKx^#U4?RbT=&D*v%Ca)CN+2H8r?poNK`RaxMbY0QqyRty>n z%uWp;z+%qK0U2pgP+$fXixLn@09xvUmhUTZfrc2EpgRItKrIj^Q08C+w+s{+6j&VV zv%o{!jD?`!V+RkOfSPKcI0x+sWY%C(09BL>p#6IqOe&Ck1M1^|8k-8By;I-~ZyHPt zpzb56Ys>)}fn{{$05w8D=7T3SKnb1+TJV5+!wL+JtR;$|)dmm;*n{i`^%ud;15Kqs z(-vsw6v7b<3d{&cfXWmF76mTQbgK+#WeGTR6hPIGCKH2`LMJGMSRExmWgIAjYcL5Y zayT-8rzXHVa==p<;L#;e=Y@%Zi4imr!VFr9rNjd&NHsyDy*wIBAipZIff`8Q`F1ul zCJ{)`3n;Kc_Emy9|Db?ZV1;-RVIXLyg#t&G5*yq=HmHFNAOkr;Q?3l4DQRX<0W1Nk zY(e95%q3Y$AZwvPtii+r3STA#XuyKH+zMGrETF;!)W86BhnO^&SUf=sIvGKsqyUOz zP`YO>$#P^+V3Y=L76Qd33rHFoldhnRJdBDUZ#f!(*1<AxUu9ursRxe;FJ%M`=7PP2 zYm^+cJO$L&QeYN9A0-Dl2Q;#R7$pa%V$f_G6L^#yq}Nd(i#DU=3QVvua!@;>f`<vT zBM{o~QD6d<Crsc$anLRjUPdqz%lJ4Wk>le?>d?o>LBq75`WKW%A;Z^T7N~*)wX>K( zlVyycLJT@hzyujGhh-MfEHz}v9O7tD^~?imzM6rzp@K)t!2~GL)PaUGVcS#~6d2Kl z%pv;`L8Gz^pz&TtsEr_nW}sRXv_TQHeG$v}FEeNwy+jGTungDuFB7OdG-Kib6=k5N z1bqA#v~83795WLed|Ngf<^pShL?u=M1|=2&M$qmZ1_c%YCM8BN2h<F5Q~@vf5CHeI z83kCBm<2#JstTx9W)=YVEWm6=5Szsj%w`b)b?4l98JQd$Z>$GzGUjCvU{hoQ^E8<m z6xki`tUmzWR>}Yt11<AqP+(u~3aT)i6grkGF@Y>*P+$TX1yTzN<ujn=H*5mzicAV@ zjvQG6B?@dBOdKo@Ac_a11LP=>4h04$(1hCo5K(W=1a2fSIDt2RF@U$8f!d$o7MTiY zTQ|tP3akPgph{VQ6Epz}@;+#lBcr3Tw*n(*oK}&MmzjqJ)c9ruwG|klR(t@}2p}sI zm?3_Lg$c-9aG0<<Dtkk;GK0(kc?-0T0u)@JF0*3;lK^<}0N7!Wj0&!21VCHWSRIvp zLER!&ka9NgC<XUDW=3{UH6@U(#Hzvo9#~=2V3JT`b5vJgg!PXEl-NPV7K0R|6U*S( z$RwZ#E+cgrKzqcX8-79E2ynp<E^Amp<6<1(kq&q8xEP}%186{t3Dh?SsQ_Wnj;d@v z(0~&=XqfarV<Du~<;bka2pSw?0x1Fw8GuS+$bb_kXuydXGz7*3YQ}$MP-Jps25*{o zWKm=W+W^{p3EEN50iN6k4>++%D>6HR2b?$*SR9#3zynSU-~lHF&;%i<iUV!>msVr} z^(2@SKt2YImN7v_%g`OKz<?Ok0}uXVbvNAQh(SH53z@k=19}jbg9r7XK7tPFft?E) zOn^F;u@E$<2kPk6XJK<DH@72rga;IDAeVzi??4+Oxc@UTGQ;O<6d1w%T4=jpfzca0 z=%T<NAfw0(3NuK@RFM%hghr^J%Yxd^WdiMM^Oja%%m$sY0Gi^(xrJVd1%6tCV@;NT zj1pv#mcR-|Q1uHQ;ZR~$V1l$cA>$lOpe2M13XIu>AXV@UoS;((W<%O?pb>b6Y*5z& z)CXb6R$>CJ5MY4znHd$B1&YD*XAlzHs{z%^pt*I>njui1540)_ybT@H(*{jbfeLn* zT?#B&z92Eku0~Mp#{w}Cv`HMoWPx;+Km+-pO|ak|krej<CPqe31_LdrgER!^^nn)f z@Iaa#916^i5CKqIgwc!%G)8cD3P=U0CCFsPB*WwYZUcbYfXt3(7_uC-Kx=}S!Ao?Y zOwh_E@M;}q&`O~g1!l(sQ$a>VC@_OoJlQCKCbuj=tCc`4IA%xCCJsk|EYKEKW(Eaj zM-9;077jRHfe9=FYLS9W2CqS4b_A_V(uP=#)fF(S=ism!><9(WdL&R|iP;gfB*+6S z2wIxt0A_(!R9Pr6JKkW(a_s4YtWvtd#K>On$OsyR0|f=BR0qX5V@Z}G7ieGXKjuP5 zjxum76jXljg8F_6pg~MU9?*E2E(0TID3}AZBp<XGni14$U{v7t=4DpkP-Iu&QDRi! zf(#5Ra44{YN_`Fm7SP^yX3&@=BiJ}5aHAWvppOZ(R1K8A!R>!uP|FR}7zGWbpqU2R zcgX=>)}+9pz#^~^)Jp=-JAejCK+{2>4J3u2u~={m7PRDn3DjZ(btILT93@H|B}$c8 z92xvT=Z-LdmJl+6I+>uMpZ^SnjvSy>bf8r{S=^wBKm{hyMtTMhUMA4!d@*RzKWJqo zBM%cdbXnDsxd#{oK+CE)9T^L`ConUy)-!?5S8>P!75$Ek;BjUKT?Q7=(nehdR>&YI z1E|T%pu`Coe+E^K5{^?r-S6qs4uEDC9GM)M6*#07*kF|fXcCu~5v&kAyUV7*gc$W? z(_oTtd@%I@gCi421ww)uG&}?9mM}0VvV#&SXoMJ~M8a_b$TrZ#A$Yb9q=uORVgZ99 zC#axfv|<ofU<IuS{sK;V4B+Ox0;{8V7AU#0LR#dk3T&E85}?imn<f*7A{U4&pa_~h zWz%I)0PS^R1=+^}YQ(cDaGNu0D6lGUnKLUWfaVuLNz?$eNgT9Ghe<${7i0-X7Su0b zK4?l%A`3KE$OEn|6<9Tx3>+teT+akzn?Rhd2wHv#8lDBM7-hF&0FAwY7vJt<W@4&m ztaCI_WOjrPV?gRZ5UIe(r@_t!1&qvWY)nu#3dI8Au`+Oj>Os(w;ya*4l%Ub?FQ6$S zR&K{nAex2S@g0Z;Z<%UnU?~Kxj!*(u0-)uTOogB!6?O1*4QLV=l<gcDL4pd*0{Y+# z=*U=<?#NW;$X>h_+`zcN(4xrd$gIc$o}GZW7c@c$%1)p~8yui9OJ>K*iw-b2YB?@n ze1O660HfpcB?lNBw=oOo@Pg_e1!l)Ba}O{$YB;vcJHX($fXQ*mf&&bW>zM>}Ajh&O zFgpJG4@&#<K5)4yFgm_(w(u@eV03%{7JP6PEO>lkX_h0qXAwx^!lRNb$3kxfM#qK; zC0ULS(!t6b8cMPp=azUE6@eBFIyU@s&vHDyBm_KU4BDlQ(_@Z|pw<Bcq{3hTwc$Z$ z8*pSPu$nV5a6e=MuTXO2E>UE0R8VAcQ~(XMgGR0(;a3Ja41o!B=FX25kVAqTxl5E- z6<9!F!~$AU%%s2s33Jd`J7^l8S%EPd)Xsxd)u1J_pmGO%N{0fYBWMpJsHg+kco;M` z#ONpwD&ANe|G=yeEmLF$buC#T6)TehBWPTM$x*?d7qpI+sl*ZPn*X3+RAdFskAg;_ z;Nqg7BY5hIvp^Bg02)LFEdV+UGVm~~H+bCvs9=PRmN9~cOjy9{*?+7&zyRveXDKm* zN>tEn4``-Ii4jz4g3dewPbCzBJ1q*1Csx4L$~!7JzCdsevpU{@@-Bf|Gms;pn81Ax z(3l~kV*_|~GZUys#si9L0Z?KCb+YXX9UB;ntQdGf%Xl2w${Y`Xcs!2a70ck`EI>mY z;NdS&10A$V%uxX(t_U9D0gY6FdP1O<f&z0EWY-yJl@u&xBiq3bwnM1Q@dMZnFpsUw z5#|IwumBH2fY%X}SHPkeY93%Ug4dB5w8$BBd=+TK8PvvCVgU{DFlQ?;f)-ap24o#T zMGynn2Owq4pt_wW3$kGlzBHNvl$5|N#BHGJ6`VpLJK7*AkO8!~BHNL<P>BV!6ov&f zE(=;3q{s#y&;xDW0_~^)&7di=!3ITHJwYR3;Pogh*-9LsRl4k1piBTdMk-4QwAzlv z0kp%*13ZDjTmq`*Kr@moAf;^Jxkb>D`)o&vLQuC1)R_R;$Ev`lz*z<w^Z-plFqJ@- zDuI=Q$B95||3OxR>J4^BMh{*F1r|`f3YvS-U;-^CQvi*Iflh;RWXXc5<_1k0g6v@d z*Yn&8OyEO`n6to(fSJMdJjjorBn#@sfri$Y6+mZ#frdZ14=^#YLVCgm;J%ZiI@qTS zP7}^Cgn)w!H2%&ET3^eV1zJ)MS{e-6RRmJdpu_>9>S~uOffl7Q7lQU^DuIsja$GR& z07I5O1A{dKXkf1nwElqI@d-mtHn_Rq$XJ4+g4wYKG+)l(^#9;>{V)X%MbMF4+zK4a zl~^1fGl1q!nH}pu`H$6+Gb;-$4nEEdY9nYBI(Cymr2vZ~7pQDPF@Xim1)wkoO*AWk zsy~nwpk?mh4Sb-?0oupm2&yW<LExyaz??0htiTE$q~ZoQ?35WC6|$8WRTvx@98a`? zyZ3^O3`$Ha4ovl+b}tL4FQCE10BQt*X1GCO;MrO53?YjEs0ab|-a(`N;Dc^Jr4%D* zw=Hs~zqg^Gp#qeor9pEd5UrrYz8ItwKtnW;iCzX!{{u8=32Fy`G5|AZV+)7?t(q2) zRAK>dHql@LHRnKkss*wHN+9+MD6v586;Wab^~xC(*g?H)7GBUQ1Mq5PMn?(IZV^@i zA5cP;P(m8s0Pny8t!w6G;D+pZ0r$ZfAcFu5pizqskOx41Rmf5SO-6p`KEU934Ac&l z$a0*~dw{{QqZhK1Xf3E3Qf6>8$W~%dVG!U4jq5pTfL#i%mKijdG(c`;&|uP0WEKzr z%PWA!KNvKa6d<#>DkuXG;Qkr-$T4OCL9lWOsB#I2av2=TnFNHu%0b}-Dpo~6tzOXP zWhRKvK%*<5H7KA#2}S{7(1;OuwY35xL>@fk!3e6q{(*eREx@D1;;10N3pNxK=^#Tv zLpBVc_(sy?r~s-xkwchCf!R^n8`^{c74G09sKCg*9a^O{FoL%{fr1ExL2(3H>n;H9 zhB7%S6oMAinKChe4xJM)QUood0ol#~?)QUs4KRUrO8;lbf{ZdSATMYEl_H>(b)e94 z<o5;*wU>c2DyXpqIw8ri5wx}yH0SR)57J%)o7TVx>g+RR3n)XTra<)&Hv7yKsBazU zyeG#7M(E@YoW<hUzz7*&UJgoU3gCe-0R_;=5hyW&CXvBMCNhHNBOpg6GCG3Rjew3! zWP%)-$OJz!5!Ag^05^%i=O%)Z2W<Y1$pLm^A!r6xfvH4^4YX>N8B`~L=H;8f18Jc1 z@fbnF`^?~7NMJ3XiD+;Q!3-V>22F@DDlnBOvVv<aR>VPyke&?_XnzDN=nzF#&=HJK z6G3O{f#+TrLCriK(C7xJB>>)x1RA&nt#wvl0JS(I_(20kph^ZbNyH2)B3M9rK&ul# zi@~|KK>`ZAjscvsz{L*_xWVBl0zPU`Bnv#Ouf!}3n&p73I8X$2-9Sl<1GLozoWMcD zvEUPqKs5_!nATAs3$*eRJV~htaz3M@NR|>PjFdpjI~5o~1M|$Fv@8JH<OZ6kQeXu+ zngLwAyMh-Af}JA+YL|i|K?yXs1ZhMHIEsMU5&{YgZqixc0cQn9$dD0uJ0pVvBQFCu z$iW0?P_+)^U>WcUOQ2RAC>$g}LIR)y9n>8J4RC-pgLl+|XKg?yB7)M6NESH0Kywym zOcLDO`HVdE;FGpMO&pMSAWNSi=Ldnxz$2jZ*Ffz9CQ#^cC^CUO3Ys<rou>wx%Z5zi zNGX7ho@52B`D9f91p(L`&{8~kFabS7jk}wPi3MJvGpT@_1l~mgI)@E3_yRT+wCh9w zG*tzPa0MnOMo@<m)Q|>s5JeQ2Ktm~@p$+f=3TXGB0%(aWd}xJ9ff+0#p#YlBbG$GC zG;ZxEktLu8I$9LelYq8YSQMBvnK?k+3h-u8Py-rrz#*td3tET|jzt9)(4k~(3XD#S z=1icq>TKrB5{^8MECL;jN}vUxVD@~_$O9wDKnc(=Flg=xG<O8{0fXa?NuZG;7D%vz zTnw5(;(o>m8MYG01|6dUib9YS_$)<Gg$dgK399*^O>ai<0z5`g+q;1U+_6vq?HGhK zzTqujoUK|=7G(mpu6ZDP2SF_dSeqEL=?)t3Mlzm^b~m?xssa;a{QEJgKcRgHkY9PS zhK(oxKs~7diA*NYtOcXvCC~&2t3Uy0kQ$W57#tb=z%5_~a3%$vb2q2`0D~h3IIJ9( zv>#v)&;>Q0et!madqMlBK&wq%L1S>B)=9Avqa&|3csV>M0+~vb7#(+XLwdTPsaB9+ z1Dr1anO~9t?MTXU`~q?dFL))h;}g)9XkCbSBgg@eu8Lz1%mEOc0uU9~K{|OLPK4+L zDd)%%2n1&w6$b8JCPo&}2&@3OnaSkfxCb(e<jCX>DgU6YA@F&Wj!d8o#^eazT?gq2 zf)YOqv^W8=L8ttICvZVWB7jtbs|e5tGDt7jbOlCDX3zwY;}Our6a$0<YAG`cXgD5# zSg*(oRthRL_kdKvw48uSGl4aOsyhZS3v4CCB(Sr=jbVtJm0<(<kY*F8!KeUk{erRz zs2K{X2UHlq_Hp+^8$1e-UJIilo8t`7>0%%PlnmG%89~ElOb(6@HXi^TXTaj%_+&G< zVGb%nK^vOEGN2(W25ztv==@UfR6J<8g$HOCB6vS0E9j&E3DA-QQ1r8cwpR%__P|f? zt@q(&0CT~cEJ3TqAY8{OP$kSP^^OxDY|z9LsB{+)1fA=vzzPZnW)8@B9J2<Kh#(V0 zuLctXs{#|ZV?D?Vp!J>JpuQk4BR2y#eAocg<p$NHOrYw9gMs@jBV^ON0(hkcsD=jR z2<XBECdkfdCh(cbpvVBt<T90j>KafB=RY&()CSP_D`@*98>pH9^&(jWK!-RfGAXd~ zGJy&=kS5TYs#ZoNCSH&zXssxV0Jvagc9hA26z0s1Yrs<j5(><q@|*`WHP8<#oI#TW z99fP_`XK}20vX`-HlWl7YDz+CX3+E*sBgf%laY}Ll-yz70oA%$N=%^L_MmJF9_#?E zIxbNHRTwOwrFBY7;7rZ|8q5Ke>flX=643LLm_ZdkXmSG7`ZQw_03~DaN)-XnFg~bV zEs!PP3a(JV`XJNXpcA!0VxWTuA>IV7>H;kQ1f6LL$~EA5WzggU$kqJZAW!XNWMqaq z9Xz}PY7m3^uApt-V4s50C!-@H$mz_W<CQ>L@*TmU<1G#DeS`MXF*(AV4sK|OfQk!n ztqOKJxP%t~Z|(yZ8Un7M9wlgf3aD5FSparAXxW1T10;q(Y;c!@B?~n42ihVCo>~Q+ z;sEN5ft=n43UviWM}AP38`O7X01uvk1``yR!PB1%j-V4ZKog#zRaDSn1khwVGiczx zrcj9)wBm-@k<kyV9PCa|J;VZvJO<G42B`bTTnOsffV>G>`UD>2Ks6W?R@~g&YoI}C z04egp!yn*=APXpsf}1j+NfZIls0_FvfW0XLDmcIm7LZF6vLIy;6N5Q3==gF*une}Q z40u5iXlM!)(afNh2c#*3+}Z%oS22ORVceTQp{LB?xWE8hMuV+!oMa%d7}TWb5Xf>| z06I700t4u@EXNB5koFB|?H+^!PGI1k2q=X&uvjyyDKI$7WjQgJgATrMlmM}qKym_b z76+WA0r3{dz9o!O3Ji`XK?f_DF@V$`G5{$983HnEmw^JK;{s6oNP&TSCdkc@(t-(k z95`tD9JD3fk--BrTmfoGgT@8G>$F&um_SQRd6^tRTM)p@4H<brM=yXDFM{$Eqa&k0 z2O~FVHWb|Q2bCF&3XB5aG6Q^62P0?|Gb3b16ujI4v@9CbmxZQEaJ3Ga!CVb;nlgjq z1jrbG0)yiPhAhVe`k?S+0AEVr2o6g{M#nSAfeGfI1}6TXQ^*1j{x=)|RdxytpvEtw z;{*dxG$}ARo&bj<INT3_!x6*;Eu7c^3daf1aO{PqR8R*TG(pOs&j^~*hn}nqn)rox zs+mBYY8LRMJ@`}_&_;exreRWIabzh2@4n{;AEV0v>ZK|%fx6Y877e`j%IL@l>tKU+ zae!2T28BRlQp_NZ3IlgLx^qB<K4>35cn%uw6mW+d*(n73_n;v`Yz_gNi|FZt&cFxt zu({iaa0s;HPg8fyWCZOd0~L^<3IH?;0^XMjE{{Q(0+da7V7bIm1kRFyvvz<ZAC!q4 zRk9o#Sj@moNRj|EAqfS{{DG8EH9%|34}cOcG(mw1Dt>N(nT!nF0<#zyxTj$_4dfuu zmTb_@S@0$jkO0Vq*xa>3A<J<B=&&EqQN$#;k)XSppiu{@f<Y7gpn8Q>Kpb+8Ff*tn z1nO`xxv@A1NGmZ3h$yiLh=Lb#!k0XO#sn1@1;iAPMgc*MI?#YDXxvPJ*%4B4Le`6d z7LkGua04$i6o_Kro(C-h1i-Bt*nJY<(G~D|5ODlyFfoAkOEPmSu<(L5q%k<Ym~a4c z=ng2NL9S*2)!Pi9VE_iu-aPP_3TSo*x+vhyqyr3~h8bwN7^5zOfPfu%5h4R<=|5O4 zsD{6RMNu=NPr?T3Yl9YEfp<%Rc4jbvHrH@~&ccw$0<X>jxddG2vMI2E+kOI|F<KD? zMo@JNTFRurC}0l~k^ohYY@juwpzQ&m5)-uS15^#MLBq>&OaB1|M+1Qwj11g`jEvyG z05$kP`4cqG3~Hl*lMj<)19-<OljHwt@Nxjqk$Rx3GaOg2KxTnK)di@z#|T={4jv3* zKu(^H4J-=GkOctDjtvUn6#%u6ZnXn=&K)#A0cwe|f?J}f4P^yZ&<U2{HXf^^4Rp~5 zs}qwslZ*nZ;}h^iw}b*KXnBVTc!t^_3v_Y|Xs#Nxu)+c?1{#zA&02$|r3Eq>xN8_0 z>lGMu8CXEg8C?d@(qQn(V<0Z5@={;~)mxx^t^_*63_9lz8jN<F4H=h&uL%Q9^Mk7` z(2V{pkR+(CMw+7MR${aQZFvM+;K<<4UBk!>THXM@X9IEaEcg@$=mD^d;6)6etq!0i zo}fShx3wT^F)<FEg<T^8+P|v+Iujn$A_R2`v7F=1oe${)LVCcUrTL&JS6~z{1y2Ek zoek>Zfwl&Lw$nly<V@gms6Zonpw<x+sF!(WGNeHcs+~Eq1k^xlB^*J?LBJF=;|6IG zDll{BL*g0Q`~o*bLCtMY2?puqf@78m6vyBNdrY911zB)nCd2|J(1}AT;1fwfYi2-) zTroMafa916RDOWYt>nmZyfG8fCm`Ft7SimSLaBXQW{~8+Co>@a%Yla{sEq*blYmMg zaCm}yP~h-nhKDCJBs`fNkAT-wf%*s%W=tyJMMjYDWCpdGG{6F&U<EC90^K73$|hm( z9u#C45RyE=&1X<+7c`H9SUm+ANCNMT0Jnxg?PBoN0L-8g8Puf^P=gIbknRU&b0$zP zaK$W0m@z}r1ZXiAK|h2*0t{XSfr~<Ti7Ww{6h>MACII5YM=3!qc2FZ9<OM{fqRqe^ z2{#lJFpvX*KqWn>%LN{6hJ^uWU>Y>e2;(b2`U;?-B$h1LuqP=0z``b-0WxI;nh3nG z9y;y-UV#c(&C3XhPe#Wh6Cq*22nh@D{VqD7{Y8$DAOV#^I$36*{SKfJtTz)O=|_PP z($k=l4_-_M58NXAAPhZh$OsrL#`-gY7KMT8KTu47C-faTvLN&R4A}}ypm~4Ll|W1a z(0PAQ{R>jYfT*LuZ4J<fh$92HH)x=y9z0Te1av(J=%N}>sRyd|!08-R^)fK<GD>lS zuPp+vF$W!t0NSRDdQ=B@7!zYXV;yLu2r_2H%OGH)$l&;3{Q=OBmm;I%6VSjGD35~0 zpfL$vm(2)Xm(2i~Dg_N#fz@g-F$nN+TZ0;$%;1ACK&@c~&{=b!r43BkpdbaUy<`G; z2DJ7bqkeTqjRi*$=-3LZC>H>a*@MQ)9koF#y}*M&kir$T_7IeP89_yWCd3@bm?3B! z0#r|f6B`G(cPd~EPVXuV+-{%<0?iSC#)Lqf1!hMMNCy=>01sk=yHwzb8_*~rxZ}&< zcmieIo!b&rH8VO2lsQ1g^uR~3fCdsktEXx}CtTILDKSceLjbf!!cm|^0mNnCPJs-X zK%EMn3I)%WfSPiSGbTY7{ei|Zl|W;L;3l5q3Ya+9S)lu|z)dzrP@xEJ(18gSaMMn} zmx0?J5*`ZRNo8;{fK-tTjt$@%Q9^+MGG@i#xB)BxI^mil%W=nKNdB{f>j#${pus~> zs562_2ElOv8qRG1m(}1}5NZav08MA$)&?aB2FDuEa2~kd&jQ-WtH9t`>*>f;0-EiB zY)9hfX5iKbtHo%}ftJk($U^4qP?~85;0Z7W&{AN5eo&k#FbYfn)1WOC)4*GX6&M9P z!8S4qcyMci$|?a^NPtfKcAPx%0D~j9KmoWS5C{a_c$1~Vz^#hm6i{XW?SMpXCptjF zMgTkk4Qj-I(i(WC5j2MgF1T4BjUmvG3aBa00wOp-1do6m1GfP<vOwVpTJi_Y#h?Zn zc*2>%iUG9LkO{QZ5Hje-tqF1$Ba&}Ht$hVXfl`<sp|mHr77ms0VDW$}Rc8RLZUOC& z0v%;r<j4fRDg`u5r34OP7Dzje+lrB?-th&bj8|j?v%o`Rj&C5Ph$0i11x;U|whpNL z0=51@x7L6grwnUBUH{@WPK-4mhL;ip=y-ZY0V8fXkb6N<4L;`yY%c?3f{{T$0d!9q zsKE~!#b*I+u>$oB!Q+*nDF9F_o?97I0)Phm9KqXfL9=2IXM<SKkycd(1#lWf>PUb( z8492wN$^%_0YM(ncr$KQjG#i6lL$5JM5qxYLX8juw;U*X7#%_9Jb}X#GztkiKLFxA zaL|GdWx(_l*s-h(-10cQ4VGtM;FbiHU<{x$Xh1v97(u6ng6DU+xrIQBdn7?apJmyg zGeq5_xy2x^0pHMs6tn_h;VjU8M(}cfZXU1zsO}L^;pPX$7bED}HwFcU>>_RvNc@6k z!Znz{=kG8uaC3ll2xxJG%mP=08cZw-i~>sBVhrGf1<p#4WCz+z$_;A5gVZq!7&tO; z3xn)HE1N+obQxG2nF<{l%RuHbf>IFJauxwaumPYQsse?MEM;(>LZKsT88;WG2xrJv zU?}9~1|O&lS_J0`^%f*Dz&bz<0L4ZYHy_ws1}SC-9tLg(uq1rh377{`3fc{)%FPM! zsuW0-;}cM~RvO%`<zsOC58|C?0CTy(CbBqI6mr9uj4&n-Xe~HIln3NFNwATQ9gt}k zB?d=H1qNwBCI$wWWnpkuD4Z1n4|ON74WKCp0Yh$fuvuA9IvY$oazOZw6<OT83{ICC z=iEDZ^w0Yh^&npNljl=E?dZS01I&B(?)mLA>nFZ=#=y<Yz{t&gTAvv*c*%X0@xz1( z6Fw*;C@2(s5ctsW;e)^jfe#ZVd=LO}1U`Id_%Pwa2LS~KhYu4#tO*|kK7d#g9)KWd zm(_;{6DCY}@Sy>ux8XxWLvc~EelqAz*7&^Ce7&OL00sdD0R{$!c?=8;oD2*MF#b{o z1_nMfz6ApVLup=SZb43JZfaghYKlT;UU5lLX>v(sex8CsMVf(ufexD31O@>H0|p3C zU|?Y2U|?WiVsL!Wcz}WXmM#ke0|PUXEh3Bz3}Q%pVMYc94KzNoyZ|EugCvr?Aew$2 zMg|5^BzaymJ|7yNk&%Hxf`BhmvG}rr5#mc&=>23s^~ZMx1_l)*cYJ1GU{EGtj|CQc z9-!It0L`Ag3=9l{1k5$SVy*=fs{6O0nY)F7fk7V0{c(&846+Oi4Af4Ypc}4JQxp<{ zQj1G-O029(^U8}73p6wn6!P;F67v)iT#Jel6v{x?Z-Z_%W&ouh5C)|oP|7YX$;{C! zDrV4O7GU6JU|<ktU|<kuU;wLw+ym}fRFq#-nwOoIU!E7AoS%})zzUTInFG2%IWw;$ zwJ0w!2P~VCTAW;zSpZ7lAa$VB3sT3<z`&4S9G?m@3?vQ`17VQ7Uw%reURJS!V}Pdu z#7;fbyTBDPixo=q63Y@Za}twsQeo;LY87(xQ%ZAE(bRwp2u@8dElRag2&)LsFUl@f z2=a4BQk<7xqL7)FS(2HUlUbFT5}ulb>;SZz%h5~+DTdsC4%UO}ydak#M?V)Ulxz*M zthltGAit<2H3g<1Eip4EHANw}G^Zr9ASYF!Br`X)*h;}UAXFi<xVSV`Aty6CHK#JD zB*#594|0#Xl|nf9vhiYt;>zNZ)LezM(mY6E0y3<$C^a}W8KTlTzo62ovIJycrhl*k zI4m+tDizX-5_40_^NX?-pgK~Eit_TU6jCx%6hN*lN-ZfZ%2P;GD9A6)EXgcOg(RNz z)DlpT=I2@|lw@QUE0iP_rKgsFf(4`r?lp!8RsjYhRsk^0z$(CSnMHtM35x(jGm8L2 z8jApfB@09jq>h6{fZ++V0K*n$0S0vj1_m_-1~6^JzyMlYz@X2-z@W$g$v3)CIgmO- zXxRWNC6g2LKn_SuPEIW@R!CF`$tX%qOi}R3PfpBH2rkJlN=%3PKp~~HC^IizA-_l= zF|8!ENFgN^UKHyWl@^yI>!+ofCK(#1m{}Nt?mka7Gch+wN;R@fF*mVDHZx63Nlvje zOf$1g)z8UHDoQM>)Gsbc(Ff(C60oiMIUrj>xru?nj7@;SpMim45hDYG6a%ED0F~*W z`@*>x7#Q?Ji&Kk=^|SI5a}(3`^^y~d()0C;QqwbwOF-rpCF`eWmSmJB=_Tjq>Kdjd zCz}~rTBamfrY2h$8S7^i>lRlQ>l)}8>zSH^U67NRq*qi7jXY2cF)%Q!U=v^nV_;y| zAjZG|vKLejfbtv&gYr1Jz6Rg;4Ysu?u{^#kHCeByIHV{uHASH$U!gdoC^Ih`#7b1i z0VR?mh2+G7#N^D9$_jP?aG9>dF2LZ!z`(GQ5fU!RIr+t<MX3szd1d+8sVNFYpnO}L zS(chp35qRnJmpuWrYI!lfFidtuQVq|p&%bz>?jl@78gTo2E`#<t3qjUszQDms0hqY z%1TWxQAkNGNlgY-!Q~m5$r%de`K3823OS&}Qj)Jwnpd2dma34LlbKiyN+g+i3LxVk zaR&-75SC_OV32{PM_9S*4=(A@%Uy*8zx=#ZXu0bMPMhFj6mr$O0yqE@K}96Ui3&-e zYzoefsVSheF^@w4TnBIF5CFF*&{G>&yIxW86%GM#-RlVrM|5=siFujH*_nCidPT*5 zI0V3McVmLoOEA7W69a=B0|fIx%XLt_1xmA^vKi!WP%8wK4?(RS5MK#e7bB|y)zzvD z3=A+gfZd3eMquecA-^oOC@m+yoPmL14yORP{%zodxQ~^I0bIZ6=`k>z;1mGYStNut z=-_EkHUp=3P??dLmy%kckY8E?3hAW$(!7*nPzjc*kdvARD!mkvN=p<<GC+wBq%gBs zfq_AQ3)DYgU@+hkU=U(pV8~4@NzO<uR<yOX1tqp3aKwR1&(stvg#?Y9)U*->g`&*# zj1tWRE(HY;-%24NL5B+>2x41-!Z4mo0NnPffSTLICBOg*^J!cH;5J<X0|NstHG8-O zz<vT51PT|BCm(P@!bgXJ0c;K^ZuCH93j+hV1*rf6)m%DUItmKa3bhKgng$gH1_p)( zMh3<PCI+SkW(MX476z7v28M=)Mux_QCWfYlW`^d57KWBa21bTPMn=X)CPt=4W=7^l z7DkrF2F8ZQM#jd*CdQ`5X2#~m7RHt)1}26kMkdB4CMKpPpaRpv#M0Ej)X>z()Y#O- z)YR0>)ZEm<)Y8nr%+Sop%-GDt%+$=x%-qbv%+lPz+|b;}+}Pa2+|=C6+}zy4+|t6p z!qCFV!q~#Z!qmdd!ra2b!qU>f($Lb#(%90(($vz-(%jO*(h}rnkbglKR3Cuq3}Xfc zhN8s0^i+l7lEflV?GB1kSmZ-w((;QGigPlP!G%LkYF>Ith5`c*B%XzM1Q<YR8<vKX z^NUi!Nf@j_ADomxrMnA{0Jv>u!T?FD5R+5$K&^X_jU_yg9s<lfn0imBda!W{VB;Vz zEmlY@Q2?jJ)VvfhhhYhi0JvShi${Qgmw|x+W=0ImjQrA~<W#6vp#IR%R7lBBEe2QP z;4}-a;xh9fB@M(@n9>(KkTT{kj{t)<s7&LAq%TarmlWye=BMZt6-Vpo>BWM|DyY*z z*#{+KfwCAV4JYtI+LGD45I?}ut^=<C1E>tmhSrBksp*+{3O2S1prF$LF*Fs*Gg9-w zz5<oP2@DKtcp+urE?xm}n{*#9By7>m%!h^x%#K=U8v^7=a05XhIU})10bBwk7FAj+ zWP%$nnR&&TDX9ty8X$iaD`<kf0CT?pp8&W`V#X%`?uFU$2{3@#yXbb;L+!>0yVAT& zP=i^&peQr11k`c`r5*-`8a@FAP+18oKg~cHnUR^5m5q&wosEN?Q;<uBTarhLSAdU~ zpGAOKNJyAdj8U9Lf>DxLid~vfhDlybfmxebms^j~fZ34Ah;cd7N~TpTt2ur#{bv2k z^pE*J=h}+O?%qWP;St?E6J+l4@q7FH{-dXF9}^q@sHL}W;-r-uckMoU?BuC?PoDl~ zU=bA3G&C`{vbOc~if!qev~uH?UAvE+I(PobQwA1ZKCrBnwX2(_S6oVJ%f#7pPo6u^ zE2wE@?dlmG9TOLylG;0Q<(6H$kDk2$<mm@qL08X|)P|N#d-m<Wdi}%4=GN{d%l7R* zdi>n^Tem%C>^*zz+<8ww|M1AT_|CrmjoY^GKX~l;bwM%l=$LQc|NU>sExdQ1Pd+bS zMmD~-Zr%F&-Fw8u<>cMm{rn@MV&dxRw;sK6_0ETnUyF+SOG>Ay>FF<DzkUDl^Vjds za+p5DpkMydm2?07{G+1TIrs(C_20eE%Qv^RcXFLDF*v>S#K{X6Z`^$T?>~b=yi(JB zmL?YtX%;rY#x;Bmt6Aka8>N{gIT%^=Sxi{inHkyH*af))_=VU**_l~nxHy?PnAw?` zn3<V*SXi04*%<jmS^e0h*~8hH*u;1PSe%)4nHgCG+4y;^SY%b>6>?d!R2xpPHf>;* zVr%-#9LX-mDZweiBf^u##>FPZ7Rj#7>c*wR!o$MIY{;#{BE`nd+^~j?i%s9qm$_jH zhYd47vkkiihc;`|e?bWjeL-DjMSey8h8~ut>5|-{9aC8KS*_Wb_#`+R_9>O{G+dM7 zVQu)&+Hjxe(_ChA&c+y#h8-LYr&+lqteLskEI8aac-Ts~<(Q*bA~_qHC1kk7IDJ?e zy4Y4P;SpysT*%UROO2g}m9=4+K;u_-Mg=W4AC`t*mWF-I(#-t43~Y>y%#18dtnBPe z92}fXT&&zod@KTtf=oiJ!h#}<qD<mUlDsmkvK%UmSuEL1>zVg3ooBkpbcN?C=QXD5 zOt%;xus&pZ&hmokox%r}A56cPelzl@Tif~h_s^R*zq)(!v_+eCwQXc$=QOjm3;TTG zB8!NGnR!@v{i=2A_nJNsYVYiu2TzHhgy`p=k{YvZyR-~D2N$=9xS6HZij_BRa+*(= zxPqO_+Ab}#e^P$@{&(-AlD_=;KWnz0zPd)}+yx64FIm1~?XEpX*tmH_Wv%R81D7p7 zb9Mo{q?D4Xo&EDS@Bbe?#-gCCs-|IVVddfF6A&C421;}Z$*F1C#TB*nT}xK2-*Di< zy7hValjD@CS(#aMnbVjV_4OK>WSI^5Wmr@=<yf^@U0C?E8dkBXu&A(TaF}rWIX9Yf zig9sBSi4#>Cv$Kbh_Na%OS3XMSg?4r>a%dMbFw=qsI%~JnlW3kO0l!>um^aW8}k~o z>v3>3ss;IJaA=81smX|lbNaEUaJukHvU9O{aHw;Za@#v<v01Znu?4a*vI;P>HgqS+ zd2nzwEQ?ce<>q4J6}DpIGSguZZ`f&*63pYl$>ruM?ZFYu>)FWe#wE+_?rF}<$HB#B z$<EbiCfTr^k>7~7d3IVUcf*k`pJd(^{r-zh?hAJ|S+Z-f#IUJxxp8T*3O8+tPW5K7 zWEXS*rGe=`I9jf0aV~n^XspXD$imUs)5(&}%FE2jE-)#<owLNI;Tu;mM}eqY!z>Y= za8AjF_C|N+RwsVZmH_#NhuRHSb(y7Dm>M1A1+7>aTOKrg*YsiGVqt0)bn&rmIAp`d z$P&sbZNk*Zr^AxM6UNoB&O(+~hlP`!iI1&eR`U&30cKw25}rtQ9u`J^9u^A@E;bDg zCBMcH9#LjiW_C_lW^PtCE-p3>4tCCl)2du8Y&Z*OXh9vHl30=mDWhdY1i<5H-VBiO zG#Eb|I?e;*2Qe@(*n!G$W(J0476pchtO*RU!V4IL#1-Uu6cXg$>nzaLGEmUTU-m$U zX+?sr-0uWEhJOlX^ZqB8{bhV;#>k~;uEP7!d^KO9rM|>MO9PpQ?w{opeLiG8^!LwK z44AuTVF1JV#K6>x3j-N$DF!h-co=N(Ffnx9qlIA?UOWs_V8{#q!1zC$p@4y%L6?z{ zk%^Isk<o+OKvaM+m7R%=iHVU#nNdzUn%jz#lTm_&k&}^`kyV@7hC@q&QNf&%k&%Ui zg^`(^i%FKz3Z$KdgOQVwi%E)+iOG_Yg^7iUiJ6&Ej)|F(8^mX2WMW|yVG?6w1gU0X zWZ__BXXav(W3*;uWaMGwWYl0}W;ADHWM*Y%WMOCG2Ac|U5;G$!6NoRvWXZ(H$ON@T zmeGTeg^`Jwk&%NjkdcX<ha-uRiIbbni%A-Mv#&WHBP$~lD>tJGCu155BO4<V6O$wp z3$p+VFC!BZ8zVm>12Y4&ER!6Q9FqeRBj_?@ZcavBW=6(RCMCu)W)>z+MmFZ#OiUox zg6|S$;$Y)qVl<F9WHDf8WMpO3;N)RaU}0inWVT=gJA~PagNbPdGb1k}JIF$2reh8a zjE5B&n0px$6d2etnHX3YxfGZJm>58XBBLY|E8}z~DIs1)H4aH`J!S(&P>`uJI<qk` zF){Hlaxm&M8Z$C7GcmC;F*0f~aWKAPWMX7tWME_v5D;KsVqkp4IF*%wnURT=MT41z zaVZl63j@<kZbO!8Ml*g*MkXdMW<wSxCU!<!W))UO4m(C3CKFCB7AD4cW>7$~F)m<a z<`4ykDkGy9BOg06>tPO1h>C+klns<1KxyJNBNGc76C;x}Qz!>WUlu5?*%%p9nVDD^ zSs6GPnZ7b|F)=c-Fiv1$Vq#%b;L>0N#}XS8vmP%a69YRV8zW<o7!y0lv?@06ozCox zj2<AX7#WxtSgqL^80}dCL2NxHaRx?4W)@Zs4kmUvmMP2(<}5}WjC_n@tc?5&?2Lk7 zQ&<_;85nI@7}#^!84?;kK=+_9GBH$h78K>DlqRPZ6*F<<B<7`;CZ?w{vILbDmoV@Y z6y<}4dQww#lPZ~6L7ntuSwlTDLp=ip4fyztf{}rdk*<NMuAzk{TX|wmQE4%ofu5nI mo&isJVsWl+Qf6LCdTO4Kfu50`g@J;GNpfPMrG=rHsU`rO>k^Ov literal 0 HcmV?d00001 diff --git a/examples/web/chip_ahoyto_bg.wasm.d.ts b/examples/web/chip_ahoyto_bg.wasm.d.ts new file mode 100644 index 0000000..2f8c42c --- /dev/null +++ b/examples/web/chip_ahoyto_bg.wasm.d.ts @@ -0,0 +1,13 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export function __wbg_chip8neo_free(a: number): void; +export function chip8neo_new(): number; +export function chip8neo_load_rom_ws(a: number, b: number, c: number): void; +export function chip8neo_reset_ws(a: number): void; +export function chip8neo_reset_hard_ws(a: number): void; +export function chip8neo_clock_ws(a: number): void; +export function __wbg_chip8classic_free(a: number): void; +export function chip8classic_new(): number; +export function __wbindgen_malloc(a: number): number; +export function __wbindgen_exn_store(a: number): void; diff --git a/examples/web/index.html b/examples/web/index.html new file mode 100644 index 0000000..b50d7ca --- /dev/null +++ b/examples/web/index.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <head> + <title>CHIP-Ahoyto</title> + </head> + <body> + <script type="module" src="./index.js"></script> + </body> +</html> diff --git a/examples/web/index.js b/examples/web/index.js new file mode 100644 index 0000000..f35c253 --- /dev/null +++ b/examples/web/index.js @@ -0,0 +1,14 @@ +import { default as wasm, Chip8Neo } from "./chip_ahoyto.js"; + +(async () => { + // initializes the WASM module, this is required + // so that the global symbols become available + await wasm(); + + console.info("LOADED"); + + const chip8 = new Chip8Neo(); + chip8.clock_ws(); + + console.info("CLOCK"); +})(); diff --git a/src/chip8_neo.rs b/src/chip8_neo.rs index fcf1a76..730a15a 100644 --- a/src/chip8_neo.rs +++ b/src/chip8_neo.rs @@ -75,6 +75,89 @@ impl Chip8 for Chip8Neo { self.reset(); } + fn beep(&self) -> bool { + self.st > 0 + } + + fn pc(&self) -> u16 { + self.pc + } + + fn sp(&self) -> u8 { + self.sp + } + + fn ram(&self) -> Vec<u8> { + self.ram.to_vec() + } + + fn vram(&self) -> Vec<u8> { + self.vram.to_vec() + } + + fn get_state(&self) -> Vec<u8> { + let mut buffer: Vec<u8> = Vec::new(); + buffer.extend(self.ram.iter()); + buffer.extend(self.vram.iter()); + buffer.extend(self.stack.map(|v| v.to_le_bytes()).iter().flatten()); + buffer.extend(self.regs.iter()); + buffer.extend(self.pc.to_le_bytes().iter()); + buffer.extend(self.i.to_le_bytes().iter()); + buffer.extend(self.sp.to_le_bytes().iter()); + buffer.extend(self.dt.to_le_bytes().iter()); + buffer.extend(self.st.to_le_bytes().iter()); + buffer.extend(self.keys.map(|v| v as u8).iter()); + buffer.extend(self.last_key.to_le_bytes().iter()); + buffer + } + + fn set_state(&mut self, state: &[u8]) { + let mut u8_buffer = [0u8; 1]; + let mut u16_buffer = [0u8; 2]; + let mut regs_buffer = [0u8; REGISTERS_SIZE * 2]; + let mut keys_buffer = [0u8; KEYS_SIZE]; + + let mut cursor = Cursor::new(state.to_vec()); + + cursor.read_exact(&mut self.ram).unwrap(); + cursor.read_exact(&mut self.vram).unwrap(); + cursor.read_exact(&mut regs_buffer).unwrap(); + self.stack.clone_from_slice( + regs_buffer + .chunks(2) + .map(|v| { + u16_buffer.clone_from_slice(&v[0..2]); + u16::from_le_bytes(u16_buffer) + }) + .collect::<Vec<u16>>() + .as_slice(), + ); + cursor.read_exact(&mut self.regs).unwrap(); + cursor.read_exact(&mut u16_buffer).unwrap(); + self.pc = u16::from_le_bytes(u16_buffer); + cursor.read_exact(&mut u16_buffer).unwrap(); + self.i = u16::from_le_bytes(u16_buffer); + cursor.read_exact(&mut u8_buffer).unwrap(); + self.sp = u8::from_le_bytes(u8_buffer); + cursor.read_exact(&mut u8_buffer).unwrap(); + self.dt = u8::from_le_bytes(u8_buffer); + cursor.read_exact(&mut u8_buffer).unwrap(); + self.st = u8::from_le_bytes(u8_buffer); + cursor.read_exact(&mut keys_buffer).unwrap(); + self.keys.clone_from_slice( + keys_buffer + .map(|v| if v == 1 { true } else { false }) + .iter() + .as_slice(), + ); + cursor.read_exact(&mut u8_buffer).unwrap(); + self.last_key = u8::from_le_bytes(u8_buffer); + } + + fn load_rom(&mut self, rom: &[u8]) { + self.ram[ROM_START..ROM_START + rom.len()].clone_from_slice(&rom); + } + fn clock(&mut self) { // fetches the current instruction and increments // the PC (program counter) accordingly @@ -224,89 +307,6 @@ impl Chip8 for Chip8Neo { } self.keys[key as usize] = false; } - - fn load_rom(&mut self, rom: &[u8]) { - self.ram[ROM_START..ROM_START + rom.len()].clone_from_slice(&rom); - } - - fn beep(&self) -> bool { - self.st > 0 - } - - fn pc(&self) -> u16 { - self.pc - } - - fn sp(&self) -> u8 { - self.sp - } - - fn ram(&self) -> Vec<u8> { - self.ram.to_vec() - } - - fn vram(&self) -> Vec<u8> { - self.vram.to_vec() - } - - fn get_state(&self) -> Vec<u8> { - let mut buffer: Vec<u8> = Vec::new(); - buffer.extend(self.ram.iter()); - buffer.extend(self.vram.iter()); - buffer.extend(self.stack.map(|v| v.to_le_bytes()).iter().flatten()); - buffer.extend(self.regs.iter()); - buffer.extend(self.pc.to_le_bytes().iter()); - buffer.extend(self.i.to_le_bytes().iter()); - buffer.extend(self.sp.to_le_bytes().iter()); - buffer.extend(self.dt.to_le_bytes().iter()); - buffer.extend(self.st.to_le_bytes().iter()); - buffer.extend(self.keys.map(|v| v as u8).iter()); - buffer.extend(self.last_key.to_le_bytes().iter()); - buffer - } - - fn set_state(&mut self, state: &[u8]) { - let mut u8_buffer = [0u8; 1]; - let mut u16_buffer = [0u8; 2]; - let mut regs_buffer = [0u8; REGISTERS_SIZE * 2]; - let mut keys_buffer = [0u8; KEYS_SIZE]; - - let mut cursor = Cursor::new(state.to_vec()); - - cursor.read_exact(&mut self.ram).unwrap(); - cursor.read_exact(&mut self.vram).unwrap(); - cursor.read_exact(&mut regs_buffer).unwrap(); - self.stack.clone_from_slice( - regs_buffer - .chunks(2) - .map(|v| { - u16_buffer.clone_from_slice(&v[0..2]); - u16::from_le_bytes(u16_buffer) - }) - .collect::<Vec<u16>>() - .as_slice(), - ); - cursor.read_exact(&mut self.regs).unwrap(); - cursor.read_exact(&mut u16_buffer).unwrap(); - self.pc = u16::from_le_bytes(u16_buffer); - cursor.read_exact(&mut u16_buffer).unwrap(); - self.i = u16::from_le_bytes(u16_buffer); - cursor.read_exact(&mut u8_buffer).unwrap(); - self.sp = u8::from_le_bytes(u8_buffer); - cursor.read_exact(&mut u8_buffer).unwrap(); - self.dt = u8::from_le_bytes(u8_buffer); - cursor.read_exact(&mut u8_buffer).unwrap(); - self.st = u8::from_le_bytes(u8_buffer); - cursor.read_exact(&mut keys_buffer).unwrap(); - self.keys.clone_from_slice( - keys_buffer - .map(|v| if v == 1 { true } else { false }) - .iter() - .as_slice(), - ); - cursor.read_exact(&mut u8_buffer).unwrap(); - self.last_key = u8::from_le_bytes(u8_buffer); - } } #[cfg_attr(feature = "wasm", wasm_bindgen)] @@ -364,6 +364,25 @@ impl Chip8Neo { } } +#[cfg_attr(feature = "wasm", wasm_bindgen)] +impl Chip8Neo { + pub fn load_rom_ws(&mut self, rom: &[u8]) { + self.load_rom(rom) + } + + pub fn reset_ws(&mut self) { + self.reset() + } + + pub fn reset_hard_ws(&mut self) { + self.reset_hard() + } + + pub fn clock_ws(&mut self) { + self.clock() + } +} + impl Default for Chip8Neo { fn default() -> Chip8Neo { Chip8Neo::new() -- GitLab