Deploy site
@@ -1,492 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="640" - height="480" - viewBox="0 0 169.33333 127" - version="1.1" - id="svg8" - inkscape:version="0.92.2 2405546, 2018-03-11" - sodipodi:docname="awaitkwd1.svg"> - <defs - id="defs2"> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0.0" - refX="0.0" - id="marker2498" - style="overflow:visible;" - inkscape:isstock="true"> - <path - id="path2496" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" - transform="scale(0.8) rotate(180) translate(12.5,0)" /> - </marker> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0.0" - refX="0.0" - id="marker2434" - style="overflow:visible;" - inkscape:isstock="true"> - <path - id="path2432" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" - transform="scale(0.8) rotate(180) translate(12.5,0)" /> - </marker> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0.0" - refX="0.0" - id="marker2376" - style="overflow:visible;" - inkscape:isstock="true"> - <path - id="path2374" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" - transform="scale(0.8) rotate(180) translate(12.5,0)" /> - </marker> - <marker - inkscape:isstock="true" - style="overflow:visible;" - id="marker2306" - refX="0.0" - refY="0.0" - orient="auto" - inkscape:stockid="Arrow1Lend" - inkscape:collect="always"> - <path - transform="scale(0.8) rotate(180) translate(12.5,0)" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - id="path2304" /> - </marker> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0.0" - refX="0.0" - id="marker2035" - style="overflow:visible;" - inkscape:isstock="true" - inkscape:collect="always"> - <path - id="path2033" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" - transform="scale(0.8) rotate(180) translate(12.5,0)" /> - </marker> - <marker - inkscape:isstock="true" - style="overflow:visible;" - id="marker1920" - refX="0.0" - refY="0.0" - orient="auto" - inkscape:stockid="Arrow1Lend"> - <path - transform="scale(0.8) rotate(180) translate(12.5,0)" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - id="path1918" /> - </marker> - <marker - inkscape:isstock="true" - style="overflow:visible;" - id="marker1441" - refX="0.0" - refY="0.0" - orient="auto" - inkscape:stockid="Arrow1Lend" - inkscape:collect="always"> - <path - transform="scale(0.8) rotate(180) translate(12.5,0)" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - id="path1439" /> - </marker> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0.0" - refX="0.0" - id="marker1399" - style="overflow:visible;" - inkscape:isstock="true" - inkscape:collect="always"> - <path - id="path1397" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" - transform="scale(0.8) rotate(180) translate(12.5,0)" /> - </marker> - <marker - inkscape:isstock="true" - style="overflow:visible;" - id="marker1363" - refX="0.0" - refY="0.0" - orient="auto" - inkscape:stockid="Arrow1Lend" - inkscape:collect="always"> - <path - transform="scale(0.8) rotate(180) translate(12.5,0)" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - id="path1361" /> - </marker> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0.0" - refX="0.0" - id="Arrow1Lend" - style="overflow:visible;" - inkscape:isstock="true" - inkscape:collect="always"> - <path - id="path1074" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" - transform="scale(0.8) rotate(180) translate(12.5,0)" /> - </marker> - </defs> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="0.98994949" - inkscape:cx="180.99756" - inkscape:cy="228.4128" - inkscape:document-units="mm" - inkscape:current-layer="layer1" - showgrid="false" - units="px" - inkscape:window-width="1366" - inkscape:window-height="739" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" /> - <metadata - id="metadata5"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title /> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-169.99997)"> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="8.8198938" - y="183.27678" - id="text831"><tspan - sodipodi:role="line" - id="tspan829" - x="8.8198938" - y="192.64055" - style="stroke-width:0.26458332" /></text> - <g - id="g929" - transform="translate(-3.8334857,-1.0690781)"> - <rect - y="173.66089" - x="99.108299" - height="59.932461" - width="71.760002" - id="rect812" - style="opacity:1;fill:#d6ffff;fill-opacity:1;stroke:#000000;stroke-width:0.17683256;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - <rect - y="175.69913" - x="101.14654" - height="9.8860025" - width="39.184521" - id="rect827" - style="opacity:1;fill:#d8ffff;fill-opacity:1;stroke:#000000;stroke-width:0.17793897;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - <text - id="text835" - y="183.15857" - x="102.76424" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.11755896px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.17793897" - xml:space="preserve"><tspan - style="stroke-width:0.17793897" - y="183.15857" - x="102.76424" - id="tspan833" - sodipodi:role="line">method 1</tspan></text> - <text - id="text848" - y="190.75735" - x="101.92492" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.51081705px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.11277042" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - y="190.75735" - x="101.92492" - id="tspan846" - sodipodi:role="line">async def m1():</tspan><tspan - id="tspan850" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - y="196.39587" - x="101.92492" - sodipodi:role="line"> prepare request</tspan><tspan - id="tspan852" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - y="202.03439" - x="101.92492" - sodipodi:role="line"> await send request</tspan><tspan - id="tspan854" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - y="207.67291" - x="101.92492" - sodipodi:role="line" /><tspan - id="tspan856" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - y="213.31145" - x="101.92492" - sodipodi:role="line"> await receive request</tspan><tspan - id="tspan860" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - y="218.94997" - x="101.92492" - sodipodi:role="line" /><tspan - id="tspan858" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - y="224.58849" - x="101.92492" - sodipodi:role="line"> process request</tspan><tspan - id="tspan862" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - y="230.22701" - x="101.92492" - sodipodi:role="line"> return result</tspan></text> - </g> - <g - transform="translate(-3.8334857,60.867907)" - id="g957"> - <rect - style="opacity:1;fill:#d6ffff;fill-opacity:1;stroke:#000000;stroke-width:0.17683256;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect931" - width="71.760002" - height="59.932461" - x="99.108299" - y="173.66089" /> - <rect - style="opacity:1;fill:#d8ffff;fill-opacity:1;stroke:#000000;stroke-width:0.17793897;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect933" - width="39.184521" - height="9.8860025" - x="101.14654" - y="175.69913" /> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.11755896px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.17793897" - x="102.76424" - y="183.15857" - id="text937"><tspan - sodipodi:role="line" - id="tspan935" - x="102.76424" - y="183.15857" - style="stroke-width:0.17793897">method 2</tspan></text> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.51081705px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.11277042" - x="101.92492" - y="190.75735" - id="text955"><tspan - sodipodi:role="line" - id="tspan939" - x="101.92492" - y="190.75735" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042">async def m2():</tspan><tspan - sodipodi:role="line" - x="101.92492" - y="196.39587" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - id="tspan941"> prepare parameters</tspan><tspan - sodipodi:role="line" - x="101.92492" - y="202.03439" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - id="tspan943"> await send query</tspan><tspan - sodipodi:role="line" - x="101.92492" - y="207.67291" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - id="tspan945" /><tspan - sodipodi:role="line" - x="101.92492" - y="213.31145" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - id="tspan947"> await receive answer</tspan><tspan - sodipodi:role="line" - x="101.92492" - y="218.94997" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - id="tspan949" /><tspan - sodipodi:role="line" - x="101.92492" - y="224.58849" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - id="tspan951"> process answer</tspan><tspan - sodipodi:role="line" - x="101.92492" - y="230.22701" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - id="tspan953"> return result</tspan></text> - </g> - <rect - style="opacity:1;fill:#faffb5;fill-opacity:1;stroke:#000000;stroke-width:0.26830563;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect959" - width="87.068367" - height="121.70461" - x="2.6476943" - y="172.26968" /> - <rect - y="174.00694" - x="3.9172246" - height="9.8860025" - width="39.184525" - id="rect963" - style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.17793897;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - <text - id="text967" - y="180.99866" - x="4.7331147" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.11755896px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.17793897" - xml:space="preserve"><tspan - style="stroke-width:0.17793897" - y="180.99866" - x="4.7331147" - id="tspan965" - sodipodi:role="line">event loop</tspan></text> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.54473734px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.16361843" - x="6.6373014" - y="190.92131" - id="text1043"><tspan - sodipodi:role="line" - id="tspan1041" - x="6.6373014" - y="190.92131" - style="stroke-width:0.16361843">no events</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="199.10223" - style="stroke-width:0.16361843" - id="tspan1045">enter m1</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="207.28316" - style="stroke-width:0.16361843" - id="tspan1047">pause m1</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="215.46408" - style="stroke-width:0.16361843" - id="tspan1049">enter m2</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="223.645" - style="stroke-width:0.16361843" - id="tspan1051">pause m2</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="231.82591" - style="stroke-width:0.16361843" - id="tspan1053">check events</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="240.00684" - style="stroke-width:0.16361843" - id="tspan1055">resume m2</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="248.18776" - style="stroke-width:0.16361843" - id="tspan1057">pause m2</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="256.36868" - style="stroke-width:0.16361843;fill:#7f7f7f;fill-opacity:1" - id="tspan1059">check events</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="264.54959" - style="stroke-width:0.16361843;fill:#7f7f7f;fill-opacity:1" - id="tspan1061">resume m1</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="272.73053" - style="stroke-width:0.16361843;fill:#7f7f7f;fill-opacity:1" - id="tspan1063">pause m1</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="280.91144" - style="stroke-width:0.16361843;fill:#7f7f7f;fill-opacity:1" - id="tspan1065">resume m2</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="289.09238" - style="stroke-width:0.16361843;fill:#7f7f7f;fill-opacity:1" - id="tspan1067">...</tspan></text> - <path - style="fill:none;stroke:#000000;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none;marker-end:url(#Arrow1Lend)" - d="m 39.288619,196.50663 65.748301,-3.20724" - id="path1069" - inkscape:connector-curvature="0" /> - <path - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker1363)" - d="M 106.96726,199.48211 42.144344,204.3958" - id="path1353" - inkscape:connector-curvature="0" /> - <path - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker1399)" - d="m 39.309524,212.71128 64.391056,42.32737" - id="path1389" - inkscape:connector-curvature="0" /> - <path - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker1441)" - d="M 105.54985,260.99774 43.832203,220.82815" - id="path1431" - inkscape:connector-curvature="0" /> - <path - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker2035)" - d="m 46.113094,237.65771 58.397326,28.5372" - id="path2288" - inkscape:connector-curvature="0" /> - <path - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker2306)" - d="M 106.02232,272.43152 44.60119,245.78419" - id="path2290" - inkscape:connector-curvature="0" /> - </g> -</svg>
@@ -1,507 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="640" - height="480" - viewBox="0 0 169.33333 127" - version="1.1" - id="svg8" - inkscape:version="0.92.2 2405546, 2018-03-11" - sodipodi:docname="awaitkwd2.svg"> - <defs - id="defs2"> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0.0" - refX="0.0" - id="marker2498" - style="overflow:visible;" - inkscape:isstock="true"> - <path - id="path2496" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" - transform="scale(0.8) rotate(180) translate(12.5,0)" /> - </marker> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0.0" - refX="0.0" - id="marker2434" - style="overflow:visible;" - inkscape:isstock="true"> - <path - id="path2432" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" - transform="scale(0.8) rotate(180) translate(12.5,0)" /> - </marker> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0.0" - refX="0.0" - id="marker2376" - style="overflow:visible;" - inkscape:isstock="true"> - <path - id="path2374" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" - transform="scale(0.8) rotate(180) translate(12.5,0)" /> - </marker> - <marker - inkscape:isstock="true" - style="overflow:visible;" - id="marker2306" - refX="0.0" - refY="0.0" - orient="auto" - inkscape:stockid="Arrow1Lend" - inkscape:collect="always"> - <path - transform="scale(0.8) rotate(180) translate(12.5,0)" - style="fill-rule:evenodd;stroke:#7f7f7f;stroke-width:1pt;stroke-opacity:1;fill:#7f7f7f;fill-opacity:1" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - id="path2304" /> - </marker> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0.0" - refX="0.0" - id="marker2035" - style="overflow:visible;" - inkscape:isstock="true" - inkscape:collect="always"> - <path - id="path2033" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - style="fill-rule:evenodd;stroke:#7f7f7f;stroke-width:1pt;stroke-opacity:1;fill:#7f7f7f;fill-opacity:1" - transform="scale(0.8) rotate(180) translate(12.5,0)" /> - </marker> - <marker - inkscape:isstock="true" - style="overflow:visible;" - id="marker1920" - refX="0.0" - refY="0.0" - orient="auto" - inkscape:stockid="Arrow1Lend"> - <path - transform="scale(0.8) rotate(180) translate(12.5,0)" - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - id="path1918" /> - </marker> - <marker - inkscape:isstock="true" - style="overflow:visible;" - id="marker1441" - refX="0.0" - refY="0.0" - orient="auto" - inkscape:stockid="Arrow1Lend" - inkscape:collect="always"> - <path - transform="scale(0.8) rotate(180) translate(12.5,0)" - style="fill-rule:evenodd;stroke:#7f7f7f;stroke-width:1pt;stroke-opacity:1;fill:#7f7f7f;fill-opacity:1" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - id="path1439" /> - </marker> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0.0" - refX="0.0" - id="marker1399" - style="overflow:visible;" - inkscape:isstock="true" - inkscape:collect="always"> - <path - id="path1397" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - style="fill-rule:evenodd;stroke:#7f7f7f;stroke-width:1pt;stroke-opacity:1;fill:#7f7f7f;fill-opacity:1" - transform="scale(0.8) rotate(180) translate(12.5,0)" /> - </marker> - <marker - inkscape:isstock="true" - style="overflow:visible;" - id="marker1363" - refX="0.0" - refY="0.0" - orient="auto" - inkscape:stockid="Arrow1Lend" - inkscape:collect="always"> - <path - transform="scale(0.8) rotate(180) translate(12.5,0)" - style="fill-rule:evenodd;stroke:#7f7f7f;stroke-width:1pt;stroke-opacity:1;fill:#7f7f7f;fill-opacity:1" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - id="path1361" /> - </marker> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0.0" - refX="0.0" - id="Arrow1Lend" - style="overflow:visible;" - inkscape:isstock="true" - inkscape:collect="always"> - <path - id="path1074" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - style="fill-rule:evenodd;stroke:#7f7f7f;stroke-width:1pt;stroke-opacity:1;fill:#7f7f7f;fill-opacity:1" - transform="scale(0.8) rotate(180) translate(12.5,0)" /> - </marker> - </defs> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="0.98994949" - inkscape:cx="172.66711" - inkscape:cy="250.50475" - inkscape:document-units="mm" - inkscape:current-layer="layer1" - showgrid="false" - units="px" - inkscape:window-width="1366" - inkscape:window-height="739" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" /> - <metadata - id="metadata5"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title /> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-169.99997)"> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="8.8198938" - y="183.27678" - id="text831"><tspan - sodipodi:role="line" - id="tspan829" - x="8.8198938" - y="192.64055" - style="stroke-width:0.26458332" /></text> - <g - id="g929" - transform="translate(-3.8334857,-1.0690781)"> - <rect - y="173.66089" - x="99.108299" - height="59.932461" - width="71.760002" - id="rect812" - style="opacity:1;fill:#d6ffff;fill-opacity:1;stroke:#000000;stroke-width:0.17683256;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - <rect - y="175.69913" - x="101.14654" - height="9.8860025" - width="39.184521" - id="rect827" - style="opacity:1;fill:#d8ffff;fill-opacity:1;stroke:#000000;stroke-width:0.17793897;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - <text - id="text835" - y="183.15857" - x="102.76424" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.11755896px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.17793897" - xml:space="preserve"><tspan - style="stroke-width:0.17793897" - y="183.15857" - x="102.76424" - id="tspan833" - sodipodi:role="line">method 1</tspan></text> - <text - id="text848" - y="190.75735" - x="101.92492" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.51081705px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.11277042" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - y="190.75735" - x="101.92492" - id="tspan846" - sodipodi:role="line">async def m1():</tspan><tspan - id="tspan850" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - y="196.39587" - x="101.92492" - sodipodi:role="line"> prepare request</tspan><tspan - id="tspan852" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - y="202.03439" - x="101.92492" - sodipodi:role="line"> await send request</tspan><tspan - id="tspan854" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - y="207.67291" - x="101.92492" - sodipodi:role="line" /><tspan - id="tspan856" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - y="213.31145" - x="101.92492" - sodipodi:role="line"> await receive request</tspan><tspan - id="tspan860" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - y="218.94997" - x="101.92492" - sodipodi:role="line" /><tspan - id="tspan858" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - y="224.58849" - x="101.92492" - sodipodi:role="line"> process request</tspan><tspan - id="tspan862" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - y="230.22701" - x="101.92492" - sodipodi:role="line"> return result</tspan></text> - </g> - <g - transform="translate(-3.8334857,60.867907)" - id="g957"> - <rect - style="opacity:1;fill:#d6ffff;fill-opacity:1;stroke:#000000;stroke-width:0.17683256;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect931" - width="71.760002" - height="59.932461" - x="99.108299" - y="173.66089" /> - <rect - style="opacity:1;fill:#d8ffff;fill-opacity:1;stroke:#000000;stroke-width:0.17793897;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect933" - width="39.184521" - height="9.8860025" - x="101.14654" - y="175.69913" /> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.11755896px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.17793897" - x="102.76424" - y="183.15857" - id="text937"><tspan - sodipodi:role="line" - id="tspan935" - x="102.76424" - y="183.15857" - style="stroke-width:0.17793897">method 2</tspan></text> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.51081705px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.11277042" - x="101.92492" - y="190.75735" - id="text955"><tspan - sodipodi:role="line" - id="tspan939" - x="101.92492" - y="190.75735" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042">async def m2():</tspan><tspan - sodipodi:role="line" - x="101.92492" - y="196.39587" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - id="tspan941"> prepare parameters</tspan><tspan - sodipodi:role="line" - x="101.92492" - y="202.03439" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - id="tspan943"> await send query</tspan><tspan - sodipodi:role="line" - x="101.92492" - y="207.67291" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - id="tspan945" /><tspan - sodipodi:role="line" - x="101.92492" - y="213.31145" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - id="tspan947"> await receive answer</tspan><tspan - sodipodi:role="line" - x="101.92492" - y="218.94997" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - id="tspan949" /><tspan - sodipodi:role="line" - x="101.92492" - y="224.58849" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - id="tspan951"> process answer</tspan><tspan - sodipodi:role="line" - x="101.92492" - y="230.22701" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.11277042" - id="tspan953"> return result</tspan></text> - </g> - <rect - style="opacity:1;fill:#faffb5;fill-opacity:1;stroke:#000000;stroke-width:0.26830563;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect959" - width="87.068367" - height="121.70461" - x="2.6476943" - y="172.26968" /> - <rect - y="174.00694" - x="3.9172246" - height="9.8860025" - width="39.184525" - id="rect963" - style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.17793897;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - <text - id="text967" - y="180.99866" - x="4.7331147" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.11755896px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.17793897" - xml:space="preserve"><tspan - style="stroke-width:0.17793897" - y="180.99866" - x="4.7331147" - id="tspan965" - sodipodi:role="line">event loop</tspan></text> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.54473734px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.16361843" - x="6.6373014" - y="190.92131" - id="text1043"><tspan - sodipodi:role="line" - id="tspan1041" - x="6.6373014" - y="190.92131" - style="stroke-width:0.16361843;fill:#7f7f7f;fill-opacity:1">no events</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="199.10223" - style="stroke-width:0.16361843;fill:#7f7f7f;fill-opacity:1" - id="tspan1045">enter m1</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="207.28316" - style="stroke-width:0.16361843;fill:#7f7f7f;fill-opacity:1" - id="tspan1047">pause m1</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="215.46408" - style="stroke-width:0.16361843;fill:#7f7f7f;fill-opacity:1" - id="tspan1049">enter m2</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="223.645" - style="stroke-width:0.16361843;fill:#7f7f7f;fill-opacity:1" - id="tspan1051">pause m2</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="231.82591" - style="stroke-width:0.16361843;fill:#7f7f7f;fill-opacity:1" - id="tspan1053">check events</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="240.00684" - style="stroke-width:0.16361843;fill:#7f7f7f;fill-opacity:1" - id="tspan1055">resume m2</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="248.18776" - style="stroke-width:0.16361843;fill:#7f7f7f;fill-opacity:1" - id="tspan1057">pause m2</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="256.36868" - style="stroke-width:0.16361843" - id="tspan1059">check events</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="264.54959" - style="stroke-width:0.16361843" - id="tspan1061">resume m1</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="272.73053" - style="stroke-width:0.16361843" - id="tspan1063">pause m1</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="280.91144" - style="stroke-width:0.16361843" - id="tspan1065">resume m2</tspan><tspan - sodipodi:role="line" - x="6.6373014" - y="289.09238" - style="stroke-width:0.16361843" - id="tspan1067">...</tspan></text> - <path - style="fill:none;stroke:#7f7f7f;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none;marker-end:url(#Arrow1Lend)" - d="m 39.288619,196.50663 65.748301,-3.20724" - id="path1069" - inkscape:connector-curvature="0" /> - <path - style="fill:none;stroke:#7f7f7f;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker1363)" - d="M 106.96726,199.48211 42.144344,204.3958" - id="path1353" - inkscape:connector-curvature="0" /> - <path - style="fill:none;stroke:#7f7f7f;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker1399)" - d="m 39.309524,212.71128 64.391056,42.32737" - id="path1389" - inkscape:connector-curvature="0" /> - <path - style="fill:none;stroke:#7f7f7f;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker1441)" - d="M 105.54985,260.99774 43.832203,220.82815" - id="path1431" - inkscape:connector-curvature="0" /> - <path - style="fill:none;stroke:#7f7f7f;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker2035)" - d="m 46.113094,237.65771 58.397326,28.5372" - id="path2288" - inkscape:connector-curvature="0" /> - <path - style="fill:none;stroke:#7f7f7f;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker2306)" - d="M 106.02232,272.43152 44.60119,245.78419" - id="path2290" - inkscape:connector-curvature="0" /> - <path - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker2434)" - d="M 48.758927,261.47021 105.45536,205.52973" - id="path2362" - inkscape:connector-curvature="0" /> - <path - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker2376)" - d="M 106.21131,213.65622 42.900298,270.35265" - id="path2364" - inkscape:connector-curvature="0" /> - <path - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker2498)" - d="m 46.302083,277.91217 58.586307,0.37798" - id="path2366" - inkscape:connector-curvature="0" /> - </g> -</svg>
@@ -1,166 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="640" - height="480" - viewBox="0 0 169.33333 127" - version="1.1" - id="svg8" - inkscape:version="0.92.2 2405546, 2018-03-11" - sodipodi:docname="eventloop.svg"> - <defs - id="defs2"> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0.0" - refX="0.0" - id="Arrow1Lend" - style="overflow:visible;" - inkscape:isstock="true"> - <path - id="path855" - d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " - style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" - transform="scale(0.8) rotate(180) translate(12.5,0)" /> - </marker> - </defs> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="0.7" - inkscape:cx="316.89692" - inkscape:cy="221.88288" - inkscape:document-units="mm" - inkscape:current-layer="layer1" - showgrid="false" - units="px" - inkscape:window-width="1366" - inkscape:window-height="739" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" - showguides="true" /> - <metadata - id="metadata5"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title /> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-169.99995)"> - <rect - style="opacity:1;fill:#ffffc3;fill-opacity:1;stroke:#000000;stroke-width:0.25628251;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect834" - width="163.12053" - height="66.714058" - x="3.1063988" - y="172.44116" /> - <rect - style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.25628251;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect836" - width="72.491341" - height="14.827774" - x="5.7682643" - y="176.38446" /> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.25129986px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25628251" - x="12.724504" - y="186.4527" - id="text840"><tspan - sodipodi:role="line" - id="tspan838" - x="12.724504" - y="186.4527" - style="stroke-width:0.25628251">event loop</tspan></text> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:8.77740383px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.21943507" - x="18.673456" - y="204.51938" - id="text844"><tspan - sodipodi:role="line" - id="tspan842" - x="18.673456" - y="204.51938" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.21943507">while running:</tspan><tspan - sodipodi:role="line" - x="18.673456" - y="215.49113" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.21943507" - id="tspan846"> process events</tspan><tspan - sodipodi:role="line" - x="18.673456" - y="226.46289" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.21943507" - id="tspan848"> call user_function()</tspan></text> - <path - style="fill:none;stroke:#000000;stroke-width:0.25628251px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow1Lend)" - d="m 24.661439,211.12809 v 23.81738 H 150.99704 V 187.8285 H 86.793703 v 5.17769" - id="path850" - inkscape:connector-curvature="0" /> - <rect - y="243.02905" - x="3.1063988" - height="51.462708" - width="163.12053" - id="rect1134" - style="opacity:1;fill:#daffff;fill-opacity:1;stroke:#000000;stroke-width:0.25004876;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - <rect - y="248.85146" - x="5.7682643" - height="14.827774" - width="72.491341" - id="rect1136" - style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.25628251;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - <text - id="text1140" - y="258.91971" - x="12.724504" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.25129986px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25628251" - xml:space="preserve"><tspan - style="stroke-width:0.25628251" - y="258.91971" - x="12.724504" - id="tspan1138" - sodipodi:role="line">your code</tspan></text> - <text - id="text1148" - y="275.38342" - x="14.646159" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:8.77740383px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.21943507" - xml:space="preserve"><tspan - id="tspan1146" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.21943507" - y="275.38342" - x="14.646159" - sodipodi:role="line">async def user_function():</tspan><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:monospace;-inkscape-font-specification:monospace;stroke-width:0.21943507" - y="286.35516" - x="14.646159" - sodipodi:role="line" - id="tspan1155"> ... # some code</tspan></text> - </g> -</svg>
@@ -1,254 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> An Introduction to Asyncio | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>An Introduction to Asyncio</h1><div class=time><p>2018-06-13<p>last updated 2020-10-03</div><h2 id=index>Index</h2><ul><li><a href=https://lonami.dev/blog/asyncio/#background>Background</a><li><a href=https://lonami.dev/blog/asyncio/#input_output>Input / Output</a><li><a href=https://lonami.dev/blog/asyncio/#diving_in>Diving In</a><li><a href=https://lonami.dev/blog/asyncio/#a_toy_example>A Toy Example</a><li><a href=https://lonami.dev/blog/asyncio/#a_real_example>A Real Example</a><li><a href=https://lonami.dev/blog/asyncio/#extra_material>Extra Material</a></ul><h2 id=background>Background</h2><p>After seeing some friends struggle with <code>asyncio</code> I decided that it could be a good idea to write a blog post using my own words to explain how I understand the world of asynchronous IO. I will focus on Python's <code>asyncio</code> module but this post should apply to any other language easily.<p>So what is <code>asyncio</code> and what makes it good? Why don't we just use the old and known threads to run several parts of the code concurrently, at the same time?<p>The first reason is that <code>asyncio</code> makes your code easier to reason about, as opposed to using threads, because the amount of ways in which your code can run grows exponentially. Let's see that with an example. Imagine you have this code:<pre><code class=language-python data-lang=python>def method(): - line 1 - line 2 - line 3 - line 4 - line 5 -</code></pre><p>And you start two threads to run the method at the same time. What is the order in which the lines of code get executed? The answer is that you can't know! The first thread can run the entire method before the second thread even starts. Or it could be the first thread that runs after the second thread. Perhaps both run the "line 1", and then the line 2. Maybe the first thread runs lines 1 and 2, and then the second thread only runs the line 1 before the first thread finishes.<p>As you can see, any combination of the order in which the lines run is possible. If the lines modify some global shared state, that will get messy quickly.<p>Second, in Python, threads <em>won't</em> make your code faster most of the time. It will only increase the concurrency of your program (which is okay if it makes many blocking calls), allowing you to run several things at the same time.<p>If you have a lot of CPU work to do though, threads aren't a real advantage. Indeed, your code will probably run slower under the most common Python implementation, CPython, which makes use of a Global Interpreter Lock (GIL) that only lets a thread run at once. The operations won't run in parallel!<h2 id=input-output>Input / Output</h2><p>Before we go any further, let's first stop to talk about input and output, commonly known as "IO". There are two main ways to perform IO operations, such as reading or writing from a file or a network socket.<p>The first one is known as "blocking IO". What this means is that, when you try performing IO, the current application thread is going to <em>block</em> until the Operative System can tell you it's done. Normally, this is not a problem, since disks are pretty fast anyway, but it can soon become a performance bottleneck. And network IO will be much slower than disk IO!<pre><code class=language-python data-lang=python>import socket - -# Setup a network socket and a very simple HTTP request. -# By default, sockets are open in blocking mode. -sock = socket.socket() -request = b'''HEAD / HTTP/1.0\r -Host: example.com\r -\r -''' - -# "connect" will block until a successful TCP connection -# is made to the host "example.com" on port 80. -sock.connect(('example.com', 80)) - -# "sendall" will repeatedly call "send" until all the data in "request" is -# sent to the host we just connected, which blocks until the data is sent. -sock.sendall(request) - -# "recv" will try to receive up to 1024 bytes from the host, and block until -# there is any data to receive (or empty if the host closes the connection). -response = sock.recv(1024) - -# After all those blocking calls, we got out data! These are the headers from -# making a HTTP request to example.com. -print(response.decode()) -</code></pre><p>Blocking IO offers timeouts, so that you can get control back in your code if the operation doesn't finish. Imagine that the remote host doesn't want to reply, your code would be stuck for as long as the connection remains alive!<p>But wait, what if we make the timeout small? Very, very small? If we do that, we will never block waiting for an answer. That's how asynchronous IO works, and it's the opposite of blocking IO (you can also call it non-blocking IO if you want to).<p>How does non-blocking IO work if the IO device needs a while to answer with the data? In that case, the operative system responds with "not ready", and your application gets control back so it can do other stuff while the IO device completes your request. It works a bit like this:<pre><code><app> Hey, I would like to read 16 bytes from this file -<OS> Okay, but the disk hasn't sent me the data yet -<app> Alright, I will do something else then -(a lot of computer time passes) -<app> Do you have my 16 bytes now? -<OS> Yes, here they are! "Hello, world !!\n" -</code></pre><p>In reality, you can tell the OS to notify you when the data is ready, as opposed to polling (constantly asking the OS whether the data is ready yet or not), which is more efficient.<p>But either way, that's the difference between blocking and non-blocking IO, and what matters is that your application gets to run more without ever needing to wait for data to arrive, because the data will be there immediately when you ask, and if it's not yet, your app can do more things meanwhile.<h2 id=diving-in>Diving In</h2><p>Now we've seen what blocking and non-blocking IO is, and how threads make your code harder to reason about, but they give concurrency (yet not more speed). Is there any other way to achieve this concurrency that doesn't involve threads? Yes! The answer is <code>asyncio</code>.<p>So how does <code>asyncio</code> help? First we need to understand a very crucial concept before we can dive any deeper, and I'm talking about the <em>event loop</em>. What is it and why do we need it?<p>You can think of the event loop as a <em>loop</em> that will be responsible for calling your <code>async</code> functions:<p><img src=https://lonami.dev/blog/asyncio/eventloop.svg alt="The Event Loop"><p>That's silly you may think. Now not only we run our code but we also have to run some "event loop". It doesn't sound beneficial at all. What are these events? Well, they are the IO events we talked about before!<p><code>asyncio</code>'s event loop is responsible for handling those IO events, such as file is ready, data arrived, flushing is done, and so on. As we saw before, we can make these events non-blocking by setting their timeout to 0.<p>Let's say you want to read from 10 files at the same time. You will ask the OS to read data from 10 files, and at first none of the reads will be ready. But the event loop will be constantly asking the OS to know which are done, and when they are done, you will get your data.<p>This has some nice advantages. It means that, instead of waiting for a network request to send you a response or some file, instead of blocking there, the event loop can decide to run other code meanwhile. Whenever the contents are ready, they can be read, and your code can continue. Waiting for the contents to be received is done with the <code>await</code> keyword, and it tells the loop that it can run other code meanwhile:<p><img src=https://lonami.dev/blog/asyncio/awaitkwd1.svg alt="Step 1, await keyword"><p><img src=https://lonami.dev/blog/asyncio/awaitkwd2.svg alt="Step 2, await keyword"><p>Start reading the code of the event loop and follow the arrows. You can see that, in the beginning, there are no events yet, so the loop calls one of your functions. The code runs until it has to <code>await</code> for some IO operation to complete, such as sending a request over the network. The method is "paused" until an event occurs (for example, an "event" occurs when the request has been sent completely).<p>While the first method is busy, the event loop can enter the second method, and run its code until the first <code>await</code>. But it can happen that the event of the second query occurs before the request on the first method, so the event loop can re-enter the second method because it has already sent the query, but the first method isn't done sending the request yet.<p>Then, the second method <code>await</code>'s for an answer, and an event occurs telling the event loop that the request from the first method was sent. The code can be resumed again, until it has to <code>await</code> for a response, and so on. Here's an explanation with pseudo-code for this process if you prefer:<pre><code class=language-python data-lang=python>async def method(request): - prepare request - await send request - - await receive request - - process request - return result - -run in parallel ( - method with request 1, - method with request 2, -) -</code></pre><p>This is what the event loop will do on the above pseudo-code:<pre><code>no events pending, can advance - -enter method with request 1 - prepare request - await sending request -pause method with request 1 - -no events ready, can advance - -enter method with request 2 - prepare request - await sending request -pause method with request 2 - -both requests are paused, cannot advance -wait for events -event for request 2 arrives (sending request completed) - -enter method with request 2 - await receiving response -pause method with request 2 - -event for request 1 arrives (sending request completed) - -enter method with request 1 - await receiving response -pause method with request 1 - -...and so on -</code></pre><p>You may be wondering "okay, but threads work for me, so why should I change?". There are some important things to note here. The first is that we only need one thread to be running! The event loop decides when and which methods should run. This results in less pressure for the operating system. The second is that we know when it may run other methods. Those are the <code>await</code> keywords! Whenever there is one of those, we know that the loop is able to run other things until the resource (again, like network) becomes ready (when a event occurs telling us it's ready to be used without blocking or it has completed).<p>So far, we already have two advantages. We are only using a single thread so the cost for switching between methods is low, and we can easily reason about where our program may interleave operations.<p>Another advantage is that, with the event loop, you can easily schedule when a piece of code should run, such as using the method <a href=https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.call_at><code>loop.call_at</code></a>, without the need for spawning another thread at all.<p>To tell the <code>asyncio</code> to run the two methods shown above, we can use <a href=https://docs.python.org/3/library/asyncio-future.html#asyncio.ensure_future><code>asyncio.ensure_future</code></a>, which is a way of saying "I want the future of my method to be ensured". That is, you want to run your method in the future, whenever the loop is free to do so. This method returns a <code>Future</code> object, so if your method returns a value, you can <code>await</code> this future to retrieve its result.<p>What is a <code>Future</code>? This object represents the value of something that will be there in the future, but might not be there yet. Just like you can <code>await</code> your own <code>async def</code> functions, you can <code>await</code> these <code>Future</code>'s.<p>The <code>async def</code> functions are also called "coroutines", and Python does some magic behind the scenes to turn them into such. The coroutines can be <code>await</code>'ed, and this is what you normally do.<h2 id=a-toy-example>A Toy Example</h2><p>That's all about <code>asyncio</code>! Let's wrap up with some example code. We will create a server that replies with the text a client sends, but reversed. First, we will show what you could write with normal synchronous code, and then we will port it.<p>Here is the <strong>synchronous version</strong>:<pre><code class=language-python data-lang=python># server.py -import socket - - -def server_method(): - # create a new server socket to listen for connections - server = socket.socket() - - # bind to localhost:6789 for new connections - server.bind(('localhost', 6789)) - - # we will listen for one client at most - server.listen(1) - - # *block* waiting for a new client - client, _ = server.accept() - - # *block* waiting for some data - data = client.recv(1024) - - # reverse the data - data = data[::-1] - - # *block* sending the data - client.sendall(data) - - # close client and server - server.close() - client.close() - - -if __name__ == '__main__': - # block running the server - server_method() -</code></pre><pre><code class=language-python data-lang=python># client.py -import socket - - -def client_method(): - message = b'Hello Server!\n' - client = socket.socket() - - # *block* trying to stabilish a connection - client.connect(('localhost', 6789)) - - # *block* trying to send the message - print('Sending', message) - client.sendall(message) - - # *block* until we receive a response - response = client.recv(1024) - print('Server replied', response) - - client.close() - - -if __name__ == '__main__': - client_method() -</code></pre><p>From what we've seen, this code will block on all the lines with a comment above them saying that they will block. This means that for running more than one client or server, or both in the same file, you will need threads. But we can do better, we can rewrite it into <code>asyncio</code>!<p>The first step is to mark all your <code>def</code>initions that may block with <code>async</code>. This marks them as coroutines, which can be <code>await</code>ed on.<p>Second, since we're using low-level sockets, we need to make use of the methods that <code>asyncio</code> provides directly. If this was a third-party library, this would be just like using their <code>async def</code>initions.<p>Here is the <strong>asynchronous version</strong>:<pre><code class=language-python data-lang=python># server.py -import asyncio -import socket - -# get the default "event loop" that we will run -loop = asyncio.get_event_loop() - - -# notice our new "async" before the definition -async def server_method(): - server = socket.socket() - server.bind(('localhost', 6789)) - server.listen(1) - - # await for a new client - # the event loop can run other code while we wait here! - client, _ = await loop.sock_accept(server) - - # await for some data - data = await loop.sock_recv(client, 1024) - data = data[::-1] - - # await for sending the data - await loop.sock_sendall(client, data) - - server.close() - client.close() - - -if __name__ == '__main__': - # run the loop until "server method" is complete - loop.run_until_complete(server_method()) -</code></pre><pre><code class=language-python data-lang=python># client.py -import asyncio -import socket - -loop = asyncio.get_event_loop() - - -async def client_method(): - message = b'Hello Server!\n' - client = socket.socket() - - # await to stabilish a connection - await loop.sock_connect(client, ('localhost', 6789)) - - # await to send the message - print('Sending', message) - await loop.sock_sendall(client, message) - - # await to receive a response - response = await loop.sock_recv(client, 1024) - print('Server replied', response) - - client.close() - - -if __name__ == '__main__': - loop.run_until_complete(client_method()) -</code></pre><p>That's it! You can place these two files separately and run, first the server, then the client. You should see output in the client.<p>The big difference here is that you can easily modify the code to run more than one server or clients at the same time. Whenever you <code>await</code> the event loop will run other of your code. It seems to "block" on the <code>await</code> parts, but remember it's actually jumping to run more code, and the event loop will get back to you whenever it can.<p>In short, you need an <code>async def</code> to <code>await</code> things, and you run them with the event loop instead of calling them directly. So this…<pre><code class=language-python data-lang=python>def main(): - ... # some code - - -if __name__ == '__main__': - main() -</code></pre><p>…becomes this:<pre><code class=language-python data-lang=python>import asyncio - - -async def main(): - ... # some code - - -if __name__ == '__main__': - asyncio.get_event_loop().run_until_complete(main) -</code></pre><p>This is pretty much how most of your <code>async</code> scripts will start, running the main method until its completion.<h2 id=a-real-example>A Real Example</h2><p>Let's have some fun with a real library. We'll be using <a href=https://github.com/LonamiWebs/Telethon>Telethon</a> to broadcast a message to our three best friends, all at the same time, thanks to the magic of <code>asyncio</code>. We'll dive right into the code, and then I'll explain our new friend <code>asyncio.wait(...)</code>:<pre><code class=language-python data-lang=python># broadcast.py -import asyncio -import sys - -from telethon import TelegramClient - -# (you need your own values here, check Telethon's documentation) -api_id = 123 -api_hash = '123abc' -friends = [ - '@friend1__username', - '@friend2__username', - '@bestie__username' -] - -# we will have to await things, so we need an async def -async def main(message): - # start is a coroutine, so we need to await it to run it - client = await TelegramClient('me', api_id, api_hash).start() - - # wait for all three client.send_message to complete - await asyncio.wait([ - client.send_message(friend, message) - for friend in friends - ]) - - # and close our client - await client.disconnect() - - -if __name__ == '__main__': - if len(sys.argv) != 2: - print('You must pass the message to broadcast!') - quit() - - message = sys.argv[1] - asyncio.get_event_loop().run_until_complete(main(message)) -</code></pre><p>Wait… how did that send a message to all three of my friends? The magic is done here:<pre><code class=language-python data-lang=python>[ - client.send_message(friend, message) - for friend in friends -] -</code></pre><p>This list comprehension creates another list with three coroutines, the three <code>client.send_message(...)</code>. Then we just pass that list to <code>asyncio.wait</code>:<pre><code class=language-python data-lang=python>await asyncio.wait([...]) -</code></pre><p>This method, by default, waits for the list of coroutines to run until they've all finished. You can read more on the Python <a href=https://docs.python.org/3/library/asyncio-task.html#asyncio.wait>documentation</a>. Truly a good function to know about!<p>Now whenever you have some important news for your friends, you can simply <code>python3 broadcast.py 'I bought a car!'</code> to tell all your friends about your new car! All you need to remember is that you need to <code>await</code> on coroutines, and you will be good. <code>asyncio</code> will warn you when you forget to do so.<h2 id=extra-material>Extra Material</h2><p>If you want to understand how <code>asyncio</code> works under the hood, I recommend you to watch this hour-long talk <a href=https://youtu.be/M-UcUs7IMIM>Get to grips with asyncio in Python 3</a> by Robert Smallshire. In the video, they will explain the differences between concurrency and parallelism, along with others concepts, and how to implement your own <code>asyncio</code> "scheduler" from scratch.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,4099 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"> - <title>Lonami's Site - My Blog</title> - <link href="https://lonami.dev/blog/atom.xml" rel="self" type="application/atom+xml"/> - <link href="https://lonami.dev/blog/"/> + <title>Bi-Rabittoh's Site - BiRabittoh's blog</title> + <link href="https://birabittoh.dev/blog/atom.xml" rel="self" type="application/atom+xml"/> + <link href="https://birabittoh.dev/blog/"/> <generator uri="https://www.getzola.org/">Zola</generator> - <updated>2021-03-13T00:00:00+00:00</updated> - <id>https://lonami.dev/blog/atom.xml</id> + <updated>2021-04-09T00:00:00+00:00</updated> + <id>https://birabittoh.dev/blog/atom.xml</id> <entry xml:lang="en"> - <title>Writing our own Cheat Engine: Pointers</title> - <published>2021-03-13T00:00:00+00:00</published> - <updated>2021-03-13T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/woce-6/" type="text/html"/> - <id>https://lonami.dev/blog/woce-6/</id> - <content type="html"><p>This is part 6 on the <em>Writing our own Cheat Engine</em> series:</p> + <title>Modern web bloat</title> + <published>2021-04-09T00:00:00+00:00</published> + <updated>2021-04-09T00:00:00+00:00</updated> + <link href="https://birabittoh.dev/blog/modern-web-bloat/" type="text/html"/> + <id>https://birabittoh.dev/blog/modern-web-bloat/</id> + <content type="html"><p>This is it. My first blog post; I guess I just became a boomer.</p> +<h2 id="inspiration">Inspiration</h2> +<p>Some time ago I stumbled upon <a href="https://odysee.com/@Luke:7/a-demonstration-of-modern-web-bloat:f">this video</a>, +where the popular Linux influencer <a href="https://lukesmith.xyz">Luke Smith</a> +talked about the effort of looking up a Chicken Parmesan recipe in 2021 +without having any adblock or privacy extensions enabled.</p> +<p>I decided to build this website on <a href="https://www.getzola.org/"><code>zola</code></a>, +which is a modern engine for static sites! I was inspired by <a href="https://lonami.dev/">lonami</a> +and I actually forked <a href="https://github.com/LonamiWebs/lonamiwebs.github.io">his code</a> +to make <a href="https://github.com/Bi-Rabittoh/birabittoh.github.io">mine</a>. I +think our websites are a good example of how clean and fast a webpage +should be.</p> +<p>Yeah, this looks like a first world problem and it probably is, but it's +not as subtle as you think. I'm actually convinced that the internet +<em>could</em> actually benefit from this way of thinking, and that's what I'm +going to talk about in this first article. </p> +<h2 id="the-problem">The problem</h2> +<p>In the early days of the internet, it was common for webpages to be +written using only HTML, so we had very ugly but functional websites.</p> +<p>As technology went on, sites needed to get more modern-looking and +<em>interactive</em>; that's why CSS and JavaScript were introduced into the +mix, allowing for dynamic websites that could actually change based +on user input.</p> +<p>As of nowadays, a lot more stuff went into the mix, to the point where +the browser is now the most common program we use in our OS: you can, in +fact, use it for doing things that 15+ years ago required external +programs, like:</p> <ul> -<li><a href="/blog/woce-1">Part 1: Introduction</a> (start here if you're new to the series!)</li> -<li><a href="/blog/woce-2">Part 2: Exact Value scanning</a></li> -<li><a href="/blog/woce-3">Part 3: Unknown initial value</a></li> -<li><a href="/blog/woce-4">Part 4: Floating points</a></li> -<li><a href="/blog/woce-5">Part 5: Code finder</a></li> -<li>Part 6: Pointers</li> +<li>playing music and video,</li> +<li>reading PDF files,</li> +<li>doing office work,</li> +<li>checking e-mail,</li> +<li>cloud storage,</li> +<li>etc...</li> </ul> -<p>In part 5 we wrote our very own debugger. We learnt that Cheat Engine is using hardware breakpoints to watch memory change, and how to do the same ourselves. We also learnt that hardware points are not the only way to achieve the effect of watchpoints, although they certainly are the fastest and cleanest approach.</p> -<p>In this post, we will be reusing some of that knowledge to find out a closely related value, the <em>pointer</em> that points to the real value<sup class="footnote-reference"><a href="#1">1</a></sup>. As a quick reminder, a pointer is nothing but an <code>usize</code><sup class="footnote-reference"><a href="#2">2</a></sup> representing the address of another portion of memory, in this case, the actual value we will be scanning for. A pointer is a value that, well, points elsewhere. In Rust we normally use reference instead, which are safer (typed and their lifetime is tracked) than pointers, but in the end we can achieve the same with both.</p> -<p>Why care about pointers? It turns out that things, such as your current health in-game, are very unlikely to end up in the same memory position when you restart the game (or even change to another level, or even during gameplay). So, if you perform a scan and find that the address where your health is stored is <code>0x73AABABE</code>, you might be tempted to save it and reuse it next time you launch the game. Now you don't need to scan for it again! Alas, as soon as you restart the game, the health is now stored at <code>0x5AADBEEF</code>.</p> -<p>Not all hope is lost! The game must <em>somehow</em> have a way to reliably find this value, and the way it's done is with pointers. There will always be some base address that holds a pointer, and the game code knows where to find this pointer. If we are also able to find the pointer at said base address, and follow it ourselves (&quot;dereferencing&quot; it), we can perform the same steps the game is doing, and reliably find the health no matter how much we restart the game<sup class="footnote-reference"><a href="#3">3</a></sup>.</p> -<h2 id="code-finder">Code finder</h2> -<details open><summary>Cheat Engine Tutorial: Step 6</summary> -<blockquote> -<p>In the previous step I explained how to use the Code finder to handle changing locations. But that method alone makes it difficult to find the address to set the values you want. That's why there are pointers:</p> -<p>At the bottom you'll find 2 buttons. One will change the value, and the other changes the value AND the location of the value. For this step you don't really need to know assembler, but it helps a lot if you do.</p> -<p>First find the address of the value. When you've found it use the function to find out what accesses this address.</p> -<p>Change the value again, and a item will show in the list. Double click that item. (or select and click on more info) and a new window will open with detailed information on what happened when the instruction ran.</p> -<p>If the assembler instruction doesn't have anything between a '[' and ']' then use another item in the list. If it does it will say what it think will be the value of the pointer you need.</p> -<p>Go back to the main cheat engine window (you can keep this extra info window open if you want, but if you close it, remember what is between the [ and ]) and do a 4 byte scan in hexadecimal for the value the extra info told you. When done scanning it may return 1 or a few hundred addresses. Most of the time the address you need will be the smallest one. Now click on manually add and select the pointer checkbox.</p> -<p>The window will change and allow you to type in the address of a pointer and a offset. Fill in as address the address you just found. If the assembler instruction has a calculation (e.g: [esi+12]) at the end then type the value in that's at the end. else leave it 0. If it was a more complicated instruction look at the calculation.</p> -<p>Example of a more complicated instruction:</p> -<p>[EAX*2+EDX+00000310] eax=4C and edx=00801234.</p> -<p>In this case EDX would be the value the pointer has, and EAX*2+00000310 the offset, so the offset you'd fill in would be 2*4C+00000310=3A8. (this is all in hex, use calc.exe from windows in scientific mode to calculate).</p> -<p>Back to the tutorial, click OK and the address will be added, If all went right the address will show P-&gt;xxxxxxx, with xxxxxxx being the address of the value you found. If thats not right, you've done something wrong. Now, change the value using the pointer you added in 5000 and freeze it. Then click Change pointer, and if all went right the next button will become visible.</p> -<p><em>extra</em>: And you could also use the pointer scanner to find the pointer to this address.</p> -</blockquote> -</details> -<h2 id="on-access-watchpoints">On-access watchpoints</h2> -<p>Last time we managed to learn how hardware breakpoints were being set by observing Cheat Engine's behaviour. I think it's now time to handle this properly instead. We'll check out the <a href="https://wiki.osdev.org/CPU_Registers_x86#Debug_Registers">CPU Registers x86 page on OSDev</a> to learn about it:</p> +<p>I guess people just find it more comfortable if they can do everything +with a single program, and they're not to blame for that. This <em>is</em> +the easiest approach for unexperienced people: just have a program that +does everything, instead of having to learn how to use a bunch of +different software.</p> +<p>This plethora of uses is possible today because of the existence of +various libraries and frameworks that simplify JavaScript and CSS and +make them easier to develop complicated websites with. +This is good for basic web users who just want functional websites, and +great for developers since they can easily code complicated code inside +the browser, making it the perfect cross-platform wet dream we all have.</p> +<p>Sadly, this brings us to the problem: any modern website has become a +burden for any browser to load, since our browser needs to download and +parse through each library used and often fill the page contents as you +scroll through. +In his video, Luke Smith found that a simple Chicken Parmesan recipe +would take up to 5-10 megabytes, which doesn't sound like a lot, but it +actually is.</p> +<p>It's easier to understand it if you think about it with video-games; +any game on 16-bit<sup class="footnote-reference"><a href="#gaming-storage">1</a></sup> consoles and earlier, including +full-fledged 30+ hour adventures like <em>Final Fantasy 6</em> and <em>Chrono +Trigger</em>, weighs less than that one single recipe webpage.</p> +<h2 id="the-solution">The solution</h2> +<p>Well, I don't think this &quot;problem&quot; is getting solved soon, as new +frameworks for web development are constantly being introduced. Sadly, +it's a one-way train, but if you're a web-dev you could actually make a +difference yourself!</p> +<p>I mean, this can not apply to all websites. Some of them just <em>NEED</em> to +be as responsive and interactive as they are; most of them actually just +became bloated at a certain time period (probably mid-2000s) when having +a flashy website was cool and different from what everyone else had.</p> +<p>Nowadays you can be different than other websites by using plain HTML +and CSS for your website: this ensures your pages will load instantly +and be compatible even with the oldest of browsers!</p> +<p>If you like this philosophy, you can check out other projects that aim +for a simpler and faster web, like these:</p> <ul> -<li>DR0, DR1, DR2 and DR3 can hold a memory address each. This address will be used by the breakpoint.</li> -<li>DR4 is actually an <a href="https://en.wikipedia.org/wiki/X86_debug_register">obsolete synonym</a> for DR6.</li> -<li>DR5 is another obsolete synonym, this time for DR7.</li> -<li>DR6 is debug status. The four lowest bits indicate which breakpoint was hit, and the four highest bits contain additional information. We should make sure to clear this ourselves when a breakpoint is hit.</li> -<li>DR7 is debug control, which we need to study more carefully.</li> +<li><a href="https://based.cooking/">based.cooking</a>, a modern recipe website based +on collaboration via GitHub;</li> +<li><a href="https://wiby.me/">wiby.me</a>, a search engine that aims to only index +classic style webpages.</li> </ul> -<p>Each debug register DR0 through DR3 has two corresponding bits in DR7, starting from the lowest-order bit, to indicate whether the corresponding register is a <strong>L</strong>ocal or <strong>G</strong>lobal breakpoint. So it looks like this:</p> -<pre><code> Meaning: [ .. .. | G3 | L3 | G2 | L2 | G1 | L1 | G0 | L0 ] -Bit-index: 31-08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 | 00 -</code></pre> -<p>Cheat Engine was using local breakpoints, because the zeroth bit was set. Probably because we don't want these breakpoints to infect other programs! Because we were using only one breakpoint, only the lowermost bit was being set. The local 1st, 2nd and 3rd bits were unset.</p> -<p>Now, each debug register DR0 through DR4 has four additional bits in DR7, two for the <strong>C</strong>ondition and another two for the <strong>S</strong>ize:</p> -<pre><code> Meaning: [ S3 | C3 | S2 | C2 | S1 | C1 | S0 | C0 | .. .. ] -Bit-index: 31 30 | 29 28 | 27 26 | 25 24 | 23 22 | 21 20 | 19 18 | 17 16 | 15-00 -</code></pre> -<p>The two bits of the condition mean the following:</p> -<ul> -<li><code>00</code> execution breakpoint.</li> -<li><code>01</code> write watchpoint.</li> -<li><code>11</code> read/write watchpoint.</li> -<li><code>10</code> unsupported I/O read/write.</li> -</ul> -<p>When we were using Cheat Engine to add write watchpoints, the bits 17 and 16 were indeed set to <code>01</code>, and the bits 19 and 18 were set to <code>11</code>. Hm, but <em>11<sub>2</sub> = 3<sub>10</sub></em> , and yet, we were watching writes to 4 bytes. So what's up with this? Is there a different mapping for the size which isn't documented at the time of writing? Seems we need to learn from Cheat Engine's behaviour one more time.</p> -<p>For reference, this is what DR7 looked like when we added a single write watchpoint:</p> -<pre><code>hex: 000d_0001 -bin: 00000000_00001101_00000000_00000001 -</code></pre> -<p>And this is the code I will be using to check the breakpoints of different sizes:</p> -<pre><code>thread::enum_threads(pid) - .unwrap() - .into_iter() - .for_each(|tid| { - let thread = thread::Thread::open(tid).unwrap(); - let ctx = thread.get_context().unwrap(); - eprintln!(&quot;hex: {:08x}&quot;, ctx.Dr7); - eprintln!(&quot;bin: {:032b}&quot;, ctx.Dr7); - }); -</code></pre> -<p>Let's compare this to watchpoints for sizes 1, 2, 4 and 8 bytes:</p> -<pre><code>1 byte -hex: 0001_0401 -bin: 00000000_00000001_00000100_00000001 - -2 bytes -hex: 0005_0401 -bin: 00000000_00000101_00000100_00000001 - -4 bytes -hex: 000d_0401 -bin: 00000000_00001101_00000100_00000001 - -8 bytes -hex: 0009_0401 -bin: 00000000_00001001_00000100_00000001 - ^ wut? -</code></pre> -<p>I have no idea what's up with that stray tenth bit. Its use does not seem documented, and things worked fine without it, so we'll ignore it. The lowest bit is set to indicate we're using DR0, bits 17 and 16 represent the write watchpoint, and the size seems to be as follows:</p> -<ul> -<li><code>00</code> for a single byte.</li> -<li><code>01</code> for two bytes (a &quot;word&quot;).</li> -<li><code>11</code> for four bytes (a &quot;double word&quot;).</li> -<li><code>10</code> for eight bytes (a &quot;quadruple word&quot;).</li> -</ul> -<p>Doesn't make much sense if you ask me, but we'll roll with it. Just to confirm, this is what the &quot;on-access&quot; breakpoint looks like according to Cheat Engine:</p> -<pre><code>hex: 000f_0401 -bin: 00000000_00001111_00000100_00000001 -</code></pre> -<p>So it all checks out! The bit pattern is <code>11</code> for read/write (technically, a write is also an access). Let's implement this!</p> -<h2 id="proper-breakpoint-handling">Proper breakpoint handling</h2> -<p>The first thing we need to do is represent the possible breakpoint conditions:</p> -<pre><code class="language-rust" data-lang="rust">#[repr(u8)] -pub enum Condition { - Execute = 0b00, - Write = 0b01, - Access = 0b11, -} -</code></pre> -<p>And also the legal breakpoint sizes:</p> -<pre><code class="language-rust" data-lang="rust">#[repr(u8)] -pub enum Size { - Byte = 0b00, - Word = 0b01, - DoubleWord = 0b11, - QuadWord = 0b10, -} -</code></pre> -<p>We are using <code>#[repr(u8)]</code> so that we can convert a given variant into the corresponding bit pattern. With the right types defined in order to set a breakpoint, we can start implementing the method that will set them (inside <code>impl Thread</code>):</p> -<pre><code class="language-rust" data-lang="rust">pub fn add_breakpoint(&amp;self, addr: usize, cond: Condition, size: Size) -&gt; io::Result&lt;Breakpoint&gt; { - let mut context = self.get_context()?; - todo!() -} -</code></pre> -<p>First, let's try finding an &quot;open spot&quot; where we could set our breakpoint. We will &quot;slide&quot; a the <code>0b11</code> bitmask over the lowest eight bits, and if and only if both the local and global bits are unset, then we're free to set a breakpoint at this index<sup class="footnote-reference"><a href="#4">4</a></sup>:</p> -<pre><code class="language-rust" data-lang="rust">let index = (0..4) - .find_map(|i| ((context.Dr7 &amp; (0b11 &lt;&lt; (i * 2))) == 0).then(|| i)) - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, &quot;no debug register available&quot;))?; -</code></pre> -<p>Once an <code>index</code> is found, we can set the address we want to watch in the corresponding register and update the debug control bits:</p> -<pre><code class="language-rust" data-lang="rust">let addr = addr as u64; -match index { - 0 =&gt; context.Dr0 = addr, - 1 =&gt; context.Dr1 = addr, - 2 =&gt; context.Dr2 = addr, - 3 =&gt; context.Dr3 = addr, - _ =&gt; unreachable!(), -} - -let clear_mask = !((0b1111 &lt;&lt; (16 + index * 4)) | (0b11 &lt;&lt; (index * 2))); -context.Dr7 &amp;= clear_mask; - -context.Dr7 |= 1 &lt;&lt; (index * 2); - -let sc = (((size as u8) &lt;&lt; 2) | (cond as u8)) as u64; -context.Dr7 |= sc &lt;&lt; (16 + index * 4); - -self.set_context(&amp;context)?; -Ok(Breakpoint { - thread: self, - clear_mask, -}) -</code></pre> -<p>Note that we're first creating a &quot;clear mask&quot;. We switch on all the bits that we may use for this breakpoint, and then negate. Effectively, <code>Dr7 &amp; clear_mask</code> will make sure we don't leave any bit high on accident. We apply the mask before OR-ing the rest of bits to also clear any potential garbage on the size and condition bits. Next, we set the bit to enable the new local breakpoint, and also store the size and condition bits at the right location.</p> -<p>With the context updated, we can set it back and return the <code>Breakpoint</code>. It stores the <code>thread</code> and the <code>clear_mask</code> so that it can clean up on <code>Drop</code>. We are technically relying on <code>Drop</code> to run behaviour here, but the cleanup is done on a best-effort basis. If the user intentionally forgets the <code>Breakpoint</code>, maybe they want the <code>Breakpoint</code> to forever be set.</p> -<p>This logic is begging for a testcase though; I'll split it into a new <code>Breakpoint::update_dbg_control</code> method and test that out:</p> -<pre><code class="language-rust" data-lang="rust"> -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn brk_add_one() { - // DR7 starts with garbage which should be respected. - let (clear_mask, dr, dr7) = - Breakpoint::update_dbg_control(0x1700, Condition::Write, Size::DoubleWord).unwrap(); - - assert_eq!(clear_mask, 0xffff_ffff_fff0_fffc); - assert_eq!(dr, DebugRegister::Dr0); - assert_eq!(dr7, 0x0000_0000_000d_1701); - } - - #[test] - fn brk_add_two() { - let (clear_mask, dr, dr7) = Breakpoint::update_dbg_control( - 0x0000_0000_000d_0001, - Condition::Write, - Size::DoubleWord, - ) - .unwrap(); - - assert_eq!(clear_mask, 0xffff_ffff_ff0f_fff3); - assert_eq!(dr, DebugRegister::Dr1); - assert_eq!(dr7, 0x0000_0000_00dd_0005); - } - - #[test] - fn brk_try_add_when_max() { - assert!(Breakpoint::update_dbg_control( - 0x0000_0000_dddd_0055, - Condition::Write, - Size::DoubleWord - ) - .is_none()); - } -} -</code></pre> -<pre><code>running 3 tests -test thread::tests::brk_add_one ... ok -test thread::tests::brk_add_two ... ok -test thread::tests::brk_try_add_when_max ... ok -</code></pre> -<p>Very good! With proper breakpoint handling usable, we can continue.</p> -<h2 id="inferring-the-pointer-value">Inferring the pointer value</h2> -<p>After scanning memory for the location we're looking for (say, our current health), we then add an access watchpoint, and wait for an exception to occur. As a reminder, here's the page with the <a href="https://docs.microsoft.com/en-us/windows/win32/debug/debugging-events">Debugging Events</a>:</p> -<pre><code class="language-rust" data-lang="rust">let addr = ...; -let mut threads = ...; - -let _watchpoints = threads - .iter_mut() - .map(|thread| { - thread - .add_breakpoint(addr, thread::Condition::Access, thread::Size::DoubleWord) - .unwrap() - }) - .collect::&lt;Vec&lt;_&gt;&gt;(); - -loop { - let event = debugger.wait_event(None).unwrap(); - if event.dwDebugEventCode == winapi::um::minwinbase::EXCEPTION_DEBUG_EVENT { - let exc = unsafe { event.u.Exception() }; - if exc.ExceptionRecord.ExceptionCode == winapi::um::minwinbase::EXCEPTION_SINGLE_STEP { - todo!(); - } - } - debugger.cont(event, true).unwrap(); -} -</code></pre> -<p>Now, inside the <code>todo!()</code> we will want to do a few things, namely printing out the instructions &quot;around this location&quot; and dumping the entire thread context on screen. To print the instructions, we need to import <code>iced_x86</code> again, iterate over all memory regions to find the region where the exception happened, read the corresponding bytes, decode the instructions, and when we find the one with a corresponding instruction pointer, print &quot;around it&quot;:</p> -<pre><code class="language-rust" data-lang="rust">use iced_x86::{Decoder, DecoderOptions, Formatter, Instruction, NasmFormatter}; - -let addr = exc.ExceptionRecord.ExceptionAddress as usize; -let region = process - .memory_regions() - .into_iter() - .find(|region| { - let base = region.BaseAddress as usize; - base &lt;= addr &amp;&amp; addr &lt; base + region.RegionSize - }) - .unwrap(); - -let bytes = process - .read_memory(region.BaseAddress as usize, region.RegionSize) - .unwrap(); - -let mut decoder = Decoder::new(64, &amp;bytes, DecoderOptions::NONE); -decoder.set_ip(region.BaseAddress as _); - -let mut formatter = NasmFormatter::new(); -let mut output = String::new(); - -let instructions = decoder.into_iter().collect::&lt;Vec&lt;_&gt;&gt;(); -for (i, ins) in instructions.iter().enumerate() { - if ins.next_ip() as usize == addr { - let low = i.saturating_sub(5); - let high = (i + 5).min(instructions.len()); - for j in low..high { - let ins = &amp;instructions[j]; - print!(&quot;{} {:016X} &quot;, if j == i { &quot;&gt;&gt;&gt;&quot; } else { &quot; &quot; }, ins.ip()); - let k = (ins.ip() - region.BaseAddress as usize as u64) as usize; - let instr_bytes = &amp;bytes[k..k + ins.len()]; - for b in instr_bytes.iter() { - print!(&quot;{:02X}&quot;, b); - } - if instr_bytes.len() &lt; 10 { - for _ in 0..10usize.saturating_sub(instr_bytes.len()) { - print!(&quot; &quot;); - } - } - - output.clear(); - formatter.format(ins, &amp;mut output); - println!(&quot; {}&quot;, output); - } - break; - } -} -debugger.cont(event, true).unwrap(); -break; -</code></pre> -<p>The result is pretty fancy:</p> -<pre><code> 000000010002CAAC 48894DF0 mov [rbp-10h],rcx - 000000010002CAB0 488955F8 mov [rbp-8],rdx - 000000010002CAB4 48C745D800000000 mov qword [rbp-28h],0 - 000000010002CABC 90 nop - 000000010002CABD 488B050CA02D00 mov rax,[rel 100306AD0h] -&gt;&gt;&gt; 000000010002CAC4 8B00 mov eax,[rax] - 000000010002CAC6 8945EC mov [rbp-14h],eax - 000000010002CAC9 B9E8030000 mov ecx,3E8h - 000000010002CACE E88D2FFEFF call 000000010000FA60h - 000000010002CAD3 8945E8 mov [rbp-18h],eax -</code></pre> -<p>Cool! So <code>rax</code> is holding an address, meaning it's a pointer, and the value it reads (dereferences) is stored back into <code>eax</code> (because it does not need <code>rax</code> anymore). Alas, the current thread context has the register state <em>after</em> the instruction was executed, and <code>rax</code> no longer contains the address at this point. However, notice how the previous instruction writes a fixed value to <code>rax</code>, and then that value is used to access memory, like so:</p> -<pre><code class="language-rust" data-lang="rust">let eax = memory[memory[0x100306AD0]]; -</code></pre> -<p>The value at <code>memory[0x100306AD0]</code> <em>is</em> the pointer! No offsets are used, because nothing is added to the pointer after it's read. This means that, if we simply scan for the address we were looking for, we should find out where the pointer is stored:</p> -<pre><code class="language-rust" data-lang="rust">let addr = ...; -let scan = process.scan_regions(&amp;regions, Scan::Exact(addr as u64)); - -scan.into_iter().for_each(|region| { - region.locations.iter().for_each(|ptr_addr| { - println!(&quot;[{:x}] = {:x}&quot;, ptr_addr, addr); - }); -}); -</code></pre> -<p>And just like that:</p> -<pre><code>[100306ad0] = 15de9f0 -</code></pre> -<p>Notice how the pointer address found matches with the offset used by the instructions:</p> -<pre><code> 000000010002CABD 488B050CA02D00 mov rax,[rel 100306AD0h] - this is the same as the value we just found ^^^^^^^^^^ -</code></pre> -<p>Very interesting indeed. We were actually very lucky to have only found a single memory location containing the pointer value, <code>0x15de9f0</code>. Cheat Engine somehow knows that this value is always stored at <code>0x100306ad0</code> (or rather, at <code>Tutorial-x86_64.exe+306AD0</code>), because the address shows green. How does it do this?</p> -<h2 id="base-addresses">Base addresses</h2> -<p>Remember back in <a href="/blog/woce-2">part 2</a> when we introduced the memory regions? They're making a comeback! A memory region contains both the current memory protection option <em>and</em> the protection level when the region was created. If we try printing out the protection levels for both the memory region containing the value, and the memory region containing the pointer, this is what we get (the addresses differ from the ones previously because I restarted the tutorial):</p> -<pre><code>Region holding the value: - BaseAddress: 0xb0000 - AllocationBase: 0xb0000 - AllocationProtect: 0x4 - RegionSize: 1007616 - State: 4096 - Protect: 4 - Type: 0x20000 - -Region holding the pointer: - BaseAddress: 0x100304000 - AllocationBase: 0x100000000 - AllocationProtect: 0x80 - RegionSize: 28672 - State: 4096 - Protect: 4 - Type: 0x1000000 -</code></pre> -<p>Interesting! According to the <a href="https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-memory_basic_information"><code>MEMORY_BASIC_INFORMATION</code> page</a>, the type for the first region is <code>MEM_PRIVATE</code>, and the type for the second region is <code>MEM_IMAGE</code> which:</p> -<blockquote> -<p>Indicates that the memory pages within the region are mapped into the view of an image section.</p> -</blockquote> -<p>The protection also changes from <code>PAGE_EXECUTE_WRITECOPY</code> to simply <code>PAGE_READWRITE</code>, but I don't think it's relevant. Neither the type seems to be much more relevant. In <a href="/blog/woce-2">part 2</a> we also mentioned the concept of &quot;base address&quot;, but decided against using it, because starting to look for regions at address zero seemed to work fine. However, it would make sense that fixed &quot;addresses&quot; start at some known &quot;base&quot;. Let's try getting the <a href="https://stackoverflow.com/a/26573045/4759433">base address for all loaded modules</a>. Currently, we only get the address for the base module, in order to retrieve its name, but now we need them all:</p> -<pre><code class="language-rust" data-lang="rust">pub fn enum_modules(&amp;self) -&gt; io::Result&lt;Vec&lt;winapi::shared::minwindef::HMODULE&gt;&gt; { - let mut size = 0; - if unsafe { - winapi::um::psapi::EnumProcessModules( - self.handle.as_ptr(), - ptr::null_mut(), - 0, - &amp;mut size, - ) - } == FALSE - { - return Err(io::Error::last_os_error()); - } - - let mut modules = Vec::with_capacity(size as usize / mem::size_of::&lt;HMODULE&gt;()); - if unsafe { - winapi::um::psapi::EnumProcessModules( - self.handle.as_ptr(), - modules.as_mut_ptr(), - (modules.capacity() * mem::size_of::&lt;HMODULE&gt;()) as u32, - &amp;mut size, - ) - } == FALSE - { - return Err(io::Error::last_os_error()); - } - - unsafe { - modules.set_len(size as usize / mem::size_of::&lt;HMODULE&gt;()); - } - - Ok(modules) -} -</code></pre> -<p>The first call is used to retrieve the correct <code>size</code>, then we allocate just enough, and make the second call. The returned type are pretty much memory addresses, so let's see if we can find regions that contain them:</p> -<pre><code class="language-rust" data-lang="rust">let mut bases = 0; -let modules = process.enum_modules().unwrap(); -let regions = process.memory_regions(); -regions.iter().for_each(|region| { - if modules.iter().any(|module| { - let base = region.AllocationBase as usize; - let addr = *module as usize; - base &lt;= addr &amp;&amp; addr &lt; base + region.RegionSize - }) { - bases += 1; - } -}); - -println!( - &quot;{}/{} regions have a module address within them&quot;, - bases, - regions.len() -); -</code></pre> -<pre><code>41/353 regions have a module address within them -</code></pre> -<p>Exciting stuff! It appears <code>base == addr</code> also does the trick<sup class="footnote-reference"><a href="#5">5</a></sup>, so now we could build a <code>bases: HashSet&lt;usize&gt;</code> and simply check if <code>bases.contains(&amp;region.AllocationBase as usize)</code> to determine whether <code>region</code> is a base address or not<sup class="footnote-reference"><a href="#6">6</a></sup>. So there we have it! The address holding the pointer value does fall within one of these &quot;base regions&quot;. You can also get the name from one of these module addresses, and print it in the same way as Cheat Engine does it (such as <code>Tutorial-x86_64.exe+306AD0</code>).</p> -<h2 id="finale">Finale</h2> -<p>So, there's no &quot;automated&quot; solution to all of this? That's the end? Well, yes, once you have a pointer you can dereference it once and then write to the given address to complete the tutorial step! I can understand how this would feel a bit underwhelming, but in all fairness, we were required to pretty-print assembly to guess what pointer address we could potentially need to look for. There is an <a href="https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-2a-manual.html">stupidly large amount of instructions</a>, and I'm sure a lot of them can access memory, so automating that would be rough. We were lucky that the instructions right before the one that hit the breakpoint were changing the memory address, but you could imagine this value coming from somewhere completely different. It could also be using a myriad of different techniques to apply the offset. I would argue manual intervention is a must here<sup class="footnote-reference"><a href="#7">7</a></sup>.</p> -<p>We have learnt how to pretty-print instructions, and had a very gentle introduction to figuring out what we may need to look for. The code to retrieve the loaded modules, and their corresponding regions, will come in handy later on. Having access to this information lets us know when to stop looking for additional pointers. As soon as a pointer is found within a memory region corresponding to a base module, we're done! Also, I know the title doesn't really much the contents of this entry (sorry about that), but I'm just following the convention of calling it whatever the Cheat Engine tutorial calls them.</p> -<p>The <a href="https://github.com/lonami/memo">code for this post</a> is available over at my GitHub. You can run <code>git checkout step6</code> after cloning the repository to get the right version of the code, although you will have to <code>checkout</code> to individual commits if you want to review, for example, how the instructions were printed out. Only the code necessary to complete the step is included at the <code>step6</code> tag.</p> -<p>In the next post, we'll tackle the sixth step of the tutorial: Code Injection. This will be pretty similar to part 5, but instead of writing out a simple NOP instruction, we will have to get a bit more creative.</p> -<h3 id="footnotes">Footnotes</h3> -<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup> -<p>This will only be a gentle introduction to pointers. Part 8 of this series will have to rely on more advanced techniques.</p> +<h2 id="footnotes">Footnotes</h2> +<div class="footnote-definition" id="gaming-storage"><sup class="footnote-definition-label">1</sup> +<p>As stated in <a href="https://blogs.umass.edu/Techbytes/2014/02/10/history-of-gaming-storage/#attachment_2827">this article</a>.</p> </div> -<div class="footnote-definition" id="2"><sup class="footnote-definition-label">2</sup> -<p>Kind of. The size of a pointer isn't necessarily the size as <code>usize</code>, although <code>usize</code> is guaranteed to be able of representing every possible address. For our purposes, we can assume a pointer is as big as <code>usize</code>.</p> -</div> -<div class="footnote-definition" id="3"><sup class="footnote-definition-label">3</sup> -<p>Game updates are likely to pull more code and shuffle stuff around. This is unfortunately a difficult problem to solve. But storing a pointer which is usable across restarts for as long as the game doesn't update is still a pretty darn big improvement over having to constantly scan for the locations we care about. Although if you're smart enough to look for certain unique patterns, even if the code is changed, finding those patterns will give you the new updated address, so it's not <em>impossible</em>.</p> -</div> -<div class="footnote-definition" id="4"><sup class="footnote-definition-label">4</sup> -<p><code>bool::then</code> is a pretty recent addition at the time of writing (1.50.0), so make sure you <code>rustup update</code> if it's erroring out!</p> -</div> -<div class="footnote-definition" id="5"><sup class="footnote-definition-label">5</sup> -<p>I wasn't sure if there would be some metadata before the module base address but within the region, so I went with the range check. What <em>is</em> important however is using <code>AllocationBase</code>, not <code>BaseAddress</code>. They're different, and this did bite me.</p> -</div> -<div class="footnote-definition" id="6"><sup class="footnote-definition-label">6</sup> -<p>As usual, I have no idea if this is how Cheat Engine is doing it, but it seems reasonable.</p> -</div> -<div class="footnote-definition" id="6"><sup class="footnote-definition-label">6</sup> -<p>But nothing's stopping you from implementing some heuristics to get the job done for you. If you run some algorithm in your head to find what the pointer value could be, you can program it in Rust as well, although I don't think it's worth the effort.</p> -</div> -</content> - </entry> - <entry xml:lang="en"> - <title>Writing our own Cheat Engine: Code finder</title> - <published>2021-03-06T00:00:00+00:00</published> - <updated>2021-03-06T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/woce-5/" type="text/html"/> - <id>https://lonami.dev/blog/woce-5/</id> - <content type="html"><p>This is part 5 on the <em>Writing our own Cheat Engine</em> series:</p> -<ul> -<li><a href="/blog/woce-1">Part 1: Introduction</a> (start here if you're new to the series!)</li> -<li><a href="/blog/woce-2">Part 2: Exact Value scanning</a></li> -<li><a href="/blog/woce-3">Part 3: Unknown initial value</a></li> -<li><a href="/blog/woce-4">Part 4: Floating points</a></li> -<li>Part 5: Code finder</li> -<li><a href="/blog/woce-6">Part 6: Pointers</a></li> -</ul> -<p>In part 4 we spent a good deal of time trying to make our scans generic, and now we have something that works<sup class="footnote-reference"><a href="#1">1</a></sup>! Now that the scanning is fairly powerful and all covered, the Cheat Engine tutorial shifts focus into slightly more advanced techniques that you will most certainly need in anything bigger than a toy program.</p> -<p>It's time to write our very own <strong>debugger</strong> in Rust!</p> -<h2 id="code-finder">Code finder</h2> -<details open><summary>Cheat Engine Tutorial: Step 5</summary> -<blockquote> -<p>Sometimes the location something is stored at changes when you restart the game, or even while you're playing… In that case you can use 2 things to still make a table that works. In this step I'll try to describe how to use the Code Finder function.</p> -<p>The value down here will be at a different location each time you start the tutorial, so a normal entry in the address list wouldn't work. First try to find the address. (You've got to this point so I assume you know how to.)</p> -<p>When you've found the address, right-click the address in Cheat Engine and choose &quot;Find out what writes to this address&quot;. A window will pop up with an empty list.</p> -<p>Then click on the Change value button in this tutorial, and go back to Cheat Engine. If everything went right there should be an address with assembler code there now.</p> -<p>Click it and choose the replace option to replace it with code that does nothing. That will also add the code address to the code list in the advanced options window. (Which gets saved if you save your table.)</p> -<p>Click on stop, so the game will start running normal again, and close to close the window. Now, click on Change value, and if everything went right the Next button should become enabled.</p> -<p>Note: When you're freezing the address with a high enough speed it may happen that next becomes visible anyhow</p> -</blockquote> -</details> -<h2 id="baby-steps-to-debugging">Baby steps to debugging</h2> -<p>Although I have used debuggers before, I have never had a need to write one myself so it's time for some research.</p> -<p>Searching on DuckDuckGo, I can find entire series to <a href="http://system.joekain.com/debugger/">Writing a Debugger</a>. We would be done by now if only that series wasn't written for Linux. The Windows documentation contains a section called <a href="https://docs.microsoft.com/en-us/windows/win32/debug/creating-a-basic-debugger">Creating a Basic Debugger</a>, but as far as I can tell, it only teaches you the <a href="https://docs.microsoft.com/en-us/windows/win32/debug/debugging-functions">functions</a> needed to configure the debugging loop. Which mind you, we will need, but in due time.</p> -<p>According to <a href="https://www.gironsec.com/blog/2013/12/writing-your-own-debugger-windows-in-c/">Writing your own windows debugger in C</a>, the steps needed to write a debugger are:</p> -<ul> -<li><a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-suspendthread"><code>SuspendThread(proc)</code></a>. It makes sense that we need to pause all the threads<sup class="footnote-reference"><a href="#2">2</a></sup> before messing around with the code the program is executing, or things are very prone to go wrong.</li> -<li><a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadcontext"><code>GetThreadContext(proc)</code></a>. This function retrieves the appropriate context of the specified thread and is highly processor specific. It basically takes a snapshot of all the registers. Think of registers like extremely fast, but also extremely limited, memory the processor uses.</li> -<li><a href="https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-debugbreakprocess"><code>DebugBreakProcess</code></a>. Essentially <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/x86-instructions#miscellaneous">writes out the 0xCC opcode</a>, <code>int 3</code> in assembly, also known as software breakpoint. It's written wherever the Register Instruction Pointer (RIP<sup class="footnote-reference"><a href="#3">3</a></sup>) currently points to, so in essence, when the thread resumes, it will immediately <a href="https://stackoverflow.com/q/3915511/">trigger the breakpoint</a>.</li> -<li><a href="https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-continuedebugevent"><code>ContinueDebugEvent</code></a>. Presumably continues debugging.</li> -</ul> -<p>There are pages documenting <a href="https://docs.microsoft.com/en-us/windows/win32/debug/debugging-events">all of the debug events</a> that our debugger will be able to handle.</p> -<p>Okay, nice! Software breakpoints seem to be done by writing out memory to the region where the program is reading instructions from. We know how to write memory, as that's what all the previous posts have been doing to complete the corresponding tutorial steps. After the breakpoint is executed, all we need to do is <a href="https://stackoverflow.com/q/3747852/">restore the original memory back</a> so that the next time the program executes the code it sees no difference.</p> -<p>But a software breakpoint will halt execution when the code executes the interrupt instruction. This step of the tutorial wants us to find <em>what writes to a memory location</em>. Where should we place the breakpoint to detect such location? Writing out the instruction to the memory we want to break in won't do; it's not an instruction, it's just data.</p> -<p>The name may have given it away. If we're talking about software breakpoints, it makes sense that there would exist such a thing as <a href="https://en.wikipedia.org/wiki/Breakpoint#Hardware"><em>hardware</em> breakpoints</a>. Because they're tied to the hardware, they're highly processor-specific, but luckily for us, the processor on your usual desktop computer probably has them! Even the <a href="https://interrupt.memfault.com/blog/cortex-m-breakpoints">cortex-m</a> does. The wikipedia page also tells us the name of the thing we're looking for, watchpoints:</p> -<blockquote> -<p>Other kinds of conditions can also be used, such as the reading, writing, or modification of a specific location in an area of memory. This is often referred to as a conditional breakpoint, a data breakpoint, or a watchpoint.</p> -</blockquote> -<p>A breakpoint that triggers when a specific memory location is written to is exactly what we need, and <a href="https://stackoverflow.com/a/19109153/">x86 has debug registers D0 to D3 to track memory addresses</a>. As far as I can tell, there is no API in specific to mess with the registers. But we don't need any of that! We can just go ahead and <a href="https://doc.rust-lang.org/stable/unstable-book/library-features/asm.html">write some assembly by hand</a> to access these registers. At the time of writing, inline assembly is unstable, so we need a nightly compiler. Run <code>rustup toolchain install nightly</code> if you haven't yet, and execute the following code with <code>cargo +nightly run</code>:</p> -<pre><code class="language-rust" data-lang="rust">#![feature(asm)] // top of the file - -fn main() { - let x: u64 = 123; - unsafe { - asm!(&quot;mov dr7, {}&quot;, in(reg) x); - } -} - -</code></pre> -<p><code>dr7</code> stands is the <a href="https://en.wikipedia.org/wiki/X86_debug_register">debug control register</a>, and running this we get…</p> -<pre><code>&gt;cargo +nightly run - Compiling memo v0.1.0 - Finished dev [unoptimized + debuginfo] target(s) in 0.74s - Running `target\debug\memo.exe` -error: process didn't exit successfully: `target\debug\memo.exe` (exit code: 0xc0000096, STATUS_PRIVILEGED_INSTRUCTION) -</code></pre> -<p>…an exception! In all fairness, I have no idea what that code would have done. So maybe the <code>STATUS_PRIVILEGED_INSTRUCTION</code> is just trying to protect us. Can we read from the register instead, and see it's default value?</p> -<pre><code class="language-rust" data-lang="rust">let x: u64; -unsafe { - asm!(&quot;mov {}, dr7&quot;, out(reg) x); -} -assert_eq!(x, 5); -</code></pre> -<pre><code>&gt;cargo +nightly run -... -error: process didn't exit successfully: `target\debug\memo.exe` (exit code: 0xc0000096, STATUS_PRIVILEGED_INSTRUCTION) -</code></pre> -<p>Nope. Okay, it seems directly reading from or writing to the debug register is a ring-0 thing. Surely there's a way around this. But first we should figure out how to enumerate and pause all the threads.</p> -<h2 id="pausing-all-the-threads">Pausing all the threads</h2> -<p>It seems there is no straightforward way to enumerate the threads. One has to <a href="https://stackoverflow.com/a/1206915/">create a &quot;toolhelp&quot;</a> and poll the entries. I won't bore you with the details. Let's add <code>tlhelp32</code> to the crate features of <code>winapi</code> and try it out:</p> -<pre><code class="language-rust" data-lang="rust"> -#[derive(Debug)] -pub struct Toolhelp { - handle: winapi::um::winnt::HANDLE, -} - -impl Drop for Toolhelp { - fn drop(&amp;mut self) { - unsafe { winapi::um::handleapi::CloseHandle(self.handle) }; - } -} - -pub fn enum_threads(pid: u32) -&gt; io::Result&lt;Vec&lt;u32&gt;&gt; { - const ENTRY_SIZE: u32 = mem::size_of::&lt;winapi::um::tlhelp32::THREADENTRY32&gt;() as u32; - - // size_of(dwSize + cntUsage + th32ThreadID + th32OwnerProcessID) - const NEEDED_ENTRY_SIZE: u32 = 4 * mem::size_of::&lt;DWORD&gt;() as u32; - - // SAFETY: it is always safe to attempt to call this function. - let handle = unsafe { - winapi::um::tlhelp32::CreateToolhelp32Snapshot(winapi::um::tlhelp32::TH32CS_SNAPTHREAD, 0) - }; - if handle == winapi::um::handleapi::INVALID_HANDLE_VALUE { - return Err(io::Error::last_os_error()); - } - let toolhelp = Toolhelp { handle }; - - let mut result = Vec::new(); - let mut entry = winapi::um::tlhelp32::THREADENTRY32 { - dwSize: ENTRY_SIZE, - cntUsage: 0, - th32ThreadID: 0, - th32OwnerProcessID: 0, - tpBasePri: 0, - tpDeltaPri: 0, - dwFlags: 0, - }; - - // SAFETY: we have a valid handle, and point to memory we own with the right size. - if unsafe { winapi::um::tlhelp32::Thread32First(toolhelp.handle, &amp;mut entry) } != FALSE { - loop { - if entry.dwSize &gt;= NEEDED_ENTRY_SIZE &amp;&amp; entry.th32OwnerProcessID == pid { - result.push(entry.th32ThreadID); - } - - entry.dwSize = ENTRY_SIZE; - // SAFETY: we have a valid handle, and point to memory we own with the right size. - if unsafe { winapi::um::tlhelp32::Thread32Next(toolhelp.handle, &amp;mut entry) } == FALSE { - break; - } - } - } - - Ok(result) -} -</code></pre> -<p>Annoyingly, invalid handles returned by <a href="https://docs.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot"><code>CreateToolhelp32Snapshot</code></a>, are <code>INVALID_HANDLE_VALUE</code> (which is -1), not null. But that's not a big deal, we simply can't use <code>NonNull</code> here. The function ignores the process identifier when using <code>TH32CS_SNAPTHREAD</code>, used to include all threads, and we need to compare the process identifier ourselves.</p> -<p>In summary, we create a &quot;toolhelp&quot; (wrapped in a helper <code>struct</code> so that whatever happens, <code>Drop</code> will clean it up), initialize a thread enntry (with everything but the structure size to zero) and call <code>Thread32First</code> the first time, <code>Thread32Next</code> subsequent times. It seems to work all fine!</p> -<pre><code class="language-rust" data-lang="rust">dbg!(process::enum_threads(pid)); -</code></pre> -<pre><code>[src\main.rs:46] process::enum_threads(pid) = Ok( - [ - 10560, - ], -) -</code></pre> -<p>According to this, the Cheat Engine tutorial is only using one thread. Good to know. Much like processes, threads need to be opened before we can use them, with <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openthread"><code>OpenThread</code></a>:</p> -<pre><code class="language-rust" data-lang="rust">pub struct Thread { - tid: u32, - handle: NonNull&lt;c_void&gt;, -} - -impl Thread { - pub fn open(tid: u32) -&gt; io::Result&lt;Self&gt; { - // SAFETY: the call doesn't have dangerous side-effects - NonNull::new(unsafe { - winapi::um::processthreadsapi::OpenThread( - winapi::um::winnt::THREAD_SUSPEND_RESUME, - FALSE, - tid, - ) - }) - .map(|handle| Self { tid, handle }) - .ok_or_else(io::Error::last_os_error) - } - - pub fn tid(&amp;self) -&gt; u32 { - self.tid - } -} - -impl Drop for Thread { - fn drop(&amp;mut self) { - unsafe { winapi::um::handleapi::CloseHandle(self.handle.as_mut()) }; - } -} -</code></pre> -<p>Just your usual RAII pattern. The thread is opened with permission to suspend and resume it. Let's try to pause the handles with <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-suspendthread"><code>SuspendThread</code></a> to make sure that this thread is actually the one we're looking for:</p> -<pre><code class="language-rust" data-lang="rust">pub fn suspend(&amp;mut self) -&gt; io::Result&lt;usize&gt; { - // SAFETY: the handle is valid. - let ret = unsafe { - winapi::um::processthreadsapi::SuspendThread(self.handle.as_ptr()) - }; - if ret == -1i32 as u32 { - Err(io::Error::last_os_error()) - } else { - Ok(ret as usize) - } -} - -pub fn resume(&amp;mut self) -&gt; io::Result&lt;usize&gt; { - // SAFETY: the handle is valid. - let ret = unsafe { - winapi::um::processthreadsapi::ResumeThread(self.handle.as_ptr()) - }; - if ret == -1i32 as u32 { - Err(io::Error::last_os_error()) - } else { - Ok(ret as usize) - } -} -</code></pre> -<p>Both suspend and resume return the previous &quot;suspend count&quot;. It's kind of like a barrier or semaphore where the thread only runs if the suspend count is zero. Trying it out:</p> -<pre><code class="language-rust" data-lang="rust">let mut threads = thread::enum_threads(pid) - .unwrap() - .into_iter() - .map(Thread::open) - .collect::&lt;Result&lt;Vec&lt;_&gt;, _&gt;&gt;() - .unwrap(); - -threads - .iter_mut() - .for_each(|thread| { - println!(&quot;Pausing thread {} for 10 seconds…&quot;, thread.tid()); - thread.suspend().unwrap(); - - std::thread::sleep(std::time::Duration::from_secs(10)); - - println!(&quot;Wake up, {}!&quot;, thread.tid()); - thread.resume().unwrap(); - }); -</code></pre> -<p>If you run this code with the process ID of the Cheat Engine tutorial, you will see that the tutorial window freezes for ten seconds! Because the main and only thread is paused, it cannot process any window events, so it becomes unresponsive. It is now &quot;safe&quot; to mess around with the thread context.</p> -<h2 id="setting-hardware-breakpoints">Setting hardware breakpoints</h2> -<p>I'm definitely not the first person to wonder <a href="https://social.msdn.microsoft.com/Forums/en-US/0cb3360d-3747-42a7-bc0e-668c5d9ee1ee/how-to-set-a-hardware-breakpoint">How to set a hardware breakpoint?</a>. This is great, because it means I don't need to ask that question myself. It appears we need to change the debug register <em>via the thread context</em>.</p> -<p>One has to be careful to use the right context structure. Confusingly enough, <a href="https://stackoverflow.com/q/17504174/"><code>WOW64_CONTEXT</code></a> is 32 bits, not 64. <code>CONTEXT</code> alone seems to be the right one:</p> -<pre><code class="language-rust" data-lang="rust">pub fn get_context(&amp;self) -&gt; io::Result&lt;winapi::um::winnt::CONTEXT&gt; { - let context = MaybeUninit::&lt;winapi::um::winnt::CONTEXT&gt;::zeroed(); - // SAFETY: it's a C struct, and all-zero is a valid bit-pattern for the type. - let mut context = unsafe { context.assume_init() }; - context.ContextFlags = winapi::um::winnt::CONTEXT_ALL; - - // SAFETY: the handle is valid and structure points to valid memory. - if unsafe { - winapi::um::processthreadsapi::GetThreadContext(self.handle.as_ptr(), &amp;mut context) - } == FALSE - { - Err(io::Error::last_os_error()) - } else { - Ok(context) - } -} -</code></pre> -<p>Trying it out:</p> -<pre><code class="language-rust" data-lang="rust">thread.suspend().unwrap(); - -let context = thread.get_context().unwrap(); -println!(&quot;Dr0: {:016x}&quot;, context.Dr0); -println!(&quot;Dr7: {:016x}&quot;, context.Dr7); -println!(&quot;Dr6: {:016x}&quot;, context.Dr6); -println!(&quot;Rax: {:016x}&quot;, context.Rax); -println!(&quot;Rbx: {:016x}&quot;, context.Rbx); -println!(&quot;Rcx: {:016x}&quot;, context.Rcx); -println!(&quot;Rip: {:016x}&quot;, context.Rip); -</code></pre> -<pre><code>Dr0: 0000000000000000 -Dr7: 0000000000000000 -Dr6: 0000000000000000 -Rax: 0000000000001446 -Rbx: 0000000000000000 -Rcx: 0000000000000000 -Rip: 00007ffda4259904 -</code></pre> -<p>Looks about right! Hm, I wonder what happens if I use Cheat Engine to add the watchpoint on the memory location we care about?</p> -<pre><code>Dr0: 000000000157e650 -Dr7: 00000000000d0001 -</code></pre> -<p>Look at that! The debug registers changed! DR0 contains the location we want to watch for writes, and the debug control register DR7 changed. Cheat Engine sets the same values on all threads (for some reason I now see more than one thread printed for the tutorial, not sure what's up with that; maybe the single-thread is the weird one out).</p> -<p>Hmm, what happens if I watch for access instead of write?</p> -<pre><code>Dr0: 000000000157e650 -Dr7: 00000000000f0001 -</code></pre> -<p>What if I set both?</p> -<pre><code>Dr0: 000000000157e650 -Dr7: 0000000000fd0005 -</code></pre> -<p>Most intriguing! This was done by telling Cheat Engine to find &quot;what writes&quot; to the address, then &quot;what accesses&quot; the address. I wonder if the order matters?</p> -<pre><code>Dr0: 000000000157e650 -Dr7: 0000000000df0005 -</code></pre> -<p>&quot;What accesses&quot; and then &quot;what writes&quot; does change it. Very well! We're only concerned in a single breakpoint, so we won't worry about this, but it's good to know that we can inspect what Cheat Engine is doing. It's also interesting to see how Cheat Engine is using hardware breakpoints and not software breakpoints.</p> -<p>For simplicity, our code is going to assume that we're the only ones messing around with the debug registers, and that there will only be a single debug register in use. Make sure to add <code>THREAD_SET_CONTEXT</code> to the permissions when opening the thread handle:</p> -<pre><code class="language-rust" data-lang="rust">pub fn set_context(&amp;self, context: &amp;winapi::um::winnt::CONTEXT) -&gt; io::Result&lt;()&gt; { - // SAFETY: the handle is valid and structure points to valid memory. - if unsafe { - winapi::um::processthreadsapi::SetThreadContext(self.handle.as_ptr(), context) - } == FALSE - { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } -} - -pub fn watch_memory_write(&amp;self, addr: usize) -&gt; io::Result&lt;()&gt; { - let mut context = self.get_context()?; - context.Dr0 = addr as u64; - context.Dr7 = 0x00000000000d0001; - self.set_context(&amp;context)?; - todo!() -} -</code></pre> -<p>If we do this (and temporarily get rid of the <code>todo!()</code>), trying to change the value in the Cheat Engine tutorial will greet us with a warm message:</p> -<blockquote> -<p><strong>Tutorial-x86_64</strong></p> -<p>External exception 80000004.</p> -<p>Press OK to ignore and risk data corruption.<br /> -Press Abort to kill the program.</p> -<p><kbd>OK</kbd> <kbd>Abort</kbd></p> -</blockquote> -<p>There is no debugger attached yet that could possibly handle this exception, so the exception just propagates. Let's fix that.</p> -<h2 id="handling-debug-events">Handling debug events</h2> -<p>Now that we've succeeded on setting breakpoints, we can actually follow the steps described in <a href="https://docs.microsoft.com/en-us/windows/win32/debug/creating-a-basic-debugger">Creating a Basic Debugger</a>. It starts by saying that we should use <a href="https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-debugactiveprocess"><code>DebugActiveProcess</code></a> to attach our processor, the debugger, to the process we want to debug, the debuggee. This function lives under the <code>debugapi</code> header, so add it to <code>winapi</code> features:</p> -<pre><code class="language-rust" data-lang="rust">pub struct DebugToken { - pid: u32, -} - -pub fn debug(pid: u32) -&gt; io::Result&lt;DebugToken&gt; { - if unsafe { winapi::um::debugapi::DebugActiveProcess(pid) } == FALSE { - return Err(io::Error::last_os_error()); - }; - let token = DebugToken { pid }; - if unsafe { winapi::um::winbase::DebugSetProcessKillOnExit(FALSE) } == FALSE { - return Err(io::Error::last_os_error()); - }; - Ok(token) -} - -impl Drop for DebugToken { - fn drop(&amp;mut self) { - unsafe { winapi::um::debugapi::DebugActiveProcessStop(self.pid) }; - } -} -</code></pre> -<p>Once again, we create a wrapper <code>struct</code> with <code>Drop</code> to stop debugging the process once the token is dropped. The call to <code>DebugSetProcessKillOnExit</code> in our <code>debug</code> method ensures that, if our process (the debugger) dies, the process we're debugging (the debuggee) stays alive. We don't want to be restarting the entire Cheat Engine tutorial every time our Rust code crashes!</p> -<p>With the debugger attached, we can wait for debug events. We will put this method inside of <code>impl DebugToken</code>, so that the only way you can call it is if you successfully attached to another process:</p> -<pre><code class="language-rust" data-lang="rust">impl DebugToken { - pub fn wait_event( - &amp;self, - timeout: Option&lt;Duration&gt;, - ) -&gt; io::Result&lt;winapi::um::minwinbase::DEBUG_EVENT&gt; { - let mut result = MaybeUninit::uninit(); - let timeout = timeout - .map(|d| d.as_millis().try_into().ok()) - .flatten() - .unwrap_or(winapi::um::winbase::INFINITE); - - // SAFETY: can only wait for events with a token, so the debugger is active. - if unsafe { winapi::um::debugapi::WaitForDebugEvent(result.as_mut_ptr(), timeout) } == FALSE - { - Err(io::Error::last_os_error()) - } else { - // SAFETY: the call returned non-zero, so the structure is initialized. - Ok(unsafe { result.assume_init() }) - } - } -} -</code></pre> -<p><code>WaitForDebugEvent</code> wants a timeout in milliseconds, so our function lets the user pass the more Rusty <code>Duration</code> type. <code>None</code> will indicate &quot;there is no timeout&quot;, i.e., it's infinite. If the duration is too large to fit in the <code>u32</code> (<code>try_into</code> fails), it will also be infinite.</p> -<p>If we attach the debugger, set the hardware watchpoint, and modify the memory location from the tutorial, an event with <code>dwDebugEventCode = 3</code> will be returned! Now, back to the page with the <a href="https://docs.microsoft.com/en-us/windows/win32/debug/debugging-events">Debugging Events</a>… Gah! It only has the name of the constants, not the values. Well, good thing <a href="https://docs.rs/">docs.rs</a> has a source view! We can just check the values in the <a href="https://docs.rs/winapi/0.3.9/src/winapi/um/minwinbase.rs.html#203-211">source code for <code>winapi</code></a>:</p> -<pre><code class="language-rust" data-lang="rust">pub const EXCEPTION_DEBUG_EVENT: DWORD = 1; -pub const CREATE_THREAD_DEBUG_EVENT: DWORD = 2; -pub const CREATE_PROCESS_DEBUG_EVENT: DWORD = 3; -pub const EXIT_THREAD_DEBUG_EVENT: DWORD = 4; -pub const EXIT_PROCESS_DEBUG_EVENT: DWORD = 5; -pub const LOAD_DLL_DEBUG_EVENT: DWORD = 6; -pub const UNLOAD_DLL_DEBUG_EVENT: DWORD = 7; -pub const OUTPUT_DEBUG_STRING_EVENT: DWORD = 8; -pub const RIP_EVENT: DWORD = 9; -</code></pre> -<p>So, we've got a <code>CREATE_PROCESS_DEBUG_EVENT</code>:</p> -<blockquote> -<p>Generated whenever a new process is created in a process being debugged or whenever the debugger begins debugging an already active process. The system generates this debugging event before the process begins to execute in user mode and before the system generates any other debugging events for the new process.</p> -</blockquote> -<p>It makes sense that this is our first event. By the way, if you were trying this out with a <code>sleep</code> lying around in your code, you may have noticed that the window froze until the debugger terminated. That's because:</p> -<blockquote> -<p>When the system notifies the debugger of a debugging event, it also suspends all threads in the affected process. The threads do not resume execution until the debugger continues the debugging event by using <a href="https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-continuedebugevent"><code>ContinueDebugEvent</code></a>.</p> -</blockquote> -<p>Let's call <code>ContinueDebugMethod</code> but also wait on more than one event and see what happens:</p> -<pre><code class="language-rust" data-lang="rust">for _ in 0..10 { - let event = debugger.wait_event(None).unwrap(); - println!(&quot;Got {}&quot;, event.dwDebugEventCode); - debugger.cont(event, true).unwrap(); -} -</code></pre> -<pre><code>Got 3 -Got 6 -Got 6 -Got 6 -Got 6 -Got 6 -Got 6 -Got 6 -Got 6 -Got 6 -</code></pre> -<p>That's a lot of <code>LOAD_DLL_DEBUG_EVENT</code>. Pumping it up to one hundred and also showing the index we get the following:</p> -<pre><code>0. Got 3 -1. Got 6 -... -40. Got 6 -41. Got 2 -42. Got 1 -43. Got 4 -</code></pre> -<p>In order, we got:</p> -<ul> -<li>One <code>CREATE_PROCESS_DEBUG_EVENT</code>.</li> -<li>Forty <code>LOAD_DLL_DEBUG_EVENT</code>.</li> -<li>One <code>CREATE_THREAD_DEBUG_EVENT</code>.</li> -<li>One <code>EXCEPTION_DEBUG_EVENT</code>.</li> -<li>One <code>EXIT_THREAD_DEBUG_EVENT</code>.</li> -</ul> -<p>And, if after all this, you change the value in the Cheat Engine tutorial (thus triggering our watch point), we get <code>EXCEPTION_DEBUG_EVENT</code>!</p> -<blockquote> -<p>Generated whenever an exception occurs in the process being debugged. Possible exceptions include attempting to access inaccessible memory, executing breakpoint instructions, attempting to divide by zero, or any other exception noted in Structured Exception Handling.</p> -</blockquote> -<p>If we print out all the fields in the <a href="https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-exception_debug_info"><code>EXCEPTION_DEBUG_INFO</code></a> structure:</p> -<pre><code>Watching writes to 10e3a0 for 10s -First chance: 1 -ExceptionCode: 2147483652 -ExceptionFlags: 0 -ExceptionRecord: 0x0 -ExceptionAddress: 0x10002c5ba -NumberParameters: 0 -ExceptionInformation: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] -</code></pre> -<p>The <code>ExceptionCode</code>, which is <code>0x80000004</code>, corresponds with <code>EXCEPTION_SINGLE_STEP</code>:</p> -<blockquote> -<p>A trace trap or other single-instruction mechanism signaled that one instruction has been executed.</p> -</blockquote> -<p>The <code>ExceptionAddress</code> is supposed to be &quot;the address where the exception occurred&quot;. Very well! I have already completed this step of the tutorial, and I know the instruction is <code>mov [rax],edx</code> (or, as Cheat Engine shows, the bytes <code>89 10</code> in hexadecimal). The opcode for the <code>nop</code> instruction is <code>90</code> in hexadecimal, so if we replace two bytes at this address, we should be able to complete the tutorial.</p> -<p>Note that we also need to flush the instruction cache, as noted in the Windows documentation:</p> -<blockquote> -<p>Debuggers frequently read the memory of the process being debugged and write the memory that contains instructions to the instruction cache. After the instructions are written, the debugger calls the <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-flushinstructioncache"><code>FlushInstructionCache</code></a> function to execute the cached instructions.</p> -</blockquote> -<p>So we add a new method to <code>impl Process</code>:</p> -<pre><code class="language-rust" data-lang="rust">/// Flushes the instruction cache. -/// -/// Should be called when writing to memory regions that contain code. -pub fn flush_instruction_cache(&amp;self) -&gt; io::Result&lt;()&gt; { - // SAFETY: the call doesn't have dangerous side-effects. - if unsafe { - winapi::um::processthreadsapi::FlushInstructionCache( - self.handle.as_ptr(), - ptr::null(), - 0, - ) - } == FALSE - { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } -} -</code></pre> -<p>And write some quick and dirty code to get this done:</p> -<pre><code class="language-rust" data-lang="rust">let addr = ...; -println!(&quot;Watching writes to {:x} for 10s&quot;, addr); -threads.iter_mut().for_each(|thread| { - thread.watch_memory_write(addr).unwrap(); -}); -loop { - let event = debugger.wait_event(None).unwrap(); - if event.dwDebugEventCode == 1 { - let exc = unsafe { event.u.Exception() }; - if exc.ExceptionRecord.ExceptionCode == 2147483652 { - let addr = exc.ExceptionRecord.ExceptionAddress as usize; - match process.write_memory(addr, &amp;[0x90, 0x90]) { - Ok(_) =&gt; eprintln!(&quot;Patched [{:x}] with NOP&quot;, addr), - Err(e) =&gt; eprintln!(&quot;Failed to patch [{:x}] with NOP: {}&quot;, addr, e), - }; - process.flush_instruction_cache().unwrap(); - debugger.cont(event, true).unwrap(); - break; - } - } - debugger.cont(event, true).unwrap(); -} -</code></pre> -<p>Although it seems to work:</p> -<pre><code>Watching writes to 15103f0 for 10s -Patched [10002c5ba] with NOP -</code></pre> -<p>It really doesn't:</p> -<blockquote> -<p><strong>Tutorial-x86_64</strong></p> -<p>Access violation.</p> -<p>Press OK to ignore and risk data corruption.<br /> -Press Abort to kill the program.</p> -<p><kbd>OK</kbd> <kbd>Abort</kbd></p> -</blockquote> -<p>Did we write memory somewhere we shouldn't? The documentation does mention &quot;segment-relative&quot; and &quot;linear virtual addresses&quot;:</p> -<blockquote> -<p><code>GetThreadSelectorEntry</code> returns the descriptor table entry for a specified selector and thread. Debuggers use the descriptor table entry to convert a segment-relative address to a linear virtual address. The <code>ReadProcessMemory</code> and <code>WriteProcessMemory</code> functions require linear virtual addresses.</p> -</blockquote> -<p>But nope! This isn't the problem. The problem is that the <code>ExceptionRecord.ExceptionAddress</code> is <em>after</em> the execution happened, so it's already 2 bytes beyond where it should be. We were accidentally writing out the first half of the next instruction, which, yeah, could not end good.</p> -<p>So does it work if I do this instead?:</p> -<pre><code class="language-rust" data-lang="rust">process.write_memory(addr - 2, &amp;[0x90, 0x90]) -// ^^^ new -</code></pre> -<p>This totally does work. Step 5: complete 🎉</p> -<h2 id="properly-patching-instructions">Properly patching instructions</h2> -<p>You may not be satisfied at all with our solution. Not only are we hardcoding some magic constants to set hardware watchpoints, we're also relying on knowledge specific to the Cheat Engine tutorial (insofar that we're replacing two bytes worth of instruction with NOPs).</p> -<p>Properly supporting more than one hardware breakpoint, along with supporting different types of breakpoints, is definitely doable. The meaning of the bits for the debug registers is well defined, and you can definitely study that to come up with <a href="https://github.com/mmorearty/hardware-breakpoints">something more sophisticated</a> and support multiple different breakpoints. But for now, that's out of the scope of this series. The tutorial only wants us to use an on-write watchpoint, and our solution is fine and portable for that use case.</p> -<p>However, relying on the size of the instructions is pretty bad. The instructions x86 executes are of variable length, so we can't possibly just look back until we find the previous instruction, or even naively determine its length. A lot of unrelated sequences of bytes are very likely instructions themselves. We need a disassembler. No, we're not writing our own<sup class="footnote-reference"><a href="#4">4</a></sup>.</p> -<p>Searching on <a href="https://crates.io">crates.io</a> for &quot;disassembler&quot; yields a few results, and the first one I've found is <a href="https://crates.io/crates/iced-x86">iced-x86</a>. I like the name, it has a decent amount of GitHub stars, and it was last updated less than a month ago. I don't know about you, but I think we've just hit a jackpot!</p> -<p>It's quite heavy though, so I will add it behind a feature gate, and users that want it may opt into it:</p> -<pre><code class="language-toml" data-lang="toml">[features] -patch-nops = [&quot;iced-x86&quot;] - -[dependencies] -iced-x86 = { version = &quot;1.10.3&quot;, optional = true } -</code></pre> -<p>You can make use of it with <code>cargo run --features=patch-nops</code>. I don't want to turn this blog post into a tutorial for <code>iced-x86</code>, but in essence, we need to make use of its <code>Decoder</code>. Here's the plan:</p> -<ol> -<li>Find the memory region corresponding to the address we want to patch.</li> -<li>Read the entire region.</li> -<li>Decode the read bytes until the instruction pointer reaches our address.</li> -<li>Because we just parsed the previous instruction, we know its length, and can be replaced with NOPs.</li> -</ol> -<pre><code class="language-rust" data-lang="rust">#[cfg(feature = &quot;patch-nops&quot;)] -pub fn nop_last_instruction(&amp;self, addr: usize) -&gt; io::Result&lt;()&gt; { - use iced_x86::{Decoder, DecoderOptions, Formatter, Instruction, NasmFormatter}; - - let region = self - .memory_regions() - .into_iter() - .find(|region| { - let base = region.BaseAddress as usize; - base &lt;= addr &amp;&amp; addr &lt; base + region.RegionSize - }) - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, &quot;no matching region found&quot;))?; - - let bytes = self.read_memory(region.BaseAddress as usize, region.RegionSize)?; - - let mut decoder = Decoder::new(64, &amp;bytes, DecoderOptions::NONE); - decoder.set_ip(region.BaseAddress as _); - - let mut instruction = Instruction::default(); - while decoder.can_decode() { - decoder.decode_out(&amp;mut instruction); - if instruction.next_ip() as usize == addr { - return self - .write_memory(instruction.ip() as usize, &amp;vec![0x90; instruction.len()]) - .map(drop); - } - } - - Err(io::Error::new( - io::ErrorKind::Other, - &quot;no matching instruction found&quot;, - )) -} -</code></pre> -<p>Pretty straightforward! We can set the &quot;instruction pointer&quot; of the decoder so that it matches with the address we're reading from. The <code>next_ip</code> method comes in really handy. Overall, it's a bit inefficient, because we could reuse the regions retrieved previously, but other than that, there is not much room for improvement.</p> -<p>With this, we are no longer hardcoding the instruction size or guessing which instruction is doing what. You may wonder, what if the region does not start with valid executable code? It could be possible that the instructions are in some memory region with garbage except for a very specific location with real code. I don't know how Cheat Engine handles this, but I think it's reasonable to assume that the region starts with valid code.</p> -<p>As far as I can tell (after having asked a bit around), the encoding is usually self synchronizing (similar to UTF-8), so eventually we should end up with correct instructions. But someone can still intentionally write real code between garbage data which we would then disassemble incorrectly. This is a problem on all variable-length ISAs. Half a solution is to <a href="https://stackoverflow.com/q/3983735/">start at the entry point</a>, decode all instructions, and follow the jumps. The other half would be correctly identifying jumps created just to trip a disassembler up, and jumps pointing to dynamically-calculated addresses!</p> -<h2 id="finale">Finale</h2> -<p>That was quite a deep dive! We have learnt about the existence of the various breakpoint types (software, hardware, and even behaviour, such as watchpoints), how to debug a separate process, and how to correctly update the code other process is running on-the-fly. The <a href="https://github.com/lonami/memo">code for this post</a> is available over at my GitHub. You can run <code>git checkout step5</code> after cloning the repository to get the right version of the code.</p> -<p>Although we've only talked about <em>setting</em> breakpoints, there are of course <a href="https://reverseengineering.stackexchange.com/a/16547">ways of detecting them</a>. There's <a href="https://www.codeproject.com/Articles/30815/An-Anti-Reverse-Engineering-Guide">entire guides about it</a>. Again, we currently hardcode the fact we want to add a single watchpoint using the first debug register. A proper solution here would be to actually calculate the needs that need to be set, as well as keeping track of how many breakpoints have been added so far.</p> -<p>Hardware breakpoints are also limited, since they're simply a bunch of registers, and our machine does not have infinite registers. How are other debuggers like <code>gdb</code> able to create a seemingly unlimited amount of breakpoints? Well, the GDB wiki actually has a page on <a href="https://sourceware.org/gdb/wiki/Internals%20Watchpoints">Internals Watchpoints</a>, and it's really interesting! <code>gdb</code> essentially single-steps through the entire program and tests the expressions after every instruction:</p> -<blockquote> -<p>Software watchpoints are very slow, since GDB needs to single-step the program being debugged and test the value of the watched expression(s) after each instruction.</p> -</blockquote> -<p>However, that's not the only way. One could <a href="https://stackoverflow.com/a/7805842/">change the protection level</a> of the region of interest (for example, remove the write permission), and when the program tries to write there, it will fail! In any case, the GDB wiki is actually a pretty nice resource. It also has a section on <a href="https://sourceware.org/gdb/wiki/Internals/Breakpoint%20Handling">Breakpoint Handling</a>, which contains some additional insight.</p> -<p>With regards to code improvements, <code>DebugToken::wait_event</code> could definitely be both nicer and safer to use, with a custom <code>enum</code>, so the user does not need to rely on magic constants or having to resort to <code>unsafe</code> access to get the right <code>union</code> variant.</p> -<p>In the <a href="/blog/woce-6">next post</a>, we'll tackle the sixth step of the tutorial: Pointers. It reuses the debugging techniques presented here to backtrack where the pointer for our desired value is coming from, so here we will need to actually <em>understand</em> what the instructions are doing, not just patching them out!</p> -<h3 id="footnotes">Footnotes</h3> -<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup> -<p>I'm not super happy about the design of it all, but we won't actually need anything beyond scanning for integers for the rest of the steps so it doesn't really matter.</p> -</div> -<div class="footnote-definition" id="2"><sup class="footnote-definition-label">2</sup> -<p>There seems to be a way to pause the entire process in one go, with the <a href="https://stackoverflow.com/a/4062698/">undocumented <code>NtSuspendProcess</code></a> function!</p> -</div> -<div class="footnote-definition" id="3"><sup class="footnote-definition-label">3</sup> -<p>It really is called that. The naming went from &quot;IP&quot; (instruction pointer, 16 bits), to &quot;EIP&quot; (extended instruction pointer, 32 bits) and currently &quot;RIP&quot; (64 bits). The naming convention for upgraded registers is the same (RAX, RBX, RCX, and so on). The <a href="https://wiki.osdev.org/CPU_Registers_x86_64">OS Dev wiki</a> is a great resource for this kind of stuff.</p> -</div> -<div class="footnote-definition" id="4"><sup class="footnote-definition-label">4</sup> -<p>Well, we don't need an entire disassembler. Knowing the length of each instruction is enough, but that on its own is also a lot of work.</p> -</div> -</content> - </entry> - <entry xml:lang="en"> - <title>Writing our own Cheat Engine: Floating points</title> - <published>2021-02-28T00:00:00+00:00</published> - <updated>2021-02-28T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/woce-4/" type="text/html"/> - <id>https://lonami.dev/blog/woce-4/</id> - <content type="html"><p>This is part 4 on the <em>Writing our own Cheat Engine</em> series:</p> -<ul> -<li><a href="/blog/woce-1">Part 1: Introduction</a> (start here if you're new to the series!)</li> -<li><a href="/blog/woce-2">Part 2: Exact Value scanning</a></li> -<li><a href="/blog/woce-3">Part 3: Unknown initial value</a></li> -<li>Part 4: Floating points</li> -<li><a href="/blog/woce-5">Part 5: Code finder</a></li> -<li><a href="/blog/woce-6">Part 6: Pointers</a></li> -</ul> -<p>In part 3 we did a fair amount of plumbing in order to support scan modes beyond the trivial &quot;exact value scan&quot;. As a result, we have abstracted away the <code>Scan</code>, <code>CandidateLocations</code> and <code>Value</code> types as a separate <code>enum</code> each. Scanning for changed memory regions in an opened process can now be achieved with three lines of code:</p> -<pre><code class="language-rust" data-lang="rust">let regions = process.memory_regions(); -let first_scan = process.scan_regions(&amp;regions, Scan::InRange(0, 500)); -let second_scan = process.rescan_regions(&amp;first_scan, Scan::DecreasedBy(7)); -</code></pre> -<p>How's that for programmability? No need to fire up Cheat Engine's GUI anymore!</p> -<p>The <code>first_scan</code> in the example above remembers all the found <code>Value</code> within the range specified by <code>Scan</code>. Up until now, we have only worked with <code>i32</code>, so that's the type the scans expect and what they work with.</p> -<p>Now it's time to introduce support for different types, like <code>f32</code>, <code>i64</code>, or even more atypical ones, like arbitrary sequences of bytes (think of strings) or even numbers in big-endian.</p> -<p>Tighten your belt, because this post is quite the ride. Let's get right into it!</p> -<h2 id="floating-points">Floating points</h2> -<details open><summary>Cheat Engine Tutorial: Step 4</summary> -<blockquote> -<p>In the previous tutorial we used bytes to scan, but some games store information in so called 'floating point' notations. -(probably to prevent simple memory scanners from finding it the easy way). A floating point is a value with some digits behind the point. (like 5.12 or 11321.1)</p> -<p>Below you see your health and ammo. Both are stored as Floating point notations, but health is stored as a float and ammo is stored as a double. -Click on hit me to lose some health, and on shoot to decrease your ammo with 0.5</p> -<p>You have to set BOTH values to 5000 or higher to proceed.</p> -<p>Exact value scan will work fine here, but you may want to experiment with other types too.</p> -<p>Hint: It is recommended to disable &quot;Fast Scan&quot; for type double</p> -</blockquote> -</details> -<h2 id="generic-values">Generic values</h2> -<p>The <code>Value</code> enumeration holds scanned values, and is currently hardcoded to store <code>i32</code>. The <code>Scan</code> type also holds a value, the value we want to scan for. Changing it to support other types is trivial:</p> -<pre><code class="language-rust" data-lang="rust">pub enum Scan&lt;T&gt; { - Exact(T), - Unknown, - Decreased, - // ...other variants... -} - -pub enum Value&lt;T&gt; { - Exact(T), - AnyWithin(Vec&lt;u8&gt;), -} -</code></pre> -<p><code>AnyWithin</code> is the raw memory, and <code>T</code> can be interpreted from any sequence of bytes thanks to our friend <a href="https://doc.rust-lang.org/stable/std/mem/fn.transmute.html"><code>mem::transmute</code></a>. This change alone is enough to store an arbitrary <code>T</code>! So we're done now? Not really, no.</p> -<p>First of all, we need to update all the places where <code>Scan</code> or <code>Value</code> are used. Our first stop is the scanned <code>Region</code>, which holds the found <code>Value</code>:</p> -<pre><code class="language-rust" data-lang="rust">pub struct Region&lt;T&gt; { - pub info: MEMORY_BASIC_INFORMATION, - pub locations: CandidateLocations, - pub value: Value&lt;T&gt;, -} -</code></pre> -<p>Then, we need to update everywhere <code>Region</code> is used, and on and on… All in all this process is just repeating <code>cargo check</code>, letting the compiler vent on you, and taking good care of it by fixing the errors. It's quite reassuring to know you will not miss a single place. Thank you, compiler!</p> -<p>But wait, how could scanning for a decreased value work for any <code>T</code>? The type is not <code>Ord</code>, we should add some trait bounds. And also, what happens if the type is not <code>Copy</code>? It could implement <code>Drop</code><sup class="footnote-reference"><a href="#1">1</a></sup>, and we will be transmuting from raw bytes, which would trigger the <code>Drop</code> implementation when we're done with the value! Not memory safe at all! And how could we possibly cast raw memory to the type without knowing its siz– oh nevermind, <a href="https://doc.rust-lang.org/stable/std/marker/trait.Sized.html"><code>T</code> is already <code>Sized</code> by default</a>. But anyway, we need the other bounds.</p> -<p>In order to not repeat ourselves, we will implement a new <code>trait</code>, let's say <code>Scannable</code>, which requires all other bounds:</p> -<pre><code class="language-rust" data-lang="rust">pub trait Scannable: Copy + PartialEq + PartialOrd {} - -impl&lt;T: Copy + PartialEq + PartialOrd&gt; Scannable for T {} -</code></pre> -<p>And fix our definitions:</p> -<pre><code class="language-rust" data-lang="rust">pub enum Scan&lt;T: Scannable&gt; { ... } -pub enum Value&lt;T: Scannable&gt; { ... } -pub struct Region&lt;T: Scannable&gt; { ... } - -// ...and the many other places referring to T -</code></pre> -<p>Every type which is <code>Copy</code>, <code>PartialEq</code> and <code>PartialOrd</code> can be scanned over<sup class="footnote-reference"><a href="#2">2</a></sup>, because we <code>impl Scan for T</code> where the bounds are met. Unfortunately, we cannot require <code>Eq</code> or <code>Ord</code> because the floating point types do not implement it.</p> -<h2 id="transmuting-memory">Transmuting memory</h2> -<p>Also known as reinterpreting a bunch of bytes as something else, or perhaps it stands for &quot;summoning the demon&quot;:</p> -<blockquote> -<p><code>transmute</code> is <strong>incredibly</strong> unsafe. There are a vast number of ways to cause <a href="https://doc.rust-lang.org/stable/reference/behavior-considered-undefined.html">undefined behavior</a> with this function. <code>transmute</code> should be the absolute last resort.</p> -</blockquote> -<p>Types like <code>i32</code> define methods such as <a href="https://doc.rust-lang.org/stable/std/primitive.i32.html#method.from_ne_bytes"><code>from_ne_bytes</code></a> and <a href="https://doc.rust-lang.org/stable/std/primitive.i32.html#method.to_ne_bytes"><code>to_ne_bytes</code></a> which convert raw bytes from and into its native representation. This is all really nice, but unfortunately, there's no standard trait in the Rust's standard library to &quot;interpret a type <code>T</code> as the byte sequence of its native representation&quot;. <code>transmute</code>, however, does exist, and similar to any other <code>unsafe</code> function, it's safe to call <strong>as long as we respect its invariants</strong>. What are these invariants<sup class="footnote-reference"><a href="#3">3</a></sup>?</p> -<blockquote> -<p>Both types must have the same size</p> -</blockquote> -<p>Okay, we can just assert that the window length matches the type's length. What else?</p> -<blockquote> -<p>Neither the original, nor the result, may be an <a href="https://doc.rust-lang.org/nomicon/what-unsafe-does.html">invalid value</a>.</p> -</blockquote> -<p>What's an invalid value?</p> -<blockquote> -<ul> -<li>a <code>bool</code> that isn't 0 or 1</li> -<li>an <code>enum</code> with an invalid discriminant</li> -<li>a null <code>fn</code> pointer</li> -<li>a <code>char</code> outside the ranges [0x0, 0xD7FF] and [0xE000, 0x10FFFF]</li> -<li>a <code>!</code> (all values are invalid for this type)</li> -<li>an integer (<code>i*</code>/<code>u*</code>), floating point value (<code>f*</code>), or raw pointer read from uninitialized memory, or uninitialized memory in a <code>str</code>.</li> -<li>a reference/<code>Box</code> that is dangling, unaligned, or points to an invalid value.</li> -<li>a wide reference, <code>Box</code>, or raw pointer that has invalid metadata: -<ul> -<li><code>dyn Trait</code> metadata is invalid if it is not a pointer to a vtable for <code>Trait</code> that matches the actual dynamic trait the pointer or reference points to</li> -<li>slice metadata is invalid if the length is not a valid <code>usize</code> (i.e., it must not be read from uninitialized memory)</li> -</ul> -</li> -<li>a type with custom invalid values that is one of those values, such as a <code>NonNull</code> that is null. (Requesting custom invalid values is an unstable feature, but some stable libstd types, like <code>NonNull</code>, make use of it.)</li> -</ul> -</blockquote> -<p>Okay, that's actually an awful lot. Types like <code>bool</code> implement all the trait bounds we defined, and it would be insta-UB to ever try to cast them from arbitrary bytes. The same goes for <code>char</code>, and all <code>enum</code> are out of our control, too. At least we're safe on the &quot;memory is initialized&quot; front.</p> -<p>Dang it, I really wanted to use <code>transmute</code>! But if we were to use it for arbitrary types, it would trigger undefined behaviour sooner than later.</p> -<p>We have several options here:</p> -<ul> -<li>Make it an <code>unsafe trait</code>. Implementors will be responsible for ensuring that the type they're implementing it for can be safely transmuted from and into.</li> -<li><a href="https://rust-lang.github.io/api-guidelines/future-proofing.html">Seal the <code>trait</code></a> and implement it only for types we know are safe<sup class="footnote-reference"><a href="#4">4</a></sup>, like <code>i32</code>.</li> -<li>Add methods to the <code>trait</code> definition that do the conversion of the type into its native representation.</li> -</ul> -<p>We will go with the first option<sup class="footnote-reference"><a href="#5">5</a></sup>, because I really want to use <code>transmute</code>, and I want users to be able to implement the trait on their own types.</p> -<p>In any case, we need to change our <code>impl</code> to something more specific, in order to prevent it from automatically implementing the trait for types for which their memory representation has invalid values. So we get rid of this:</p> -<pre><code class="language-rust" data-lang="rust">pub trait Scannable: Copy + PartialEq + PartialOrd {} - -impl&lt;T: Copy + PartialEq + PartialOrd&gt; Scannable for T {} -</code></pre> -<p>And replace it with this:</p> -<pre><code class="language-rust" data-lang="rust">pub unsafe trait Scannable: Copy + PartialEq + PartialOrd {} - -macro_rules! impl_many { - ( unsafe impl $trait:tt for $( $ty:ty ),* ) =&gt; { - $( unsafe impl $trait for $ty {} )* - }; -} - -// SAFETY: all these types respect `Scannable` invariants. -impl_many!(unsafe impl Scannable for i8, u8, i16, u16, i32, u32, i64, u64, f32, f64); -</code></pre> -<p>Making a small macro for things like these is super useful. You could of course write <code>unsafe impl Scannable for T</code> for all ten <code>T</code> as well, but that introduces even more <code>unsafe</code> to read. Last but not least, let's replace the hardcoded <code>i32::from_ne_bytes</code> and <code>i32::to_ne_bytes</code> with <code>mem::transmute</code>.</p> -<p>All the <code>windows(4)</code> need to be replaced with <code>windows(mem::size_of::&lt;T&gt;())</code> because the size may no longer be <code>4</code>. All the <code>i32::from_ne_bytes(...)</code> need to be replaced with <code>mem::transmute::&lt;_, T&gt;(...)</code>. We explicitly write out <code>T</code> to make sure the compiler doesn't accidentally infer something we didn't intend.</p> -<p>And… it doesn't work at all. We're working with byte slices of arbitrary length. We cannot transmute a <code>&amp;[]</code> type, which is 16 bytes (8 for the pointer and 8 for the length), to our <code>T</code>. My plan to use transmute can't possibly work here. Sigh.</p> -<h2 id="not-quite-transmuting-memory">Not quite transmuting memory</h2> -<p>Okay, we can't transmute, because we don't have a sized value, we only have a slice of bytes pointing somewhere else. What we <em>could</em> do is reinterpret the pointer to those bytes as a different type, and then dereference it! This is still a form of &quot;transmutation&quot;, just without using <code>transmute</code>.</p> -<pre><code class="language-rust" data-lang="rust">let value = unsafe { *(window.as_ptr() as *const T) }; -</code></pre> -<p>Woop! You can compile this and test it out on the step 2 and 3 of the tutorial, using <code>i32</code>, and it will still work! Something troubles me, though. Can you see what it is?</p> -<p>When we talked about invalid values, it had a note about unaligned references:</p> -<blockquote> -<p>a reference/<code>Box</code> that is dangling, unaligned, or points to an invalid value.</p> -</blockquote> -<p>Our <code>window</code> is essentially a reference to <code>T</code>. The only difference is we're working at the pointer level, but they're pretty much references. Let's see what the documentation for <a href="https://doc.rust-lang.org/std/primitive.pointer.html"><code>pointer</code></a> has to say as well, since we're dereferencing pointers:</p> -<blockquote> -<p>when a raw pointer is dereferenced (using the <code>*</code> operator), it must be non-null and aligned.</p> -</blockquote> -<p>It must be aligned. The only reason why our data is aligned is because we are also performing a &quot;fast scan&quot;, so we only look at aligned locations. This is a time bomb waiting to blow up. Is there any other way to <a href="https://doc.rust-lang.org/std/ptr/fn.read.html"><code>read</code></a> from a pointer which is safer?</p> -<blockquote> -<p><code>src</code> must be properly aligned. Use <a href="https://doc.rust-lang.org/std/ptr/fn.read_unaligned.html"><code>read_unaligned</code></a> if this is not the case.</p> -</blockquote> -<p>Bingo! Both <code>read</code> and <code>read_unaligned</code>, unlike dereferencing the pointer, will perform a copy, but if it can make the code less prone to blowing up, I'll take it<sup class="footnote-reference"><a href="#6">6</a></sup>. Let's change the code one more time:</p> -<pre><code class="language-rust" data-lang="rust">let current = unsafe { window.as_ptr().cast::&lt;T&gt;().read_unaligned() }; -</code></pre> -<p>I prefer to avoid type annotations in variables where possible, which is why I use the <a href="https://www.reddit.com/r/rust/comments/3fimgp/why_double_colon_rather_that_dot/ctozkd0/">turbofish</a> so often. You can get rid of the cast and use a type annotation instead, but make sure the type is known, otherwise it will think it's <code>u8</code> because <code>window</code> is a <code>&amp;[u8]</code>.</p> -<p>Now, this is all cool and good. You can replace <code>i32</code> with <code>f32</code> for <code>T</code> and you'll be able to get halfway done with the step 4 of Cheat Engine's tutorial. Unfortunately, as it is, this code is not enough to complete step 4 with exact scans<sup class="footnote-reference"><a href="#7">7</a></sup>. You see, comparing floating point values is not as simple as checking for bitwise equality. We were actually really lucky that the <code>f32</code> part works! But the values in the <code>f64</code> part are not as precise as our inputs, so our exact scan fails.</p> -<p>Using a fixed type parameter is pretty limiting as well. On the one hand, it is nice that, if you scan for <code>i32</code>, the compiler statically guarantees that subsequent scans will also happen on <code>i32</code> and thus be compatible. On the other, this requires us to know the type at compile time, which for an interactive program, is not possible. While we <em>could</em> create different methods for each supported type and, at runtime, decide to which we should jump, I am not satisfied with that solution. It also means we can't switch from scanning an <code>u32</code> to an <code>i32</code>, for whatever reason.</p> -<p>So we need to work around this once more.</p> -<h2 id="rethinking-the-scans">Rethinking the scans</h2> -<p>What does our scanning function need, really? It needs a way to compare two chunks of memory as being equal or not (as we have seen, this isn't trivial with types such as floating point numbers) and, for other types of scans, it needs to be able to produce an ordering, or calculate a difference.</p> -<p>Instead of having a our trait require the bounds <code>PartialEq</code> and <code>PartialOrd</code>, we can define our own methods to compare <code>Self</code> with <code>&amp;[u8]</code>. It still should be <code>Clone</code>, so we can pass it around without worrying about lifetimes:</p> -<pre><code class="language-rust" data-lang="rust">// Callers must `assert_eq!(memory.len(), mem::size_of::&lt;Self&gt;())`. -unsafe fn eq(&amp;self, memory: &amp;[u8]) -&gt; bool; -unsafe fn cmp(&amp;self, memory: &amp;[u8]) -&gt; Ordering; -</code></pre> -<p>This can be trivially implemented for all integer types:</p> -<pre><code class="language-rust" data-lang="rust">macro_rules! impl_scannable_for_int { - ( $( $ty:ty ),* ) =&gt; { - $( - // SAFETY: caller is responsible to `assert_eq!(memory.len(), mem::size_of::&lt;T&gt;())` - impl Scannable for $ty { - unsafe fn eq(&amp;self, memory: &amp;[u8]) -&gt; bool { - let other = unsafe { memory.as_ptr().cast::&lt;$ty&gt;().read_unaligned() }; - *self == other - } - - unsafe fn cmp(&amp;self, memory: &amp;[u8]) -&gt; Ordering { - let other = unsafe { memory.as_ptr().cast::&lt;$ty&gt;().read_unaligned() }; - &lt;$ty as Ord&gt;::cmp(self, &amp;other) - } - } - )* - }; -} - -impl_scannable_for_int!(i8, u8, i16, u16, i32, u32, i64, u64); -</code></pre> -<p>The funny <code>&lt;$ty as Ord&gt;</code> is because I decided to call the method <code>Scannable::cmp</code>, so I have to disambiguate between it and <code>Ord::cmp</code>. We can go ahead and update the code using <code>Scannable</code> to use these new functions instead.</p> -<p>Now, you may have noticed I only implemented it for the integer types. That's because floats need some extra care. Unfortunately, floating point types do not have any form of &quot;precision&quot; embedded in them, so we can't accurately say &quot;compare these floats to the precision level the user specified&quot;. What we can do, however, is drop a few bits from the mantissa, so &quot;relatively close&quot; quantities are considered equal. It's definitely not as good as comparing floats to the user's precision, but it will get the job done.</p> -<p>I'm going to arbitrarily say that we are okay comparing with &quot;half&quot; the precision. We can achieve that by masking half of the bits from the mantissa to zero:</p> -<pre><code class="language-rust" data-lang="rust"> -macro_rules! impl_scannable_for_float { - ( $( $ty:ty : $int_ty:ty ),* ) =&gt; { - $( - #[allow(unused_unsafe)] // mind you, it is necessary - impl Scannable for $ty { - unsafe fn eq(&amp;self, memory: &amp;[u8]) -&gt; bool { - const MASK: $int_ty = !((1 &lt;&lt; (&lt;$ty&gt;::MANTISSA_DIGITS / 2)) - 1); - - // SAFETY: caller is responsible to `assert_eq!(memory.len(), mem::size_of::&lt;T&gt;())` - let other = unsafe { memory.as_ptr().cast::&lt;$ty&gt;().read_unaligned() }; - let left = &lt;$ty&gt;::from_bits(self.to_bits() &amp; MASK); - let right = &lt;$ty&gt;::from_bits(other.to_bits() &amp; MASK); - left == right - } - - ... - } - )* - }; -} - -impl_scannable_for_float!(f32: u32, f64: u64); -</code></pre> -<p>You may be wondering what's up with that weird <code>MASK</code>. Let's visualize it with a <a href="https://en.wikipedia.org/wiki/Bfloat16_floating-point_format"><code>f16</code></a>. This type has 16 bits, 1 for sign, 5 for exponent, and 10 for the mantissa:</p> -<pre><code>S EEEEE MMMMMMMMMM -</code></pre> -<p>If we substitute the constant with the numeric value and operate:</p> -<pre><code class="language-rust" data-lang="rust">!((1 &lt;&lt; (10 / 2)) - 1) -!((1 &lt;&lt; 5) - 1) -!(0b00000000_00100000 - 1) -!(0b00000000_00011111) -0b11111111_11100000 -</code></pre> -<p>So effectively, half of the mantisssa bit will be masked to 0. For the <code>f16</code> example, this makes us lose 5 bits of precision. Comparing two floating point values with their last five bits truncated is equivalent to checking if they are &quot;roughly equal&quot;!</p> -<p>When Cheat Engine scans for floating point values, several additional settings show, and one such option is &quot;truncated&quot;. I do not know if it behaves like this, but it might.</p> -<p>Let's try this out:</p> -<pre><code class="language-rust" data-lang="rust">#[test] -fn f32_roughly_eq() { - let left = 0.25f32; - let right = 0.25000123f32; - let memory = unsafe { mem::transmute::&lt;_, [u8; 4]&gt;(right) }; - assert_ne!(left, right); - assert!(unsafe { Scannable::eq(&amp;left, &amp;memory) }); -} -</code></pre> -<pre><code>&gt;cargo test f32_roughly_eq - -running 1 test -test scan::candidate_location_tests::f32_roughly_eq ... ok -</code></pre> -<p>Huzzah! The <code>assert_ne!</code> makes sure that a normal comparision would fail, and then we <code>assert!</code> that our custom one passes the test. When the user performs an exact scan, the code will be more tolerant to the user's less precise inputs, which overall should result in a nicer experience.</p> -<h2 id="dynamically-sized-scans">Dynamically sized scans</h2> -<p>The second problem we need to solve is the possibility of the size not being known at compile time<sup class="footnote-reference"><a href="#8">8</a></sup>. While we can go as far as scanning over strings of a known length, this is rather limiting, because we need to know the length at compile time<sup class="footnote-reference"><a href="#9">9</a></sup>. Heap allocated objects are another problem, because we don't want to compare the memory representation of the stack object, but likely the memory where they point to (such as <code>String</code>).</p> -<p>Instead of using <code>mem::size_of</code>, we can add a new method to our <code>Scannable</code>, <code>size</code>, which will tell us the size required of the memory view we're comparing against:</p> -<pre><code class="language-rust" data-lang="rust">unsafe impl Scannable { - ... - - fn size(&amp;self) -&gt; usize; -} -</code></pre> -<p>It is <code>unsafe</code> to implement, because we are relying on the returned value to be truthful and unchanging. It should be safe to call, because it cannot have any invariants. Unfortunately, signaling &quot;unsafe to implement&quot; is done by marking the entire trait as <code>unsafe</code>, since &quot;unsafe to call&quot; is reserved for <code>unsafe fn</code>, and even though the rest of methods are not necessarily unsafe to implement, they're treated as such.</p> -<p>At the moment, <code>Scannable</code> cannot be made into a trait object because it is <a href="https://doc.rust-lang.org/stable/error-index.html#E0038">not object safe</a>. This is caused by the <code>Clone</code> requirement on all <code>Scannable</code> object, which in turn needs the types to be <code>Sized</code> because <code>clone</code> returns <code>Self</code>. Because of this, the size must be known.</p> -<p>However, we <em>can</em> move the <code>Clone</code> requirement to the methods that need it! This way, <code>Scannable</code> can remain object safe, enabling us to do the following:</p> -<pre><code class="language-rust" data-lang="rust">unsafe impl&lt;T: AsRef&lt;dyn Scannable&gt; + AsMut&lt;dyn Scannable&gt;&gt; Scannable for T { - unsafe fn eq(&amp;self, memory: &amp;[u8]) -&gt; bool { - self.as_ref().eq(memory) - } - - unsafe fn cmp(&amp;self, memory: &amp;[u8]) -&gt; Ordering { - self.as_ref().cmp(memory) - } - - fn mem_view(&amp;self) -&gt; &amp;[u8] { - self.as_ref().mem_view() - } - - fn size(&amp;self) -&gt; usize { - self.as_ref().size() - } -} -</code></pre> -<p>Any type which can be interpreted as a reference to <code>Scannable</code> is also a scannable! This enables us to perform scans over <code>Box&lt;dyn i32&gt;</code>, where the type is known at runtime! Or rather, it would, if <code>Box&lt;dyn T&gt;</code> implemented <code>Clone</code>, which it can't<sup class="footnote-reference"><a href="#10">10</a></sup> because that's what prompted this entire issue. Dang it! I can't catch a breath today!</p> -<p>Okay, let's step back. Why did we need our scannables to be clone in the first place? When we perform exact scans, we store the original value in the region, which we don't own, so we clone it. But what if we <em>did</em> own the value? Instead of taking the <code>Scan</code> by reference, which holds <code>T: Scannable</code>, we could take it by value. If we get rid of all the <code>Clone</code> bounds and update <code>Scan::run</code> to take <code>self</code>, along with updating all the things that take a <code>Region</code> to take them by value as well, it should all work out.</p> -<p>But it does not. If we take <code>Scan</code> by value, with it not being <code>Clone</code>, we simply can't use it to scan over multiple regions. After the first region, we have lost the <code>Scan</code>.</p> -<p>Let's take a second step back. We are scanning memory, and we want to compare memory, but we want to treat the memory with different semantics (for example, if we treat it as <code>f32</code>, we want to check for rough equality). Instead of storing the <em>value</em> itself, we could store its <em>memory representation</em>, and when we compare memory representations, we can do so under certain semantics.</p> -<p>First off, let's revert getting rid of all <code>Clone</code>. Wherever we stored a <code>T</code>, we will now store a <code>Vec&lt;u8&gt;</code>. We will still use a type parameter to represent the &quot;implementations of <code>Scannable</code>&quot;. For this to work, our definitions need to use <code>T</code> somewhere, or else the compiler refuses to compile the code with error <a href="https://doc.rust-lang.org/stable/error-index.html#E0392">E0392</a>. For this, I will stick a <a href="https://doc.rust-lang.org/stable/std/marker/struct.PhantomData.html"><code>PhantomData</code></a> in the <code>Exact</code> variant. It's a bit pointless to include it in all variants, and <code>Exact</code> seems the most appropriated:</p> -<pre><code class="language-rust" data-lang="rust">pub enum Scan&lt;T: Scannable&gt; { - Exact(Vec&lt;u8&gt;, PhantomData&lt;T&gt;), - Unknown, - ... -} -</code></pre> -<p>This keeps in line with <code>Value</code>:</p> -<pre><code class="language-rust" data-lang="rust">pub enum Value&lt;T: Scannable&gt; { - Exact(Vec&lt;u8&gt;, PhantomData&lt;T&gt;), - ... -} -</code></pre> -<p>Our <code>Scannable</code> will no longer work on <code>T</code> and <code>&amp;[u8]</code>. Instead, it will work on two <code>&amp;[u8]</code>. We will also need a way to interpret a <code>T</code> as <code>&amp;[u8]</code>, which we can achieve with a new method, <code>mem_view</code>. This method interprets the raw memory representation of <code>self</code> as its raw bytes. It also lets us get rid of <code>size</code>, because we can simply do <code>mem_view().len()</code>. It's still <code>unsafe</code> to implement, because it should return the same length every time:</p> -<pre><code class="language-rust" data-lang="rust">pub unsafe trait Scannable { - // Callers must `assert_eq!(left.len(), right.len(), self.mem_view().len())`. - unsafe fn eq(left: &amp;[u8], right: &amp;[u8]) -&gt; bool; - unsafe fn cmp(left: &amp;[u8], right: &amp;[u8]) -&gt; Ordering; - fn mem_view(&amp;self) -&gt; &amp;[u8]; -} -</code></pre> -<p>But now we can't use it in trait object, so the following no longer works:</p> -<pre><code class="language-rust" data-lang="rust">unsafe impl&lt;T: AsRef&lt;dyn Scannable&gt; + AsMut&lt;dyn Scannable&gt;&gt; Scannable for T { - ... -} -</code></pre> -<p>Ugh! Well, to be fair, we no longer have a &quot;scannable&quot; at this point. It's more like a scan mode that tells us how memory should be compared according to a certain type. Let's split the trait into two: one for the scan mode, and other for &quot;things which are scannable&quot;:</p> -<pre><code class="language-rust" data-lang="rust">pub trait ScanMode { - unsafe fn eq(left: &amp;[u8], right: &amp;[u8]) -&gt; bool; - unsafe fn cmp(left: &amp;[u8], right: &amp;[u8]) -&gt; Ordering; -} - -pub unsafe trait Scannable { - type Mode: ScanMode; - - fn mem_view(&amp;self) -&gt; &amp;[u8]; -} -</code></pre> -<p>Note that we have an associated <code>type Mode</code> which contains the corresponding <code>ScanMode</code>. If we used a trait bound such as <code>Scannable: ScanMode</code>, we'd be back to square one: it would inherit the method definitions that don't use <code>&amp;self</code> and thus cannot be used as trait objects.</p> -<p>With these changes, it is possible to implement <code>Scannable</code> for any <code>dyn Scannable</code>:</p> -<pre><code class="language-rust" data-lang="rust">unsafe impl&lt;T: ScanMode + AsRef&lt;dyn Scannable&lt;Mode = Self&gt;&gt;&gt; Scannable for T { - type Mode = Self; - - fn mem_view(&amp;self) -&gt; &amp;[u8] { - self.as_ref().mem_view() - } -} -</code></pre> -<p>We do have to adjust a few places of the code to account for both <code>Scannable</code> and <code>ScanMode</code>, but all in all, it's pretty straightforward. Things like <code>Value</code> don't need to store the <code>Scannable</code> anymore, just a <code>Vec&lt;u8&gt;</code>. It also doesn't need the <code>ScanMode</code>, because it's not going to be scanning anything on its own. This applies transitively to <code>Region</code> which was holding a <code>Value</code>.</p> -<p><code>Value</code> <em>does</em> need to be updated to store the size of the region we are scanning for, however, because we need that information when running a subsequent scan. For all <code>Scan</code> that don't have a explicit thing to scan for (like <code>Decreased</code>), the <code>size</code> also needs to be stored in them.</p> -<p>Despite all our efforts, we're still unable to return an <code>Scannable</code> chosen at runtime.</p> -<pre><code class="language-rust" data-lang="rust">fn prompt_user_for_scan() -&gt; Scan&lt;Box&lt;dyn Scannable&lt;Mode = ???&gt;&gt;&gt; { - todo!() -} -</code></pre> -<p>As far as I can tell, there's simply no way to specify that type. We want to return a type which is scannable, which has itself (which is also a <code>ScanMode</code>) as the corresponding mode. Even if we just tried to return the mode, we simply can't, because it's not object-safe. Is this the end of the road?</p> -<h2 id="specifying-the-scan-mode">Specifying the scan mode</h2> -<p>We need a way to pass an arbitrary scan mode to our <code>Scan</code>. This scan mode should go in tandem with <code>Scannable</code> types, because it would be unsafe otherwise. We've seen that using a type just doesn't cut it. What else can we do?</p> -<p>Using an enumeration is a no-go, because I want users to be able to extend it further. I also would like to avoid having to update the <code>enum</code> and all the matches every time I come up with a different type combination. And it could get pretty complicated if I ever built something dynamically, such as letting the user combine different scans in one pass.</p> -<p>So what if we make <code>Scannable</code> return a value that implements the functions we need?</p> -<pre><code class="language-rust" data-lang="rust">pub struct ScanMode { - eq: unsafe fn(left: &amp;[u8], right: &amp;[u8]) -&gt; bool, - cmp: unsafe fn(left: &amp;[u8], right: &amp;[u8]) -&gt; Ordering, -} -</code></pre> -<p>It's definitely… non-conventional. But hey, now we're left with the <code>Scannable</code> trait, which is object-safe, and does not have any type parameters!</p> -<pre><code class="language-rust" data-lang="rust">pub unsafe trait Scannable { - fn mem_view(&amp;self) -&gt; &amp;[u8]; - fn scan_mode(&amp;self) -&gt; ScanMode; -} -</code></pre> -<p>It is a bit weird, but defining local functions and using those in the returned value is a nice way to keep things properly scoped:</p> -<pre><code class="language-rust" data-lang="rust">macro_rules! impl_scannable_for_int { - ( $( $ty:ty ),* ) =&gt; { - $( - unsafe impl Scannable for $ty { - fn mem_view(&amp;self) -&gt; &amp;[u8] { - unsafe { std::slice::from_raw_parts(self as *const _ as *const u8, mem::size_of::&lt;$ty&gt;()) } - } - - fn scan_mode(&amp;self) -&gt; ScanMode { - unsafe fn eq(left: &amp;[u8], right: &amp;[u8]) -&gt; bool { - ... - } - - unsafe fn cmp(left: &amp;[u8], right: &amp;[u8]) -&gt; Ordering { - ... - } - - ScanMode { eq, cmp } - } - } - )* - }; -} -</code></pre> -<p>Our <code>Scan</code> needs to store the <code>Scannable</code> type, and not just the memory, once again. For variants that don't need any value, they can store the <code>ScanMode</code> and size instead.</p> -<p>Does this solution work? Yes! It's possible to return a <code>Box&lt;dyn Scannable&gt;</code> from a function, and underneath, it may be using any type which is <code>Scannable</code>. Is this the best solution? Well, that's hard to say. This is <em>one</em> of the possible solutions.</p> -<p>We have been going around in circles for quite some time now, so I'll leave it there. It's a solution, which may not be pretty, but it works. With these changes, the code is capable of completing all of the steps in the Cheat Engine tutorial up until point!</p> -<h2 id="finale">Finale</h2> -<p>If there's one lesson to learn from this post, it's that there is often no single correct solution to a problem. We could have approached the scan types in many, many ways (and we tried quite a few!), but in the end, choosing one option or the other comes down to your (sometimes self-imposed) requirements.</p> -<p>You may <a href="https://github.com/lonami/memo">obtain the code for this post</a> over at my GitHub. You can run <code>git checkout step4</code> after cloning the repository to get the right version of the code. The code has gone through a lot of iterations, and I'd still like to polish it a bit more, so it might slightly differ from the code presented in this entry.</p> -<p>If you feel adventurous, Cheat Engine has different options for scanning floating point types: &quot;rounded (default)&quot;, &quot;rounded (extreme)&quot;, and truncated. Optionally, it can scan for &quot;simple values only&quot;. You could go ahead and toy around with these!</p> -<p>We didn't touch on types with different lengths, such as strings. You could support UTF-8, UTF-16, or arbitrary byte sequences. This post also didn't cover scanning for multiple things at once, known as &quot;groupscan commands&quot;, although from what I can tell, these are just a nice way to scan for arbitrary byte sequences.</p> -<p>We also didn't look into supporting different the same scan with different alignments. All these things may be worth exploring depending on your requirements. You could even get rid of such genericity and go with something way simpler. Supporting <code>i32</code>, <code>f32</code> and <code>f64</code> is enough to complete the Cheat Engine tutorial. But I wanted something more powerful, although my solution currently can't scan for a sequence such as &quot;exact type, unknown, exact matching the unknown&quot;. So yeah.</p> -<p>In the <a href="/blog/woce-5">next post</a>, we'll tackle the fifth step of the tutorial: Code finder. Cheat Engine attaches its debugger to the process for this one, and then replaces the instruction that performs the write with a different no-op so that nothing is written anymore. This will be quite the challenge!</p> -<h3 id="footnotes">Footnotes</h3> -<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup> -<p><a href="https://doc.rust-lang.org/stable/std/ops/trait.Drop.html#copy-and-drop-are-exclusive"><code>Copy</code> and <code>Drop</code> are exclusive</a>. See also <a href="https://doc.rust-lang.org/stable/error-index.html#E0184">E0184</a>.</p> -</div> -<div class="footnote-definition" id="2"><sup class="footnote-definition-label">2</sup> -<p>If you added more scan types that require additional bounds, make sure to add them too. For example, the &quot;decreased by&quot; scan requires the type to <code>impl Sub</code>.</p> -</div> -<div class="footnote-definition" id="3"><sup class="footnote-definition-label">3</sup> -<p>This is a good time to remind you to read the documentation. It is of special importance when dealing with <code>unsafe</code> methods; I recommend reading it a couple times.</p> -</div> -<div class="footnote-definition" id="4"><sup class="footnote-definition-label">4</sup> -<p>Even with this option, it would not be a bad idea to make the trait <code>unsafe</code>.</p> -</div> -<div class="footnote-definition" id="5"><sup class="footnote-definition-label">5</sup> -<p>Not for long. As we will find out later, this approach has its limitations.</p> -</div> -<div class="footnote-definition" id="6"><sup class="footnote-definition-label">6</sup> -<p>We can still perform the pointer dereference when we know it's aligned. This would likely be an optimization, although it would definitely complicate the code more.</p> -</div> -<div class="footnote-definition" id="7"><sup class="footnote-definition-label">7</sup> -<p>It <em>would</em> work if you scanned for unknown values and then checked for decreased values repeatedly. But we can't just leave exact scan broken!</p> -</div> -<div class="footnote-definition" id="8"><sup class="footnote-definition-label">8</sup> -<p>Unfortunately, this makes some optimizations harder or even impossible to perform. Providing specialized functions for types where the size is known at compile time could be worth doing. Programming is all tradeoffs.</p> -</div> -<div class="footnote-definition" id="9"><sup class="footnote-definition-label">9</sup> -<p><a href="https://blog.rust-lang.org/2021/02/26/const-generics-mvp-beta.html">Rust 1.51</a>, which was not out at the time of writing, would make it a lot easier to allow scanning for fixed-length sequences of bytes, thanks to const generics.</p> -</div> -<div class="footnote-definition" id="10"><sup class="footnote-definition-label">10</sup> -<p>Workarounds do exist, such as <a href="https://crates.io/crates/dyn-clone">dtolnay's <code>dyn-clone</code></a>. But I would rather not go that route.</p> -</div> -</content> - </entry> - <entry xml:lang="en"> - <title>Writing our own Cheat Engine: Unknown initial value</title> - <published>2021-02-19T00:00:00+00:00</published> - <updated>2021-02-19T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/woce-3/" type="text/html"/> - <id>https://lonami.dev/blog/woce-3/</id> - <content type="html"><p>This is part 3 on the <em>Writing our own Cheat Engine</em> series:</p> -<ul> -<li><a href="/blog/woce-1">Part 1: Introduction</a> (start here if you're new to the series!)</li> -<li><a href="/blog/woce-2">Part 2: Exact Value scanning</a></li> -<li>Part 3: Unknown initial value</li> -<li><a href="/blog/woce-4">Part 4: Floating points</a></li> -<li><a href="/blog/woce-5">Part 5: Code finder</a></li> -<li><a href="/blog/woce-6">Part 6: Pointers</a></li> -</ul> -<p>In part 2 we left off with a bit of a cliff-hanger. Our little program is now able to scan for an exact value, remember the couple hundred addresses pointing to said value, and perform subsequent scans to narrow the list of addresses down until we're left with a handful of them.</p> -<p>However, it is not always the case that you have an exact value to work with. The best you can do in these cases is guess what the software might be storing. For example, it could be a floating point for your current movement speed in a game, or an integer for your current health.</p> -<p>The problem with this is that there are far too many possible locations storing our desired value. If you count misaligned locations, this means there is a different location to address every single byte in memory. A program with one megabyte of memory already has a <em>million</em> of addresses. Clearly, we need to do better than performing one million memory reads<sup class="footnote-reference"><a href="#1">1</a></sup>.</p> -<p>This post will shift focus a bit from using <code>winapi</code> to possible techniques to perform the various scans.</p> -<h2 id="unknown-initial-value">Unknown initial value</h2> -<details open><summary>Cheat Engine Tutorial: Step 3</summary> -<blockquote> -<p>Ok, seeing that you've figured out how to find a value using exact value let's move on to the next step.</p> -<p>First things first though. Since you are doing a new scan, you have to click on New Scan first, to start a new scan. (You may think this is straighforward, but you'd be surprised how many people get stuck on that step) I won't be explaining this step again, so keep this in mind -Now that you've started a new scan, let's continue</p> -<p>In the previous test we knew the initial value so we could do a exact value, but now we have a status bar where we don't know the starting value. -We only know that the value is between 0 and 500. And each time you click 'hit me' you lose some health. The amount you lose each time is shown above the status bar.</p> -<p>Again there are several different ways to find the value. (like doing a decreased value by... scan), but I'll only explain the easiest. &quot;Unknown initial value&quot;, and decreased value. -Because you don't know the value it is right now, a exact value wont do any good, so choose as scantype 'Unknown initial value', again, the value type is 4-bytes. (most windows apps use 4-bytes)click first scan and wait till it's done.</p> -<p>When it is done click 'hit me'. You'll lose some of your health. (the amount you lost shows for a few seconds and then disappears, but you don't need that) -Now go to Cheat Engine, and choose 'Decreased Value' and click 'Next Scan' -When that scan is done, click hit me again, and repeat the above till you only find a few.</p> -<p>We know the value is between 0 and 500, so pick the one that is most likely the address we need, and add it to the list. -Now change the health to 5000, to proceed to the next step.</p> -</blockquote> -</details> -<h2 id="dense-memory-locations">Dense memory locations</h2> -<p>The key thing to notice here is that, when we read memory from another process, we do so over <em>entire regions</em>. A memory region is represented by a starting offset, a size, and a bunch of other things like protection level.</p> -<p>When running the first scan for an unknown value, all we need to remember is the starting offset and size for every single region. All the candidate locations that could point to our value fall within this range, so it is enough for us to store the range definition, and not every location within it.</p> -<p>To gain a better understanding of what this means, let's come up with a more specific scenario. With our current approach of doing things, we store an address (<code>usize</code>) for every location pointing to our desired value. In the case of unknown values, all locations are equally valid, since we don't know what value they should point to yet, and any value they point to is good. With this representation, we would end up with a very large vector:</p> -<pre><code class="language-rust" data-lang="rust">let locations = vec![0x2000, 0x2001, ..., 0x20ff, 0x2100]; -</code></pre> -<p>This representation is dense. Every single number in the range <code>0x2000..=0x2100</code> is present. So why bother storing the values individually when the range is enough?:</p> -<pre><code class="language-rust" data-lang="rust">let locations = EntireRegion { range: 0x2000..=0x2100 }; -</code></pre> -<p>Much better! With two <code>usize</code>, one for the starting location and another for the end, we can indicate that we care about all the locations falling in that range.</p> -<p>In fact, some accessible memory regions immediately follow eachother, so we could even compact this further and merge regions which are together. But due to their potential differences with regards to protection levels, we will not attempt to merge regions.</p> -<p>We don't want to get rid of the old way of storing locations, because once we start narrowing them down, we will want to go back to storing just a few candidates. To keep things tidy, let's introduce a new <code>enum</code> representing either possibility:</p> -<pre><code class="language-rust" data-lang="rust">use std::ops::Range; - -pub enum CandidateLocations { - Discrete { - locations: Vec&lt;usize&gt;, - }, - Dense { - range: Range&lt;usize&gt;, - } -} -</code></pre> -<p>Let's also introduce another <code>enum</code> to perform the different scan types. For the time being, we will only worry about looking for <code>i32</code> in memory:</p> -<pre><code class="language-rust" data-lang="rust">pub enum Scan { - Exact(i32), - Unknown, -} -</code></pre> -<h2 id="storing-scanned-values">Storing scanned values</h2> -<p>When scanning for exact values, it's not necessary to store the value found. We already know they're all the same, for example, value <code>42</code>. However, if the value is unknown, we do need to store it so that we can compare it in a subsequent scan to see if the value is the same or it changed. This means the value can be &quot;any within&quot; the read memory chunk:</p> -<pre><code class="language-rust" data-lang="rust">pub enum Value { - Exact(i32), - AnyWithin(Vec&lt;u8&gt;), -} -</code></pre> -<p>For every region in memory, there will be some candidate locations and a value (or value range) we need to compare against in subsequent scans:</p> -<pre><code class="language-rust" data-lang="rust">pub struct Region { - pub info: winapi::um::winnt::MEMORY_BASIC_INFORMATION, - pub locations: CandidateLocations, - pub value: Value, -} -</code></pre> -<p>With all the data structures needed setup, we can finally refactor our old scanning code into a new method capable of dealing with all these cases. For brevity, I will omit the exact scan, as it remains mostly unchanged:</p> -<pre><code class="language-rust" data-lang="rust">use winapi::um::winnt::MEMORY_BASIC_INFORMATION; - -... - -// inside `impl Process` -pub fn scan_regions(&amp;self, regions: &amp;[MEMORY_BASIC_INFORMATION], scan: Scan) -&gt; Vec&lt;Region&gt; { - regions - .iter() - .flat_map(|region| match scan { - Scan::Exact(n) =&gt; todo!(&quot;old scan implementation&quot;), - Scan::Unknown =&gt; { - let base = region.BaseAddress as usize; - match self.read_memory(region.BaseAddress as _, region.RegionSize) { - Ok(memory) =&gt; Some(Region { - info: region.clone(), - locations: CandidateLocations::Dense { - range: base..base + region.RegionSize, - }, - value: Value::AnyWithin(memory), - }), - Err(_) =&gt; None, - } - } - }) - .collect() -} -</code></pre> -<p>Time to try it out!</p> -<pre><code class="language-rust" data-lang="rust">impl CandidateLocations { - pub fn len(&amp;self) -&gt; usize { - match self { - CandidateLocations::Discrete { locations } =&gt; locations.len(), - CandidateLocations::Dense { range } =&gt; range.len(), - } - } -} - -... - -fn main() { - // -snip- - - println!(&quot;Scanning {} memory regions&quot;, regions.len()); - let last_scan = process.scan_regions(&amp;regions, Scan::Unknown); - println!( - &quot;Found {} locations&quot;, - last_scan.iter().map(|r| r.locations.len()).sum::&lt;usize&gt;() - ); -} -</code></pre> -<pre><code>Scanning 88 memory regions -Found 3014656 locations -</code></pre> -<p>If we consider misaligned locations, there is a lot of potential addresses where we could look for. Running the same scan on Cheat Engine yields <code>2,449,408</code> addresses, which is pretty close. It's probably skipping some additional regions that we are considering. Emulating Cheat Engine to perfection is not a concern for us at the moment, so I'm not going to investigate what regions it actually uses.</p> -<h2 id="comparing-scanned-values">Comparing scanned values</h2> -<p>Now that we have performed the initial scan and have stored all the <code>CandidateLocations</code> and <code>Value</code>, we can re-implement the &quot;next scan&quot; step to handle any variant of our <code>Scan</code> enum. This enables us to mix-and-match any <code>Scan</code> mode in any order. For example, one could perform an exact scan, then one for decreased values, or start with unknown scan and scan for unchanged values.</p> -<p>The tutorial suggests using &quot;decreased value&quot; scan, so let's start with that:</p> -<pre><code class="language-rust" data-lang="rust">pub enum Scan { - Exact(i32), - Unknown, - Decreased, // new! -} -</code></pre> -<p>Other scanning modes, such as decreased by a known amount rather than any decrease, increased, unchanged, changed and so on, are not very different from the &quot;decreased&quot; scan, so I won't bore you with the details.</p> -<p>I will use a different method to perform a &quot;rescan&quot;, since the first one is a bit more special in that it doesn't start with any previous values:</p> -<pre><code class="language-rust" data-lang="rust">pub fn rescan_regions(&amp;self, regions: &amp;[Region], scan: Scan) -&gt; Vec&lt;Region&gt; { - regions - .iter() - .flat_map(|region| match scan { - Scan::Decreased =&gt; { - let mut locations = Vec::new(); - match region.locations { - CandidateLocations::Dense { range } =&gt; { - match self.read_memory(range.start, range.end - range.start) { - Ok(memory) =&gt; match region.value { - Value::AnyWithin(previous) =&gt; { - memory - .windows(4) - .zip(previous.windows(4)) - .enumerate() - .step_by(4) - .for_each(|(offset, (new, old))| { - let new = i32::from_ne_bytes([ - new[0], new[1], new[2], new[3], - ]); - let old = i32::from_ne_bytes([ - old[0], old[1], old[2], old[3], - ]); - if new &lt; old { - locations.push(range.start + offset); - } - }); - - Some(Region { - info: region.info.clone(), - locations: CandidateLocations::Discrete { locations }, - value: Value::AnyWithin(memory), - }) - } - _ =&gt; todo!(), - }, - _ =&gt; todo!(), - } - } - _ =&gt; todo!(), - } - } - _ =&gt; todo!(), - }) - .collect() -} -</code></pre> -<p>If you've skimmed over that, I do not blame you. Here's the summary: for every existing region, when executing the scan mode &quot;decreased&quot;, if the previous locations were dense, read the entire memory region. On success, if the previous values were a chunk of memory, iterate over the current and old memory at the same time, and for every aligned <code>i32</code>, if the new value is less, store it.</p> -<p>It's also making me ill. Before I leave a mess on the floor, does it work?</p> -<pre><code class="language-rust" data-lang="rust">std::thread::sleep(std::time::Duration::from_secs(10)); -let last_scan = process.rescan_regions(&amp;last_scan, Scan::Decreased); -println!( - &quot;Found {} locations&quot;, - last_scan.iter().map(|r| r.locations.len()).sum::&lt;usize&gt;() -); -</code></pre> -<pre><code class="language-rust" data-lang="rust">Found 3014656 locations -Found 177 locations -</code></pre> -<p>Okay, great, let's clean up this mess…</p> -<h2 id="refactoring">Refactoring</h2> -<p>Does it also make you uncomfortable to be writing something that you know will end up <em>huge</em> unless you begin refactoring other parts right now? I definitely feel that way. But I think it's good discipline to push through with something that works first, even if it's nasty, before going on a tangent. Now that we have the basic implementation working, let's take on this monster before it eats us alive.</p> -<p>First things first, that method is inside an <code>impl</code> block. The deepest nesting level is 13. I almost have to turn around my chair to read the entire thing out!</p> -<p>Second, we're nesting four matches. Three of them we care about: scan, candidate location, and value. If each of these <code>enum</code> has <code>S</code>, <code>C</code> and <code>V</code> variants respectively, writing each of these by hand will require <code>S * C * V</code> different implementations! Cheat Engine offers 10 different scans, I can think of at least 3 different ways to store candidate locations, and another 3 ways to store the values found. That's <code>10 * 3 * 3 = 90</code> different combinations. I am not willing to write out all these<sup class="footnote-reference"><a href="#2">2</a></sup>, so we need to start introducing some abstractions. Just imagine what a monster function you would end with! The horror!</p> -<p>Third, why is the scan being executed in the process? This is something that should be done in the <code>impl Scan</code> instead!</p> -<p>Let's begin the cleanup:</p> -<pre><code class="language-rust" data-lang="rust">pub fn rescan_regions(&amp;self, regions: &amp;[Region], scan: Scan) -&gt; Vec&lt;Region&gt; { - todo!() -} -</code></pre> -<p>I already feel ten times better.</p> -<p>Now, this method will unconditionally read the entire memory region, even if the scan or the previous candidate locations don't need it<sup class="footnote-reference"><a href="#3">3</a></sup>. In the worst case with a single discrete candidate location, we will be reading a very large chunk of memory when we could have read just the 4 bytes needed for the <code>i32</code>. On the bright side, if there <em>are</em> more locations in this memory region, we will get read of them at the same time<sup class="footnote-reference"><a href="#4">4</a></sup>. So even if we're moving more memory around all the time, it isn't <em>too</em> bad.</p> -<pre><code class="language-rust" data-lang="rust">regions - .iter() - .flat_map( - |region| match self.read_memory(region.info.BaseAddress as _, region.info.RegionSize) { - Ok(memory) =&gt; todo!(), - Err(err) =&gt; { - eprintln!( - &quot;Failed to read {} bytes at {:?}: {}&quot;, - region.info.RegionSize, region.info.BaseAddress, err, - ); - None - } - }, - ) - .collect() -</code></pre> -<p>Great! If reading memory succeeds, we want to rerun the scan:</p> -<pre><code class="language-rust" data-lang="rust">Ok(memory) =&gt; Some(scan.rerun(region, memory)), -</code></pre> -<p>The rerun will live inside <code>impl Scan</code>:</p> -<pre><code class="language-rust" data-lang="rust">pub fn rerun(&amp;self, region: &amp;Region, memory: Vec&lt;u8&gt;) -&gt; Region { - match self { - Scan::Exact(_) =&gt; self.run(region.info.clone(), memory), - Scan::Unknown =&gt; region.clone(), - Scan::Decreased =&gt; todo!(), - } -} -</code></pre> -<p>An exact scan doesn't care about any previous values, so it behaves like a first scan. The first scan is done by the <code>run</code> function (it contains the implementation factored out of the <code>Process::scan_regions</code> method), which only needs the region information and the current memory chunk we just read.</p> -<p>The unknown scan leaves the region unchanged: any value stored is still valid, because it is unknown what we're looking for.</p> -<p>The decreased scan will have to iterate over all the candidate locations, and compare them with the current memory chunk. But this time, we'll abstract this iteration too:</p> -<pre><code class="language-rust" data-lang="rust">impl Region { - fn iter_locations&lt;'a&gt;( - &amp;'a self, - new_memory: &amp;'a [u8], - ) -&gt; impl Iterator&lt;Item = (usize, i32, i32)&gt; + 'a { - match &amp;self.locations { - CandidateLocations::Dense { range } =&gt; range.clone().step_by(4).map(move |addr| { - let old = self.value_at(addr); - let new = i32::from_ne_bytes([ - new_memory[0], - new_memory[1], - new_memory[2], - new_memory[3], - ]); - (addr, old, new) - }), - _ =&gt; todo!(), - } - } -} -</code></pre> -<p>For a dense candidate location, we iterate over all the 4-aligned addresses (fast scan for <code>i32</code> values), and yield <code>(current address, old value, new value)</code>. This way, the <code>Scan</code> can do anything it wants with the old and new values, and if it finds a match, it can use the address.</p> -<p>The <code>value_at</code> method will deal with all the <code>Value</code> variants:</p> -<pre><code class="language-rust" data-lang="rust">fn value_at(&amp;self, addr: usize) -&gt; i32 { - match &amp;self.value { - Value::AnyWithin(chunk) =&gt; { - let base = addr - self.info.BaseAddress as usize; - let bytes = &amp;chunk[base..base + 4]; - i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) - } - _ =&gt; todo!(), - } -} -</code></pre> -<p>This way, <code>iter_locations</code> can easily use any value type. With this, we have all <code>enum</code> covered: <code>Scan</code> in <code>rerun</code>, <code>CandidateLocation</code> in <code>iter_locations</code>, and <code>Value</code> in <code>value_at</code>. Now we can add as many variants as we want, and we will only need to update a single <code>match</code> arm for each of them. Let's implement <code>Scan::Decreased</code> and try it out:</p> -<pre><code class="language-rust" data-lang="rust">pub fn rerun(&amp;self, region: &amp;Region, memory: Vec&lt;u8&gt;) -&gt; Region { - match self { - Scan::Decreased =&gt; Region { - info: region.info.clone(), - locations: CandidateLocations::Discrete { - locations: region - .iter_locations(&amp;memory) - .flat_map(|(addr, old, new)| if new &lt; old { Some(addr) } else { None }) - .collect(), - }, - value: Value::AnyWithin(memory), - },, - } -} -</code></pre> -<pre><code>Found 3014656 locations -Found 223791 locations -</code></pre> -<p>Hmm… before we went down from <code>3014656</code> to <code>177</code> locations, and now we went down to <code>223791</code>. Where did we go wrong?</p> -<p>After spending several hours on this, I can tell you where we went wrong. <code>iter_locations</code> is always accessing the memory range <code>0..4</code>, and not the right address. Here's the fix:</p> -<pre><code class="language-rust" data-lang="rust">CandidateLocations::Dense { range } =&gt; range.clone().step_by(4).map(move |addr| { - let old = self.value_at(addr); - let base = addr - self.info.BaseAddress as usize; - let bytes = &amp;new_memory[base..base + 4]; - let new = i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); - (addr, old, new) -}), -</code></pre> -<h2 id="going-beyond">Going beyond</h2> -<p>Let's take a look at other possible <code>Scan</code> types. Cheat Engine supports the following initial scan types:</p> -<ul> -<li>Exact Value</li> -<li>Bigger than…</li> -<li>Smaller than…</li> -<li>Value between…</li> -<li>Unknown initial value</li> -</ul> -<p>&quot;Bigger than&quot; and &quot;Smaller than&quot; can both be represented by &quot;Value between&quot;, so it's pretty much just three.</p> -<p>For subsequent scans, in addition to the scan types described above, we find:</p> -<ul> -<li>Increased value</li> -<li>Increased value by…</li> -<li>Decreased value</li> -<li>Decreased value by…</li> -<li>Changed value</li> -<li>Unchanged value</li> -</ul> -<p>Not only does Cheat Engine provide all of these scans, but all of them can also be negated. For example, &quot;find values that were not increased by 7&quot;. One could imagine to also support things like &quot;increased value by range&quot;. For the increased and decreased scans, Cheat Engine also supports &quot;at least xx%&quot;, so that if the value changed within the specified percentage interval, it will be considered.</p> -<p>What about <code>CandidateLocations</code>? I can't tell you how Cheat Engine stores these, but I can tell you that <code>CandidateLocations::Discrete</code> can still be quite inefficient. Imagine you've started with a scan for unknown values and then ran a scan for unchanged valueus. Most values in memory will have been unchanged, but with our current implementation, we are now storing an entire <code>usize</code> address for each of these. One option would be to introduce <code>CandidateLocations::Sparse</code>, which would be a middle ground. You could implement it like <code>Dense</code> and include a vector of booleans telling you which values to consider, or go smaller and use a bitstring or bit vector. You could use a sparse vector data structure.</p> -<p><code>Value</code> is very much like <code>CandidateLocations</code>, except that it stores a value to compare against and not an address. Here we can either have an exact value, or an older copy of the memory. Again, keeping a copy of the entire memory chunk when all we need is a handful of values is inefficient. You could keep a mapping from addresses to values if you don't have too many. Or you could shrink and fragment the copied memory in a more optimal way. There's a lot of room for improvement!</p> -<p>What if, despite all of the efforts above, we still don't have enough RAM to store all this information? The Cheat Engine Tutorial doesn't use a lot of memory, but as soon as you try scanning bigger programs, like games, you may find yourself needing several gigabytes worth of memory to remember all the found values in order to compare them in subsequent scans. You may even need to consider dumping all the regions to a file and read from it to run the comparisons. For example, running a scan for &quot;unknown value&quot; in Cheat Engine brings its memory up by the same amount of memory used by the process scanned (which makes sense), but as soon as I ran a scan for &quot;unchanged value&quot; over the misaligned values, Cheat Engine's disk usage skyrocketed to 1GB/s (!) for several seconds on my SSD. After it finished, memory usage went down to normal. It was very likely writing out all candidate locations to disk.</p> -<h2 id="finale">Finale</h2> -<p>There is a lot of things to learn from Cheat Engine just by observing its behaviour, and we're only scratching its surface.</p> -<p>In the <a href="/blog/woce-4">next post</a>, we'll tackle the fourth step of the tutorial: Floating points. So far, we have only been working with <code>i32</code> for simplicity. We will need to update our code to be able to account for different data types, which will make it easy to support other types like <code>i16</code>, <code>i64</code>, or even strings, represented as an arbitrary sequence of bytes.</p> -<p>As usual, you can <a href="https://github.com/lonami/memo">obtain the code for this post</a> over at my GitHub. You can run <code>git checkout step3</code> after cloning the repository to get the right version of the code. This version is a bit cleaner than the one presented in the blog, and contains some of the things described in the <a href="https://lonami.dev/blog/woce-3/#going-beyond">Going beyond</a> section. Until next time!</p> -<h3 id="footnotes">Footnotes</h3> -<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup> -<p>Well, technically, we will perform a million memory reads<sup class="footnote-reference"><a href="#5">5</a></sup>. The issue here is the million calls to <code>ReadProcessMemory</code>, not reading memory per se.</p> -</div> -<div class="footnote-definition" id="2"><sup class="footnote-definition-label">2</sup> -<p>Not currently. After a basic implementation works, writing each implementation by hand and fine-tuning them by treating each of them as a special case could yield significant speed improvements. So although it would be a lot of work, this option shouldn't be ruled out completely.</p> -</div> -<div class="footnote-definition" id="3"><sup class="footnote-definition-label">3</sup> -<p>You could ask the candidate locations where one should read, which would still keep the code reasonably simple.</p> -</div> -<div class="footnote-definition" id="4"><sup class="footnote-definition-label">4</sup> -<p>You could also optimize for this case by determining both the smallest and largest address, and reading enough to cover them both. Or apply additional heuristics to only do so if the ratio of the size you're reading compared to the size you need isn't too large and abort the joint read otherwise. There is a lot of room for optimization here.</p> -</div> -<div class="footnote-definition" id="5"><sup class="footnote-definition-label">5</sup> -<p>(A footnote in a footnote?) The machine registers, memory cache and compiler will all help lower this cost, so the generated executable might not actually need that many reads from RAM. But that's getting way too deep into the details now.</p> -</div> -</content> - </entry> - <entry xml:lang="en"> - <title>Writing our own Cheat Engine: Exact Value scanning</title> - <published>2021-02-12T00:00:00+00:00</published> - <updated>2021-02-19T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/woce-2/" type="text/html"/> - <id>https://lonami.dev/blog/woce-2/</id> - <content type="html"><p>This is part 2 on the <em>Writing our own Cheat Engine</em> series:</p> -<ul> -<li><a href="/blog/woce-1">Part 1: Introduction</a> (start here if you're new to the series!)</li> -<li>Part 2: Exact Value scanning</li> -<li><a href="/blog/woce-3">Part 3: Unknown initial value</a></li> -<li><a href="/blog/woce-4">Part 4: Floating points</a></li> -<li><a href="/blog/woce-5">Part 5: Code finder</a></li> -<li><a href="/blog/woce-6">Part 6: Pointers</a></li> -</ul> -<p>In the introduction, we spent a good deal of time enumerating all running processes just so we could find out the pid we cared about. With the pid now in our hands, we can do pretty much anything to its corresponding process.</p> -<p>It's now time to read the process' memory and write to it. If our process was a single-player game, this would enable us to do things like setting a very high value on the player's current health pool, making us invincible. This technique will often not work for multi-player games, because the server likely knows your true current health (the most you could probably do is make the client render an incorrect value). However, if the server is crappy and it trusts the client, then you're still free to mess around with your current health.</p> -<p>Even if we don't want to write to the process' memory, reading is still very useful. Maybe you could enhance your experience by making a custom overlay that displays useful information, or something that makes noise if it detects the life is too low, or even simulating a keyboard event to automatically recover some mana when you're running low.</p> -<p>Be warned about anti-cheat systems. Anything beyond a basic game is likely to have some protection measures in place, making the analysis more difficult (perhaps the values are scrambled in memory), or even pinging the server if it detects something fishy.</p> -<p><strong>I am not responsible for any bans!</strong> Use your brain before messing with online games, and don't ruin the fun for everyone else. If you get caught for cheating, I don't want to know about it.</p> -<p>Now that all <a href="https://www.urbandictionary.com/define.php?term=script%20kiddie">script kiddies</a> have left the room, let's proceed with the post.</p> -<h2 id="exact-value-scanning">Exact Value scanning</h2> -<details open><summary>Cheat Engine Tutorial: Step 2</summary> -<blockquote> -<p>Now that you have opened the tutorial with Cheat Engine let's get on with the next step.</p> -<p>You can see at the bottom of this window is the text Health: xxx. Each time you click 'Hit me' your health gets decreased.</p> -<p>To get to the next step you have to find this value and change it to 1000</p> -<p>To find the value there are different ways, but I'll tell you about the easiest, 'Exact Value': First make sure value type is set to at least 2-bytes or 4-bytes. 1-byte will also work, but you'll run into an easy to fix problem when you've found the address and want to change it. The 8-byte may perhaps works if the bytes after the address are 0, but I wouldn't take the bet. Single, double, and the other scans just don't work, because they store the value in a different way.</p> -<p>When the value type is set correctly, make sure the scantype is set to 'Exact Value'. Then fill in the number your health is in the value box. And click 'First Scan'. After a while (if you have a extremely slow pc) the scan is done and the results are shown in the list on the left</p> -<p>If you find more than 1 address and you don't know for sure which address it is, click 'Hit me', fill in the new health value into the value box, and click 'Next Scan'. Repeat this until you're sure you've found it. (that includes that there's only 1 address in the list.....)</p> -<p>Now double click the address in the list on the left. This makes the address pop-up in the list at the bottom, showing you the current value. Double click the value, (or select it and press enter), and change the value to 1000.</p> -<p>If everything went ok the next button should become enabled, and you're ready for the next step.</p> -<p>Note: If you did anything wrong while scanning, click &quot;New Scan&quot; and repeat the scanning again. Also, try playing around with the value and click 'hit me'</p> -</blockquote> -</details> -<h2 id="our-first-scan">Our First Scan</h2> -<p>The Cheat Engine tutorial talks about &quot;value types&quot; and &quot;scan types&quot; like &quot;exact value&quot;.</p> -<p>The <strong>value types</strong> will help us narrow down <em>what</em> we're looking for. For example, the integer type <code>i32</code> is represented in memory as 32 bits, or 4 bytes. However, <code>f32</code> is <em>also</em> represented by 4 bytes, and so is <code>u32</code>. Or perhaps the 4 bytes represent RGBA values of a color! So any 4 bytes in memory can be interpreted in many ways, and it's up to us to decide which way we interpret the bytes in.</p> -<p>When programming, numbers which are 32-bit wide are common, as they're a good (and fast) size to work with. Scanning for this type is often a good bet. For positive numbers, <code>i32</code> is represented the same as <code>u32</code> in memory, so even if the value turns out to not be signed, the scan is likely to work. Focusing on <code>i32</code> will save us from scanning for <code>f32</code> or even other types, like interpreting 8 bytes for <code>i64</code>, <code>f64</code>, or less bytes like <code>i16</code>.</p> -<p>The <strong>scan types</strong> will help us narrow down <em>how</em> we're looking for a value. Scanning for an exact value means what you think it does: interpret all 4 bytes in the process' memory as our value type, and check if they exactly match our value. This will often yield a lot of candidates, but it will be enough to get us started. Variations of the exact scan include checking for all values below a threshold, above, in between, or even just… unknown.</p> -<p>What's the point of scanning for unknown values if <em>everything</em> in memory is unknown? Sometimes you don't have a concrete value. Maybe your health pool is a bar and it nevers tell you how much health you actually have, just a visual indicator of your percentage left, even if the health is not stored as a percentage. As we will find later on, scanning for unknown values is more useful than it might appear at first.</p> -<p>We can access the memory of our own program by guessing random pointers and trying to read from them. But Windows isolates the memory of each program, so no pointer we could ever guess will let us read from the memory of another process. Luckily for us, searching for &quot;read process memory winapi&quot; leads us to the <a href="https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-readprocessmemory"><code>ReadProcessMemory</code></a> function. Spot on.</p> -<pre><code class="language-rust" data-lang="rust">pub fn read_memory(&amp;self, addr: usize, n: usize) -&gt; io::Result&lt;Vec&lt;u8&gt;&gt; { - todo!() -} -</code></pre> -<p>Much like trying to dereference a pointer pointing to released memory or even null, reading from an arbitrary address can fail for the same reasons (and more). We will want to signal this with <code>io::Result</code>. It's funny to note that, even though we're doing something that seems wildly unsafe (reading arbitrary memory, even if the other process is mutating it at the same time), the function is perfectly safe. If we cannot read something, it will return <code>Err</code>, but if it succeeds, it has taken a snapshot of the memory of the process, and the returned value will be correctly initialized.</p> -<p>The function will be defined inside our <code>impl Process</code>, since it conveniently holds an open handle to the process in question. It takes <code>&amp;self</code>, because we do not need to mutate anything in the <code>Process</code> instance. After adding the <code>memoryapi</code> feature to <code>Cargo.toml</code>, we can perform the call:</p> -<pre><code class="language-rust" data-lang="rust">let mut buffer = Vec::&lt;u8&gt;::with_capacity(n); -let mut read = 0; - -// SAFETY: the buffer points to valid memory, and the buffer size is correctly set. -if unsafe { - winapi::um::memoryapi::ReadProcessMemory( - self.handle.as_ptr(), - addr as *const _, - buffer.as_mut_ptr().cast(), - buffer.capacity(), - &amp;mut read, - ) -} == FALSE -{ - Err(io::Error::last_os_error()) -} else { - // SAFETY: the call succeeded and `read` contains the amount of bytes written. - unsafe { buffer.set_len(read as usize) }; - Ok(buffer) -} -</code></pre> -<p>Great! But the address space is somewhat large. 64 bits large. Eighteen quintillion, four hundred and forty-six quadrillion, seven hundred and forty-four trillion, seventy-three billion, seven hundred and nine million, five hundred and fifty-one thousand, six hundred and sixteen<sup class="footnote-reference"><a href="#1">1</a></sup> large. You gave up reading that, didn't you? Anyway, 18'446'744'073'709'551'616 is a <em>big</em> number.</p> -<p>I am not willing to wait for the program to scan over so many values. I don't even have 16 <a href="https://en.wikipedia.org/wiki/Orders_of_magnitude_(data)">exbibytes</a> of RAM installed on my laptop yet<sup class="footnote-reference"><a href="#2">2</a></sup>! What's up with that?</p> -<h2 id="memory-regions">Memory regions</h2> -<p>The program does not actually have all that memory allocated (surprise!). Random-guessing an address is extremely likely to point out to invalid memory. Reading from the start of the address space all the way to the end would not be any better. And we <strong>need</strong> to do better.</p> -<p>We need to query for the memory regions allocated to the program. For this purpose we can use <a href="https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualqueryex"><code>VirtualQueryEx</code></a>.</p> -<blockquote> -<p>Retrieves information about a range of pages within the virtual address space of a specified process.</p> -</blockquote> -<p>We have enumerated things before, and this function is not all that different.</p> -<pre><code class="language-rust" data-lang="rust">fn memory_regions(&amp;self) -&gt; io::Result&lt;winapi::um::winnt::MEMORY_BASIC_INFORMATION&gt; { - let mut info = MaybeUninit::uninit(); - - // SAFETY: the info structure points to valid memory. - let written = unsafe { - winapi::um::memoryapi::VirtualQueryEx( - self.handle.as_ptr(), - std::ptr::null(), - info.as_mut_ptr(), - mem::size_of::&lt;winapi::um::winnt::MEMORY_BASIC_INFORMATION&gt;(), - ) - }; - if written == 0 { - Err(io::Error::last_os_error()) - } else { - // SAFETY: a non-zero amount was written to the structure - Ok(unsafe { info.assume_init() }) - } -} -</code></pre> -<p>We start with a base address of zero<sup class="footnote-reference"><a href="#3">3</a></sup> (<code>std::ptr::null()</code>), and ask the function to tell us what's in there. Let's try it out, with the <code>impl-debug</code> crate feature in <code>Cargo.toml</code>:</p> -<pre><code class="language-rust" data-lang="rust">dbg!(process.memory_regions()); -</code></pre> -<pre><code>&gt;cargo run -Compiling memo v0.1.0 - -error[E0277]: `winapi::um::winnt::MEMORY_BASIC_INFORMATION` doesn't implement `std::fmt::Debug` - --&gt; src\main.rs:185:5 - | -185 | dbg!(process.memory_regions()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `winapi::um::winnt::MEMORY_BASIC_INFORMATION` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` -</code></pre> -<p>That's annoying. It seems not everything has an <code>impl std::fmt::Debug</code>, and <a href="https://github.com/retep998/winapi-rs/issues/548#issuecomment-355278090">you're supposed to send a PR</a> if you want it to have debug, even if the <code>impl-debug</code> feature is set. I'm surprised they don't auto-generate all of this and have to rely on manually adding <code>Debug</code> as needed? Oh well, let's get rid of the feature and print it out ourselves:</p> -<pre><code>eprintln!( - &quot;Region: - BaseAddress: {:?} - AllocationBase: {:?} - AllocationProtect: {:?} - RegionSize: {:?} - State: {:?} - Protect: {:?} - Type: {:?}&quot;, - region.BaseAddress, - region.AllocationBase, - region.AllocationProtect, - region.RegionSize, - region.State, - region.Protect, - region.Type, -); -</code></pre> -<p>Hopefully we don't need to do this often:</p> -<pre><code>&gt;cargo run - Compiling memo v0.1.0 - Finished dev [unoptimized + debuginfo] target(s) in 0.60s - Running `target\debug\memo.exe` - -Region: - BaseAddress: 0x0 - AllocationBase: 0x0 - AllocationProtect: 0 - RegionSize: 65536 - State: 65536 - Protect: 1 - Type: 0 -</code></pre> -<p>Awesome! There is a region at <code>null</code>, and the <code>AllocationProtect</code> of zero indicates that &quot;the caller does not have access&quot; when the region was created. However, <code>Protect</code> is <code>1</code>, and that is the <em>current</em> protection level. A value of one indicates <a href="https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants"><code>PAGE_NOACCESS</code></a>:</p> -<blockquote> -<p>Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed region results in an access violation.</p> -</blockquote> -<p>Now that we know that the first region starts at 0 and has a size of 64 KiB, we can simply query for the page at <code>(current base + current size)</code> to fetch the next region. Essentially, we want to loop until it fails, after which we'll know there are no more pages<sup class="footnote-reference"><a href="#4">4</a></sup>:</p> -<pre><code class="language-rust" data-lang="rust">pub fn memory_regions(&amp;self) -&gt; Vec&lt;winapi::um::winnt::MEMORY_BASIC_INFORMATION&gt; { - let mut base = 0; - let mut regions = Vec::new(); - let mut info = MaybeUninit::uninit(); - - loop { - // SAFETY: the info structure points to valid memory. - let written = unsafe { - winapi::um::memoryapi::VirtualQueryEx( - self.handle.as_ptr(), - base as *const _, - info.as_mut_ptr(), - mem::size_of::&lt;winapi::um::winnt::MEMORY_BASIC_INFORMATION&gt;(), - ) - }; - if written == 0 { - break regions; - } - // SAFETY: a non-zero amount was written to the structure - let info = unsafe { info.assume_init() }; - base = info.BaseAddress as usize + info.RegionSize; - regions.push(info); - } -} -</code></pre> -<p><code>RegionSize</code> is:</p> -<blockquote> -<p>The size of the region beginning at the base address in which all pages have identical attributes, in bytes.</p> -</blockquote> -<p>…which also hints that the value we want is &quot;base address&quot;, not the &quot;allocation base&quot;. With these two values, we can essentially iterate over all the page ranges:</p> -<pre><code class="language-rust" data-lang="rust">dbg!(process.memory_regions().len()); -</code></pre> -<pre><code>&gt;cargo run - Compiling memo v0.1.0 - Finished dev [unoptimized + debuginfo] target(s) in 0.63s - Running `target\debug\memo.exe` - -[src\main.rs:189] process.memory_regions().len() = 367 -</code></pre> -<p>That's a lot of pages!</p> -<h2 id="protection-levels">Protection levels</h2> -<p>Let's try to narrow the amount of pages down. How many pages aren't <code>PAGE_NOACCESS</code>?</p> -<pre><code class="language-rust" data-lang="rust">dbg!(process - .memory_regions() - .into_iter() - .filter(|p| p.Protect != winapi::um::winnt::PAGE_NOACCESS) - .count()); -</code></pre> -<pre><code>295 -</code></pre> -<p>Still a fair bit! Most likely, there are just a few interleaved <code>NOACCESS</code> pages, and the rest are allocated each with different protection levels. How much memory do we need to scan through?</p> -<pre><code class="language-rust" data-lang="rust">dbg!(process - .memory_regions() - .into_iter() - .filter(|p| p.Protect != winapi::um::winnt::PAGE_NOACCESS) - .map(|p| p.RegionSize) - .sum::&lt;usize&gt;()); -</code></pre> -<pre><code>4480434176 -</code></pre> -<p>Wait, what? What do you mean over 4 GiB? The Task Manager claims that the Cheat Engine Tutorial is only using 2.1 MB worth of RAM! Perhaps we can narrow down the <a href="https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants">protection levels</a> a bit more. If you look at the scan options in Cheat Engine, you will notice the &quot;Memory Scan Options&quot; groupbox. By default, it only scans for memory that is writable, and doesn't care if it's executable or not:</p> -<pre><code class="language-rust" data-lang="rust">let mask = winnt::PAGE_EXECUTE_READWRITE - | winnt::PAGE_EXECUTE_WRITECOPY - | winnt::PAGE_READWRITE - | winnt::PAGE_WRITECOPY; - -dbg!(process - .memory_regions() - .into_iter() - .filter(|p| (p.Protect &amp; mask) != 0) - .map(|p| p.RegionSize) - .sum::&lt;usize&gt;()); -</code></pre> -<p>Each memory protection level has its own bit, so we can OR them all together to have a single mask. When ANDing this mask with the protection level, if any bit is set, it will be non-zero, meaning we want to keep this region.</p> -<p>Don't ask me why there isn't a specific bit for &quot;write&quot;, &quot;read&quot;, &quot;execute&quot;, and there are only bits for combinations. I guess this way Windows forbids certain combinations.</p> -<pre><code>2580480 -</code></pre> -<p>Hey, that's close to the value shown by the Task Manager! A handfull of megabytes is a lot more manageable than 4 entire gigabytes.</p> -<h2 id="actually-running-our-first-scan">Actually running our First Scan</h2> -<p>Okay, we have all the memory regions from which the program can read, write, or execute. Now we also can read the memory in these regions:</p> -<pre><code class="language-rust" data-lang="rust">let regions = process - .memory_regions() - .into_iter() - .filter(|p| (p.Protect &amp; mask) != 0) - .collect::&lt;Vec&lt;_&gt;&gt;(); - -println!(&quot;Scanning {} memory regions&quot;, regions.len()); - -regions.into_iter().for_each(|region| { - match process.read_memory(region.BaseAddress as _, region.RegionSize) { - Ok(memory) =&gt; todo!(), - Err(err) =&gt; eprintln!( - &quot;Failed to read {} bytes at {:?}: {}&quot;, - region.RegionSize, region.BaseAddress, err, - ), - } -}) -</code></pre> -<p>All that's left is for us to scan for a target value. To do this, we want to iterate over all the <a href="https://doc.rust-lang.org/stable/std/primitive.slice.html#method.windows"><code>slice::windows</code></a> of size equal to the size of our scan type.</p> -<pre><code class="language-rust" data-lang="rust">let target: i32 = ...; -let target = target.to_ne_bytes(); - -// -snip- - -// inside the Ok match, replacing the todo!() -- this is where the first scan happens -Ok(memory) =&gt; memory - .windows(target.len()) - .enumerate() - .for_each(|(offset, window)| { - if window == target { - println!( - &quot;Found exact value at [{:?}+{:x}]&quot;, - region.BaseAddress, offset - ); - } - }) -</code></pre> -<p>We convert the 32-bit exact target value to its memory representation as a byte array in <a href="https://doc.rust-lang.org/stable/std/primitive.i32.html#method.to_ne_bytes">native byte order</a>. This way we can compare the target bytes with the window bytes. Another option is to interpret the window bytes as an <code>i32</code> with <code>from_be_bytes</code>, but <code>slice::windows</code> gives us slices of type <code>&amp;[u8]</code>, and <code>from_be_bytes</code> wants an <code>[u8; 4]</code> array, so it's a bit more annoying to convert.</p> -<p>This is enough to find the value in the process' memory!</p> -<pre><code>Found exact value at [0x10000+aec] -Failed to read 12288 bytes at 0x13f8000: Only part of a ReadProcessMemory or WriteProcessMemory request was completed. (os error 299) -Found exact value at [0x14f0000+3188] -Found exact value at [0x14f0000+ac74] -... -Found exact value at [0x10030e000+1816] -Found exact value at [0x7ff8f7b93000+441a] -... -Found exact value at [0x7ff8fb381000+4023] -</code></pre> -<p>The tutorial starts out with health &quot;100&quot;, which is what I scanned. Apparently, there are nearly a hundred of <code>100</code>-valued integers stored in the memory of the tutorial.</p> -<p>Attentive readers will notice that some values are located at an offset modulo 4. In Cheat Engine, this is known as &quot;Fast Scan&quot;, which is enabled by default with an alignment of 4. Most of the time, values are aligned in memory, and this alignment often corresponds with the size of the type itself. For 4-byte integers, it's common that they're 4-byte aligned.</p> -<p>We can perform a fast scan ourselves with <a href="https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.step_by"><code>step_by</code></a><sup class="footnote-reference"><a href="#5">5</a></sup>:</p> -<pre><code class="language-rust" data-lang="rust">memory - .windows(target.len()) - .enumerate() - .step_by(4) - .for_each(...) -</code></pre> -<p>As a bonus, over half the addresses are gone, so we have less results to worry about<sup class="footnote-reference"><a href="#6">6</a></sup>.</p> -<h2 id="next-scan">Next Scan</h2> -<p>The first scan gave us way too many results. We have no way to tell which is the correct one, as they all have the same value. What we need to do is a <em>second</em> scan at the <em>locations we just found</em>. This way, we can get a second reading, and compare it against a new value. If it's the same, we're on good track, and if not, we can discard a location. Repeating this process lets us cut the hundreds of potential addresses to just a handful of them.</p> -<p>For example, let's say we're scanning our current health of <code>100</code> in a game. This gives us over a hundred addresses that point to the value of <code>100</code>. If we go in-game and get hit<sup class="footnote-reference"><a href="#7">7</a></sup> by some enemy and get our health down to, say, <code>99</code> (we have a lot of defense), we can then read the memory at the hundred memory locations we found before. If this second reading is not <code>99</code>, we know the address does not actually point to our health pool and it just happened to also contain a <code>100</code> on the first scan. This address can be removed from the list of potential addresses pointing to our health.</p> -<p>Let's do that:</p> -<pre><code class="language-rust" data-lang="rust">// new vector to hold the locations, before getting into `memory.windows`' for-each -let mut locations = Vec::with_capacity(regions.len()); - -// -snip- - -// updating the `println!(&quot;Found exact value...&quot;)` to store the location instead. -if window == target { - locations.push(region.BaseAddress as usize + offset); -} - -// -snip- - -// performing a second scan on the locations the first scan found. -let target: i32 = ...; -let target = target.to_ne_bytes(); -locations.retain(|addr| match process.read_memory(*addr, target.len()) { - Ok(memory) =&gt; memory == target, - Err(_) =&gt; false, -}); - -println!(&quot;Now have {} locations&quot;, locations.len()); -</code></pre> -<p>We create a vector to store all the locations the first scan finds, and then retain those that match a second target value. You may have noticed that we perform a memory read, and thus a call to the Windows API, for every single address. With a hundred locations to read from, this is not a big deal, but oftentimes you will have tens of thousands of addresses. For the time being, we will not worry about this inefficiency, but we will get back to it once it matters:</p> -<pre><code>Scanning 98 memory regions -Which exact value to scan for?: 100 -Failed to read 12288 bytes at 0x13f8000: Only part of a ReadProcessMemory or WriteProcessMemory request was completed. (os error 299) -... -Found 49 locations -Which exact value to scan for next?: 99 -Now have 1 locations -</code></pre> -<p>Sweet! In a real-world scenario, you will likely need to perform these additional scans a couple of times, and even then, there may be more than one value left no matter what.</p> -<p>For good measure, we'll wrap our <code>retain</code> in a <code>while</code> loop<sup class="footnote-reference"><a href="#8">8</a></sup>:</p> -<pre><code class="language-rust" data-lang="rust">while locations.len() != 1 { - let target: i32 = ...; - let target = target.to_ne_bytes(); - locations.retain(...); -} -</code></pre> -<h2 id="modifying-memory">Modifying memory</h2> -<p>Now that we have very likely locations pointing to our current health in memory, all that's left is writing our new desired value to gain infinite health<sup class="footnote-reference"><a href="#9">9</a></sup>. Much like how we're able to read memory with <code>ReadProcessMemory</code>, we can write to it with <a href="https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory"><code>WriteProcessMemory</code></a>. Its usage is straightforward:</p> -<pre><code class="language-rust" data-lang="rust">pub fn write_memory(&amp;self, addr: usize, value: &amp;[u8]) -&gt; io::Result&lt;usize&gt; { - let mut written = 0; - - // SAFETY: the input value buffer points to valid memory. - if unsafe { - winapi::um::memoryapi::WriteProcessMemory( - self.handle.as_ptr(), - addr as *mut _, - value.as_ptr().cast(), - value.len(), - &amp;mut written, - ) - } == FALSE - { - Err(io::Error::last_os_error()) - } else { - Ok(written) - } -} -</code></pre> -<p>Similar to how writing to a file can return short, writing to a memory location could also return short. Here we mimic the API for writing files and return the number of bytes written. The documentation indicates that we could actually ignore the amount written by passing <code>ptr::null_mut()</code> as the last parameter, but it does no harm to retrieve the written count as well.</p> -<pre><code class="language-rust" data-lang="rust">let new_value: i32 = ...; -locations - .into_iter() - .for_each(|addr| match process.write_memory(addr, &amp;new_value) { - Ok(n) =&gt; eprintln!(&quot;Written {} bytes to [{:x}]&quot;, n, addr), - Err(e) =&gt; eprintln!(&quot;Failed to write to [{:x}]: {}&quot;, addr, e), - }); -</code></pre> -<p>And just like that:</p> -<pre><code>Now have 1 location(s) -Enter new memory value: 1000 -Failed to write to [15d8b90]: Access is denied. (os error 5) -</code></pre> -<p>…oh noes. Oh yeah. The documentation, which I totally didn't forget to read, mentions:</p> -<blockquote> -<p>The handle must have <code>PROCESS_VM_WRITE</code> and <code>PROCESS_VM_OPERATION</code> access to the process.</p> -</blockquote> -<p>We currently open our process with <code>PROCESS_QUERY_INFORMATION</code> and <code>PROCESS_VM_READ</code>, which is enough for reading, but not for writing. Let's adjust <code>OpenProcess</code> to accomodate for our new requirements:</p> -<pre><code class="language-rust" data-lang="rust">winapi::um::processthreadsapi::OpenProcess( - winnt::PROCESS_QUERY_INFORMATION - | winnt::PROCESS_VM_READ - | winnt::PROCESS_VM_WRITE - | winnt::PROCESS_VM_OPERATION, - FALSE, - pid, -) -</code></pre> -<p>Behold:</p> -<pre><code>Now have 1 location(s) -Enter new memory value: 1000 -Written 4 bytes to [15d8b90] -</code></pre> -<p><img src="https://user-images.githubusercontent.com/6297805/107829541-3f4f2d00-6d8a-11eb-87c4-e2f2d505afbc.png" alt="Tutorial complete with memo" /></p> -<p>Isn't that active <em>Next</em> button just beautiful?</p> -<h2 id="finale">Finale</h2> -<p>This post somehow ended up being longer than part one, but look at what we've achieved! We completed a step of the Cheat Engine Tutorial <em>without using Cheat Engine</em>. Just pure Rust. Figuring out how a program works and reimplementing it yourself is a great way to learn what it's doing behind the scenes. And now that this code is yours, you can extend it as much as you like, without being constrained by Cheat Engine's UI. You can automate it as much as you want.</p> -<p>And we're not even done. The current tutorial has nine steps, and three additional graphical levels.</p> -<p>In the <a href="/blog/woce-3">next post</a>, we'll tackle the third step of the tutorial: Unknown initial value. This will pose a challenge, because with just 2 MiB of memory, storing all the 4-byte aligned locations would require 524288 addresses (<code>usize</code>, 8 bytes). This adds up to twice as much memory as the original program (4 MiB), but that's not our main concern, having to perform over five hundred thousand API calls is!</p> -<p>Remember that you can <a href="https://github.com/lonami/memo">obtain the code for this post</a> over at my GitHub. You can run <code>git checkout step2</code> after cloning the repository to get the right version of the code.</p> -<h3 id="footnotes">Footnotes</h3> -<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup> -<p>I did in fact use an online tool to spell it out for me.</p> -</div> -<div class="footnote-definition" id="2"><sup class="footnote-definition-label">2</sup> -<p>16 GiB is good enough for my needs. I don't think I'll ever upgrade to 16 EiB.</p> -</div> -<div class="footnote-definition" id="3"><sup class="footnote-definition-label">3</sup> -<p>Every address we query should have a corresponding region, even if it's not allocated or we do not have access. This is why we can query for the memory address zero to get its corresponding region.</p> -</div> -<div class="footnote-definition" id="4"><sup class="footnote-definition-label">4</sup> -<p>Another option is to <a href="https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsysteminfo"><code>GetSystemInfo</code></a> to determine the <code>lpMinimumApplicationAddress</code> and <code>lpMaximumApplicationAddress</code> and only work within bounds.</p> -</div> -<div class="footnote-definition" id="5"><sup class="footnote-definition-label">5</sup> -<p>Memory regions are page-aligned, which is a large power of two. Our alignment of 4 is much lower than this, so we're guaranteed to start off at an aligned address.</p> -</div> -<div class="footnote-definition" id="6"><sup class="footnote-definition-label">6</sup> -<p>If it turns out that the value was actually misaligned, we will miss it. You will notice this if, after going through the whole process, there are no results. It could mean that either the value type is wrong, or the value type is misaligned. In the worst case, the value is not stored directly but is rather computed with something like <code>maximum - stored</code>, or XORed with some magic value, or a myriad other things.</p> -</div> -<div class="footnote-definition" id="7"><sup class="footnote-definition-label">7</sup> -<p>You could do this without getting hit, and just keep on repeating the scan for the same value over and over again. This does work, but the results are suboptimal, because there are also many other values that didn't change. Scanning for a changed value is a better option.</p> -</div> -<div class="footnote-definition" id="8"><sup class="footnote-definition-label">8</sup> -<p>You could actually just go ahead and try to modify the memory at the hundred addresses you just found, although don't be surprised if the program starts to misbehave!</p> -</div> -<div class="footnote-definition" id="9"><sup class="footnote-definition-label">9</sup> -<p>Okay, we cannot fit infinity in an <code>i32</code>. However, we can fit sufficiently large numbers. Like <code>1000</code>, which is enough to complete the tutorial.</p> -</div> -</content> - </entry> - <entry xml:lang="en"> - <title>Writing our own Cheat Engine: Introduction</title> - <published>2021-02-07T00:00:00+00:00</published> - <updated>2021-02-19T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/woce-1/" type="text/html"/> - <id>https://lonami.dev/blog/woce-1/</id> - <content type="html"><p>This is part 1 on the <em>Writing our own Cheat Engine</em> series:</p> -<ul> -<li>Part 1: Introduction</li> -<li><a href="/blog/woce-2">Part 2: Exact Value scanning</a></li> -<li><a href="/blog/woce-3">Part 3: Unknown initial value</a></li> -<li><a href="/blog/woce-4">Part 4: Floating points</a></li> -<li><a href="/blog/woce-5">Part 5: Code finder</a></li> -<li><a href="/blog/woce-6">Part 6: Pointers</a></li> -</ul> -<p><a href="https://cheatengine.org/">Cheat Engine</a> is a tool designed to modify single player games and contains other useful tools within itself that enable its users to debug games or other applications. It comes with a memory scanner, (dis)assembler, inspection tools and a handful other things. In this series, we will be writing our own tiny Cheat Engine capable of solving all steps of the tutorial, and diving into how it all works underneath.</p> -<p>Needless to say, we're doing this for private and educational purposes only. One has to make sure to not violate the EULA or ToS of the specific application we're attaching to. This series, much like cheatengine.org, does not condone the illegal use of the code shared.</p> -<p>Cheat Engine is a tool for Windows, so we will be developing for Windows as well. However, you can also <a href="https://stackoverflow.com/q/12977179/4759433">read memory from Linux-like systems</a>. <a href="https://github.com/scanmem/scanmem">GameConqueror</a> is a popular alternative to Cheat Engine on Linux systems, so if you feel adventurous, you could definitely follow along too! The techniques shown in this series apply regardless of how we read memory from a process. You will learn a fair bit about doing FFI in Rust too.</p> -<p>We will be developing the application in Rust, because it enables us to interface with the Windows API easily, is memory safe (as long as we're careful with <code>unsafe</code>!), and is speedy (we will need this for later steps in the Cheat Engine tutorial). You could use any language of your choice though. For example, <a href="https://lonami.dev/blog/ctypes-and-windows/">Python also makes it relatively easy to use the Windows API</a>. You don't need to be a Rust expert to follow along, but this series assumes some familiarity with C-family languages. Slightly advanced concepts like the use of <code>unsafe</code> or the <code>MaybeUninit</code> type will be briefly explained. What a <code>fn</code> is or what <code>let</code> does will not be explained.</p> -<p><a href="https://github.com/cheat-engine/cheat-engine/">Cheat Engine's source code</a> is mostly written in Pascal and C. And it's <em>a lot</em> of code, with a very flat project structure, and files ranging in the thousand lines of code each. It's daunting<sup class="footnote-reference"><a href="#1">1</a></sup>. It's a mature project, with a lot of knowledge encoded in the code base, and a lot of features like distributed scanning or an entire disassembler. Unfortunately, there's not a lot of comments. For these reasons, I'll do some guesswork when possible as to how it's working underneath, rather than actually digging into what Cheat Engine is actually doing.</p> -<p>With that out of the way, let's get started!</p> -<h2 id="welcome-to-the-cheat-engine-tutorial">Welcome to the Cheat Engine Tutorial</h2> -<details open><summary>Cheat Engine Tutorial: Step 1</summary> -<blockquote> -<p>This tutorial will teach you the basics of cheating in video games. It will also show you foundational aspects of using Cheat Engine (or CE for short). Follow the steps below to get started.</p> -<ol> -<li>Open Cheat Engine if it currently isn't running.</li> -<li>Click on the &quot;Open Process&quot; icon (it's the top-left icon with the computer on it, below &quot;File&quot;.).</li> -<li>With the Process List window now open, look for this tutorial's process in the list. It will look something like &gt; &quot;00001F98-Tutorial-x86_64.exe&quot; or &quot;0000047C-Tutorial-i386.exe&quot;. (The first 8 numbers/letters will probably be different.)</li> -<li>Once you've found the process, click on it to select it, then click the &quot;Open&quot; button. (Don't worry about all the &gt; other buttons right now. You can learn about them later if you're interested.)</li> -</ol> -<p>Congratulations! If you did everything correctly, the process window should be gone with Cheat Engine now attached to the &gt; tutorial (you will see the process name towards the top-center of CE).</p> -<p>Click the &quot;Next&quot; button below to continue, or fill in the password and click the &quot;OK&quot; button to proceed to that step.)</p> -<p>If you're having problems, simply head over to forum.cheatengine.org, then click on &quot;Tutorials&quot; to view beginner-friendly &gt; guides!</p> -</blockquote> -</details> -<h2 id="enumerating-processes">Enumerating processes</h2> -<p>Our first step is attaching to the process we want to work with. But we need a way to find that process in the first place! Having to open the task manager, look for the process we care about, noting down the process ID (PID), and slapping it in the source code is not satisfying at all. Instead, let's enumerate all the processes from within the program, and let the user select one by typing its name.</p> -<p>From a quick <a href="https://ddg.gg/winapi%20enumerate%20all%20processes">DuckDuckGo search</a>, we find an official tutorial for <a href="https://docs.microsoft.com/en-us/windows/win32/psapi/enumerating-all-processes">Enumerating All Processes</a>, which leads to the <a href="https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocesses"><code>EnumProcesses</code></a> call. Cool! Let's slap in the <a href="https://crates.io/crates/winapi"><code>winapi</code></a> crate on <code>Cargo.toml</code>, because I don't want to write all the definitions by myself:</p> -<pre><code class="language-toml" data-lang="toml">[dependencies] -winapi = { version = &quot;0.3.9&quot;, features = [&quot;psapi&quot;] } -</code></pre> -<p>Because <a href="https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocesses"><code>EnumProcesses</code></a> is in <code>Psapi.h</code> (you can see this in the online page of its documentation), we know we'll need the <code>psapi</code> crate feature. Another option is to search for it in the <a href="https://docs.rs/winapi/"><code>winapi</code> documentation</a> and noting down the parent module where its stored.</p> -<p>The documentation for the method has the following remark:</p> -<blockquote> -<p>It is a good idea to use a large array, because it is hard to predict how many processes there will be at the time you call <strong>EnumProcesses</strong>.</p> -</blockquote> -<p><em>Sidenote: reading the documentation for the methods we'll use from the Windows API is extremely important. There's a lot of gotchas involved, so we need to make sure we're extra careful.</em></p> -<p>1024 is a pretty big number, so let's go with that:</p> -<pre><code class="language-rust" data-lang="rust">use std::io; -use std::mem; -use winapi::shared::minwindef::{DWORD, FALSE}; - -pub fn enum_proc() -&gt; io::Result&lt;Vec&lt;u32&gt;&gt; { - let mut pids = Vec::&lt;DWORD&gt;::with_capacity(1024); - let mut size = 0; - // SAFETY: the pointer is valid and the size matches the capacity. - if unsafe { - winapi::um::psapi::EnumProcesses( - pids.as_mut_ptr(), - (pids.capacity() * mem::size_of::&lt;DWORD&gt;()) as u32, - &amp;mut size, - ) - } == FALSE - { - return Err(io::Error::last_os_error()); - } - - todo!() -} -</code></pre> -<p>We allocate enough space<sup class="footnote-reference"><a href="#2">2</a></sup> for 1024 <code>pids</code> in a vector<sup class="footnote-reference"><a href="#3">3</a></sup>, and pass a mutable pointer to the contents to <code>EnumProcesses</code>. Note that the size of the array is in <em>bytes</em>, not items, so we need to multiply the capacity by the size of <code>DWORD</code>. The API likes to use <code>u32</code> for sizes, unlike Rust which uses <code>usize</code>, so we need a cast.</p> -<p>Last, we need another mutable variable where the amount of bytes written is stored, <code>size</code>.</p> -<blockquote> -<p>If the function fails, the return value is zero. To get extended error information, call <a href="https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror"><code>GetLastError</code></a>.</p> -</blockquote> -<p>That's precisely what we do. If it returns false (zero), we return the last OS error. Rust provides us with <a href="https://doc.rust-lang.org/stable/std/io/struct.Error.html#method.last_os_error"><code>std::io::Error::last_os_error</code></a>, which essentially makes that same call but returns a proper <code>io::Error</code> instance. Cool!</p> -<blockquote> -<p>To determine how many processes were enumerated, divide the <em>lpcbNeeded</em> value by <code>sizeof(DWORD)</code>.</p> -</blockquote> -<p>Easy enough:</p> -<pre><code class="language-rust" data-lang="rust">let count = size as usize / mem::size_of::&lt;DWORD&gt;(); -// SAFETY: the call succeeded and count equals the right amount of items. -unsafe { pids.set_len(count) }; -Ok(pids) -</code></pre> -<p>Rust doesn't know that the memory for <code>count</code> items were initialized by the call, but we do, so we make use of the <a href="https://doc.rust-lang.org/stable/std/vec/struct.Vec.html#method.set_len"><code>Vec::set_len</code></a> call to indicate this. The Rust documentation even includes a FFI similar to our code!</p> -<p>Let's give it a ride:</p> -<pre><code class="language-rust" data-lang="rust">fn main() { - dbg!(enum_proc().unwrap().len()); -} -</code></pre> -<pre><code>&gt;cargo run - Compiling memo v0.1.0 - Finished dev [unoptimized + debuginfo] target(s) in 0.20s - Running `target\debug\memo.exe` -[src\main.rs:27] enum_proc().unwrap().len() = 178 -</code></pre> -<p>It works! But currently we only have a bunch of process identifiers, with no way of knowing which process they refer to.</p> -<blockquote> -<p>To obtain process handles for the processes whose identifiers you have just obtained, call the <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess"><code>OpenProcess</code></a> function.</p> -</blockquote> -<p>Oh!</p> -<h2 id="opening-a-process">Opening a process</h2> -<p>The documentation for <code>OpenProcess</code> also contains the following:</p> -<blockquote> -<p>When you are finished with the handle, be sure to close it using the <a href="https://lonami.dev/blog/woce-1/closehandle"><code>CloseHandle</code></a> function.</p> -</blockquote> -<p>This sounds to me like the perfect time to introduce a custom <code>struct Process</code> with an <code>impl Drop</code>! We're using <code>Drop</code> to cleanup resources, not behaviour, so it's fine. <a href="https://internals.rust-lang.org/t/pre-rfc-leave-auto-trait-for-reliable-destruction/13825">Using <code>Drop</code> to cleanup behaviour is a bad idea</a>. But anyway, let's get back to the code:</p> -<pre><code class="language-rust" data-lang="rust">use std::ptr::NonNull; -use winapi::ctypes::c_void; - -pub struct Process { - pid: u32, - handle: NonNull&lt;c_void&gt;, -} - -impl Process { - pub fn open(pid: u32) -&gt; io::Result&lt;Self&gt; { - todo!() - } -} - -impl Drop for Process { - fn drop(&amp;mut self) { - todo!() - } -} -</code></pre> -<p>For <code>open</code>, we'll want to use <code>OpenProcess</code> (and we also need to add the <code>processthreadsapi</code> feature to the <code>winapi</code> dependency in <code>Cargo.toml</code>). It returns a <code>HANDLE</code>, which is a nullable mutable pointer to <code>c_void</code>. If it's null, the call failed, and if it's non-null, it succeeded and we have a valid handle. This is why we use Rust's <a href="https://doc.rust-lang.org/stable/std/ptr/struct.NonNull.html"><code>NonNull</code></a>:</p> -<pre><code class="language-rust" data-lang="rust">// SAFETY: the call doesn't have dangerous side-effects. -NonNull::new(unsafe { winapi::um::processthreadsapi::OpenProcess(0, FALSE, pid) }) - .map(|handle| Self { pid, handle }) - .ok_or_else(io::Error::last_os_error) -</code></pre> -<p><code>NonNull</code> will return <code>Some</code> if the pointer is non-null. We map the non-null pointer to a <code>Process</code> instance with <code>Self { .. }</code>. <code>ok_or_else</code> converts the <code>Option</code> to a <code>Result</code> with the error builder function we provide if it was <code>None</code>.</p> -<p>The first parameter is a bitflag of permissions we want to have. For now, we can leave it as zero (all bits unset, no specific permissions granted). The second one is whether we want to inherit the handle, which we don't, and the third one is the process identifier. Let's close the resource handle on <code>Drop</code> (after adding <code>handleapi</code> to the crate features):</p> -<pre><code class="language-rust" data-lang="rust">// SAFETY: the handle is valid and non-null. -unsafe { winapi::um::handleapi::CloseHandle(self.handle.as_mut()) }; -</code></pre> -<p><code>CloseHandle</code> can actually fail (for example, on double-close), but given our invariants, it won't. You could add an <code>assert!</code> to panic if this is not the case.</p> -<p>We can now open processes, and they will be automatically closed on <code>Drop</code>. Does any of this work though?</p> -<pre><code class="language-rust" data-lang="rust">fn main() { - let mut success = 0; - let mut failed = 0; - enum_proc().unwrap().into_iter().for_each(|pid| match Process::open(pid) { - Ok(_) =&gt; success += 1, - Err(_) =&gt; failed += 1, - }); - - eprintln!(&quot;Successfully opened {}/{} processes&quot;, success, success + failed); -} -</code></pre> -<pre><code>&gt;cargo run - Compiling memo v0.1.0 - Finished dev [unoptimized + debuginfo] target(s) in 0.36s - Running `target\debug\memo.exe` -Successfully opened 0/191 processes -</code></pre> -<p>…nope. Maybe the documentation for <code>OpenProcess</code> says something?</p> -<blockquote> -<p><code>dwDesiredAccess</code></p> -<p>The access to the process object. This access right is checked against the security descriptor for the process. This parameter can be <strong>one or more</strong> of the process access rights.</p> -</blockquote> -<p>One or more, but we're setting zero permissions. I told you, reading the documentation is important<sup class="footnote-reference"><a href="#4">4</a></sup>! The <a href="https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights">Process Security and Access Rights</a> page lists all possible values we could use. <code>PROCESS_QUERY_INFORMATION</code> seems to be appropriated:</p> -<blockquote> -<p>Required to retrieve certain information about a process, such as its token, exit code, and priority class</p> -</blockquote> -<pre><code class="language-rust" data-lang="rust">OpenProcess(winapi::um::winnt::PROCESS_QUERY_INFORMATION, ...) -</code></pre> -<p>Does this fix it?</p> -<pre><code class="language-rust" data-lang="rust">&gt;cargo run - Compiling memo v0.1.0 - Finished dev [unoptimized + debuginfo] target(s) in 0.36s - Running `target\debug\memo.exe` -Successfully opened 69/188 processes -</code></pre> -<p><em>Nice</em>. It does solve it. But why did we only open 69 processes out of 188? Does it help if we run our code as administrator? Let's search for <code>cmd</code> in the Windows menu and right click to Run as administrator, then <code>cd</code> into our project and try again:</p> -<pre><code>&gt;cargo run - Finished dev [unoptimized + debuginfo] target(s) in 0.01s - Running `target\debug\memo.exe` -Successfully opened 77/190 processes -</code></pre> -<p>We're able to open a few more, so it does help. In general, we'll want to run as administrator, so normal programs can't sniff on what we're doing, and so that we have permission to do more things.</p> -<h2 id="getting-the-name-of-a-process">Getting the name of a process</h2> -<p>We're not done enumerating things just yet. To get the &quot;name&quot; of a process, we need to enumerate the modules that it has loaded, and only then can we get the module base name. The first module is the program itself, so we don't need to enumerate <em>all</em> modules, just the one is enough.</p> -<p>For this we want <a href="https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocessmodules"><code>EnumProcessModules</code></a> and <a href="https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmodulebasenamea"><code>GetModuleBaseNameA</code></a>. I'm using the ASCII variant of <code>GetModuleBaseName</code> because I'm too lazy to deal with UTF-16 of the <code>W</code> (wide, unicode) variants.</p> -<pre><code class="language-rust" data-lang="rust">use std::mem::MaybeUninit; -use winapi::shared::minwindef::HMODULE; - -pub fn name(&amp;self) -&gt; io::Result&lt;String&gt; { - let mut module = MaybeUninit::&lt;HMODULE&gt;::uninit(); - let mut size = 0; - // SAFETY: the pointer is valid and the size is correct. - if unsafe { - winapi::um::psapi::EnumProcessModules( - self.handle.as_ptr(), - module.as_mut_ptr(), - mem::size_of::&lt;HMODULE&gt;() as u32, - &amp;mut size, - ) - } == FALSE - { - return Err(io::Error::last_os_error()); - } - - // SAFETY: the call succeeded, so module is initialized. - let module = unsafe { module.assume_init() }; - todo!() -} -</code></pre> -<p><code>EnumProcessModules</code> takes a pointer to an array of <code>HMODULE</code>. We could use a <code>Vec</code> of capacity one to hold the single module, but in memory, a pointer a single item can be seen as a pointer to an array of items. <code>MaybeUninit</code> helps us reserve enough memory for the one item we need.</p> -<p>With the module handle, we can retrieve its base name:</p> -<pre><code class="language-rust" data-lang="rust">let mut buffer = Vec::&lt;u8&gt;::with_capacity(64); -// SAFETY: the handle, module and buffer are all valid. -let length = unsafe { - winapi::um::psapi::GetModuleBaseNameA( - self.handle.as_ptr(), - module, - buffer.as_mut_ptr().cast(), - buffer.capacity() as u32, - ) -}; -if length == 0 { - return Err(io::Error::last_os_error()); -} - -// SAFETY: the call succeeded and length represents bytes. -unsafe { buffer.set_len(length as usize) }; -Ok(String::from_utf8(buffer).unwrap()) -</code></pre> -<p>Similar to how we did with <code>EnumProcesses</code>, we create a buffer that will hold the ASCII string of the module's base name<sup class="footnote-reference"><a href="#5">5</a></sup>. The call wants us to pass a pointer to a mutable buffer of <code>i8</code>, but Rust's <code>String::from_utf8</code> wants a <code>Vec&lt;u8&gt;</code>, so instead we declare a buffer of <code>u8</code> and <code>.cast()</code> the pointer in the call. You could also do this with <code>as _</code>, and Rust would infer the right type, but <code>cast</code> is neat.</p> -<p>We <code>unwrap</code> the creation of the UTF-8 string because the buffer should contain only ASCII characters (which are also valid UTF-8). We could use the <code>unsafe</code> variant to create the string, but what if somehow it contains non-ASCII characters? The less <code>unsafe</code>, the better.</p> -<p>Let's see it in action:</p> -<pre><code class="language-rust" data-lang="rust">fn main() { - enum_proc() - .unwrap() - .into_iter() - .for_each(|pid| match Process::open(pid) { - Ok(proc) =&gt; match proc.name() { - Ok(name) =&gt; println!(&quot;{}: {}&quot;, pid, name), - Err(e) =&gt; println!(&quot;{}: (failed to get name: {})&quot;, pid, e), - }, - Err(e) =&gt; eprintln!(&quot;failed to open {}: {}&quot;, pid, e), - }); -} -</code></pre> -<pre><code>&gt;cargo run - Compiling memo v0.1.0 - Finished dev [unoptimized + debuginfo] target(s) in 0.32s - Running `target\debug\memo.exe` -failed to open 0: The parameter is incorrect. (os error 87) -failed to open 4: Access is denied. (os error 5) -... -failed to open 5940: Access is denied. (os error 5) -5608: (failed to get name: Access is denied. (os error 5)) -... -1704: (failed to get name: Access is denied. (os error 5)) -failed to open 868: Access is denied. (os error 5) -... -</code></pre> -<p>That's not good. What's up with that? Maybe…</p> -<blockquote> -<p>The handle must have the <code>PROCESS_QUERY_INFORMATION</code> and <code>PROCESS_VM_READ</code> access rights.</p> -</blockquote> -<p>…I should've read the documentation. Okay, fine:</p> -<pre><code class="language-rust" data-lang="rust">use winapi::um::winnt; -OpenProcess(winnt::PROCESS_QUERY_INFORMATION | winnt::PROCESS_VM_READ, ...) -</code></pre> -<pre><code>&gt;cargo run - Compiling memo v0.1.0 (C:\Users\L\Desktop\memo) - Finished dev [unoptimized + debuginfo] target(s) in 0.35s - Running `target\debug\memo.exe` -failed to open 0: The parameter is incorrect. (os error 87) -failed to open 4: Access is denied. (os error 5) -... -9348: cheatengine-x86_64.exe -3288: Tutorial-x86_64.exe -8396: cmd.exe -4620: firefox.exe -7964: cargo.exe -10052: cargo.exe -5756: memo.exe -</code></pre> -<p>Hooray 🎉! There's some processes we can't open, but that's because they're system processes. Security works!</p> -<h2 id="finale">Finale</h2> -<p>That was a fairly long post when all we did was print a bunch of pids and their corresponding name. But in all fairness, we also laid out a good foundation for what's coming next.</p> -<p>You can <a href="https://github.com/lonami/memo">obtain the code for this post</a> over at my GitHub. At the end of every post, the last commit will be tagged, so you can <code>git checkout step1</code> to see the final code for any blog post.</p> -<p>In the <a href="/blog/woce-2">next post</a>, we'll tackle the second step of the tutorial: Exact Value scanning.</p> -<h3 id="footnotes">Footnotes</h3> -<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup> -<p>You could say I simply love reinventing the wheel, which I do, but in this case, the codebase contains <em>far</em> more features than we're interested in. The (apparent) lack of structure and documentation regarding the code, along with the unfortunate <a href="https://github.com/cheat-engine/cheat-engine/issues/60">lack of license</a> for the source code, make it a no-go. There's a license, but I think that's for the distributed program itself.</p> -</div> -<div class="footnote-definition" id="2"><sup class="footnote-definition-label">2</sup> -<p>If it turns out that there are more than 1024 processes, our code will be unaware of those extra processes. The documentation suggests to perform the call again with a larger buffer if <code>count == provided capacity</code>, but given I have under 200 processes on my system, it seems unlikely we'll reach this limit. If you're worried about hitting this limit, simply use a larger limit or retry with a larger vector.</p> -</div> -<div class="footnote-definition" id="3"><sup class="footnote-definition-label">3</sup> -<p>C code would likely use <a href="https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globalalloc"><code>GlobalAlloc</code></a> here, but Rust's <code>Vec</code> handles the allocation for us, making the code both simpler and more idiomatic. In general, if you see calls to <code>GlobalAlloc</code> when porting some code to Rust, you can probably replace it with a <code>Vec</code>.</p> -</div> -<div class="footnote-definition" id="4"><sup class="footnote-definition-label">4</sup> -<p>This will be a recurring theme.</p> -</div> -<div class="footnote-definition" id="5"><sup class="footnote-definition-label">5</sup> -<p>…and similar to <code>EnumProcesses</code>, if the name doesn't fit in our buffer, the result will be truncated.</p> -</div> -</content> - </entry> - <entry xml:lang="en"> - <title>Data Mining, Warehousing and Information Retrieval</title> - <published>2020-07-03T00:00:00+00:00</published> - <updated>2020-07-03T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/university/" type="text/html"/> - <id>https://lonami.dev/blog/university/</id> - <content type="html"><p>During university, there were a few subjects where I had to write blog posts for (either as evaluable tasks or just for fun). I thought it was really fun and I wanted to preserve that work here, with the hopes it's interesting to someone.</p> -<p>The posts series were auto-generated from the original HTML files and manually anonymized later.</p> -<ul> -<li><a href="/blog/mdad">Data Mining and Data Warehousing</a></li> -<li><a href="/blog/ribw">Information Retrieval and Web Search</a></li> -</ul> -</content> - </entry> - <entry xml:lang="en"> - <title>My new computer</title> - <published>2020-06-19T00:00:00+00:00</published> - <updated>2020-07-03T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/new-computer/" type="text/html"/> - <id>https://lonami.dev/blog/new-computer/</id> - <content type="html"><p>This post will be mostly me ranting about setting up a new laptop, but I also just want to share my upgrade. If you're considering installing Arch Linux with dual-boot for Windows, maybe this post will help. Or perhaps you will learn something new to troubleshoot systems in the future. Let's begin!</p> -<p>Last Sunday, I ordered a Asus Rog Strix G531GT-BQ165 for 900€ (on a 20% discount) with the following specifications:</p> -<ul> -<li>Intel® Core i7-9750H (6 cores, 12MB cache, 2.6GHz up to 4.5GHz, 64-bit)</li> -<li>16GB RAM (8GB*2) DDR4 2666MHz</li> -<li>512GB SSD M.2 PCIe® NVMe</li> -<li>Display 15.6&quot; (1920x1080/16:9) 60Hz</li> -<li>Graphics NVIDIA® GeForce® GTX1650 4GB GDDR5 VRAM</li> -<li>LAN 10/100/1000</li> -<li>Wi-Fi 5 (802.11ac) 2x2 RangeBoost</li> -<li>Bluetooth 5.0</li> -<li>48Wh battery with 3 cells</li> -<li>3 x USB 3.1 (GEN1)</li> -</ul> -<p>I was mostly interested in a general upgrade (better processor, disk, more RAM), although the graphics card is a really nice addition which will allow me to take some time off on more games. After using it for a bit, I really love the feel of the keyboard, and I love the lack of numpad! (No sarcasm, I really don't like numpads.)</p> -<p>This is an upgrade from my previous laptop (Asus X554LA-XX822T), which I won in a competition before entering university in a programming challenge. It has served me really well for the past five years, and had the following specifications:</p> -<ul> -<li>Intel® Core™ i5-5200U</li> -<li>4GB RAM DDR3L 1600MHz (which I upgraded to have 8GB)</li> -<li>1TB HDD</li> -<li>Display 15.6&quot; (1366x768/16:9)</li> -<li>Intel® HD Graphics 4400</li> -<li>LAN 10/100/1000</li> -<li>Wifi 802.11 bgn</li> -<li>Bluetooth 4.0</li> -<li>Battery 2 cells</li> -<li>1 x USB 2.0</li> -<li>2 x USB 3.0</li> -</ul> -<p>Prior to this one, I had a Lenovo (also won in the same competition of the previous year), and prior to that (just for the sake of history), it was HP Pavilion, AMD A4-3300M processor, which unfortunately ended with heating problems. But that's very old now.</p> -<h2 id="laptop-arrival">Laptop arrival</h2> -<p>The laptop arrived 2 days ago at roughly 19:00, which I put charged for 3 hours as the book said. The day after, nightmares began!</p> -<p>Trying to boot it the first two times was fun, as it comes with a somewhat loud sound on boot. I don't know why they would do this, and I immediately turned it off in the BIOS.</p> -<h2 id="installation-journey">Installation journey</h2> -<p>I spent all of yesterday trying to setup Windows and Arch Linux (and didn't even finish, it took me this morning too and even now it's only half functional). I absolutely <em>hate</em> the amount of partitions the Windows installer creates on a clean disk. So instead, I first went with Arch Linux, and followed the <a href="https://wiki.archlinux.org/index.php/Installation_guide">installation guide on the Arch wiki</a>. Pre-installation, setting up the wireless network, creating the partitions and formatting them went all good. I decided to avoid GRUB at first and go with rEFInd, but alas I missed a big warning on the wiki and after reboot (I would later find out) it was not mounting root properly, so all I had was whatever was in the Initramfs. Reboot didn't work, so I had to hold the power button.</p> -<p>Anyway, once the partitions were created, I went to install Windows (there was a lot of back and forth burning different <code>.iso</code> images on the USB, which was a bit annoying because it wasn't the fastest thing in the world). This was pretty painless, and the process was standard: select advanced to let me choose the right partition, pick the one, say &quot;no&quot; to everything in the services setup, and done. But this was the first Windows <code>.iso</code> I tried. It was an old revision, and the drivers were causing issues when running (something weird about their <code>.dll</code>, manually installing the <code>.ini</code> driver files seemed to work?). The Nvidia drivers didn't want to be installed on such an old revision, after updating everything I could via Windows updates. So back I went to burning a newer Windows <code>.iso</code> and going through the same process again…</p> -<p>Once Windows was ready and I verified that I could boot to it correctly, it was time to have a second go at Arch Linux. And I went through the setup at least three times, getting it wrong every single time, formatting root every single time, redownloading the packages every single pain. If only had I known earlier what the issue was!</p> -<p>Why bother with Arch? I was pretty happy with Linux Mint, and I lowkey wanted to try NixOS, but I had used Arch before and it's a really nice distro overall (up-to-date, has AUR, quite minimal, imperative), except for trying to install rEFInd while chrooted…</p> -<p>In the end I managed to get something half-working, I still need to properly configure WiFi and pulseaudio in my system but hey it works.</p> -<p>I like to be able to dual-boot Windows and Linux because Linux is amazing for productivity, but unfortunately, some games only work fine on Windows. Might as well have both systems and use one for gaming, while the other is my daily driver.</p> -<h2 id="setting-up-arch-linux">Setting up Arch Linux</h2> -<p>This is the process I followed to install Arch Linux in the end, along with a brief explanation on what I think the things are doing and why we are doing them. I think the wiki could do a better job at this, but I also know it's hard to get it right for everyone. Something I do dislike is the link colour, after opening a link it becomes gray and it's a lot easier to miss the fact that it is a link in the first place, which was tough when re-reading it because some links actually matter a lot. Furthermore, important information may just be a single line, also easy to skim over. Anyway, on to the installation process…</p> -<p>The first thing we want to do is configure our keyboard layout or else the keys won't correspond to what we expect:</p> -<pre><code class="language-sh" data-lang="sh">loadkeys es -</code></pre> -<p>Because we're on a recent system, we want to verify that UEFI works correctly. If we see files listed, then it works fine:</p> -<pre><code class="language-sh" data-lang="sh">ls /sys/firmware/efi/efivars -</code></pre> -<p>The next thing we want to do is configure the WiFi, because I don't have any ethernet cable nearby. To do this, we check what network interfaces our laptop has (we're looking for the one prefixed with &quot;w&quot;, presumably for wireless, such as &quot;wlan0&quot; or &quot;wlo1&quot;), we set it up, scan for available wireless network, and finally connect. In my case, the network has WPA security so we rely on <code>wpa_supplicant</code> to connect, passing the SSID (network name) and password:</p> -<pre><code class="language-sh" data-lang="sh">ip link -ip link set &lt;IFACE&gt; up -iw dev &lt;IFACE&gt; scan | less -wpa_supplicant -B -i &lt;IFACE&gt; -c &lt;(wpa_passphrase &lt;SSID&gt; &lt;PASS&gt;) -</code></pre> -<p>After that's done, pinging an IP address like &quot;1.1.1.1&quot; should Just Work™, but to be able to resolve hostnames, we need to also setup a nameserver. I'm using Cloudflare's, but you could use any other:</p> -<pre><code class="language-sh" data-lang="sh">echo nameserver 1.1.1.1 &gt; /etc/resolv.conf -ping archlinux.org -^C -</code></pre> -<p>If the ping works, then network works! If you still have issues, you may need to <a href="https://wiki.archlinux.org/index.php/Network_configuration#Static_IP_address">manually configure a static IP address</a> and add a route with the address of your, well, router. This basically shows if we have any address, adds a static address (so people know who we are), shows what route we have, and adds a default one (so our packets know where to go):</p> -<pre><code class="language-sh" data-lang="sh">ip address show -ip address add &lt;YOUR ADDR&gt;/24 broadcast + dev &lt;IFACE&gt; -ip route show -ip route add default via &lt;ROUTER ADDR&gt; dev &lt;IFACE&gt; -</code></pre> -<p>Now that we have network available, we can enable NTP to synchronize our system time (this may be required for network operations where certificates have a validity period, not sure; in any case nobody wants a wrong system time):</p> -<pre><code class="language-sh" data-lang="sh">timedatectl set-ntp true -</code></pre> -<p>After that, we can manage our disk and partitions using <code>fdisk</code>. We want to define partitions to tell the system where it should live. To determine the disk name, we first list them, and then edit it. <code>fdisk</code> is really nice and reminds you at every step that help can be accessed with &quot;m&quot;, which you should constantly use to guide you through.</p> -<pre><code class="language-sh" data-lang="sh">fdisk -l -fdisk /dev/&lt;DISK&gt; -</code></pre> -<p>The partitions I made are the following:</p> -<ul> -<li>A 100MB one for the EFI system.</li> -<li>A 32GB one for Linux' root <code>/</code> partition.</li> -<li>A 200GB one for Linux' home <code>/home</code> partition.</li> -<li>The rest was unallocated for Windows because I did this first.</li> -</ul> -<p>I like to have <code>/home</code> and <code>/</code> separate because I can reinstall root without losing anything from home (projects, music, photos, screenshots, videos…).</p> -<p>After the partitions are made, we format them in FAT32 and EXT4 which are good defaults for EFI, root and home. They need to have a format, or else they won't be usable:</p> -<pre><code class="language-sh" data-lang="sh">mkfs.fat -F32 /dev/&lt;DISK&gt;&lt;PART1&gt; -mkfs.ext4 /dev/&lt;DISK&gt;&lt;PART2&gt; -mkfs.ext4 /dev/&lt;DISK&gt;&lt;PART3&gt; -</code></pre> -<p>Because the laptop was new, there was no risk to lose anything, but if you're doing a install on a previous system, be very careful with the partition names. Make sure they match with the ones in <code>fdisk -l</code>.</p> -<p>Now that we have usable partitions, we need to mount them or they won't be accessible. We can do this with <code>mount</code>:</p> -<pre><code class="language-sh" data-lang="sh">mount /dev/&lt;DISK&gt;&lt;PART2&gt; /mnt -mkdir /mnt/efi -mount /dev/&lt;DISK&gt;&lt;PART1&gt; /mnt/efi -mkdir /mnt/home -mount /dev/&lt;DISK&gt;&lt;PART3&gt; /mnt/home -</code></pre> -<p>Remember to use the correct partitions while mounting. We mount everything so that the system knows which partitions we care about, which we will let know about later on.</p> -<p>Next step is to setup the basic Arch Linux system on root, which can be done with <code>pacstrap</code>. What follows the directory is a list of packages, and you may choose any you wish (at least add <code>base</code>, <code>linux</code> and <code>linux-firmware</code>). These can be installed later, but I'd recommend having them from the beginning, just in case:</p> -<pre><code class="language-sh" data-lang="sh">pacstrap /mnt base linux linux-firmware sudo vim-minimal dhcpcd wpa_supplicant man-db man-pages intel-ucode grub efibootmgr os-prober ntfs-3g -</code></pre> -<p>Because my system has an intel CPU, I also installed <code>intel-ucode</code>.</p> -<p>Next up is generating the <code>fstab</code> file, which we tell to use UUIDs to be on the safe side through <code>-U</code>. This file is important, because without it the system won't know what partitions exist and will happily only boot with the initramfs, without anything of what we just installed at root. Not knowing this made me restart the entire installation process a few times.</p> -<pre><code class="language-sh" data-lang="sh">genfstab -U /mnt &gt;&gt; /mnt/etc/fstab -</code></pre> -<p>After that's done, we can change our root into our mount point and finish up configuration. We setup our timezone (so DST can be handled correctly if needed), synchronize the hardware clock (to persist the current time to the BIOS), uncomment our locales (exit <code>vim</code> by pressing ESC, then type <code>:wq</code> and press enter), generate locale files (which some applications need), configure language and keymap, update the hostname of our laptop and what indicate what <code>localhost</code> means…</p> -<pre><code class="language-sh" data-lang="sh">ln -sf /usr/share/zoneinfo/&lt;REGION&gt;/&lt;CITY&gt; /etc/localtime -hwclock --systohc -vim /etc/locale.gen -locale-gen -echo LANG=es_ES.UTF-8 &gt; /etc/locale.conf -echo KEYMAP=es &gt; /etc/vconsole.conf -echo &lt;HOST&gt; /etc/hostname -cat &lt;&lt;EOF &gt; /etc/hosts -127.0.0.1 localhost -::1 localhost -127.0.1.1 &lt;HOST&gt;.localdomain &lt;HOST&gt; -EOF -</code></pre> -<p>Really, we could've done all of this later, and the same goes for setting root's password with <code>passwd</code> or creating users (some of the groups you probably want are <code>power</code> and <code>wheel</code>).</p> -<p>The important part here is installing GRUB (which also needed the <code>efibootmgr</code> package):</p> -<pre><code class="language-sh" data-lang="sh">grub-install --target=x86_64-efi --efi-directory=/efi --bootloader-id=GRUB -</code></pre> -<p>If we want GRUB to find our Windows install, we also need the <code>os-prober</code> and <code>ntfs-3g</code> packages that we installed earlier with <code>pacstrap</code>, and with those we need to mount the Windows partition somewhere. It doesn't matter where. With that done, we can generate the GRUB configuration file which lists all the boot options:</p> -<pre><code class="language-sh" data-lang="sh">mkdir /windows -mount /dev/&lt;DISK&gt;&lt;PART5&gt; /windows -grub-mkconfig -o /boot/grub/grub.cfg -</code></pre> -<p>(In my case, I installed Windows before completing the Arch install, which created an additional partition in between).</p> -<p>With GRUB ready, we can exit the chroot and reboot the system, and if all went well, you should be greeted with a choice of operating system to use:</p> -<pre><code class="language-sh" data-lang="sh">exit -reboot -</code></pre> -<p>If for some reason you need to find what mountpoints were active prior to rebooting (to <code>unmount</code> them for example), you can use <code>findmnt</code>.</p> -<p>Before GRUB I tried rEFInd, which as I explained had issues with for missing a warning. Then I tried systemd-boot, which did not pick up Arch at first. That's where the several reinstalls come from, I didn't want to work with a half-worked system so I mostly redid the entire process quite a few times.</p> -<h2 id="migrating-to-the-new-laptop">Migrating to the new laptop</h2> -<p>I had a external disk formatted with NTFS. Of course, after moving every file I cared about from my previous Linux install caused all the permissions to reset. All my <code>.git</code> repositories, dirty with file permission changes! This is going to take a while to fix, or maybe I should just <code>git config core.fileMode false</code>. Here is a <a href="https://stackoverflow.com/a/2083563">lovely command</a> to sort them out on a per-repository basis:</p> -<pre><code class="language-sh" data-lang="sh">git diff --summary | grep --color 'mode change 100644 =&gt; 100755' | cut -d' ' -f7- | xargs -d'\n' chmod -x -</code></pre> -<p>I never realized how much I had stored over the years, but it really was a lot. While moving things to the external disk, I tried to do some cleanup, such as removing some build artifacts which needlessly occupy space, or completely skipping all the binary application files. If I need those I will install them anyway. The process was mostly focused on finding all the projects and program data that I did care about, or even some game saves. Nothing too difficult, but definitely time consuming.</p> -<h2 id="tuning-arch">Tuning Arch</h2> -<p>Now that our system is ready, install <code>pacman-contrib</code> to grab a copy of the <code>rankmirrors</code> speed. It should help speed up the download of whatever packages you want to install, since it will help us <a href="https://wiki.archlinux.org/index.php/Mirrors#List_by_speed">rank the mirrors by download speed</a>. Making a copy of the file is important, otherwise whenever you try to install something it will fail saying it can't find anything.</p> -<pre><code class="language-sh" data-lang="sh">cp /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.backup -sed -i 's/^#Server/Server/' /etc/pacman.d/mirrorlist.backup -rankmirrors -n 6 /etc/pacman.d/mirrorlist.backup | tee /etc/pacman.d/mirrorlist -</code></pre> -<p>This will take a while, but it should be well worth it. We're using <code>tee</code> to see the progress as it goes.</p> -<p>Some other packages I installed after I had a working system in no particular order:</p> -<ul> -<li><code>xfce4</code> and <code>xorg-server</code>. I just love the simplicity of XFCE.</li> -<li><code>xfce4-whiskermenu-plugin</code>, a really nice start menu.</li> -<li><code>xfce4-pulseaudio-plugin</code> and <code>pavucontrol</code>, to quickly adjust the audio with my mouse.</li> -<li><code>xfce4-taskmanager</code>, a GUI alternative I generally prefer to <code>htop</code>.</li> -<li><code>pulseaudio</code> and <code>pulseaudio-alsa</code> to get nice integration with XFCE4 and audio mixing.</li> -<li><code>firefox</code>, which comes with fonts too. A really good web browser.</li> -<li><code>git</code>, to commit <del>crimes</del> code.</li> -<li><code>code</code>, a wonderful editor which I used to write this blog entry.</li> -<li><code>nano</code>, so much nicer to write a simple commit message.</li> -<li><code>python</code> and <code>python-pip</code>, my favourite language to toy around ideas or use as a calculator.</li> -<li><code>telegram-desktop</code>, for my needs on sharing memes.</li> -<li><code>cmus</code> and <code>mpv</code>, a simple terminal music player and media player.</li> -<li><code>openssh</code>, to connect into any VPS I have access to.</li> -<li><code>base-devel</code>, necessary to build most projects I'll find myself working with (or even compiling some projects Rust which I installed via <code>rustup</code>).</li> -<li><code>flac</code>, <code>libmad</code>, <code>opus</code>, and <code>libvorbis</code>, to be able to play more audio files.</li> -<li><code>inkscape</code>, to make random drawings.</li> -<li><code>ffmpeg</code>, to convert media or record screen.</li> -<li><code>xclip</code>, to automatically copy screenshots to my clipboard.</li> -<li><code>gvfs</code>, needed by Thunar to handle mounting and having a trash (perma-deletion by default can be nasty sometimes).</li> -<li><code>noto-fonts</code>, <code>noto-fonts-cjk</code>, <code>noto-fonts-extra</code> and <code>noto-fonts-emoji</code>, if you don't want missing gliphs everywhere.</li> -<li><code>xfce4-notifyd</code> and <code>libnotify</code>, for notifications.</li> -<li><code>cronie</code>, to be able to <code>crontab -e</code>. Make sure to <code>system enable cronie</code>.</li> -<li><code>xarchiver</code> (with <code>p7zip</code>, <code>zip</code>, <code>unzip</code> and <code>unrar</code>) to uncompress stuff.</li> -<li><code>xreader</code> to read <code>.pdf</code> files.</li> -<li><code>sqlitebrowser</code> is always nice to tinker around with SQLite databases.</li> -<li><code>jre8-openjdk</code> if you want to run Java applications.</li> -<li><code>smartmontools</code> is nice with a SSD to view your disk statistics.</li> -</ul> -<p>After that, I configured my Super L key to launch <code>xfce4-popup-whiskermenu</code> so that it opens the application menu, pretty much the same as it would on Windows, moved the panels around and configured them to my needs, and it feels like home once more.</p> -<p>I made some mistakes while <a href="https://wiki.archlinux.org/index.php/Systemd-networkd">configuring systemd-networkd</a> and accidentally added a service that was incorrect, which caused boot to wait for it to timeout before completing. My boot time was taking 90 seconds longer because of this! <a href="https://www.reddit.com/r/archlinux/comments/4nv9yi/my_arch_greets_me_now_with_a_start_job/">The solution was to remove said service</a>, so this is something to look out for.</p> -<p>In order to find what was taking long, I had to edit the <a href="https://wiki.archlinux.org/index.php/kernel_parameters">kernel parameters</a> to remove the <code>quiet</code> option. I prefer seeing the output on what my computer is doing anyway, because it gives me a sense of progress and most importantly is of great value when things go wrong. Another interesting option is <code>noauto,x-systemd.automount</code>, which makes a disk lazily-mounted. If you have a slow disk, this could help speed things up.</p> -<p>If you see a service taking long, you can also use <code>systemd-analyze blame</code> to see what takes the longest, and <code>systemctl list-dependencies</code> is also helpful to find what services are active.</p> -<p>My <code>locale charmap</code> was spitting out a bunch of warnings:</p> -<pre><code class="language-sh" data-lang="sh">$ locale charmap -locale: Cannot set LC_CTYPE to default locale: No such file or directory -locale: Cannot set LC_MESSAGES to default locale: No such file or directory -locale: Cannot set LC_ALL to default locale: No such file or directory -ANSI_X3.4-1968 -</code></pre> -<p>…ANSI encoding? Immediately I added the following to <code>~/.bashrc</code> and <code>~/.profile</code>:</p> -<pre><code class="language-sh" data-lang="sh">export LC_ALL=en_US.UTF-8 -export LANG=en_US.UTF-8 -export LANGUAGE=en_US.UTF-8 -</code></pre> -<p>For some reason, I also had to edit <code>xfce4-terminal</code>'s preferences in advanced to change the default character encoding to UTF-8. This also solved my issues with pasting things into the terminal, and also proper rendering! I guess pastes were not working because it had some characters that could not be encoded.</p> -<p>To have working notifications, I added the following to <code>~/.bash_profile</code> after <code>exec startx</code>:</p> -<pre><code class="language-sh" data-lang="sh">systemctl --user start xfce4-notifyd.service -</code></pre> -<p>I'm pretty sure there's a better way to do this, or maybe it's not even necessary, but this works for me.</p> -<p>Some of the other things I had left to do was setting up <code>sccache</code> to speed up Rust builds:</p> -<pre><code class="language-sh" data-lang="sh">cargo install sccache -echo export RUSTC_WRAPPER=sccache &gt;&gt; ~/.bashrc -</code></pre> -<p>Once I had <code>cargo</code> ready, installed <code>hacksaw</code> and <code>shotgun</code> with it to perform screenshots.</p> -<p>I also disabled the security delay when downloading files in Firefox because it's just annoying, in <code>about:config</code> setting <code>security.dialog_enable_delay</code> to <code>0</code>, and added the <a href="https://alisdair.mcdiarmid.org/kill-sticky-headers/">Kill sticky headers</a> to my bookmarks (you may prefer <a href="https://github.com/t-mart/kill-sticky">the updated version</a>).</p> -<p>The <code>utils-linux</code> comes with a <code>fstrim</code> utility to <a href="https://wiki.archlinux.org/index.php/Solid_state_drive#Periodic_TRIM">trim the SSD weekly</a>, which I want enabled via <code>systemctl enable fstrim.timer</code> (you may also want to <code>start</code> it if you don't reboot often). For more SSD tips, check <a href="https://easylinuxtipsproject.blogspot.com/p/ssd.html">How to optimize your Solid State Drive</a>.</p> -<p>If the sound is funky prior to reboot, try <code>pulseaudio --kill</code> and <code>pulseaudio --start</code>, or delete <code>~/.config/pulse</code>.</p> -<p>I haven't been able to get the brightness keys to work yet, but it's not a big deal, because scrolling on the power manager plugin of Xfce does work (and also <code>xbacklight</code> works, or writing directly to <code>/sys/class/backlight/*</code>).</p> -<h2 id="tuning-windows">Tuning Windows</h2> -<p>On the Windows side, I disabled the annoying Windows defender by running (<kbd>Ctrl+R</kbd>) <code>gpedit.msc</code> and editing:</p> -<ul> -<li><em>Computer Configuration &gt; Administrative Templates &gt; Windows Components &gt; Windows Defender » Turn off Windows Defender » Enable</em></li> -<li><em>User Configuration &gt; Administrative Templates &gt; Start Menu and Taskbar » Remove Notifications and Action Center » Enable</em></li> -</ul> -<p>I also updated the <a href="https://github.com/WindowsLies/BlockWindows/raw/master/hosts"><code>hosts</code> file</a> (located at <code>%windir%\system32\Drivers\etc\hosts</code>) with the hope that it will stop some of the telemetry.</p> -<p>Last, to have consistent time on Windows and Linux, I changed the following registry key for a <code>qword</code> with value <code>1</code>:</p> -<pre><code>HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation\RealTimeIsUniversal -</code></pre> -<p>(The key might not exist, but you can create it if that's the case).</p> -<p>All this time, my laptop had the keyboard lights on, which have been quite annoying. Apparently, they also can cause <a href="https://www.reddit.com/r/ValveIndex/comments/cm6pos/psa_uninstalldisable_aura_sync_lighting_if_you/">massive FPS drops</a>. I headed over to <a href="https://rog.asus.com/downloads/">Asus Rog downloads</a>, selected Aura Sync…</p> -<pre><code class="language-md" data-lang="md"># Not Found - -The requested URL /campaign/aura/us/Sync.html was not found on this server. - -Additionally, a 404 Not Found error was encountered while trying to use an ErrorDocument to handle the request. -</code></pre> -<p>…great! I'll just find the <a href="https://www.asus.com/campaign/aura/global/">Aura site</a> somewhere else…</p> -<pre><code class="language-md" data-lang="md"># ASUS - -# We'll be back. - -Hi, our website is temporarily closed for service enhancements. - -We'll be back shortly.Thank you for your patience! -</code></pre> -<p>Oh come on. After waiting for the next day, I headed over, downloaded their software, tried to install it and it was an awful experience. It felt like I was purposedly installing malware. It spammed and flashed a lot of <code>cmd</code>'s on screen as if it was a virus. It was stuck at 100% doing that and then, Windows blue-screened with <code>KERNEL_MODE_HEAP_CORRUPTION</code>. Amazing. How do you screw up this bad?</p> -<p>Well, at least rebooting worked. I tried to <a href="https://answers.microsoft.com/en-us/windows/forum/all/unable-to-uninstall-asus-aura-sync-utility/e9bec36c-e62f-4773-80be-88fb68dace16">uninstall Aura, but of course that failed</a>. Using the <a href="https://support.microsoft.com/en-us/help/17588/windows-fix-problems-that-block-programs-being-installed-or-removed">troubleshooter to uninstall programs</a> helped me remove most of the crap that was installed.</p> -<p>After searching around how to disable the lights (because <a href="https://rog.asus.com/forum/showthread.php?112786-Option-to-Disable-Aura-Lights-on-Strix-G-series-(G531GT)-irrespective-of-OSes">my BIOS did not have this setting</a>), I stumbled upon <a href="https://rog.asus.com/us/innovation/armoury_crate/">&quot;Armoury Crate&quot;</a>. Okay, fine, I will install that.</p> -<p>The experience wasn't much better. It did the same thing with a lot of consoles flashing on screen. And of course, it resulted in another blue-screen, this time <code>KERNEL_SECURITY_CHECK_FAILURE</code>. To finish up, the BSOD kept happening as I rebooted the system. <del>Time to reinstall Windows once more.</del> After booting and crashing a few more times I could get into secure mode and perform the reinstall from there, which saved me from burning the <code>.iso</code> again.</p> -<p>Asus software might be good, but the software is utter crap.</p> -<p>After trying out <a href="https://github.com/wroberts/rogauracore">rogauracore</a> (which didn't list my model), it worked! I could disable the stupid lights from Linux, and <a href="https://gitlab.com/CalcProgrammer1/OpenRGB/-/wikis/home">OpenRGB</a> also works on Windows which may be worth checking out too.</p> -<p>Because <code>rougauracore</code> helped me and they linked to <a href="https://github.com/linuxhw/hw-probe/blob/master/README.md#appimage">hw-probe</a>, I decided to <a href="https://linux-hardware.org/?probe=0e3e48c501">run it on my system</a>, with the hopes it is useful for other people.</p> -<h2 id="closing-words">Closing words</h2> -<p>I hope the installation journey is at least useful to someone, or that you enjoyed reading about it all. If not, sorry!</p> -</content> - </entry> - <entry xml:lang="en"> - <title>Tips for Outpost</title> - <published>2020-05-10T00:00:00+00:00</published> - <updated>2020-05-22T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/tips-outpost/" type="text/html"/> - <id>https://lonami.dev/blog/tips-outpost/</id> - <content type="html"><p><a href="https://store.steampowered.com/app/1127110/Outpost/">Outpost</a> is a fun little game by Open Mid Interactive that has popped in recently in my recommended section of Steam, and I decided to give it a try.</p> -<p>It's a fun tower-defense game with progression, different graphics and random world generation which makes it quite fun for a few hours. In this post I want to talk about some tips I found useful to get past night 50.</p> -<h2 id="build-pattern">Build Pattern</h2> -<p>At first, you may be inclined to design a checkerboard pattern like the following, where &quot;C&quot; is the Crystal shrine, &quot;S&quot; is a stone launcher and &quot;B&quot; is a booster:</p> -<p><img src="https://lonami.dev/blog/tips-outpost/outpost-bad-pattern.svg" alt="Bad Outpost build pattern" /></p> -<p>Indeed, this pattern will apply <strong>4</strong> boosts to every turret, but unfortunately, the other 4 slots of the booster are wasted! This is because boosters are able to power 8 different towers, and you really want to maximize that. Here's a better design:</p> -<p><img src="https://lonami.dev/blog/tips-outpost/outpost-good-pattern.svg" alt="Good Outpost build pattern" /></p> -<p>The shrine's tower does get boosted, but it's still not really worth it to boost it. This pattern works good, and it's really easy to tile: just repeat the same 3x3 pattern.</p> -<p>Nonetheless, we can do better. What if we applied multiple boosters to the same tower while still applying all 8 boosts?</p> -<p><img src="https://lonami.dev/blog/tips-outpost/outpost-best-pattern.svg" alt="Best Outpost build pattern" /></p> -<p>That's what peak performance looks like. You can actually apply multiple boosters to the same tower, and it works great.</p> -<p>Now, is it really worth it building anywhere except around the shrine? Not really. You never know where a boss will come from, so all sides need a lot of defense if you want to stand a chance.</p> -<p>The addition of traps in 1.6 is amazing. You want to build these outside your strong &quot;core&quot;, mostly to slow the enemies down so your turrets have more time to finish them off. Don't waste boosters on the traps, and build them at a reasonable distance from the center (the sixth tile is a good spot):</p> -<p><img src="https://lonami.dev/blog/tips-outpost/outpost-trap-pattern.svg" alt="Trap Outpost build pattern" /></p> -<p>If you gather enough materials, you can build more trap and cannon layers outside, roughly at enough distance to slow them for enough duration until they reach the next layer of traps, and so on. Probably a single gap of &quot;cannon, booster, cannon&quot; is enough between trap layers, just not in the center where you need a lot of fire power.</p> -<h2 id="talents">Talents</h2> -<p>Talents are the way progression works in the game. Generally, after a run, you will have enough experience to upgrade nearly all talents of roughly the same tier. However, some are worth upgrading more than others (which provide basically no value).</p> -<p>The best ones to upgrade are:</p> -<ul> -<li>Starting supplies. Amazing to get good tools early.</li> -<li>Shrine shield. Very useful to hold against tough bosses.</li> -<li>Better buildings (cannon, boosters, bed and traps). They're a must to deal the most damage.</li> -<li>Better pickaxe. Stone is limited, so better make good use of it.</li> -<li>Better chests. They provide an insane amount of resources early.</li> -<li>Winter slow. Turrets will have more time to deal damage, it's perfect.</li> -<li>More time. Useful if you're running out, although generally you enter nights early after having a good core anyway.</li> -<li>More rocks. Similar to a better pickaxe, more stone is always better.</li> -</ul> -<p>Some decent ones:</p> -<ul> -<li>In-shrine turret. It's okay to get past the first night without building but not much beyond that.</li> -<li>Better axe and greaves. Great to save some energy and really nice quality of life to move around.</li> -<li>Tree growth. Normally there's enough trees for this not to be an issue but it can save some time gathering wood.</li> -<li>Wisps. They're half-decent since they can provide materials once you max out or max out expensive gear.</li> -</ul> -<p>Some okay ones:</p> -<ul> -<li>Extra XP while playing. Generally not needed due to the way XP scales per night, but can be a good boost.</li> -<li>Runestones. Not as reliable as chests but some can grant more energy per day.</li> -</ul> -<p>Some crap ones:</p> -<ul> -<li>Boosts for other seasons. I mean, winter is already the best, no use there.</li> -<li>Bow. The bow is very useless at the moment, it's not worth your experience.</li> -<li>More energy per bush. Not really worth hunting for bushes since you will have enough energy to do well.</li> -</ul> -<h2 id="turrets">Turrets</h2> -<p>Always build the highest tier, there's no point in anything lower than that. You will need to deal a lot of damage in a small area, which means space is a premium.</p> -<h2 id="boosters">Boosters</h2> -<p>If you're very early in the game, I recommend alternating both the flag and torch in a checkerboard pattern where the boosters should go in the pattern above. This way your towers will get extra speed and extra range, which works great.</p> -<p>When you're in mid-game (stone launchers, gears and campfires), I do not recommend using campfires. The issue is their range boost is way too long, and the turrets will miss quite a few shots. It's better to put all your power into fire speed for increased DPS, at least near the center. If you manage to build too far out and some of the turrets hardly ever shoot, you may put campfires there.</p> -<p>In end-game, of course alternate both of the highest tier upgrades. They are really good, and provide the best benefit / cost ratio.</p> -<h2 id="gathering-materials">Gathering Materials</h2> -<p>It is <strong>very</strong> important to use all your energy every day! Otherwise it will go to waste, and you will need a lot of materials.</p> -<p>As of 1.6, you can mine two things at once if they're close enough! I don't know if this is intended or a bug, but it sure is great.</p> -<p>Once you're in mid-game, your stone-based fort should stand pretty well against the nights on its own. After playing for a while you will notice, if your base can defend a boss, then it will have no issue carrying you through the nights until the next boss. You can (and should!) spend the nights gathering materials, but only when you're confident that the night won't run out.</p> -<p>Before the boss hits (every fifth night), come back to your base and use all of your materials. This is the next fort upgrade that will carry it the five next nights.</p> -<p>You may also speed up time during night, but make sure you use all your energy before hand. And also take care, in the current version of the game speeding up time only speeds up monster movement, not the fire rate or projectile speed of your turrets! This means they will miss more shots and can be pretty dangerous. If you're speeding up time, consider speeding it up for a little bit, then go back to normal until things are more calm, and repeat.</p> -<p>If you're in the end-game, try to rush for chests. They provide a huge amount of materials which is really helpful to upgrade all your tools early so you can make sure to get the most out of every rock left in the map.</p> -<p>In the end-game, after all stone has been collected, you don't really need to use all of your energy anymore. Just enough to have enough wood to build with the remaining stone. This will also be nice with the bow upgrades, which admitedly can get quite powerful, but it's best to have a strong fort first.</p> -<h2 id="season">Season</h2> -<p>In my opinion, winter is just the best of the seasons. You don't <em>really</em> need that much energy (it gets tiresome), or extra tree drops, or luck. Slower movement means your turrets will be able to shoot enemies for longer, dealing more damage over time, giving them more chance to take enemies out before they reach the shrine.</p> -<p>Feel free to re-roll the map a few times (play and exit, or even restart the game) until you get winter if you want to go for The Play.</p> -<h2 id="gear">Gear</h2> -<p>In my opinion, you really should rush for the best pickaxe you can afford. Stone is a limited resource that doesn't regrow like trees, so once you run out, it's over. Better to make the best use out of it with a good pickaxe!</p> -<p>You may also upgrade your greaves, we all known faster movement is a <em>really</em> nice quality of life improvement.</p> -<p>Of course, you will eventually upgrade your axe to chop wood (otherwise it's wasted energy, really), but it's not as much of a priority as the pickaxe.</p> -<p>Now, the bow is completely useless. Don't bother with it. Your energy is better spent gathering materials to build permanent turrets that deal constant damage while you're away, and the damage adds up with every extra turret you build.</p> -<p>With regards to items you carry (like sword, or helmet), look for these (from best to worst):</p> -<ul> -<li>Less minion life.</li> -<li>Chance to not consume energy.</li> -<li>+1 turret damage.</li> -<li>Extra energy.</li> -<li>+1 drop from trees or stones.</li> -<li>+1 free wood or stone per day.</li> -</ul> -<p>Less minion life, nothing to say. You will need it near end-game.</p> -<p>The chance to not consume energy is better the more energy you have. With a 25% chance not to consume energy, you can think of it as 1 extra energy for every 4 energy you have on average.</p> -<p>Turret damage is a tough one, it's <em>amazing</em> mid-game (it basically doubles your damage) but falls short once you unlock the cannon where you may prefer other items. Definitely recommended if you're getting started. You may even try to roll it on low tiers by dying on the second night, because it's that good.</p> -<p>Extra energy is really good, because it means you can get more materials before it gets too rough. Make sure you have built at least two beds in the first night! This extra energy will pay of for the many nights to come.</p> -<p>The problem with free wood or stone per day is that you have, often, five times as much energy per day. By this I mean you can get easily 5 stone every day, which means 5 extra stone, whereas the other would provide just 1 per night. On a good run, you will get around 50 free stone or 250 extra stone. It's a clear winner.</p> -<p>In end-game, more quality of life are revealing chests so that you can rush them early, if you like to hunt for them try to make better use of the slot.</p> -<h2 id="closing-words">Closing words</h2> -<p>I hope you enjoy the game as much as I do! Movement is sometimes janky and there's the occassional lag spikes, but despite this it should provide at least a few good hours of gameplay. Beware however a good run can take up to an hour!</p> -</content> - </entry> - <entry xml:lang="en"> - <title>Python ctypes and Windows</title> - <published>2019-06-19T00:00:00+00:00</published> - <updated>2019-06-19T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/ctypes-and-windows/" type="text/html"/> - <id>https://lonami.dev/blog/ctypes-and-windows/</id> - <content type="html"><p><a href="https://www.python.org/">Python</a>'s <a href="https://docs.python.org/3/library/ctypes.html"><code>ctypes</code></a> is quite a nice library to easily load and invoke C methods available in already-compiled <a href="https://en.wikipedia.org/wiki/Dynamic-link_library"><code>.dll</code> files</a> without any additional dependencies. And I <em>love</em> depending on as little as possible.</p> -<p>In this blog post, we will walk through my endeavors to use <code>ctypes</code> with the <a href="https://docs.microsoft.com/en-us/windows/desktop/api/">Windows API</a>, and do some cool stuff with it.</p> -<p>We will assume some knowledge of C/++ and Python, since we will need to read and write a bit of both. Please note that this post is only an introduction to <code>ctypes</code>, and if you need more information you should consult the <a href="https://docs.python.org/3/library/ctypes.html">Python's documentation for <code>ctypes</code></a>.</p> -<p>While the post focuses on Windows' API, the code here probably applies to unix-based systems with little modifications.</p> -<h2 id="basics">Basics</h2> -<p>First of all, let's learn how to load a library. Let's say we want to load <code>User32.dll</code>:</p> -<pre><code class="language-python" data-lang="python">import ctypes - -ctypes.windll.user32 -</code></pre> -<p>Yes, it's that simple. When you access an attribute of <code>windll</code>, said library will load. Since Windows is case-insensitive, we will use lowercase consistently.</p> -<p>Calling a function is just as simple. Let's say you want to call <a href="https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-setcursorpos"><code>SetCursorPos</code></a>, which is defined as follows:</p> -<pre><code class="language-c" data-lang="c">BOOL SetCursorPos( - int X, - int Y -); -</code></pre> -<p>Okay, it returns a <code>bool</code> and takes two inputs, <code>x</code> and <code>y</code>. So we can call it like so:</p> -<pre><code class="language-python" data-lang="python">ctypes.windll.user32.SetCursorPos(100, 100) -</code></pre> -<p>Try it! Your cursor will move!</p> -<h2 id="funky-stuff">Funky Stuff</h2> -<p>We can go a bit more crazy and make it form a spiral:</p> -<pre><code class="language-python" data-lang="python">import math -import time - -for i in range(200): - x = int(500 + math.cos(i / 5) * i) - y = int(500 + math.sin(i / 5) * i) - ctypes.windll.user32.SetCursorPos(x, y) - time.sleep(0.05) -</code></pre> -<p>Ah, it's always so pleasant to do random stuff when programming. Sure makes it more fun.</p> -<h2 id="complex-structures">Complex Structures</h2> -<p><code>SetCursorPos</code> was really simple. It took two parameters and they both were integers. Let's go with something harder. Let's go with <a href="https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-sendinput"><code>SendInput</code></a>! Emulating input will be a fun exercise:</p> -<pre><code class="language-c" data-lang="c">UINT SendInput( - UINT cInputs, - LPINPUT pInputs, - int cbSize -); -</code></pre> -<p>Okay, <code>LPINPUT</code>, what are you? Microsoft likes to prefix types with what they are. In this case, <code>LP</code> stands for &quot;Long Pointer&quot; (I guess?), so <code>LPINPUT</code> is just a Long Pointer to <a href="https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-taginput"><code>INPUT</code></a>:</p> -<pre><code class="language-c" data-lang="c">typedef struct tagINPUT { - DWORD type; - union { - MOUSEINPUT mi; - KEYBDINPUT ki; - HARDWAREINPUT hi; - } DUMMYUNIONNAME; -} INPUT, *PINPUT, *LPINPUT; -</code></pre> -<p>Alright, that's new. We have a <code>struct</code> and <code>union</code>, two different concepts. We can define both with <code>ctypes</code>:</p> -<pre><code class="language-python" data-lang="python">INPUT_MOUSE = 0 -INPUT_KEYBOARD = 1 -INPUT_HARDWARE = 2 - -class INPUT(ctypes.Structure): - _fields_ = [ - ('type', ctypes.c_long), - ... - ] -</code></pre> -<p>Structures are classes that subclass <code>ctypes.Structure</code>, and you define their fields in the <code>_fields_</code> class-level variable, which is a list of tuples <code>(field name, field type)</code>.</p> -<p>The C structure had a <code>DWORD type</code>. <code>DWORD</code> is a <code>c_long</code>, and <code>type</code> is a name like any other, which is why we did <code>('type', ctypes.c_long)</code>.</p> -<p>But what about the union? It's anonymous, and we can't make anonymous unions (<em>citation needed</em>) with <code>ctypes</code>. We will give it a concrete name and a type.</p> -<p>Before defining the union, we need to define its inner structures, <a href="https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-tagmouseinput"><code>MOUSEINPUT</code></a>, <a href="https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-tagkeybdinput"><code>KEYBDINPUT</code></a> and <a href="https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-taghardwareinput"><code>HARDWAREINPUT</code></a>. We won't be using them all, but since they count towards the final struct size (C will choose the largest structure as the final size), we need them, or Windows' API will get confused and refuse to work (personal experience):</p> -<pre><code class="language-python" data-lang="python">class MOUSEINPUT(ctypes.Structure): - _fields_ = [ - ('dx', ctypes.c_long), - ('dy', ctypes.c_long), - ('mouseData', ctypes.c_long), - ('dwFlags', ctypes.c_long), - ('time', ctypes.c_long), - ('dwExtraInfo', ctypes.POINTER(ctypes.c_ulong)) - ] - - -class KEYBDINPUT(ctypes.Structure): - _fields_ = [ - ('wVk', ctypes.c_short), - ('wScan', ctypes.c_short), - ('dwFlags', ctypes.c_long), - ('time', ctypes.c_long), - ('dwExtraInfo', ctypes.POINTER(ctypes.c_ulong)) - ] - - -class HARDWAREINPUT(ctypes.Structure): - _fields_ = [ - ('uMsg', ctypes.c_long), - ('wParamL', ctypes.c_short), - ('wParamH', ctypes.c_short) - ] - - -class INPUTUNION(ctypes.Union): - _fields_ = [ - ('mi', MOUSEINPUT), - ('ki', KEYBDINPUT), - ('hi', HARDWAREINPUT) - ] - - -class INPUT(ctypes.Structure): - _fields_ = [ - ('type', ctypes.c_long), - ('value', INPUTUNION) - ] -</code></pre> -<p>Some things to note:</p> -<ul> -<li>Pointers are defined as <code>ctypes.POINTER(inner type)</code>.</li> -<li>The field names can be anything you want. You can make them more &quot;pythonic&quot; if you want (such as changing <code>dwExtraInfo</code> for just <code>extra_info</code>), but I chose to stick with the original naming.</li> -<li>The union is very similar, but it uses <code>ctypes.Union</code> instead of <code>ctypes.Structure</code>.</li> -<li>We gave a name to the anonymous union, <code>INPUTUNION</code>, and used it inside <code>INPUT</code> with also a made-up name, <code>('value', INPUTUNION)</code>.</li> -</ul> -<p>Now that we have all the types we need defined, we can use them:</p> -<pre><code class="language-python" data-lang="python">KEYEVENTF_KEYUP = 0x0002 - -def press(vk, down): - inputs = INPUT(type=INPUT_KEYBOARD, value=INPUTUNION(ki=KEYBDINPUT( - wVk=vk, - wScan=0, - dwFlags=0 if down else KEYEVENTF_KEYUP, - time=0, - dwExtraInfo=None - ))) - ctypes.windll.user32.SendInput(1, ctypes.byref(inputs), ctypes.sizeof(inputs)) - - -for char in 'HELLO': - press(ord(char), down=True) - press(ord(char), down=False) -</code></pre> -<p>Run it! It will press and release the keys <code>hello</code> to type the word <code>&quot;hello&quot;</code>!</p> -<p><code>vk</code> stands for &quot;virtual key&quot;. Letters correspond with their upper-case ASCII value, which is what we did above. You can find all the available keys in the page with all the <a href="https://docs.microsoft.com/en-us/windows/desktop/inputdev/virtual-key-codes">Virtual Key Codes</a>.</p> -<h2 id="dynamic-inputs-and-pointers">Dynamic Inputs and Pointers</h2> -<p>What happens if a method wants something by reference? That is, a pointer to your thing? For example, <a href="https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getcursorpos"><code>GetCursorPos</code></a>:</p> -<pre><code class="language-c" data-lang="c">typedef struct tagPOINT { - LONG x; - LONG y; -} POINT, *PPOINT, *NPPOINT, *LPPOINT; - -BOOL GetCursorPos( - LPPOINT lpPoint -); -</code></pre> -<p>It wants a Long Pointer to <a href="https://docs.microsoft.com/en-us/windows/desktop/api/windef/ns-windef-point"><code>POINT</code></a>. We can do just that with <code>ctypes.byref</code>:</p> -<pre><code class="language-python" data-lang="python">class POINT(ctypes.Structure): - _fields_ = [ - ('x', ctypes.c_long), - ('y', ctypes.c_long) - ] - - -def get_mouse(): - point = POINT() - ctypes.windll.user32.GetCursorPos(ctypes.byref(point)) - # pass our point by ref ^^^^^ - # this lets GetCursorPos fill its x and y fields - - return point.x, point.y - - -while True: - print(get_mouse()) - time.sleep(0.05) -</code></pre> -<p>Now you can track the mouse position! Make sure to <code>Ctrl+C</code> the program when you're tired of it.</p> -<p>What happens if a method wants a dynamically-sized input?</p> -<pre><code class="language-python" data-lang="python">buffer = ctypes.create_string_buffer(size) -</code></pre> -<p>In that case, you can create an in-memory <code>buffer</code> of <code>size</code> with <code>ctypes.create_string_buffer</code>. It will return a character array of that size, which you can pass as a pointer directly (without <code>ctypes.byref</code>).</p> -<p>To access the buffer's contents, you can use either <code>.raw</code> or <code>.value</code>:</p> -<pre><code class="language-python" data-lang="python">entire_buffer_as_bytes = buffer.raw -up_until_null = buffer.value -</code></pre> -<p>When the method fills in the data, you can <code>cast</code> your buffer back into a pointer of a concrete type:</p> -<pre><code class="language-python" data-lang="python">result_ptr = ctypes.cast(buffer, ctypes.POINTER(ctypes.c_long)) -</code></pre> -<p>And you can de-reference pointers with <code>.contents</code>:</p> -<pre><code class="language-python" data-lang="python">first_result = result_ptr.contents -</code></pre> -<h2 id="arrays">Arrays</h2> -<p>Arrays are defined as <code>type * size</code>. Your linter may not like that, and if you don't know the size beforehand, consider creating a 0-sized array. For example:</p> -<pre><code class="language-python" data-lang="python"># 10 longs -ten_longs = (ctypes.c_long * 10)() -for i in range(10): - ten_longs[i] = 2 ** i - -# Unknown size of longs, e.g. inside some Structure -longs = (ctypes.c_long * 0) - -# Now you know how many longs it actually was -known_longs = ctypes.cast( - ctypes.byref(longs), - ctypes.POINTER(ctypes.c_long * size) -).contents -</code></pre> -<p>If there's a better way to initialize arrays, please let me know.</p> -<h2 id="wintypes">wintypes</h2> -<p>Under Windows, the <code>ctypes</code> module has a <code>wintypes</code> submodule. This one contains definitions like <code>HWND</code> which may be useful and can be imported as:</p> -<pre><code class="language-python" data-lang="python">from ctypes.wintypes import HWND, LPCWSTR, UINT -</code></pre> -<h2 id="callbacks">Callbacks</h2> -<p>Some functions (I'm looking at you, <a href="https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumwindows"><code>EnumWindows</code></a>) ask us to pass a callback. In this case, it wants a <a href="https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms633498(v=vs.85)"><code>EnumWindowsProc</code></a>:</p> -<pre><code class="language-c" data-lang="c">BOOL EnumWindows( - WNDENUMPROC lpEnumFunc, - LPARAM lParam -); - -BOOL CALLBACK EnumWindowsProc( - _In_ HWND hwnd, - _In_ LPARAM lParam -); -</code></pre> -<p>The naive approach won't work:</p> -<pre><code class="language-python" data-lang="python">def callback(hwnd, lParam): - print(hwnd) - return True - -ctypes.windll.user32.EnumWindows(callback, 0) -# ctypes.ArgumentError: argument 1: &lt;class 'TypeError'&gt;: Don't know how to convert parameter 1 -# Aww. -</code></pre> -<p>Instead, you must wrap your function as a C definition like so:</p> -<pre><code class="language-python" data-lang="python">from ctypes.wintypes import BOOL, HWND, LPARAM - -EnumWindowsProc = ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM) - -def callback(hwnd, lParam): - print(hwnd) - return True - -# Wrap the function in the C definition -callback = EnumWindowsProc(callback) - -ctypes.windll.user32.EnumWindows(callback, 0) -# Yay, it works. -</code></pre> -<p>You may have noticed this is what decorators do, wrap the function. So…</p> -<pre><code class="language-python" data-lang="python">from ctypes.wintypes import BOOL, HWND, LPARAM - -@ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM) -def callback(hwnd, lParam): - print(hwnd) - return True - -ctypes.windll.user32.EnumWindows(callback, 0) -</code></pre> -<p>…will also work. And it is a <em>lot</em> fancier.</p> -<h2 id="closing-words">Closing Words</h2> -<p>With the knowledge above and some experimentation, you should be able to call and do (almost) anything you want. That was pretty much all I needed on my project anyway :)</p> -<p>We have been letting Python convert Python values into C values, but you can do so explicitly too. For example, you can use <code>ctypes.c_short(17)</code> to make sure to pass that <code>17</code> as a <code>short</code>. And if you have a <code>c_short</code>, you can convert or cast it to its Python <code>.value</code> as <code>some_short.value</code>. The same applies for integers, longs, floats, doubles… pretty much anything, char pointers (strings) included.</p> -<p>If you can't find something in their online documentation, you can always <a href="https://github.com/BurntSushi/ripgrep"><code>rg</code></a> for it in the <code>C:\Program Files (x86)\Windows Kits\10\Include\*</code> directory.</p> -<p>Note that the <code>ctypes.Structure</code>'s that you define can have more methods of your own. For example, you can write them a <code>__str__</code> to easily view its fields, or define a <code>@property</code> to re-interpret some data in a meaningful way.</p> -<p>For enumerations, you can pass just the right integer number, make a constant for it, or if you prefer, use a <a href="https://docs.python.org/3/library/enum.html#enum.IntEnum"><code>enum.IntEnum</code></a>. For example, <a href="https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/dism/dismloglevel-enumeration"><code>DismLogLevel</code></a> would be:</p> -<pre><code class="language-python" data-lang="python">class DismLogLevel(enum.IntEnum): - DismLogErrors = 0 - DismLogErrorsWarnings = 1 - DismLogErrorsWarningsInfo = 2 -</code></pre> -<p>And you <em>should</em> be able to pass <code>DismLogLevel.DismLogErrors</code> as the parameter now.</p> -<p>If you see a function definition like <code>Function(void)</code>, that's C's way of saying it takes no parameters, so just call it as <code>Function()</code>.</p> -<p>Make sure to pass all parameters, even if they seem optional they probably still want a <code>NULL</code> at least, and of course, read the documentation well. Some methods have certain pre-conditions.</p> -<p>Have fun hacking!</p> -</content> - </entry> - <entry xml:lang="en"> - <title>Shattered Pixel Dungeon</title> - <published>2019-06-03T00:00:00+00:00</published> - <updated>2019-06-03T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/pixel-dungeon/" type="text/html"/> - <id>https://lonami.dev/blog/pixel-dungeon/</id> - <content type="html"><p><a href="https://shatteredpixel.com/shatteredpd/">Shattered Pixel Dungeon</a> is the classic roguelike RPG game with randomly-generated dungeons. As a new player, it was a bit frustrating to be constantly killed on the first levels of the dungeon, but with some practice it's easy to reach high levels if you can kill the first boss.</p> -<h2 id="basic-tips">Basic Tips</h2> -<p>The game comes with its own tips, but here's a short and straight-forward summary:</p> -<ul> -<li><strong>Don't rush into enemies</strong>. Abuse doors and small corridors to kill them one by one. You can use the clock on the bottom left to wait a turn without moving.</li> -<li><strong>Explore each level at full</strong>. You will find goodies and gain XP while doing so.</li> -<li><strong>Upon finding a special room</strong> (e.g. has a chest but is protected by piranhas), drink all potions that you found in that level until there's one that helps you (e.g. be invisible so piranhas leave you alone). There is guaranteed to be a helpful one per level with special rooms.</li> -<li><strong>Drink potions as early as possible</strong>. Harmful potions do less damage on early levels (and if you die, you lose less). This will keep them identified early for the rest of the game.</li> -<li><strong>Read scrolls as early as possible</strong> as well. This will keep them identified. It may be worth to wait until you have an item which may be cursed and until the level is clear, because some scrolls clean curses and others alert enemies.</li> -<li><strong>Food and health are resources</strong> that you have to <em>manage</em>, not keep them always at full. Even if you are starving and taking damage, you may not need to eat <em>just yet</em>, since food is scarce. Eat when you are low on health or in possible danger.</li> -<li><strong>Piranhas</strong>. Seriously, just leave them alone if you are melee. They're free food if you're playing ranged, though.</li> -<li><strong>Prefer armor over weapons</strong>. And make sure to identify or clean it from curses before wearing anything!</li> -<li><strong>Find a dew vial early</strong>. It's often a better idea to store dew (health) for later than to use it as soon as possible.</li> -</ul> -<h2 id="bosses">Bosses</h2> -<p>There is a boss every 5 levels.</p> -<ul> -<li><strong>Level 5 boss</strong>. Try to stay on water, but don't let <em>it</em> stay on water since it will heal. Be careful when he starts enraging.</li> -<li><strong>Level 10 boss</strong>. Ranged weapons are good against it.</li> -<li><strong>Level 15 boss</strong>. I somehow managed to tank it with a health potion.</li> -<li><strong>Level 20 boss</strong>. I didn't get this far just yet. You are advised to use scrolls of magic mapping in the last levels to skip straight to the boss, since there's nothing else of value.</li> -<li><strong>Level 25 boss</strong>. The final boss. Good job if you made it this far!</li> -</ul> -<h2 id="mage">Mage</h2> -<p>If you followed the basic tips, you will sooner or later make use of two scrolls of upgrade in a single run. This will unlock the mage class, which is ridiculously powerful. He starts with a ranged-weapon, a magic missile wand, which is really helpful to keep enemies at a distance. Normally, you want to use this at first to surprise attack them soon, and if you are low on charges, you may go melee on normal enemies if you are confident.</p> -<h2 id="luck">Luck</h2> -<p>This game is all about luck and patience! Some runs will be better than others, and you should thank and pray the RNG gods for them. If you don't, they will only give you cursed items and not a single scroll to clean them. So, good luck and enjoy playing!</p> -</content> - </entry> - <entry xml:lang="en"> - <title>Installing NixOS, Take 2</title> - <published>2019-02-15T00:00:00+00:00</published> - <updated>2019-02-16T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/installing-nixos-2/" type="text/html"/> - <id>https://lonami.dev/blog/installing-nixos-2/</id> - <content type="html"><p>This is my second take at installing NixOS, after a while being frustrated with Arch Linux and the fact that a few kernel upgrades ago, the system crashed randomly from time to time. <code>journalctl</code> did not have any helpful hints and I thought reinstalling could be worthwhile anyway.</p> -<p>This time, I started with more knowledge! The first step is heading to the <a href="https://nixos.org">NixOS website</a> and downloading their minimal installation CD for 64 bits. I didn't go with their graphical live CD, because their <a href="https://nixos.org/nixos/manual">installation manual</a> is a wonderful resource that guides you nicely.</p> -<p>Once you have downloaded their <code>.iso</code>, you should probably verify it's <code>sha256sum</code> and make sure that it matches. The easiest thing to do in my opinion is using an USB to burn the image in it. Plug it in and check its device name with <code>fdisk -l</code>. In my case, it was <code>/dev/sdb</code>, so I went ahead with it and ran <code>dd if=nixos.iso of=/dev/sdb status=progress</code>. Make sure to run <code>sync</code> once that's done.</p> -<p>If either <code>dd</code> or <code>sync</code> seem &quot;stuck&quot; in the end, they are just flushing the changes to disk to make sure all is good. This is normal, and depends on your drives.</p> -<p>Now, reboot your computer with the USB plugged in and make sure to boot into it. You should be welcome with a pretty screen. Just select the first option and wait until it logs you in as root. Once you're there you probably want to <code>loadkeys es</code> or whatever your keyboard layout is, or you will have a hard time with passwords, since the characters are all over the place.</p> -<p>In a clean disk, you would normally create the partitions now. In my case, I already had the partitions made (100MB for the EFI system, where <code>/boot</code> lives, 40GB for the root <code>/</code> partition with my old Linux installation, and 700G for <code>/home</code>), so I didn't need to do anything here. The manual showcases <code>parted</code>, but I personally use <code>fdisk</code>, which has very helpful help I check every time I use it.</p> -<p><strong>Important</strong>: The <code>XY</code> in <code>/dev/sdXY</code> is probably different in your system! Make sure you use <code>fdisk -l</code> to see the correct letters and numbers!</p> -<p>With the partitions ready in my UEFI system, I formatted both <code>/</code> and <code>/boot</code> just to be safe with <code>mkfs.ext4 -L nixos /dev/sda2</code> and <code>mkfs.fat -F 32 -n boot /dev/sda1</code> (remember that these are the letters and numbers used in my partition scheme). Don't worry about the warning in the second command regarding lowercase letters and Windows. It's not really an issue.</p> -<p>Now, since we gave each partition a label, we can easily mount them through <code>mount /dev/disk/by-label/nixos /mnt</code> and, in UEFI systems, be sure to <code>mkdir -p /mnt/boot</code> and <code>mount /dev/disk/by-label/boot /mnt/boot</code>. I didn't bother setting up swap, since I have 8GB of RAM in my laptop and that's really enough for my use case.</p> -<p>With that done, we will now ask the configuration wizard to do some work for us (in particular, generate a template) with <code>nixos-generate-config --root /mnt</code>. This generates a very well documented file that we should edit right now (and this is important!) with whatever editor you prefer. I used <code>vim</code>, but you can change it for <code>nano</code> if you prefer.</p> -<p>On to the configuration file, we need to enable a few things, so <code>vim /mnt/etc/nixos/configuration.nix</code> and start scrolling down. We want to make sure to uncomment:</p> -<pre><code># We really want network! -networking.wireless.enable = true; - -# This &quot;fixes&quot; the keyboard layout. Put the one you use. -i18n = { -consoleKeyMap = &quot;es&quot;; -} - -# Timezones are tricky so let's get this right. -time.timeZone = &quot;Europe/Madrid&quot;; - -# We *really* want some base packages installed, such as -# wpa_supplicant, or we won't have a way to connect to the -# network once we install... -environment.systemPackages = with pkgs; [ -wpa_supplicant wget curl vim neovim cmus mpv firefox git tdesktop -]; - -# Printing is useful, sure, enable CUPS -services.printing.enable = true; - -# We have speakers, let's make use of them. -sound.enable = true; -hardware.pulseaudio.enable = true; - -# We want the X11 windowing system enabled, in Spanish. -services.xserver.enable = true; -services.xserver.layout = &quot;es&quot;; - -# I want a desktop manager in my laptop. -# I personally prefer XFCE, but the manual shows plenty -# of other options, such as Plasma, i3 WM, or whatever. -services.xserver.desktopManager.xfce.enable = true; -services.xserver.desktopManager.default = &quot;xfce&quot;; - -# Touchpad is useful (although sometimes annoying) in a laptop -services.xserver.libinput.enable = true; - -# We don't want to do everything as root! -users.users.lonami = { -isNormalUser = true; -uid = 1000; -home = &quot;/home/lonami&quot;; -extraGroups = [ &quot;wheel&quot; &quot;networkmanager&quot; &quot;audio&quot; ]; -}; -</code></pre> -<p><em>(Fun fact, I overlooked the configuration file until I wrote this and hadn't noticed sound/pulseaudio was there. It wasn't hard to find online how to enable it though!)</em></p> -<p>Now, let's modify <code>hardware-configuration.nix</code>. But if you have <code>/home</code> in a separate partition like me, you should run <code>blkid</code> to figure out its UUID. To avoid typing it out myself, I just ran <code>blkid &gt;&gt; /mnt/etc/nixos/hardware-configuration.nix</code> so that I could easily move it around with <code>vim</code>:</p> -<pre><code># (stuff...) - -fileSystems.&quot;/home&quot; = -{ device = &quot;/dev/disk/by-uuid/d344c686-cae7-4dd3-840e-308eddf86608&quot;; -fsType = &quot;ext4&quot;; -}; - -# (more stuff...) -</code></pre> -<p>Note that, obviously, you should put your own partition's UUID there. Modifying the configuration is where I think the current NixOS' manual should have made more emphasis, at this step of the installation. They do detail it below, but that was already too late in my first attempt. Anyway, you can boot from the USB and run <code>nixos-install</code> as many times as you need until you get it working!</p> -<p>But before installing, we need to configure the network since there are plenty of things to download. If you want to work from WiFi, you should first figure out the name of your network card with <code>ip link show</code>. In my case it's called <code>wlp3s0</code>. So with that knowledge we can run <code>wpa_supplicant -B -i wlp3s0 -c &lt;(wpa_passphrase SSID key)</code>. Be sure to replace both <code>SSID</code> and <code>key</code> with the name of your network and password key, respectively. If they have spaces, surround them in quotes.</p> -<p>Another funny pitfall was typing <code>wpa_supplicant</code> in the command above twice (instead of <code>wpa_passphrase</code>). That sure spit out a few funny errors! Once you have ran that, wait a few seconds and <code>ping 1.1.1.1</code> to make sure that you can reach the internet. If you do, <code>^C</code> and let's install NixOS!</p> -<pre><code>nixos-install -</code></pre> -<p>Well, that was pretty painless. You can now <code>reboot</code> and enjoy your new, functional system.</p> -<h2 id="afterword">Afterword</h2> -<p>The process of installing NixOS was really painless once you have made sense out of what things mean. I was far more pleased this time than in my previous attempt, despite the four attempts I needed to have it up and running.</p> -<p>However not all is so good. I'm not sure where I went wrong, but the first time I tried with <code>i3</code> instead of <code>xfce</code>, all I was welcome with was a white, small terminal in the top left corner. I even generated a configuration file with <code>i3-config-wizard</code> to make sure it could detect my Mod1/Mod4 keys (which, it did), but even after rebooting, my commands weren't responding. For example, I couldn't manage to open another terminal with <code>Mod1+Enter</code>. I'm not even sure that I was in <code>i3</code>…</p> -<p>In my very first attempt, I pressed <code>Alt+F8</code> as suggested in the welcome message. This took me an offline copy of the manual, which is really nicely done. Funny enough, though, I couldn't exit <code>w3m</code>. Both <code>Q</code> and <code>B</code> to quit and take me back wouldn't work. Somehow, it kept throwing me back into <code>w3m</code>, so I had to forcibly shutdown.</p> -<p>In my second attempt, I also forgot to configure network, so I had no way to download <code>wpa_supplicant</code> without having <code>wpa_supplicant</code> itself to connect my laptop to the network! So, it was important to do that through the USB before installing it (which comes with the program preinstalled), just by making sure to add it in the configuration file.</p> -<p>Some other notes, if you can't reach the internet, don't add any DNS in <code>/etc/resolv.conf</code>. This should be done declaratively in <code>configuration.nix</code>.</p> -<p>In the end, I spent the entire afternoon playing around with it, taking breaks and what-not. I still haven't figured out why <code>nvim</code> was printing the literal escape character when going from normal to insert mode in the <code>xfce4-terminal</code> (and other actions also made it print this &quot;garbage&quot; to the console), why sometimes the network can reach the internet (and only some sites!) and sometimes not, and how to setup dualboot.</p> -<p>But despite all of this, I think it was a worth installing it again. One sure sees things from a different perspective, and gets the chance to write another blog post!</p> -<p>If there's something I overlooked or that could be done better, or maybe you can explain it differently, please be sure to <a href="https://lonami.dev/contact">contact me</a> to let me know!</p> -<h2 id="update">Update</h2> -<p>Well, that was surprisingly fast feedback. Thank you very much <a href="https://bb010g.keybase.pub/">@bb010g</a> for it! As they rightfully pointed out, one can avoid adding <code>/home</code> manually to <code>hardware-configuration.nix</code> if you mount it before generating the configuration files. However, the installation process doesn't need <code>/home</code> mounted, so I didn't do it.</p> -<p>The second weird issue with <code>w3m</code> is actually a funny one. <code>Alt+F8</code> <em>switches to another TTY</em>! That's why quitting the program wouldn't do anything. You'd still be in a different TTY! Normally, this is <code>Ctrl+Alt+FX</code>, so I hadn't even thought that this is what could be happening. Anyway, the solution is not quitting the program, but rather going back to the main TTY with <code>Alt+F1</code>. You can switch back and forth all you need to consult the manual.</p> -<p>More suggestions are having <a href="https://github.com/rycee/home-manager"><code>home-manager</code></a> manage the graphical sessions, since it should be easier to deal with than the alternatives.</p> -<p>Despite having followed the guide and having read it over and over several times, it seems like my thoughts in this blog post may be a bit messy. So I recommend you also reading through the guide to have two versions of all this, just in case.</p> -<p>Regarding network issues, they use <code>connman</code> so that may be worth checking out.</p> -<p>Regarding terminal issues with <code>nvim</code> printing the literal escape character, I was told off for not having checked what my <code>$TERM</code> was. I hadn't really looked into it much myself, just complained about it here, so sorry for being annoying about that. A quick search in the <code>nixpkgs</code> repository lets us find <a href="https://github.com/NixOS/nixpkgs/blob/release-18.09/pkgs/applications/editors/neovim/default.nix">neovim/default.nix</a>, with version 0.3.1. Looking at <a href="https://github.com/neovim/neovim">Neovim's main repository</a> we can see that this is a bit outdated, but that is fine.</p> -<p>If only I had bothered to look at <a href="https://github.com/neovim/neovim/wiki/FAQ#nvim-shows-weird-symbols-2-q-when-changing-modes">Neovim's wiki</a>, (which they found through <a href="https://github.com/neovim/neovim/issues/7749">Neovim's GitHub issues</a>) I would've seen that some terminals just don't support the program properly. The solution is, of course, to use a different terminal emulator with better support or to disable the <code>guicursor</code> in Neovim's config.</p> -<p>This is a pretty good life lesson. 30 seconds of searching, maybe two minutes and a half for also checking XFCE issues, are often more than enough to troubleshoot your issues. The internet is a big place and more people have surely came across the problem before, so make sure to look online first. In my defense I'll say that it didn't bother me so much so I didn't bother looking for that soon either.</p> -</content> - </entry> - <entry xml:lang="en"> - <title>Breaking Risk of Rain</title> - <published>2019-01-12T00:00:00+00:00</published> - <updated>2019-01-12T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/breaking-ror/" type="text/html"/> - <id>https://lonami.dev/blog/breaking-ror/</id> - <content type="html"><p><a href="https://riskofraingame.com/">Risk of Rain</a> is a fun little game you can spend a lot of hours on. It's incredibly challenging for new players, and fun once you have learnt the basics. This blog will go through what I've learnt and how to play the game correctly.</p> -<h2 id="getting-started">Getting Started</h2> -<p>If you're new to the game, you may find it frustrating. You must learn very well to dodge.</p> -<p>Your first <a href="http://riskofrain.wikia.com/wiki/Category:Characters">character</a> will be <a href="http://riskofrain.wikia.com/wiki/Commando">Commando</a>. He's actually a very nice character. Use your third skill (dodge) to move faster, pass through large groups of enemies, and negate fall damage.</p> -<p>If there are a lot of monsters, remember to <strong>leave</strong> from there! It's really important for survival. Most enemies <strong>don't do body damage</strong>. Not even the body of the <a href="http://riskofrain.wikia.com/wiki/Magma_Worm">Magma Worm</a> or the <a href="http://riskofrain.wikia.com/wiki/Wandering_Vagrant">Wandering Vagrant</a> (just dodge the head and projectiles respectively).</p> -<p>The first thing you must do is always <strong>rush for the teleporter</strong>. Completing the levels quick will make the game easier. But make sure to take note of <strong>where the chests are</strong>! When you have time (even when the countdown finishes), go back for them and buy as many as you can. Generally, prefer <a href="http://riskofrain.wikia.com/wiki/Chest">chests</a> over <a href="http://riskofrain.wikia.com/wiki/Shrine">shrines</a> since they may eat all your money.</p> -<p>Completing the game on <a href="http://riskofrain.wikia.com/wiki/Difficulty">Drizzle</a> is really easy if you follow these tips.</p> -<h2 id="requisites">Requisites</h2> -<p>Before breaking the game, you must obtain several <a href="http://riskofrain.wikia.com/wiki/Item#Artifacts">artifacts</a>. We are interested in particular in the following:</p> -<ul> -<li><a href="http://riskofrain.wikia.com/wiki/Sacrifice">Sacrifice</a>. You really need this one, and may be a bit hard to get. With it, you will be able to farm the first level for 30 minutes and kill the final boss in 30 seconds.</li> -<li><a href="http://riskofrain.wikia.com/wiki/Command">Command</a>. You need this unless you want to grind for hours to get enough of the items you really need for the rest of the game. Getting this one is easy.</li> -<li><a href="http://riskofrain.wikia.com/wiki/Glass">Glass</a>. Your life will be very small (at the beginning…), but you will be able to one-shot everything easily.</li> -<li><a href="http://riskofrain.wikia.com/wiki/Kin">Kin</a> (optional). It makes it easier to obtain a lot of boxes if you restart the first level until you get <a href="http://riskofrain.wikia.com/wiki/Lemurian">lemurians</a> or <a href="http://riskofrain.wikia.com/wiki/Jellyfish">jellyfish</a> as the monster, since they're cheap to spawn.</li> -</ul> -<p>With those, the game becomes trivial. Playing as <a href="http://riskofrain.wikia.com/wiki/Huntress">Huntress</a> is excellent since she can move at high speed while killing everything on screen.</p> -<h2 id="breaking-the-game">Breaking the Game</h2> -<p>The rest is easy! With the command artifact you want the following items.</p> -<h3 id="common-items"><a href="http://riskofrain.wikia.com/wiki/Category:Common_Items">Common Items</a></h3> -<ul> -<li><a href="http://riskofrain.wikia.com/wiki/Soldier&#x27;s_Syringe">Soldier's Syringe</a>. <strong>Stack 13</strong> of these and you will triple your attack speed. You can get started with 4 or so.</li> -<li><a href="http://riskofrain.wikia.com/wiki/Paul&#x27;s_Goat_Hoof">Paul's Goat Hoof</a>. <strong>Stack +30</strong> of these and your movement speed will be insane. You can get a very good speed with 8 or so.</li> -<li><a href="http://riskofrain.wikia.com/wiki/Crowbar">Crowbar</a>. <strong>Stack +20</strong> to guarantee you can one-shot bosses.</li> -</ul> -<p>If you want to be safer:</p> -<ul> -<li><a href="http://riskofrain.wikia.com/wiki/Hermit&#x27;s_Scarf">Hermit's Scarf</a>. <strong>Stack 6</strong> of these to dodge 1/3 of the attacks.</li> -<li><a href="http://riskofrain.wikia.com/wiki/Monster_Tooth">Monster Tooth</a>. <strong>Stack 9</strong> of these to recover 50 life on kill. This is plenty, since you will be killing <em>a lot</em>.</li> -</ul> -<p>If you don't have enough and want more fun, get one of these:</p> -<ul> -<li><a href="http://riskofrain.wikia.com/wiki/Gasoline">Gasoline</a>. Burn the ground on kill, and more will die!</li> -<li><a href="http://riskofrain.wikia.com/wiki/Headstompers">Headstompers</a>. They make a pleasing sound on fall, and hurt.</li> -<li><a href="http://riskofrain.wikia.com/wiki/Lens-Maker&#x27;s_Glasses">Lens-Maker's Glasses</a>. <strong>Stack 14</strong> and you will always deal a critical strike for double the damage.</li> -</ul> -<h3 id="uncommon-items"><a href="http://riskofrain.wikia.com/wiki/Category:Uncommon_Items">Uncommon Items</a></h3> -<ul> -<li><a href="http://riskofrain.wikia.com/wiki/Infusion">Infusion</a>. You only really need one of this. Your life will skyrocket after a while, since this gives you 1HP per kill.</li> -<li><a href="http://riskofrain.wikia.com/wiki/Hopoo_Feather">Hopoo Feather</a>. <strong>Stack +10</strong> of these. You will pretty much be able to fly with so many jumps.</li> -<li><a href="http://riskofrain.wikia.com/wiki/Guardian&#x27;s_Heart">Guardian's Heart</a>. Not really necessary, but useful for early and late game, since it will absorb infinite damage the first hit.</li> -</ul> -<p>If, again, you want more fun, get one of these:</p> -<ul> -<li><a href="http://riskofrain.wikia.com/wiki/Ukulele">Ukelele</a>. Spazz your enemies!</li> -<li><a href="http://riskofrain.wikia.com/wiki/Will-o&#x27;-the-wisp">Will-o'-the-wisp</a>. Explode your enemies!</li> -<li><a href="http://riskofrain.wikia.com/wiki/Chargefield_Generator">Chargefield Generator</a>. It should cover your entire screen after a bit, hurting all enemies without moving a finger.</li> -<li><a href="http://riskofrain.wikia.com/wiki/Golden_Gun">Golden Gun</a>. You will be rich, so this gives you +40% damage.</li> -<li><a href="http://riskofrain.wikia.com/wiki/Predatory_Instincts">Predatory Instincts</a>. If you got 14 glasses, you will always be doing critical strikes, and this will give even more attack speed.</li> -<li><a href="http://riskofrain.wikia.com/wiki/56_Leaf_Clover">56 Leaf Clover</a>. More drops, in case you didn't have enough.</li> -</ul> -<h3 id="rare-items"><a href="http://riskofrain.wikia.com/wiki/Category:Rare_Items">Rare Items</a></h3> -<ul> -<li><a href="http://riskofrain.wikia.com/wiki/Ceremonial_Dagger">Ceremonial Dagger</a>. <strong>Stack +3</strong>, then killing one thing kills another thing and makes a chain reaction.</li> -<li><a href="http://riskofrain.wikia.com/wiki/Alien_Head">Alien Head</a>. <strong>Stack 3</strong>, and you will be able to use your abilities more often.</li> -</ul> -<p>For more fun:</p> -<ul> -<li><a href="http://riskofrain.wikia.com/wiki/Brilliant_Behemoth">Brilliant Behemoth</a>. Boom boom.</li> -</ul> -<h2 id="closing-words">Closing Words</h2> -<p>You can now beat the game in Monsoon solo with any character. Have fun! And be careful with the sadly common crashes.</p> -</content> - </entry> - <entry xml:lang="en"> - <title>WorldEdit Commands</title> - <published>2018-07-11T00:00:00+00:00</published> - <updated>2018-07-11T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/world-edit/" type="text/html"/> - <id>https://lonami.dev/blog/world-edit/</id> - <content type="html"><p><a href="https://dev.bukkit.org/projects/worldedit">WorldEdit</a> is an extremely powerful tool for modifying entire worlds within <a href="https://minecraft.net">Minecraft</a>, which can be used as either a mod for your single-player worlds or as a plugin for your <a href="https://getbukkit.org/">Bukkit</a> servers.</p> -<p>This command guide was written for Minecraft 1.12.1, version <a href="https://dev.bukkit.org/projects/worldedit/files/2460562">6.1.7.3</a>, but should work for newer versions too. All WorldEdit commands can be used with a double slash (<code>//</code>) so they don't conlict with built-in commands. This means you can get a list of all commands with <code>//help</code>. Let's explore different categories!</p> -<h2 id="movement">Movement</h2> -<p>In order to edit a world properly you need to learn how to move in said world properly. There are several straightforward commands that let you move:</p> -<ul> -<li><code>//ascend</code> goes up one floor.</li> -<li><code>//descend</code> goes down one floor.</li> -<li><code>//thru</code> let's you pass through walls.</li> -<li><code>//jumpto</code> to go wherever you are looking.</li> -</ul> -<h2 id="information">Information</h2> -<p>Knowing your world properly is as important as knowing how to move within it, and will also let you change the information in said world if you need to.</p> -<ul> -<li><code>//biomelist</code> shows all known biomes.</li> -<li><code>//biomeinfo</code> shows the current biome.</li> -<li><code>//setbiome</code> lets you change the biome.</li> -</ul> -<h2 id="blocks">Blocks</h2> -<p>You can act over all blocks in a radius around you with quite a few commands. Some won't actually act over the entire range you specify, so 100 is often a good number.</p> -<h3 id="filling">Filling</h3> -<p>You can fill pools with <code>//fill water 100</code> or caves with <code>//fillr water 100</code>, both of which act below your feet.</p> -<h3 id="fixing">Fixing</h3> -<p>If the water or lava is buggy use <code>//fixwater 100</code> or <code>//fixlava 100</code> respectively.</p> -<p>Some creeper removed the snow or the grass? Fear not, you can use <code>//snow 10</code> or <code>//grass 10</code>.</p> -<h3 id="emptying">Emptying</h3> -<p>You can empty a pool completely with <code>//drain 100</code>, remove the snow with <code>//thaw 10</code>, and remove fire with <code>//ex 10</code>.</p> -<h3 id="removing">Removing</h3> -<p>You can remove blocks above and below you in some area with the <code>//removeabove N</code> and <code>//removebelow N</code>. You probably want to set a limit though, or you could fall off the world with <code>//removebelow 1 10</code> for radius and depth. You can also remove near blocks with <code>//removenear block 10</code>.</p> -<h3 id="shapes">Shapes</h3> -<p>Making a cylinder (or circle) can be done with through <code>//cyl stone 10</code>, a third argument for the height. The radius can be comma-separated to make a ellipses instead, such as <code>//cyl stone 5,10</code>.</p> -<p>Spheres are done with <code>//sphere stone 5</code>. This will build one right at your center, so you can raise it to be on your feet with <code>//sphere stone 5 yes</code>. Similar to cylinders, you can comma separate the radius <code>x,y,z</code>.</p> -<p>Pyramids can be done with <code>//pyramic stone 5</code>.</p> -<p>All these commands can be prefixed with &quot;h&quot; to make them hollow. For instance, <code>//hsphere stone 10</code>.</p> -<h2 id="regions">Regions</h2> -<h3 id="basics">Basics</h3> -<p>Operating over an entire region is really important, and the first thing you need to work comfortably with them is a tool to make selections. The default wooden-axe tool can be obtained with <code>//wand</code>, but you must be near the blocks to select. You can use a different tool, like a golden axe, to use as your &quot;far wand&quot; (wand usable over distance). Once you have one in your hand type <code>//farwand</code> to use it as your &quot;far wand&quot;. You can select the two corners of your region with left and right click. If you have selected the wrong tool, use <code>//none</code> to clear it.</p> -<p>If there are no blocks but you want to use your current position as a corner, use <code>//pos1</code> or 2.</p> -<p>If you made a region too small, you can enlarge it with <code>//expand 10 up</code>, or <code>//expand vert</code> for the entire vertical range, etc., or make it smaller with <code>//contract 10 up</code> etc., or <code>//inset</code> it to contract in both directions. You can use short-names for the cardinal directions (NSEW).</p> -<p>Finally, if you want to move your selection, you can <code>//shift 1 north</code> it to wherever you need.</p> -<h3 id="information-1">Information</h3> -<p>You can get the <code>//size</code> of the selection or even <code>//count torch</code> in some area. If you want to count all blocks, get their distribution <code>//distr</code>.</p> -<h3 id="filling-1">Filling</h3> -<p>With a region selected, you can <code>//set</code> it to be any block! For instance, you can use <code>//set air</code> to clear it entirely. You can use more than one block evenly by separting them with a comma <code>//set stone,dirt</code>, or with a custom chance <code>//set 20%stone,80%dirt</code>.</p> -<p>You can use <code>//replace from to</code> instead if you don't want to override all blocks in your selection.</p> -<p>You can make an hollow set with <code>//faces</code>, and if you just want the walls, use <code>//walls</code>.</p> -<h3 id="cleaning">Cleaning</h3> -<p>If someone destroyed your wonderful snow landscape, fear not, you can use <code>//overlay snow</code> over it (although for this you actually have <code>//snow N</code> and its opposite <code>//thaw</code>).</p> -<p>If you set some rough area, you can always <code>//smooth</code> it, even more than one time with <code>//smooth 3</code>. You can get your dirt and stone back with <code>//naturalize</code> and put some plants with <code>//flora</code> or <code>//forest</code>, both of which support a density or even the type for the trees. If you already have the dirt use <code>//green</code> instead. If you want some pumpkins, with <code>//pumpkins</code>.</p> -<h3 id="moving">Moving</h3> -<p>You can repeat an entire selection many times by stacking them with <code>//stack N DIR</code>. This is extremely useful to make things like corridors or elevators. For instance, you can make a small section of the corridor, select it entirely, and then repeat it 10 times with <code>//stack 10 north</code>. Or you can make the elevator and then <code>//stack 10 up</code>. If you need to also copy the air use <code>//stackair</code>.</p> -<p>Finally, if you don't need to repeat it and simply move it just a bit towards the right direction, you can use <code>//move N</code>. The default direction is &quot;me&quot; (towards where you are facing) but you can set one with <code>//move 1 up</code> for example.</p> -<h3 id="selecting">Selecting</h3> -<p>You can not only select cuboids. You can also select different shapes, or even just points:</p> -<ul> -<li><code>//sel cuboid</code> is the default.</li> -<li><code>//sel extend</code> expands the default.</li> -<li><code>//sel poly</code> first point with left click and right click to add new points.</li> -<li><code>//sel ellipsoid</code> first point to select the center and right click to select the different radius.</li> -<li><code>//sel sphere</code> first point to select the center and one more right click for the radius.</li> -<li><code>//sel cyl</code> for cylinders, first click being the center.</li> -<li><code>//sel convex</code> for convex shapes. This one is extremely useful for <code>//curve</code>.</li> -</ul> -<h2 id="brushes">Brushes</h2> -<p>Brushes are a way to paint in 3D without first bothering about making a selection, and there are spherical and cylinder brushes with e.g. <code>//brush sphere stone 2</code>, or the shorter form <code>//br s stone</code>. For cylinder, one must use <code>cyl</code> instead <code>sphere</code>.</p> -<p>There also exists a brush to smooth the terrain which can be enabled on the current item with <code>//br smooth</code>, which can be used with right-click like any other brush.</p> -<h2 id="clipboard">Clipboard</h2> -<p>Finally, you can copy and cut things around like you would do with normal text with <code>//copy</code> and <code>//cut</code>. The copy is issued from wherever you issue the command, so when you use <code>//paste</code>, remember that if you were 4 blocks apart when copying, it will be 4 blocks apart when pasting.</p> -<p>The contents of the clipboard can be flipped to wherever you are looking via <code>//flip</code>, and can be rotated via the <code>//rotate 90</code> command (in degrees).</p> -<p>To remove the copy use <code>//clearclipboard</code>.</p> -</content> - </entry> - <entry xml:lang="en"> - <title>An Introduction to Asyncio</title> - <published>2018-06-13T00:00:00+00:00</published> - <updated>2020-10-03T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/asyncio/" type="text/html"/> - <id>https://lonami.dev/blog/asyncio/</id> - <content type="html"><h2 id="index">Index</h2> -<ul> -<li><a href="https://lonami.dev/blog/asyncio/#background">Background</a></li> -<li><a href="https://lonami.dev/blog/asyncio/#input_output">Input / Output</a></li> -<li><a href="https://lonami.dev/blog/asyncio/#diving_in">Diving In</a></li> -<li><a href="https://lonami.dev/blog/asyncio/#a_toy_example">A Toy Example</a></li> -<li><a href="https://lonami.dev/blog/asyncio/#a_real_example">A Real Example</a></li> -<li><a href="https://lonami.dev/blog/asyncio/#extra_material">Extra Material</a></li> -</ul> -<h2 id="background">Background</h2> -<p>After seeing some friends struggle with <code>asyncio</code> I decided that it could be a good idea to write a blog post using my own words to explain how I understand the world of asynchronous IO. I will focus on Python's <code>asyncio</code> module but this post should apply to any other language easily.</p> -<p>So what is <code>asyncio</code> and what makes it good? Why don't we just use the old and known threads to run several parts of the code concurrently, at the same time?</p> -<p>The first reason is that <code>asyncio</code> makes your code easier to reason about, as opposed to using threads, because the amount of ways in which your code can run grows exponentially. Let's see that with an example. Imagine you have this code:</p> -<pre><code class="language-python" data-lang="python">def method(): - line 1 - line 2 - line 3 - line 4 - line 5 -</code></pre> -<p>And you start two threads to run the method at the same time. What is the order in which the lines of code get executed? The answer is that you can't know! The first thread can run the entire method before the second thread even starts. Or it could be the first thread that runs after the second thread. Perhaps both run the &quot;line 1&quot;, and then the line 2. Maybe the first thread runs lines 1 and 2, and then the second thread only runs the line 1 before the first thread finishes.</p> -<p>As you can see, any combination of the order in which the lines run is possible. If the lines modify some global shared state, that will get messy quickly.</p> -<p>Second, in Python, threads <em>won't</em> make your code faster most of the time. It will only increase the concurrency of your program (which is okay if it makes many blocking calls), allowing you to run several things at the same time.</p> -<p>If you have a lot of CPU work to do though, threads aren't a real advantage. Indeed, your code will probably run slower under the most common Python implementation, CPython, which makes use of a Global Interpreter Lock (GIL) that only lets a thread run at once. The operations won't run in parallel!</p> -<h2 id="input-output">Input / Output</h2> -<p>Before we go any further, let's first stop to talk about input and output, commonly known as &quot;IO&quot;. There are two main ways to perform IO operations, such as reading or writing from a file or a network socket.</p> -<p>The first one is known as &quot;blocking IO&quot;. What this means is that, when you try performing IO, the current application thread is going to <em>block</em> until the Operative System can tell you it's done. Normally, this is not a problem, since disks are pretty fast anyway, but it can soon become a performance bottleneck. And network IO will be much slower than disk IO!</p> -<pre><code class="language-python" data-lang="python">import socket - -# Setup a network socket and a very simple HTTP request. -# By default, sockets are open in blocking mode. -sock = socket.socket() -request = b'''HEAD / HTTP/1.0\r -Host: example.com\r -\r -''' - -# &quot;connect&quot; will block until a successful TCP connection -# is made to the host &quot;example.com&quot; on port 80. -sock.connect(('example.com', 80)) - -# &quot;sendall&quot; will repeatedly call &quot;send&quot; until all the data in &quot;request&quot; is -# sent to the host we just connected, which blocks until the data is sent. -sock.sendall(request) - -# &quot;recv&quot; will try to receive up to 1024 bytes from the host, and block until -# there is any data to receive (or empty if the host closes the connection). -response = sock.recv(1024) - -# After all those blocking calls, we got out data! These are the headers from -# making a HTTP request to example.com. -print(response.decode()) -</code></pre> -<p>Blocking IO offers timeouts, so that you can get control back in your code if the operation doesn't finish. Imagine that the remote host doesn't want to reply, your code would be stuck for as long as the connection remains alive!</p> -<p>But wait, what if we make the timeout small? Very, very small? If we do that, we will never block waiting for an answer. That's how asynchronous IO works, and it's the opposite of blocking IO (you can also call it non-blocking IO if you want to).</p> -<p>How does non-blocking IO work if the IO device needs a while to answer with the data? In that case, the operative system responds with &quot;not ready&quot;, and your application gets control back so it can do other stuff while the IO device completes your request. It works a bit like this:</p> -<pre><code>&lt;app&gt; Hey, I would like to read 16 bytes from this file -&lt;OS&gt; Okay, but the disk hasn't sent me the data yet -&lt;app&gt; Alright, I will do something else then -(a lot of computer time passes) -&lt;app&gt; Do you have my 16 bytes now? -&lt;OS&gt; Yes, here they are! &quot;Hello, world !!\n&quot; -</code></pre> -<p>In reality, you can tell the OS to notify you when the data is ready, as opposed to polling (constantly asking the OS whether the data is ready yet or not), which is more efficient.</p> -<p>But either way, that's the difference between blocking and non-blocking IO, and what matters is that your application gets to run more without ever needing to wait for data to arrive, because the data will be there immediately when you ask, and if it's not yet, your app can do more things meanwhile.</p> -<h2 id="diving-in">Diving In</h2> -<p>Now we've seen what blocking and non-blocking IO is, and how threads make your code harder to reason about, but they give concurrency (yet not more speed). Is there any other way to achieve this concurrency that doesn't involve threads? Yes! The answer is <code>asyncio</code>.</p> -<p>So how does <code>asyncio</code> help? First we need to understand a very crucial concept before we can dive any deeper, and I'm talking about the <em>event loop</em>. What is it and why do we need it?</p> -<p>You can think of the event loop as a <em>loop</em> that will be responsible for calling your <code>async</code> functions:</p> -<p><img src="https://lonami.dev/blog/asyncio/eventloop.svg" alt="The Event Loop" /></p> -<p>That's silly you may think. Now not only we run our code but we also have to run some &quot;event loop&quot;. It doesn't sound beneficial at all. What are these events? Well, they are the IO events we talked about before!</p> -<p><code>asyncio</code>'s event loop is responsible for handling those IO events, such as file is ready, data arrived, flushing is done, and so on. As we saw before, we can make these events non-blocking by setting their timeout to 0.</p> -<p>Let's say you want to read from 10 files at the same time. You will ask the OS to read data from 10 files, and at first none of the reads will be ready. But the event loop will be constantly asking the OS to know which are done, and when they are done, you will get your data.</p> -<p>This has some nice advantages. It means that, instead of waiting for a network request to send you a response or some file, instead of blocking there, the event loop can decide to run other code meanwhile. Whenever the contents are ready, they can be read, and your code can continue. Waiting for the contents to be received is done with the <code>await</code> keyword, and it tells the loop that it can run other code meanwhile:</p> -<p><img src="https://lonami.dev/blog/asyncio/awaitkwd1.svg" alt="Step 1, await keyword" /></p> -<p><img src="https://lonami.dev/blog/asyncio/awaitkwd2.svg" alt="Step 2, await keyword" /></p> -<p>Start reading the code of the event loop and follow the arrows. You can see that, in the beginning, there are no events yet, so the loop calls one of your functions. The code runs until it has to <code>await</code> for some IO operation to complete, such as sending a request over the network. The method is &quot;paused&quot; until an event occurs (for example, an &quot;event&quot; occurs when the request has been sent completely).</p> -<p>While the first method is busy, the event loop can enter the second method, and run its code until the first <code>await</code>. But it can happen that the event of the second query occurs before the request on the first method, so the event loop can re-enter the second method because it has already sent the query, but the first method isn't done sending the request yet.</p> -<p>Then, the second method <code>await</code>'s for an answer, and an event occurs telling the event loop that the request from the first method was sent. The code can be resumed again, until it has to <code>await</code> for a response, and so on. Here's an explanation with pseudo-code for this process if you prefer:</p> -<pre><code class="language-python" data-lang="python">async def method(request): - prepare request - await send request - - await receive request - - process request - return result - -run in parallel ( - method with request 1, - method with request 2, -) -</code></pre> -<p>This is what the event loop will do on the above pseudo-code:</p> -<pre><code>no events pending, can advance - -enter method with request 1 - prepare request - await sending request -pause method with request 1 - -no events ready, can advance - -enter method with request 2 - prepare request - await sending request -pause method with request 2 - -both requests are paused, cannot advance -wait for events -event for request 2 arrives (sending request completed) - -enter method with request 2 - await receiving response -pause method with request 2 - -event for request 1 arrives (sending request completed) - -enter method with request 1 - await receiving response -pause method with request 1 - -...and so on -</code></pre> -<p>You may be wondering &quot;okay, but threads work for me, so why should I change?&quot;. There are some important things to note here. The first is that we only need one thread to be running! The event loop decides when and which methods should run. This results in less pressure for the operating system. The second is that we know when it may run other methods. Those are the <code>await</code> keywords! Whenever there is one of those, we know that the loop is able to run other things until the resource (again, like network) becomes ready (when a event occurs telling us it's ready to be used without blocking or it has completed).</p> -<p>So far, we already have two advantages. We are only using a single thread so the cost for switching between methods is low, and we can easily reason about where our program may interleave operations.</p> -<p>Another advantage is that, with the event loop, you can easily schedule when a piece of code should run, such as using the method <a href="https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.call_at"><code>loop.call_at</code></a>, without the need for spawning another thread at all.</p> -<p>To tell the <code>asyncio</code> to run the two methods shown above, we can use <a href="https://docs.python.org/3/library/asyncio-future.html#asyncio.ensure_future"><code>asyncio.ensure_future</code></a>, which is a way of saying &quot;I want the future of my method to be ensured&quot;. That is, you want to run your method in the future, whenever the loop is free to do so. This method returns a <code>Future</code> object, so if your method returns a value, you can <code>await</code> this future to retrieve its result.</p> -<p>What is a <code>Future</code>? This object represents the value of something that will be there in the future, but might not be there yet. Just like you can <code>await</code> your own <code>async def</code> functions, you can <code>await</code> these <code>Future</code>'s.</p> -<p>The <code>async def</code> functions are also called &quot;coroutines&quot;, and Python does some magic behind the scenes to turn them into such. The coroutines can be <code>await</code>'ed, and this is what you normally do.</p> -<h2 id="a-toy-example">A Toy Example</h2> -<p>That's all about <code>asyncio</code>! Let's wrap up with some example code. We will create a server that replies with the text a client sends, but reversed. First, we will show what you could write with normal synchronous code, and then we will port it.</p> -<p>Here is the <strong>synchronous version</strong>:</p> -<pre><code class="language-python" data-lang="python"># server.py -import socket - - -def server_method(): - # create a new server socket to listen for connections - server = socket.socket() - - # bind to localhost:6789 for new connections - server.bind(('localhost', 6789)) - - # we will listen for one client at most - server.listen(1) - - # *block* waiting for a new client - client, _ = server.accept() - - # *block* waiting for some data - data = client.recv(1024) - - # reverse the data - data = data[::-1] - - # *block* sending the data - client.sendall(data) - - # close client and server - server.close() - client.close() - - -if __name__ == '__main__': - # block running the server - server_method() -</code></pre> -<pre><code class="language-python" data-lang="python"># client.py -import socket - - -def client_method(): - message = b'Hello Server!\n' - client = socket.socket() - - # *block* trying to stabilish a connection - client.connect(('localhost', 6789)) - - # *block* trying to send the message - print('Sending', message) - client.sendall(message) - - # *block* until we receive a response - response = client.recv(1024) - print('Server replied', response) - - client.close() - - -if __name__ == '__main__': - client_method() -</code></pre> -<p>From what we've seen, this code will block on all the lines with a comment above them saying that they will block. This means that for running more than one client or server, or both in the same file, you will need threads. But we can do better, we can rewrite it into <code>asyncio</code>!</p> -<p>The first step is to mark all your <code>def</code>initions that may block with <code>async</code>. This marks them as coroutines, which can be <code>await</code>ed on.</p> -<p>Second, since we're using low-level sockets, we need to make use of the methods that <code>asyncio</code> provides directly. If this was a third-party library, this would be just like using their <code>async def</code>initions.</p> -<p>Here is the <strong>asynchronous version</strong>:</p> -<pre><code class="language-python" data-lang="python"># server.py -import asyncio -import socket - -# get the default &quot;event loop&quot; that we will run -loop = asyncio.get_event_loop() - - -# notice our new &quot;async&quot; before the definition -async def server_method(): - server = socket.socket() - server.bind(('localhost', 6789)) - server.listen(1) - - # await for a new client - # the event loop can run other code while we wait here! - client, _ = await loop.sock_accept(server) - - # await for some data - data = await loop.sock_recv(client, 1024) - data = data[::-1] - - # await for sending the data - await loop.sock_sendall(client, data) - - server.close() - client.close() - - -if __name__ == '__main__': - # run the loop until &quot;server method&quot; is complete - loop.run_until_complete(server_method()) -</code></pre> -<pre><code class="language-python" data-lang="python"># client.py -import asyncio -import socket - -loop = asyncio.get_event_loop() - - -async def client_method(): - message = b'Hello Server!\n' - client = socket.socket() - - # await to stabilish a connection - await loop.sock_connect(client, ('localhost', 6789)) - - # await to send the message - print('Sending', message) - await loop.sock_sendall(client, message) - - # await to receive a response - response = await loop.sock_recv(client, 1024) - print('Server replied', response) - - client.close() - - -if __name__ == '__main__': - loop.run_until_complete(client_method()) -</code></pre> -<p>That's it! You can place these two files separately and run, first the server, then the client. You should see output in the client.</p> -<p>The big difference here is that you can easily modify the code to run more than one server or clients at the same time. Whenever you <code>await</code> the event loop will run other of your code. It seems to &quot;block&quot; on the <code>await</code> parts, but remember it's actually jumping to run more code, and the event loop will get back to you whenever it can.</p> -<p>In short, you need an <code>async def</code> to <code>await</code> things, and you run them with the event loop instead of calling them directly. So this…</p> -<pre><code class="language-python" data-lang="python">def main(): - ... # some code - - -if __name__ == '__main__': - main() -</code></pre> -<p>…becomes this:</p> -<pre><code class="language-python" data-lang="python">import asyncio - - -async def main(): - ... # some code - - -if __name__ == '__main__': - asyncio.get_event_loop().run_until_complete(main) -</code></pre> -<p>This is pretty much how most of your <code>async</code> scripts will start, running the main method until its completion.</p> -<h2 id="a-real-example">A Real Example</h2> -<p>Let's have some fun with a real library. We'll be using <a href="https://github.com/LonamiWebs/Telethon">Telethon</a> to broadcast a message to our three best friends, all at the same time, thanks to the magic of <code>asyncio</code>. We'll dive right into the code, and then I'll explain our new friend <code>asyncio.wait(...)</code>:</p> -<pre><code class="language-python" data-lang="python"># broadcast.py -import asyncio -import sys - -from telethon import TelegramClient - -# (you need your own values here, check Telethon's documentation) -api_id = 123 -api_hash = '123abc' -friends = [ - '@friend1__username', - '@friend2__username', - '@bestie__username' -] - -# we will have to await things, so we need an async def -async def main(message): - # start is a coroutine, so we need to await it to run it - client = await TelegramClient('me', api_id, api_hash).start() - - # wait for all three client.send_message to complete - await asyncio.wait([ - client.send_message(friend, message) - for friend in friends - ]) - - # and close our client - await client.disconnect() - - -if __name__ == '__main__': - if len(sys.argv) != 2: - print('You must pass the message to broadcast!') - quit() - - message = sys.argv[1] - asyncio.get_event_loop().run_until_complete(main(message)) -</code></pre> -<p>Wait… how did that send a message to all three of -my friends? The magic is done here:</p> -<pre><code class="language-python" data-lang="python">[ - client.send_message(friend, message) - for friend in friends -] -</code></pre> -<p>This list comprehension creates another list with three -coroutines, the three <code>client.send_message(...)</code>. -Then we just pass that list to <code>asyncio.wait</code>:</p> -<pre><code class="language-python" data-lang="python">await asyncio.wait([...]) -</code></pre> -<p>This method, by default, waits for the list of coroutines to run until they've all finished. You can read more on the Python <a href="https://docs.python.org/3/library/asyncio-task.html#asyncio.wait">documentation</a>. Truly a good function to know about!</p> -<p>Now whenever you have some important news for your friends, you can simply <code>python3 broadcast.py 'I bought a car!'</code> to tell all your friends about your new car! All you need to remember is that you need to <code>await</code> on coroutines, and you will be good. <code>asyncio</code> will warn you when you forget to do so.</p> -<h2 id="extra-material">Extra Material</h2> -<p>If you want to understand how <code>asyncio</code> works under the hood, I recommend you to watch this hour-long talk <a href="https://youtu.be/M-UcUs7IMIM">Get to grips with asyncio in Python 3</a> by Robert Smallshire. In the video, they will explain the differences between concurrency and parallelism, along with others concepts, and how to implement your own <code>asyncio</code> &quot;scheduler&quot; from scratch.</p> -</content> - </entry> - <entry xml:lang="en"> - <title>Atemporal Blog Posts</title> - <published>2018-02-03T00:00:00+00:00</published> - <updated>2021-02-19T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/posts/" type="text/html"/> - <id>https://lonami.dev/blog/posts/</id> - <content type="html"><p>These are some interesting posts and links I've found around the web. I believe they are quite interesting and nice reads, so if you have the time, I encourage you to check some out.</p> -<h2 id="algorithms">Algorithms</h2> -<ul> -<li><a href="http://www.tannerhelland.com/4660/dithering-eleven-algorithms-source-code/">Image Dithering: Eleven Algorithms and Source Code</a>. What does it mean and how to achieve it?</li> -<li><a href="https://cristian.io/post/bloom-filters/">Idempotence layer on bloom filters</a>. What are they and how can they help?</li> -<li><a href="https://en.wikipedia.org/wiki/Huffman_coding">Huffman coding</a>. This encoding is a simple yet interesting way of compressing information.</li> -<li><a href="https://github.com/mxgmn/WaveFunctionCollapse">Wave Function Collapse</a>. Bitmap &amp; tilemap generation from a single example with the help of ideas from quantum mechanics.</li> -<li><a href="https://blog.nelhage.com/2015/02/regular-expression-search-with-suffix-arrays/">Regular Expression Search with Suffix Arrays</a>. A way to efficiently search large amounts of text.</li> -</ul> -<h2 id="culture">Culture</h2> -<ul> -<li><a href="https://www.wired.com/story/ideas-joi-ito-robot-overlords/">Why Westerners Fear Robots and the Japanese Do Not</a>. Explains some possible reasons for this case.</li> -<li><a href="http://catb.org/%7Eesr/faqs/smart-questions.html">How To Ask Questions The Smart Way</a>. Some bits of hacker culture and amazing tips on how to ask a question.</li> -<li><a href="http://apenwarr.ca/log/?m=201809#14">XML, blockchains, and the strange shapes of progress</a>. Some of history about XML and blockchain.</li> -<li><a href="https://czep.net/17/legion-of-lobotomized-unices.html">Legion of lobotomized unices</a>. A time where computers are treated a lot more nicely.</li> -<li><a href="https://eli.thegreenplace.net/2016/the-expression-problem-and-its-solutions/">The Expression Problem and its solutions</a>. What is it and what can we do to solve it?</li> -<li><a href="http://allendowney.blogspot.com/2015/08/the-inspection-paradox-is-everywhere.html">The Inspection Paradox is Everywhere</a>. Interesting and very common phenomena.</li> -<li><a href="https://github.com/ChrisKnott/Algojammer">An experimental code editor for writing algorithms</a>. Contains several links to different tools for reverse debugging.</li> -<li><a href="http://habitatchronicles.com/2017/05/what-are-capabilities/">What Are Capabilities?</a> Good ideas with great security implications.</li> -<li><a href="https://blog.aurynn.com/2015/12/16-contempt-culture">Contempt Culture</a>. Or why you should not speak crap about your non-favourite programming languages.</li> -<li><a href="https://www.lesswrong.com/posts/tscc3e5eujrsEeFN4/well-kept-gardens-die-by-pacifism">Well-Kept Gardens Die By Pacifism</a>. Risks any online community can run into.</li> -<li><a href="https://ncase.me/">It's Nicky Case!</a> They make some cool things worth checking out, I really like &quot;we become what we behold&quot;.</li> -</ul> -<h2 id="debate">Debate</h2> -<ul> -<li><a href="https://steemit.com/opensource/@crell/open-source-is-awful">Open Source is awful</a>. Has some points about why is it bad and how it could improve.</li> -<li><a href="http://www.mondo2000.com/2018/01/17/pink-lexical-goop-dark-side-autocorrect/">Pink Lexical Goop: The Dark Side of Autocorrect</a>. It can shape how you think.</li> -<li><a href="http://blog.ploeh.dk/2015/08/03/idiomatic-or-idiosyncratic/">Idiomatic or idiosyncratic?</a> Can porting code constructs from other languages have a positive effect?</li> -<li><a href="https://gamasutra.com/view/news/169296/Indepth_Functional_programming_in_C.php">In-depth: Functional programming in C++</a>. Is it useful to bother with functional concepts in a language like C++?</li> -<li><a href="https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/">Notes on structured concurrency, or: Go statement considered harmful</a>.</li> -<li><a href="https://queue.acm.org/detail.cfm?id=3212479">C Is Not a Low-level Language</a>. Could there be alternative programming models designed for more specialized CPUs?</li> -</ul> -<h2 id="food-for-thought">Food for Thought</h2> -<ul> -<li><a href="https://www.hillelwayne.com/post/divide-by-zero/">1/0 = 0</a>. Explores why it makes sense to redefine mathemathics under some circumstances, and why it is possible to do so.</li> -<li><a href="https://jeremykun.com/2018/04/13/for-mathematicians-does-not-mean-equality/">For mathematicians, = does not mean equality</a>. What other definitions does the equal sign have?</li> -<li><a href="https://www.lesswrong.com/posts/2MD3NMLBPCqPfnfre/cached-thoughts">Cached Thoughts</a>. How is it possible that our brains work at all?</li> -<li><a href="http://tonsky.me/blog/disenchantment/">Software disenchantment</a>. Faster hardware and slower software is a trend. -<ul> -<li><a href="https://blackhole12.com/blog/software-engineering-is-bad-but-it-s-not-that-bad/">Software Engineering Is Bad, But That's Not Why</a>. This post has some good counterpoints to Software disenchantment.</li> -</ul> -</li> -<li><a href="http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/">What Color is Your Function?</a>. Spoiler: can we approach asynchronous IO better?</li> -<li><a href="https://hackernoon.com/im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5">I'm harvesting credit card numbers and passwords from your site</a>. A word of warning when mindlessly adding dependencies.</li> -<li><a href="https://medium.com/message/everything-is-broken-81e5f33a24e1">Everything Is Broken</a>. Some of the (probable) truths about our world.</li> -<li><a href="http://johnsalvatier.org/blog/2017/reality-has-a-surprising-amount-of-detail">Reality has a surprising amount of detail</a>.</li> -</ul> -<h2 id="funny">Funny</h2> -<ul> -<li><a href="http://thedailywtf.com/articles/We-Use-BobX">We Use BobX</a>. BobX.</li> -<li><a href="http://thedailywtf.com/articles/the-inner-json-effect">The Inner JSON Effect</a>. For some reason, custom languages are in.</li> -<li><a href="https://thedailywtf.com/articles/exponential-backup">Exponential Backup</a>. Far better than git.</li> -<li><a href="https://thedailywtf.com/articles/ITAPPMONROBOT">ITAPPMONROBOT</a>. Solving software problems with hardware.</li> -<li><a href="https://thedailywtf.com/articles/a-tapestry-of-threads">A Tapestry of Threads</a>. More threads must mean faster code, right?</li> -<li><a href="https://medium.com/commitlog/a-brief-totally-accurate-history-of-programming-languages-cd93ec806124">A Brief Totally Accurate History Of Programming Languages</a>. Don't take offense for it!</li> -</ul> -<h2 id="graphics">Graphics</h2> -<ul> -<li><a href="http://shaunlebron.github.io/visualizing-projections/">Visualizing Projections</a>. Small post about different projection methods.</li> -<li><a href="http://www.iquilezles.org/www/index.htm">Inigo Quilez :: fractals, computer graphics, mathematics, shaders, demoscene and more</a> A <em>lot</em> of useful and quality articles regarding computer graphics.</li> -</ul> -<h2 id="history">History</h2> -<ul> -<li><a href="https://twobithistory.org/2018/08/18/ada-lovelace-note-g.html">What Did Ada Lovelace's Program Actually Do?</a>. And other characters that took part in the beginning's of programming.</li> -<li><a href="https://chrisdown.name/2018/01/02/in-defence-of-swap.html">In defence of swap: common misconceptions</a>. Swap is still an useful concept.</li> -<li><a href="https://www.pacifict.com/Story/">The Graphing Calculator Story</a>. A great classic Apple tale.</li> -<li><a href="https://twobithistory.org/2018/10/14/lisp.html">How Lisp Became God's Own Programming Language</a>. Lisp as a foundational programming language.</li> -</ul> -<h2 id="motivational">Motivational</h2> -<ul> -<li><a href="https://www.joelonsoftware.com/2002/01/06/fire-and-motion/">Fire And Motion</a>. What does actually take to get things done?</li> -<li><a href="https://realmensch.org/2017/08/25/the-parable-of-the-two-programmers/">The Parable of the Two Programmers</a>. This tale is about two different types of programmer and their respective endings in a company, illustrating how the one you wouldn't expect to actually ends in a better situation.</li> -<li><a href="https://byorgey.wordpress.com/2018/05/06/conversations-with-a-six-year-old-on-functional-programming/">Conversations with a six-year-old on functional programming</a>. Little kids today can be really interested in technological topics.</li> -<li><a href="https://bulletproofmusician.com/how-many-hours-a-day-should-you-practice/">How Many Hours a Day Should You Practice?</a>. While the article is about music, it applies to any other areas.</li> -<li><a href="http://nathanmarz.com/blog/suffering-oriented-programming.html">Suffering-oriented programming</a>. A possibly new approach on how you could tackle your new projects.</li> -<li><a href="https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/">Things You Should Never Do, Part I</a>. There is no need to rewrite your code.</li> -</ul> -<h2 id="optimization">Optimization</h2> -<ul> -<li><a href="http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html">What Every C Programmer Should Know About Undefined Behavior #1/3</a>. Explains what undefined behaviour is and why it makes sense.</li> -<li><a href="http://ridiculousfish.com/blog/posts/labor-of-division-episode-i.html">Labor of Division (Episode I)</a>. Some tricks to divide without division.</li> -<li><a href="http://blog.moertel.com/posts/2013-12-14-great-old-timey-game-programming-hack.html">A Great Old-Timey Game-Programming Hack</a>. Abusing instructions to make games playable even on the slowest hardware.</li> -<li><a href="https://web.archive.org/web/20191213224640/https://people.eecs.berkeley.edu/%7Esangjin/2012/12/21/epoll-vs-kqueue.html">Scalable Event Multiplexing: epoll vs kqueue</a>. How good OS primitives can really help performance and scability.</li> -<li><a href="https://adamdrake.com/command-line-tools-can-be-235x-faster-than-your-hadoop-cluster.html">Command-line Tools can be 235x Faster than your Hadoop Cluster</a>. Or how to use the right tool for the right job.</li> -<li><a href="https://nullprogram.com/blog/2018/05/27/">When FFI Function Calls Beat Native C</a>. How lua beat C at it and the explanation behind it.</li> -<li><a href="http://igoro.com/archive/gallery-of-processor-cache-effects/">Gallery of Processor Cache Effects</a>. Knowing a few things about the cache can make a big difference.</li> -</ul> -</content> - </entry> - <entry xml:lang="en"> - <title>Graphs</title> - <published>2017-06-02T00:00:00+00:00</published> - <updated>2017-06-02T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/graphs/" type="text/html"/> - <id>https://lonami.dev/blog/graphs/</id> - <content type="html"><p><noscript>There are a few things which won't render unless you enable -JavaScript. No tracking, I promise!</noscript></p> -<blockquote> -<p>Don't know English? <a href="https://lonami.dev/blog/graphs/spanish.html">Read the Spanish version instead</a>.</p> -</blockquote> -<p>Let's imagine we have 5 bus stations, which we'll denote by ((s_i)):</p> -<div class="matrix"> - ' s_1 ' s_2 ' s_3 ' s_4 ' s_5 \\ -s_1 ' ' V ' ' ' \\ -s_2 ' V ' ' ' ' V \\ -s_3 ' ' ' ' V ' \\ -s_4 ' ' V ' V ' ' \\ -s_5 ' V ' ' ' V ' -</div> -<p>This is known as a &quot;table of direct interconnections&quot;. -The ((V)) represent connected paths. For instance, on the first -row starting at ((s_1)), reaching the ((V)), -allows us to turn up to get to ((s_2)).</p> -<p>We can see the above table represented in a more graphical way:</p> -<p><img src="https://lonami.dev/blog/graphs/example1.svg" alt="Table 1 as a Graph" /></p> -<p>This type of graph is called, well, a graph, and it's a directed -graph (or digraph), since the direction on which the arrows go does -matter. It's made up of vertices, joined together by edges (also known as -lines or directed arcs).</p> -<p>One can walk from a node to another through different paths. For -example, ((s_4 $rightarrow s_2 $rightarrow s_5)) is an indirect path of order -two, because we must use two edges to go from ((s_4)) to -((s_5)).</p> -<p>Let's now represent its adjacency matrix called A which represents the -same table, but uses 1 instead V to represent -a connection:</p> -<div class="matrix"> - 0 ' 1 ' 0 ' 0 ' 0 \\ - 1 ' 0 ' 0 ' 0 ' 1 \\ - 0 ' 0 ' 0 ' 1 ' 0 \\ - 0 ' 1 ' 1 ' 0 ' 0 \\ - 1 ' 0 ' 0 ' 1 ' 0 -</div> -<p>This way we can see how the ((a_{2,1})) element represents the -connection ((s_2 $rightarrow s_1)), and the ((a_{5,1})) element the -((s_5 $rightarrow s_1)) connection, etc.</p> -<p>In general, ((a_{i,j})) represents a connection from -((s_i $rightarrow s_j))as long as ((a_{i,j}$geq 1)).</p> -<p>Working with matrices allows us to have a computable representation of -any graph, which is very useful.</p> -<hr /> -<p>Graphs have a lot of interesting properties besides being representable -by a computer. What would happen if, for instance, we calculated -((A^2))? We obtain the following matrix:</p> -<div class="matrix"> -1 ' 0 ' 0 ' 0 ' 1 \\ -1 ' 1 ' 0 ' 1 ' 0 \\ -0 ' 1 ' 1 ' 0 ' 0 \\ -1 ' 0 ' 0 ' 1 ' 1 \\ -0 ' 2 ' 1 ' 0 ' 0 -</div> -<p>We can interpret this as the paths of order two. -But what does the element ((a_{5,2}=2)) represent? It indicates -the amount of possible ways to go from ((s_5 $rightarrow s_i $rightarrow s_2)).</p> -<p>One can manually multiply the involved row and column to determine which -element is the one we need to pass through, this way we have the row -(([1 0 0 1 0])) and the column (([1 0 0 1 0])) (on -vertical). The elements ((s_i$geq 1)) are ((s_1)) and -((s_4)). This is, we can go from ((s_5)) to -((s_2)) via ((s_5 $rightarrow s_1 $rightarrow s_2)) or via -((s_5 $rightarrow s_4 $rightarrow s_2)): -<img src="example2.svg" /></p> -<p>It's important to note that graphs to not consider self-connections, this -is, ((s_i $rightarrow s_i)) is not allowed; neither we work with multigraphs -here (those which allow multiple connections, for instance, an arbitrary -number ((n)) of times).</p> -<div class="matrix"> -1 ' 1 ' 0 ' 1 ' 0 \\ -1 ' 2 ' \textbf{1} ' 0 ' 1 \\ -1 ' 0 ' 0 ' 1 ' 1 \\ -1 ' 2 ' 1 ' 1 ' 0 \\ -2 ' 0 ' 0 ' 1 ' 2 -</div> -<p>We can see how the first ((1)) just appeared on the element -((a_{2,3})), which means that the shortest path to it is at least -of order three.</p> -<hr /> -<p>A graph is said to be strongly connected as long as there is a -way to reach all its elements.</p> -<p>We can see all the available paths until now by simply adding up all the -direct and indirect ways to reach a node, so for now, we can add -((A+A^2+A^3)) in such a way that:</p> -<div class="matrix"> -2 ' 2 ' 0 ' 1 ' 1 \\ -3 ' 3 ' 1 ' 1 ' 3 \\ -1 ' 1 ' 1 ' 2 ' 1 \\ -2 ' 3 ' 2 ' 2 ' 1 \\ -3 ' 2 ' 1 ' 2 ' 2 -</div> -<p>There isn't a connection between ((s_1)) and ((s_3)) yet. -If we were to calculate ((A^4)):</p> -<div class="matrix"> -1 ' 2 ' 1 ' ' \\ - ' ' ' ' \\ - ' ' ' ' \\ - ' ' ' ' \\ - ' ' ' ' -</div> -<p>We don't need to calculate anymore. We now know that the graph is -strongly connected!</p> -<hr /> -<p>Congratulations! You've completed this tiny introduction to graphs. -Now you can play around with them and design your own connections.</p> -<p>Hold the left mouse button on the above area and drag it down to create -a new node, or drag a node to this area to delete it.</p> -<p>To create new connections, hold the right mouse button on the node you -want to start with, and drag it to the node you want it to be connected to.</p> -<p>To delete the connections coming from a specific node, middle click it.</p> -<table><tr><td style="width:100%;"> - <button onclick="resetConnections()">Reset connections</button> - <button onclick="clearNodes()">Clear all the nodes</button> - <br /> - <br /> - <label for="matrixOrder">Show matrix of order:</label> - <input id="matrixOrder" type="number" min="1" max="5" - value="1" oninput="updateOrder()"> - <br /> - <label for="matrixAccum">Show accumulated matrix</label> - <input id="matrixAccum" type="checkbox" onchange="updateOrder()"> - <br /> - <br /> - <div> - <table id="matrixTable"></table> - </div> -</td><td> - <canvas id="canvas" width="400" height="400" oncontextmenu="return false;"> - Looks like your browser won't let you see this fancy example :( - </canvas> - <br /> -</td></tr></table> -<script src="tinyparser.js"></script> -<script src="enhancements.js"></script> -<script src="graphs.js"></script> -</content> - </entry> - <entry xml:lang="en"> - <title>Installing NixOS</title> - <published>2017-05-13T00:00:00+00:00</published> - <updated>2019-02-16T00:00:00+00:00</updated> - <link href="https://lonami.dev/blog/installing-nixos/" type="text/html"/> - <id>https://lonami.dev/blog/installing-nixos/</id> - <content type="html"><h2 id="update">Update</h2> -<p><em>Please see <a href="../installing_nixos_2/index.html">my followup post with NixOS</a> for a far better experience with it</em></p> -<hr /> -<p>Today I decided to install <a href="http://nixos.org/">NixOS</a> as a recommendation, a purely functional Linux distribution, since <a href="https://xubuntu.org/">Xubuntu</a> kept crashing. Here's my journey, and how I managed to install it from a terminal for the first time in my life. Steps aren't hard, but they may not seem obvious at first.</p> -<ul> -<li> -<p>Grab the Live CD, burn it on a USB stick and boot. I recommend using <a href="https://etcher.io/">Etcher</a>.</p> -</li> -<li> -<p>Type <code>systemctl start display-manager</code> and wait.<sup class="footnote-reference"><a href="#1">1</a></sup></p> -</li> -<li> -<p>Open both the manual and the <code>konsole</code>.</p> -</li> -<li> -<p>Connect to the network using the GUI.</p> -</li> -<li> -<p>Create the disk partitions by using <code>fdisk</code>.</p> -<p>You can list them with <code>fdisk -l</code>, modify a certain drive with <code>fdisk /dev/sdX</code> (for instance, <code>/dev/sda</code>) and follow the instructions.</p> -<p>To create the file system, use <code>mkfs.ext4 -L &lt;label&gt; /dev/sdXY</code> and swap with <code>mkswap -L &lt;label&gt; /dev/sdXY</code>.</p> -<p>The EFI partition should be done with <code>mkfs.vfat</code>.</p> -</li> -<li> -<p>Mount the target to <code>/mnt</code> e.g. if the label was <code>nixos</code>, <code>mount /dev/disk/by-label/nixos /mnt</code></p> -</li> -<li> -<p><code>mkdir /mnt/boot</code> and then mount your EFI partition to it.</p> -</li> -<li> -<p>Generate a configuration template with <code>nixos-generate-config --root /mnt</code>, and modify it with <code>nano /etc/nixos/configuration.nix</code>.</p> -</li> -<li> -<p>While modifying the configuration, make sure to add <code>boot.loader.grub.device = &quot;/dev/sda&quot;</code></p> -</li> -<li> -<p>More useful configuration things are:</p> -<ul> -<li>Uncomment the whole <code>i18n</code> block.</li> -<li>Add some essential packages like <code>environment.systemPackages = with pkgs; [wget git firefox pulseaudio networkmanagerapplet];</code>.</li> -<li>If you want to use XFCE, add <code>services.xserver.desktopManager.xfce.enable = true;</code>, otherwise, you don't need <code>networkmanagerapplet</code> either. Make sure to add <code>networking.networkmanager.enable = true;</code> too.</li> -<li>Define some user for yourself (modify <code>guest</code> name) and use a UID greater than 1000. Also, add yourself to <code>extraGroups = [&quot;wheel&quot; &quot;networkmanager&quot;];</code> (the first to be able to <code>sudo</code>, the second to use network related things).</li> -</ul> -</li> -<li> -<p>Run <code>nixos-install</code>. If you ever modify that file again, to add more packages for instance (this is how they're installed), run <code>nixos-rebuild switch</code> (or use <code>test</code> to test but don't boot to it, or <code>boot</code> not to switch but to use on next boot.</p> -</li> -<li> -<p><code>reboot</code>.</p> -</li> -<li> -<p>Login as <code>root</code>, and set a password for your user with <code>passwd &lt;user&gt;</code>. Done!</p> -</li> -</ul> -<p>I enjoyed the process of installing it, and it's really cool that it has versioning and is so clean to keep track of which packages you install. But not being able to run arbitrary binaries by default is something very limitting in my opinion, though they've done a good job.</p> -<p>I'm now back to Xubuntu, with a fresh install.</p> -<h2 id="update-1">Update</h2> -<p>It is not true that &quot;they don't allow running arbitrary binaries by default&quot;, as pointed out in their <a href="https://nixos.org/nixpkgs/manual/#sec-fhs-environments">manual, buildFHSUserEnv</a>:</p> -<blockquote> -<p><code>buildFHSUserEnv</code> provides a way to build and run FHS-compatible lightweight sandboxes. It creates an isolated root with bound <code>/nix/store</code>, so its footprint in terms of disk space needed is quite small. This allows one to run software which is hard or unfeasible to patch for NixOS -- 3rd-party source trees with FHS assumptions, games distributed as tarballs, software with integrity checking and/or external self-updated binaries. It uses Linux namespaces feature to create temporary lightweight environments which are destroyed after all child processes exit, without root user rights requirement.</p> -</blockquote> -<p>Thanks to <a href="https://github.com/bb010g">@bb010g</a> for pointing this out.</p> -<h2 id="notes">Notes</h2> -<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup> -<p>The keyboard mapping is a bit strange. On my Spanish keyboard, the keys were as follows:</p> -</div> -<table><thead><tr><th>Keyboard</th><th>Maps to</th><th>Shift</th></tr></thead><tbody> -<tr><td>'</td><td>-</td><td>_</td></tr> -<tr><td>´</td><td>'</td><td>&quot;</td></tr> -<tr><td>`</td><td>[</td><td></td></tr> -<tr><td>+</td><td>]</td><td></td></tr> -<tr><td>¡</td><td>=</td><td></td></tr> -<tr><td>-</td><td>/</td><td></td></tr> -<tr><td>ñ</td><td>;</td><td></td></tr> -</tbody></table> </content> </entry> </feed>
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Breaking Risk of Rain | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Breaking Risk of Rain</h1><div class=time><p>2019-01-12</div><p><a href=https://riskofraingame.com/>Risk of Rain</a> is a fun little game you can spend a lot of hours on. It's incredibly challenging for new players, and fun once you have learnt the basics. This blog will go through what I've learnt and how to play the game correctly.<h2 id=getting-started>Getting Started</h2><p>If you're new to the game, you may find it frustrating. You must learn very well to dodge.<p>Your first <a href=http://riskofrain.wikia.com/wiki/Category:Characters>character</a> will be <a href=http://riskofrain.wikia.com/wiki/Commando>Commando</a>. He's actually a very nice character. Use your third skill (dodge) to move faster, pass through large groups of enemies, and negate fall damage.<p>If there are a lot of monsters, remember to <strong>leave</strong> from there! It's really important for survival. Most enemies <strong>don't do body damage</strong>. Not even the body of the <a href=http://riskofrain.wikia.com/wiki/Magma_Worm>Magma Worm</a> or the <a href=http://riskofrain.wikia.com/wiki/Wandering_Vagrant>Wandering Vagrant</a> (just dodge the head and projectiles respectively).<p>The first thing you must do is always <strong>rush for the teleporter</strong>. Completing the levels quick will make the game easier. But make sure to take note of <strong>where the chests are</strong>! When you have time (even when the countdown finishes), go back for them and buy as many as you can. Generally, prefer <a href=http://riskofrain.wikia.com/wiki/Chest>chests</a> over <a href=http://riskofrain.wikia.com/wiki/Shrine>shrines</a> since they may eat all your money.<p>Completing the game on <a href=http://riskofrain.wikia.com/wiki/Difficulty>Drizzle</a> is really easy if you follow these tips.<h2 id=requisites>Requisites</h2><p>Before breaking the game, you must obtain several <a href=http://riskofrain.wikia.com/wiki/Item#Artifacts>artifacts</a>. We are interested in particular in the following:<ul><li><a href=http://riskofrain.wikia.com/wiki/Sacrifice>Sacrifice</a>. You really need this one, and may be a bit hard to get. With it, you will be able to farm the first level for 30 minutes and kill the final boss in 30 seconds.<li><a href=http://riskofrain.wikia.com/wiki/Command>Command</a>. You need this unless you want to grind for hours to get enough of the items you really need for the rest of the game. Getting this one is easy.<li><a href=http://riskofrain.wikia.com/wiki/Glass>Glass</a>. Your life will be very small (at the beginning…), but you will be able to one-shot everything easily.<li><a href=http://riskofrain.wikia.com/wiki/Kin>Kin</a> (optional). It makes it easier to obtain a lot of boxes if you restart the first level until you get <a href=http://riskofrain.wikia.com/wiki/Lemurian>lemurians</a> or <a href=http://riskofrain.wikia.com/wiki/Jellyfish>jellyfish</a> as the monster, since they're cheap to spawn.</ul><p>With those, the game becomes trivial. Playing as <a href=http://riskofrain.wikia.com/wiki/Huntress>Huntress</a> is excellent since she can move at high speed while killing everything on screen.<h2 id=breaking-the-game>Breaking the Game</h2><p>The rest is easy! With the command artifact you want the following items.<h3 id=common-items><a href=http://riskofrain.wikia.com/wiki/Category:Common_Items>Common Items</a></h3><ul><li><a href=http://riskofrain.wikia.com/wiki/Soldier's_Syringe>Soldier's Syringe</a>. <strong>Stack 13</strong> of these and you will triple your attack speed. You can get started with 4 or so.<li><a href=http://riskofrain.wikia.com/wiki/Paul's_Goat_Hoof>Paul's Goat Hoof</a>. <strong>Stack +30</strong> of these and your movement speed will be insane. You can get a very good speed with 8 or so.<li><a href=http://riskofrain.wikia.com/wiki/Crowbar>Crowbar</a>. <strong>Stack +20</strong> to guarantee you can one-shot bosses.</ul><p>If you want to be safer:<ul><li><a href=http://riskofrain.wikia.com/wiki/Hermit's_Scarf>Hermit's Scarf</a>. <strong>Stack 6</strong> of these to dodge 1/3 of the attacks.<li><a href=http://riskofrain.wikia.com/wiki/Monster_Tooth>Monster Tooth</a>. <strong>Stack 9</strong> of these to recover 50 life on kill. This is plenty, since you will be killing <em>a lot</em>.</ul><p>If you don't have enough and want more fun, get one of these:<ul><li><a href=http://riskofrain.wikia.com/wiki/Gasoline>Gasoline</a>. Burn the ground on kill, and more will die!<li><a href=http://riskofrain.wikia.com/wiki/Headstompers>Headstompers</a>. They make a pleasing sound on fall, and hurt.<li><a href=http://riskofrain.wikia.com/wiki/Lens-Maker's_Glasses>Lens-Maker's Glasses</a>. <strong>Stack 14</strong> and you will always deal a critical strike for double the damage.</ul><h3 id=uncommon-items><a href=http://riskofrain.wikia.com/wiki/Category:Uncommon_Items>Uncommon Items</a></h3><ul><li><a href=http://riskofrain.wikia.com/wiki/Infusion>Infusion</a>. You only really need one of this. Your life will skyrocket after a while, since this gives you 1HP per kill.<li><a href=http://riskofrain.wikia.com/wiki/Hopoo_Feather>Hopoo Feather</a>. <strong>Stack +10</strong> of these. You will pretty much be able to fly with so many jumps.<li><a href=http://riskofrain.wikia.com/wiki/Guardian's_Heart>Guardian's Heart</a>. Not really necessary, but useful for early and late game, since it will absorb infinite damage the first hit.</ul><p>If, again, you want more fun, get one of these:<ul><li><a href=http://riskofrain.wikia.com/wiki/Ukulele>Ukelele</a>. Spazz your enemies!<li><a href=http://riskofrain.wikia.com/wiki/Will-o'-the-wisp>Will-o'-the-wisp</a>. Explode your enemies!<li><a href=http://riskofrain.wikia.com/wiki/Chargefield_Generator>Chargefield Generator</a>. It should cover your entire screen after a bit, hurting all enemies without moving a finger.<li><a href=http://riskofrain.wikia.com/wiki/Golden_Gun>Golden Gun</a>. You will be rich, so this gives you +40% damage.<li><a href=http://riskofrain.wikia.com/wiki/Predatory_Instincts>Predatory Instincts</a>. If you got 14 glasses, you will always be doing critical strikes, and this will give even more attack speed.<li><a href=http://riskofrain.wikia.com/wiki/56_Leaf_Clover>56 Leaf Clover</a>. More drops, in case you didn't have enough.</ul><h3 id=rare-items><a href=http://riskofrain.wikia.com/wiki/Category:Rare_Items>Rare Items</a></h3><ul><li><a href=http://riskofrain.wikia.com/wiki/Ceremonial_Dagger>Ceremonial Dagger</a>. <strong>Stack +3</strong>, then killing one thing kills another thing and makes a chain reaction.<li><a href=http://riskofrain.wikia.com/wiki/Alien_Head>Alien Head</a>. <strong>Stack 3</strong>, and you will be able to use your abilities more often.</ul><p>For more fun:<ul><li><a href=http://riskofrain.wikia.com/wiki/Brilliant_Behemoth>Brilliant Behemoth</a>. Boom boom.</ul><h2 id=closing-words>Closing Words</h2><p>You can now beat the game in Monsoon solo with any character. Have fun! And be careful with the sadly common crashes.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,184 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Python ctypes and Windows | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Python ctypes and Windows</h1><div class=time><p>2019-06-19</div><p><a href=https://www.python.org/>Python</a>'s <a href=https://docs.python.org/3/library/ctypes.html><code>ctypes</code></a> is quite a nice library to easily load and invoke C methods available in already-compiled <a href=https://en.wikipedia.org/wiki/Dynamic-link_library><code>.dll</code> files</a> without any additional dependencies. And I <em>love</em> depending on as little as possible.<p>In this blog post, we will walk through my endeavors to use <code>ctypes</code> with the <a href=https://docs.microsoft.com/en-us/windows/desktop/api/>Windows API</a>, and do some cool stuff with it.<p>We will assume some knowledge of C/++ and Python, since we will need to read and write a bit of both. Please note that this post is only an introduction to <code>ctypes</code>, and if you need more information you should consult the <a href=https://docs.python.org/3/library/ctypes.html>Python's documentation for <code>ctypes</code></a>.<p>While the post focuses on Windows' API, the code here probably applies to unix-based systems with little modifications.<h2 id=basics>Basics</h2><p>First of all, let's learn how to load a library. Let's say we want to load <code>User32.dll</code>:<pre><code class=language-python data-lang=python>import ctypes - -ctypes.windll.user32 -</code></pre><p>Yes, it's that simple. When you access an attribute of <code>windll</code>, said library will load. Since Windows is case-insensitive, we will use lowercase consistently.<p>Calling a function is just as simple. Let's say you want to call <a href=https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-setcursorpos><code>SetCursorPos</code></a>, which is defined as follows:<pre><code class=language-c data-lang=c>BOOL SetCursorPos( - int X, - int Y -); -</code></pre><p>Okay, it returns a <code>bool</code> and takes two inputs, <code>x</code> and <code>y</code>. So we can call it like so:<pre><code class=language-python data-lang=python>ctypes.windll.user32.SetCursorPos(100, 100) -</code></pre><p>Try it! Your cursor will move!<h2 id=funky-stuff>Funky Stuff</h2><p>We can go a bit more crazy and make it form a spiral:<pre><code class=language-python data-lang=python>import math -import time - -for i in range(200): - x = int(500 + math.cos(i / 5) * i) - y = int(500 + math.sin(i / 5) * i) - ctypes.windll.user32.SetCursorPos(x, y) - time.sleep(0.05) -</code></pre><p>Ah, it's always so pleasant to do random stuff when programming. Sure makes it more fun.<h2 id=complex-structures>Complex Structures</h2><p><code>SetCursorPos</code> was really simple. It took two parameters and they both were integers. Let's go with something harder. Let's go with <a href=https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-sendinput><code>SendInput</code></a>! Emulating input will be a fun exercise:<pre><code class=language-c data-lang=c>UINT SendInput( - UINT cInputs, - LPINPUT pInputs, - int cbSize -); -</code></pre><p>Okay, <code>LPINPUT</code>, what are you? Microsoft likes to prefix types with what they are. In this case, <code>LP</code> stands for "Long Pointer" (I guess?), so <code>LPINPUT</code> is just a Long Pointer to <a href=https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-taginput><code>INPUT</code></a>:<pre><code class=language-c data-lang=c>typedef struct tagINPUT { - DWORD type; - union { - MOUSEINPUT mi; - KEYBDINPUT ki; - HARDWAREINPUT hi; - } DUMMYUNIONNAME; -} INPUT, *PINPUT, *LPINPUT; -</code></pre><p>Alright, that's new. We have a <code>struct</code> and <code>union</code>, two different concepts. We can define both with <code>ctypes</code>:<pre><code class=language-python data-lang=python>INPUT_MOUSE = 0 -INPUT_KEYBOARD = 1 -INPUT_HARDWARE = 2 - -class INPUT(ctypes.Structure): - _fields_ = [ - ('type', ctypes.c_long), - ... - ] -</code></pre><p>Structures are classes that subclass <code>ctypes.Structure</code>, and you define their fields in the <code>_fields_</code> class-level variable, which is a list of tuples <code>(field name, field type)</code>.<p>The C structure had a <code>DWORD type</code>. <code>DWORD</code> is a <code>c_long</code>, and <code>type</code> is a name like any other, which is why we did <code>('type', ctypes.c_long)</code>.<p>But what about the union? It's anonymous, and we can't make anonymous unions (<em>citation needed</em>) with <code>ctypes</code>. We will give it a concrete name and a type.<p>Before defining the union, we need to define its inner structures, <a href=https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-tagmouseinput><code>MOUSEINPUT</code></a>, <a href=https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-tagkeybdinput><code>KEYBDINPUT</code></a> and <a href=https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-taghardwareinput><code>HARDWAREINPUT</code></a>. We won't be using them all, but since they count towards the final struct size (C will choose the largest structure as the final size), we need them, or Windows' API will get confused and refuse to work (personal experience):<pre><code class=language-python data-lang=python>class MOUSEINPUT(ctypes.Structure): - _fields_ = [ - ('dx', ctypes.c_long), - ('dy', ctypes.c_long), - ('mouseData', ctypes.c_long), - ('dwFlags', ctypes.c_long), - ('time', ctypes.c_long), - ('dwExtraInfo', ctypes.POINTER(ctypes.c_ulong)) - ] - - -class KEYBDINPUT(ctypes.Structure): - _fields_ = [ - ('wVk', ctypes.c_short), - ('wScan', ctypes.c_short), - ('dwFlags', ctypes.c_long), - ('time', ctypes.c_long), - ('dwExtraInfo', ctypes.POINTER(ctypes.c_ulong)) - ] - - -class HARDWAREINPUT(ctypes.Structure): - _fields_ = [ - ('uMsg', ctypes.c_long), - ('wParamL', ctypes.c_short), - ('wParamH', ctypes.c_short) - ] - - -class INPUTUNION(ctypes.Union): - _fields_ = [ - ('mi', MOUSEINPUT), - ('ki', KEYBDINPUT), - ('hi', HARDWAREINPUT) - ] - - -class INPUT(ctypes.Structure): - _fields_ = [ - ('type', ctypes.c_long), - ('value', INPUTUNION) - ] -</code></pre><p>Some things to note:<ul><li>Pointers are defined as <code>ctypes.POINTER(inner type)</code>.<li>The field names can be anything you want. You can make them more "pythonic" if you want (such as changing <code>dwExtraInfo</code> for just <code>extra_info</code>), but I chose to stick with the original naming.<li>The union is very similar, but it uses <code>ctypes.Union</code> instead of <code>ctypes.Structure</code>.<li>We gave a name to the anonymous union, <code>INPUTUNION</code>, and used it inside <code>INPUT</code> with also a made-up name, <code>('value', INPUTUNION)</code>.</ul><p>Now that we have all the types we need defined, we can use them:<pre><code class=language-python data-lang=python>KEYEVENTF_KEYUP = 0x0002 - -def press(vk, down): - inputs = INPUT(type=INPUT_KEYBOARD, value=INPUTUNION(ki=KEYBDINPUT( - wVk=vk, - wScan=0, - dwFlags=0 if down else KEYEVENTF_KEYUP, - time=0, - dwExtraInfo=None - ))) - ctypes.windll.user32.SendInput(1, ctypes.byref(inputs), ctypes.sizeof(inputs)) - - -for char in 'HELLO': - press(ord(char), down=True) - press(ord(char), down=False) -</code></pre><p>Run it! It will press and release the keys <code>hello</code> to type the word <code>"hello"</code>!<p><code>vk</code> stands for "virtual key". Letters correspond with their upper-case ASCII value, which is what we did above. You can find all the available keys in the page with all the <a href=https://docs.microsoft.com/en-us/windows/desktop/inputdev/virtual-key-codes>Virtual Key Codes</a>.<h2 id=dynamic-inputs-and-pointers>Dynamic Inputs and Pointers</h2><p>What happens if a method wants something by reference? That is, a pointer to your thing? For example, <a href=https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getcursorpos><code>GetCursorPos</code></a>:<pre><code class=language-c data-lang=c>typedef struct tagPOINT { - LONG x; - LONG y; -} POINT, *PPOINT, *NPPOINT, *LPPOINT; - -BOOL GetCursorPos( - LPPOINT lpPoint -); -</code></pre><p>It wants a Long Pointer to <a href=https://docs.microsoft.com/en-us/windows/desktop/api/windef/ns-windef-point><code>POINT</code></a>. We can do just that with <code>ctypes.byref</code>:<pre><code class=language-python data-lang=python>class POINT(ctypes.Structure): - _fields_ = [ - ('x', ctypes.c_long), - ('y', ctypes.c_long) - ] - - -def get_mouse(): - point = POINT() - ctypes.windll.user32.GetCursorPos(ctypes.byref(point)) - # pass our point by ref ^^^^^ - # this lets GetCursorPos fill its x and y fields - - return point.x, point.y - - -while True: - print(get_mouse()) - time.sleep(0.05) -</code></pre><p>Now you can track the mouse position! Make sure to <code>Ctrl+C</code> the program when you're tired of it.<p>What happens if a method wants a dynamically-sized input?<pre><code class=language-python data-lang=python>buffer = ctypes.create_string_buffer(size) -</code></pre><p>In that case, you can create an in-memory <code>buffer</code> of <code>size</code> with <code>ctypes.create_string_buffer</code>. It will return a character array of that size, which you can pass as a pointer directly (without <code>ctypes.byref</code>).<p>To access the buffer's contents, you can use either <code>.raw</code> or <code>.value</code>:<pre><code class=language-python data-lang=python>entire_buffer_as_bytes = buffer.raw -up_until_null = buffer.value -</code></pre><p>When the method fills in the data, you can <code>cast</code> your buffer back into a pointer of a concrete type:<pre><code class=language-python data-lang=python>result_ptr = ctypes.cast(buffer, ctypes.POINTER(ctypes.c_long)) -</code></pre><p>And you can de-reference pointers with <code>.contents</code>:<pre><code class=language-python data-lang=python>first_result = result_ptr.contents -</code></pre><h2 id=arrays>Arrays</h2><p>Arrays are defined as <code>type * size</code>. Your linter may not like that, and if you don't know the size beforehand, consider creating a 0-sized array. For example:<pre><code class=language-python data-lang=python># 10 longs -ten_longs = (ctypes.c_long * 10)() -for i in range(10): - ten_longs[i] = 2 ** i - -# Unknown size of longs, e.g. inside some Structure -longs = (ctypes.c_long * 0) - -# Now you know how many longs it actually was -known_longs = ctypes.cast( - ctypes.byref(longs), - ctypes.POINTER(ctypes.c_long * size) -).contents -</code></pre><p>If there's a better way to initialize arrays, please let me know.<h2 id=wintypes>wintypes</h2><p>Under Windows, the <code>ctypes</code> module has a <code>wintypes</code> submodule. This one contains definitions like <code>HWND</code> which may be useful and can be imported as:<pre><code class=language-python data-lang=python>from ctypes.wintypes import HWND, LPCWSTR, UINT -</code></pre><h2 id=callbacks>Callbacks</h2><p>Some functions (I'm looking at you, <a href=https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumwindows><code>EnumWindows</code></a>) ask us to pass a callback. In this case, it wants a <a href=https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms633498(v=vs.85)><code>EnumWindowsProc</code></a>:<pre><code class=language-c data-lang=c>BOOL EnumWindows( - WNDENUMPROC lpEnumFunc, - LPARAM lParam -); - -BOOL CALLBACK EnumWindowsProc( - _In_ HWND hwnd, - _In_ LPARAM lParam -); -</code></pre><p>The naive approach won't work:<pre><code class=language-python data-lang=python>def callback(hwnd, lParam): - print(hwnd) - return True - -ctypes.windll.user32.EnumWindows(callback, 0) -# ctypes.ArgumentError: argument 1: <class 'TypeError'>: Don't know how to convert parameter 1 -# Aww. -</code></pre><p>Instead, you must wrap your function as a C definition like so:<pre><code class=language-python data-lang=python>from ctypes.wintypes import BOOL, HWND, LPARAM - -EnumWindowsProc = ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM) - -def callback(hwnd, lParam): - print(hwnd) - return True - -# Wrap the function in the C definition -callback = EnumWindowsProc(callback) - -ctypes.windll.user32.EnumWindows(callback, 0) -# Yay, it works. -</code></pre><p>You may have noticed this is what decorators do, wrap the function. So…<pre><code class=language-python data-lang=python>from ctypes.wintypes import BOOL, HWND, LPARAM - -@ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM) -def callback(hwnd, lParam): - print(hwnd) - return True - -ctypes.windll.user32.EnumWindows(callback, 0) -</code></pre><p>…will also work. And it is a <em>lot</em> fancier.<h2 id=closing-words>Closing Words</h2><p>With the knowledge above and some experimentation, you should be able to call and do (almost) anything you want. That was pretty much all I needed on my project anyway :)<p>We have been letting Python convert Python values into C values, but you can do so explicitly too. For example, you can use <code>ctypes.c_short(17)</code> to make sure to pass that <code>17</code> as a <code>short</code>. And if you have a <code>c_short</code>, you can convert or cast it to its Python <code>.value</code> as <code>some_short.value</code>. The same applies for integers, longs, floats, doubles… pretty much anything, char pointers (strings) included.<p>If you can't find something in their online documentation, you can always <a href=https://github.com/BurntSushi/ripgrep><code>rg</code></a> for it in the <code>C:\Program Files (x86)\Windows Kits\10\Include\*</code> directory.<p>Note that the <code>ctypes.Structure</code>'s that you define can have more methods of your own. For example, you can write them a <code>__str__</code> to easily view its fields, or define a <code>@property</code> to re-interpret some data in a meaningful way.<p>For enumerations, you can pass just the right integer number, make a constant for it, or if you prefer, use a <a href=https://docs.python.org/3/library/enum.html#enum.IntEnum><code>enum.IntEnum</code></a>. For example, <a href=https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/dism/dismloglevel-enumeration><code>DismLogLevel</code></a> would be:<pre><code class=language-python data-lang=python>class DismLogLevel(enum.IntEnum): - DismLogErrors = 0 - DismLogErrorsWarnings = 1 - DismLogErrorsWarningsInfo = 2 -</code></pre><p>And you <em>should</em> be able to pass <code>DismLogLevel.DismLogErrors</code> as the parameter now.<p>If you see a function definition like <code>Function(void)</code>, that's C's way of saying it takes no parameters, so just call it as <code>Function()</code>.<p>Make sure to pass all parameters, even if they seem optional they probably still want a <code>NULL</code> at least, and of course, read the documentation well. Some methods have certain pre-conditions.<p>Have fun hacking!</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,207 +0,0 @@
-// Enhancing prototypes -CanvasRenderingContext2D.prototype.clear = function() { - // http://stackoverflow.com/a/9722502/4759433 - this.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.beginPath(); // Clear strokes -}; - -var TWOPI = 2 * Math.PI; -CanvasRenderingContext2D.prototype.circle = function(x, y, r) { - this.beginPath(); - this.arc(x, y, r, 0, TWOPI); - this.fill(); - this.beginPath(); -}; - -var SIXTHPI = Math.PI / 6.0; -CanvasRenderingContext2D.prototype.arrow = function( - x0, y0, x1, y1, headLen=10) { - // http://stackoverflow.com/a/6333775 - var angle = Math.atan2(y1 - y0, x1 - x0); - this.moveTo(x0, y0); - - this.lineTo(x1, y1); - this.lineTo(x1 - headLen * Math.cos(angle - SIXTHPI), - y1 - headLen * Math.sin(angle - SIXTHPI)); - - this.moveTo(x1, y1); - this.lineTo(x1 - headLen * Math.cos(angle + SIXTHPI), - y1 - headLen * Math.sin(angle + SIXTHPI)); - - this.stroke(); -} - -String.prototype.format = function() { - // http://stackoverflow.com/a/4673436/4759433 - var args = arguments; - return this.replace(/{(\d+)}/g, function(match, number) { - return typeof args[number] != 'undefined' ? args[number] : match; - }); -}; - -String.prototype.lpad = function(pad) { - return String(pad + this).slice(-pad.length); -}; - -Array.matrix = function(numrows, numcols, initial) { - // http://stackoverflow.com/a/30056867/4759433 - var arr = []; - for (var i = 0; i != numrows; ++i) { - var columns = []; - for (var j = 0; j != numcols; ++j) - columns[j] = initial; - arr[i] = columns; - } - return arr; -} - -function matrixCpy(src, dst) { - // Ensure it has at least one item - if (src.length != 0 && dst.length != 0) { - var ii = Math.min(src.length, dst.length); - var jj = Math.min(src[0].length, dst[0].length); - - for (var i = 0; i != ii; ++i) - for (var j = 0; j != jj; ++j) - dst[i][j] = src[i][j]; - } -} - -function matrixAdd(src, dst) { - // Ensure it has at least one item - if (src.length != 0 && dst.length != 0) { - var ii = Math.min(src.length, dst.length); - var jj = Math.min(src[0].length, dst[0].length); - - for (var i = 0; i != ii; ++i) - for (var j = 0; j != jj; ++j) - dst[i][j] += src[i][j]; - } -} - -function matrixMul(a, b) { - var ii = a.length; - var jj = b[0].length; - var c = Array.matrix(ii, jj, 0); - - var kk = a[0].length; - if (kk == b.length) // Ensure nxp times pxm - for (var i = 0; i != ii; ++i) - for (var j = 0; j != jj; ++j) - for (var k = 0; k != kk; ++k) - c[i][j] += a[i][k] * b[k][j]; - - return c; -} - -// Pops the i'th row and j'th column off the matrix -function matrixPop(matrix, i, j) { - if (!matrix || !matrix[0]) - return Array.matrix(0, 0, 0); - - var ii = matrix.length; - var jj = matrix[0].length; - if (i < 0 || i >= ii || j < 0 || j >= jj) - return matrix; - - var srci, srcj, dsti, dstj; - var result = Array.matrix(ii-1, jj-1, 0); - - dsti = 0; - for (srci = 0; srci < ii; ++srci) { - dstj = 0; - if (srci != i) { - for (srcj = 0; srcj < jj; ++srcj) { - if (srcj != j) { - result[dsti][dstj] = matrix[srci][srcj]; - ++dstj; - } - } - ++dsti; - } - } - - return result; -} - -function matrixStr(a) { - if (!a || !a[0]) - return '[]'; - - var ii = a.length; - var jj = a[0].length; - var jjm1 = jj - 1; - - var max = a[0][0]; - for (var i = 0; i != ii; ++i) - for (var j = 0; j != jj; ++j) - if (a[i][j] > max) - max = a[i][j]; - - var result = ''; - max = '' + max; - var pad = ''; - for (var i = 0; i != max; ++i) - pad += ' '; - - for (var i = 0; i != ii; ++i) { - result += '['; - for (var j = 0; j != jjm1; ++j) { - result += ('' + a[i][j]).lpad(pad); - result += ','; - } - result += ('' + a[i][jjm1]).lpad(pad); - result += ']\n'; - } - - return result; -} - -// highlight = [belowRow, belowCol, color] -function matrixRepr(matrix, table, highlight=null) { - if (!matrix || !matrix[0]) { - table.innerHTML = ''; - return; - } - - if (!highlight) - highlight = [-1, -1, '#000']; - - var result = ''; - var ii = matrix.length; - var jj = matrix[0].length; - - result += '<tr><td></td>'; - for (var j = 0; j != jj; ++j) - result += '<td><b>' + (j+1) + '</b></td>'; - result += '</tr>'; - - for (var i = 0; i != ii; ++i) { - result += '<tr><td><b>' + (i+1) + '</b></td>'; - for (var j = 0; j != jj; ++j) { - // We want to higlight 0..col on row and 0..row on col - if ((i <= highlight[0] && j == highlight[1]) || - (i == highlight[0] && j <= highlight[1])) { - result += '<td style="background-color:' + highlight[2] + - ';">' + matrix[i][j] + '</td>'; - } else { - result += '<td>' + matrix[i][j] + '</td>'; - } - } - result += '</tr>'; - } - - table.innerHTML = result; -} - -function lerp(start, end, t) { - return start + t * (end - start); -} - -function lerpArray(start, end, t) { - var result = []; - var ii = Math.min(start.length, end.length); - for (var i = 0; i != ii; ++i) - result.push(start[i] + t * (end[i] - start[i])); - return result; -}
@@ -1,306 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - id="svg8" - version="1.1" - viewBox="0 0 52.916665 51.064584" - height="193" - width="200" - sodipodi:docname="example1.svg" - inkscape:version="0.92.1 r15371"> - <sodipodi:namedview - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1" - objecttolerance="10" - gridtolerance="10" - guidetolerance="10" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:window-width="1366" - inkscape:window-height="744" - id="namedview53" - showgrid="false" - inkscape:zoom="1.0697256" - inkscape:cx="60.504485" - inkscape:cy="128.85834" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" - inkscape:current-layer="svg8" /> - <defs - id="defs2"> - <marker - orient="auto" - refY="0" - refX="0" - id="marker5335" - style="overflow:visible"> - <path - id="path5333" - d="M 0,0 5,-5 -12.5,0 5,5 Z" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" - transform="matrix(0.8,0,0,0.8,10,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - style="overflow:visible" - id="Arrow1Lstart" - refX="0" - refY="0" - orient="auto"> - <path - transform="matrix(0.8,0,0,0.8,10,0)" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" - d="M 0,0 5,-5 -12.5,0 5,5 Z" - id="path4579" - inkscape:connector-curvature="0" /> - </marker> - <marker - style="overflow:visible" - id="marker5041" - refX="0" - refY="0" - orient="auto"> - <path - transform="matrix(-0.8,0,0,-0.8,-10,0)" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" - d="M 0,0 5,-5 -12.5,0 5,5 Z" - id="path5039" - inkscape:connector-curvature="0" /> - </marker> - <marker - orient="auto" - refY="0" - refX="0" - id="marker4989" - style="overflow:visible"> - <path - id="path4987" - d="M 0,0 5,-5 -12.5,0 5,5 Z" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" - transform="matrix(-0.8,0,0,-0.8,-10,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - style="overflow:visible" - id="marker4943" - refX="0" - refY="0" - orient="auto"> - <path - transform="matrix(-0.8,0,0,-0.8,-10,0)" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" - d="M 0,0 5,-5 -12.5,0 5,5 Z" - id="path4941" - inkscape:connector-curvature="0" /> - </marker> - <marker - orient="auto" - refY="0" - refX="0" - id="marker4903" - style="overflow:visible"> - <path - id="path4901" - d="M 0,0 5,-5 -12.5,0 5,5 Z" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" - transform="matrix(-0.8,0,0,-0.8,-10,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - orient="auto" - refY="0" - refX="0" - id="marker4869" - style="overflow:visible"> - <path - id="path4867" - d="M 0,0 5,-5 -12.5,0 5,5 Z" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" - transform="matrix(-0.8,0,0,-0.8,-10,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - style="overflow:visible" - id="Arrow1Lend" - refX="0" - refY="0" - orient="auto"> - <path - transform="matrix(-0.8,0,0,-0.8,-10,0)" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" - d="M 0,0 5,-5 -12.5,0 5,5 Z" - id="path4582" - inkscape:connector-curvature="0" /> - </marker> - </defs> - <metadata - id="metadata5"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - transform="matrix(1.2350865,0,0,1.2350865,-4.4147099e-8,-315.75609)" - id="layer1"> - <g - transform="translate(-58.254533,115.25159)" - id="g4513"> - <circle - style="fill:#b7c4c8;fill-opacity:1;stroke:#000000;stroke-width:0.26458332;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="path4485" - cx="77.975891" - cy="144.92363" - r="4.1865373" /> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;line-height:1.25;font-family:Serif;-inkscape-font-specification:Serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="75.418594" - y="145.38777" - id="text4489"><tspan - id="tspan4487" - x="75.418594" - y="145.38777" - style="stroke-width:0.26458332">s<tspan - style="font-size:3.52777767px" - id="tspan4491" - dx="0" - dy="2">2</tspan></tspan></text> - </g> - <g - transform="translate(-73.657062,129.99266)" - id="g4523"> - <circle - r="4.1865373" - cy="144.92363" - cx="77.975891" - id="circle4515" - style="fill:#b7c4c8;fill-opacity:1;stroke:#000000;stroke-width:0.26458332;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - id="text4521" - y="145.38777" - x="75.418594" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;line-height:1.25;font-family:Serif;-inkscape-font-specification:Serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="stroke-width:0.26458332" - y="145.38777" - x="75.418594" - id="tspan4519">s<tspan - dy="2" - id="tspan4555" - style="font-size:3.52777767px">1</tspan></tspan></text> - </g> - <g - id="g4533" - transform="translate(-39.450217,127.53582)"> - <circle - style="fill:#b7c4c8;fill-opacity:1;stroke:#000000;stroke-width:0.26458332;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4525" - cx="77.975891" - cy="144.92363" - r="4.1865373" /> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;line-height:1.25;font-family:Serif;-inkscape-font-specification:Serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="75.418594" - y="145.38777" - id="text4531"><tspan - id="tspan4529" - x="75.418594" - y="145.38777" - style="stroke-width:0.26458332">s<tspan - dy="2" - id="tspan4563" - style="font-size:3.52777767px">3</tspan></tspan></text> - </g> - <g - transform="translate(-65.530574,147.56855)" - id="g4543"> - <circle - r="4.1865373" - cy="144.92363" - cx="77.975891" - id="circle4535" - style="fill:#b7c4c8;fill-opacity:1;stroke:#000000;stroke-width:0.26458332;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - id="text4541" - y="145.38777" - x="75.418594" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;line-height:1.25;font-family:Serif;-inkscape-font-specification:Serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="stroke-width:0.26458332" - y="145.38777" - x="75.418594" - id="tspan4539">s<tspan - dy="2" - id="tspan4557" - style="font-size:3.52777767px">5</tspan></tspan></text> - </g> - <g - id="g4553" - transform="translate(-49.183104,147.75754)"> - <circle - style="fill:#b7c4c8;fill-opacity:1;stroke:#000000;stroke-width:0.26458332;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4545" - cx="77.975891" - cy="144.92363" - r="4.1865373" /> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;line-height:1.25;font-family:Serif;-inkscape-font-specification:Serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="75.418594" - y="145.38777" - id="text4551"><tspan - id="tspan4549" - x="75.418594" - y="145.38777" - style="stroke-width:0.26458332">s<tspan - dy="2" - id="tspan4561" - style="font-size:3.52777767px">4</tspan></tspan></text> - </g> - <path - id="path4565" - d="m 15.511855,264.18427 -7.2162772,6.81537" - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow1Lstart);marker-end:url(#marker5041)" - inkscape:connector-curvature="0" /> - <path - id="path4567" - d="m 18.719089,265.25335 -5.078121,21.11429" - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker4903)" - inkscape:connector-curvature="0" /> - <path - id="path4571" - d="M 27.208867,287.23737 22.130745,265.98945" - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker4943)" - inkscape:connector-curvature="0" /> - <path - id="path4573" - d="m 31.147124,287.57035 4.543581,-10.28987" - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#marker5335);marker-end:url(#marker4989)" - inkscape:connector-curvature="0" /> - <path - id="path4575" - d="m 17.650011,292.24757 h 5.479026" - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker4869)" - inkscape:connector-curvature="0" /> - <path - id="path4577" - d="m 8.9637538,288.63943 -3.073601,-8.15172" - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow1Lend)" - inkscape:connector-curvature="0" /> - </g> -</svg>
@@ -1,350 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - id="svg8" - version="1.1" - viewBox="0 0 52.916665 51.064584" - height="193" - width="200" - sodipodi:docname="example2.svg" - inkscape:version="0.92.1 r15371"> - <sodipodi:namedview - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1" - objecttolerance="10" - gridtolerance="10" - guidetolerance="10" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:window-width="1366" - inkscape:window-height="744" - id="namedview53" - showgrid="false" - inkscape:zoom="1.5128205" - inkscape:cx="65.358647" - inkscape:cy="121.33424" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" - inkscape:current-layer="svg8" /> - <defs - id="defs2"> - <marker - orient="auto" - refY="0" - refX="0" - id="marker5335" - style="overflow:visible"> - <path - id="path5333" - d="M 0,0 5,-5 -12.5,0 5,5 Z" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" - transform="matrix(0.8,0,0,0.8,10,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - style="overflow:visible" - id="Arrow1Lstart" - refX="0" - refY="0" - orient="auto"> - <path - transform="matrix(0.8,0,0,0.8,10,0)" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" - d="M 0,0 5,-5 -12.5,0 5,5 Z" - id="path4579" - inkscape:connector-curvature="0" /> - </marker> - <marker - style="overflow:visible" - id="marker5041" - refX="0" - refY="0" - orient="auto"> - <path - transform="matrix(-0.8,0,0,-0.8,-10,0)" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" - d="M 0,0 5,-5 -12.5,0 5,5 Z" - id="path5039" - inkscape:connector-curvature="0" /> - </marker> - <marker - orient="auto" - refY="0" - refX="0" - id="marker4989" - style="overflow:visible"> - <path - id="path4987" - d="M 0,0 5,-5 -12.5,0 5,5 Z" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" - transform="matrix(-0.8,0,0,-0.8,-10,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - style="overflow:visible" - id="marker4943" - refX="0" - refY="0" - orient="auto"> - <path - transform="matrix(-0.8,0,0,-0.8,-10,0)" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" - d="M 0,0 5,-5 -12.5,0 5,5 Z" - id="path4941" - inkscape:connector-curvature="0" /> - </marker> - <marker - orient="auto" - refY="0" - refX="0" - id="marker4903" - style="overflow:visible"> - <path - id="path4901" - d="M 0,0 5,-5 -12.5,0 5,5 Z" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" - transform="matrix(-0.8,0,0,-0.8,-10,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - orient="auto" - refY="0" - refX="0" - id="marker4869" - style="overflow:visible"> - <path - id="path4867" - d="M 0,0 5,-5 -12.5,0 5,5 Z" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" - transform="matrix(-0.8,0,0,-0.8,-10,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - style="overflow:visible" - id="Arrow1Lend" - refX="0" - refY="0" - orient="auto"> - <path - transform="matrix(-0.8,0,0,-0.8,-10,0)" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" - d="M 0,0 5,-5 -12.5,0 5,5 Z" - id="path4582" - inkscape:connector-curvature="0" /> - </marker> - </defs> - <metadata - id="metadata5"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title /> - </cc:Work> - </rdf:RDF> - </metadata> - <g - transform="matrix(1.2350865,0,0,1.2350865,-4.4147099e-8,-315.75609)" - id="layer1"> - <g - transform="translate(-58.254533,115.25159)" - id="g4513"> - <circle - style="fill:#5fbcd3;fill-opacity:1;stroke:#000000;stroke-width:0.26458332;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="path4485" - cx="77.975891" - cy="144.92363" - r="4.1865373" /> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;line-height:1.25;font-family:Serif;-inkscape-font-specification:Serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="75.418594" - y="145.38777" - id="text4489"><tspan - id="tspan4487" - x="75.418594" - y="145.38777" - style="stroke-width:0.26458332">s<tspan - style="font-size:3.52777767px" - id="tspan4491" - dx="0" - dy="2">2</tspan></tspan></text> - </g> - <g - transform="translate(-73.657062,129.99266)" - id="g4523"> - <circle - r="4.1865373" - cy="144.92363" - cx="77.975891" - id="circle4515" - style="fill:#ffb380;fill-opacity:1;stroke:#000000;stroke-width:0.26458332;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - id="text4521" - y="145.38777" - x="75.418594" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;line-height:1.25;font-family:Serif;-inkscape-font-specification:Serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="stroke-width:0.26458332" - y="145.38777" - x="75.418594" - id="tspan4519">s<tspan - dy="2" - id="tspan4555" - style="font-size:3.52777767px">1</tspan></tspan></text> - </g> - <g - id="g4533" - transform="translate(-39.450217,127.53582)"> - <circle - style="fill:#b7c4c8;fill-opacity:1;stroke:#000000;stroke-width:0.26458332;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4525" - cx="77.975891" - cy="144.92363" - r="4.1865373" /> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;line-height:1.25;font-family:Serif;-inkscape-font-specification:Serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="75.418594" - y="145.38777" - id="text4531"><tspan - id="tspan4529" - x="75.418594" - y="145.38777" - style="stroke-width:0.26458332">s<tspan - dy="2" - id="tspan4563" - style="font-size:3.52777767px">3</tspan></tspan></text> - </g> - <g - transform="translate(-65.530574,147.56855)" - id="g4543"> - <circle - r="4.1865373" - cy="144.92363" - cx="77.975891" - id="circle4535" - style="fill:#00ff00;fill-opacity:1;stroke:#000000;stroke-width:0.26458332;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - id="text4541" - y="145.38777" - x="75.418594" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;line-height:1.25;font-family:Serif;-inkscape-font-specification:Serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="stroke-width:0.26458332" - y="145.38777" - x="75.418594" - id="tspan4539">s<tspan - dy="2" - id="tspan4557" - style="font-size:3.52777767px">5</tspan></tspan></text> - </g> - <g - id="g4553" - transform="translate(-49.183104,147.75754)"> - <circle - style="fill:#ffb380;fill-opacity:1;stroke:#000000;stroke-width:0.26458332;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4545" - cx="77.975891" - cy="144.92363" - r="4.1865373" /> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;line-height:1.25;font-family:Serif;-inkscape-font-specification:Serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="75.418594" - y="145.38777" - id="text4551"><tspan - id="tspan4549" - x="75.418594" - y="145.38777" - style="stroke-width:0.26458332">s<tspan - dy="2" - id="tspan4561" - style="font-size:3.52777767px">4</tspan></tspan></text> - </g> - <path - id="path4565" - d="m 15.511855,264.18427 -7.2162772,6.81537" - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow1Lstart);marker-end:url(#marker5041)" - inkscape:connector-curvature="0" /> - <path - id="path4567" - d="m 18.719089,265.25335 -5.078121,21.11429" - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker4903)" - inkscape:connector-curvature="0" /> - <path - id="path4571" - d="M 27.208867,287.23737 22.130745,265.98945" - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker4943)" - inkscape:connector-curvature="0" /> - <path - id="path4573" - d="m 31.147124,287.57035 4.543581,-10.28987" - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#marker5335);marker-end:url(#marker4989)" - inkscape:connector-curvature="0" /> - <path - id="path4575" - d="m 17.650011,292.24757 h 5.479026" - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker4869)" - inkscape:connector-curvature="0" /> - <path - id="path4577" - d="m 8.9637538,288.63943 -3.073601,-8.15172" - style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow1Lend)" - inkscape:connector-curvature="0" /> - </g> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.85977268px;line-height:1.25;font-family:Serif;-inkscape-font-specification:Serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.22780183" - x="6.2450199" - y="47.761745" - id="text4579"><tspan - sodipodi:role="line" - id="tspan4577" - x="6.2450199" - y="47.761745" - style="stroke-width:0.22780183">1</tspan></text> - <text - id="text4631" - y="7.4923263" - x="30.73904" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.85977268px;line-height:1.25;font-family:Serif;-inkscape-font-specification:Serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.22780183" - xml:space="preserve"><tspan - style="stroke-width:0.22780183" - y="7.4923263" - x="30.73904" - id="tspan4629" - sodipodi:role="line">3</tspan></text> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.85977268px;line-height:1.25;font-family:Serif;-inkscape-font-specification:Serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.22780183" - x="1.26939" - y="17.198946" - id="text4583"><tspan - sodipodi:role="line" - id="tspan4581" - x="1.26939" - y="17.198946" - style="stroke-width:0.22780183">2.1</tspan></text> - <text - id="text4635" - y="46.231361" - x="41.495026" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.85977268px;line-height:1.25;font-family:Serif;-inkscape-font-specification:Serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.22780183" - xml:space="preserve"><tspan - style="stroke-width:0.22780183" - y="46.231361" - x="41.495026" - id="tspan4633" - sodipodi:role="line">2.2</tspan></text> -</svg>
@@ -1,405 +0,0 @@
-// -------------------------------------------------------------------------- // -// Constants -var INTERPOLATION = 0.6; - -var BOX_HEIGHT = 0.15; // %, from and to which drop the items -var BOX_HOVER = [255, 0, 0]; -var BOX_NORMAL = [255, 200, 200]; - -var NODE_NORMAL = '#000'; -var NODE_TEXT = '#fff'; -var NODE_FONT = 'px Georgia'; - -var FRAME_DURATION = 25; // ms - -// -------------------------------------------------------------------------- // -// Retrieve used HTML elements -canvas = document.getElementById('canvas'); -matrixTable = document.getElementById('matrixTable'); -matrixOrder = document.getElementById('matrixOrder'); -matrixAccum = document.getElementById('matrixAccum'); -matrixTableWrapper = document.getElementById('matrixTableWrapper'); - -// -------------------------------------------------------------------------- // -// Variables -var orderShown, accumShown; -c = canvas.getContext('2d'); - -// Box color, from which and to which nodes should be dragged -var boxColor = BOX_NORMAL.slice(); - -// Available nodes and their connection matrix- these should be kept in sync -var nodes = []; -var connMatrix = Array.matrix(0, 0, 0); - -// Which node was the first we right clicked to create a new connection? -var startConn = null; - -// Are we dragging any node yet? -var anyDragging = false; - -// Highlight mode [row, col, color] for the matrix representing the graph -var highlight = [-1, -1, '#fff']; - -function updateOrder() { - orderShown = Number(matrixOrder.value); - accumShown = matrixAccum.checked; - calcShowConnMatrix(); -} -updateOrder(); - -// -------------------------------------------------------------------------- // -// Declare a prototype (Node) to render the selectable elements -function Node(x, y, r, id) { - this.x = x; - this.y = y; - this.r = r; - this.id = id; - this.color = null; - this.dragging = false; - - this.draw = function() { - if (this.dragging) { - c.fillStyle = NODE_NORMAL; - this.x = lerp(this.x, canvas.mouseX, INTERPOLATION); - this.y = lerp(this.y, canvas.mouseY, INTERPOLATION); - } else { - c.fillStyle = this.color; - } - c.circle(this.x, this.y, this.r); - - c.fillStyle = NODE_TEXT; - c.font = ''+r+NODE_FONT; - // Arbitrary but nice - var x = this.x - r * 0.3; - var y = this.y + r * 0.3; - - c.fillText(""+this.id, x, y); - } - - this.resetColor = function() { - this.color = '#777'; - } - this.resetColor(); - - this.inBounds = function(x, y) { - return Math.abs(this.x - x) < this.r && - Math.abs(this.y - y) < this.r; - } -} - -// -------------------------------------------------------------------------- // -// Node handling -function addNode(x, y) { - nodes.push(new Node(x, y, 20, nodes.length + 1)); - limitOrder(nodes.length); - - var newMatrix = Array.matrix(nodes.length, nodes.length, 0); - matrixCpy(connMatrix, newMatrix); - setConnMatrix(newMatrix); -} - -function deleteNode(at) { - nodes.splice(at, 1); - limitOrder(nodes.length); - - // Re-set the nodes IDs - for (var i = 0; i != nodes.length; ++i) { - nodes[i].id = i+1; - } - - // Pop the at'th row (connections from the node we deleted) - // and also the at'th column (connections to the node we deleted) - setConnMatrix(matrixPop(connMatrix, at, at)); -} - -function clearNodes() { - nodes.length = 0; - limitOrder(1); - resetConnections(); -} - -// -------------------------------------------------------------------------- // -// Connection matrix handling -function setConnMatrix(matrix) { - connMatrix = matrix; - makeNodesHighlightable(matrixTable, connMatrix); - calcShowConnMatrix(); -} - -function resetConnections() { - setConnMatrix(Array.matrix(nodes.length, nodes.length, 0)); -} - -// -------------------------------------------------------------------------- // -// Matrix representation -function calcShowConnMatrix() { - if (orderShown > 1) { - var multiplied = Array.matrix(nodes.length, nodes.length, 0); - matrixCpy(connMatrix, multiplied); - - if (accumShown) { - var shown = Array.matrix(nodes.length, nodes.length, 0); - matrixCpy(connMatrix, shown); - } - - for (var i = 1; i != orderShown; ++i) { - multiplied = matrixMul(connMatrix, multiplied); - if (accumShown) { - matrixAdd(multiplied, shown); - } - } - if (accumShown) { - matrixRepr(shown, matrixTable, highlight); - } else { - matrixRepr(multiplied, matrixTable, highlight); - } - } else { - matrixRepr(connMatrix, matrixTable, highlight); - } -} - -// Makes a table "highlightable" (when the mouse hovers, -// it will be redrawn) by using the specified matrix. -function makeNodesHighlightable(table, matrix) { - table.matrix = matrix; - table.addEventListener('mousemove', function(e) { - if (!table.matrix && !table.matrix[0]) - return; - - // TODO I know, this shouldn't have to go here and I should make a - // better script to show the whole path and stuff... oh well. - if (orderShown > 1) { - if (highlight[0] != -1 || highlight[1] != -1) { - highlight[0] = highlight[1] = -1; - for (var i = 0; i != nodes.length; ++i) - nodes[i].resetColor(); - calcShowConnMatrix(); - } - return; - } - - var rect = table.getBoundingClientRect(); - var x = e.clientX - rect.left; - var y = e.clientY - rect.top; - - var colWidth = rect.width / (table.matrix[0].length+1); - var col = Math.floor(x / colWidth)-1; - - var rowHeight = rect.height / (table.matrix.length+1); - var row = Math.floor(y / rowHeight)-1; - - if (row != highlight[0] || col != highlight[1]) { - highlight[0] = row; - highlight[1] = col; - if (row >= 0 && col >= 0) { - if (table.matrix[row][col]) { - // there is a connection so color them - nodes[row].color = '#77f'; - nodes[col].color = '#7f7'; - highlight[2] = '#afa'; - } else { - // no connection so reset its colors - for (var i = 0; i != nodes.length; ++i) - nodes[i].resetColor(); - - highlight[2] = '#faa'; - } - } - calcShowConnMatrix(); - } - - }, false); -} - -// -------------------------------------------------------------------------- // -// Canvas rendering -function renderElements() { - c.clear(); - if (canvas.mouseDown && canvas.mouseY < canvas.height * BOX_HEIGHT) { - boxColor = lerpArray(boxColor, BOX_HOVER, 0.2); - } else { - boxColor = lerpArray(boxColor, BOX_NORMAL, 0.2); - } - c.rect(0, 0, canvas.width, canvas.height * BOX_HEIGHT); - c.fillStyle = 'rgb(' + - Math.round(boxColor[0]) + ',' + - Math.round(boxColor[1]) + ',' + - Math.round(boxColor[2]) + ')'; - c.fill(); - - - if (nodes.length == 0) - return; - - for (var i = 0; i != nodes.length; ++i) - nodes[i].draw(); - - c.strokeStyle = '#f00'; - - var ii = connMatrix.length; - var jj = connMatrix.length; - - var x0, y0, x1, y1, cos, sin; - var rsq = Math.pow(nodes[0].r + 40, 2); // Radius SQuared + some margin - for (var i = 0; i != ii; ++i) { - for (var j = 0; j != jj; ++j) { - if (connMatrix[i][j]) { - // Non-zero item, this implies a connection between nodes. - // An element on the matrix connects row (i) -> column (j) - x0 = nodes[i].x; - y0 = nodes[i].y; - x1 = nodes[j].x; - y1 = nodes[j].y; - - // Interpolate a bit to exit the radius unless we're too close - if (Math.pow(y1 - y0, 2) + Math.pow(x1 - x0, 2) > rsq) { - var angle = Math.atan2(y1 - y0, x1 - x0); - cos = Math.cos(angle) * nodes[0].r; - sin = Math.sin(angle) * nodes[0].r; - x0 += cos; - y0 += sin; - x1 -= cos; - y1 -= sin; - } - c.arrow(x0, y0, x1, y1); - } - } - } - - if (startConn != null) { - c.arrow(nodes[startConn].x, nodes[startConn].y, - canvas.mouseX, canvas.mouseY); - } -} - -function renderLoop() { - renderElements(); - setTimeout(renderLoop, FRAME_DURATION); -} - -// -------------------------------------------------------------------------- // -// HTML interaction - -// To limit the maximum orden which can be specified on the HTML and -// also clamp the currently shown order to the limit if it's exceeded -function limitOrder(value) { - matrixOrder.max = value; - if (orderShown > value) { - orderShown = value; - matrixOrder.value = '' + value; - } -} - -canvas.addEventListener('mousedown', function(e) { - if (e.button == 0) { - if (canvas.mouseY < canvas.height * BOX_HEIGHT) { - // Add node - addNode(canvas.mouseX, 0); - nodes[nodes.length - 1].dragging = true; - anyDragging = true; - } else { - // Drag nodes - for (var i = nodes.length; i--;) { - if (nodes[i].inBounds(canvas.mouseX, canvas.mouseY)) { - nodes[i].dragging = true; - anyDragging = true; - break; - } - } - } - canvas.style.cursor = anyDragging ? "crosshair" : "default"; - - } else if (e.button == 2) { - // Right button, select two nodes to join - if (startConn == null) { - for (var i = nodes.length; i--;) { - if (nodes[i].inBounds(canvas.mouseX, canvas.mouseY)) { - startConn = i; - break; - } - } - } - } else if (e.button == 1) { - // Middle click, clear the connections from and to a given node - for (var i = nodes.length; i--;) { - if (nodes[i].inBounds(canvas.mouseX, canvas.mouseY)) { - for (var j = 0; j != nodes.length; ++j) - connMatrix[i][j] = 0; - break; - } - } - } - - canvas.mouseDown = e.button == 0 ? 'L' : 'R'; -}, false); - -canvas.addEventListener('mouseup', function(e) { - canvas.mouseDown = ''; - - if (anyDragging) { - anyDragging = false; - for (var i = nodes.length; i--; ) { - if (nodes[i].dragging) { - if (canvas.mouseY < canvas.height * BOX_HEIGHT) { - deleteNode(i); - } else { - nodes[i].dragging = false; - } - } - } - } else if (startConn != null) { - for (var i = nodes.length; i--;) { - if (nodes[i].inBounds(canvas.mouseX, canvas.mouseY)) { - // Node row connected to node column - if (i != startConn) - connMatrix[startConn][i] = 1; - break; - } - } - startConn = null; - } - - calcShowConnMatrix(); -}, false); - -canvas.addEventListener('mousemove', function(e) { - var rect = canvas.getBoundingClientRect(); - canvas.mouseX = e.clientX - rect.left; - canvas.mouseY = e.clientY - rect.top; - -}, false); - -// -------------------------------------------------------------------------- // -// Represent the example we walked through -function lilNoise() { - return (Math.random() - 0.5) * canvas.width * 0.05; -} - -function getNoiseX(relative) { - return canvas.width * relative + lilNoise(); -} - -function getNoiseY(relative) { - return canvas.height * relative + lilNoise(); -} - -addNode(getNoiseX(0.2), getNoiseY(0.5)); -addNode(getNoiseX(0.5), getNoiseY(0.3)); -addNode(getNoiseX(0.8), getNoiseY(0.5)); -addNode(getNoiseX(0.7), getNoiseY(0.7)); -addNode(getNoiseX(0.3), getNoiseY(0.7)); - -setConnMatrix([ - [0, 1, 0, 0, 0], - [1, 0, 0, 0, 1], - [0, 0, 0, 1, 0], - [0, 1, 1, 0, 0], - [1, 0, 0, 1, 0] -]); - -calcShowConnMatrix(); - - -// Let's go! -renderLoop();
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Graphs | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Graphs</h1><div class=time><p>2017-06-02</div><p><noscript>There are a few things which won't render unless you enable JavaScript. No tracking, I promise!</noscript><blockquote><p>Don't know English? <a href=https://lonami.dev/blog/graphs/spanish.html>Read the Spanish version instead</a>.</blockquote><p>Let's imagine we have 5 bus stations, which we'll denote by ((s_i)):<div class=matrix>' s_1 ' s_2 ' s_3 ' s_4 ' s_5 \\ s_1 ' ' V ' ' ' \\ s_2 ' V ' ' ' ' V \\ s_3 ' ' ' ' V ' \\ s_4 ' ' V ' V ' ' \\ s_5 ' V ' ' ' V '</div><p>This is known as a "table of direct interconnections". The ((V)) represent connected paths. For instance, on the first row starting at ((s_1)), reaching the ((V)), allows us to turn up to get to ((s_2)).<p>We can see the above table represented in a more graphical way:<p><img src=https://lonami.dev/blog/graphs/example1.svg alt="Table 1 as a Graph"><p>This type of graph is called, well, a graph, and it's a directed graph (or digraph), since the direction on which the arrows go does matter. It's made up of vertices, joined together by edges (also known as lines or directed arcs).<p>One can walk from a node to another through different paths. For example, ((s_4 $rightarrow s_2 $rightarrow s_5)) is an indirect path of order two, because we must use two edges to go from ((s_4)) to ((s_5)).<p>Let's now represent its adjacency matrix called A which represents the same table, but uses 1 instead V to represent a connection:<div class=matrix>0 ' 1 ' 0 ' 0 ' 0 \\ 1 ' 0 ' 0 ' 0 ' 1 \\ 0 ' 0 ' 0 ' 1 ' 0 \\ 0 ' 1 ' 1 ' 0 ' 0 \\ 1 ' 0 ' 0 ' 1 ' 0</div><p>This way we can see how the ((a_{2,1})) element represents the connection ((s_2 $rightarrow s_1)), and the ((a_{5,1})) element the ((s_5 $rightarrow s_1)) connection, etc.<p>In general, ((a_{i,j})) represents a connection from ((s_i $rightarrow s_j))as long as ((a_{i,j}$geq 1)).<p>Working with matrices allows us to have a computable representation of any graph, which is very useful.<hr><p>Graphs have a lot of interesting properties besides being representable by a computer. What would happen if, for instance, we calculated ((A^2))? We obtain the following matrix:<div class=matrix>1 ' 0 ' 0 ' 0 ' 1 \\ 1 ' 1 ' 0 ' 1 ' 0 \\ 0 ' 1 ' 1 ' 0 ' 0 \\ 1 ' 0 ' 0 ' 1 ' 1 \\ 0 ' 2 ' 1 ' 0 ' 0</div><p>We can interpret this as the paths of order two. But what does the element ((a_{5,2}=2)) represent? It indicates the amount of possible ways to go from ((s_5 $rightarrow s_i $rightarrow s_2)).<p>One can manually multiply the involved row and column to determine which element is the one we need to pass through, this way we have the row (([1 0 0 1 0])) and the column (([1 0 0 1 0])) (on vertical). The elements ((s_i$geq 1)) are ((s_1)) and ((s_4)). This is, we can go from ((s_5)) to ((s_2)) via ((s_5 $rightarrow s_1 $rightarrow s_2)) or via ((s_5 $rightarrow s_4 $rightarrow s_2)): <img src=example2.svg><p>It's important to note that graphs to not consider self-connections, this is, ((s_i $rightarrow s_i)) is not allowed; neither we work with multigraphs here (those which allow multiple connections, for instance, an arbitrary number ((n)) of times).<div class=matrix>1 ' 1 ' 0 ' 1 ' 0 \\ 1 ' 2 ' \textbf{1} ' 0 ' 1 \\ 1 ' 0 ' 0 ' 1 ' 1 \\ 1 ' 2 ' 1 ' 1 ' 0 \\ 2 ' 0 ' 0 ' 1 ' 2</div><p>We can see how the first ((1)) just appeared on the element ((a_{2,3})), which means that the shortest path to it is at least of order three.<hr><p>A graph is said to be strongly connected as long as there is a way to reach all its elements.<p>We can see all the available paths until now by simply adding up all the direct and indirect ways to reach a node, so for now, we can add ((A+A^2+A^3)) in such a way that:<div class=matrix>2 ' 2 ' 0 ' 1 ' 1 \\ 3 ' 3 ' 1 ' 1 ' 3 \\ 1 ' 1 ' 1 ' 2 ' 1 \\ 2 ' 3 ' 2 ' 2 ' 1 \\ 3 ' 2 ' 1 ' 2 ' 2</div><p>There isn't a connection between ((s_1)) and ((s_3)) yet. If we were to calculate ((A^4)):<div class=matrix>1 ' 2 ' 1 ' ' \\ ' ' ' ' \\ ' ' ' ' \\ ' ' ' ' \\ ' ' ' '</div><p>We don't need to calculate anymore. We now know that the graph is strongly connected!<hr><p>Congratulations! You've completed this tiny introduction to graphs. Now you can play around with them and design your own connections.<p>Hold the left mouse button on the above area and drag it down to create a new node, or drag a node to this area to delete it.<p>To create new connections, hold the right mouse button on the node you want to start with, and drag it to the node you want it to be connected to.<p>To delete the connections coming from a specific node, middle click it.<table><tr><td style=width:100%;><button onclick=resetConnections()>Reset connections</button> <button onclick=clearNodes()>Clear all the nodes</button> <br> <br> <label for=matrixOrder>Show matrix of order:</label> <input id=matrixOrder type=number min=1 max=5 value=1 oninput=updateOrder()> <br> <label for=matrixAccum>Show accumulated matrix</label> <input id=matrixAccum type=checkbox onchange=updateOrder()> <br> <br> <div><table id=matrixTable></table></div><td><canvas id=canvas width=400 height=400 oncontextmenu="return false;">Looks like your browser won't let you see this fancy example :(</canvas> <br></table><script src=tinyparser.js></script><script src=enhancements.js></script><script src=graphs.js></script></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,188 +0,0 @@
-<!DOCTYPE html> -<html> -<head> - <link href="https://fonts.googleapis.com/css?family=Montserrat|Ubuntu" - rel="stylesheet"> - <link href="css/graphs.css" rel="stylesheet"> -</head> -<body> -<main> - <script src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML' async></script> - <noscript>Hay cosas que no se van a ver a menos que actives JavaScript. - No <i>tracking</i>, ¡lo prometo!</noscript> - - <h1>Grafos</h1> - <p class="right"><em>Escrito por - <a href="https://lonami.dev" >Imanol H.</a><br /> - el 02-06-2017. Última revisión el 02-06-2017 - </em></p> - - <p>Imaginemos 5 estaciones de autobús, que denotaremos por \(s_i\):</p> - \(\begin{bmatrix} - & s_1 & s_2 & s_3 & s_4 & s_5 \\ - s_1 & & V & & & \\ - s_2 & V & & & & V \\ - s_3 & & & & V & \\ - s_4 & & V & V & & \\ - s_5 & V & & & V & - \end{bmatrix}\) - <p>Esto se conoce como <i>"cuadro de interconexiones directas"</i>.</p> - <p>Las \(V\) representan caminos conectados. Por ejemplo, en la - primera fila partiendo de \(s_1\), llegando hasta la \(V\), - se nos permite girar hacia arriba para llegar a \(s_2\).</p> - - <p>Podemos ver esta misma tabla representada de una manera más gráfica:</p> - <img src="example1.svg" /> - <p>Este tipo de gráfica es un grafo, y además dirigido (o <i>digrafo</i>), - ya que el sentido en el que van las flechas sí importa. Está compuesto - por vértices, unidos entre si por ejes (también llamados aristas o - <b>arcos</b> dirigidos).</p> - - <p>Se puede ir de un nodo otro mediante distintos <b>caminos</b> o - <i>tours</i>. Por ejemplo, \(s_4 \rightarrow s_2 \rightarrow s_5\) es un camino - indirecto de <b>orden</b> dos, porque debemos usar dos aristas para ir - de \(s_4\) a \(s_5\).</p> - - <p>Pasemos ahora a representar la matriz de <b>adyacencia</b> llamada A, que - representa el mismo cuadro, pero usa \(1\) en vez de \(V\) - para representar una conexión:</p> - - \(\begin{bmatrix} - 0 & 1 & 0 & 0 & 0 \\ - 1 & 0 & 0 & 0 & 1 \\ - 0 & 0 & 0 & 1 & 0 \\ - 0 & 1 & 1 & 0 & 0 \\ - 1 & 0 & 0 & 1 & 0 - \end{bmatrix}\) - - <p>Así podemos ver como el elemento \(a_{2,1}\) representa la - conexión \(s_2 \rightarrow s_1\), y el \(a_{5,1}\) la - \(s_5 \rightarrow s_1\), etc.</p> - - <p>En general, \(a_ij\) representa una conexión de - \(s_i \rightarrow s_j\) siempre que \(a_{i,j} \geq 1\).</p> - - <p>Trabajar con matrices nos permite tener una representación computable - de un grafo cualquiera, lo cual es realmente útil.</p> - - <hr /> - - <p>Los grafos tienen muchas más propiedades interesantes a parte de ser - representables computacionalmente. ¿Qué ocurre si, por ejemplo, hallamos - \(A^2\)? Resulta la siguiente matriz:</p> - - \(\begin{bmatrix} - 1 & 0 & 0 & 0 & 1 \\ - 1 & 1 & 0 & 1 & 0 \\ - 0 & 1 & 1 & 0 & 0 \\ - 1 & 0 & 0 & 1 & 1 \\ - 0 & 2 & 1 & 0 & 0 - \end{bmatrix}\) - - <p>Podemos interpretar esta matriz como los caminos de orden <b>dos</b>.</p> - <p>¿Pero qué representa el elemento \(a_{5,2} = 2\)? Indica que hay - dos posibles caminos para ir de \(s_5 \rightarrow s_i \rightarrow s_2\)</p> - - <p>Es posible realizar la multiplicación de la fila y columna implicadas - para ver qué elemento es el que hay que atravesar, así se tiene la fila - \([1, 0, 0, 1, 0]\) y la columna \([1, 0, 0, 1, 0]\) (en - vertical). Los elementos \(s_1 \geq 1\) son \(s_1\) y - \(s_4\). Es decir, se puede ir de \(s_5\) a - \(s_2\) o bien mediante \(s_5 \rightarrow s_1 \rightarrow s_2\) ó bien - \(s_5 \rightarrow s_4 \rightarrow s_2\):</p> - <img src="example2.svg" /> - - <p>Es importante notar que en los gráfos no se consideran lazos, es decir, - \(s_i \rightarrow s_i\) no está permitido; ni tampoco se trabaja con - multigrafos (que permiten muchas conexiones, por ejemplo, de un número - arbitrario \(n\) de veces.</p> - - <p>Terminemos con \(A^3\):</p> - \(\begin{bmatrix} - 1 & 1 & 0 & 1 & 0 \\ - 1 & 2 & \textbf{1} & 0 & 1 \\ - 1 & 0 & 0 & 1 & 1 \\ - 1 & 2 & 1 & 1 & 0 \\ - 2 & 0 & 0 & 1 & 2 - \end{bmatrix}\) - - <p>Podemos ver como ha aparecido el primer \(1\) en - \(a_{2,3}\), lo que representa que el camino más corto es de al menos - de orden tres. - - <hr /> - - <p>Un grafo es <b>fuertemente conexo</b> siempre que se pueda encontrar una - conexión para <i>todos</i> los elementos.</p> - - <p>Para ver todos los caminos posibles hasta ahora, basta con sumar las - formas directas más las formas indirectas, por lo que hasta ahora podemos - sumar \(A + A^2 + A^3\) tal que:</p> - - \(\begin{bmatrix} - 2 & 2 & 0 & 1 & 1 \\ - 3 & 3 & 1 & 1 & 3 \\ - 1 & 1 & 1 & 2 & 1 \\ - 2 & 3 & 2 & 2 & 1 \\ - 3 & 2 & 1 & 2 & 2 - \end{bmatrix}\) - - <p>Sigue sin haber una conexión entre \(s_1\) y \(s_3\). - Calculando \(A^4\):</p> - - \(\begin{bmatrix} - 1 & 2 & 1 & & \\ - & & & & \\ - & & & & \\ - & & & & \\ - & & & & - \end{bmatrix}\) - - <p>No hace falta seguir calculando, ya tenemos un grafo totalmente conexo. - </p> - - <hr /> - - <p>¡Felicidades! Has completado esta pequeña introducción a los gráficos. - Ahora puedes jugar tú y diseñar tus propias conexiones.</p> - - <p>Mantén pulsado el botón izquierdo del ratón en el área de arriba y - arrastra hacia abajo para crear un nuevo nodo, o arrastra un nodo a este - área para eliminarlo.</p> - - <p>Para crear nuevas conexiones, mantén pulsado el botón derecho del ratón - en el nodo del que quiera partir, y arrástralo hasta el nodo con el que - lo quieras conectar.</p> - - <p>Para eliminar las conexiones que salen de un nodo en concreto, haz clic - con el botón central del ratón en el nodo que quieras.</p> - - <table><tr><td style="width:100%;"> - <button onclick="resetConnections()">Reiniciar conexiones</button> - <button onclick="clearNodes()">Limpiar todos los nodos</button> - <br /> - <br /> - <label for="matrixOrder">Mostrar matriz de orden:</label> - <input id="matrixOrder" type="number" min="1" max="5" - value="1" oninput="updateOrder()"> - <br /> - <label for="matrixAccum">Mostrar matriz acumulada</label> - <input id="matrixAccum" type="checkbox" onchange="updateOrder()"> - <br /> - <br /> - <div class="matrix"> - <table id="matrixTable"></table> - </div> - </td><td> - <canvas id="canvas" width="400" height="400" oncontextmenu="return false;"> - Parece que tu navegador no vas a poder probar el ejemplo en tu navegador :( - </canvas> - <br /> - </td></tr></table> -</main> - -<script src="tinyparser.js"></script> -<script src="enhancements.js"></script> -<script src="graphs.js"></script> -</body> -</html>
@@ -1,64 +0,0 @@
-const mathify = content => { - let result = '' - let close = null; - let symbol = null; - Array.from(content).forEach(c => { - if (symbol !== null) { - if (c === ' ') { - if (symbol === 'rightarrow') { - result += '→' - } else if (symbol === 'geq') { - result += '≥' - } else { - result += '?' - } - symbol = null; - result += ' ' - } else { - symbol += c - } - } else if (c == '$') { - symbol = '' - } else if (c === '*') { - result += '×' - } else if (c === '^') { - result += '<sup>' - close = '</sup>' - } else if (c === '_') { - result += '<sub>'; - close = '</sub>'; - } else if (c === ' ' || c == '\n') { - if (close !== null) { - result += close - close = null - } - result += ' ' - } else { - result += c - } - }) - if (close !== null) { - result += close; - } - return `<em class="math">${result.trim()}</em>` -} - -const parse_maths = html => html.replaceAll(/\(\(.+?\)\)/g, match => - mathify(match.slice(2, -2)) -); - -Array.from(document.getElementsByClassName('matrix')).forEach(matrix => { - let result = '<table>' - matrix.innerHTML.trim().split('\\').forEach(row => { - result += '<tr>' - row.trim().split("'").forEach(elem => { - result += `<td>${mathify(elem.trim())}</td>` - }) - result += '</tr>' - }) - matrix.innerHTML = result -}) - -Array.from(document.getElementsByTagName('p')).forEach(paragraph => { - paragraph.innerHTML = parse_maths(paragraph.innerHTML) -})
@@ -1,7 +1,7 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>My Blog</h1><p id=welcome onclick=pls_stop()>Welcome to my blog!<p>Here I occasionally post new entries, mostly tech related. Perhaps it's tips for a new game I'm playing, perhaps it has something to do with FFI, or perhaps I'm fighting the borrow checker (just kidding, I'm over that. Mostly).<hr><ul><li><a href=https://lonami.dev/blog/woce-6/>Writing our own Cheat Engine: Pointers</a><span class=dim> [mod sw; 'windows, 'rust, 'hacking] </span><li><a href=https://lonami.dev/blog/woce-5/>Writing our own Cheat Engine: Code finder</a><span class=dim> [mod sw; 'windows, 'rust, 'hacking] </span><li><a href=https://lonami.dev/blog/woce-4/>Writing our own Cheat Engine: Floating points</a><span class=dim> [mod sw; 'windows, 'rust, 'hacking] </span><li><a href=https://lonami.dev/blog/woce-3/>Writing our own Cheat Engine: Unknown initial value</a><span class=dim> [mod sw; 'windows, 'rust, 'hacking] </span><li><a href=https://lonami.dev/blog/woce-2/>Writing our own Cheat Engine: Exact Value scanning</a><span class=dim> [mod sw; 'windows, 'rust, 'hacking] </span><li><a href=https://lonami.dev/blog/woce-1/>Writing our own Cheat Engine: Introduction</a><span class=dim> [mod sw; 'windows, 'rust, 'hacking] </span><li><a href=https://lonami.dev/blog/university/>Data Mining, Warehousing and Information Retrieval</a><span class=dim> [mod algos; 'series, 'bigdata, 'databases] </span><li><a href=https://lonami.dev/blog/new-computer/>My new computer</a><span class=dim> [mod hw; 'showoff] </span><li><a href=https://lonami.dev/blog/tips-outpost/>Tips for Outpost</a><span class=dim> [mod games; 'tips] </span><li><a href=https://lonami.dev/blog/ctypes-and-windows/>Python ctypes and Windows</a><span class=dim> [mod sw; 'python, 'ffi, 'windows] </span><li><a href=https://lonami.dev/blog/pixel-dungeon/>Shattered Pixel Dungeon</a><span class=dim> [mod games; 'tips] </span><li><a href=https://lonami.dev/blog/installing-nixos-2/>Installing NixOS, Take 2</a><span class=dim> [mod sw; 'os, 'nixos] </span><li><a href=https://lonami.dev/blog/breaking-ror/>Breaking Risk of Rain</a><span class=dim> [mod games; 'tips] </span><li><a href=https://lonami.dev/blog/world-edit/>WorldEdit Commands</a><span class=dim> [mod games; 'minecraft, 'worldedit, 'tips] </span><li><a href=https://lonami.dev/blog/asyncio/>An Introduction to Asyncio</a><span class=dim> [mod sw; 'python, 'asyncio] </span><li><a href=https://lonami.dev/blog/posts/>Atemporal Blog Posts</a><span class=dim> [mod algos; 'algorithms, 'culture, 'debate, 'foodforthought, 'graphics, 'optimization] </span><li><a href=https://lonami.dev/blog/graphs/>Graphs</a><span class=dim> [mod algos; 'graphs] </span><li><a href=https://lonami.dev/blog/installing-nixos/>Installing NixOS</a><span class=dim> [mod sw; 'os, 'nixos] </span></ul><script> +<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="BiRabittoh's official website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> BiRabittoh's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>birabittoh's site</a><li><a href=/blog class=selected>blog</a></ul><div class=right><a href=https://github.com/Bi-Rabittoh><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h2 class=title>BiRabittoh's blog</h2><p id=welcome onclick=pls_stop()>Welcome to my blog!<p>I will post about anything I find interesting or noteworthy, be it related to irl stuff or the tech world. I hope someone can read this and maybe find it inspiring? If you do, feel free to let me know.<hr><ul><li><a href=https://birabittoh.dev/blog/modern-web-bloat/>Modern web bloat</a><span class=dim> [mod tech; 'tips, 'vent] </span></ul><script> const WELCOME_EN = 'Welcome to my blog!' - const WELCOME_ES = '¡Bienvenido a mi blog!' - const APOLOGIES = "ok sorry i'll stop" + const WELCOME_IT = 'Benvenuto al mio blog!' + const APOLOGIES = "sorry i'll stop" const REWRITE_DELAY = 5000 const CHAR_DELAY = 30 const welcome = document.getElementById('welcome')@@ -26,7 +26,7 @@ welcome.innerHTML = welcome.innerHTML.slice(0, -1) || '…'
} setTimeout(begin_rewrite, CHAR_DELAY) } else { - let text = english ? WELCOME_EN : WELCOME_ES + let text = english ? WELCOME_EN : WELCOME_IT welcome.innerHTML = text.slice(0, welcome.innerHTML.length + 1) deleting = welcome.innerHTML.length == text.length english = deleting - english@@ -35,4 +35,4 @@ }
} setTimeout(begin_rewrite, REWRITE_DELAY) -</script></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!+</script></main><footer><div><p>Please use email for business inquiries <a href=mailto:andronacomarco@gmail.com><img src=/img/mail.svg alt=mail></a></div></footer></article><p class=abyss>owo what's this
@@ -1,55 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Installing NixOS, Take 2 | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Installing NixOS, Take 2</h1><div class=time><p>2019-02-15<p>last updated 2019-02-16</div><p>This is my second take at installing NixOS, after a while being frustrated with Arch Linux and the fact that a few kernel upgrades ago, the system crashed randomly from time to time. <code>journalctl</code> did not have any helpful hints and I thought reinstalling could be worthwhile anyway.<p>This time, I started with more knowledge! The first step is heading to the <a href=https://nixos.org>NixOS website</a> and downloading their minimal installation CD for 64 bits. I didn't go with their graphical live CD, because their <a href=https://nixos.org/nixos/manual>installation manual</a> is a wonderful resource that guides you nicely.<p>Once you have downloaded their <code>.iso</code>, you should probably verify it's <code>sha256sum</code> and make sure that it matches. The easiest thing to do in my opinion is using an USB to burn the image in it. Plug it in and check its device name with <code>fdisk -l</code>. In my case, it was <code>/dev/sdb</code>, so I went ahead with it and ran <code>dd if=nixos.iso of=/dev/sdb status=progress</code>. Make sure to run <code>sync</code> once that's done.<p>If either <code>dd</code> or <code>sync</code> seem "stuck" in the end, they are just flushing the changes to disk to make sure all is good. This is normal, and depends on your drives.<p>Now, reboot your computer with the USB plugged in and make sure to boot into it. You should be welcome with a pretty screen. Just select the first option and wait until it logs you in as root. Once you're there you probably want to <code>loadkeys es</code> or whatever your keyboard layout is, or you will have a hard time with passwords, since the characters are all over the place.<p>In a clean disk, you would normally create the partitions now. In my case, I already had the partitions made (100MB for the EFI system, where <code>/boot</code> lives, 40GB for the root <code>/</code> partition with my old Linux installation, and 700G for <code>/home</code>), so I didn't need to do anything here. The manual showcases <code>parted</code>, but I personally use <code>fdisk</code>, which has very helpful help I check every time I use it.<p><strong>Important</strong>: The <code>XY</code> in <code>/dev/sdXY</code> is probably different in your system! Make sure you use <code>fdisk -l</code> to see the correct letters and numbers!<p>With the partitions ready in my UEFI system, I formatted both <code>/</code> and <code>/boot</code> just to be safe with <code>mkfs.ext4 -L nixos /dev/sda2</code> and <code>mkfs.fat -F 32 -n boot /dev/sda1</code> (remember that these are the letters and numbers used in my partition scheme). Don't worry about the warning in the second command regarding lowercase letters and Windows. It's not really an issue.<p>Now, since we gave each partition a label, we can easily mount them through <code>mount /dev/disk/by-label/nixos /mnt</code> and, in UEFI systems, be sure to <code>mkdir -p /mnt/boot</code> and <code>mount /dev/disk/by-label/boot /mnt/boot</code>. I didn't bother setting up swap, since I have 8GB of RAM in my laptop and that's really enough for my use case.<p>With that done, we will now ask the configuration wizard to do some work for us (in particular, generate a template) with <code>nixos-generate-config --root /mnt</code>. This generates a very well documented file that we should edit right now (and this is important!) with whatever editor you prefer. I used <code>vim</code>, but you can change it for <code>nano</code> if you prefer.<p>On to the configuration file, we need to enable a few things, so <code>vim /mnt/etc/nixos/configuration.nix</code> and start scrolling down. We want to make sure to uncomment:<pre><code># We really want network! -networking.wireless.enable = true; - -# This "fixes" the keyboard layout. Put the one you use. -i18n = { -consoleKeyMap = "es"; -} - -# Timezones are tricky so let's get this right. -time.timeZone = "Europe/Madrid"; - -# We *really* want some base packages installed, such as -# wpa_supplicant, or we won't have a way to connect to the -# network once we install... -environment.systemPackages = with pkgs; [ -wpa_supplicant wget curl vim neovim cmus mpv firefox git tdesktop -]; - -# Printing is useful, sure, enable CUPS -services.printing.enable = true; - -# We have speakers, let's make use of them. -sound.enable = true; -hardware.pulseaudio.enable = true; - -# We want the X11 windowing system enabled, in Spanish. -services.xserver.enable = true; -services.xserver.layout = "es"; - -# I want a desktop manager in my laptop. -# I personally prefer XFCE, but the manual shows plenty -# of other options, such as Plasma, i3 WM, or whatever. -services.xserver.desktopManager.xfce.enable = true; -services.xserver.desktopManager.default = "xfce"; - -# Touchpad is useful (although sometimes annoying) in a laptop -services.xserver.libinput.enable = true; - -# We don't want to do everything as root! -users.users.lonami = { -isNormalUser = true; -uid = 1000; -home = "/home/lonami"; -extraGroups = [ "wheel" "networkmanager" "audio" ]; -}; -</code></pre><p><em>(Fun fact, I overlooked the configuration file until I wrote this and hadn't noticed sound/pulseaudio was there. It wasn't hard to find online how to enable it though!)</em><p>Now, let's modify <code>hardware-configuration.nix</code>. But if you have <code>/home</code> in a separate partition like me, you should run <code>blkid</code> to figure out its UUID. To avoid typing it out myself, I just ran <code>blkid >> /mnt/etc/nixos/hardware-configuration.nix</code> so that I could easily move it around with <code>vim</code>:<pre><code># (stuff...) - -fileSystems."/home" = -{ device = "/dev/disk/by-uuid/d344c686-cae7-4dd3-840e-308eddf86608"; -fsType = "ext4"; -}; - -# (more stuff...) -</code></pre><p>Note that, obviously, you should put your own partition's UUID there. Modifying the configuration is where I think the current NixOS' manual should have made more emphasis, at this step of the installation. They do detail it below, but that was already too late in my first attempt. Anyway, you can boot from the USB and run <code>nixos-install</code> as many times as you need until you get it working!<p>But before installing, we need to configure the network since there are plenty of things to download. If you want to work from WiFi, you should first figure out the name of your network card with <code>ip link show</code>. In my case it's called <code>wlp3s0</code>. So with that knowledge we can run <code>wpa_supplicant -B -i wlp3s0 -c <(wpa_passphrase SSID key)</code>. Be sure to replace both <code>SSID</code> and <code>key</code> with the name of your network and password key, respectively. If they have spaces, surround them in quotes.<p>Another funny pitfall was typing <code>wpa_supplicant</code> in the command above twice (instead of <code>wpa_passphrase</code>). That sure spit out a few funny errors! Once you have ran that, wait a few seconds and <code>ping 1.1.1.1</code> to make sure that you can reach the internet. If you do, <code>^C</code> and let's install NixOS!<pre><code>nixos-install -</code></pre><p>Well, that was pretty painless. You can now <code>reboot</code> and enjoy your new, functional system.<h2 id=afterword>Afterword</h2><p>The process of installing NixOS was really painless once you have made sense out of what things mean. I was far more pleased this time than in my previous attempt, despite the four attempts I needed to have it up and running.<p>However not all is so good. I'm not sure where I went wrong, but the first time I tried with <code>i3</code> instead of <code>xfce</code>, all I was welcome with was a white, small terminal in the top left corner. I even generated a configuration file with <code>i3-config-wizard</code> to make sure it could detect my Mod1/Mod4 keys (which, it did), but even after rebooting, my commands weren't responding. For example, I couldn't manage to open another terminal with <code>Mod1+Enter</code>. I'm not even sure that I was in <code>i3</code>…<p>In my very first attempt, I pressed <code>Alt+F8</code> as suggested in the welcome message. This took me an offline copy of the manual, which is really nicely done. Funny enough, though, I couldn't exit <code>w3m</code>. Both <code>Q</code> and <code>B</code> to quit and take me back wouldn't work. Somehow, it kept throwing me back into <code>w3m</code>, so I had to forcibly shutdown.<p>In my second attempt, I also forgot to configure network, so I had no way to download <code>wpa_supplicant</code> without having <code>wpa_supplicant</code> itself to connect my laptop to the network! So, it was important to do that through the USB before installing it (which comes with the program preinstalled), just by making sure to add it in the configuration file.<p>Some other notes, if you can't reach the internet, don't add any DNS in <code>/etc/resolv.conf</code>. This should be done declaratively in <code>configuration.nix</code>.<p>In the end, I spent the entire afternoon playing around with it, taking breaks and what-not. I still haven't figured out why <code>nvim</code> was printing the literal escape character when going from normal to insert mode in the <code>xfce4-terminal</code> (and other actions also made it print this "garbage" to the console), why sometimes the network can reach the internet (and only some sites!) and sometimes not, and how to setup dualboot.<p>But despite all of this, I think it was a worth installing it again. One sure sees things from a different perspective, and gets the chance to write another blog post!<p>If there's something I overlooked or that could be done better, or maybe you can explain it differently, please be sure to <a href=https://lonami.dev/contact>contact me</a> to let me know!<h2 id=update>Update</h2><p>Well, that was surprisingly fast feedback. Thank you very much <a href=https://bb010g.keybase.pub/>@bb010g</a> for it! As they rightfully pointed out, one can avoid adding <code>/home</code> manually to <code>hardware-configuration.nix</code> if you mount it before generating the configuration files. However, the installation process doesn't need <code>/home</code> mounted, so I didn't do it.<p>The second weird issue with <code>w3m</code> is actually a funny one. <code>Alt+F8</code> <em>switches to another TTY</em>! That's why quitting the program wouldn't do anything. You'd still be in a different TTY! Normally, this is <code>Ctrl+Alt+FX</code>, so I hadn't even thought that this is what could be happening. Anyway, the solution is not quitting the program, but rather going back to the main TTY with <code>Alt+F1</code>. You can switch back and forth all you need to consult the manual.<p>More suggestions are having <a href=https://github.com/rycee/home-manager><code>home-manager</code></a> manage the graphical sessions, since it should be easier to deal with than the alternatives.<p>Despite having followed the guide and having read it over and over several times, it seems like my thoughts in this blog post may be a bit messy. So I recommend you also reading through the guide to have two versions of all this, just in case.<p>Regarding network issues, they use <code>connman</code> so that may be worth checking out.<p>Regarding terminal issues with <code>nvim</code> printing the literal escape character, I was told off for not having checked what my <code>$TERM</code> was. I hadn't really looked into it much myself, just complained about it here, so sorry for being annoying about that. A quick search in the <code>nixpkgs</code> repository lets us find <a href=https://github.com/NixOS/nixpkgs/blob/release-18.09/pkgs/applications/editors/neovim/default.nix>neovim/default.nix</a>, with version 0.3.1. Looking at <a href=https://github.com/neovim/neovim>Neovim's main repository</a> we can see that this is a bit outdated, but that is fine.<p>If only I had bothered to look at <a href=https://github.com/neovim/neovim/wiki/FAQ#nvim-shows-weird-symbols-2-q-when-changing-modes>Neovim's wiki</a>, (which they found through <a href=https://github.com/neovim/neovim/issues/7749>Neovim's GitHub issues</a>) I would've seen that some terminals just don't support the program properly. The solution is, of course, to use a different terminal emulator with better support or to disable the <code>guicursor</code> in Neovim's config.<p>This is a pretty good life lesson. 30 seconds of searching, maybe two minutes and a half for also checking XFCE issues, are often more than enough to troubleshoot your issues. The internet is a big place and more people have surely came across the problem before, so make sure to look online first. In my defense I'll say that it didn't bother me so much so I didn't bother looking for that soon either.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Installing NixOS | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Installing NixOS</h1><div class=time><p>2017-05-13<p>last updated 2019-02-16</div><h2 id=update>Update</h2><p><em>Please see <a href=../installing_nixos_2/index.html>my followup post with NixOS</a> for a far better experience with it</em><hr><p>Today I decided to install <a href=http://nixos.org/>NixOS</a> as a recommendation, a purely functional Linux distribution, since <a href=https://xubuntu.org/>Xubuntu</a> kept crashing. Here's my journey, and how I managed to install it from a terminal for the first time in my life. Steps aren't hard, but they may not seem obvious at first.<ul><li><p>Grab the Live CD, burn it on a USB stick and boot. I recommend using <a href=https://etcher.io/>Etcher</a>.<li><p>Type <code>systemctl start display-manager</code> and wait.<sup class=footnote-reference><a href=#1>1</a></sup><li><p>Open both the manual and the <code>konsole</code>.<li><p>Connect to the network using the GUI.<li><p>Create the disk partitions by using <code>fdisk</code>.</p> <p>You can list them with <code>fdisk -l</code>, modify a certain drive with <code>fdisk /dev/sdX</code> (for instance, <code>/dev/sda</code>) and follow the instructions.</p> <p>To create the file system, use <code>mkfs.ext4 -L <label> /dev/sdXY</code> and swap with <code>mkswap -L <label> /dev/sdXY</code>.</p> <p>The EFI partition should be done with <code>mkfs.vfat</code>.<li><p>Mount the target to <code>/mnt</code> e.g. if the label was <code>nixos</code>, <code>mount /dev/disk/by-label/nixos /mnt</code><li><p><code>mkdir /mnt/boot</code> and then mount your EFI partition to it.<li><p>Generate a configuration template with <code>nixos-generate-config --root /mnt</code>, and modify it with <code>nano /etc/nixos/configuration.nix</code>.<li><p>While modifying the configuration, make sure to add <code>boot.loader.grub.device = "/dev/sda"</code><li><p>More useful configuration things are:</p> <ul><li>Uncomment the whole <code>i18n</code> block.<li>Add some essential packages like <code>environment.systemPackages = with pkgs; [wget git firefox pulseaudio networkmanagerapplet];</code>.<li>If you want to use XFCE, add <code>services.xserver.desktopManager.xfce.enable = true;</code>, otherwise, you don't need <code>networkmanagerapplet</code> either. Make sure to add <code>networking.networkmanager.enable = true;</code> too.<li>Define some user for yourself (modify <code>guest</code> name) and use a UID greater than 1000. Also, add yourself to <code>extraGroups = ["wheel" "networkmanager"];</code> (the first to be able to <code>sudo</code>, the second to use network related things).</ul><li><p>Run <code>nixos-install</code>. If you ever modify that file again, to add more packages for instance (this is how they're installed), run <code>nixos-rebuild switch</code> (or use <code>test</code> to test but don't boot to it, or <code>boot</code> not to switch but to use on next boot.<li><p><code>reboot</code>.<li><p>Login as <code>root</code>, and set a password for your user with <code>passwd <user></code>. Done!</ul><p>I enjoyed the process of installing it, and it's really cool that it has versioning and is so clean to keep track of which packages you install. But not being able to run arbitrary binaries by default is something very limitting in my opinion, though they've done a good job.<p>I'm now back to Xubuntu, with a fresh install.<h2 id=update-1>Update</h2><p>It is not true that "they don't allow running arbitrary binaries by default", as pointed out in their <a href=https://nixos.org/nixpkgs/manual/#sec-fhs-environments>manual, buildFHSUserEnv</a>:<blockquote><p><code>buildFHSUserEnv</code> provides a way to build and run FHS-compatible lightweight sandboxes. It creates an isolated root with bound <code>/nix/store</code>, so its footprint in terms of disk space needed is quite small. This allows one to run software which is hard or unfeasible to patch for NixOS -- 3rd-party source trees with FHS assumptions, games distributed as tarballs, software with integrity checking and/or external self-updated binaries. It uses Linux namespaces feature to create temporary lightweight environments which are destroyed after all child processes exit, without root user rights requirement.</blockquote><p>Thanks to <a href=https://github.com/bb010g>@bb010g</a> for pointing this out.<h2 id=notes>Notes</h2><div class=footnote-definition id=1><sup class=footnote-definition-label>1</sup><p>The keyboard mapping is a bit strange. On my Spanish keyboard, the keys were as follows:</div><table><thead><tr><th>Keyboard<th>Maps to<th>Shift<tbody><tr><td>'<td>-<td>_<tr><td>´<td>'<td>"<tr><td>`<td>[<td><tr><td>+<td>]<td><tr><td>¡<td>=<td><tr><td>-<td>/<td><tr><td>ñ<td>;<td></table></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,89 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> A practical example with Hadoop | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>A practical example with Hadoop</h1><div class=time><p>2020-03-30T01:00:00+00:00<p>last updated 2020-04-18T13:25:43+00:00</div><p>In our <a href=/blog/mdad/introduction-to-hadoop-and-its-mapreduce/>previous Hadoop post</a>, we learnt what it is, how it originated, and how it works, from a theoretical standpoint. Here we will instead focus on a more practical example with Hadoop.<p>This post will reproduce the example on Chapter 2 of the book <a href=http://www.hadoopbook.com/>Hadoop: The Definitive Guide, Fourth Edition</a> (<a href=http://grut-computing.com/HadoopBook.pdf>pdf,</a><a href=http://www.hadoopbook.com/code.html>code</a>), that is, finding the maximum global-wide temperature for a given year.<h2 id=installation>Installation</h2><p>Before running any piece of software, its executable code must first be downloaded into our computers so that we can run it. Head over to <a href=http://hadoop.apache.org/releases.html>Apache Hadoop’s releases</a> and download the <a href=https://www.apache.org/dyn/closer.cgi/hadoop/common/hadoop-3.2.1/hadoop-3.2.1.tar.gz>latest binary version</a> at the time of writing (3.2.1).<p>We will be using the <a href=https://linuxmint.com/>Linux Mint</a> distribution because I love its simplicity, although the process shown here should work just fine on any similar Linux distribution such as <a href=https://ubuntu.com/>Ubuntu</a>.<p>Once the archive download is complete, extract it with any tool of your choice (graphical or using the terminal) and execute it. Make sure you have a version of Java installed, such as <a href=https://openjdk.java.net/>OpenJDK</a>.<p>Here are all the three steps in the command line:<pre><code>wget https://apache.brunneis.com/hadoop/common/hadoop-3.2.1/hadoop-3.2.1.tar.gz -tar xf hadoop-3.2.1.tar.gz -hadoop-3.2.1/bin/hadoop version -</code></pre><p>We will be using the two example data files that they provide in <a href=https://github.com/tomwhite/hadoop-book/tree/master/input/ncdc/all>their GitHub repository</a>, although the full dataset is offered by the <a href=https://www.ncdc.noaa.gov/>National Climatic Data Center</a> (NCDC).<p>We will also unzip and concatenate both files into a single text file, to make it easier to work with. As a single command pipeline:<pre><code>curl https://raw.githubusercontent.com/tomwhite/hadoop-book/master/input/ncdc/all/190{1,2}.gz | gunzip > 190x -</code></pre><p>This should create a <code>190x</code> text file in the current directory, which will be our input data.<h2 id=processing-data>Processing data</h2><p>To take advantage of Hadoop, we have to design our code to work in the MapReduce model. Both the map and reduce phase work on key-value pairs as input and output, and both have a programmer-defined function.<p>We will use Java, because it’s a dependency that we already have anyway, so might as well.<p>Our map function needs to extract the year and air temperature, which will prepare the data for later use (finding the maximum temperature for each year). We will also drop bad records here (if the temperature is missing, suspect or erroneous).<p>Copy or reproduce the following code in a file called <code>MaxTempMapper.java</code>, using any text editor of your choice:<pre><code>import java.io.IOException; - -import org.apache.hadoop.io.IntWritable; -import org.apache.hadoop.io.LongWritable; -import org.apache.hadoop.io.Text; -import org.apache.hadoop.mapreduce.Mapper; - -public class MaxTempMapper extends Mapper<LongWritable, Text, Text, IntWritable> { - private static final int TEMP_MISSING = 9999; - private static final String GOOD_QUALITY_RE = "[01459]"; - - @Override - public void map(LongWritable key, Text value, Context context) - throws IOException, InterruptedException { - String line = value.toString(); - String year = line.substring(15, 19); - String temp = line.substring(87, 92).replaceAll("^\\+", ""); - String quality = line.substring(92, 93); - - int airTemperature = Integer.parseInt(temp); - if (airTemperature != TEMP_MISSING && quality.matches(GOOD_QUALITY_RE)) { - context.write(new Text(year), new IntWritable(airTemperature)); - } - } -} -</code></pre><p>Now, let’s create the <code>MaxTempReducer.java</code> file. Its job is to reduce the data from multiple values into just one. We do that by keeping the maximum out of all the values we receive:<pre><code>import java.io.IOException; -import java.util.Iterator; - -import org.apache.hadoop.io.IntWritable; -import org.apache.hadoop.io.Text; -import org.apache.hadoop.mapreduce.Reducer; - -public class MaxTempReducer extends Reducer<Text, IntWritable, Text, IntWritable> { - @Override - public void reduce(Text key, Iterable<IntWritable> values, Context context) - throws IOException, InterruptedException { - Iterator<IntWritable> iter = values.iterator(); - if (iter.hasNext()) { - int maxValue = iter.next().get(); - while (iter.hasNext()) { - maxValue = Math.max(maxValue, iter.next().get()); - } - context.write(key, new IntWritable(maxValue)); - } - } -} -</code></pre><p>Except for some Java weirdness (…why can’t we just iterate over an <code>Iterator</code>? Or why can’t we just manually call <code>next()</code> on an <code>Iterable</code>?), our code is correct. There can’t be a maximum if there are no elements, and we want to avoid dummy values such as <code>Integer.MIN_VALUE</code>.<p>We can also take a moment to appreciate how absolutely tiny this code is, and it’s Java! Hadoop’s API is really awesome and lets us write such concise code to achieve what we need.<p>Last, let’s write the <code>main</code> method, or else we won’t be able to run it. In our new file <code>MaxTemp.java</code>:<pre><code>import org.apache.hadoop.fs.Path; -import org.apache.hadoop.io.IntWritable; -import org.apache.hadoop.io.Text; -import org.apache.hadoop.mapreduce.Job; -import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; -import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; - -public class MaxTemp { - public static void main(String[] args) throws Exception { - if (args.length != 2) { - System.err.println("usage: java MaxTemp <input path> <output path>"); - System.exit(-1); - } - - Job job = Job.getInstance(); - - job.setJobName("Max temperature"); - job.setJarByClass(MaxTemp.class); - job.setMapperClass(MaxTempMapper.class); - job.setReducerClass(MaxTempReducer.class); - job.setOutputKeyClass(Text.class); - job.setOutputValueClass(IntWritable.class); - - FileInputFormat.addInputPath(job, new Path(args[0])); - FileOutputFormat.setOutputPath(job, new Path(args[1])); - - boolean result = job.waitForCompletion(true); - - System.exit(result ? 0 : 1); - } -} -</code></pre><p>And compile by including the required <code>.jar</code> dependencies in Java’s classpath with the <code>-cp</code> switch:<pre><code>javac -cp "hadoop-3.2.1/share/hadoop/common/*:hadoop-3.2.1/share/hadoop/mapreduce/*" *.java -</code></pre><p>At last, we can run it (also specifying the dependencies in the classpath, this one’s a mouthful):<pre><code>java -cp ".:hadoop-3.2.1/share/hadoop/common/*:hadoop-3.2.1/share/hadoop/common/lib/*:hadoop-3.2.1/share/hadoop/mapreduce/*:hadoop-3.2.1/share/hadoop/mapreduce/lib/*:hadoop-3.2.1/share/hadoop/yarn/*:hadoop-3.2.1/share/hadoop/yarn/lib/*:hadoop-3.2.1/share/hadoop/hdfs/*:hadoop-3.2.1/share/hadoop/hdfs/lib/*" MaxTemp 190x results -</code></pre><p>Hooray! We should have a new <code>results/</code> folder along with the following files:<pre><code>$ ls results -part-r-00000 _SUCCESS -$ cat results/part-r-00000 -1901 317 -1902 244 -</code></pre><p>It worked! Now this example was obviously tiny, but hopefully enough to demonstrate how to get the basics running on real world data.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Big Data | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Big Data</h1><div class=time><p>2020-02-25T01:00:30+00:00<p>last updated 2020-03-18T09:51:17+00:00</div><p>Big Data sounds like a buzzword you may be hearing everywhere, but it’s actually here to stay!<h2 id=what-is-big-data>What is Big Data?</h2><p>And why is it so important? We use this term to refer to the large amount of data available, rapidly growing every day, that cannot be processed in conventional ways. It’s not only about the amount, it’s also about the variety and rate of growth.<p>Thanks to technological advancements, there are new ways to process this insane amount of data, which would otherwise be too costly for processing in traditional database systems.<h2 id=where-does-data-come-from>Where does data come from?</h2><p>It can be pictures in your phone, industry transactions, messages in social networks, a sensor in the mountains. It can come from anywhere, which makes the data very varied.<p>Just to give some numbers, over 12TB of data is generated on Twitter <em>daily</em>. If you purchase a laptop today (as of March 2020), the disk will be roughly 1TB, maybe 2TB. Twitter would fill 6 of those drives every day!<p>What about Facebook? It is estimated they store around 100PB of photos and videos. That would be 50000 laptop disks. Not a small number. And let’s not talk about worldwide network traffic…<h2 id=what-data-can-be-exploited>What data can be exploited?</h2><p>So, we have a lot of data. Should we attempt and process everything? We can distinguish several categories.<ul><li><strong>Web and Social Media</strong>: Clickstream Data, Twitter Feeds, Facebook Postings, Web content… Stuff coming from social networks.<li><strong>Biometrics</strong>: Facial Recognion, Genetics… Any kind of personal recognition.<li><strong>Machine-to-Machine</strong>: Utility Smart Meter Readings, RFID Readings, Oil Rig Sensor Readings, GPS Signals… Any sensor shared with other machines.<li><strong>Human Generated</strong>: Call Center Voice Recordings, Email, Electronic Medical Records… Even the voice notes one sends over WhatsApp count.<li><strong>Big Transaction Data</strong>: Healthcare Claims, Telecommunications Call Detail Records, Utility Billing Records… Financial transactions.</ul><p>But asking what to process is asking the wrong question. Instead, one should think about «What problem am I trying to solve?».<h2 id=how-to-exploit-this-data>How to exploit this data?</h2><p>What are some of the ways to deal with this data? If the problem fits the Map-Reduce paradigm then Hadoop is a great option! Hadoop is inspired by Google File System (GFS), and achieves great parallelism across the nodes of a cluster, and has the following components:<ul><li><strong>Hadoop Distributed File System</strong>. Data is divided into smaller «blocks» and distributed across the cluster, which makes it possible to execute the mapping and reduction in smaller subsets, and makes it possible to scale horizontally.<li><strong>Hadoop MapReduce</strong>. First, a data set is «mapped» into a different set, and data becomes a list of tuples (key, value). The «reduce» step works on these tuples and combines them into a smaller subset.<li><strong>Hadoop Common</strong>. These are a set of libraries that ease working with Hadoop.</ul><h2 id=key-insights>Key insights</h2><p>Big Data is a field whose goal is to extract information from very large sets of data, and find ways to do so. To summarize its different dimensions, we can refer to what’s known as «the Four V’s of Big Data»:<ul><li><strong>Volume</strong>. Really large quantities.<li><strong>Velocity</strong>. Processing response time matters!<li><strong>Variety</strong>. Data comes from plenty of sources.<li><strong>Veracity.</strong> Can we trust all sources, though?</ul><p>Some sources talk about a fifth V for <strong>Value</strong>; because processing this data is costly, it is important we can get value out of it.<p>…And some other sources go as high as seven V’s, including <strong>Viability</strong> and <strong>Visualization</strong>. Computers can’t take decissions on their own (yet), a human has to. And they can only do so if they’re presented the data (and visualize it) in a meaningful way.<h2 id=infographics>Infographics</h2><p>Let’s see some pictures, we all love pictures:<p><img src=https://lonami.dev/blog/mdad/big-data/4-Vs-of-big-data.jpg><h2 id=common-patterns>Common patterns</h2><h2 id=references>References</h2><ul><li>¿Qué es Big Data? – <a href=https://www.ibm.com/developerworks/ssa/local/im/que-es-big-data/>https://www.ibm.com/developerworks/ssa/local/im/que-es-big-data/</a><li>The Four V’s of Big Data – <a href=https://www.ibmbigdatahub.com/infographic/four-vs-big-data>https://www.ibmbigdatahub.com/infographic/four-vs-big-data</a><li>Big data – <a href=https://en.wikipedia.org/wiki/Big_data>https://en.wikipedia.org/wiki/Big_data</a><li>Las 5 V’s del Big Data – <a href=https://www.quanticsolutions.es/big-data/las-5-vs-del-big-data>https://www.quanticsolutions.es/big-data/las-5-vs-del-big-data</a><li>Las 7 V del Big data: Características más importantes – <a href=https://www.iic.uam.es/innovacion/big-data-caracteristicas-mas-importantes-7-v/#viabilidad>https://www.iic.uam.es/innovacion/big-data-caracteristicas-mas-importantes-7-v/</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,11 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Cassandra: Introducción | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Cassandra: Introducción</h1><div class=time><p>2020-03-05T00:00:33+00:00<p>last updated 2020-03-30T09:28:07+00:00</div><p><img src=https://lonami.dev/blog/mdad/cassandra-introduccion/1200px-Cassandra_logo.png><p>Este es el primer post en la serie sobre Cassandra, en el cuál introduciremos dicha bases de datos NoSQL y veremos sus características e instalación.<p>Otros posts en esta serie:<ul><li><a href=/blog/mdad/cassandra-introduccion/>Cassandra: Introducción</a> (este post)<li><a href=/blog/mdad/cassandra-operaciones-basicas-y-arquitectura/>Cassandra: Operaciones Básicas y Arquitectura</a></ul><p>Este post está hecho en colaboración con un compañero.<hr><h2 id=finalidad-de-la-tecnologia>Finalidad de la tecnología</h2><p>Apache Cassandra es una base de datos NoSQL distribuida y de código abierto (<a href=https://github.com/apache/cassandra>con un espejo en GitHub</a>). Su filosofía es de tipo «clave-valor», y puede manejar grandes volúmenes de datos<p>Entre sus objetivos, busca ser escalable horizontalmente (puede replicarse en varios centros manteniendo la latencia baja) y alta disponibilidad sin ceder en rendimiento.<h2 id=como-funciona>Cómo funciona</h2><p>Instancias de Cassandra se distribuyen en nodos iguales (es decir, no hay maestro-esclavo) que se comunican entre sí (P2P). De este modo, da buen soporte entre varios centros de datos, con redundancia y réplicas síncronas.<p><img src=https://lonami.dev/blog/mdad/cassandra-introduccion/multiple-data-centers-and-data-replication-in-cassandra.jpg><p>Con respecto al modelo de datos, Cassandra particiona las filas con el objetivo de re-organizarla a lo largo distintas tablas. Como clave primaria, se usa un primer componente conocido como «clave de la partición». Dentro de cada partición, las filas se agrupan según el resto de columnas de la clave. Cualquier otra columna se puede indexar independientemente de la clave primaria.<p>Las tablas se pueden crear, borrar, actualizar y consultar sin bloqueos. No hay soporte para JOIN o subconsultas, pero Cassandra prefiere de-normalizar los datos haciendo uso de características como coleciones.<p>Para realizar las operaciones sobre cassandra se usa CQL (Cassandra Query Language), que tiene una sintaxis muy similar a SQL.<h2 id=caracteristicas>Características</h2><p>Como ya hemos mencionado antes, la arquitectura de Cassandra es <strong>decentralizada</strong>. No tiene un único punto que pudiera fallar porque todos los nodos son iguales (sin maestros), y por lo tanto, cualquiera puede dar servicio a la petición.<p>Los datos se encuentran <strong>replicados</strong> entre los distintos nodos del clúster (lo que ofrece gran <strong>tolerancia a fallos</strong> sin necesidad de interrumpir la aplicación), y es trivial <strong>escalar</strong> añadiendo más nodos al sistema.<p>El nivel de <strong>consistencia</strong> para lecturas y escrituras es configurable.<p>Siendo de la familia Apache, Cassandra ofrece integración con Apache Hadoop para tener soporte MapReduce.<h2 id=arista-dentro-del-teorema-cap>Arista dentro del Teorema CAP</h2><p>Cassandra se encuentra dentro de la esquina «AP» junto con CouchDB y otros, porque garantiza tanto la disponibilidad como la tolerancia a fallos.<p>Sin embargo, puede configurarse como un sistema «CP» si se prefiere respetar la consistencia en todo momento.<p><img src=https://lonami.dev/blog/mdad/cassandra-introduccion/0.jpeg><h2 id=descarga>Descarga</h2><p>Se pueden seguir las instrucciones de la página oficial para <a href=https://cassandra.apache.org/download/>descargar Cassandra</a>. Para ello, se debe clicar en la <a href=https://www.apache.org/dyn/closer.lua/cassandra/3.11.6/apache-cassandra-3.11.6-bin.tar.gz>última versión para descargar el archivo</a>. En nuestro caso, esto es el enlace nombrado «3.11.6», versión que utilizamos.<h2 id=instalacion>Instalación</h2><p>Cassandra no ofrece binarios para Windows, por lo que usaremos Linux para instalarlo. En nuestro caso, tenemos un sistema Linux Mint (derivado de Ubuntu), pero una máquina virtual con cualquier Linux debería funcionar.<p>Debemos asegurarnos de tener Java y Python 2 instalado mediante el siguiente comando:<pre><code>apt install openjdk-8-jdk openjdk-8-jre python2.7 -</code></pre><p>Para verificar que la instalación ha sido correcta, podemos mostrar las versiones de los programas:<pre><code>$ java -version -openjdk version "1.8.0_242" -OpenJDK Runtime Environment (build 1.8.0_242-8u242-b08-0ubuntu3~18.04-b08) -OpenJDK 64-Bit Server VM (build 25.242-b08, mixed mode) - -$ python2 --version -Python 2.7.17 -</code></pre><p>Una vez las dependencias estén instaladas, extraemos el fichero descargado o bien mediante la interfaz gráfica de nuestro sistema, o bien mediante un comando:<pre><code>tar xf apache-cassandra-3.11.6-bin.tar.gz -</code></pre><p>Y finalmente, lanzar la ejecución de Cassandra:<pre><code>apache-cassandra-3.11.6/bin/cassandra -</code></pre><p>Es posible que tarde un poco en abrirse, pero luego debería haber muchas líneas de log indicando. Para apagar el servidor, simplemente basta con pulsar <code>Ctrl+C</code>.<h2 id=referencias>Referencias</h2><ul><li><a href=https://blog.yugabyte.com/apache-cassandra-architecture-how-it-works-lightweight-transactions/>Apache Cassandra Architecture Fundamentals – The Distributed SQL Blog</a><li><a href=https://cassandra.apache.org/>Apache Cassandra</a><li><a href=https://www.datastax.com/blog/2019/05/how-apache-cassandratm-balances-consistency-availability-and-performance>How Apache Cassandra™ Balances Consistency, Availability, and Performance – Datasax</a><li><a href=https://blog.yugabyte.com/apache-cassandra-architecture-how-it-works-lightweight-transactions/>Apache Cassandra Architecture Fundamentals</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,24 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Cassandra: Operaciones Básicas y Arquitectura | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Cassandra: Operaciones Básicas y Arquitectura</h1><div class=time><p>2020-03-05T02:00:41+00:00<p>last updated 2020-03-20T11:36:18+00:00</div><p>Este es el segundo post en la serie sobre Cassandra, con una breve descripción de las operaciones básicas (tales como inserción, recuperación e indexado), y ejecución por completo junto con el modelo de datos y arquitectura.<p>Otros posts en esta serie:<ul><li><a href=/blog/mdad/cassandra-introduccion/>Cassandra: Introducción</a><li><a href=/blog/mdad/cassandra-operaciones-basicas-y-arquitectura/>Cassandra: Operaciones Básicas y Arquitectura</a> (este post)</ul><p>Este post está hecho en colaboración con un compañero.<hr><p>Antes de poder ejecutar ninguna consulta, debemos lanzar la base de datos en caso de que no se encuentre en ejecución aún. Para ello, en una terminal, lanzamos el binario de <code>cassandra</code>:<pre><code>$ cassandra-3.11.6/bin/cassandra -</code></pre><p>Sin cerrar esta consola, abrimos otra en la que podamos usar la <a href=https://cassandra.apache.org/doc/latest/tools/cqlsh.html>CQL shell</a>:<pre><code>$ cassandra-3.11.6/bin/cqlsh -Connected to Test Cluster at 127.0.0.1:9042. -[cqlsh 5.0.1 | Cassandra 3.11.6 | CQL spec 3.4.4 | Native protocol v4] -Use HELP for help. -cqlsh> -</code></pre><h2 id=crear>Crear</h2><h3 id=crear-una-base-de-datos>Crear una base de datos</h3><p>Cassandra denomina a las «bases de datos» como «espacio de claves» (keyspace en inglés).<pre><code>cqlsh> create keyspace helloworld with replication = {'class': 'SimpleStrategy', 'replication_factor': 3}; -</code></pre><p>Cuando creamos un nuevo <em>keyspace</em>, indicamos el nombre y la estrategia de replicación a usar. Nosotros usamos la estrategia simple con un factor 3 de replicación.<h3 id=crear-una-tabla>Crear una tabla</h3><p>Una vez estemos dentro de un <em>keyspace</em>, podemos crear tablas. Vamos a crear una tabla llamada «greetings» con identificador (número entero), mensaje (texto) y lenguaje (<code>varchar</code>).<pre><code>cqlsh> use helloworld; -cqlsh:helloworld> create table greetings(id int primary key, message text, lang varchar); -</code></pre><h3 id=crear-una-fila>Crear una fila</h3><p>Insertar nuevas filas es similar a otros sistemas gestores de datos, mediante la sentencia <code>INSERT</code>:<pre><code>cqlsh:helloworld> insert into greetings(id, message, lang) values(1, '¡Bienvenido!', 'es'); -cqlsh:helloworld> insert into greetings(id, message, lang) values(2, 'Welcome!', 'es'); -</code></pre><h2 id=leer>Leer</h2><p>La lectura se lleva a cabo mediante la sentencia <code>SELECT</code>:<pre><code>cqlsh:helloworld> select * from greetings; - - id | lang | message -----+------+-------------- - 1 | es | ¡Bienvenido! - 2 | es | Welcome! - -(2 rows) -</code></pre><p><code>cqlsh</code> colorea la salida, lo cuál resulta muy útil para identificar la clave primaria y distintos tipos de datos como texto, cadenas o números:<p><img src=https://lonami.dev/blog/mdad/cassandra-operaciones-basicas-y-arquitectura/image.png><h2 id=actualizar>Actualizar</h2><p>La actualización se lleva a cabo con la sentencia <code>UPDATE</code>. Vamos a arreglar el fallo que hemos cometido al insertar «Welcome!» como español:<pre><code>cqlsh:helloworld> update greetings set lang = 'en' where id = 2; -</code></pre><h2 id=indexar>Indexar</h2><pre><code>cqlsh:helloworld> create index langIndex on greetings(lang); -</code></pre><h2 id=borrar>Borrar</h2><p>Finalmente, el borrado se lleva a cabo con la sentencia <code>DELETE</code>. Es posible borrar solo campos individuales, lo cuál los pone a nulos:<pre><code>cqlsh:helloworld> delete message from greetings where id = 1; -</code></pre><p>Para eliminar la fila entera, basta con no especificar la columna:<pre><code>cqlsh:helloworld> delete from greetings where id = 1; -</code></pre><h2 id=referencias>Referencias</h2><ul><li><a href=https://www.tutorialspoint.com/cassandra/cassandra_create_keyspace.htm>tutorialspoint – Creating a Keyspace using Cqlsh</a><li><a href=https://www.tutorialspoint.com/cassandra/cassandra_cql_datatypes.htm>tutorialspoint – Cassandra – CQL Datatypes</a><li><a href=https://www.tutorialspoint.com/cassandra/cassandra_create_table.htm>tutorialspoint – Cassandra – Create Table</a><li><a href=https://data-flair.training/blogs/cassandra-crud-operation/>Data Flair – Cassandra Crud Operation – Create, Update, Read & Delete</a><li><a href=https://cassandra.apache.org/doc/latest/cql/indexes.html>Cassandra Documentation – Secondary Indexes</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Data Warehousing and OLAP | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Data Warehousing and OLAP</h1><div class=time><p>2020-03-23T01:00:00+00:00<p>last updated 2020-04-01T09:45:41+00:00</div><p>Business intelligence (BI) refers to systems used to gain insights from data, traditionally taken from relational databases and being used to build a data warehouse. Performance and scalability are key aspects of BI systems.<p>Commonly, the data in the warehouse is a transformation of the original, operational data into a form better suited for reporting and analysis.<p>This whole process is known as Online Analytical Processing (OLAP), and is different to the approach taken by relational databases, which is known as Online Transaction Processing (OLTP) and is optimized for individual transactions. OLAP is based on multidimensional databases simply by the way it works.<p>The Business Intelligence Semantic Model (BISM) refers to the different semantics in which data can be accessed and queried.<p>On the one hand, MDX is the language used for Microsoft’s BISM of multidimensional mode, and on the other, DAX is the language of tabular mode, based on Excel’s formula language and designed to be easy to use by those familiar with Excel.<h2 id=types-of-data>Types of data</h2><p>The business data is often called detail data or <em>fact</em> data, goes in a de-normalized table called the fact table. The term «facts» literally refers to the facts, such as number of products sold and amount received for products sold. Different tables will often represent different dimensions of the data, where «dimensions» simply means different ways to look at the data.<p>Data can also be referred to as measures, because most of it is numbers and subject to aggregations. By measures, we refer to these values and numbers.<p>Multidimensional databases are formed with separate fact and dimension tables, grouped to create a «cube» with both facts and dimensions.<h2 id=places-to-store-data>Places to store data</h2><p>Three different terms are often heard when talking about the places where data is stored: data lakes, data warehouses, and data marts. All of these have different target users, cost, size and growth.<p>The data lake contains <strong>all</strong> the data generated by your business. Nothing is filtered out, not even cancelled or invalid transactions. If there are future plans to use the data, or a need to analyze it in various ways, a data lake is often necessary.<p>The data warehouse contains <strong>structured</strong> data, or has already been modelled. It’s also multi-purpose, but often of a lot smaller scale. Operational users are able to easily evaluate reports or analyze performance here, since it is built for their needs.<p>The data mart contains a <strong>small portion</strong> of the data, and is often part of data warehouses themselves. It can be seen as a subsection built for specific departments, and as a benefit, users get isolated security and performance. The data here is clean, and subject-oriented.<h2 id=ways-to-store-data>Ways to store data</h2><p>Data is often stored de-normalized, because it would not be feasible to store otherwise.<p>There are two main techniques to implement data warehouses, known as Inmon approach and Kimball approach. They are named after Ralph Kimball <em>et al.</em> for their work on «The Data Warehouse Lifecycle Toolkit», and Bill Inmon <em>et al.</em> for their work on «Corporate Information Factory» respectively.<p>When several independent systems identify and store data in different ways, we face what’s known as the problem of the stovepipe. Something as simple as trying to connect these systems or use their data in a warehouse results in an overly complicated system.<p>To tackle this issue, Kimball advocates the use of «conformed dimensions», that is, some dimensions will be «of interest», and have the same attributes and rollups (or at least a subset) in different data marts. This way, warehouses contain dimensional databases to ease analysis in the data marts it is composed of, and users query the warehouse.<p>The Inmon approach on the other hand has the warehouse laid out in third normal form, and users query the data marts, not the warehouse (so the data marts are dimensional in nature).<h2 id=key-takeaways>Key takeaways</h2><ul><li>«BI» stands for «Business Intelligence» and refers to the system that <em>perform</em> data analysis.<li>«BISM» stands for «Business Intelligence Semantic Model», and Microsoft has two languages to query data: MDX and DAX.<li>«OLAP» stands for «Online Analytical Processing», and «OLTP» for «Online Transaction Processing».<li>Data mart, warehouse and lake refer to places at different scales and with different needs to store data.<li>Inmon and Kimbal are different ways to implement data warehouses.<li>Data facts contains various measures arranged into different dimensions, which together form a data cube.</ul><h2 id=references>References</h2><ul><li><a href=https://media.wiley.com/product_data/excerpt/03/11181011/1118101103-157.pdf>Chapter 1 – Professional Microsoft SQL Server 2012 Analysis Services with MDX and DAX (Harinath et al., 2012)</a><li><a href=https://youtu.be/m_DzhW-2pWI>YouTube – Data Mining in SQL Server Analysis Services</a><li>Almacenes de Datos y Procesamiento Analítico On-Line (Félix R.)<li><a href=https://youtu.be/qkJOace9FZg>YouTube – What are Dimensions and Measures?</a><li><a href=https://www.holistics.io/blog/data-lake-vs-data-warehouse-vs-data-mart/>Data Lake vs Data Warehouse vs Data Mart</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,102 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Developing a Python application for Cassandra | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Developing a Python application for Cassandra</h1><div class=time><p>2020-03-23T00:00:00+00:00<p>last updated 2020-04-16T07:52:26+00:00</div><p><em><strong>Warning</strong>: this post is, in fact, a shameless self-plug to my own library. If you continue reading, you accept that you are okay with this. Otherwise, please close the tab, shut down your computer, and set it on fire.__(Also, that was a joke. Please don’t do that.)</em><p>Let’s do some programming! Today we will be making a tiny CLI application in <a href=http://python.org/>Python</a> that queries <a href=https://core.telegram.org/api>Telegram’s API</a> and stores the data in <a href=http://cassandra.apache.org/>Cassandra</a>.<h2 id=our-goal>Our goal</h2><p>Our goal is to make a Python console application. This application will connect to <a href=https://telegram.org/>Telegram</a>, and ask for your account credentials. Once you have logged in, the application will fetch all of your open conversations and we will store these in Cassandra.<p>With the data saved in Cassandra, we can now very efficiently query information about your conversations given their identifier offline (no need to query Telegram anymore).<p><strong>In short</strong>, we are making an application that performs efficient offline queries to Cassandra to print out information about your Telegram conversations given the ID you want to query.<h2 id=data-model>Data model</h2><p>The application itself is really simple, and we only need one table to store all the relevant information we will be needing. This table called <code>**users**</code> will contain the following columns:<ul><li><code>**id**</code>, of type <code>int</code>. This will also be the <code>primary key</code> and we’ll use it to query the database later on.<li><code>**first_name**</code>, of type <code>varchar</code>. This field contains the first name of the stored user.<li><code>**last_name**</code>, of type <code>varchar</code>. This field contains the last name of the stored user.<li><code>**username**</code>, of type <code>varchar</code>. This field contains the username of the stored user. Because Cassandra uses a <a href=https://cassandra.apache.org/doc/latest/architecture/overview.html>wide column storage model</a>, direct access through a key is the most efficient way to query the database. In our case, the key is the primary key of the <code>users</code> table, using the <code>id</code> column. The index for the primary key is ready to be used as soon as we create the table, so we don’t need to create it on our own.</ul><h2 id=dependencies>Dependencies</h2><p>Because we will program it in Python, you need Python installed. You can install it using a package manager of your choice or heading over to the <a href=https://www.python.org/downloads/>Python downloads section</a>, but if you’re on Linux, chances are you have it installed already.<p>Once Python 3.5 or above is installed, get a copy of the Cassandra driver for Python and Telethon through <code>pip</code>:<pre><code>pip install cassandra-driver telethon -</code></pre><p>For more details on that, see the <a href=https://docs.datastax.com/en/developer/python-driver/3.22/installation/>installation guide for <code>cassandra-driver</code></a>, or the <a href=https://docs.telethon.dev/en/latest/basic/installation.html>installation guide for <code>telethon</code></a>.<p>As we did in our <a href=/blog/mdad/cassandra-operaciones-basicas-y-arquitectura/>previous post</a>, we will setup a new keyspace for this application with <code>cqlsh</code>. We will also create a table to store the users into. This could all be automated in the Python code, but because it’s a one-time thing, we prefer to use <code>cqlsh</code>.<p>Make sure that Cassandra is running in the background. We can’t make queries to it if it’s not running.<pre><code>$ bin/cqlsh -Connected to Test Cluster at 127.0.0.1:9042. -[cqlsh 5.0.1 | Cassandra 3.11.6 | CQL spec 3.4.4 | Native protocol v4] -Use HELP for help. -cqlsh> create keyspace mdad with replication = {'class': 'SimpleStrategy', 'replication_factor': 3}; -cqlsh> use mdad; -cqlsh:mdad> create table users(id int primary key, first_name varchar, last_name varchar, username varchar); -</code></pre><p>Python installed? Check. Python dependencies? Check. Cassandra ready? Check.<h2 id=the-code>The code</h2><h3 id=getting-users>Getting users</h3><p>The first step is connecting to <a href=https://core.telegram.org/api>Telegram’s API</a>, for which we’ll use <a href=https://telethon.dev/>Telethon</a>, a wonderful (wink, wink) Python library to interface with it.<p>As with most APIs, we need to supply <a href=https://my.telegram.org/>our API key</a> in order to use it (here <code>API_ID</code> and <code>API_HASH</code>). We will refer to them as constants. At the end, you may download the entire code and use my own key for this example. But please don’t use those values for your other applications!<p>It’s pretty simple: we create a client, and for every dialog (that is, open conversation) we have, do some checks:<ul><li>If it’s an user, we just store that in a dictionary mapping <code>ID → User</code>.<li>Else if it’s a group, we iterate over the participants and store those users instead.</ul><pre><code>async def load_users(): - from telethon import TelegramClient - - users = {} - - async with TelegramClient(SESSION, API_ID, API_HASH) as client: - async for dialog in client.iter_dialogs(): - if dialog.is_user: - user = dialog.entity - users[user.id] = user - print('found user:', user.id, file=sys.stderr) - - elif dialog.is_group: - async for user in client.iter_participants(dialog): - users[user.id] = user - print('found member:', user.id, file=sys.stderr) - - return list(users.values()) -</code></pre><p>With this we have a mapping ID to user, so we know we won’t have duplicates. We simply return the list of user values, because that’s all we care about.<h3 id=saving-users>Saving users</h3><p>Inserting users into Cassandra is pretty straightforward. We take the list of <code>User</code> objects as input, and prepare a new <code>INSERT</code> statement that we can reuse (because we will be using it in a loop, this is the best way to do it).<p>For each user, execute the statement with the user data as input parameters. Simple as that.<pre><code>def save_users(session, users): - insert_stmt = session.prepare( - 'INSERT INTO users (id, first_name, last_name, username) ' - 'VALUES (?, ?, ?, ?)') - - for user in users: - row = (user.id, user.first_name, user.last_name, user.username) - session.execute(insert_stmt, row) -</code></pre><h3 id=fetching-users>Fetching users</h3><p>Given a list of users, yield all of them from the database. Similar to before, we prepare a <code>SELECT</code> statement and just execute it repeatedly over the input user IDs.<pre><code>def fetch_users(session, users): - select_stmt = session.prepare('SELECT * FROM users WHERE id = ?') - - for user_id in users: - yield session.execute(select_stmt, (user_id,)).one() -</code></pre><h3 id=parsing-arguments>Parsing arguments</h3><p>We’ll be making a little CLI application, so we need to parse console arguments. It won’t be anything fancy, though. For that we’ll be using <a href=https://docs.python.org/3/library/argparse.html>Python’s <code>argparse</code> module</a>:<pre><code>def parse_args(): - import argparse - - parser = argparse.ArgumentParser( - description='Dump and query Telegram users') - - parser.add_argument('users', type=int, nargs='*', - help='one or more user IDs to query for') - - parser.add_argument('--load-users', action='store_true', - help='load users from Telegram (do this first run)') - - return parser.parse_args() -</code></pre><h3 id=all-together>All together</h3><p>Last, the entry point. We import a Cassandra Cluster, and connect to some default keyspace (we called it <code>mdad</code> earlier).<p>If the user wants to load the users into the database, we’ll do just that first.<p>Then, for each user we fetch from the database, we print it. Last names and usernames are optional, so don’t print those if they’re missing (<code>None</code>).<pre><code>async def main(args): - from cassandra.cluster import Cluster - - cluster = Cluster(CLUSTER_NODES) - session = cluster.connect(KEYSPACE) - - if args.load_users: - users = await load_users() - save_users(session, users) - - for user in fetch_users(session, args.users): - print('User', user.id, ':') - print(' First name:', user.first_name) - if user.last_name: - print(' Last name:', user.last_name) - if user.username: - print(' Username:', user.username) - - print() - -if __name__ == '__main__': - asyncio.run(main(parse_args())) -</code></pre><p>Because Telethon is an <code>[asyncio](https://docs.python.org/3/library/asyncio.html)</code> library, we define it as <code>async def main(...)</code> and run it with <code>asyncio.run(main(...))</code>.<p>Here’s what it looks like in action:<pre><code>$ python data.py --help -usage: data.py [-h] [--load-users] [users [users ...]] - -Dump and query Telegram users - -positional arguments: - users one or more user IDs to query for - -optional arguments: - -h, --help show this help message and exit - --load-users load users from Telegram (do this first run) - -$ python data.py --load-users -found user: 487158 -found member: 59794114 -found member: 487158 -found member: 191045991 -(...a lot more output) - -$ python data.py 487158 59794114 -User 487158 : - First name: Rick - Last name: Pickle - -User 59794114 : - Firt name: Peter - Username: pete -</code></pre><p>Telegram’s data now persists in Cassandra, and we can efficiently query it whenever we need to! I would’ve shown a video presenting its usage, but I’m afraid that would leak some of the data I want to keep private :-).<p>Feel free to download the code and try it yourself:<p><em>download removed</em><h2 id=references>References</h2><ul><li><a href=https://docs.datastax.com/en/developer/python-driver/3.22/getting_started/>DataStax Python Driver for Apache Cassandra – Getting Started</a><li><a href=https://docs.telethon.dev/en/latest/>Telethon’s Documentation</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Privado: Final NoSQL evaluation | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Privado: Final NoSQL evaluation</h1><div class=time><p>2020-05-13T00:00:00+00:00<p>last updated 2020-05-14T08:31:06+00:00</div><p>This evaluation is a bit different to my <a href=/blog/mdad/nosql-evaluation/>previous one</a> because this time I have been tasked to evaluate student <code>a(i - 2)</code>, and because I am <code>i = 11</code> that happens to be <code>a(9) =</code> a classmate.<h2 id=classmate-s-evaluation>Classmate’s Evaluation</h2><p><strong>Grading: A.</strong><p>The post I have evaluated is Trabajo en grupo – Bases de datos NoSQL, 3ª entrada: Aplicación con una Base de datos NoSQL seleccionada.<p>It starts with a very brief introduction with who has written the post, what data they will be using, and what database they have chosen.<p>They properly describe their objective, how they will do it and what library will be used.<p>They also explain where they obtain the data from, and what other things the site can do, which is a nice bonus.<p>The post continues listing and briefly explaining all the tools used and what they are for, including commands to execute.<p>At last, they list what files their project uses, what they do, and contains a showcase of images which lets the reader know what the application does.<p>All in all, in my opinion, it’s clear they have put work into this entry and I have not noticed any major flaws, so they deserve the highest grade.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,38 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Data Mining and Data Warehousing</h1><p id=welcome onclick=pls_stop()>Welcome to my blog!<p>Here I occasionally post new entries, mostly tech related. Perhaps it's tips for a new game I'm playing, perhaps it has something to do with FFI, or perhaps I'm fighting the borrow checker (just kidding, I'm over that. Mostly).<hr><ul><li><a href=https://lonami.dev/blog/mdad/final-nosql-evaluation/>Privado: Final NoSQL evaluation</a><span class=dim> </span><li><a href=https://lonami.dev/blog/mdad/a-practical-example-with-hadoop/>A practical example with Hadoop</a><span class=dim> </span><li><a href=https://lonami.dev/blog/mdad/introduction-to-hadoop-and-its-mapreduce/>Introduction to Hadoop and its MapReduce</a><span class=dim> </span><li><a href=https://lonami.dev/blog/mdad/data-warehousing-and-olap/>Data Warehousing and OLAP</a><span class=dim> </span><li><a href=https://lonami.dev/blog/mdad/developing-a-python-application-for-cassandra/>Developing a Python application for Cassandra</a><span class=dim> </span><li><a href=https://lonami.dev/blog/mdad/mining-of-massive-datasets/>Mining of Massive Datasets</a><span class=dim> </span><li><a href=https://lonami.dev/blog/mdad/nosql-evaluation/>Privado: NoSQL evaluation</a><span class=dim> </span><li><a href=https://lonami.dev/blog/mdad/visualizing-caceres-opendata/>Visualizing Cáceres’ OpenData</a><span class=dim> </span><li><a href=https://lonami.dev/blog/mdad/mongodb-operaciones-basicas-y-arquitectura/>MongoDB: Operaciones Básicas y Arquitectura</a><span class=dim> </span><li><a href=https://lonami.dev/blog/mdad/cassandra-operaciones-basicas-y-arquitectura/>Cassandra: Operaciones Básicas y Arquitectura</a><span class=dim> </span><li><a href=https://lonami.dev/blog/mdad/mongodb-introduction/>MongoDB: Introducción</a><span class=dim> </span><li><a href=https://lonami.dev/blog/mdad/cassandra-introduccion/>Cassandra: Introducción</a><span class=dim> </span><li><a href=https://lonami.dev/blog/mdad/introduction-to-nosql/>Introduction to NoSQL</a><span class=dim> </span><li><a href=https://lonami.dev/blog/mdad/big-data/>Big Data</a><span class=dim> </span><li><a href=https://lonami.dev/blog/mdad/what-is-an-algorithm/>What is an algorithm?</a><span class=dim> </span></ul><script> - const WELCOME_EN = 'Welcome to my blog!' - const WELCOME_ES = '¡Bienvenido a mi blog!' - const APOLOGIES = "ok sorry i'll stop" - const REWRITE_DELAY = 5000 - const CHAR_DELAY = 30 - const welcome = document.getElementById('welcome') - - let deleting = true - let english = false - let stopped = false - - const pls_stop = () => { - stopped = true - welcome.innerHTML = APOLOGIES - } - - const begin_rewrite = () => { - if (stopped) { - // now our visitor is angry :( - } else if (deleting) { - if (welcome.innerHTML == '…') { - deleting = false - } else { - welcome.innerHTML = welcome.innerHTML.slice(0, -1) || '…' - } - setTimeout(begin_rewrite, CHAR_DELAY) - } else { - let text = english ? WELCOME_EN : WELCOME_ES - welcome.innerHTML = text.slice(0, welcome.innerHTML.length + 1) - deleting = welcome.innerHTML.length == text.length - english = deleting - english - setTimeout(begin_rewrite, deleting ? REWRITE_DELAY : CHAR_DELAY) - } - } - - setTimeout(begin_rewrite, REWRITE_DELAY) -</script></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Introduction to Hadoop and its MapReduce | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Introduction to Hadoop and its MapReduce</h1><div class=time><p>2020-03-30T00:00:00+00:00<p>last updated 2020-04-01T11:01:46+00:00</div><p>Hadoop is an open-source, free, Java-based programming framework that helps processing large datasets in a distributed environment and the problems that arise when trying to harness the knowledge from BigData, capable of running on thousands of nodes and dealing with petabytes of data. It is based on Google File System (GFS) and originated from the work on the Nutch open-source project on search engines.<p>Hadoop also offers a distributed filesystem (HDFS) enabling for fast transfer among nodes, and a way to program with MapReduce.<p>It aims to strive for the 4 V’s: Volume, Variety, Veracity and Velocity. For veracity, it is a secure environment that can be trusted.<h2 id=milestones>Milestones</h2><p>The creators of Hadoop are Doug Cutting and Mike Cafarella, who just wanted to design a search engine, Nutch, and quickly found the problems of dealing with large amounts of data. They found their solution with the papers Google published.<p>The name comes from the plush of Cutting’s child, a yellow elephant.<ul><li>In July 2005, Nutch used GFS to perform MapReduce operations.<li>In February 2006, Nutch started a Lucene subproject which led to Hadoop.<li>In April 2007, Yahoo used Hadoop in a 1 000-node cluster.<li>In January 2008, Apache took over and made Hadoop a top-level project.<li>In July 2008, Apache tested a 4000-node cluster. The performance was the fastest compared to other technologies that year.<li>In May 2009, Hadoop sorted a petabyte of data in 17 hours.<li>In December 2011, Hadoop reached 1.0.<li>In May 2012, Hadoop 2.0 was released with the addition of YARN (Yet Another Resource Navigator) on top of HDFS, splitting MapReduce and other processes into separate components, greatly improving the fault tolerance.</ul><p>From here onwards, many other alternatives have born, like Spark, Hive & Drill, Kafka, HBase, built around the Hadoop ecosystem.<p>As of 2017, Amazon has clusters between 1 and 100 nodes, Yahoo has over 100 000 CPUs running Hadoop, AOL has clusters with 50 machines, and Facebook has a 320-machine (2 560 cores) and 1.3PB of raw storage.<h2 id=why-not-use-rdbms>Why not use RDBMS?</h2><p>Relational database management systems simply cannot scale horizontally, and vertical scaling will require very expensive servers. Similar to RDBMS, Hadoop has a notion of jobs (analogous to transactions), but without ACID or concurrency control. Hadoop supports any form of data (unstructured or semi-structured) in read-only mode, and failures are common but there’s a simple yet efficient fault tolerance.<p>So what problems does Hadoop solve? It solves the way we should think about problems, and distributing them, which is key to do anything related with BigData nowadays. We start working with clusters of nodes, and coordinating the jobs between them. Hadoop’s API makes this really easy.<p>Hadoop also takes very seriously the loss of data with replication, and if a node falls, they are moved to a different node.<h2 id=major-components>Major components</h2><p>The previously-mentioned HDFS runs on commodity machine, which are cost-friendly. It is very fault-tolerant and efficient enough to process huge amounts of data, because it splits large files into smaller chunks (or blocks) that can be more easily handled. Multiple nodes can work on multiple chunks at the same time.<p>NameNode stores the metadata of the various datablocks (map of blocks) along with their location. It is the brain and the master in Hadoop’s master-slave architecture, also known as the namespace, and makes use of the DataNode.<p>A secondary NameNode is a replica that can be used if the first NameNode dies, so that Hadoop doesn’t shutdown and can restart.<p>DataNode stores the blocks of data, and are the slaves in the architecture. This data is split into one or more files. Their only job is to manage this access to the data. They are often distributed among racks to avoid data lose.<p>JobTracker creates and schedules jobs from the clients for either map or reduce operations.<p>TaskTracker runs MapReduce tasks assigned to the current data node.<p>When clients need data, they first interact with the NameNode and replies with the location of the data in the correct DataNode. Client proceeds with interaction with the DataNode.<h2 id=mapreduce>MapReduce</h2><p>MapReduce, as the name implies, is split into two steps: the map and the reduce. The map stage is the «divide and conquer» strategy, while the reduce part is about combining and reducing the results.<p>The mapper has to process the input data (normally a file or directory), commonly line-by-line, and produce one or more outputs. The reducer uses all the results from the mapper as its input to produce a new output file itself.<p><img src=https://lonami.dev/blog/mdad/introduction-to-hadoop-and-its-mapreduce/bitmap.png><p>When reading the data, some may be junk that we can choose to ignore. If it is valid data, however, we label it with a particular type that can be useful for the upcoming process. Hadoop is responsible for splitting the data accross the many nodes available to execute this process in parallel.<p>There is another part to MapReduce, known as the Shuffle-and-Sort. In this part, types or categories from one node get moved to a different node. This happens with all nodes, so that every node can work on a complete category. These categories are known as «keys», and allows Hadoop to scale linearly.<h2 id=references>References</h2><ul><li><a href=https://youtu.be/oT7kczq5A-0>YouTube – Hadoop Tutorial For Beginners | What Is Hadoop? | Hadoop Tutorial | Hadoop Training | Simplilearn</a><li><a href=https://youtu.be/bcjSe0xCHbE>YouTube – Learn MapReduce with Playing Cards</a><li><a href=https://youtu.be/j8ehT1_G5AY?list=PLi4tp-TF_qjM_ed4lIzn03w7OnEh0D8Xi>YouTube – Video Post #2: Hadoop para torpes (I)-¿Qué es y para qué sirve?</a><li><a href=https://youtu.be/NQ8mjVPCDvk?list=PLi4tp-TF_qjM_ed4lIzn03w7OnEh0D8Xi>Video Post #3: Hadoop para torpes (II)-¿Cómo funciona? HDFS y MapReduce</a><li><a href=https://hadoop.apache.org/old/releases.html>Apache Hadoop Releases</a><li><a href=https://youtu.be/20qWx2KYqYg?list=PLi4tp-TF_qjM_ed4lIzn03w7OnEh0D8Xi>Video Post #4: Hadoop para torpes (III y fin)- Ecosistema y distribuciones</a><li><a href=http://www.hadoopbook.com/>Chapter 2 – Hadoop: The Definitive Guide, Fourth Edition</a> (<a href=http://grut-computing.com/HadoopBook.pdf>pdf,</a><a href=http://www.hadoopbook.com/code.html>code</a>)</ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Introduction to NoSQL | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Introduction to NoSQL</h1><div class=time><p>2020-02-25T02:00:30+00:00<p>last updated 2020-03-18T09:51:33+00:00</div><p>This post will primarly focus on the talk held in the <a href=https://youtu.be/qI_g07C_Q5I>GOTO 2012 conference: Introduction to NoSQL by Martin Fowler</a>. It can be seen as an informal, summarized transcript of the talk<hr><p>The relational database model is affected by the <em><a href=https://en.wikipedia.org/wiki/Object-relational_impedance_mismatch>impedance mismatch problem</a></em>. This occurs because we have to match our high-level design with the separate columns and rows used by relational databases.<p>Taking the in-memory objects and putting them into a relational database (which were dominant at the time) simply didn’t work out. Why? Relational databases were more than just databases, they served as a an integration mechanism across applications, up to the 2000s. For 20 years!<p>With the rise of the Internet and the sheer amount of traffic, databases needed to scale. Unfortunately, relational databases only scale well vertically (by upgrading a <em>single</em> node). This is <em>very</em> expensive, and not something many could afford.<p>The problem are those pesky <code>JOIN</code>‘s, and its friends <code>GROUP BY</code>. Because our program and reality model don’t match the tables used by SQL, we have to rely on them to query the data. It is because the model doesn’t map directly.<p>Furthermore, graphs don’t map very well at all to relational models.<p>We needed a way to scale horizontally (by increasing the <em>amount</em> of nodes), something relational databases were not designed to do.<blockquote><p><em>We need to do something different, relational across nodes is an unnatural act</em></blockquote><p>This inspired the NoSQL movement.<blockquote><p><em>#nosql was only meant to be a hashtag to advertise it, but unfortunately it’s how it is called now</em></blockquote><p>It is not possible to define NoSQL, but we can identify some of its characteristics:<ul><li>Non-relational<li><strong>Cluster-friendly</strong> (this was the original spark)<li>Open-source (until now, generally)<li>21st century web culture<li>Schema-less (easier integration or conjugation of several models, structure aggregation)</ul><p>These databases use different data models to those used by the relational model. However, it is possible to identify 4 broad chunks (some may say 3, or even 2!):<ul><li><strong>Key-value store</strong>. With a certain key, you obtain the value corresponding to it. It knows nothing else, nor does it care. We say the data is opaque.<li><strong>Document-based</strong>. It stores an entire mass of documents with complex structure, normally through the use of JSON (XML has been left behind). Then, you can ask for certain fields, structures, or portions. We say the data is transparent.<li><strong>Column-family</strong>. There is a «row key», and within it we store multiple «column families» (columns that fit together, our aggregate). We access by row-key and column-family name.</ul><p>All of these kind of serve to store documents without any <em>explicit</em> schema. Just shove in anything! This gives a lot of flexibility and ease of migration, except… that’s not really true. There’s an <em>implicit</em> schema when querying.<p>For example, a query where we may do <code>anOrder['price'] * anOrder['quantity']</code> is assuming that <code>anOrder</code> has both a <code>price</code> and a <code>quantity</code>, and that both of these can be multiplied together. «Schema-less» is a fuzzy term.<p>However, it is the lack of a <em>fixed</em> schema that gives flexibility.<p>One could argue that the line between key-value and document-based is very fuzzy, and they would be right! Key-value databases often let you include additional metadata that behaves like an index, and in document-based, documents often have an identifier anyway.<p>The common notion between these three types is what matters. They save an entire structure as an <em>unit</em>. We can refer to these as «Aggregate Oriented Databases». Aggregate, because we group things when designing or modeling our systems, as opposed to relational databases that scatter the information across many tables.<p>There exists a notable outlier, though, and that’s:<ul><li><strong>Graph</strong> databases. They use a node-and-arc graph structure. They are great for moving on relationships across things. Ironically, relational databases are not very good at jumping across relationships! It is possibly to perform very interesting queries in graph databases which would be really hard and costly on relational models. Unlike the aggregated databases, graphs break things into even smaller units. NoSQL is not <em>the</em> solution. It depends on how you’ll work with your data. Do you need an aggregate database? Will you have a lot of relationships? Or would the relational model be good fit for you?</ul><p>NoSQL, however, is a good fit for large-scale projects (data will <em>always</em> grow) and faster development (the impedance mismatch is drastically reduced).<p>Regardless of our choice, it is important to remember that NoSQL is a young technology, which is still evolving really fast (SQL has been stable for <em>decades</em>). But the <em>polyglot persistence</em> is what matters. One must know the alternatives, and be able to choose.<hr><p>Relational databases have the well-known ACID properties: Atomicity, Consistency, Isolation and Durability.<p>NoSQL (except graph-based!) are about being BASE instead: Basically Available, Soft state, Eventual consistency.<p>SQL needs transactions because we don’t want to perform a read while we’re only half-way done with a write! The readers and writers are the problem, and ensuring consistency results in a performance hit, even if the risk is low (two writers are extremely rare but it still must be handled).<p>NoSQL on the other hand doesn’t need ACID because the aggregate <em>is</em> the transaction boundary. Even before NoSQL itself existed! Any update is atomic by nature. When updating many documents it <em>is</em> a problem, but this is very rare.<p>We have to distinguish between logical and replication consistency. During an update and if a conflict occurs, it must be resolved to preserve the logical consistency. Replication consistency on the other hand is preserveed when distributing the data across many machines, for example during sharding or copies.<p>Replication buys us more processing power and resillence (at the cost of more storage) in case some of the nodes die. But what happens if what dies is the communication across the nodes? We could drop the requests and preserve the consistency, or accept the risk to continue and instead preserve the availability.<p>The choice on whether trading consistency for availability is acceptable or not depends on the domain rules. It is the domain’s choice, the business people will choose. If you’re Amazon, you always want to be able to sell, but if you’re a bank, you probably don’t want your clients to have negative numbers in their account!<p>Regardless of what we do, in a distributed system, the CAP theorem always applies: Consistecy, Availability, Partitioning-tolerancy (error tolerancy). It is <strong>impossible</strong> to guarantee all 3 at 100%. Most of the times, it does work, but it is mathematically impossible to guarantee at 100%.<p>A database has to choose what to give up at some point. When designing a distributed system, this must be considered. Normally, the choice is made between consistency or response time.<h2 id=further-reading>Further reading</h2><ul><li><a href=https://www.martinfowler.com/articles/nosql-intro-original.pdf>The future is: <del>NoSQL Databases</del> Polyglot Persistence</a><li><a href=https://www.thoughtworks.com/insights/blog/nosql-databases-overview>NoSQL Databases: An Overview</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Mining of Massive Datasets | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Mining of Massive Datasets</h1><div class=time><p>2020-03-16T01:00:00+00:00<p>last updated 2020-03-28T19:09:44+00:00</div><p>In this post we will talk about the Chapter 1 of the book Mining of Massive Datasets Leskovec, J. et al., available online, and I will summarize and share my thoughts on it.<p>Data mining often refers to the discovery of models for data, where the model can be for statistics, machine learning, summarizing, extracting features, or other computational approaches to perform complex queries on the data.<p>Commonly, problems related to data mining involve discovering unusual events hidden in massive data sets. There is another problem when trying to achieve Total Information Awareness (TIA), though, a project that was proposed by the Bush administration but shut down. The problem is, if you look at so much data, and try to find activities that look like (for example) terrorist behavior, inevitably one will find other illicit activities that are not terrorism with bad consequences. So it is important to narrow the activities we are looking for, in this case.<p>When looking at data, even completely random data, for a certain event type, the event will likely occur. With more data, it will occur more times. However, these are bogus results. The Bonferroni correction gives a statistically sound way to avoid most of these bogus results, however, the Bonferroni’s Principle can be used as an informal version to achieve the same thing.<p>For that, we calculate the expected number of occurrences of the events you are looking for on the assumption that data is random. If this number is way larger than the number of real instances one hoped to find, then nearly everything will be Bogus.<hr><p>When analysing documents, some words will be more important than others, and can help determine the topic of the document. One could think the most repeated words are the most important, but that’s far from the truth. The most common words are the stop-words, which carry no meaning, reason why we should remove them prior to processing. We are mostly looking for rare nouns.<p>There are of course formal measures for how concentrated into relatively few documents are the occurrences of a given word, known as TF.IDF (Term Frequency times In-verse Document Frequency). We won’t go into details on how to compute it, because there are multiple ways.<p>Hash functions are also frequently used, because they can turn hash keys into a bucket number (the index of the bucket where this hash key belongs). They «randomize» and spread the universe of keys into a smaller number of buckets, useful for storage and access.<p>An index is an efficient structure to query for values given a key, and can be built with hash functions and buckets.<p>Having all of these is important when analysing documents when doing data mining, because otherwise it would take far too long.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,28 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> MongoDB: Introducción | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>MongoDB: Introducción</h1><div class=time><p>2020-03-05T01:00:18+00:00<p>last updated 2020-03-20T10:31:10+00:00</div><p>Este es el primer post en la serie sobre Mongo, en el cuál introduciremos dicha bases de datos NoSQL y veremos sus características e instalación.<p>Otros posts en esta serie:<ul><li><a href=/blog/mdad/mongodb-introduction/>MongoDB: Introducción</a> (este post)<li><a href=/blog/mdad/mongodb-operaciones-basicas-y-arquitectura/>MongoDB: Operaciones Básicas y Arquitectura</a></ul><p>Este post está hecho en colaboración con un compañero.<hr><p><img src=https://lonami.dev/blog/mdad/mongodb-introduction/0LRP4__jIIkJ-0gl8j2RDzWscL1Rto-NwvdqzmYk0jmYBIVbJ78n1ZLByPgV.png><h2 id=definicion>Definición</h2><p>MongoDB es una base de datos orientada a documentos. Esto quiere decir que en lugar de guardar los datos en registros, guarda los datos en documentos. Estos documentos son almacenados en BSON, que es una representación binaria de JSON. Una de las principales diferencias respecto a las bases de datos relacionales es que no necesita seguir ningún esquema, los documentos de una misma colección pueden tener esquemas diferentes.<p>MongoDB está escrito en C++, aunque las consultas se hacen pasando objetos JSON como parámetro.<pre><code>{ - "_id" : ObjectId("52f602d787945c344bb4bda5"), - "name" : "Tyrion", - "hobbies" : [ - "books", - "girls", - "wine" - ], - "friends" : [ - { - "name" : "Bronn", - "ocuppation" : "sellsword" - }, - { - "name" : "Shae", - "ocuppation" : "handmaiden" - } - ] - } -</code></pre><h2 id=caracteristicas>Características</h2><p><img src=https://lonami.dev/blog/mdad/mongodb-introduction/WxZenSwSsimGvXVu5XH4cFUd3kr3Is_arrdSZGX8Hi0Ligqgw_ZTvGSIeXZm.png><p>MongoDB alcanza un balance perfecto entre rendimiento y funcionalidad gracias a su sistema de consulta de contenidos. Pero sus características principales no se limitan solo a esto, también cuenta con otras que lo posicionan como el preferido de muchos desarrolladores de aplicaciones como aplicaciones móviles, gaming, logging o e-commerce.<p>Algunas de las principales características de esta base de datos son:<ul><li>Almacenamiento orientado a documentos (documentos JSON con esquemas dinámicos).<li>Soporte Full index: puede crear índices sobre cualquier atributo y añadir múltiples índices secundarios.<li>Replicación y alta disponibilidad: espejos entre LANs y WANs.<li>Auto-Sharding: escalabilidad horizontal sin comprometer la funcionalidad, está limitada, actualmente, a 20 nodos, aunque el objetivo es alcanzar una cifra cercana a los 1000.<li>Consultas ricas y basadas en documentos.<li>Rápidas actualizaciones en el contexto.<li>Soporte comercial, capacitación y consultoría disponibles.<li>También puede ser utilizada para el almacenamiento de archivos aprovechando la capacidad de MongoDB para el balanceo de carga y la replicación de datos.</ul><p>En cuanto a la arquitectura, podríamos decir que divide en tres partes: las bases de datos, las colecciones y los documentos (que contienen los campos de cada entrada).<ul><li><strong>Base de datos</strong>: cada una de las bases de datos tiene un conjunto propio de archivos en el sistema de archivos con diversas bases de datos existentes en un solo servidor.<li><strong>Colección</strong>: un conjunto de documentos de base de datos. El equivalente RDBMS de la colección es una tabla. Toda colección existe dentro de una única base de datos.<li><strong>Documento</strong>: un conjunto de pares clave/valor. Los documentos están asociados con esquemas dinámicos. La ventaja de tener esquemas dinámicos es que el documento en una sola colección no tiene que tener la misma estructura o campos.</ul><h2 id=arista-dentro-del-teorema-cap>Arista dentro del Teorema CAP</h2><p><img src=https://lonami.dev/blog/mdad/mongodb-introduction/t73Q1t-HXfWij-Q1o5AYEnO39Kz2oyLLCdQz6lWQQPaSQWamlDMjmptAn97h.png><p>MongoDB es CP por defecto, es decir, garantiza consistencia y tolerancia a particiones (fallos). Pero también podemos configurar el nivel de consistencia, eligiendo el número de nodos a los que se replicarán los datos. O podemos configurar si se pueden leer datos de los nodos secundarios (en MongoDB solo hay un servidor principal, que es el único que acepta inserciones o modificaciones). Si permitimos leer de un nodo secundario mediante la replicación, sacrificamos consistencia, pero ganamos disponibilidad.<h2 id=descarga-e-instalacion>Descarga e instalación</h2><h3 id=windows>Windows</h3><p>Descargar el archivo desde <a href=https://www.mongodb.com/download-center#production>https://www.mongodb.com/download-center#production</a><ol><li>Doble clic en el archivo <code>.msi</code><li>El instalador de Windows lo guía a través del proceso de instalación. Si elige la opción de instalación personalizada, puede especificar un directorio de instalación. MongoDB no tiene ninguna otra dependencia del sistema. Puede instalar y ejecutar MongoDB desde cualquier carpeta que elija.<li>Ejecutar el <code>.exe</code> que hemos instalado.</ol><h3 id=linux>Linux</h3><p>Abrimos una terminal y ejecutamos:<pre><code>sudo apt-get update -sudo apt install -y mongodb-org -</code></pre><p>Luego comprobamos el estado del servicio:<pre><code>sudo systemctl start mongod -sudo systemctl status mongod -</code></pre><p>Finalmente ejecutamos la base de datos con el comando:<pre><code>sudo mongo -</code></pre><h3 id=macos>macOS</h3><p>Abrimos una terminal y ejecutamos:<pre><code>brew update -brew install mongodb -</code></pre><p>Iniciamos el servicio:<pre><code>brew services start mongodb -</code></pre><h2 id=referencias>Referencias</h2><ul><li><a href=https://expertoenbigdata.com/que-es-mongodb/#La_arquitectura_de_MongoDB>Todo lo que debes saber sobre MongoDB</a><li><a href=https://www.ecured.cu/MongoDB>MongoDB – EcuRed</a><li><a href=https://mappinggis.com/2014/07/mongodb-y-gis/>Bases de datos NoSQL, MongoDB y GIS – MappingGIS</a><li><a href=https://es.slideshare.net/maxfontana90/caractersticas-mongo-db>Características MONGO DB</a><li><a href=https://openwebinars.net/blog/que-es-mongodb>Qué es MongoDB y características</a><li><a href=https://www.genbeta.com/desarrollo/mongodb-que-es-como-funciona-y-cuando-podemos-usarlo-o-no>MongoDB. Qué es, cómo funciona y cuándo podemos usarlo (o no)</a><li><a href=https://docs.mongodb.com/>MongoDB Documentation</a><li><a href=https://www.genbeta.com/desarrollo/nosql-clasificacion-de-las-bases-de-datos-segun-el-teorema-cap>NoSQL: Clasificación de las bases de datos según el teorema CAP</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,187 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> MongoDB: Operaciones Básicas y Arquitectura | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>MongoDB: Operaciones Básicas y Arquitectura</h1><div class=time><p>2020-03-05T03:00:53+00:00<p>last updated 2020-03-20T11:42:15+00:00</div><p>Este es el segundo post en la serie sobre MongoDB, con una breve descripción de las operaciones básicas (tales como inserción, recuperación e indexado), y ejecución por completo junto con el modelo de datos y arquitectura.<p>Otros posts en esta serie:<ul><li><a href=/blog/mdad/mongodb-introduction/>MongoDB: Introducción</a><li><a href=/blog/mdad/mongodb-operaciones-basicas-y-arquitectura/>MongoDB: Operaciones Básicas y Arquitectura</a> (este post)</ul><p>Este post está hecho en colaboración con un compañero, y en él veremos algunos ejemplos de las operaciones básicas (<a href=https://stackify.com/what-are-crud-operations/>CRUD</a>) sobre MongoDB.<hr><p>Empezaremos viendo cómo creamos una nueva base de datos dentro de MongoDB y una nueva colección donde poder insertar nuestros documentos.<h2 id=creacion-de-una-base-de-datos-e-insercion-de-un-primer-documento>Creación de una base de datos e inserción de un primer documento</h2><p>Podemos ver las bases de datos que tenemos disponibles ejecutando el comando:<pre><code>> show databases -admin 0.000GB -config 0.000GB -local 0.000GB -</code></pre><p>Para crear una nueva base de datos, o utilizar una de las que tenemos creadas ejecutamos <code>use</code> junto con el nombre que le vamos a dar:<pre><code>> use new_DB -switched to db new_DB -</code></pre><p>Una vez hecho esto, podemos ver que si volvemos a ejecutar «show databases», la nueva base de datos no aparece. Esto es porque para que Mongo registre una base de datos en la lista de las existentes, necesitamos insertar al menos un nuevo documento en una colección de esta. Lo podemos hacer de la siguiente forma:<pre><code>> db.movie.insert({"name":"tutorials point"}) -WriteResult({ "nInserted" : 1 }) - -> show databases -admin 0.000GB -config 0.000GB -local 0.000GB -movie 0.000GB -</code></pre><p>Al igual que podemos ver las bases de datos existentes, también podemos consultar las colecciones que existen dentro de estas. Siguiendo la anterior ejecución, si ejecutamos:<pre><code>> show collections -movie -</code></pre><h3 id=borrar-base-de-datos>Borrar base de datos</h3><p>Para borrar una base de datos tenemos que ejecutar el siguiente comando:<pre><code>> db.dropDatabase() -{ "dropped" : "new_DB", "ok" : 1 } -</code></pre><h3 id=crear-coleccion>Crear colección</h3><p>Para crear una colección podemos hacerlo de dos formas. O bien mediante el comando:<pre><code>db.createCollection(<nombre de la colección>, opciones) -</code></pre><p>Donde el primer parámetro es el nombre que le queremos asignar a la colección, y los siguientes, todos opcionales, pueden ser (entre otros):<table><thead><tr><th>Campo<th>Tipo<th>Descripción<tbody><tr><td><code> - capped - </code><td>Booleano<td>Si es <code> - true - </code> , permite una colección limitada. Una colección limitada es una colección de tamaño fijo que sobrescribe automáticamente sus entradas más antiguas cuando alcanza su tamaño máximo. Si especifica <code> - true - </code> , también debe especificar el parámetro de <code> - size - </code> .<tr><td><code> - autoIndexId - </code><td>Booleano<td>Si es <code> - true - </code> crea automáticamente un índice en el campo <code> - _id - </code> . Por defecto es <code> - false - </code><tr><td><code> - size - </code><td>Número<td>Especifica el tamaño máximo en bytes para una colección limitada. Es obligatorio si el campo <code> - capped - </code> está a <code> - true - </code> .<tr><td><code> - max - </code><td>Número<td>Especifica el número máximo de documentos que están permitidos en la colección limitada.</table><pre><code>> use test -switched to db test - -> db.createCollection("mycollection") -{ "ok" : 1 } - -> db.createCollection("mycol", {capped : true, autoIndexId: true, size: 6142800, max: 10000}) -{ - "note" : "the autoIndexId option is deprecated and will be removed in a future release", - "ok" : 1 -} - -> show collections -mycol -mycollection -</code></pre><p>Como se ha visto anteriormente al crear la base de datos, podemos insertar un documento en una colección sin que la hayamos creado anteriormente. Esto es porque MongoDB crea automáticamente una colección cuando insertas algún documento en ella:<pre><code>> db.tutorialspoint.insert({"name":"tutorialspoint"}) -WriteResult({ "nInserted" : 1 }) - -> show collections -mycol -mycollection -tutorialspoint -</code></pre><h3 id=borrar-coleccion>Borrar colección</h3><p>Para borrar una colección basta con situarnos en la base de datos que la contiene, y ejecutar lo siguiente:<pre><code>db.<nombre_de_la_colección>.drop() -</code></pre><pre><code>> db.mycollection.drop() -true - -> show collections -mycol -tutorialspoint -</code></pre><h3 id=insertar-documento>Insertar documento</h3><p>Para insertar datos en una colección de MongoDB necesitaremos usar el método <code>insert()</code> o <code>save()</code>.<p>Ejemplo del método <code>insert</code>:<pre><code>> db.colection.insert({ -... title: 'Esto es una prueba para MDAD', -... description: 'MongoDB es una BD no SQL', -... by: 'Classmate and Me', -... tags: ['mongodb', 'database'], -... likes: 100 -... }) -WriteResults({ "nInserted" : 1 }) -</code></pre><p>En este ejemplo solo se ha insertado un único documento, pero podemos insertar los que queramos separándolos de la siguiente forma:<pre><code>db.collection.insert({documento}, {documento2}, {documento3}) -</code></pre><p>No hace falta especificar un ID ya que el propio mongo asigna un ID a cada documento automáticamente, aunque nos da la opción de poder asignarle uno mediante el atributo <code>_id</code> en la inserción de los datos<p>Como se indica en el título de este apartado también se puede insertar mediante el método <code>db.coleccion.save(documento)</code>, funcionando este como el método <code>insert</code>.<h3 id=metodo-find>Método <code>find()</code></h3><p>El método find en MongoDB es el que nos permite realizar consultas a las colecciones de nuestra base de datos:<pre><code>db.<nombre_de_la_colección>.find() -</code></pre><p>Este método mostrará de una forma no estructurada todos los documentos de la colección. Si le añadimos la función <code>pretty</code> a este método, se mostrarán de una manera más «bonita».<pre><code>> db.colection.find() -{ "_id": ObjectId("5e738f0989f85a7eafdf044a"), "title" : "Esto es una prueba para MDAD", "description" : "MongoDB es una BD no SQL", "by" : "Classmate and Me", "tags" : [ "mongodb", "database" ], "likes" : 100 } - -> db.colection.find().pretty() -{ - "_id": ObjectId("5e738f0989f85a7eafdf044a"), - "title" : "Esto es una prueba para MDAD", - "description" : "MongoDB es una BD no SQL", - "by" : "Classmate and Me", - "tags" : [ - "mongodb", - "database" - ], - "likes" : 100 -} -</code></pre><p>Los equivalentes del <code>where</code> en las bases de datos relacionales son:<table><thead><tr><th>Operación<th>Sintaxis<th>Ejemplo<th>Equivalente en RDBMS<tbody><tr><td>Igual<td><code> - {<clave>:<valor>} - </code><td><code> - db.mycol.find({"by":"Classmate and Me"}) - </code><td><code> - where by = 'Classmate and Me' - </code><tr><td>Menor que<td><code> - {<clave>:{$lt:<valor>}} - </code><td><code> - db.mycol.find({"likes":{$lt:60}}) - </code><td><code> - where likes < 60 - </code><tr><td>Menor o igual que<td><code> - {<clave>:{$lte:<valor>}} - </code><td><code> - db.mycol.find({"likes":{$lte:60}}) - </code><td><code> - where likes <= 60 - </code><tr><td>Mayor que<td><code> - {<clave>:{$gt:<valor>}} - </code><td><code> - db.mycol.find({"likes":{$gt:60}}) - </code><td><code> - where likes > 60 - </code><tr><td>Mayor o igual que<td><code> - {<clave>:{$gte:<valor>}} - </code><td><code> - db.mycol.find({"likes":{$gte:60}}) - </code><td><code> - where likes >= 60 - </code><tr><td>No igual<td><code> - {<clave>:{$ne:<valor>}} - </code><td><code> - db.mycol.find({"likes":{$ne:60}}) - </code><td><code> - where likes != 60 - </code></table><p>En el método <code>find()</code> podemos añadir condiciones AND y OR de la siguiente manera:<pre><code>(AND) -> db.colection.find({$and:[{"by":"Classmate and Me"},{"title": "Esto es una prueba para MDAD"}]}).pretty() - -(OR) -> db.colection.find({$or:[{"by":"Classmate and Me"},{"title": "Esto es una prueba para MDAD"}]}).pretty() - -(Ambos a la vez) -> db.colection.find({"likes": {$gt:10}, $or: [{"by": "Classmate and Me"}, {"title": "Esto es una prueba para MDAD"}]}).pretty() -</code></pre><p>La última llamada con ambos a la vez equivalente en una consulta SQL a:<pre><code>where likes>10 AND (by = 'Classmate and Me' OR title = 'Esto es una prueba para MDAD') -</code></pre><h3 id=actualizar-un-documento>Actualizar un documento</h3><p>En MongoDB se hace utilizando el método <code>update</code>:<pre><code>db.<nombre_colección>.update(<criterio_de_selección>, <dato_actualizado>) -</code></pre><p>Para este ejemplo vamos a actualizar el documento que hemos insertado en el apartado anterior:<pre><code>> db.colection.update({'title':'Esto es una prueba para MDAD'},{$set:{'title':'Título actualizado'}}) -WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) -> db.colection.find().pretty() -{ - "_id": ObjectId("5e738f0989f85a7eafdf044a"), - "title" : "Título actualizado", - "description" : "MongoDB es una BD no SQL", - "by" : "Classmate and Me", - "tags" : [ - "mongodb", - "database" - ], - "likes" : 100 -} -</code></pre><p>Anteriormente se ha mencionado el método <code>save()</code> para la inserción de documentos, pero también podemos utilizarlo para sustituir documentos enteros por uno nuevo:<pre><code>> db.<nombre_de_la_colección>.save({_id:ObjectId(), <nuevo_documento>}) -</code></pre><p>Con nuestro documento:<pre><code>> db.colection.save( -... { -... "_id": ObjectId("5e738f0989f85a7eafdf044a"), "title": "Este es el nuevo título", "by": "MDAD" -... } -... ) -WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) - -> db.colection.find() -{ - "_id": ObjectId("5e738f0989f85a7eafdf044a"), - "title": "Este es el nuevo título", - "by": "MDAD" -} -</code></pre><h3 id=borrar-documento>Borrar documento</h3><p>Para borrar un documento utilizaremos el método <code>remove()</code> de la siguiente manera:<pre><code>db.<nombre_de_la_colección>.remove(<criterio_de_borrado>) -</code></pre><p>Considerando la colección del apartado anterior borraremos el único documento que tenemos:<pre><code>> db.colection.remove({'title': 'Este es el nuevo título'}) -WriteResult({ "nRemoved" : 1 }) -> db.colection.find().pretty() -> -</code></pre><p>Para borrar todos los documentos de una colección usamos:<pre><code>db.<colección>.remove({}) -</code></pre><h3 id=indexacion>Indexación</h3><p>MongDB nos permite crear índices sobre atributos de una colección de la siguiente forma:<pre><code>db.<colección>.createIndex( {<atributo>:<opciones>}) -</code></pre><p>Como ejemplo:<pre><code>> db.mycol.createIndex({"title":1}) -{ - "createdCollectionAutomatically" : false, - "numIndexesBefore" : 1, - "numIndexesAfter" : 2, - "ok" : 1 -} -</code></pre><p>Si queremos más de un atributo en el índice lo haremos así:<pre><code>> db.mycol.ensureIndex({"title":1,"description":-1}) -</code></pre><p>Los valores que puede tomar son <code>+1</code> para ascendente o <code>-1</code> para descendente.<h3 id=referencias>Referencias</h3><ul><li>Manual MongoDB. (n.d.). <a href=https://docs.mongodb.com/manual/>https://docs.mongodb.com/manual/</a><li>MongoDB Tutorial – Tutorialspoint. (n.d.). – <a href=https://www.tutorialspoint.com/mongodb/index.htm>https://www.tutorialspoint.com/mongodb/index.htm</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Privado: NoSQL evaluation | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Privado: NoSQL evaluation</h1><div class=time><p>2020-03-16T00:00:35+00:00<p>last updated 2020-03-28T19:22:31+00:00</div><p>This evaluation is based on the criteria for the first delivery described by Trabajos en grupo sobre Bases de Datos NoSQL.<p>I have chosen to evaluate the following people and works:<ul><li>a12: Classmate (username) with Druid.<li>a21: Classmate (username) with Neo4J.</ul><h2 id=classmate-s-evaluation>Classmate’s Evaluation</h2><p><strong>Grading: A.</strong><p>The post evaluated is Bases de datos NoSQL – Apache Druid – Primera entrega.<p>It is a very well-written, complete post, with each section meeting one of the points in the required criteria. The only thing that bothered me a little is the abuse of strong emphasis in the text, which I found quite distracting. However, the content deserves the highest grading.<h2 id=classmate-s-evaluation-1>Classmate’s Evaluation</h2><p><strong>Grading: A.</strong><p>The post evaluated is Bases de datos NoSQL – Neo4j – Primera entrega.<p>Well-written post, although a bit smaller than Classmate’s, but that’s not really an issue. It still talks about everything it should talk and includes photos to go along the text which help. There is no noticeable wrong things in it, so it gets the highest grading as well.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,145 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Visualizing Cáceres’ OpenData | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Visualizing Cáceres’ OpenData</h1><div class=time><p>2020-03-09T00:00:08+00:00<p>last updated 2020-03-19T14:38:41+00:00</div><p>The city of Cáceres has online services to provide <a href=http://opendata.caceres.es/>Open Data</a> over a wide range of <a href=http://opendata.caceres.es/dataset>categories</a>, all of which are very interesting to explore!<p>We have chosen two different datasets, and will explore four different ways to visualize the data.<p>This post is co-authored with Classmate.<h2 id=obtain-the-data>Obtain the data</h2><p>We are interested in the JSON format for the <a href=http://opendata.caceres.es/dataset/informacion-del-padron-de-caceres-2017>census in 2017</a> and those for the <a href=http://opendata.caceres.es/dataset/vias-urbanas-caceres>vias of the city</a>. This way, we can explore the population and their location in interesting ways! You may follow those two links and select the JSON format under Resources to download it.<p>Why JSON? We will be using <a href=https://python.org/>Python</a> (3.7 or above) and <a href=https://matplotlib.org/>matplotlib</a> for quick iteration, and loading the data with <a href=https://docs.python.org/3/library/json.html>Python’s <code>json</code> module</a> will be trivial.<h2 id=implementation>Implementation</h2><h3 id=imports-and-constants>Imports and constants</h3><p>We are going to need a lot of things in this code, such as <code>json</code> to load the data, <code>matplotlib</code> to visualize it, and other data types and type hinting for use in the code.<p>We also want automatic download of the JSON files if they’re missing, so we add their URLs and download paths as constants.<pre><code>import json -import re -import os -import sys -import urllib.request -import matplotlib.pyplot as plt -from dataclasses import dataclass -from collections import namedtuple -from datetime import date -from pathlib import Path -from typing import Optional - -CENSUS_URL = 'http://opendata.caceres.es/GetData/GetData?dataset=om:InformacionCENSUS&year=2017&format=json' -VIAS_URL = 'http://opendata.caceres.es/GetData/GetData?dataset=om:Via&format=json' - -CENSUS_JSON = Path('data/demografia/Padrón_Cáceres_2017.json') -VIAS_JSON = Path('data/via/Vías_Cáceres.json') -</code></pre><h3 id=data-classes>Data classes</h3><p><a href=https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/>Parse, don’t validate</a>. By defining a clear data model, we will be able to tell at a glance what information we have available. It will also be typed, so we won’t be confused as to what is what! Python 3.7 introduces <code>[dataclasses](https://docs.python.org/3/library/dataclasses.html)</code>, which are a wonderful feature to define… well, data classes concisely.<p>We also have a <code>[namedtuple](https://docs.python.org/3/library/collections.html#collections.namedtuple)</code> for points, because it’s extremely common to represent them as tuples.<pre><code>Point = namedtuple('Point', 'long lat') - -@dataclass -class Census: - year: int - via: int - count_per_year: dict - count_per_city: dict - count_per_gender: dict - count_per_nationality: dict - time_year: int - -@dataclass -class Via: - name: str - kind: str - code: int - history: Optional[str] - old_name: Optional[str] - length: Optional[float] - start: Optional[Point] - middle: Optional[Point] - end: Optional[Point] - geometry: Optional[list] -</code></pre><h3 id=helper-methods>Helper methods</h3><p>We will have a little helper method to automatically download the JSON when missing. This is just for convenience, we could as well just download it manually. But it is fun to automate things.<pre><code>def ensure_file(file, url): - if not file.is_file(): - print('Downloading', file.name, 'because it was missing...', end='', flush=True, file=sys.stderr) - file.parent.mkdir(parents=True, exist_ok=True) - urllib.request.urlretrieve(url, file) - print(' Done.', file=sys.stderr) -</code></pre><h3 id=parsing-the-data>Parsing the data</h3><p>I will be honest, parsing Cáceres’ OpenData is a pain in the neck! The official descriptions are huge and not all that helpful. Maybe if one needs documentation for a specific field. But luckily for us, the names are pretty self-descriptive, and we can explore the data to get a feel for what we will find.<p>We define two methods, one to iterate over <code>Census</code> values, and another to iterate over <code>Via</code> values. Here’s where our friend <code>[re](https://docs.python.org/3/library/re.html)</code> comes in, and oh boy the format of the data…<p>For example, the year and via identifier are best extracted from the URI! The information is also available in the <code>rdfs_label</code> field, but that’s just a Spanish text! At least the URI will be more reliable… hopefully.<p>Birth date. They could have used a JSON list, but nah, that would’ve been too simple. Instead, you are given a string separated by semicolons. The values? They could have been dictionaries with names for «year» and «age», but nah! That would’ve been too simple! Instead, you are given strings that look like «2001 (7)», and that’s the year and the count.<p>The birth place? Sometimes it’s «City (Province) (Count)», but sometimes the province is missing. Gender? Semicolon-separated. And there are only two genders. I know a few people who would be upset just reading this, but it’s not my data, it’s theirs. Oh, and plenty of things are optional. That was a lot of <code>AttributeError: 'NoneType' object has no attribute 'foo'</code> to work through!<p>But as a reward, we have nicely typed data, and we no longer have to deal with this mess when trying to visualize it. For brevity, we will only be showing how to parse the census data, and not the data for the vias. This post is already long enough on its own.<pre><code>def iter_census(file): - with file.open() as fd: - data = json.load(fd) - - for row in data['results']['bindings']: - year, via = map(int, row['uri']['value'].split('/')[-1].split('-')) - - count_per_year = {} - for item in row['schema_birthDate']['value'].split(';'): - y, c = map(int, re.match(r'(\d+) \((\d+)\)', item).groups()) - count_per_year[y] = c - - count_per_city = {} - for item in row['schema_birthPlace']['value'].split(';'): - match = re.match(r'([^(]+) \(([^)]+)\) \((\d+)\)', item) - if match: - l, _province, c = match.groups() - else: - l, c = re.match(r'([^(]+) \((\d+)\)', item).groups() - - count_per_city[l] = int(c) - - count_per_gender = {} - for item in row['foaf_gender']['value'].split(';'): - g, c = re.match(r'([^(]+) \((\d+)\)', item).groups() - count_per_gender[g] = int(c) - - count_per_nationality = {} - for item in row['schema_nationality']['value'].split(';'): - match = re.match(r'([^(]+) \((\d+)\)', item) - if match: - g, c = match.groups() - else: - g, _alt_name, c = re.match(r'([^(]+) \(([^)]+)\) \((\d+)\)', item).groups() - - count_per_nationality[g] = int(c) - time_year = int(row['time_year']['value']) - - yield Census( - year=year, - via=via, - count_per_year=count_per_year, - count_per_city=count_per_city, - count_per_gender=count_per_gender, - count_per_nationality=count_per_nationality, - time_year=time_year, - ) -</code></pre><h2 id=visualizing-the-data>Visualizing the data</h2><p>Here comes the fun part! After parsing all the desired data from the mentioned JSON files, we plotted the data in four different graphics making use of Python’s <a href=https://matplotlib.org/><code>matplotlib</code> library.</a> This powerful library helps with the creation of different visualizations in Python.<h3 id=visualizing-the-genders-in-a-pie-chart>Visualizing the genders in a pie chart</h3><p>After seeing that there are only two genders in the data of the census, we, displeased, started work in a chart for it. The pie chart was the best option since we wanted to show only the percentages of each gender. The result looks like this:<p><img src=https://lonami.dev/blog/mdad/visualizing-caceres-opendata/pie_chart.png><p>Pretty straight forward, isn’t it? To display this wonderful graphic, we used the following code:<pre><code>def pie_chart(ax, data): - lists = sorted(data.items()) - - x, y = zip(*lists) - ax.pie(y, labels=x, autopct='%1.1f%%', - shadow=True, startangle=90) - ax.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle. -</code></pre><p>We pass the axis as the input parameter (later we will explain why) and the data collected from the JSON regarding the genders, which are in a dictionary with the key being the labels and the values the tally of each gender. We sort the data and with some unpacking magic we split it into two values: <code>x</code> being the labels and <code>y</code> the amount of each gender.<p>After that we plot the pie chart with the data and labels from <code>y</code> and <code>x</code>, we specify that we want the percentage with one decimal place with the <code>autopct</code> parameter, we enable shadows for the presentation, and specify the start angle at 90º.<h3 id=date-tick-labels>Date tick labels</h3><p>We wanted to know how many of the living people were born on each year, so we are making a date plot! In the census we have the year each person was born in, and using that information is an easy task after parsing the data (parsing was an important task of this work). The result looks as follows:<p><img src=https://lonami.dev/blog/mdad/visualizing-caceres-opendata/date_tick.png><p>How did we do this? The following code was used:<pre><code>def date_tick(ax, data): - lists = sorted(data.items()) - - x, y = zip(*lists) - x = [date(year, 1, 1) for year in x] - ax.plot(x, y) -</code></pre><p>Again, we pass in an axis and the data related with the year born, we sort it, split it into two lists, being the keys the years and the values the number per year. After that, we put the years in a date format for the plot to be more accurate. Finally, we plot the values into that wonderful graphic.<h3 id=stacked-bar-chart>Stacked bar chart</h3><p>We wanted to know if there was any relation between the latitudes and count per gender, so we developed the following code:<pre><code>def stacked_bar_chart(ax, data): - labels = [] - males = [] - females = [] - - for latitude, genders in data.items(): - labels.append(str(latitude)) - males.append(genders['Male']) - females.append(genders['Female']) - - ax.bar(labels, males, label='Males') - ax.bar(labels, females, bottom=males, label='Females') - - ax.set_ylabel('Counts') - ax.set_xlabel('Latitudes') - ax.legend() -</code></pre><p>The key of the data dictionary is the latitude rounded to two decimals, and value is another dictionary, which is composed by the key that is the name of the gender and the value, the number of people per gender. So, in a single entry of the data dictionary we have the latitude and how many people per gender are in that latitude.<p>We iterate the dictionary to extract the different latitudes and people per gender (because we know only two genders are used, we hardcode it to two lists). Then we plot them putting the <code>males</code> and <code>females</code> lists at the bottom and set the labels of each axis. The result is the following:<p><img src=https://lonami.dev/blog/mdad/visualizing-caceres-opendata/stacked_bar_chart-1.png><h3 id=scatter-plots>Scatter plots</h3><p>This last graphic was very tricky to get right. It’s incredibly hard to find the extent of a city online! We were getting confused because some of the points were way farther than the centre of Cáceres, and the city background is a bit stretched even if the coordinates appear correct. But in the end, we did a pretty good job on it.<pre><code>def scatter_map(ax, data): - xs = [] - ys = [] - areas = [] - for (long, lat), count in data.items(): - xs.append(long) - ys.append(lat) - areas.append(count / 100) - - if CACERES_MAP.is_file(): - ax.imshow(plt.imread(str(CACERES_MAP)), extent=CACERES_EXTENT) - else: - print('Note:', CACERES_MAP, 'does not exist, not showing it', file=sys.stderr) - - ax.scatter(xs, ys, areas, alpha=0.1) -</code></pre><p>This time, the keys in the data dictionary are points and the values are the total count of people in that point. We use a normal <code>for</code> loop to create the different lists. For the areas on how big the circles we are going to represent will be, we divide the count of people by some number, like <code>100</code>, or otherwise they would be huge.<p>If the file of the map is present, we render it so that we can get a sense on where the points are, but if the file is missing we print a warning.<p>At last, we draw the scatter plot with some low alpha value (there’s a lot of overlapping points). The result is <em>absolutely gorgeous</em>. (For some definitions of gorgeous, anyway):<p><img src=https://lonami.dev/blog/mdad/visualizing-caceres-opendata/scatter_map.png><p>Just for fun, here’s what it looks like if we don’t divide the count by 100 and lower the opacity to <code>0.01</code>:<p><img src=https://lonami.dev/blog/mdad/visualizing-caceres-opendata/scatter_map-2.png><p>That’s a big solid blob, and the opacity is only set to <code>0.01</code>!<h3 id=drawing-all-the-graphs-in-the-same-window>Drawing all the graphs in the same window</h3><p>To draw all the graphs in the same window instead of getting four different windows we made use of the <a href=https://matplotlib.org/3.2.0/api/_as_gen/matplotlib.pyplot.subplots.html><code>subplots</code> function</a>, like this:<pre><code>fig, axes = plt.subplots(2, 2) -</code></pre><p>This will create a matrix of two by two of axes that we store in the axes variable (fitting name!). Following this code are the different calls to the methods commented before, where we access each individual axis and pass it to the methods to draw on:<pre><code>pie_chart(axes[0, 0], genders) -date_tick(axes[0, 1], years) -stacked_bar_chart(axes[1, 0], latitudes) -scatter_map(axes[1, 1], positions) -</code></pre><p>Lastly, we plot the different graphics:<pre><code>plt.show() -</code></pre><p>Wrapping everything together, here’s the result:<p><img src=https://lonami.dev/blog/mdad/visualizing-caceres-opendata/figures-1.png><p>The numbers in some of the graphs are a bit crammed together, but we’ll blame that on <code>matplotlib</code>.<h2 id=closing-words>Closing words</h2><p>Wow, that was a long journey! We hope that this post helped you pick some interest in data exploration, it’s such a fun world. We also offer the full download for the code below, because we know it’s quite a bit!<p>Which of the graphs was your favourite? I personally like the count per date, I think it’s nice to see the growth. Let us know in the comments below!<p><em>download removed</em></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> What is an algorithm? | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>What is an algorithm?</h1><div class=time><p>2020-02-25T00:00:16+00:00<p>last updated 2020-03-18T09:51:02+00:00</div><p>Algorithms are a sequence of instructions that can be followed to achieve <em>something</em>. That something can be anything, and depends entirely on your problem!<p>For example, a recipe to cook some really nice food is an algorithm: it guides you, step by step, to cook something nice. People dealing with mathemathics also apply algorithms to transform their data. And computers <em>love</em> algorithms, too!<p>In reality, any computer program can basically be thought as an algorithm. It contains a series of instructions for the computer to execute. Running them is a process that takes time, consumes input and produces output. This is also why terms like «procedure» come up when talking about them.<p>Computer programs (their algorithms) are normally written in some more specific language, like Java or Python. The instructions are very clear here, which is what we need! A natural language like English is a lot harder to process, and ambiguous. I’m sure you’ve been in arguments because the other person didn’t understand you!<h2 id=references>References</h2><ul><li>algorithm – definition and meaning: <a href=https://www.wordnik.com/words/algorithm>https://www.wordnik.com/words/algorithm</a><li>Algorithm: <a href=https://en.wikipedia.org/wiki/Algorithm>https://en.wikipedia.org/wiki/Algorithm</a><li>What is a «computer algorithm»?: <a href=https://computer.howstuffworks.com/what-is-a-computer-algorithm.htm>https://computer.howstuffworks.com/what-is-a-computer-algorithm.htm</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -0,0 +1,1 @@
+<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="BiRabittoh's official website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Modern web bloat | BiRabittoh's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>birabittoh's site</a><li><a href=/blog class=selected>blog</a></ul><div class=right><a href=https://github.com/Bi-Rabittoh><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Modern web bloat</h1><div class=time><p>2021-04-09</div><p>This is it. My first blog post; I guess I just became a boomer.<h2 id=inspiration>Inspiration</h2><p>Some time ago I stumbled upon <a href=https://odysee.com/@Luke:7/a-demonstration-of-modern-web-bloat:f>this video</a>, where the popular Linux influencer <a href=https://lukesmith.xyz>Luke Smith</a> talked about the effort of looking up a Chicken Parmesan recipe in 2021 without having any adblock or privacy extensions enabled.<p>I decided to build this website on <a href=https://www.getzola.org/><code>zola</code></a>, which is a modern engine for static sites! I was inspired by <a href=https://lonami.dev/>lonami</a> and I actually forked <a href=https://github.com/LonamiWebs/lonamiwebs.github.io>his code</a> to make <a href=https://github.com/Bi-Rabittoh/birabittoh.github.io>mine</a>. I think our websites are a good example of how clean and fast a webpage should be.<p>Yeah, this looks like a first world problem and it probably is, but it's not as subtle as you think. I'm actually convinced that the internet <em>could</em> actually benefit from this way of thinking, and that's what I'm going to talk about in this first article.<h2 id=the-problem>The problem</h2><p>In the early days of the internet, it was common for webpages to be written using only HTML, so we had very ugly but functional websites.<p>As technology went on, sites needed to get more modern-looking and <em>interactive</em>; that's why CSS and JavaScript were introduced into the mix, allowing for dynamic websites that could actually change based on user input.<p>As of nowadays, a lot more stuff went into the mix, to the point where the browser is now the most common program we use in our OS: you can, in fact, use it for doing things that 15+ years ago required external programs, like:<ul><li>playing music and video,<li>reading PDF files,<li>doing office work,<li>checking e-mail,<li>cloud storage,<li>etc...</ul><p>I guess people just find it more comfortable if they can do everything with a single program, and they're not to blame for that. This <em>is</em> the easiest approach for unexperienced people: just have a program that does everything, instead of having to learn how to use a bunch of different software.<p>This plethora of uses is possible today because of the existence of various libraries and frameworks that simplify JavaScript and CSS and make them easier to develop complicated websites with. This is good for basic web users who just want functional websites, and great for developers since they can easily code complicated code inside the browser, making it the perfect cross-platform wet dream we all have.<p>Sadly, this brings us to the problem: any modern website has become a burden for any browser to load, since our browser needs to download and parse through each library used and often fill the page contents as you scroll through. In his video, Luke Smith found that a simple Chicken Parmesan recipe would take up to 5-10 megabytes, which doesn't sound like a lot, but it actually is.<p>It's easier to understand it if you think about it with video-games; any game on 16-bit<sup class=footnote-reference><a href=#gaming-storage>1</a></sup> consoles and earlier, including full-fledged 30+ hour adventures like <em>Final Fantasy 6</em> and <em>Chrono Trigger</em>, weighs less than that one single recipe webpage.<h2 id=the-solution>The solution</h2><p>Well, I don't think this "problem" is getting solved soon, as new frameworks for web development are constantly being introduced. Sadly, it's a one-way train, but if you're a web-dev you could actually make a difference yourself!<p>I mean, this can not apply to all websites. Some of them just <em>NEED</em> to be as responsive and interactive as they are; most of them actually just became bloated at a certain time period (probably mid-2000s) when having a flashy website was cool and different from what everyone else had.<p>Nowadays you can be different than other websites by using plain HTML and CSS for your website: this ensures your pages will load instantly and be compatible even with the oldest of browsers!<p>If you like this philosophy, you can check out other projects that aim for a simpler and faster web, like these:<ul><li><a href=https://based.cooking/>based.cooking</a>, a modern recipe website based on collaboration via GitHub;<li><a href=https://wiby.me/>wiby.me</a>, a search engine that aims to only index classic style webpages.</ul><h2 id=footnotes>Footnotes</h2><div class=footnote-definition id=gaming-storage><sup class=footnote-definition-label>1</sup><p>As stated in <a href=https://blogs.umass.edu/Techbytes/2014/02/10/history-of-gaming-storage/#attachment_2827>this article</a>.</div></main><footer><div><p>Please use email for business inquiries <a href=mailto:andronacomarco@gmail.com><img src=/img/mail.svg alt=mail></a></div></footer></article><p class=abyss>owo what's this
@@ -1,73 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> My new computer | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>My new computer</h1><div class=time><p>2020-06-19<p>last updated 2020-07-03</div><p>This post will be mostly me ranting about setting up a new laptop, but I also just want to share my upgrade. If you're considering installing Arch Linux with dual-boot for Windows, maybe this post will help. Or perhaps you will learn something new to troubleshoot systems in the future. Let's begin!<p>Last Sunday, I ordered a Asus Rog Strix G531GT-BQ165 for 900€ (on a 20% discount) with the following specifications:<ul><li>Intel® Core i7-9750H (6 cores, 12MB cache, 2.6GHz up to 4.5GHz, 64-bit)<li>16GB RAM (8GB*2) DDR4 2666MHz<li>512GB SSD M.2 PCIe® NVMe<li>Display 15.6" (1920x1080/16:9) 60Hz<li>Graphics NVIDIA® GeForce® GTX1650 4GB GDDR5 VRAM<li>LAN 10/100/1000<li>Wi-Fi 5 (802.11ac) 2x2 RangeBoost<li>Bluetooth 5.0<li>48Wh battery with 3 cells<li>3 x USB 3.1 (GEN1)</ul><p>I was mostly interested in a general upgrade (better processor, disk, more RAM), although the graphics card is a really nice addition which will allow me to take some time off on more games. After using it for a bit, I really love the feel of the keyboard, and I love the lack of numpad! (No sarcasm, I really don't like numpads.)<p>This is an upgrade from my previous laptop (Asus X554LA-XX822T), which I won in a competition before entering university in a programming challenge. It has served me really well for the past five years, and had the following specifications:<ul><li>Intel® Core™ i5-5200U<li>4GB RAM DDR3L 1600MHz (which I upgraded to have 8GB)<li>1TB HDD<li>Display 15.6" (1366x768/16:9)<li>Intel® HD Graphics 4400<li>LAN 10/100/1000<li>Wifi 802.11 bgn<li>Bluetooth 4.0<li>Battery 2 cells<li>1 x USB 2.0<li>2 x USB 3.0</ul><p>Prior to this one, I had a Lenovo (also won in the same competition of the previous year), and prior to that (just for the sake of history), it was HP Pavilion, AMD A4-3300M processor, which unfortunately ended with heating problems. But that's very old now.<h2 id=laptop-arrival>Laptop arrival</h2><p>The laptop arrived 2 days ago at roughly 19:00, which I put charged for 3 hours as the book said. The day after, nightmares began!<p>Trying to boot it the first two times was fun, as it comes with a somewhat loud sound on boot. I don't know why they would do this, and I immediately turned it off in the BIOS.<h2 id=installation-journey>Installation journey</h2><p>I spent all of yesterday trying to setup Windows and Arch Linux (and didn't even finish, it took me this morning too and even now it's only half functional). I absolutely <em>hate</em> the amount of partitions the Windows installer creates on a clean disk. So instead, I first went with Arch Linux, and followed the <a href=https://wiki.archlinux.org/index.php/Installation_guide>installation guide on the Arch wiki</a>. Pre-installation, setting up the wireless network, creating the partitions and formatting them went all good. I decided to avoid GRUB at first and go with rEFInd, but alas I missed a big warning on the wiki and after reboot (I would later find out) it was not mounting root properly, so all I had was whatever was in the Initramfs. Reboot didn't work, so I had to hold the power button.<p>Anyway, once the partitions were created, I went to install Windows (there was a lot of back and forth burning different <code>.iso</code> images on the USB, which was a bit annoying because it wasn't the fastest thing in the world). This was pretty painless, and the process was standard: select advanced to let me choose the right partition, pick the one, say "no" to everything in the services setup, and done. But this was the first Windows <code>.iso</code> I tried. It was an old revision, and the drivers were causing issues when running (something weird about their <code>.dll</code>, manually installing the <code>.ini</code> driver files seemed to work?). The Nvidia drivers didn't want to be installed on such an old revision, after updating everything I could via Windows updates. So back I went to burning a newer Windows <code>.iso</code> and going through the same process again…<p>Once Windows was ready and I verified that I could boot to it correctly, it was time to have a second go at Arch Linux. And I went through the setup at least three times, getting it wrong every single time, formatting root every single time, redownloading the packages every single pain. If only had I known earlier what the issue was!<p>Why bother with Arch? I was pretty happy with Linux Mint, and I lowkey wanted to try NixOS, but I had used Arch before and it's a really nice distro overall (up-to-date, has AUR, quite minimal, imperative), except for trying to install rEFInd while chrooted…<p>In the end I managed to get something half-working, I still need to properly configure WiFi and pulseaudio in my system but hey it works.<p>I like to be able to dual-boot Windows and Linux because Linux is amazing for productivity, but unfortunately, some games only work fine on Windows. Might as well have both systems and use one for gaming, while the other is my daily driver.<h2 id=setting-up-arch-linux>Setting up Arch Linux</h2><p>This is the process I followed to install Arch Linux in the end, along with a brief explanation on what I think the things are doing and why we are doing them. I think the wiki could do a better job at this, but I also know it's hard to get it right for everyone. Something I do dislike is the link colour, after opening a link it becomes gray and it's a lot easier to miss the fact that it is a link in the first place, which was tough when re-reading it because some links actually matter a lot. Furthermore, important information may just be a single line, also easy to skim over. Anyway, on to the installation process…<p>The first thing we want to do is configure our keyboard layout or else the keys won't correspond to what we expect:<pre><code class=language-sh data-lang=sh>loadkeys es -</code></pre><p>Because we're on a recent system, we want to verify that UEFI works correctly. If we see files listed, then it works fine:<pre><code class=language-sh data-lang=sh>ls /sys/firmware/efi/efivars -</code></pre><p>The next thing we want to do is configure the WiFi, because I don't have any ethernet cable nearby. To do this, we check what network interfaces our laptop has (we're looking for the one prefixed with "w", presumably for wireless, such as "wlan0" or "wlo1"), we set it up, scan for available wireless network, and finally connect. In my case, the network has WPA security so we rely on <code>wpa_supplicant</code> to connect, passing the SSID (network name) and password:<pre><code class=language-sh data-lang=sh>ip link -ip link set <IFACE> up -iw dev <IFACE> scan | less -wpa_supplicant -B -i <IFACE> -c <(wpa_passphrase <SSID> <PASS>) -</code></pre><p>After that's done, pinging an IP address like "1.1.1.1" should Just Work™, but to be able to resolve hostnames, we need to also setup a nameserver. I'm using Cloudflare's, but you could use any other:<pre><code class=language-sh data-lang=sh>echo nameserver 1.1.1.1 > /etc/resolv.conf -ping archlinux.org -^C -</code></pre><p>If the ping works, then network works! If you still have issues, you may need to <a href=https://wiki.archlinux.org/index.php/Network_configuration#Static_IP_address>manually configure a static IP address</a> and add a route with the address of your, well, router. This basically shows if we have any address, adds a static address (so people know who we are), shows what route we have, and adds a default one (so our packets know where to go):<pre><code class=language-sh data-lang=sh>ip address show -ip address add <YOUR ADDR>/24 broadcast + dev <IFACE> -ip route show -ip route add default via <ROUTER ADDR> dev <IFACE> -</code></pre><p>Now that we have network available, we can enable NTP to synchronize our system time (this may be required for network operations where certificates have a validity period, not sure; in any case nobody wants a wrong system time):<pre><code class=language-sh data-lang=sh>timedatectl set-ntp true -</code></pre><p>After that, we can manage our disk and partitions using <code>fdisk</code>. We want to define partitions to tell the system where it should live. To determine the disk name, we first list them, and then edit it. <code>fdisk</code> is really nice and reminds you at every step that help can be accessed with "m", which you should constantly use to guide you through.<pre><code class=language-sh data-lang=sh>fdisk -l -fdisk /dev/<DISK> -</code></pre><p>The partitions I made are the following:<ul><li>A 100MB one for the EFI system.<li>A 32GB one for Linux' root <code>/</code> partition.<li>A 200GB one for Linux' home <code>/home</code> partition.<li>The rest was unallocated for Windows because I did this first.</ul><p>I like to have <code>/home</code> and <code>/</code> separate because I can reinstall root without losing anything from home (projects, music, photos, screenshots, videos…).<p>After the partitions are made, we format them in FAT32 and EXT4 which are good defaults for EFI, root and home. They need to have a format, or else they won't be usable:<pre><code class=language-sh data-lang=sh>mkfs.fat -F32 /dev/<DISK><PART1> -mkfs.ext4 /dev/<DISK><PART2> -mkfs.ext4 /dev/<DISK><PART3> -</code></pre><p>Because the laptop was new, there was no risk to lose anything, but if you're doing a install on a previous system, be very careful with the partition names. Make sure they match with the ones in <code>fdisk -l</code>.<p>Now that we have usable partitions, we need to mount them or they won't be accessible. We can do this with <code>mount</code>:<pre><code class=language-sh data-lang=sh>mount /dev/<DISK><PART2> /mnt -mkdir /mnt/efi -mount /dev/<DISK><PART1> /mnt/efi -mkdir /mnt/home -mount /dev/<DISK><PART3> /mnt/home -</code></pre><p>Remember to use the correct partitions while mounting. We mount everything so that the system knows which partitions we care about, which we will let know about later on.<p>Next step is to setup the basic Arch Linux system on root, which can be done with <code>pacstrap</code>. What follows the directory is a list of packages, and you may choose any you wish (at least add <code>base</code>, <code>linux</code> and <code>linux-firmware</code>). These can be installed later, but I'd recommend having them from the beginning, just in case:<pre><code class=language-sh data-lang=sh>pacstrap /mnt base linux linux-firmware sudo vim-minimal dhcpcd wpa_supplicant man-db man-pages intel-ucode grub efibootmgr os-prober ntfs-3g -</code></pre><p>Because my system has an intel CPU, I also installed <code>intel-ucode</code>.<p>Next up is generating the <code>fstab</code> file, which we tell to use UUIDs to be on the safe side through <code>-U</code>. This file is important, because without it the system won't know what partitions exist and will happily only boot with the initramfs, without anything of what we just installed at root. Not knowing this made me restart the entire installation process a few times.<pre><code class=language-sh data-lang=sh>genfstab -U /mnt >> /mnt/etc/fstab -</code></pre><p>After that's done, we can change our root into our mount point and finish up configuration. We setup our timezone (so DST can be handled correctly if needed), synchronize the hardware clock (to persist the current time to the BIOS), uncomment our locales (exit <code>vim</code> by pressing ESC, then type <code>:wq</code> and press enter), generate locale files (which some applications need), configure language and keymap, update the hostname of our laptop and what indicate what <code>localhost</code> means…<pre><code class=language-sh data-lang=sh>ln -sf /usr/share/zoneinfo/<REGION>/<CITY> /etc/localtime -hwclock --systohc -vim /etc/locale.gen -locale-gen -echo LANG=es_ES.UTF-8 > /etc/locale.conf -echo KEYMAP=es > /etc/vconsole.conf -echo <HOST> /etc/hostname -cat <<EOF > /etc/hosts -127.0.0.1 localhost -::1 localhost -127.0.1.1 <HOST>.localdomain <HOST> -EOF -</code></pre><p>Really, we could've done all of this later, and the same goes for setting root's password with <code>passwd</code> or creating users (some of the groups you probably want are <code>power</code> and <code>wheel</code>).<p>The important part here is installing GRUB (which also needed the <code>efibootmgr</code> package):<pre><code class=language-sh data-lang=sh>grub-install --target=x86_64-efi --efi-directory=/efi --bootloader-id=GRUB -</code></pre><p>If we want GRUB to find our Windows install, we also need the <code>os-prober</code> and <code>ntfs-3g</code> packages that we installed earlier with <code>pacstrap</code>, and with those we need to mount the Windows partition somewhere. It doesn't matter where. With that done, we can generate the GRUB configuration file which lists all the boot options:<pre><code class=language-sh data-lang=sh>mkdir /windows -mount /dev/<DISK><PART5> /windows -grub-mkconfig -o /boot/grub/grub.cfg -</code></pre><p>(In my case, I installed Windows before completing the Arch install, which created an additional partition in between).<p>With GRUB ready, we can exit the chroot and reboot the system, and if all went well, you should be greeted with a choice of operating system to use:<pre><code class=language-sh data-lang=sh>exit -reboot -</code></pre><p>If for some reason you need to find what mountpoints were active prior to rebooting (to <code>unmount</code> them for example), you can use <code>findmnt</code>.<p>Before GRUB I tried rEFInd, which as I explained had issues with for missing a warning. Then I tried systemd-boot, which did not pick up Arch at first. That's where the several reinstalls come from, I didn't want to work with a half-worked system so I mostly redid the entire process quite a few times.<h2 id=migrating-to-the-new-laptop>Migrating to the new laptop</h2><p>I had a external disk formatted with NTFS. Of course, after moving every file I cared about from my previous Linux install caused all the permissions to reset. All my <code>.git</code> repositories, dirty with file permission changes! This is going to take a while to fix, or maybe I should just <code>git config core.fileMode false</code>. Here is a <a href=https://stackoverflow.com/a/2083563>lovely command</a> to sort them out on a per-repository basis:<pre><code class=language-sh data-lang=sh>git diff --summary | grep --color 'mode change 100644 => 100755' | cut -d' ' -f7- | xargs -d'\n' chmod -x -</code></pre><p>I never realized how much I had stored over the years, but it really was a lot. While moving things to the external disk, I tried to do some cleanup, such as removing some build artifacts which needlessly occupy space, or completely skipping all the binary application files. If I need those I will install them anyway. The process was mostly focused on finding all the projects and program data that I did care about, or even some game saves. Nothing too difficult, but definitely time consuming.<h2 id=tuning-arch>Tuning Arch</h2><p>Now that our system is ready, install <code>pacman-contrib</code> to grab a copy of the <code>rankmirrors</code> speed. It should help speed up the download of whatever packages you want to install, since it will help us <a href=https://wiki.archlinux.org/index.php/Mirrors#List_by_speed>rank the mirrors by download speed</a>. Making a copy of the file is important, otherwise whenever you try to install something it will fail saying it can't find anything.<pre><code class=language-sh data-lang=sh>cp /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.backup -sed -i 's/^#Server/Server/' /etc/pacman.d/mirrorlist.backup -rankmirrors -n 6 /etc/pacman.d/mirrorlist.backup | tee /etc/pacman.d/mirrorlist -</code></pre><p>This will take a while, but it should be well worth it. We're using <code>tee</code> to see the progress as it goes.<p>Some other packages I installed after I had a working system in no particular order:<ul><li><code>xfce4</code> and <code>xorg-server</code>. I just love the simplicity of XFCE.<li><code>xfce4-whiskermenu-plugin</code>, a really nice start menu.<li><code>xfce4-pulseaudio-plugin</code> and <code>pavucontrol</code>, to quickly adjust the audio with my mouse.<li><code>xfce4-taskmanager</code>, a GUI alternative I generally prefer to <code>htop</code>.<li><code>pulseaudio</code> and <code>pulseaudio-alsa</code> to get nice integration with XFCE4 and audio mixing.<li><code>firefox</code>, which comes with fonts too. A really good web browser.<li><code>git</code>, to commit <del>crimes</del> code.<li><code>code</code>, a wonderful editor which I used to write this blog entry.<li><code>nano</code>, so much nicer to write a simple commit message.<li><code>python</code> and <code>python-pip</code>, my favourite language to toy around ideas or use as a calculator.<li><code>telegram-desktop</code>, for my needs on sharing memes.<li><code>cmus</code> and <code>mpv</code>, a simple terminal music player and media player.<li><code>openssh</code>, to connect into any VPS I have access to.<li><code>base-devel</code>, necessary to build most projects I'll find myself working with (or even compiling some projects Rust which I installed via <code>rustup</code>).<li><code>flac</code>, <code>libmad</code>, <code>opus</code>, and <code>libvorbis</code>, to be able to play more audio files.<li><code>inkscape</code>, to make random drawings.<li><code>ffmpeg</code>, to convert media or record screen.<li><code>xclip</code>, to automatically copy screenshots to my clipboard.<li><code>gvfs</code>, needed by Thunar to handle mounting and having a trash (perma-deletion by default can be nasty sometimes).<li><code>noto-fonts</code>, <code>noto-fonts-cjk</code>, <code>noto-fonts-extra</code> and <code>noto-fonts-emoji</code>, if you don't want missing gliphs everywhere.<li><code>xfce4-notifyd</code> and <code>libnotify</code>, for notifications.<li><code>cronie</code>, to be able to <code>crontab -e</code>. Make sure to <code>system enable cronie</code>.<li><code>xarchiver</code> (with <code>p7zip</code>, <code>zip</code>, <code>unzip</code> and <code>unrar</code>) to uncompress stuff.<li><code>xreader</code> to read <code>.pdf</code> files.<li><code>sqlitebrowser</code> is always nice to tinker around with SQLite databases.<li><code>jre8-openjdk</code> if you want to run Java applications.<li><code>smartmontools</code> is nice with a SSD to view your disk statistics.</ul><p>After that, I configured my Super L key to launch <code>xfce4-popup-whiskermenu</code> so that it opens the application menu, pretty much the same as it would on Windows, moved the panels around and configured them to my needs, and it feels like home once more.<p>I made some mistakes while <a href=https://wiki.archlinux.org/index.php/Systemd-networkd>configuring systemd-networkd</a> and accidentally added a service that was incorrect, which caused boot to wait for it to timeout before completing. My boot time was taking 90 seconds longer because of this! <a href=https://www.reddit.com/r/archlinux/comments/4nv9yi/my_arch_greets_me_now_with_a_start_job/>The solution was to remove said service</a>, so this is something to look out for.<p>In order to find what was taking long, I had to edit the <a href=https://wiki.archlinux.org/index.php/kernel_parameters>kernel parameters</a> to remove the <code>quiet</code> option. I prefer seeing the output on what my computer is doing anyway, because it gives me a sense of progress and most importantly is of great value when things go wrong. Another interesting option is <code>noauto,x-systemd.automount</code>, which makes a disk lazily-mounted. If you have a slow disk, this could help speed things up.<p>If you see a service taking long, you can also use <code>systemd-analyze blame</code> to see what takes the longest, and <code>systemctl list-dependencies</code> is also helpful to find what services are active.<p>My <code>locale charmap</code> was spitting out a bunch of warnings:<pre><code class=language-sh data-lang=sh>$ locale charmap -locale: Cannot set LC_CTYPE to default locale: No such file or directory -locale: Cannot set LC_MESSAGES to default locale: No such file or directory -locale: Cannot set LC_ALL to default locale: No such file or directory -ANSI_X3.4-1968 -</code></pre><p>…ANSI encoding? Immediately I added the following to <code>~/.bashrc</code> and <code>~/.profile</code>:<pre><code class=language-sh data-lang=sh>export LC_ALL=en_US.UTF-8 -export LANG=en_US.UTF-8 -export LANGUAGE=en_US.UTF-8 -</code></pre><p>For some reason, I also had to edit <code>xfce4-terminal</code>'s preferences in advanced to change the default character encoding to UTF-8. This also solved my issues with pasting things into the terminal, and also proper rendering! I guess pastes were not working because it had some characters that could not be encoded.<p>To have working notifications, I added the following to <code>~/.bash_profile</code> after <code>exec startx</code>:<pre><code class=language-sh data-lang=sh>systemctl --user start xfce4-notifyd.service -</code></pre><p>I'm pretty sure there's a better way to do this, or maybe it's not even necessary, but this works for me.<p>Some of the other things I had left to do was setting up <code>sccache</code> to speed up Rust builds:<pre><code class=language-sh data-lang=sh>cargo install sccache -echo export RUSTC_WRAPPER=sccache >> ~/.bashrc -</code></pre><p>Once I had <code>cargo</code> ready, installed <code>hacksaw</code> and <code>shotgun</code> with it to perform screenshots.<p>I also disabled the security delay when downloading files in Firefox because it's just annoying, in <code>about:config</code> setting <code>security.dialog_enable_delay</code> to <code>0</code>, and added the <a href=https://alisdair.mcdiarmid.org/kill-sticky-headers/>Kill sticky headers</a> to my bookmarks (you may prefer <a href=https://github.com/t-mart/kill-sticky>the updated version</a>).<p>The <code>utils-linux</code> comes with a <code>fstrim</code> utility to <a href=https://wiki.archlinux.org/index.php/Solid_state_drive#Periodic_TRIM>trim the SSD weekly</a>, which I want enabled via <code>systemctl enable fstrim.timer</code> (you may also want to <code>start</code> it if you don't reboot often). For more SSD tips, check <a href=https://easylinuxtipsproject.blogspot.com/p/ssd.html>How to optimize your Solid State Drive</a>.<p>If the sound is funky prior to reboot, try <code>pulseaudio --kill</code> and <code>pulseaudio --start</code>, or delete <code>~/.config/pulse</code>.<p>I haven't been able to get the brightness keys to work yet, but it's not a big deal, because scrolling on the power manager plugin of Xfce does work (and also <code>xbacklight</code> works, or writing directly to <code>/sys/class/backlight/*</code>).<h2 id=tuning-windows>Tuning Windows</h2><p>On the Windows side, I disabled the annoying Windows defender by running (<kbd>Ctrl+R</kbd>) <code>gpedit.msc</code> and editing:<ul><li><em>Computer Configuration > Administrative Templates > Windows Components > Windows Defender » Turn off Windows Defender » Enable</em><li><em>User Configuration > Administrative Templates > Start Menu and Taskbar » Remove Notifications and Action Center » Enable</em></ul><p>I also updated the <a href=https://github.com/WindowsLies/BlockWindows/raw/master/hosts><code>hosts</code> file</a> (located at <code>%windir%\system32\Drivers\etc\hosts</code>) with the hope that it will stop some of the telemetry.<p>Last, to have consistent time on Windows and Linux, I changed the following registry key for a <code>qword</code> with value <code>1</code>:<pre><code>HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation\RealTimeIsUniversal -</code></pre><p>(The key might not exist, but you can create it if that's the case).<p>All this time, my laptop had the keyboard lights on, which have been quite annoying. Apparently, they also can cause <a href=https://www.reddit.com/r/ValveIndex/comments/cm6pos/psa_uninstalldisable_aura_sync_lighting_if_you/>massive FPS drops</a>. I headed over to <a href=https://rog.asus.com/downloads/>Asus Rog downloads</a>, selected Aura Sync…<pre><code class=language-md data-lang=md># Not Found - -The requested URL /campaign/aura/us/Sync.html was not found on this server. - -Additionally, a 404 Not Found error was encountered while trying to use an ErrorDocument to handle the request. -</code></pre><p>…great! I'll just find the <a href=https://www.asus.com/campaign/aura/global/>Aura site</a> somewhere else…<pre><code class=language-md data-lang=md># ASUS - -# We'll be back. - -Hi, our website is temporarily closed for service enhancements. - -We'll be back shortly.Thank you for your patience! -</code></pre><p>Oh come on. After waiting for the next day, I headed over, downloaded their software, tried to install it and it was an awful experience. It felt like I was purposedly installing malware. It spammed and flashed a lot of <code>cmd</code>'s on screen as if it was a virus. It was stuck at 100% doing that and then, Windows blue-screened with <code>KERNEL_MODE_HEAP_CORRUPTION</code>. Amazing. How do you screw up this bad?<p>Well, at least rebooting worked. I tried to <a href=https://answers.microsoft.com/en-us/windows/forum/all/unable-to-uninstall-asus-aura-sync-utility/e9bec36c-e62f-4773-80be-88fb68dace16>uninstall Aura, but of course that failed</a>. Using the <a href=https://support.microsoft.com/en-us/help/17588/windows-fix-problems-that-block-programs-being-installed-or-removed>troubleshooter to uninstall programs</a> helped me remove most of the crap that was installed.<p>After searching around how to disable the lights (because <a href=https://rog.asus.com/forum/showthread.php?112786-Option-to-Disable-Aura-Lights-on-Strix-G-series-(G531GT)-irrespective-of-OSes>my BIOS did not have this setting</a>), I stumbled upon <a href=https://rog.asus.com/us/innovation/armoury_crate/>"Armoury Crate"</a>. Okay, fine, I will install that.<p>The experience wasn't much better. It did the same thing with a lot of consoles flashing on screen. And of course, it resulted in another blue-screen, this time <code>KERNEL_SECURITY_CHECK_FAILURE</code>. To finish up, the BSOD kept happening as I rebooted the system. <del>Time to reinstall Windows once more.</del> After booting and crashing a few more times I could get into secure mode and perform the reinstall from there, which saved me from burning the <code>.iso</code> again.<p>Asus software might be good, but the software is utter crap.<p>After trying out <a href=https://github.com/wroberts/rogauracore>rogauracore</a> (which didn't list my model), it worked! I could disable the stupid lights from Linux, and <a href=https://gitlab.com/CalcProgrammer1/OpenRGB/-/wikis/home>OpenRGB</a> also works on Windows which may be worth checking out too.<p>Because <code>rougauracore</code> helped me and they linked to <a href=https://github.com/linuxhw/hw-probe/blob/master/README.md#appimage>hw-probe</a>, I decided to <a href=https://linux-hardware.org/?probe=0e3e48c501>run it on my system</a>, with the hopes it is useful for other people.<h2 id=closing-words>Closing words</h2><p>I hope the installation journey is at least useful to someone, or that you enjoyed reading about it all. If not, sorry!</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Shattered Pixel Dungeon | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Shattered Pixel Dungeon</h1><div class=time><p>2019-06-03</div><p><a href=https://shatteredpixel.com/shatteredpd/>Shattered Pixel Dungeon</a> is the classic roguelike RPG game with randomly-generated dungeons. As a new player, it was a bit frustrating to be constantly killed on the first levels of the dungeon, but with some practice it's easy to reach high levels if you can kill the first boss.<h2 id=basic-tips>Basic Tips</h2><p>The game comes with its own tips, but here's a short and straight-forward summary:<ul><li><strong>Don't rush into enemies</strong>. Abuse doors and small corridors to kill them one by one. You can use the clock on the bottom left to wait a turn without moving.<li><strong>Explore each level at full</strong>. You will find goodies and gain XP while doing so.<li><strong>Upon finding a special room</strong> (e.g. has a chest but is protected by piranhas), drink all potions that you found in that level until there's one that helps you (e.g. be invisible so piranhas leave you alone). There is guaranteed to be a helpful one per level with special rooms.<li><strong>Drink potions as early as possible</strong>. Harmful potions do less damage on early levels (and if you die, you lose less). This will keep them identified early for the rest of the game.<li><strong>Read scrolls as early as possible</strong> as well. This will keep them identified. It may be worth to wait until you have an item which may be cursed and until the level is clear, because some scrolls clean curses and others alert enemies.<li><strong>Food and health are resources</strong> that you have to <em>manage</em>, not keep them always at full. Even if you are starving and taking damage, you may not need to eat <em>just yet</em>, since food is scarce. Eat when you are low on health or in possible danger.<li><strong>Piranhas</strong>. Seriously, just leave them alone if you are melee. They're free food if you're playing ranged, though.<li><strong>Prefer armor over weapons</strong>. And make sure to identify or clean it from curses before wearing anything!<li><strong>Find a dew vial early</strong>. It's often a better idea to store dew (health) for later than to use it as soon as possible.</ul><h2 id=bosses>Bosses</h2><p>There is a boss every 5 levels.<ul><li><strong>Level 5 boss</strong>. Try to stay on water, but don't let <em>it</em> stay on water since it will heal. Be careful when he starts enraging.<li><strong>Level 10 boss</strong>. Ranged weapons are good against it.<li><strong>Level 15 boss</strong>. I somehow managed to tank it with a health potion.<li><strong>Level 20 boss</strong>. I didn't get this far just yet. You are advised to use scrolls of magic mapping in the last levels to skip straight to the boss, since there's nothing else of value.<li><strong>Level 25 boss</strong>. The final boss. Good job if you made it this far!</ul><h2 id=mage>Mage</h2><p>If you followed the basic tips, you will sooner or later make use of two scrolls of upgrade in a single run. This will unlock the mage class, which is ridiculously powerful. He starts with a ranged-weapon, a magic missile wand, which is really helpful to keep enemies at a distance. Normally, you want to use this at first to surprise attack them soon, and if you are low on charges, you may go melee on normal enemies if you are confident.<h2 id=luck>Luck</h2><p>This game is all about luck and patience! Some runs will be better than others, and you should thank and pray the RNG gods for them. If you don't, they will only give you cursed items and not a single scroll to clean them. So, good luck and enjoy playing!</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Atemporal Blog Posts | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Atemporal Blog Posts</h1><div class=time><p>2018-02-03<p>last updated 2021-02-19</div><p>These are some interesting posts and links I've found around the web. I believe they are quite interesting and nice reads, so if you have the time, I encourage you to check some out.<h2 id=algorithms>Algorithms</h2><ul><li><a href=http://www.tannerhelland.com/4660/dithering-eleven-algorithms-source-code/>Image Dithering: Eleven Algorithms and Source Code</a>. What does it mean and how to achieve it?<li><a href=https://cristian.io/post/bloom-filters/>Idempotence layer on bloom filters</a>. What are they and how can they help?<li><a href=https://en.wikipedia.org/wiki/Huffman_coding>Huffman coding</a>. This encoding is a simple yet interesting way of compressing information.<li><a href=https://github.com/mxgmn/WaveFunctionCollapse>Wave Function Collapse</a>. Bitmap & tilemap generation from a single example with the help of ideas from quantum mechanics.<li><a href=https://blog.nelhage.com/2015/02/regular-expression-search-with-suffix-arrays/>Regular Expression Search with Suffix Arrays</a>. A way to efficiently search large amounts of text.</ul><h2 id=culture>Culture</h2><ul><li><a href=https://www.wired.com/story/ideas-joi-ito-robot-overlords/>Why Westerners Fear Robots and the Japanese Do Not</a>. Explains some possible reasons for this case.<li><a href=http://catb.org/%7Eesr/faqs/smart-questions.html>How To Ask Questions The Smart Way</a>. Some bits of hacker culture and amazing tips on how to ask a question.<li><a href=http://apenwarr.ca/log/?m=201809#14>XML, blockchains, and the strange shapes of progress</a>. Some of history about XML and blockchain.<li><a href=https://czep.net/17/legion-of-lobotomized-unices.html>Legion of lobotomized unices</a>. A time where computers are treated a lot more nicely.<li><a href=https://eli.thegreenplace.net/2016/the-expression-problem-and-its-solutions/>The Expression Problem and its solutions</a>. What is it and what can we do to solve it?<li><a href=http://allendowney.blogspot.com/2015/08/the-inspection-paradox-is-everywhere.html>The Inspection Paradox is Everywhere</a>. Interesting and very common phenomena.<li><a href=https://github.com/ChrisKnott/Algojammer>An experimental code editor for writing algorithms</a>. Contains several links to different tools for reverse debugging.<li><a href=http://habitatchronicles.com/2017/05/what-are-capabilities/>What Are Capabilities?</a> Good ideas with great security implications.<li><a href=https://blog.aurynn.com/2015/12/16-contempt-culture>Contempt Culture</a>. Or why you should not speak crap about your non-favourite programming languages.<li><a href=https://www.lesswrong.com/posts/tscc3e5eujrsEeFN4/well-kept-gardens-die-by-pacifism>Well-Kept Gardens Die By Pacifism</a>. Risks any online community can run into.<li><a href=https://ncase.me/>It's Nicky Case!</a> They make some cool things worth checking out, I really like "we become what we behold".</ul><h2 id=debate>Debate</h2><ul><li><a href=https://steemit.com/opensource/@crell/open-source-is-awful>Open Source is awful</a>. Has some points about why is it bad and how it could improve.<li><a href=http://www.mondo2000.com/2018/01/17/pink-lexical-goop-dark-side-autocorrect/>Pink Lexical Goop: The Dark Side of Autocorrect</a>. It can shape how you think.<li><a href=http://blog.ploeh.dk/2015/08/03/idiomatic-or-idiosyncratic/>Idiomatic or idiosyncratic?</a> Can porting code constructs from other languages have a positive effect?<li><a href=https://gamasutra.com/view/news/169296/Indepth_Functional_programming_in_C.php>In-depth: Functional programming in C++</a>. Is it useful to bother with functional concepts in a language like C++?<li><a href=https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/>Notes on structured concurrency, or: Go statement considered harmful</a>.<li><a href=https://queue.acm.org/detail.cfm?id=3212479>C Is Not a Low-level Language</a>. Could there be alternative programming models designed for more specialized CPUs?</ul><h2 id=food-for-thought>Food for Thought</h2><ul><li><a href=https://www.hillelwayne.com/post/divide-by-zero/>1/0 = 0</a>. Explores why it makes sense to redefine mathemathics under some circumstances, and why it is possible to do so.<li><a href=https://jeremykun.com/2018/04/13/for-mathematicians-does-not-mean-equality/>For mathematicians, = does not mean equality</a>. What other definitions does the equal sign have?<li><a href=https://www.lesswrong.com/posts/2MD3NMLBPCqPfnfre/cached-thoughts>Cached Thoughts</a>. How is it possible that our brains work at all?<li><a href=http://tonsky.me/blog/disenchantment/>Software disenchantment</a>. Faster hardware and slower software is a trend. <ul><li><a href=https://blackhole12.com/blog/software-engineering-is-bad-but-it-s-not-that-bad/>Software Engineering Is Bad, But That's Not Why</a>. This post has some good counterpoints to Software disenchantment.</ul><li><a href=http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/>What Color is Your Function?</a>. Spoiler: can we approach asynchronous IO better?<li><a href=https://hackernoon.com/im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5>I'm harvesting credit card numbers and passwords from your site</a>. A word of warning when mindlessly adding dependencies.<li><a href=https://medium.com/message/everything-is-broken-81e5f33a24e1>Everything Is Broken</a>. Some of the (probable) truths about our world.<li><a href=http://johnsalvatier.org/blog/2017/reality-has-a-surprising-amount-of-detail>Reality has a surprising amount of detail</a>.</ul><h2 id=funny>Funny</h2><ul><li><a href=http://thedailywtf.com/articles/We-Use-BobX>We Use BobX</a>. BobX.<li><a href=http://thedailywtf.com/articles/the-inner-json-effect>The Inner JSON Effect</a>. For some reason, custom languages are in.<li><a href=https://thedailywtf.com/articles/exponential-backup>Exponential Backup</a>. Far better than git.<li><a href=https://thedailywtf.com/articles/ITAPPMONROBOT>ITAPPMONROBOT</a>. Solving software problems with hardware.<li><a href=https://thedailywtf.com/articles/a-tapestry-of-threads>A Tapestry of Threads</a>. More threads must mean faster code, right?<li><a href=https://medium.com/commitlog/a-brief-totally-accurate-history-of-programming-languages-cd93ec806124>A Brief Totally Accurate History Of Programming Languages</a>. Don't take offense for it!</ul><h2 id=graphics>Graphics</h2><ul><li><a href=http://shaunlebron.github.io/visualizing-projections/>Visualizing Projections</a>. Small post about different projection methods.<li><a href=http://www.iquilezles.org/www/index.htm>Inigo Quilez :: fractals, computer graphics, mathematics, shaders, demoscene and more</a> A <em>lot</em> of useful and quality articles regarding computer graphics.</ul><h2 id=history>History</h2><ul><li><a href=https://twobithistory.org/2018/08/18/ada-lovelace-note-g.html>What Did Ada Lovelace's Program Actually Do?</a>. And other characters that took part in the beginning's of programming.<li><a href=https://chrisdown.name/2018/01/02/in-defence-of-swap.html>In defence of swap: common misconceptions</a>. Swap is still an useful concept.<li><a href=https://www.pacifict.com/Story/>The Graphing Calculator Story</a>. A great classic Apple tale.<li><a href=https://twobithistory.org/2018/10/14/lisp.html>How Lisp Became God's Own Programming Language</a>. Lisp as a foundational programming language.</ul><h2 id=motivational>Motivational</h2><ul><li><a href=https://www.joelonsoftware.com/2002/01/06/fire-and-motion/>Fire And Motion</a>. What does actually take to get things done?<li><a href=https://realmensch.org/2017/08/25/the-parable-of-the-two-programmers/>The Parable of the Two Programmers</a>. This tale is about two different types of programmer and their respective endings in a company, illustrating how the one you wouldn't expect to actually ends in a better situation.<li><a href=https://byorgey.wordpress.com/2018/05/06/conversations-with-a-six-year-old-on-functional-programming/>Conversations with a six-year-old on functional programming</a>. Little kids today can be really interested in technological topics.<li><a href=https://bulletproofmusician.com/how-many-hours-a-day-should-you-practice/>How Many Hours a Day Should You Practice?</a>. While the article is about music, it applies to any other areas.<li><a href=http://nathanmarz.com/blog/suffering-oriented-programming.html>Suffering-oriented programming</a>. A possibly new approach on how you could tackle your new projects.<li><a href=https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/>Things You Should Never Do, Part I</a>. There is no need to rewrite your code.</ul><h2 id=optimization>Optimization</h2><ul><li><a href=http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html>What Every C Programmer Should Know About Undefined Behavior #1/3</a>. Explains what undefined behaviour is and why it makes sense.<li><a href=http://ridiculousfish.com/blog/posts/labor-of-division-episode-i.html>Labor of Division (Episode I)</a>. Some tricks to divide without division.<li><a href=http://blog.moertel.com/posts/2013-12-14-great-old-timey-game-programming-hack.html>A Great Old-Timey Game-Programming Hack</a>. Abusing instructions to make games playable even on the slowest hardware.<li><a href=https://web.archive.org/web/20191213224640/https://people.eecs.berkeley.edu/%7Esangjin/2012/12/21/epoll-vs-kqueue.html>Scalable Event Multiplexing: epoll vs kqueue</a>. How good OS primitives can really help performance and scability.<li><a href=https://adamdrake.com/command-line-tools-can-be-235x-faster-than-your-hadoop-cluster.html>Command-line Tools can be 235x Faster than your Hadoop Cluster</a>. Or how to use the right tool for the right job.<li><a href=https://nullprogram.com/blog/2018/05/27/>When FFI Function Calls Beat Native C</a>. How lua beat C at it and the explanation behind it.<li><a href=http://igoro.com/archive/gallery-of-processor-cache-effects/>Gallery of Processor Cache Effects</a>. Knowing a few things about the cache can make a big difference.</ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,87 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> A practical example with Hadoop | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>A practical example with Hadoop</h1><div class=time><p>2020-04-01T02:00:00+00:00<p>last updated 2020-04-03T08:43:41+00:00</div><p>In our <a href=/blog/ribw/introduction-to-hadoop-and-its-mapreduce/>previous Hadoop post</a>, we learnt what it is, how it originated, and how it works, from a theoretical standpoint. Here we will instead focus on a more practical example with Hadoop.<p>This post will showcase my own implementation to implement a word counter for any plain text document that you want to analyze.<h2 id=installation>Installation</h2><p>Before running any piece of software, its executable code must first be downloaded into our computers so that we can run it. Head over to <a href=http://hadoop.apache.org/releases.html>Apache Hadoop’s releases</a> and download the <a href=https://www.apache.org/dyn/closer.cgi/hadoop/common/hadoop-3.2.1/hadoop-3.2.1.tar.gz>latest binary version</a> at the time of writing (3.2.1).<p>We will be using the <a href=https://linuxmint.com/>Linux Mint</a> distribution because I love its simplicity, although the process shown here should work just fine on any similar Linux distribution such as <a href=https://ubuntu.com/>Ubuntu</a>.<p>Once the archive download is complete, extract it with any tool of your choice (graphical or using the terminal) and execute it. Make sure you have a version of Java installed, such as <a href=https://openjdk.java.net/>OpenJDK</a>.<p>Here are all the three steps in the command line:<pre><code>wget https://www.apache.org/dyn/closer.cgi/hadoop/common/hadoop-3.2.1/hadoop-3.2.1.tar.gz -tar xf hadoop-3.2.1.tar.gz -hadoop-3.2.1/bin/hadoop version -</code></pre><h2 id=processing-data>Processing data</h2><p>To take advantage of Hadoop, we have to design our code to work in the MapReduce model. Both the map and reduce phase work on key-value pairs as input and output, and both have a programmer-defined function.<p>We will use Java, because it’s a dependency that we already have anyway, so might as well.<p>Our map function needs to split each of the lines we receive as input into words, and we will also convert them to lowercase, thus preparing the data for later use (counting words). There won’t be bad records, so we don’t have to worry about that.<p>Copy or reproduce the following code in a file called <code>WordCountMapper.java</code>, using any text editor of your choice:<pre><code>import java.io.IOException; - -import org.apache.hadoop.io.IntWritable; -import org.apache.hadoop.io.LongWritable; -import org.apache.hadoop.io.Text; -import org.apache.hadoop.mapreduce.Mapper; - -public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> { - @Override - public void map(LongWritable key, Text value, Context context) - throws IOException, InterruptedException { - for (String word : value.toString().split("\\W")) { - context.write(new Text(word.toLowerCase()), new IntWritable(1)); - } - } -} -</code></pre><p>Now, let’s create the <code>WordCountReducer.java</code> file. Its job is to reduce the data from multiple values into just one. We do that by summing all the values (our word count so far):<pre><code>import java.io.IOException; -import java.util.Iterator; - -import org.apache.hadoop.io.IntWritable; -import org.apache.hadoop.io.Text; -import org.apache.hadoop.mapreduce.Reducer; - -public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> { - @Override - public void reduce(Text key, Iterable<IntWritable> values, Context context) - throws IOException, InterruptedException { - int count = 0; - for (IntWritable value : values) { - count += value.get(); - } - context.write(key, new IntWritable(count)); - } -} -</code></pre><p>Let’s just take a moment to appreciate how absolutely tiny this code is, and it’s Java! Hadoop’s API is really awesome and lets us write such concise code to achieve what we need.<p>Last, let’s write the <code>main</code> method, or else we won’t be able to run it. In our new file <code>WordCount.java</code>:<pre><code>import org.apache.hadoop.fs.Path; -import org.apache.hadoop.io.IntWritable; -import org.apache.hadoop.io.Text; -import org.apache.hadoop.mapreduce.Job; -import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; -import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; - -public class WordCount { - public static void main(String[] args) throws Exception { - if (args.length != 2) { - System.err.println("usage: java WordCount <input path> <output path>"); - System.exit(-1); - } - - Job job = Job.getInstance(); - - job.setJobName("Word count"); - job.setJarByClass(WordCount.class); - job.setMapperClass(WordCountMapper.class); - job.setReducerClass(WordCountReducer.class); - job.setOutputKeyClass(Text.class); - job.setOutputValueClass(IntWritable.class); - - FileInputFormat.addInputPath(job, new Path(args[0])); - FileOutputFormat.setOutputPath(job, new Path(args[1])); - - boolean result = job.waitForCompletion(true); - - System.exit(result ? 0 : 1); - } -} -</code></pre><p>And compile by including the required <code>.jar</code> dependencies in Java’s classpath with the <code>-cp</code> switch:<pre><code>javac -cp "hadoop-3.2.1/share/hadoop/common/*:hadoop-3.2.1/share/hadoop/mapreduce/*" *.java -</code></pre><p>At last, we can run it (also specifying the dependencies in the classpath, this one’s a mouthful). Let’s run it on the same <code>WordCount.java</code> source file we wrote:<pre><code>java -cp ".:hadoop-3.2.1/share/hadoop/common/*:hadoop-3.2.1/share/hadoop/common/lib/*:hadoop-3.2.1/share/hadoop/mapreduce/*:hadoop-3.2.1/share/hadoop/mapreduce/lib/*:hadoop-3.2.1/share/hadoop/yarn/*:hadoop-3.2.1/share/hadoop/yarn/lib/*:hadoop-3.2.1/share/hadoop/hdfs/*:hadoop-3.2.1/share/hadoop/hdfs/lib/*" WordCount WordCount.java results -</code></pre><p>Hooray! We should have a new <code>results/</code> folder along with the following files:<pre><code>$ ls results -part-r-00000 _SUCCESS -$ cat results/part-r-00000 - 154 -0 2 -1 3 -2 1 -addinputpath 1 -apache 6 -args 4 -boolean 1 -class 6 -count 1 -err 1 -exception 1 --snip- (output cut for clarity) -</code></pre><p>It worked! Now this example was obviously tiny, but hopefully enough to demonstrate how to get the basics running on real world data.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,8 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> About Boolean Retrieval | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>About Boolean Retrieval</h1><div class=time><p>2020-02-25T00:00:29+00:00<p>last updated 2020-03-18T09:38:02+00:00</div><p>This entry will discuss the section on the <em><a href=https://nlp.stanford.edu/IR-book/pdf/01bool.pdf>Boolean retrieval</a></em> section of the book <em><a href=https://nlp.stanford.edu/IR-book/pdf/irbookprint.pdf>An Introduction to Information Retrieval</a></em>.<h2 id=summary-on-the-topic>Summary on the topic</h2><p>Boolean retrieval is one of the many ways information retrieval (finding materials that satisfy an information need), often simply called <em>search</em>.<p>A simple way to retrieve information is to <em>grep</em> through the text (term named after the Unix tool <code>grep</code>), scanning text linearly and excluding it on certain criteria. However, this falls short when the volume of the data grows, more complex queries are desired, or one seeks some sort of ranking.<p>To avoid linear scanning, we build an <em>index</em> and record for each document whether it contains each term out of our full dictionary of terms (which may be words in a chapter and words in the book). This results in a binary term-document <em>incidence matrix</em>. Such a possible matrix is:<table><tbody><tr><td><em> word/play </em><td><strong> Antony and Cleopatra </strong><td><strong> Julius Caesar </strong><td><strong> The Tempest </strong><td><strong> … </strong><tr><td><strong> Antony </strong><td>1<td>1<td>0<td><tr><td><strong> Brutus </strong><td>1<td>1<td>0<td><tr><td><strong> Caesar </strong><td>1<td>1<td>0<td><tr><td><strong> Calpurnia </strong><td>0<td>1<td>0<td><tr><td><strong> Cleopatra </strong><td>1<td>0<td>0<td><tr><td><strong> mercy </strong><td>1<td>0<td>1<td><tr><td><strong> worser </strong><td>1<td>0<td>1<td><tr><td><strong> … </strong><td><td><td><td></table><p>We can look at this matrix’s rows or columns to obtain a vector for each term indicating where it appears, or a vector for each document indicating the terms it contains.<p>Now, answering a query such as <code>Brutus AND Caesar AND NOT Calpurnia</code> becomes trivial:<pre><code>VECTOR(Brutus) AND VECTOR(Caesar) AND COMPLEMENT(VECTOR(Calpurnia)) -= 110 AND 110 AND COMPLEMENT(010) -= 110 AND 110 AND 101 -= 100 -</code></pre><p>The query is only satisfied for our first column.<p>The <em>Boolean retrieval model</em> is thus a model that treats documents as a set of terms, in which we can perform any query in the form of Boolean expressions of terms, combined with <code>OR</code>, <code>AND</code>, and <code>NOT</code>.<p>Now, building such a matrix is often not feasible due to the sheer amount of data (say, a matrix with 500,000 terms across 1,000,000 documents, each with roughly 1,000 terms). However, it is important to notice that most of the terms will be <em>missing</em> when examining each document. In our example, this means 99.8% or more of the cells will be 0. We can instead record the <em>positions</em> of the 1’s. This is known as an <em>inverted index</em>.<p>The inverted index is a dictionary of terms, each containing a list that records in which documents it appears (<em>postings</em>). Applied to boolean retrieval, we would:<ol><li>Collects the documents to be indexed, assign a unique identifier each<li>Tokenize the text in the documents into a list of terms<li>Normalize the tokens, which now become indexing terms<li>Index the documents</ol><table><tbody><tr><td><strong> Dictionary </strong><td><strong> Postings </strong><tr><td>Brutus<td>1, 2, 4, 11, 31, 45, 173, 174<tr><td>Caesar<td>1, 2, 4, 5, 6, 16, 57, 132, …<tr><td>Calpurnia<td>2, 31, 54, 101<tr><td>…<td></table><p>Sort the pairs <code>(term, document_id)</code> so that the terms are alphabetical, and merge multiple occurences into one. Group instances of the same term and split again into a sorted list of postings.<table><tbody><tr><td><strong> term </strong><td><strong> document_id </strong><tr><td>I<td>1<tr><td>did<td>1<tr><td>…<td><tr><td>with<td>2</table><table><tbody><tr><td><strong> term </strong><td><strong> document_id </strong><tr><td>be<td>2<tr><td>brutus<td>1<tr><td>brutus<td>2<tr><td>…<td></table><table><tbody><tr><td><strong> term </strong><td><strong> frequency </strong><td><strong> postings list </strong><tr><td>be<td>1<td>2<tr><td>brutus<td>2<td>1, 2<tr><td>capitol<td>1<td>1<tr><td>…<td><td></table><p>Intersecting posting lists now becomes of transversing both lists in order:<pre><code>Brutus : 1 -> 2 -> 4 -> 11 -> 31 -> 45 -> 173 -> 174 -Calpurnia: 2 -> 31 -> 54 -> 101 -Intersect: 2 -> 31 -</code></pre><p>A simple conjunctive query (e.g. <code>Brutus AND Calpurnia</code>) is executed as follows:<ol><li>Locate <code>Brutus</code> in the dictionary<li>Retrieve its postings<li>Locate <code>Calpurnia</code> in the dictionary<li>Retrieve its postings<li>Intersect (<em>merge</em>) both postings</ol><p>Since the lists are sorted, walking both of them can be done in <em>O(n)</em> time. By also storing the frequency, we can optimize the order in which we execute arbitrary queries, although we won’t go into detail.<h2 id=thoughts>Thoughts</h2><p>The boolean retrieval model can be implemented with relative ease, and can help with storage and efficient querying of the information if we intend to perform boolean queries.<p>However, the basic design lacks other useful operations, such as a «near» operator, or the ability to rank the results.<p>All in all, it’s an interesting way to look at the data and query it efficiently.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,160 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Build your own PC | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Build your own PC</h1><div class=time><p>2020-02-25T02:00:12+00:00<p>last updated 2020-03-18T09:38:46+00:00</div><p><em>…where PC obviously stands for Personal Crawler</em>.<hr><p>This post contains the source code for a very simple crawler written in Java. You can compile and run it on any file or directory, and it will calculate the frequency of all the words it finds.<h2 id=source-code>Source code</h2><p>Paste the following code in a new file called <code>Crawl.java</code>:<pre><code>import java.io.*; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -class Crawl { - // Regex used to tokenize the words from a line of text - private final static Pattern WORDS = Pattern.compile("\\w+"); - - // The file where we will cache our results - private final static File INDEX_FILE = new File("index.bin"); - - // Helper method to determine if a file is a text file or not - private static boolean isTextFile(File file) { - String name = file.getName().toLowerCase(); - return name.endsWith(".txt") - || name.endsWith(".java") - || name.endsWith(".c") - || name.endsWith(".cpp") - || name.endsWith(".h") - || name.endsWith(".hpp") - || name.endsWith(".html") - || name.endsWith(".css") - || name.endsWith(".js"); - } - - // Normalizes a string by converting it to lowercase and removing accents - private static String normalize(String string) { - return string.toLowerCase() - .replace("á", "a") - .replace("é", "e") - .replace("í", "i") - .replace("ó", "o") - .replace("ú", "u"); - } - - // Recursively fills the map with the count of words found on all the text files - static void fillWordMap(Map<String, Integer> map, File root) throws IOException { - // Our file queue begins with the root - Queue<File> fileQueue = new ArrayDeque<>(); - fileQueue.add(root); - - // For as long as the queue is not empty... - File file; - while ((file = fileQueue.poll()) != null) { - if (!file.exists() || !file.canRead()) { - // ...ignore files for which we don't have permission... - System.err.println("warning: cannot read file: " + file); - } else if (file.isDirectory()) { - // ...else if it's a directory, extend our queue with its files... - File[] files = file.listFiles(); - if (files == null) { - System.err.println("warning: cannot list dir: " + file); - } else { - fileQueue.addAll(Arrays.asList(files)); - } - } else if (isTextFile(file)) { - // ...otherwise, count the words in the file. - countWordsInFile(map, file); - } - } - } - - // Counts the words in a single file and adds the count to the map. - public static void countWordsInFile(Map<String, Integer> map, File file) throws IOException { - BufferedReader reader = new BufferedReader(new FileReader(file)); - - String line; - while ((line = reader.readLine()) != null) { - Matcher matcher = WORDS.matcher(line); - while (matcher.find()) { - String token = normalize(matcher.group()); - Integer count = map.get(token); - if (count == null) { - map.put(token, 1); - } else { - map.put(token, count + 1); - } - } - } - - reader.close(); - } - - // Prints the map of word count to the desired output stream. - public static void printWordMap(Map<String, Integer> map, PrintStream writer) { - List<String> keys = new ArrayList<>(map.keySet()); - Collections.sort(keys); - for (String key : keys) { - writer.println(key + "\t" + map.get(key)); - } - } - - @SuppressWarnings("unchecked") - public static void main(String[] args) throws IOException, ClassNotFoundException { - // Validate arguments - if (args.length == 1 && args[0].equals("--help")) { - System.err.println("usage: java Crawl [input]"); - return; - } - - File root = new File(args.length > 0 ? args[0] : "."); - - // Loading or generating the map where we aggregate the data {word: count} - Map<String, Integer> map; - if (INDEX_FILE.isFile()) { - System.err.println("Found existing index file: " + INDEX_FILE); - try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(INDEX_FILE))) { - map = (Map<String, Integer>) ois.readObject(); - } - } else { - System.err.println("Index file not found: " + INDEX_FILE + "; indexing..."); - map = new TreeMap<>(); - fillWordMap(map, root); - // Cache the results to avoid doing the work a next time - try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(INDEX_FILE))) { - out.writeObject(map); - } - } - - // Ask the user in a loop to query for words - Scanner scanner = new Scanner(System.in); - while (true) { - System.out.print("Escriba palabra a consultar (o Enter para salir): "); - System.out.flush(); - String line = scanner.nextLine().trim(); - if (line.isEmpty()) { - break; - } - - line = normalize(line); - Integer count = map.get(line); - if (count == null) { - System.out.println(String.format("La palabra \"%s\" no está presente", line)); - } else if (count == 1) { - System.out.println(String.format("La palabra \"%s\" está presente 1 vez", line)); - } else { - System.out.println(String.format("La palabra \"%s\" está presente %d veces", line, count)); - } - } - } -} -</code></pre><p>It can be compiled and executed as follows:<pre><code>javac Crawl.java -java Crawl -</code></pre><p>Instead of copy-pasting the code, you may also download it as a <code>.zip</code>:<p><em>(contents removed)</em><h2 id=addendum>Addendum</h2><p>The following simple function can be used if one desires to print the contents of a file:<pre><code>public static void printFile(File file) { - if (isTextFile(file)) { - System.out.println('\n' + file.getName()); - try (BufferedReader reader = new BufferedReader(new FileReader(file))) { - String line; - while ((line = reader.readLine()) != null) { - System.out.println(line); - } - } catch (FileNotFoundException ignored) { - System.err.println("warning: file disappeared while reading: " + file); - } catch (IOException e) { - e.printStackTrace(); - } - } -} -</code></pre></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,16 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Cassandra: an Introduction | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Cassandra: an Introduction</h1><div class=time><p>2020-03-05T00:00:45+00:00<p>last updated 2020-03-18T09:47:05+00:00</div><p>This is the first post in the Cassandra series, where we will introduce the Cassandra database system and take a look at its features and installation methods.<p>Other posts in this series:<ul><li><a href=/blog/ribw/cassandra-an-introduction/>Cassandra: an Introduction</a> (this post)</ul><p>This post is co-authored wih Classmate.<hr><p><img src=https://lonami.dev/blog/ribw/cassandra-an-introduction/cassandra-database-e1584191543401.jpg alt="NoSQL database – Apache Cassandra – First delivery"><h2 id=purpose-of-technology>Purpose of technology</h2><p>Apache Cassandra is a <strong>NoSQL</strong>, <strong>open-source</strong>, <strong>distributed “key-value” database</strong>. It allows <strong>large volumes of distributed data</strong>. The main **goal **is provide <strong>linear scalability and availabilitywithout compromising performance</strong>. Besides, Cassandra <strong>supports replication</strong> across multiple datacenters, providing low latency.<h2 id=how-it-works>How it works</h2><p>Cassandra’s distributed **architecture **is based on a series of <strong>equal nodes</strong> that communicate with a <strong>P2P protocol</strong> so that <strong>redundancy is maximum</strong>. It offers robust support for multiple datacenters, with <strong>asynchronous replication</strong> without the need for a master server.<p>Besides, Cassandra’s <strong>data model consists of partitioning the rows</strong>, which are rearranged into <strong>different tables</strong>. The primary keys of each table have a first component that is the <strong>partition key</strong>. Within a partition, the rows are grouped by the remaining columns of the key. The other columns can be indexed separately from the primary key.<p>These tables can be <strong>created, deleted, updated and queried****at runtime without blocking</strong> each other. However it does <strong>not support joins or subqueries</strong>, but instead <strong>emphasizes denormalization</strong> through features like collections.<p>Nowadays, Cassandra uses its own query language called <strong>CQL</strong> (<strong>Cassandra Query Language</strong>), with a <strong>similar syntax to SQL</strong>. It also allows access from <strong>JDBC</strong>.<p><img src=https://lonami.dev/blog/ribw/cassandra-an-introduction/s0GHpggGZXOFcdhypRWV4trU-PkSI6lukEv54pLZnoirh0GlDVAc4LamB1Dy.png> _ Cassandra architecture _<h2 id=features>Features</h2><ul><li><strong>Decentralized</strong>: there are <strong>no single points of failure</strong>, every **node **in the cluster has the <strong>same role</strong> and there is <strong>no master node</strong>, so each node <strong>can service any request</strong>, besides the data is distributed across the cluster.<li>Supports **replication **and multiple replication of <strong>data center</strong>: the replication strategies are <strong>configurable</strong>.<li>**Scalability: **reading and writing performance increases linearly as new nodes are added, also <strong>new nodes</strong> can be <strong>added without interrupting</strong> application <strong>execution</strong>.<li><strong>Fault tolerance: data replication</strong> is done **automatically **in several nodes in order to recover from failures. It is possible to <strong>replace failure nodes****without <strong>making</strong> inactivity time or interruptions</strong> to the application.<li>**Consistency: **a choice of consistency level is provided for <strong>reading and writing</strong>.<li><strong>MapReduce support</strong>: it is **integrated **with <strong>Apache Hadoop</strong> to support MapReduce.<li><strong>Query language</strong>: it has its own query language called **CQL (Cassandra Query Language) **</ul><h2 id=corner-in-cap-theorem>Corner in CAP theorem</h2><p><strong>Apache Cassandra</strong> is usually described as an “<strong>AP</strong>” system because it guarantees <strong>availability</strong> and <strong>partition/fault tolerance</strong>. So it errs on the side of ensuring data availability even if this means <strong>sacrificing consistency</strong>. But, despite this fact, Apache Cassandra <strong>seeks to satisfy all three requirements</strong> (Consistency, Availability and Fault tolerance) simultaneously and can be <strong>configured to behave</strong> like a “<strong>CP</strong>” database, guaranteeing <strong>consistency and partition/fault tolerance</strong>.<p><img src=https://lonami.dev/blog/ribw/cassandra-an-introduction/rf3n9LTOKCQVbx4qrn7NPSVcRcwE1LxR_khi-9Qc51Hcbg6BHHPu-0GZjUwD.png> <em>Cassandra in CAP Theorem</em><h2 id=download>Download</h2><p>In order to download the file, with extension .tar.gz. you must visit the <a href=https://cassandra.apache.org/download/>download site</a> and click on the file “<a href=https://ftp.cixug.es/apache/cassandra/3.11.6/apache-cassandra-3.11.6-bin.tar.gz>https://ftp.cixug.es/apache/cassandra/3.11.6/apache-cassandra-3.11.6-bin.tar.gz</a>”. It is important to mention that the previous link is related to the 3.11.6 version.<h2 id=installation>Installation</h2><p>This database can only be installed on Linux distributions and Mac OS X systems, so, it is not possible to install it on Microsoft Windows.<p>The first main requirement is having installed Java 8 in <strong>Ubuntu</strong>, the OS that we will use. Therefore, the Java 8 installation is explained below. First open a terminal and execute the next command:<pre><code>sudo apt update -sudo apt install openjdk-8-jdk openjdk-8-jre -</code></pre><p>In order to establish Java as a environment variable it is needed to open the file “/.bashrc”:<pre><code>nano ~/.bashrc -</code></pre><p>And add at the end of it the path where Java is installed, as follows:<pre><code>export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/jre/ -export PATH=$PATH:$JAVA_HOME/bin -</code></pre><p>At this point, save the file and execute the next command, note that it does the same effect re-opening the terminal:<pre><code>source ~/.bashrc -</code></pre><p>In order to check if the Java environment variable is set correctly, run the next command:<pre><code>echo $JAVA_HOME -</code></pre><p><img src=https://lonami.dev/blog/ribw/cassandra-an-introduction/JUUmX5MIHynJR_K9EdCgKeJcpINeCGRRt2QRu4JLPtRhCVidOhcbWwVTQjyu.png> <em>$JAVAHOME variable</em><p>Afterwards, it is possible to check the installed Java version with the command:<pre><code>java -version -</code></pre><p><img src=https://lonami.dev/blog/ribw/cassandra-an-introduction/z9v1-0hpZwjI4U5UZej9cRGN5-Y4AZl0WUPWyQ_-JlzTAIvZtTFPnKY2xMQ_.png> <em>Java version</em><p>The next requirement is having installed the latest version of Python 2.7. This can be checked with the command:<pre><code>python --version -</code></pre><p>If it is not installed, to install it, it is as simple as run the next command in the terminal:<pre><code>sudo apt install python -</code></pre><p>Note: it is better to use “python2” instead of “python” because in that way, you force to user Python 2.7. Modern distributions will use Python 3 for the «python» command.<p>Therefore, it is possible to check the installed Python version with the command:<pre><code>python --version -</code></pre><p><img src=https://lonami.dev/blog/ribw/cassandra-an-introduction/Ger5Vw_e1HIK84QgRub-BwGmzIGKasgiYb4jHdfRNRrvG4d6Msp_3Vk62-9i.png> <em>Python version</em><p>Once both requirements are ready, next step is to unzip the file previously downloaded, right click on the file and select “Extract here” or with the next command, on the directory where is the downloaded file.<pre><code>tar -zxvf apache-cassandra-x.x.x-bin.tar.gz -</code></pre><p>In order to check if the installation is completed, you can execute the next command, in the root folder of the project. This will start Cassandra in a single node.<pre><code>/bin/cassandra -</code></pre><p>It is possible to make a get some data from Cassandra with CQL (Cassandra Query Language). To check this execute the next command in another terminal.<pre><code>/bin/cqlsh localhost -</code></pre><p>Once CQL is open, type the next sentence and check the result:<pre><code>SELECT cluster_name, listen_address from system.local; -</code></pre><p>The output should be:<p><img src=https://lonami.dev/blog/ribw/cassandra-an-introduction/miUO60A-RtyEAOOVFJqlkPRC18H4RKUhot6RWzhO9FmtzgTPOYHFtwxqgZEf.png> <em>Sentence output</em><p>Finally, the installation guide provided by the website of the database is attached in this <a href=https://cassandra.apache.org/doc/latest/getting_started/installing.html>installation guide</a>.<h2 id=references>References</h2><ul><li><a href=https://es.wikipedia.org/wiki/Apache_Cassandra>Wikipedia</a><li><a href=https://cassandra.apache.org/>Apache Cassandra</a><li><a href=https://www.datastax.com/blog/2019/05/how-apache-cassandratm-balances-consistency-availability-and-performance>Datastax</a><li><a href=https://blog.yugabyte.com/apache-cassandra-architecture-how-it-works-lightweight-transactions/>yugabyte</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,378 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Developing a Python application for MongoDB | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Developing a Python application for MongoDB</h1><div class=time><p>2020-03-25T00:00:04+00:00<p>last updated 2020-04-16T08:01:23+00:00</div><p>This is the third and last post in the MongoDB series, where we will develop a Python application to process and store OpenData inside Mongo.<p>Other posts in this series:<ul><li><a href=/blog/ribw/mongodb-an-introduction/>MongoDB: an Introduction</a><li><a href=/blog/ribw/mongodb-basic-operations-and-architecture/>MongoDB: Basic Operations and Architecture</a><li><a href=/blog/ribw/developing-a-python-application-for-mongodb/>Developing a Python application for MongoDB</a> (this post)</ul><p>This post is co-authored wih a Classmate.<hr><h2 id=what-are-we-making>What are we making?</h2><p>We are going to develop a web application that renders a map, in this case, the town of Cáceres, with which users can interact. When the user clicks somewhere on the map, the selected location will be sent to the server to process. This server will perform geospatial queries to Mongo and once the results are ready, the information is presented back at the webpage.<p>The data used for the application comes from <a href=https://opendata.caceres.es/>Cáceres’ OpenData</a>, and our goal is that users will be able to find information about certain areas in a quick and intuitive way, such as precise coordinates, noise level, and such.<h2 id=what-are-we-using>What are we using?</h2><p>The web application will be using <a href=https://python.org/>Python</a> for the backend, <a href=https://svelte.dev/>Svelte</a> for the frontend, and <a href=https://www.mongodb.com/>Mongo</a> as our storage database and processing center.<ul><li><strong>Why Python?</strong> It’s a comfortable language to write and to read, and has a great ecosystem with <a href=https://pypi.org/>plenty of libraries</a>.<li><strong>Why Svelte?</strong> Svelte is the New Thing<strong>™</strong> in the world of component frameworks for JavaScript. It is similar to React or Vue, but compiled and with a lot less boilerplate. Check out their <a href=https://svelte.dev/blog/svelte-3-rethinking-reactivity>Svelte post</a> to learn more.<li><strong>Why Mongo?</strong> We believe NoSQL is the right approach for doing the kind of processing and storage that we expect, and it’s <a href=https://docs.mongodb.com/>very easy to use</a>. In addition, we will be making Geospatial Queries which <a href=https://docs.mongodb.com/manual/geospatial-queries/>Mongo supports</a>.</ul><p>Why didn’t we choose to make a smaller project, you may ask? You will be shocked to hear that we do not have an answer for that!<p>Note that we will not be embedding <strong>all</strong> the code of the project in this post, or it would be too long! We will include only the relevant snippets needed to understand the core ideas of the project, and not the unnecessary parts of it (for example, parsing configuration files to easily change the port where the server runs is not included).<h2 id=python-dependencies>Python dependencies</h2><p>Because we will program it in Python, you need Python installed. You can install it using a package manager of your choice or heading over to the <a href=https://www.python.org/downloads/>Python downloads section</a>, but if you’re on Linux, chances are you have it installed already.<p>Once Python 3.7 or above is installed, install <a href=https://motor.readthedocs.io/en/stable/><code>motor</code> (Asynchronous Python driver for MongoDB)</a> and the <a href=https://docs.aiohttp.org/en/stable/web.html><code>aiohttp</code> server</a> through <code>pip</code>:<pre><code>pip install aiohttp motor -</code></pre><p>Make sure that Mongo is running in the background (this has been described in previous posts), and we should be able to get to work.<h2 id=web-dependencies>Web dependencies</h2><p>To work with Svelte and its dependencies, we will need <code>[npm](https://www.npmjs.com/)</code> which comes with <a href=https://nodejs.org/en/>NodeJS</a>, so go and <a href=https://nodejs.org/en/download/>install Node from their site</a>. The download will be different depending on your operating system.<p>Following <a href=https://svelte.dev/blog/the-easiest-way-to-get-started>the easiest way to get started with Svelte</a>, we will put our project in a <code>client/</code> folder (because this is what the clients see, the frontend). Feel free to tinker a bit with the configuration files to change the name and such, although this isn’t relevant for the rest of the post.<h2 id=finding-the-data>Finding the data</h2><p>We are going to work with the JSON files provided by <a href=http://opendata.caceres.es/>OpenData Cáceres</a>. In particular, we want information about the noise, census, vias and trees. To save you the time from <a href=http://opendata.caceres.es/dataset>searching each of these</a>, we will automate the download with code.<p>If you want to save the data offline or just know what data we’ll be using for other purposes though, you can right click on the following links and select «Save Link As…» with the name of the link:<ul><li><code>[noise.json](http://opendata.caceres.es/GetData/GetData?dataset=om:MedicionRuido&format=json)</code><li><code>[census.json](http://opendata.caceres.es/GetData/GetData?dataset=om:InformacionPadron&year=2017&format=json)</code><li><code>[vias.json](http://opendata.caceres.es/GetData/GetData?dataset=om:InformacionPadron&year=2017&format=json)</code><li><code>[trees.json](http://opendata.caceres.es/GetData/GetData?dataset=om:Arbol&format=json)</code></ul><h2 id=backend>Backend</h2><p>It’s time to get started with some code! We will put it in a <code>server/</code> folder because it will contain the Python server, that is, the backend of our application.<p>We are using <code>aiohttp</code> because we would like our server to be <code>async</code>. We don’t expect a lot of users at the same time, but it’s good to know our server would be well-designed for that use-case. As a bonus, it makes IO points clear in the code, which can help reason about it. The implicit synchronization between <code>await</code> is also a nice bonus.<h3 id=saving-the-data-in-mongo>Saving the data in Mongo</h3><p>Before running the server, we must ensure that the data we need is already stored and indexed in Mongo. Our <code>server/data.py</code> will take care of downloading the files, cleaning them up a little (Cáceres’ OpenData can be a bit awkward sometimes), inserting them into Mongo and indexing them.<p>Downloading the JSON data can be done with <code>[ClientSession.get](https://aiohttp.readthedocs.io/en/stable/client_reference.html#aiohttp.ClientSession.get)</code>. We also take this opportunity to clean up the messy encoding from the JSON, which does not seem to be UTF-8 in some cases.<pre><code>async def load_json(session, url): - fixes = [(old, new.encode('utf-8')) for old, new in [ - (b'\xc3\x83\\u2018', 'Ñ'), - (b'\xc3\x83\\u0081', 'Á'), - (b'\xc3\x83\\u2030', 'É'), - (b'\xc3\x83\\u008D', 'Í'), - (b'\xc3\x83\\u201C', 'Ó'), - (b'\xc3\x83\xc5\xa1', 'Ú'), - (b'\xc3\x83\xc2\xa1', 'á'), - ]] - - async with session.get(url) as resp: - data = await resp.read() - - # Yes, this feels inefficient, but it's not really worth improving. - for old, new in fixes: - data = data.replace(old, new) - - data = data.decode('utf-8') - return json.loads(data) -</code></pre><p>Later on, it can be reused for the various different URLs:<pre><code>import aiohttp - -NOISE_URL = 'http://opendata.caceres.es/GetData/GetData?dataset=om:MedicionRuido&format=json' -# (...other needed URLs here) - -async def insert_to_db(db): - async with aiohttp.ClientSession() as session: - data = await load_json(session, NOISE_URL) - # now we have the JSON data cleaned up, ready to be parsed -</code></pre><h3 id=data-model>Data model</h3><p>With the JSON data in our hands, it’s time to parse it. Always remember to <a href=https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/>parse, don’t validate</a>. With <a href=https://docs.python.org/3/library/dataclasses.html>Python 3.7 <code>dataclasses</code></a> it’s trivial to define classes that will store only the fields we care about, typed, and with proper names:<pre><code>from dataclasses import dataclass - -Longitude = float -Latitude = float - -@dataclass -class GSON: - type: str - coordinates: (Longitude, Latitude) - -@dataclass -class Noise: - id: int - geo: GSON - level: float -</code></pre><p>This makes it really easy to see that, if we have a <code>Noise</code>, we can access its <code>geo</code> data which is a <code>GSON</code> with a <code>type</code> and <code>coordinates</code>, having <code>Longitude</code> and <code>Latitude</code> respectively. <code>dataclasses</code> and <code>[typing](https://docs.python.org/3/library/typing.html)</code> make dealing with this very easy and clear.<p>Every dataclass will be on its own collection inside Mongo, and these are:<ul><li><p>Noise<li><p>Integer <code>id</code><li><p>GeoJSON <code>geo</code><li><p>String <code>type</code><li><p>Longitude-latitude pair <code>coordinates</code><li><p>Floating-point number <code>level</code><li><p>Tree<li><p>String <code>name</code><li><p>String <code>gender</code><li><p>Integer <code>units</code><li><p>Floating-point number <code>height</code><li><p>Floating-point number <code>cup_diameter</code><li><p>Floating-point number <code>trunk_diameter</code><li><p>Optional string <code>variety</code><li><p>Optional string <code>distribution</code><li><p>GeoJSON <code>geo</code><li><p>Optional string <code>irrigation</code><li><p>Census<li><p>Integer <code>year</code><li><p>Via <code>via</code><li><p>String <code>name</code><li><p>String <code>kind</code><li><p>Integer <code>code</code><li><p>Optional string <code>history</code><li><p>Optional string <code>old_name</code><li><p>Optional floating-point number <code>length</code><li><p>Optional GeoJSON <code>start</code><li><p>GeoJSON <code>middle</code><li><p>Optional GeoJSON <code>end</code><li><p>Optional list with geometry pairs <code>geometry</code><li><p>Integer <code>count</code><li><p>Mapping year-to-count <code>count_per_year</code><li><p>Mapping gender-to-count <code>count_per_gender</code><li><p>Mapping nationality-to-count <code>count_per_nationality</code><li><p>Integer <code>time_year</code></ul><p>Now, let’s define a method to actually parse the JSON and yield instances from these new data classes:<pre><code>@classmethod -def iter_from_json(cls, data): - for row in data['results']['bindings']: - noise_id = int(row['uri']['value'].split('/')[-1]) - long = float(row['geo_long']['value']) - lat = float(row['geo_lat']['value']) - level = float(row['om_nivelRuido']['value']) - - yield cls( - id=noise_id, - geo=GSON(type='Point', coordinates=[long, lat]), - level=level - ) -</code></pre><p>Here we iterate over the input JSON <code>data</code> bindings and <code>yield cls</code> instances with more consistent naming than the original one. We also extract the data from the many unnecessary nested levels of the JSON and have something a lot flatter to work with.<p>For those of you who don’t know what <code>yield</code> does (after all, not everyone is used to seeing generators), here’s two functions that work nearly the same:<pre><code>def squares_return(n): - result = [] - for i in range(n): - result.append(n ** 2) - return result - -def squares_yield(n): - for i in range(n): - yield n ** 2 -</code></pre><p>The difference is that the one with <code>yield</code> is «lazy» and doesn’t need to do all the work up-front. It will generate (yield) more values as they are needed when you use a <code>for</code> loop. Generally, it’s a better idea to create generator functions than do all the work early which may be unnecessary. See <a href=https://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do>What does the «yield» keyword do?</a> if you still have questions.<p>With everything parsed, it’s time to insert the data into Mongo. If the data was not present yet (0 documents), then we will download the file, parse it, insert it as documents into the given Mongo <code>db</code>, and index it:<pre><code>from dataclasses import asdict - -async def insert_to_db(db): - async with aiohttp.ClientSession() as session: - if await db.noise.estimated_document_count() == 0: - data = await load_json(session, NOISE_URL) - - await db.noise.insert_many(asdict(noise) for noise in Noise.iter_from_json(data)) - await db.noise.create_index([('geo', '2dsphere')]) -</code></pre><p>We repeat this process for all the other data, and just like that, Mongo is ready to be used in our server.<h3 id=indices>Indices</h3><p>In order to execute our geospatial queries we have to create an index on the attribute that represents the location, because the operators that we will use requires it. This attribute can be a <a href=https://docs.mongodb.com/manual/reference/geojson/>GeoJSON object</a> or a legacy coordinate pair.<p>We have decided to use a GeoJSON object because we want to avoid legacy features that may be deprecated in the future.<p>The attribute is called <code>geo</code> for the <code>Tree</code> and <code>Noise</code> objects and <code>start</code>, <code>middle</code> or <code>end</code> for the <code>Via</code> class. In the <code>Via</code> we are going to index the attribute <code>middle</code> because it is the most representative field for us. Because the <code>Via</code> is inside the <code>Census</code> and it doesn’t have its own collection, we create the index on the <code>Census</code> collection.<p>The used index type is <code>2dsphere</code> because it supports queries that work on geometries on an earth-like sphere. Another option is the <code>2d</code> index but it’s not a good fit for our because it is for queries that calculate geometries on a two-dimensional plane.<h3 id=running-the-server>Running the server</h3><p>If we ignore the configuration part of the server creation, our <code>server.py</code> file is pretty simple. Its job is to create a <a href=https://aiohttp.readthedocs.io/en/stable/web.html>server application</a>, setup Mongo and return it to the caller so that they can run it:<pre><code>import asyncio -import subprocess -import motor.motor_asyncio - -from aiohttp import web - -from . import rest, data - -def create_app(): - ret = subprocess.run('npm run build', cwd='../client', shell=True).returncode - if ret != 0: - exit(ret) - - db = motor.motor_asyncio.AsyncIOMotorClient().opendata - loop = asyncio.get_event_loop() - loop.run_until_complete(data.insert_to_db(db)) - - app = web.Application() - app['db'] = db - - app.router.add_routes([ - web.get('/', lambda r: web.HTTPSeeOther('/index.html')), - *rest.ROUTES, - web.static('/', os.path.join(config['www']['root'], 'public')), - ]) - - return app -</code></pre><p>There’s a bit going on here, but it’s nothing too complex:<ul><li>We automatically run <code>npm run build</code> on the frontend because it’s very comfortable to have the frontend built automatically before the server runs.<li>We create a Motor client and access the <code>opendata</code> database. Into it, we load the data, effectively saving it in Mongo for the server to use.<li>We create the server application and save a reference to the Mongo database in it, so that it can be used later on any endpoint without needing to recreate it.<li>We define the routes of our app: root, REST and static (where the frontend files live). We’ll get to the <code>rest</code> part soon. Running the server is now simple:</ul><pre><code>def main(): - from aiohttp import web - from . import server - - app = server.create_app() - web.run_app(app) - -if __name__ == '__main__': - main() -</code></pre><h3 id=rest-endpoints>REST endpoints</h3><p>The frontend will communicate with the backend via <a href=https://en.wikipedia.org/wiki/Representational_state_transfer>REST</a> calls, so that it can ask for things like «give me the information associated with this area», and the web server can query the Mongo server to reply with a HTTP response. This little diagram should help:<p><img src=https://lonami.dev/blog/ribw/developing-a-python-application-for-mongodb/bitmap.png><p>What we need to do, then, is define those REST endpoints we mentioned earlier when creating the server. We will process the HTTP request, ask Mongo for the data, and return the HTTP response:<pre><code>import asyncio -import pymongo - -from aiohttp import web - -async def get_area_info(request): - try: - long = float(request.query['long']) - lat = float(request.query['lat']) - distance = float(request.query['distance']) - except KeyError as e: - raise web.HTTPBadRequest(reason=f'a required parameter was missing: {e.args[0]}') - except ValueError: - raise web.HTTPBadRequest(reason='one of the parameters was not a valid float') - - geo_avg_noise_pipeline = [{ - '$geoNear': { - 'near' : {'type': 'Point', 'coordinates': [long, lat]}, - 'maxDistance': distance, - 'minDistance': 0, - 'spherical' : 'true', - 'distanceField' : 'distance' - } - }] - - db = request.app['db'] - - try: - noise_count, sum_noise, avg_noise = 0, 0, 0 - async for item in db.noise.aggregate(geo_avg_noise_pipeline): - noise_count += 1 - sum_noise += item['level'] - - if noise_count != 0: - avg_noise = sum_noise / noise_count - else: - avg_noise = None - - except pymongo.errors.ConnectionFailure: - raise web.HTTPServiceUnavailable(reason='no connection to database') - - return web.json_response({ - 'tree_count': tree_count, - 'trees_per_type': [[k, v] for k, v in trees_per_type.items()], - 'census_count': census_count, - 'avg_noise': avg_noise, - }) - -ROUTES = [ - web.get('/rest/get-area-info', get_area_info) -] -</code></pre><p>In this code, we’re only showing how to return the average noise because that’s the simplest we can do. The real code also fetches tree count, tree count per type, and census count.<p>Again, there’s quite a bit to go through, so let’s go step by step:<ul><li>We parse the frontend’s <code>request.query</code> into <code>float</code> that we can use. In particular, the frontend is asking us for information at a certain latitude, longitude, and distance. If the query is malformed, we return a proper error.<li>We create our query for Mongo outside, just so it’s clearer to read.<li>We access the database reference we stored earlier when creating the server with <code>request.app['db']</code>. Handy!<li>We try to query Mongo. It may fail if the Mongo server is not running, so we should handle that and tell the client what’s happening. If it succeeds though, we will gather information about the average noise.<li>We return a <code>json_response</code> with Mongo results for the frontend to present to the user. You may have noticed we defined a <code>ROUTES</code> list at the bottom. This will make it easier to expand in the future, and the server creation won’t need to change anything in its code, because it’s already unpacking all the routes we define here.</ul><h3 id=geospatial-queries>Geospatial queries</h3><p>In order to retrieve the information from Mongo database we have defined two geospatial queries:<pre><code>geo_query = { - '$nearSphere' : { - '$geometry': { - 'type': 'Point', - 'coordinates': [long, lat] - }, - '$maxDistance': distance, - '$minDistance': 0 - } -} -</code></pre><p>This query uses <a href=https://docs.mongodb.com/manual/reference/operator/query/nearSphere/#op._S_nearSphere>the operator <code>$nearSphere</code></a> which return geospatial objects in proximity to a point on a sphere.<p>The sphere point is represented by the <code>$geometry</code> operator where it is specified the type of geometry and the coordinates (given by the HTTP request).<p>The maximum and minimum distance are represented by <code>$maxDistance</code> and <code>$minDistance</code> respectively. We specify that the maximum distance is the radio selected by the user.<pre><code>geo_avg_noise_pipeline = [{ - '$geoNear': { - 'near' : {'type': 'Point', 'coordinates': [long, lat]}, - 'maxDistance': distance, - 'minDistance': 0, - 'spherical' : 'true', - 'distanceField' : 'distance' - } -}] -</code></pre><p>This query uses the <a href=https://docs.mongodb.com/manual/core/aggregation-pipeline/>aggregation pipeline</a> stage <a href=https://docs.mongodb.com/manual/reference/operator/aggregation/geoNear/#pipe._S_geoNear><code>$geoNear</code></a> which returns an ordered stream of documents based on the proximity to a geospatial point. The output documents include an additional distance field.<p>The <code>near</code> field is mandatory and is the point for which to find the closest documents. In this field it is specified the type of geometry and the coordinates (given by the HTTP request).<p>The <code>distanceField</code> field is also mandatory and is the output field that will contain the calculated distance. In this case we’ve just called it <code>distance</code>.<p>Some other fields are <code>maxDistance</code> that indicates the maximum allowed distance from the center of the point, <code>minDistance</code> for the minimum distance, and <code>spherical</code> which tells MongoDB how to calculate the distance between two points.<p>We specify the maximum distance as the radio selected by the user in the frontend.<h2 id=frontend>Frontend</h2><p>As said earlier, our frontend will use Svelte. We already downloaded the template, so we can start developing. For some, this is the most fun part, because they can finally see and interact with some of the results. But for this interaction to work, we needed a functional backend which we now have!<h3 id=rest-queries>REST queries</h3><p>The frontend has to query the server to get any meaningful data to show on the page. The <a href=https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API>Fetch API</a> does not throw an exception if the server doesn’t respond with HTTP OK, but we would like one if things go wrong, so that we can handle them gracefully. The first we’ll do is define our own exception <a href=https://stackoverflow.com/a/27724419>which is not pretty</a>:<pre><code>function NetworkError(message, status) { - var instance = new Error(message); - instance.name = 'NetworkError'; - instance.status = status; - Object.setPrototypeOf(instance, Object.getPrototypeOf(this)); - if (Error.captureStackTrace) { - Error.captureStackTrace(instance, NetworkError); - } - return instance; -} - -NetworkError.prototype = Object.create(Error.prototype, { - constructor: { - value: Error, - enumerable: false, - writable: true, - configurable: true - } -}); -Object.setPrototypeOf(NetworkError, Error); -</code></pre><p>But hey, now we have a proper and reusable <code>NetworkError</code>! Next, let’s make a proper and reusabe <code>query</code> function that deals with <code>fetch</code> for us:<pre><code>async function query(endpoint) { - const res = await fetch(endpoint, { - // if we ever use cookies, this is important - credentials: 'include' - }); - if (res.ok) { - return await res.json(); - } else { - throw new NetworkError(await res.text(), res.status); - } -} -</code></pre><p>At last, we can query our web server. The export here tells Svelte that this function should be visible to outer modules (public) as opposed to being private:<pre><code>export function get_area_info(long, lat, distance) { - return query(`/rest/get-area-info?long=${long}&lat=${lat}&distance=${distance}`); -} -</code></pre><p>The attentive reader will have noticed that <code>query</code> is <code>async</code>, but <code>get_area_info</code> is not. This is intentional, because we don’t need to <code>await</code> for anything inside of it. We can just return the <code>[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)</code> that <code>query</code> created and let the caller <code>await</code> it as they see fit. The <code>await</code> here would have been redundant.<p>For those of you who don’t know what a JavaScript promise is, think of it as an object that represents «an eventual result». The result may not be there yet, but we promised it will be present in the future, and we can <code>await</code> for it. You can also find the same concept in other languages like Python under a different name, such as <a href=https://docs.python.org/3/library/asyncio-future.html#asyncio.Future><code>Future</code></a>.<h3 id=map-component>Map component</h3><p>In Svelte, we can define self-contained components that are issolated from the rest. This makes it really easy to create a modular application. Think of a Svelte component as your own HTML tag, which you can customize however you want, building upon the already-existing components HTML has to offer.<p>The main thing that our map needs to do is render the map as an image and overlay the selection area as the user hovers the map with their mouse. We could render the image in the canvas itself, but instead we’ll use the HTML <code><img></code> tag for that and put a transparent <code><canvas></code> on top with some CSS. This should make it cheaper and easier to render things on the canvas.<p>The <code>Map</code> component will thus render as the user moves the mouse over it, and produce an event when they click so that whatever component is using a <code>Map</code> knows that it was clicked. Here’s the final CSS and HTML:<pre><code><style> -div { - position: relative; -} -canvas { - position: absolute; - left: 0; - top: 0; - cursor: crosshair; -} -</style> - -<div> - <img bind:this={img} on:load={handleLoad} {height} src="caceres-municipality.svg" alt="Cáceres (municipality)"/> - <canvas - bind:this={canvas} - on:mousemove={handleMove} - on:wheel={handleWheel} - on:mouseup={handleClick}/> -</div> -</code></pre><p>We hardcode a map source here, but ideally this would be provided by the server. The project is already complex enough, so we tried to avoid more complexity than necessary.<p>We bind the tags to some variables declared in the JavaScript code of the component, along with some functions and parameters to let the users of <code>Map</code> customize it just a little.<p>Here’s the gist of the JavaScript code:<pre><code><script> - import { createEventDispatcher, onMount } from 'svelte'; - - export let height = 200; - - const dispatch = createEventDispatcher(); - - let img; - let canvas; - - const LONG_WEST = -6.426881; - const LONG_EAST = -6.354143; - const LAT_NORTH = 39.500064; - const LAT_SOUTH = 39.443201; - - let x = 0; - let y = 0; - let clickInfo = null; // [x, y, radius] - let radiusDelta = 0.005 * height; - let maxRadius = 0.2 * height; - let minRadius = 0.01 * height; - let radius = 0.05 * height; - - function handleLoad() { - canvas.width = img.width; - canvas.height = img.height; - } - - function handleMove(event) { - const { left, top } = this.getBoundingClientRect(); - x = Math.round(event.clientX - left); - y = Math.round(event.clientY - top); - } - - function handleWheel(event) { - if (event.deltaY < 0) { - if (radius < maxRadius) { - radius += radiusDelta; - } - } else { - if (radius > minRadius) { - radius -= radiusDelta; - } - } - event.preventDefault(); - } - - function handleClick(event) { - dispatch('click', { - // the real code here maps the x/y/radius values to the right range, here omitted - x: ..., - y: ..., - radius: ..., - }); - } - - onMount(() => { - const ctx = canvas.getContext('2d'); - let frame; - - (function loop() { - frame = requestAnimationFrame(loop); - - // the real code renders mouse area/selection, here omitted for brevity - ... - }()); - - return () => { - cancelAnimationFrame(frame); - }; - }); -</script> -</code></pre><p>Let’s go through bit-by-bit:<ul><li>We define a few variables and constants for later use in the final code.<li>We define the handlers to react to mouse movement and clicks. On click, we dispatch an event to outer components.<li>We setup the render loop with animation frames, and cancel the current frame appropriatedly if the component disappears.</ul><h3 id=app-component>App component</h3><p>Time to put everything together! We wil include our function to make REST queries along with our <code>Map</code> component to render things on screen.<pre><code><script> - import Map from './Map.svelte'; - import { get_area_info } from './rest.js' - let selection = null; - let area_info_promise = null; - function handleMapSelection(event) { - selection = event.detail; - area_info_promise = get_area_info(selection.x, selection.y, selection.radius); - } - function format_avg_noise(avg_noise) { - if (avg_noise === null) { - return '(no data)'; - } else { - return `${avg_noise.toFixed(2)} dB`; - } - } -</script> - -<div class="container-fluid"> - <div class="row"> - <div class="col-3" style="max-width: 300em;"> - <div class="text-center"> - <h1>Caceres Data Consultory</h1> - </div> - <Map height={400} on:click={handleMapSelection}/> - <div class="text-center mt-4"> - {#if selection === null} - <p class="m-1 p-3 border border-bottom-0 bg-info text-white">Click on the map to select the area you wish to see details for.</p> - {:else} - <h2 class="bg-dark text-white">Selected area</h2> - <p><b>Coordinates:</b> ({selection.x}, {selection.y})</p> - <p><b>Radius:</b> {selection.radius} meters</p> - {/if} - </div> - </div> - <div class="col-sm-4"> - <div class="row"> - {#if area_info_promise !== null} - {#await area_info_promise} - <p>Fetching area information…</p> - {:then area_info} - <div class="col"> - <div class="text-center"> - <h2 class="m-1 bg-dark text-white">Area information</h2> - <ul class="list-unstyled"> - <li>There are <b>{area_info.tree_count} trees </b> within the area</li> - <li>The <b>average noise</b> is <b>{format_avg_noise(area_info.avg_noise)}</b></li> - <li>There are <b>{area_info.census_count} persons </b> within the area</li> - </ul> - </div> - {#if area_info.trees_per_type.length > 0} - <div class="text-center"> - <h2 class="m-1 bg-dark text-white">Tree count per type</h2> - </div> - <ul class="list-group"> - {#each area_info.trees_per_type as [type, count]} - <li class="list-group-item">{type} <span class="badge badge-dark float-right">{count}</span></li> - {/each} - </ul> - {/if} - </div> - {:catch error} - <p>Failed to fetch area information: {error.message}</p> - {/await} - {/if} - </div> - </div> - </div> -</div> -</code></pre><ul><li>We import the <code>Map</code> component and REST function so we can use them.<li>We define a listener for the events that the <code>Map</code> produces. Such event will trigger a REST call to the server and save the result in a promise used later.<li>We’re using Bootstrap for the layout because it’s a lot easier. In the body we add our <code>Map</code> and another column to show the selection information.<li>We make use of Svelte’s <code>{#await}</code> to nicely notify the user when the call is being made, when it was successful, and when it failed. If it’s successful, we display the info.</ul><h2 id=results>Results</h2><p>Lo and behold, watch our application run!<p><video controls=controls src=sr-2020-04-14_09-28-25.mp4></video><p>In this video you can see our application running, but let’s describe what is happening in more detail.<p>When the application starts running (by opening it in your web browser of choice), you can see a map with the town of Cáceres. Then you, the user, can click to retrieve the information within the selected area.<p>It is important to note that one can make the selection area larger or smaller by trying to scroll up or down, respectively.<p>Once an area is selected, it is colored green in order to let the user know which area they have selected. Under the map, the selected coordinates and the radius (in meters) is also shown for the curious. At the right side the information concerning the selected area is shown, such as the number of trees, the average noise and the number of persons. If there are trees in the area, the application also displays the trees per type, sorted by the number of trees.<h2 id=download>Download</h2><p>We hope you enjoyed reading this post as much as we enjoyed writing it! Feel free to download the final project and play around with it. Maybe you can adapt it for even more interesting purposes!<p><em>download removed</em><p>To run the above code:<ol><li>Unzip the downloaded file.<li>Make a copy of <code>example-server-config.ini</code> and rename it to <code>server-config.ini</code>, then edit the file to suit your needs.<li>Run the server with <code>python -m server</code>.<li>Open <a href=http://localhost:9000>localhost:9000</a> in your web browser (or whatever port you chose) and enjoy!</ol></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Privado: Final NoSQL evaluation | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Privado: Final NoSQL evaluation</h1><div class=time><p>2020-05-13T00:00:00+00:00<p>last updated 2020-05-14T07:30:08+00:00</div><p>This evaluation is a bit different to my <a href=/blog/ribw/16/nosql-evaluation/>previous one</a> because this time I have been tasked to evaluate the student <code>a(i - 2)</code>, and because I am <code>a = 9</code> that happens to be <code>a(7) =</code> Classmate.<p>Unfortunately for Classmate, the only entry related to NoSQL I have found in their blog is Prima y segunda Actividad: Base de datos NoSQL which does not develop an application as requested for the third entry (as of 14th of May).<p>This means that, instead, I will evaluate <code>a(i - 3)</code> which happens to be <code>a(6) =</code> Classmate and they do have an entry.<h2 id=classmate-s-evaluation>Classmate’s Evaluation</h2><p><strong>Grading: B.</strong><p>The post I have evaluated is BB.DD. NoSQL RethinkDB 3ª Fase. Aplicación.<p>It starts with an introduction, properly explaining what database they have chosen and why, but not what application they will be making.<p>This is detailed just below in the next section, although it’s a bit vague.<p>The next section talks about the Python dependencies that are required, but they never said they would be making a Python application or that we need to install Python!<p>The next section talks about the file structure of the project, and they detail what everything part does, although I have missed some code snippets.<p>The final result is pretty cool and contains many interesting graphs, they provide a download to the source code and list all the relevant references used.<p>Except for a weird «necesario falta» in the text, it’s otherwise well-written, although given the issues above I cannot grade it with the highest score.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,2 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Google’s BigTable | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Google’s BigTable</h1><div class=time><p>2020-04-01T00:00:00+00:00<p>last updated 2020-04-03T09:30:05+00:00</div><p>Let’s talk about BigTable, and why it is what it is. But before we get into that, let’s see some important aspects anybody should consider when dealing with a lot of data (something BigTable does!).<h2 id=the-basics>The basics</h2><p>Converting a text document into a different format is often a great way to greatly speed up scanning of it in the future. It allows for efficient searches.<p>In addition, you generally want to store everything in a single, giant file. This will save a lot of time opening and closing files, because everything is in the same file! One proposal to make this happen is <a href=https://trec.nist.gov/file_help.html>Web TREC</a> (see also the <a href=https://en.wikipedia.org/wiki/Text_Retrieval_Conference>Wikipedia page on TREC</a>), which is basically HTML but every document is properly delimited from one another.<p>Because we will have a lot of data, it’s often a good idea to compress it. Most text consists of the same words, over and over again. Classic compression techniques such as <code>DEFLATE</code> or <code>LZW</code> do an excellent job here.<h2 id=so-what-s-bigtable>So what’s BigTable?</h2><p>Okay, enough of an introduction to the basics on storing data. BigTable is what Google uses to store documents, and it’s a customized approach to save, search and update web pages.<p>BigTable is is a distributed storage system for managing structured data, able to scale to petabytes of data across thousands of commodity servers, with wide applicability, scalability, high performance, and high availability.<p>In a way, it’s kind of like databases and shares many implementation strategies with them, like parallel databases, or main-memory databases, but of course, with a different schema.<p>It consists of a big table known as the «Root tablet», with pointers to many other «tablets» (or metadata in between). These are stored in a replicated filesystem accessible by all BigTable servers. Any change to a tablet gets logged (said log also gets stored in a replicated filesystem).<p>If any of the tablets servers gets locked, a different one can take its place, read the log and deal with the problem.<p>There’s no query language, transactions occur at row-level only. Every read or write in a row is atomic. Each row stores a single web page, and by combining the row and column keys along with a timestamp, it is possible to retrieve a single cell in the row. More formally, it’s a map that looks like this:<pre><code>fetch(row: string, column: string, time: int64) -> string -</code></pre><p>A row may have as many columns as it needs, and these column groups are the same for everyone (but the columns themselves may vary), which is importan to reduce disk read time.<p>Rows are split in different tablets based on the row keys, which simplifies determining an appropriated server for them. The keys can be up to 64KB big, although most commonly they range 10-100 bytes.<h2 id=conclusions>Conclusions</h2><p>BigTable is Google’s way to deal with large amounts of data on many of their services, and the ideas behind it are not too complex to understand.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> How does Google’s Search Engine work? | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>How does Google’s Search Engine work?</h1><div class=time><p>2020-03-18T01:00:00+00:00<p>last updated 2020-03-28T10:17:09+00:00</div><p>The original implementation was written in C/++ for Linux/Solaris.<p>There are three major components in the system’s anatomy, which can be thought as steps to be performed for Google to be what it is today.<p><img src=https://lonami.dev/blog/ribw/how-does-googles-search-engine-work/image-1024x649.png><p>But before we talk about the different components, let’s take a look at how they store all of this information.<h2 id=data-structures>Data structures</h2><p>A «BigFile» is a virtual file addressable by 64 bits.<p>There exists a repository with the full HTML of every page compressed, along with a document identifier, length and URL.<table><tbody><tr><td>sync<td>length<td>compressed packet</table><p>The Document Index has the document identifier, a pointer into the repository, a checksum and various other statistics.<table><tbody><tr><td>doc id<td>ecode<td>url len<td>page len<td>url<td>page</table><p>A Lexicon stores the repository of words, implemented with a hashtable over pointers linking to the barrels (sorted linked lists) of the Inverted Index.<table><tbody><tr><td>word id<td>n docs<tr><td>word id<td>n docs</table><p>The Hit Lists store occurences of a word in a document.<table><tbody><tr><td><strong> plain </strong><td>cap: 1<td>imp: 3<td>pos: 12<tr><td><strong> fancy </strong><td>cap: 1<td>imp: 7<td>type: 4<td>pos: 8<tr><td><strong> anchor </strong><td>cap: 1<td>imp: 7<td>type: 4<td>hash: 4<td>pos: 8</table><p>The Forward Index is a barrel with a range of word identifiers (document identifier and list of word identifiers).<table><tbody><tr><td rowspan=3>doc id<td>word id: 24<td>n hits: 8<td>hit hit hit hit hit hit hit hit<tr><td>word id: 24<td>n hits: 8<td>hit hit hit hit hit hit hit hit<tr><td>null word id</table><p>The Inverted Index can be sorted by either document identifier or by ranking of word occurence.<table><tbody><tr><td>doc id: 23<td>n hits: 5<td>hit hit hit hit hit<tr><td>doc id: 23<td>n hits: 3<td>hit hit hit<tr><td>doc id: 23<td>n hits: 4<td>hit hit hit hit<tr><td>doc id: 23<td>n hits: 2<td>hit hit</table><p>Back in 1998, Google compressed its repository to 53GB and had 24 million pages. The indices, lexicon, and other temporary storage required about 55GB.<h2 id=crawling>Crawling</h2><p>The crawling must be reliable, fast and robust, and also respect the decision of some authors not wanting their pages crawled. Originally, it took a week or more, so simultaneous execution became a must.<p>Back in 1998, Google had between 3 and 4 crawlers running at 100 web pages per second maximum. These were implemented in Python.<p>The crawled pages need parsing to deal with typos or formatting issues.<h2 id=indexing>Indexing</h2><p>Indexing is about putting the pages into barrels, converting words into word identifiers, and occurences into hit lists.<p>Once indexing is done, sorting of the barrels happens to have them ordered by word identifier, producing the inverted index. This process also had to be done in parallel over many machines, or would otherwise have been too slow.<h2 id=searching>Searching</h2><p>We need to find quality results efficiently. Plenty of weights are considered nowadays, but at its heart, PageRank is used. It is the algorithm they use to map the web, which is formally defined as follows:<p><img src=https://lonami.dev/blog/ribw/how-does-googles-search-engine-work/8e1e61b119e107fcb4bdd7e78f649985.png> <em>PR(A) = (1-d) + d(PR(T1)/C(T1) + … + PR(Tn)/C(Tn))</em><p>Where:<ul><li><code>A</code> is a given page<li><code>T<sub>n</sub></code> are pages that point to A<li><code>d</code> is the damping factor in the range <code>[0, 1]</code> (often 0.85)<li><code>C(A)</code> is the number of links going out of page <code>A</code><li><code>PR(A)</code> is the page rank of page <code>A</code> This formula indicates the probability that a random surfer visits a certain page, and <code>1 - d</code> is used to indicate when it will «get bored» and stop surfing. More intuitively, the page rank of a page will grow as more pages link to it, or the few that link to it have high page rank.</ul><p>The anchor text in the links also help provide a better description and helps indexing for even better results.<p>While searching, the concern is disk I/O which takes up most of the time. Caching is very important to improve performance up to 30 times.<p>Now, in order to turn user queries into something we can search, we must parse the query and convert the words into word identifiers.<h2 id=conclusion>Conclusion</h2><p>Google is designed to be a efficient, scalable, high-quality search engine. There are still bottlenecks in CPU, memory, disk speed and network I/O, but major data structures are used to make efficient use of the resources.<h2 id=references>References</h2><ul><li><a href=https://snap.stanford.edu/class/cs224w-readings/Brin98Anatomy.pdf>The anatomy of a large-scale hypertextual Web search engine</a><li><a href=https://www.site.uottawa.ca/%7Ediana/csi4107/Google_SearchEngine.pdf>The Anatomy of a Large-Scale Hypertextual Web Search Engine (slides)</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,38 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Information Retrieval and Web Search</h1><p id=welcome onclick=pls_stop()>Welcome to my blog!<p>Here I occasionally post new entries, mostly tech related. Perhaps it's tips for a new game I'm playing, perhaps it has something to do with FFI, or perhaps I'm fighting the borrow checker (just kidding, I'm over that. Mostly).<hr><ul><li><a href=https://lonami.dev/blog/ribw/final-nosql-evaluation/>Privado: Final NoSQL evaluation</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/a-practical-example-with-hadoop/>A practical example with Hadoop</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/introduction-to-hadoop-and-its-mapreduce/>Introduction to Hadoop and its MapReduce</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/googles-bigtable/>Google’s BigTable</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/developing-a-python-application-for-mongodb/>Developing a Python application for MongoDB</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/what-is-elasticsearch-and-why-should-you-care/>What is ElasticSearch and why should you care?</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/how-does-googles-search-engine-work/>How does Google’s Search Engine work?</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/integrating-apache-tika-into-our-crawler/>Integrating Apache Tika into our Crawler</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/pc-crawler-evaluation-2/>Privado: PC-Crawler evaluation 2</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/nosql-evaluation/>Privado: NoSQL evaluation</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/upgrading-our-baby-crawler/>Upgrading our Baby Crawler</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/mongodb-basic-operations-and-architecture/>MongoDB: Basic Operations and Architecture</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/nosql-databases-basic-operations-and-architecture/>Cassandra: Basic Operations and Architecture</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/mongodb-an-introduction/>MongoDB: an Introduction</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/cassandra-an-introduction/>Cassandra: an Introduction</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/pc-crawler-evaluation/>Privado: PC-Crawler evaluation</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/build-your-own-pc/>Build your own PC</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/introduction-to-nosql/>Introduction to NoSQL</a><span class=dim> </span><li><a href=https://lonami.dev/blog/ribw/about-boolean-retrieval/>About Boolean Retrieval</a><span class=dim> </span></ul><script> - const WELCOME_EN = 'Welcome to my blog!' - const WELCOME_ES = '¡Bienvenido a mi blog!' - const APOLOGIES = "ok sorry i'll stop" - const REWRITE_DELAY = 5000 - const CHAR_DELAY = 30 - const welcome = document.getElementById('welcome') - - let deleting = true - let english = false - let stopped = false - - const pls_stop = () => { - stopped = true - welcome.innerHTML = APOLOGIES - } - - const begin_rewrite = () => { - if (stopped) { - // now our visitor is angry :( - } else if (deleting) { - if (welcome.innerHTML == '…') { - deleting = false - } else { - welcome.innerHTML = welcome.innerHTML.slice(0, -1) || '…' - } - setTimeout(begin_rewrite, CHAR_DELAY) - } else { - let text = english ? WELCOME_EN : WELCOME_ES - welcome.innerHTML = text.slice(0, welcome.innerHTML.length + 1) - deleting = welcome.innerHTML.length == text.length - english = deleting - english - setTimeout(begin_rewrite, deleting ? REWRITE_DELAY : CHAR_DELAY) - } - } - - setTimeout(begin_rewrite, REWRITE_DELAY) -</script></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,2 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Integrating Apache Tika into our Crawler | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Integrating Apache Tika into our Crawler</h1><div class=time><p>2020-03-18T00:00:00+00:00<p>last updated 2020-03-25T17:38:07+00:00</div><p><a href=/blog/ribw/upgrading-our-baby-crawler/>In our last crawler post</a>, we detailed how our crawler worked, and although it did a fine job, it’s time for some extra upgrading.<h2 id=what-kind-of-upgrades>What kind of upgrades?</h2><p>A small but useful one. We are adding support for file types that contain text but cannot be processed by normal text editors because they are structured and not just plain text (such as PDF files, Excel, Word documents…).<p>And for this task, we will make use of the help offered by <a href=https://tika.apache.org/>Tika</a>, our friendly Apache tool.<h2 id=what-is-tika>What is Tika?</h2><p><a href=https://tika.apache.org/>Tika</a> is a set of libraries offered by <a href=https://en.wikipedia.org/wiki/The_Apache_Software_Foundation>The Apache Software Foundation</a> that we can include in our project in order to extract the text and metadata of files from a <a href=https://tika.apache.org/1.24/formats.html>long list of supported formats</a>.<h2 id=changes-in-the-code>Changes in the code</h2><p>Not much has changed in the structure of the crawler, we simply have added a new method in <code>Utils</code> that uses the class <code>Tika</code> from the previously mentioned library so as to process and extract the text of more filetypes.<p>Then, we use this text just like we would for our standard text file (checking the thesaurus and adding it to the word map) and voilà! We have just added support for a big range of file types.<h2 id=incorporating-gradle>Incorporating Gradle</h2><p>In order for the previous code to work, we need to make use of external libraries. To make this process easier and because the project is growing, we decided to use <a href=https://gradle.org/>Gradle</a>, a build system that can be used for projects in various programming languages, such as Java.<p>We followed their <a href=https://guides.gradle.org/building-java-applications/>guide to Building Java Applications</a>, and in a few steps added the required <code>.gradle</code> files. Now we can compile and run the code without having to worry about juggling with Java and external dependencies in a single command:<pre><code>./gradlew run -</code></pre><h2 id=download>Download</h2><p>And here you can download the final result:<p><em>download removed</em></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Introduction to Hadoop and its MapReduce | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Introduction to Hadoop and its MapReduce</h1><div class=time><p>2020-04-01T01:00:00+00:00<p>last updated 2020-04-03T08:43:44+00:00</div><p>Hadoop is an open-source, free, Java-based programming framework that helps processing large datasets in a distributed environment and the problems that arise when trying to harness the knowledge from BigData, capable of running on thousands of nodes and dealing with petabytes of data. It is based on Google File System (GFS) and originated from the work on the Nutch open-source project on search engines.<p>Hadoop also offers a distributed filesystem (HDFS) enabling for fast transfer among nodes, and a way to program with MapReduce.<p>It aims to strive for the 4 V’s: Volume, Variety, Veracity and Velocity. For veracity, it is a secure environment that can be trusted.<h2 id=milestones>Milestones</h2><p>The creators of Hadoop are Doug Cutting and Mike Cafarella, who just wanted to design a search engine, Nutch, and quickly found the problems of dealing with large amounts of data. They found their solution with the papers Google published.<p>The name comes from the plush of Cutting’s child, a yellow elephant.<ul><li>In July 2005, Nutch used GFS to perform MapReduce operations.<li>In February 2006, Nutch started a Lucene subproject which led to Hadoop.<li>In April 2007, Yahoo used Hadoop in a 1 000-node cluster.<li>In January 2008, Apache took over and made Hadoop a top-level project.<li>In July 2008, Apache tested a 4000-node cluster. The performance was the fastest compared to other technologies that year.<li>In May 2009, Hadoop sorted a petabyte of data in 17 hours.<li>In December 2011, Hadoop reached 1.0.<li>In May 2012, Hadoop 2.0 was released with the addition of YARN (Yet Another Resource Navigator) on top of HDFS, splitting MapReduce and other processes into separate components, greatly improving the fault tolerance.</ul><p>From here onwards, many other alternatives have born, like Spark, Hive & Drill, Kafka, HBase, built around the Hadoop ecosystem.<p>As of 2017, Amazon has clusters between 1 and 100 nodes, Yahoo has over 100 000 CPUs running Hadoop, AOL has clusters with 50 machines, and Facebook has a 320-machine (2 560 cores) and 1.3PB of raw storage.<h2 id=why-not-use-rdbms>Why not use RDBMS?</h2><p>Relational database management systems simply cannot scale horizontally, and vertical scaling will require very expensive servers. Similar to RDBMS, Hadoop has a notion of jobs (analogous to transactions), but without ACID or concurrency control. Hadoop supports any form of data (unstructured or semi-structured) in read-only mode, and failures are common but there’s a simple yet efficient fault tolerance.<p>So what problems does Hadoop solve? It solves the way we should think about problems, and distributing them, which is key to do anything related with BigData nowadays. We start working with clusters of nodes, and coordinating the jobs between them. Hadoop’s API makes this really easy.<p>Hadoop also takes very seriously the loss of data with replication, and if a node falls, they are moved to a different node.<h2 id=major-components>Major components</h2><p>The previously-mentioned HDFS runs on commodity machine, which are cost-friendly. It is very fault-tolerant and efficient enough to process huge amounts of data, because it splits large files into smaller chunks (or blocks) that can be more easily handled. Multiple nodes can work on multiple chunks at the same time.<p>NameNode stores the metadata of the various datablocks (map of blocks) along with their location. It is the brain and the master in Hadoop’s master-slave architecture, also known as the namespace, and makes use of the DataNode.<p>A secondary NameNode is a replica that can be used if the first NameNode dies, so that Hadoop doesn’t shutdown and can restart.<p>DataNode stores the blocks of data, and are the slaves in the architecture. This data is split into one or more files. Their only job is to manage this access to the data. They are often distributed among racks to avoid data lose.<p>JobTracker creates and schedules jobs from the clients for either map or reduce operations.<p>TaskTracker runs MapReduce tasks assigned to the current data node.<p>When clients need data, they first interact with the NameNode and replies with the location of the data in the correct DataNode. Client proceeds with interaction with the DataNode.<h2 id=mapreduce>MapReduce</h2><p>MapReduce, as the name implies, is split into two steps: the map and the reduce. The map stage is the «divide and conquer» strategy, while the reduce part is about combining and reducing the results.<p>The mapper has to process the input data (normally a file or directory), commonly line-by-line, and produce one or more outputs. The reducer uses all the results from the mapper as its input to produce a new output file itself.<p><img src=https://lonami.dev/blog/ribw/introduction-to-hadoop-and-its-mapreduce/bitmap.png><p>When reading the data, some may be junk that we can choose to ignore. If it is valid data, however, we label it with a particular type that can be useful for the upcoming process. Hadoop is responsible for splitting the data accross the many nodes available to execute this process in parallel.<p>There is another part to MapReduce, known as the Shuffle-and-Sort. In this part, types or categories from one node get moved to a different node. This happens with all nodes, so that every node can work on a complete category. These categories are known as «keys», and allows Hadoop to scale linearly.<h2 id=references>References</h2><ul><li><a href=https://youtu.be/oT7kczq5A-0>YouTube – Hadoop Tutorial For Beginners | What Is Hadoop? | Hadoop Tutorial | Hadoop Training | Simplilearn</a><li><a href=https://youtu.be/bcjSe0xCHbE>YouTube – Learn MapReduce with Playing Cards</a><li><a href=https://youtu.be/j8ehT1_G5AY?list=PLi4tp-TF_qjM_ed4lIzn03w7OnEh0D8Xi>YouTube – Video Post #2: Hadoop para torpes (I)-¿Qué es y para qué sirve?</a><li><a href=https://youtu.be/NQ8mjVPCDvk?list=PLi4tp-TF_qjM_ed4lIzn03w7OnEh0D8Xi>Video Post #3: Hadoop para torpes (II)-¿Cómo funciona? HDFS y MapReduce</a><li><a href=https://hadoop.apache.org/old/releases.html>Apache Hadoop Releases</a><li><a href=https://youtu.be/20qWx2KYqYg?list=PLi4tp-TF_qjM_ed4lIzn03w7OnEh0D8Xi>Video Post #4: Hadoop para torpes (III y fin)- Ecosistema y distribuciones</a><li><a href=http://www.hadoopbook.com/>Chapter 2 – Hadoop: The Definitive Guide, Fourth Edition</a> (<a href=http://grut-computing.com/HadoopBook.pdf>pdf,</a><a href=http://www.hadoopbook.com/code.html>code</a>)</ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Introduction to NoSQL | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Introduction to NoSQL</h1><div class=time><p>2020-02-25T01:00:15+00:00<p>last updated 2020-03-18T09:38:23+00:00</div><p>This post will primarly focus on the talk held in the <a href=https://youtu.be/qI_g07C_Q5I>GOTO 2012 conference: Introduction to NoSQL by Martin Fowler</a>. It can be seen as an informal, summarized transcript of the talk<hr><p>The relational database model is affected by the <em><a href=https://en.wikipedia.org/wiki/Object-relational_impedance_mismatch>impedance mismatch problem</a></em>. This occurs because we have to match our high-level design with the separate columns and rows used by relational databases.<p>Taking the in-memory objects and putting them into a relational database (which were dominant at the time) simply didn’t work out. Why? Relational databases were more than just databases, they served as a an integration mechanism across applications, up to the 2000s. For 20 years!<p>With the rise of the Internet and the sheer amount of traffic, databases needed to scale. Unfortunately, relational databases only scale well vertically (by upgrading a <em>single</em> node). This is <em>very</em> expensive, and not something many could afford.<p>The problem are those pesky <code>JOIN</code>‘s, and its friends <code>GROUP BY</code>. Because our program and reality model don’t match the tables used by SQL, we have to rely on them to query the data. It is because the model doesn’t map directly.<p>Furthermore, graphs don’t map very well at all to relational models.<p>We needed a way to scale horizontally (by increasing the <em>amount</em> of nodes), something relational databases were not designed to do.<blockquote><p><em>We need to do something different, relational across nodes is an unnatural act</em></blockquote><p>This inspired the NoSQL movement.<blockquote><p><em>#nosql was only meant to be a hashtag to advertise it, but unfortunately it’s how it is called now</em></blockquote><p>It is not possible to define NoSQL, but we can identify some of its characteristics:<ul><li><p>Non-relational<li><p><strong>Cluster-friendly</strong> (this was the original spark)<li><p>Open-source (until now, generally)<li><p>21st century web culture<li><p>Schema-less (easier integration or conjugation of several models, structure aggregation) These databases use different data models to those used by the relational model. However, it is possible to identify 4 broad chunks (some may say 3, or even 2!):<li><p><strong>Key-value store</strong>. With a certain key, you obtain the value corresponding to it. It knows nothing else, nor does it care. We say the data is opaque.<li><p><strong>Document-based</strong>. It stores an entire mass of documents with complex structure, normally through the use of JSON (XML has been left behind). Then, you can ask for certain fields, structures, or portions. We say the data is transparent.<li><p><strong>Column-family</strong>. There is a «row key», and within it we store multiple «column families» (columns that fit together, our aggregate). We access by row-key and column-family name. All of these kind of serve to store documents without any <em>explicit</em> schema. Just shove in anything! This gives a lot of flexibility and ease of migration, except… that’s not really true. There’s an <em>implicit</em> schema when querying.</ul><p>For example, a query where we may do <code>anOrder['price'] * anOrder['quantity']</code> is assuming that <code>anOrder</code> has both a <code>price</code> and a <code>quantity</code>, and that both of these can be multiplied together. «Schema-less» is a fuzzy term.<p>However, it is the lack of a <em>fixed</em> schema that gives flexibility.<p>One could argue that the line between key-value and document-based is very fuzzy, and they would be right! Key-value databases often let you include additional metadata that behaves like an index, and in document-based, documents often have an identifier anyway.<p>The common notion between these three types is what matters. They save an entire structure as an <em>unit</em>. We can refer to these as «Aggregate Oriented Databases». Aggregate, because we group things when designing or modeling our systems, as opposed to relational databases that scatter the information across many tables.<p>There exists a notable outlier, though, and that’s:<ul><li><strong>Graph</strong> databases. They use a node-and-arc graph structure. They are great for moving on relationships across things. Ironically, relational databases are not very good at jumping across relationships! It is possibly to perform very interesting queries in graph databases which would be really hard and costly on relational models. Unlike the aggregated databases, graphs break things into even smaller units. NoSQL is not <em>the</em> solution. It depends on how you’ll work with your data. Do you need an aggregate database? Will you have a lot of relationships? Or would the relational model be good fit for you?</ul><p>NoSQL, however, is a good fit for large-scale projects (data will <em>always</em> grow) and faster development (the impedance mismatch is drastically reduced).<p>Regardless of our choice, it is important to remember that NoSQL is a young technology, which is still evolving really fast (SQL has been stable for <em>decades</em>). But the <em>polyglot persistence</em> is what matters. One must know the alternatives, and be able to choose.<hr><p>Relational databases have the well-known ACID properties: Atomicity, Consistency, Isolation and Durability.<p>NoSQL (except graph-based!) are about being BASE instead: Basically Available, Soft state, Eventual consistency.<p>SQL needs transactions because we don’t want to perform a read while we’re only half-way done with a write! The readers and writers are the problem, and ensuring consistency results in a performance hit, even if the risk is low (two writers are extremely rare but it still must be handled).<p>NoSQL on the other hand doesn’t need ACID because the aggregate <em>is</em> the transaction boundary. Even before NoSQL itself existed! Any update is atomic by nature. When updating many documents it <em>is</em> a problem, but this is very rare.<p>We have to distinguish between logical and replication consistency. During an update and if a conflict occurs, it must be resolved to preserve the logical consistency. Replication consistency on the other hand is preserveed when distributing the data across many machines, for example during sharding or copies.<p>Replication buys us more processing power and resillence (at the cost of more storage) in case some of the nodes die. But what happens if what dies is the communication across the nodes? We could drop the requests and preserve the consistency, or accept the risk to continue and instead preserve the availability.<p>The choice on whether trading consistency for availability is acceptable or not depends on the domain rules. It is the domain’s choice, the business people will choose. If you’re Amazon, you always want to be able to sell, but if you’re a bank, you probably don’t want your clients to have negative numbers in their account!<p>Regardless of what we do, in a distributed system, the CAP theorem always applies: Consistecy, Availability, Partitioning-tolerancy (error tolerancy). It is <strong>impossible</strong> to guarantee all 3 at 100%. Most of the times, it does work, but it is mathematically impossible to guarantee at 100%.<p>A database has to choose what to give up at some point. When designing a distributed system, this must be considered. Normally, the choice is made between consistency or response time.<h2 id=further-reading>Further reading</h2><ul><li><a href=https://www.martinfowler.com/articles/nosql-intro-original.pdf>The future is: <del>NoSQL Databases</del> Polyglot Persistence</a><li><a href=https://www.thoughtworks.com/insights/blog/nosql-databases-overview>NoSQL Databases: An Overview</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,12 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> MongoDB: an Introduction | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>MongoDB: an Introduction</h1><div class=time><p>2020-03-05T01:00:06+00:00<p>last updated 2020-04-08T17:38:22+00:00</div><p>This is the first post in the MongoDB series, where we will introduce the MongoDB database system and take a look at its features and installation methods.<p>Other posts in this series:<ul><li><a href=/blog/ribw/mongodb-an-introduction/>MongoDB: an Introduction</a> (this post)<li><a href=/blog/ribw/mongodb-basic-operations-and-architecture/>MongoDB: Basic Operations and Architecture</a><li><a href=/blog/ribw/developing-a-python-application-for-mongodb/>Developing a Python application for MongoDB</a></ul><p>This post is co-authored wih Classmate.<hr><p><img src=https://lonami.dev/blog/ribw/mongodb-an-introduction/mongodb.png alt="NoSQL database – MongoDB – First delivery"><h2 id=purpose-of-technology>Purpose of technology</h2><p>MongoDB is a <strong>general purpose, document-based, distributed database</strong> built for modern application developers and for the cloud era, with the scalability and flexibility that you want with the querying and indexing that you need. It being a document database means it stores data in JSON-like documents.<p>The Mongo team believes this is the most natural way to think about data, which is (they claim) much more expressive and powerful than the traditional row/column model, since programmers think in objects.<h2 id=how-it-works>How it works</h2><p>MongoDB’s architecture can be summarized as follows:<ul><li>Document data model.<li>Distributed systems design.<li>Unified experience with freedom to run it anywhere.</ul><p>For a more in-depth explanation, MongoDB offers a <a href=https://www.mongodb.com/collateral/mongodb-architecture-guide>download to the MongoDB Architecture Guide</a> with roughly ten pages worth of text.<p><img src=https://lonami.dev/blog/ribw/mongodb-an-introduction/knGHenfTGA4kzJb1PHmS9EQvtZl2QlhbIPN15M38m8fZfZf7ODwYfhf0Tltr.png> _ Overview of MongoDB’s architecture_<p>Regarding usage, MongoDB comes with a really nice introduction along with JavaScript, Python, Java, C++ or C# code at our choice, which describes the steps necessary to make it work. Below we will describe a common workflow.<p>First, we must <strong>connect</strong> to a running MongoDB instance. Once the connection succeeds, we can access individual «collections», which we can think of as <em>tables</em> where collections of data is stored.<p>For instance, we could <strong>insert</strong> an arbitrary JSON document into the <code>restaurants</code> collection to store information about a restaurant.<p>At any other point in time, we can <strong>query</strong> these collections. The queries range from trivial, empty ones (which would retrieve all the documents and fields) to more rich and complex queries (for instance, using AND and OR operators, checking if data exists, and then looking for a value in a list).<p>MongoDB also supports the creation of <strong>indices</strong>, similar to those in other database systems. It allows for the creation of indices on any field or subfields.<p>In Mongo, the <strong>aggregation pipeline</strong> allows us to filter and analyze data based on a given set of criteria. For example, we could pull all the documents in the <code>restaurants</code> collection that have a <code>category</code> of <code>Bakery</code> using the <code>$match</code> operator. Then, we can group them by their star rating using the <code>$group</code> operator. Using the accumulator operator, <code>$sum</code>, we can see how many bakeries in our collection have each star rating.<h2 id=features>Features</h2><p>The features can be seen all over the place in their site, because it’s something they make a lot of emphasis on:<ul><li><p><strong>Easy development</strong>, thanks to the document data model, something they claim to be «the best way to work with data».<li><p>Data is stored in flexible JSON-like documents.<li><p>This model directly maps to the objects in the application’s code.<li><p>Ad hoc queries, indexing, and real time aggregation provide powerful ways to access and analyze the data.<li><p><strong>Powerful query language</strong>, with a rich and expressive query language that allows filtering and sorting by any field, no matter how nested it may be within a document. The queries are themselves JSON, and thus easily composable.<li><p><strong>Support for aggregations</strong> and other modern use-cases such as geo-based search, graph search, and text search.<li><p><strong>A distributed systems design</strong>, which allows developers to intelligently put data where they want it. High availability, horizontal scaling, and geographic distribution are built in and easy to use.<li><p><strong>A unified experience</strong> with the freedom to run anywhere, which allows developers to future-proof their work and eliminate vendor lock-in.</ul><h2 id=corner-in-cap-theorem>Corner in CAP theorem</h2><p>MongoDB’s position in the CAP theorem (Consistency, Availability, Partition Tolerance) depends on the database and driver configurations, and the type of disaster.<ul><li>With <strong>no partitions</strong>, the main focus is <strong>CA</strong>.<li>If there are **partitions **but the system is <strong>strongly connected</strong>, the main focus is <strong>AP</strong>: non-synchronized writes from the old primary are ignored.<li>If there are <strong>partitions</strong> but the system is <strong>not strongly connected</strong>, the main focus is <strong>CP</strong>: only read access is provided to avoid inconsistencies. The general consensus seems to be that Mongo is <strong>CP</strong>.</ul><h2 id=download>Download</h2><p>We will be using the apt-based installation.<p>The Community version can be downloaded by anyone through <a href=https://www.mongodb.com/download-center/community>MongoDB Download Center</a>, where one can choose the version, Operating System and Package.MongoDB also seems to be <a href=https://packages.ubuntu.com/eoan/mongodb>available in Ubuntu’s PPAs</a>.<h2 id=installation>Installation</h2><p>We will be using an Ubuntu-based system, with apt available. To install MongoDB, we open a terminal and run the following command:<pre><code>apt install mongodb -</code></pre><p>After confirming that we do indeed want to install the package, we should be able to run the following command to verify that the installation was successful:<pre><code>mongod --version -</code></pre><p>The output should be similar to the following:<pre><code>db version v4.0.16 -git version: 2a5433168a53044cb6b4fa8083e4cfd7ba142221 -OpenSSL version: OpenSSL 1.1.1 11 Sep 2018 -allocator: tcmalloc -modules: none -build environment: - distmod: ubuntu1804 - distarch: x86_64 - target_arch: x86_64 -</code></pre><h2 id=references>References</h2><ul><li><a href=https://www.mongodb.com/>MongoDB’s official site</a><li><a href=https://www.mongodb.com/what-is-mongodb>What is MongoDB?</a><li><a href=https://www.mongodb.com/mongodb-architecture>MongoDB Architecture</a><li><a href=https://stackoverflow.com/q/11292215/4759433>Where does mongodb stand in the CAP theorem?</a><li><a href=https://medium.com/@bikas.katwal10/mongodb-vs-cassandra-vs-rdbms-where-do-they-stand-in-the-cap-theorem-1bae779a7a15>What is the CAP Theorem? MongoDB vs Cassandra vs RDBMS, where do they stand in the CAP theorem?</a><li><a href=https://www.quora.com/Why-doesnt-MongoDB-have-availability-in-the-CAP-theorem>Why doesn’t MongoDB have availability in the CAP theorem?</a><li><a href=https://docs.mongodb.com/manual/installation/>Install MongoDB</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,124 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> MongoDB: Basic Operations and Architecture | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>MongoDB: Basic Operations and Architecture</h1><div class=time><p>2020-03-05T04:00:08+00:00<p>last updated 2020-04-08T17:36:25+00:00</div><p>This is the second post in the MongoDB series, where we will take a look at the <a href=https://stackify.com/what-are-crud-operations/>CRUD operations</a> they support, the data model and architecture used.<p>Other posts in this series:<ul><li><a href=/blog/ribw/mongodb-an-introduction/>MongoDB: an Introduction</a><li><a href=/blog/ribw/mongodb-basic-operations-and-architecture/>MongoDB: Basic Operations and Architecture</a> (this post)<li><a href=/blog/ribw/developing-a-python-application-for-mongodb/>Developing a Python application for MongoDB</a></ul><p>This post is co-authored wih Classmate, and in it we will take an explorative approach using the <code>mongo</code> command line shell to execute commands against the database. It even has TAB auto-completion, which is awesome!<hr><p>Before creating any documents, we first need to create somewhere for the documents to be in. And before we create anything, the database has to be running, so let’s do that first. If we don’t have a service installed, we can run the <code>mongod</code> command ourselves in some local folder to make things easier:<pre><code>$ mkdir -p mongo-database -$ mongod --dbpath mongo-database -</code></pre><p>Just like that, we will have Mongo running. Now, let’s connect to it using the <code>mongo</code> command in another terminal (don’t close the terminal where the server is running, we need it!). By default, it connects to localhost, which is just what we need.<pre><code>$ mongo -</code></pre><h2 id=create>Create</h2><h3 id=create-a-database>Create a database</h3><p>Let’s list the databases:<pre><code>> show databases -admin 0.000GB -config 0.000GB -local 0.000GB -</code></pre><p>Oh, how interesting! There’s already some databases, even though we just created the directory where Mongo will store everything. However, they seem empty, which make sense.<p>Creating a new database is done by <code>use</code>-ing a name that doesn’t exist. Let’s call our new database «helloworld».<pre><code>> use helloworld -switched to db helloworld -</code></pre><p>Good! Now the «local variable» called <code>db</code> points to our <code>helloworld</code> database.<pre><code>> db -helloworld -</code></pre><p>What happens if we print the databases again? Surely our new database will show up now…<pre><code>> show databases -admin 0.000GB -config 0.000GB -local 0.000GB -</code></pre><p>…maybe not! It seems Mongo won’t create the database until we create some collections and documents in it. Databases contain collections, and inside collections (which you can think of as tables) we can insert new documents (which you can think of as rows). Like in many programming languages, the dot operator is used to access these «members».<h3 id=create-a-document>Create a document</h3><p>Let’s add a new greeting into the <code>greetings</code> collection:<pre><code>> db.greetings.insert({message: "¡Bienvenido!", lang: "es"}) -WriteResult({ "nInserted" : 1 }) - -> show collections -greetings - -> show databases -admin 0.000GB -config 0.000GB -helloworld 0.000GB -local 0.000GB -</code></pre><p>That looks promising! We can also see our new <code>helloworld</code> database also shows up. The Mongo shell actually works on JavaScript-like code, which is why we can use a variant of JSON (BSON) to insert documents (note the lack of quotes around the keys, convenient!).<p>The <a href=https://docs.mongodb.com/manual/reference/method/db.collection.insert/index.html><code>insert</code></a> method actually supports a list of documents, and by default Mongo will assign a unique identifier to each. If we don’t want that though, all we have to do is add the <code>_id</code> key to our documents.<pre><code>> db.greetings.insert([ -... {message: "Welcome!", lang: "en"}, -... {message: "Bonjour!", lang: "fr"}, -... ]) -BulkWriteResult({ - "writeErrors" : [ ], - "writeConcernErrors" : [ ], - "nInserted" : 2, - "nUpserted" : 0, - "nMatched" : 0, - "nModified" : 0, - "nRemoved" : 0, - "upserted" : [ ] -}) -</code></pre><h3 id=create-a-collection>Create a collection</h3><p>In this example, we created the collection <code>greetings</code> implicitly, but behind the scenes Mongo made a call to <a href=https://docs.mongodb.com/manual/reference/method/db.createCollection/><code>createCollection</code></a>. Let’s do just that:<pre><code>> db.createCollection("goodbyes") -{ "ok" : 1 } - -> show collections -goodbyes -greetings -</code></pre><p>The method actually has a default parameter to configure other options, like the maximum size of the collection or maximum amount of documents in it, validation-related options, and so on. These are all described in more details in the documentation.<h2 id=read>Read</h2><p>To read the contents of a document, we have to <a href=https://docs.mongodb.com/manual/reference/method/db.collection.find/index.html><code>find</code></a> it.<pre><code>> db.greetings.find() -{ "_id" : ObjectId("5e74829a0659f802b15f18dd"), "message" : "¡Bienvenido!", "lang" : "es" } -{ "_id" : ObjectId("5e7487b90659f802b15f18de"), "message" : "Welcome!", "lang" : "en" } -{ "_id" : ObjectId("5e7487b90659f802b15f18df"), "message" : "Bonjour!", "lang" : "fr" } -</code></pre><p>That’s a bit unreadable for my taste, can we make it more <a href=https://docs.mongodb.com/manual/reference/method/cursor.pretty/index.html><code>pretty</code></a>?<pre><code>> db.greetings.find().pretty() -{ - "_id" : ObjectId("5e74829a0659f802b15f18dd"), - "message" : "¡Bienvenido!", - "lang" : "es" -} -{ - "_id" : ObjectId("5e7487b90659f802b15f18de"), - "message" : "Welcome!", - "lang" : "en" -} -{ - "_id" : ObjectId("5e7487b90659f802b15f18df"), - "message" : "Bonjour!", - "lang" : "fr" -} -</code></pre><p>Gorgeous! We can clearly see Mongo created an identifier for us automatically. The queries are also JSON, and support a bunch of operators (prefixed by <code>$</code>), known as <a href=https://docs.mongodb.com/manual/reference/operator/query/>Query Selectors</a>. Here’s a few:<table><thead><tr><th>Operation<th>Syntax<th>RDBMS equivalent<tbody><tr><td>Equals<td><code> - {key: {$eq: value}} - </code> <br> Shorthand: <code> - {key: value} - </code><td><code> - where key = value - </code><tr><td>Less Than<td><code> - {key: {$lte: value}} - </code><td><code> - where key < value - </code><tr><td>Less Than or Equal<td><code> - {key: {$lt: value}} - </code><td><code> - where key <= value - </code><tr><td>Greater Than<td><code> - {key: {$gt: value}} - </code><td><code> - where key > value - </code><tr><td>Greater Than or Equal<td><code> - {key: {$gte: value}} - </code><td><code> - where key >= value - </code><tr><td>Not Equal<td><code> - {key: {$ne: value}} - </code><td><code> - where key != value - </code><tr><td>And<td><code> - {$and: [{k1: v1}, {k2: v2}]} - </code><td><code> - where k1 = v1 and k2 = v2 - </code><tr><td>Or<td><code> - {$or: [{k1: v1}, {k2: v2}]} - </code><td><code> - where k1 = v1 or k2 = v2 - </code></table><p>The operations all do what you would expect them to do, and their names are really intuitive. Aggregating operations with <code>$and</code> or <code>$or</code> can be done anywhere in the query, nested any level deep.<h2 id=update>Update</h2><p>Updating a document can be done by using <a href=https://docs.mongodb.com/manual/reference/method/db.collection.save/index.html><code>save</code></a> on an already-existing document (that is, the document we want to save has <code>_id</code> and it’s in the collection already). If the document is not in the collection yet, this method will create it.<pre><code>> db.greetings.save({_id: ObjectId("5e74829a0659f802b15f18dd"), message: "¡Bienvenido, humano!", "lang" : "es"}) -WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) - -> db.greetings.find({lang: "es"}) -{ "_id" : ObjectId("5e74829a0659f802b15f18dd"), "message" : "¡Bienvenido, humano!", "lang" : "es" } -</code></pre><p>Alternatively, the <a href=https://docs.mongodb.com/manual/reference/method/db.collection.update/index.html><code>update</code></a> method takes a query and new value.<pre><code>> db.greetings.update({lang: "en"}, {$set: {message: "Welcome, human!"}}) -WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) - -> db.greetings.find({lang: "en"}) -{ "_id" : ObjectId("5e7487b90659f802b15f18de"), "message" : "Welcome, human!", "lang" : "en" } -</code></pre><h2 id=indexing>Indexing</h2><p>Creating an index is done with <a href=https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/index.html><code>createIndex</code></a>:<pre><code>> db.greetings.createIndex({lang: +1}) -{ - "createdCollectionAutomatically" : false, - "numIndexesBefore" : 1, - "numIndexesAfter" : 2, - "ok" : 1 -} -</code></pre><p>Here, we create an ascending index on the lang key. Descending order is done with <code>-1</code>. Now a query for <code>lang</code> in our three documents will be fast… well maybe iteration over three documents was faster than an index.<h2 id=delete>Delete</h2><h3 id=delete-a-document>Delete a document</h3><p>I have to confess, I can’t talk French. I learnt it long ago and it’s long forgotten, so let’s remove the translation I copied online from our greetings with <a href=https://docs.mongodb.com/manual/reference/method/db.collection.remove/index.html><code>remove</code></a>.<pre><code>> db.greetings.remove({lang: "fr"}) -WriteResult({ "nRemoved" : 1 }) -</code></pre><h3 id=delete-a-collection>Delete a collection</h3><p>We never really used the <code>goodbyes</code> collection. Can we get rid of that?<pre><code>> db.goodbyes.drop() -true -</code></pre><p>Yes, it is <code>true</code> that we can <a href=https://docs.mongodb.com/manual/reference/method/db.collection.drop/index.html><code>drop</code></a> it.<h3 id=delete-a-database>Delete a database</h3><p>Now, I will be honest, I don’t really like our <code>greetings</code> database either. It stinks. Let’s get rid of it as well:<pre><code>> db.dropDatabase() -{ "dropped" : "helloworld", "ok" : 1 } -</code></pre><p>Yeah, take that! The <a href=https://docs.mongodb.com/manual/reference/method/db.dropDatabase/><code>dropDatabase</code></a> can be used to drop databases.<h2 id=references>References</h2><p>The examples in this post are all fictional, and the methods that could be used where taken from Classmate’s post, and of course <a href=https://docs.mongodb.com/manual/reference/method/>Mongo’s documentation</a>.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,87 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Cassandra: Basic Operations and Architecture | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Cassandra: Basic Operations and Architecture</h1><div class=time><p>2020-03-05T02:00:36+00:00<p>last updated 2020-03-24T17:57:05+00:00</div><p>This is the second post in the NoSQL Databases series, with a brief description on the basic operations (such as insertion, retrieval, indexing…), and complete execution along with the data model / architecture.<p>Other posts in this series:<ul><li><a href=/blog/ribw/nosql-databases-an-introduction/>Cassandra: an Introduction</a><li><a href=/blog/ribw/nosql-databases-basic-operations-and-architecture/>Cassandra: Basic Operations and Architecture</a> (this post)</ul><hr><p>Cassandra uses it own Query Language for managing the databases, it is known as **CQL **(<strong>Cassandra Query Language</strong>). Cassandra stores data in <strong><em>tables</em></strong>, as in relational databases, and these tables are grouped in <strong><em>keyspaces</em></strong>. A keyspace defines a number of options that applies to all the tables it contains. The most used option is the **replication strategy. **It is recommended to have only one keyspace by application.<p>It is important to mention that <strong>tables and keyspaces</strong> are** case insensitive**, so myTable is equivalent to mytable, but it is possible to <strong>force case sensitivity</strong> using <strong>double-quotes</strong>.<p>To begin with the basic operations it is necessary to deploy Cassandra:<ol><li>Open a terminal in the root of the Apache Cassandra folder downloaded in the previous post.<li>Run the command:</ol><pre><code>$ bin/cassandra -</code></pre><p>Once Cassandra is deployed, it is time to open a** CQL Shell**, in <strong>other terminal</strong>, with the command:<pre><code>$ bin/cqlsh -</code></pre><p>It is possible to check if Cassandra is deployed if the SQL Shell prints the next message:<p><img src=https://lonami.dev/blog/ribw/nosql-databases-basic-operations-and-architecture/uwqQgQte-cuYb_pePFOuY58re23kngrDKNgL1qz4yOfnBDZkqMIH3fFuCrye.png> <em>CQL Shell</em><h2 id=create-insert>Create/Insert</h2><h3 id=ddl-data-definition-language>DDL (Data Definition Language)</h3><h4 id=create-keyspace>Create keyspace</h4><p>A keyspace is created using a **CREATE KEYSPACE **statement:<pre><code>$ **CREATE** KEYSPACE [ **IF** **NOT** **EXISTS** ] keyspace_name **WITH** options; -</code></pre><p>The supported “<strong>options</strong>” are:<ul><li>“<strong>replication</strong>”: this is **mandatory **and defines the <strong>replication strategy</strong> and the <strong>replication factor</strong> (the number of nodes that will have a copy of the data). Within this option there is a property called “<strong>class</strong>” in which the <strong>replication strategy</strong> is specified (“SimpleStrategy” or “NetworkTopologyStrategy”)<li>“<strong>durable_writes</strong>”: this is <strong>not mandatory</strong> and it is possible to use the <strong>commit logs for updates</strong>. Attempting to create an already existing keyspace will return an error unless the **IF NOT EXISTS **directive is used.</ul><p>The example associated to this statement is create a keyspace with name “test_keyspace” with “SimpleStrategy” as “class” of replication and a “replication_factor” of 3.<pre><code>**CREATE** KEYSPACE test_keyspace - **WITH** **replication** = {'class': 'SimpleStrategy', - 'replication_factor' : 3}; -</code></pre><p>The **USE **statement allows to <strong>change</strong> the current <strong>keyspace</strong>. The syntax of this statement is very simple:<pre><code>**USE** keyspace_name; -</code></pre><p><img src=https://lonami.dev/blog/ribw/nosql-databases-basic-operations-and-architecture/RDWIG2RwvEevUFQv6TGFtGzRm4_9ERpxPf0feriflaj3alvWw3FEIAr_ZdF1.png> <em>USE statement</em><p>It is also possible to get the metadata from a keyspace with the **DESCRIBE **statement.<pre><code>**DESCRIBE** KEYSPACES | KEYSPACE keyspace_name; -</code></pre><h4 id=create-table>Create table</h4><p>Creating a new table uses the **CREATE TABLE **statement:<pre><code>**CREATE** **TABLE** [ **IF** **NOT** **EXISTS** ] table_name - '(' - column_definition - ( ',' column_definition )* - [ ',' **PRIMARY** **KEY** '(' primary_key ')' ] - ')' [ **WITH** table_options ]; -</code></pre><p>With “column_definition” as: column_name cql_type [ STATIC ] [ PRIMARY KEY]; “primary_key” as: partition_key [ ‘,’ clustering_columns ]; and “table_options” as: COMPACT STORAGE [ AND table_options ] or CLUSTERING ORDER BY ‘(‘ clustering_order ‘)’ [ AND table_options ] or “options”.<p>Attempting to create an already existing table will return an error unless the <strong>IF NOT EXISTS</strong> directive is used.<p>The <strong>CQL types</strong> are described in the References section.<p>For example, we are going to create a table called “species_table” in the keyspace “test_keyspace” in which we will have a “species” text (as PRIMARY KEY), a “common_name” text, a “population” varint, a “average_size” int and a “sex” text. Besides, we are going to add a comment to the table: “Some species records”;<pre><code>**CREATE** **TABLE** species_table ( - species text **PRIMARY** **KEY**, - common_name text, - population varint, - average_size **int**, - sex text, -) **WITH** **comment**='Some species records'; -</code></pre><p>It is also possible to get the metadata from a table with the **DESCRIBE **statement.<pre><code>**DESCRIBE** **TABLES** | **TABLE** [keyspace_name.]table_name; -</code></pre><h3 id=dml-data-manipulation-language>DML (Data Manipulation Language)</h3><h4 id=insert-data>Insert data</h4><p>Inserting data for a row is done using an **INSERT **statement:<pre><code>**INSERT** **INTO** table_name ( names_values | json_clause ) - [ **IF** **NOT** **EXISTS** ] - [ **USING** update_parameter ( **AND** update_parameter )* ]; -</code></pre><p>Where “names_values” is: names VALUES tuple_literal; “json_clause” is: JSON string [ DEFAULT ( NULL | UNSET ) ]; and “update_parameter” is usually: TTL.<p>For example we are going to use both VALUES and JSON clauses to insert data in the table “species_table”. In the VALUES clause it is necessary to supply the list of columns, not as in the JSON clause that is optional.<p>Note: TTL (Time To Live) and Timestamp are metrics for expiring data, so, when the time set is passed, the operation is expired.<p>In the VALUES clause we are going to insert a new specie called “White monkey”, with an average size of 3, its common name is “Copito de nieve”, population 0 and sex “male”.<pre><code>**INSERT** **INTO** species_table (species, common_name, population, average_size, sex) - **VALUES** ('White monkey', 'Copito de nieve', 0, 3, 'male'); -</code></pre><p>In the JSON clause we are going to insert a new specie called “Cloned sheep”, with an average size of 1, its common name is “Dolly the sheep”, population 0 and sex “female”.<pre><code>**INSERT** **INTO** species_table JSON '{"species": "Cloned Sheep", - "common_name": "Dolly the Sheep", - "average_size":1, - "population":0, - "sex": "female"}'; -</code></pre><p>Note: all updates for an **INSERT **are applied **atomically **and in <strong>isolation.</strong><h2 id=read>Read</h2><p>Querying data from data is done using a **SELECT **statement:<pre><code>**SELECT** [ JSON | **DISTINCT** ] ( select_clause | '*' ) - **FROM** table_name - [ **WHERE** where_clause ] - [ **GROUP** **BY** group_by_clause ] - [ **ORDER** **BY** ordering_clause ] - [ PER **PARTITION** **LIMIT** (**integer** | bind_marker) ] - [ **LIMIT** (**integer** | bind_marker) ] - [ ALLOW FILTERING ]; -</code></pre><p>The **CQL SELECT **statement is very **similar **to the **SQL SELECT **statement due to the fact that both allows filtering (<strong>WHERE</strong>), grouping data (<strong>GROUP BY</strong>), ordering the data (<strong>ORDER BY</strong>) and limit the number of data (<strong>LIMIT</strong>). Besides, **CQL offers **a **limit per partition **and allow the **filtering **of <strong>data</strong>.<p>Note: as in SQL it it possible to set alias to the data with the statement <strong>AS.</strong><p>For example we are going to retrieve all the information about those values from the tables “species_table” which “sex” is “male”. Allow filtering is mandatory when there is a WHERE statement.<pre><code>**SELECT** * **FROM** species_table **WHERE** sex = 'male' ALLOW FILTERING; -</code></pre><p><img src=https://lonami.dev/blog/ribw/nosql-databases-basic-operations-and-architecture/s6GrKIGATvOSD7oGRNScUU5RnLN_-3X1JXvnVi_wDT_hrmPMZdnCdBI8DpIJ.png> <em>SELECT statement</em><p>Furthermore, we are going to test the SELECT JSON statement. For this, we are going to retrieve only the species name with a population of 0.<pre><code>**SELECT** JSON species **FROM** species_table **WHERE** population = 0 ALLOW FILTERING; -</code></pre><p><img src=https://lonami.dev/blog/ribw/nosql-databases-basic-operations-and-architecture/Up_eHlqKQp2RI5XIbgPOvj1B5J3gLxz7v7EI0NDRgezQTipecdfDT6AQoso0.png> <em>SELECT JSON statement</em><h2 id=update>Update</h2><h3 id=ddl-data-definition-language-1>DDL (Data Definition Language)</h3><h4 id=alter-keyspace>Alter keyspace</h4><p>The statement **ALTER KEYSPACE **allows to modify the options of a keyspace:<pre><code>**ALTER** KEYSPACE keyspace_name **WITH** options; -</code></pre><p>Note: the supported **options **are the same than for creating a keyspace, “<strong>replication</strong>” and “<strong>durable_writes</strong>”.<p>The example associated to this statement is to modify the keyspace with name “test_keyspace” and set a “replication_factor” of 4.<pre><code>**ALTER** KEYSPACE test_keyspace - **WITH** **replication** = {'class': 'SimpleStrategy', 'replication_factor' : 4}; -</code></pre><h4 id=alter-table>Alter table</h4><p>Altering an existing table uses the **ALTER TABLE **statement:<pre><code>**ALTER** **TABLE** table_name alter_table_instruction; -</code></pre><p>Where “alter_table_instruction” can be: ADD column_name cql_type ( ‘,’ column_name cql_type )<em>; or DROP column_name ( column_name )</em>; or WITH options<p>The example associated to this statement is to ADD a new column to the table “species_table”, called “extinct” with type “boolean”.<pre><code>**ALTER** **TABLE** species_table **ADD** extinct **boolean**; -</code></pre><p>Another example is to DROP the column called “sex” from the table “species_table”.<pre><code>**ALTER** **TABLE** species_table **DROP** sex; -</code></pre><p>Finally, alter the comment with the WITH clause and set the comment to “All species records”.<pre><code>**ALTER** **TABLE** species_table **WITH** **comment**='All species records'; -</code></pre><p>These changes can be checked with the **DESCRIBE **statement:<pre><code>**DESCRIBE** **TABLE** species_table; -</code></pre><p><img src=https://lonami.dev/blog/ribw/nosql-databases-basic-operations-and-architecture/xebKPqkWkn97YVHpRVXZYWvRUfeRUyCH-vPDs67aFaEeU53YTRbDOFscOlAr.png> <em>DESCRIBE table</em><h3 id=dml-data-manipulation-language-1>DML (Data Manipulation Language)</h3><h4 id=update-data>Update data</h4><p>Updating a row is done using an **UPDATE **statement:<pre><code>**UPDATE** table_name -[ **USING** update_parameter ( **AND** update_parameter )* ] -**SET** assignment ( ',' assignment )* -**WHERE** where_clause -[ **IF** ( **EXISTS** | condition ( **AND** condition )*) ]; -</code></pre><p>Where the update_parameter is: ( TIMESTAMP | TTL) (integer | bind_marker)<p>It is important to mention that the **WHERE **clause is used to select the row to update and **must <strong>include ** all columns</strong> composing the <strong>PRIMARY KEY.</strong><p>We are going to test this statement updating the column “extinct” to true to the column with name ‘White monkey’.<pre><code>**UPDATE** species_table **SET** extinct = **true** **WHERE** species='White monkey'; -</code></pre><p><img src=https://lonami.dev/blog/ribw/nosql-databases-basic-operations-and-architecture/IcaCe6VEC5c0ZQIygz-CiclzFyt491u7xPMg2muJLR8grmqaiUzkoQsVCoHf.png> <em>SELECT statement</em><h2 id=delete>Delete</h2><h3 id=ddl-data-definition-language-2>DDL (Data Definition Language)</h3><h4 id=drop-keyspace>Drop keyspace</h4><p>Dropping a keyspace can be done using the **DROP KEYSPACE **statement:<pre><code>**DROP** KEYSPACE [ **IF** **EXISTS** ] keyspace_name; -</code></pre><p>For example, drop the keyspace called “test_keyspace_2” if it exists:<pre><code>**DROP** KEYSPACE **IF** **EXISTS** test_keyspace_2; -</code></pre><p>As this keyspace does not exists, this sentence will do nothing.<h4 id=drop-table>Drop table</h4><p>Dropping a table uses the **DROP TABLE **statement:<pre><code>**DROP** **TABLE** [ **IF** **EXISTS** ] table_name; -</code></pre><p>For example, drop the table called “species_2” if it exists:<pre><code>**DROP** **TABLE** **IF** **EXISTS** species_2; -</code></pre><p>As this table does not exists, this sentence will do nothing.<h4 id=truncate-table>Truncate (table)</h4><p>A table can be truncated using the **TRUNCATE **statement:<pre><code>**TRUNCATE** [ **TABLE** ] table_name; -</code></pre><p>Do not execute this command now, because if you do it, you will need to insert the previous data again.<p>Note: as tables are the only object that can be truncated the keyword TABLE can be omitted.<p><img src=https://lonami.dev/blog/ribw/nosql-databases-basic-operations-and-architecture/FOkhfpxlWFQCzcdfeWxLTy7wx5inDv0xwVeVhE79Pqtk3yYzWsZJnz_SBhUi.png> <em>TRUNCATE statement</em><h3 id=dml-data-manipulation-language-2>DML (Data Manipulation Language)</h3><h4 id=delete-data>Delete data</h4><p>Deleting rows or parts of rows uses the **DELETE **statement:<pre><code>**DELETE** [ simple_selection ( ',' simple_selection ) ] - **FROM** table_name - [ **USING** update_parameter ( **AND** update_parameter )* ] - **WHERE** where_clause - [ **IF** ( **EXISTS** | condition ( **AND** condition )*) ] -</code></pre><p>Now we are going to delete the value of the column “average_size” from “Cloned Sheep”.<pre><code>**DELETE** average_size **FROM** species_table **WHERE** species = 'Cloned Sheep'; -</code></pre><p><img src=https://lonami.dev/blog/ribw/nosql-databases-basic-operations-and-architecture/CyuQokVL5J9TAelq-WEWhNl6kFtbIYs0R1AeU5NX4EkG-YQI81mNHdnf2yWN.png> <em>DELETE value statement</em><p>And we are going to delete the same row as mentioned before.<pre><code>**DELETE** **FROM** species_table **WHERE** species = 'Cloned Sheep'; -</code></pre><p><img src=https://lonami.dev/blog/ribw/nosql-databases-basic-operations-and-architecture/jvQ5cXJ5GTVQ6giVhBEpPJmrJw-zwKKyB9nsTm5PRcGSTzkmh-WO4kTeuLpB.png> <em>DELETE row statement</em><h2 id=batch>Batch</h2><p>Multiple <strong>INSERT</strong>, **UPDATE **and **DELETE **can be executed in a <strong>single statement</strong> by grouping them through a **BATCH **statement.<pre><code>**BEGIN** [ UNLOGGED | COUNTER ] BATCH - [ **USING** update_parameter ( **AND** update_parameter )* ] - modification_statement ( ';' modification_statement )* - APPLY BATCH; -</code></pre><p>Where modification_statement can be a insert_statement or an update_statement or a delete_statement.<ul><li>**UNLOGGED **means that either all operations in a batch eventually complete or none will.<li><strong>COUNTER</strong> means that the updates are not idempotent, so each time we execute the updates in a batch, we will have different results. For example:</ul><pre><code>**BEGIN** BATCH - **INSERT** **INTO** species_table (species, common_name, population, average_size, extinct) - **VALUES** ('Blue Shark', 'Tiburillo', 30, 10, **false**); - **INSERT** **INTO** species_table (species, common_name, population, average_size, extinct) - **VALUES** ('Cloned sheep', 'Dolly the Sheep', 1, 1, **true**); - **UPDATE** species_table **SET** population = 2 **WHERE** species='Cloned sheep'; - **DELETE** **FROM** species_table **WHERE** species = 'White monkey'; -APPLY BATCH; -</code></pre><p><img src=https://lonami.dev/blog/ribw/nosql-databases-basic-operations-and-architecture/EL9Dac26o0FqkVoeAKmopEKQe0wWq-xYI14b9RzGxtUkFJA3i2eTiR6qkuuJ.png> <em>BATCH statement</em><h2 id=index>Index</h2><p>CQL support creating secondary indexes on tables, allowing queries on the table to use those indexes.<p>**Creating **a secondary index on a table uses the **CREATE INDEX **statement:<pre><code>**CREATE** [ CUSTOM ] **INDEX** [ **IF** **NOT** **EXISTS** ] [ index_name ] - **ON** table_name '(' index_identifier ')' - [ **USING** string [ **WITH** OPTIONS = map_literal ] ]; -</code></pre><p>For example we are going to create a index called “population_idx” that is related to the column “population” in the table “species_table”.<pre><code>**CREATE** **INDEX** population_idx **ON** species_table (population); -</code></pre><p>**Dropping **a secondary index uses the <strong>DROP INDEX</strong> statement:<pre><code>**DROP** **INDEX** [ **IF** **EXISTS** ] index_name; -</code></pre><p>Now, we are going to drop the previous index:<pre><code>**DROP** **INDEX** **IF** **EXISTS** population_idx; -</code></pre><h2 id=references>References</h2><ul><li><a href=https://cassandra.apache.org/doc/latest/cql/ddl.html>Cassandra CQL</a><li><a href=https://techdifferences.com/difference-between-ddl-and-dml-in-dbms.html>Differences between DML and DDL</a><li><a href=https://docs.datastax.com/en/dse/5.1/cql/cql/cql_reference/cqlReferenceTOC.html>Datastax CQL</a><li><a href=https://cassandra.apache.org/doc/latest/cql/types.html#grammar-token-cql-type>Cassandra CQL Types</a><li><a href=https://cassandra.apache.org/doc/latest/cql/indexes.html>Cassandra Index</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Privado: NoSQL evaluation | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Privado: NoSQL evaluation</h1><div class=time><p>2020-03-16T00:00:15+00:00<p>last updated 2020-03-27T11:22:45+00:00</div><p>I have decided to evaluate Classmate‘s post and Classmate‘s post, because they review databases I have not seen or used before, and I think it would be interesting to see new ones.<p>The evaluation is based on the requirements defined by Trabajos en grupo sobre Bases de Datos NoSQL:<blockquote><p><strong>1ª entrada:</strong> Descripción de la finalidad de la tecnología y cómo funciona o trabaja la BD NoSQL, sus características, la arista que ocupa en el Teorema CAP, de dónde se descarga, y cómo se instala.</blockquote><p>-- Teacher<h2 id=classmate-s-evaluation>Classmate’s evaluation</h2><p><strong>Grading: A.</strong><p>The post I have evaluated is BB.DD. NoSQL: Voldemort 1ª Fase.<p>The post doesn’t start very well, because the first sentence has (emphasis mine):<blockquote><p>En él repasaremos en qué consiste <strong>MongoDB</strong>, sus características, y cómo se instala, entre otros.</blockquote><p>-- Classmate<p>…yet the post is about Voldemort!<p>The post does detail how it works, its architecture, corner in the CAP theorem, download and installation.<p>I have graded the post with A because I think it meets all the requirements, even if they slipped a bit in the beginning.<h2 id=classmate-s-evaluation-1>Classmate’s evaluation</h2><p><strong>Grading: A.</strong><p>The post I have evaluted is Raven.<p>They have done a good job describing the project’s goals, corner in the CAP theorem, download, and provide an extensive installation section.<p>They don’t seem to use some of WordPress features, such as lists, but otherwise the post is good and deserves an A grading.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Privado: PC-Crawler evaluation 2 | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Privado: PC-Crawler evaluation 2</h1><div class=time><p>2020-03-16T01:00:47+00:00<p>last updated 2020-03-28T10:29:49+00:00</div><p>As the student <code>a(i)</code> where <code>i = 9</code>, I have been assigned to evaluate students <code>a(i - 1)</code> and <code>a(i - 2)</code>, these being:<ul><li>a08: Classmate (username)<li>a07: Classmate (username)</ul><p>The evaluation is done according to the criteria described in Segunda entrega del PC-Crawler.<h2 id=classmate-s-evaluation>Classmate’s evaluation</h2><p><strong>Grading: A.</strong><p>This is the evaluation of Crawler – Thesauro.<p>It’s a well-written post, properly using WordPress code blocks, and they explain the process of improving the code and what it does. Because there are no noticeable issues with the post, they get the highest grading.<h2 id=classmate-s-evaluation-1>Classmate’s evaluation</h2><p><strong>Grading: B.</strong><p>This is the evaluation of Actividad 2-Crawler.<p>They start with an introduction on what they will do.<p>Next, they show the code they have written, also describing what it does, although they don’t explain <em>why</em> they chose the data structures they used.<p>The style of the code leaves a lot to be desired, and they should have embedded the code in the post instead of taking screenshots. People that rely on screen readers will not be able to see the code.<p>I have graded them B and not A for this last reason.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Privado: PC-Crawler evaluation | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Privado: PC-Crawler evaluation</h1><div class=time><p>2020-03-04T00:00:23+00:00<p>last updated 2020-03-18T09:39:27+00:00</div><p>As the student <code>a(i)</code> where <code>i = 9</code>, I have been assigned to evaluate students <code>a(i + 3)</code> and <code>a(i + 4)</code>, these being:<ul><li>a12: Classmate (username)<li>a13: Classmate (username)</ul><h2 id=classmate-s-evaluation>Classmate’s evaluation</h2><p><strong>Grading: B.</strong><p>I think they mix up a bit their considerations with program usage and how it works, not justifying why the considerations are the ones they chose, or what the alternatives would be.<p>The implementation notes are quite well-written. Even someone without knowledge of Java’s syntax can read the notes and more or less make sense of what’s going on, with the relevant code excerpts on each section.<p>Implementation-wise, some methods could definitely use some improvement:<ul><li><code>esExtensionTextual</code> is overly complicated. It could use a <code>for</code> loop and Java’s <code>String.endsWith</code>.<li><code>calcularFrecuencia</code> has quite some duplication (e.g. <code>this.getFicherosYDirectorios().remove(0)</code>) and could definitely be cleaned up.</ul><p>However, all the desired functionality is implemented.<p>Style-wise, some of the newlines and avoiding braces on <code>if</code> and <code>while</code> could be changed to improve the readability.<p>The post is written in Spanish, but uses some words that don’t translate well («remover» could better be said as «eliminar» or «quitar»).<h2 id=classmate-s-evaluation-1>Classmate’s evaluation</h2><p><strong>Grading: B.</strong><p>Their post starts with an explanation on what a crawler is, common uses for them, and what type of crawler they will be developing. This is a very good start. Regarding the post style, it seems they are not properly using some of WordPress features, such as lists, and instead rely on paragraphs with special characters prefixing each list item.<p>The post also contains some details on how to install the requirements to run the program, which can be very useful for someone not used to working with Java.<p>They do not explain their implementation and the filename of the download has a typo.<p>Implementation-wise, the code seems to be well-organized, into several packages and files, although the naming is a bit inconsistent. They even designed a GUI, which is quite impressive.<p>Some of the methods are documented, although the code inside them is not very commented, including missing rationale for the data structures chosen. There also seem to be several other unused main functions, which I’m unsure why they were kept.<p>However, all the desired functionality is implemented.<p>Similar to Classmate, the code style could be improved and settled on some standard, as well as making use of Java features such as <code>for</code> loops over iterators instead of manual loops.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Upgrading our Baby Crawler | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Upgrading our Baby Crawler</h1><div class=time><p>2020-03-11T00:00:07+00:00<p>last updated 2020-03-18T09:49:33+00:00</div><p>In our <a href=/blog/ribw/build-your-own-pc/>last post on this series</a>, we presented the code for our Personal Crawler. However, we didn’t quite explain what a crawler even is! We will use this moment to go a bit more in-depth, and make some upgrades to it.<h2 id=what-is-a-crawler>What is a Crawler?</h2><p>A crawler is a program whose job is to analyze documents and extract data from them. For example, search engines like <a href=http://duckduckgo.com/>DuckDuckGo</a>, <a href=https://bing.com/>Bing</a> or <a href=http://google.com/>Google</a> all have crawlers to analyze websites and build a database around them. They are some kind of «trackers», because they keep track of everything they find.<p>Their basic behaviour can be described as follows: given a starting list of URLs, follow them all and identify hyperlinks inside the documents. Add these to the list of links to follow, and repeat <em>ad infinitum</em>.<ul><li>This lets us create an index to quickly search across them all.<li>We can also identify broken links.<li>We can gather any other type of information that we found. Our crawler will work offline, within our own computer, scanning the text documents it finds on the root we tell it to scan.</ul><h2 id=design-decissions>Design Decissions</h2><ul><li>We will use Java. Its runtime is quite ubiquitous, so it should be able to run in virtually anywhere. The language is typed, which helps catch errors early on.<li>Our solution is iterative. While recursion can be seen as more elegants by some, iterative solutions are often more performant with less need for optimization.</ul><h2 id=requirements>Requirements</h2><p>If you don’t have Java installed yet, you can <a href=https://java.com/en/download/>Download Free Java Software</a> from Oracle’s site. To compile the code, the <a href=https://www.oracle.com/java/technologies/javase-jdk8-downloads.html>Java Development Kit</a> is also necessary.<p>We don’t depend on any other external libraries, for easier deployment and compilation.<h2 id=implementation>Implementation</h2><p>Because the code was getting pretty large, it has been split into several files, and we have also upgraded it to use a Graphical User Interface instead! We decided to use Swing, based on the Java tutorial <a href=https://docs.oracle.com/javase/tutorial/uiswing/>Creating a GUI With JFC/Swing</a>.<h3 id=app>App</h3><p>This file is the entry point of our application. Its job is to initialize the components, lay them out in the main panel, and connect the event handlers.<p>Most widgets are pretty standard, and are defined as class variables. However, some variables are notable. The <code>[DefaultTableModel](https://docs.oracle.com/javase/8/docs/api/javax/swing/table/DefaultTableModel.html)</code> is used because it allows to <a href=https://stackoverflow.com/a/22550106>dynamically add rows</a>, and we also have a <code>[SwingWorker](https://docs.oracle.com/javase/8/docs/api/javax/swing/SwingWorker.html)</code> subclass responsible for performing the word analysis (which is quite CPU intensive and should not be ran in the UI thread!).<p>There’s a few utility methods to ease some common operations, such as <code>updateStatus</code> which changes the status label in the main window, informing the user of the latest changes.<h3 id=thesaurus>Thesaurus</h3><p>A thesaurus is a collection of words or terms used to represent concepts. In literature this is commonly known as a dictionary.<p>On the subject of this project, we are using a thesaurus based on how relevant is a word for the meaning of a sentence, filtering out those that barely give us any information.<p>This file contains a simple thesaurus implementation, which can trivially be used as a normal or inverted thesaurus. However, we only treat it as inverted, and its job is loading itself and determining if words are valid or should otherwise be ignored.<h3 id=utils>Utils</h3><p>Several utility functions used across the codebase.<h3 id=wordmap>WordMap</h3><p>This file is the important one, and its implementation hasn’t changed much since our last post. Instances of a word map contain… wait for it… a map of words! It stores the mapping <code>word → count</code> in memory, and offers methods to query the count of a word or iterate over the word count entries.<p>It can be loaded from cache or told to analyze a root path. Once an instance is created, additional files could be analyzed one by one if desired.<h2 id=download>Download</h2><p>The code was getting a bit too large to embed it within the blog post itself, so instead you can download it as a<code>.zip</code> file.<p><em>download removed</em></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,11 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> What is ElasticSearch and why should you care? | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>What is ElasticSearch and why should you care?</h1><div class=time><p>2020-03-18T02:00:00+00:00<p>last updated 2020-03-27T11:04:45+00:00</div><p>ElasticSearch is a giant search index with powerful analytics capabilities. It’s like a database and search engine on steroids, really easy and fast to get up and running. One can think of it as your own Google, a search engine with analytics.<p>ElasticSearch is rich, stable, performs well, is well maintained, and able to scale to petabytes of any kind of data, whether it’s structured, semi-structured or not at all. It’s cost-effective and can be used to make business decisions.<p>Or, described in 10 seconds:<blockquote><p>Schema-free, REST & JSON based distributed document store Open source: Apache License 2.0 Zero configuration</blockquote><p>-- Alex Reelsen<h2 id=basic-capabilities>Basic capabilities</h2><p>ElasticSearch lets you ask questions about your data, not just make queries. You may think SQL can do this too, but what’s important is making a pipeline of facets, and feed the results from query to query.<p>Instead of changing your data, you can be flexible with your questions with no need to re-index it every time the questions change.<p>ElasticSearch is not just to search for full-text data, either. It can search for structured data and return more than just the results. It also yields additional data, such as ranking, highlights, and allows for pagination.<p>It doesn’t take a lot of configuration to get running, either, which can be a good boost on productivity.<h2 id=how-does-it-work>How does it work?</h2><p>ElasticSearch depends on Java, and can work in a distributed cluster if you execute multiple instances. Data will be replicated and sharded as needed. The current version at the time of writing is 7.6.1, and it’s being developed fast!<p>It also has support for plugins, with an ever-growing ecosystem and integration on many programming languages. Tools around it are being built around it, too, like Kibana which helps you visualize your data.<p>The way you use it is through a JSON API, served over HTTP/S.<h2 id=how-can-i-use-it>How can I use it?</h2><p><a href=https://www.elastic.co/downloads/>You can try ElasticSearch out for free on Elastic Cloud</a>, however, it can also be <a href=https://www.elastic.co/downloads/elasticsearch>downloaded and ran offline</a>, which is what we’ll do. Download the file corresponding to your operating system, unzip it, and execute the binary. Running it is as simple as that!<p>Now you can make queries to it over HTTP, with for example <code>curl</code>:<pre><code>curl -X PUT localhost:9200/orders/order/1 -d ' -{ - "created_at": "2013/09/05 15:45:10", - "items": [ - { - name: "HD Monitor" - } - ], - "total": 249.95 -}' -</code></pre><p>This will create a new order with some information, such as when it was created, what items it contains, and the total cost of the order.<p>You can then query or filter as needed, script it or even create statistics.<h2 id=references>References</h2><ul><li><a href=https://youtu.be/sKnkQSec1U0>YouTube – What is Elasticsearch?</a><li><a href=https://youtu.be/yWNiRC_hUAw>YouTube – GOTO 2013 • Elasticsearch – Beyond Full-text Search • Alex Reelsen</a><li><a href=https://www.elastic.co/kibana>Kibana – Your window into the Elastic Stack</a><li><a href=https://www.elastic.co/guide/index.html>Elastic Stack and Product Documentation</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Tips for Outpost | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Tips for Outpost</h1><div class=time><p>2020-05-10<p>last updated 2020-05-22</div><p><a href=https://store.steampowered.com/app/1127110/Outpost/>Outpost</a> is a fun little game by Open Mid Interactive that has popped in recently in my recommended section of Steam, and I decided to give it a try.<p>It's a fun tower-defense game with progression, different graphics and random world generation which makes it quite fun for a few hours. In this post I want to talk about some tips I found useful to get past night 50.<h2 id=build-pattern>Build Pattern</h2><p>At first, you may be inclined to design a checkerboard pattern like the following, where "C" is the Crystal shrine, "S" is a stone launcher and "B" is a booster:<p><img src=https://lonami.dev/blog/tips-outpost/outpost-bad-pattern.svg alt="Bad Outpost build pattern"><p>Indeed, this pattern will apply <strong>4</strong> boosts to every turret, but unfortunately, the other 4 slots of the booster are wasted! This is because boosters are able to power 8 different towers, and you really want to maximize that. Here's a better design:<p><img src=https://lonami.dev/blog/tips-outpost/outpost-good-pattern.svg alt="Good Outpost build pattern"><p>The shrine's tower does get boosted, but it's still not really worth it to boost it. This pattern works good, and it's really easy to tile: just repeat the same 3x3 pattern.<p>Nonetheless, we can do better. What if we applied multiple boosters to the same tower while still applying all 8 boosts?<p><img src=https://lonami.dev/blog/tips-outpost/outpost-best-pattern.svg alt="Best Outpost build pattern"><p>That's what peak performance looks like. You can actually apply multiple boosters to the same tower, and it works great.<p>Now, is it really worth it building anywhere except around the shrine? Not really. You never know where a boss will come from, so all sides need a lot of defense if you want to stand a chance.<p>The addition of traps in 1.6 is amazing. You want to build these outside your strong "core", mostly to slow the enemies down so your turrets have more time to finish them off. Don't waste boosters on the traps, and build them at a reasonable distance from the center (the sixth tile is a good spot):<p><img src=https://lonami.dev/blog/tips-outpost/outpost-trap-pattern.svg alt="Trap Outpost build pattern"><p>If you gather enough materials, you can build more trap and cannon layers outside, roughly at enough distance to slow them for enough duration until they reach the next layer of traps, and so on. Probably a single gap of "cannon, booster, cannon" is enough between trap layers, just not in the center where you need a lot of fire power.<h2 id=talents>Talents</h2><p>Talents are the way progression works in the game. Generally, after a run, you will have enough experience to upgrade nearly all talents of roughly the same tier. However, some are worth upgrading more than others (which provide basically no value).<p>The best ones to upgrade are:<ul><li>Starting supplies. Amazing to get good tools early.<li>Shrine shield. Very useful to hold against tough bosses.<li>Better buildings (cannon, boosters, bed and traps). They're a must to deal the most damage.<li>Better pickaxe. Stone is limited, so better make good use of it.<li>Better chests. They provide an insane amount of resources early.<li>Winter slow. Turrets will have more time to deal damage, it's perfect.<li>More time. Useful if you're running out, although generally you enter nights early after having a good core anyway.<li>More rocks. Similar to a better pickaxe, more stone is always better.</ul><p>Some decent ones:<ul><li>In-shrine turret. It's okay to get past the first night without building but not much beyond that.<li>Better axe and greaves. Great to save some energy and really nice quality of life to move around.<li>Tree growth. Normally there's enough trees for this not to be an issue but it can save some time gathering wood.<li>Wisps. They're half-decent since they can provide materials once you max out or max out expensive gear.</ul><p>Some okay ones:<ul><li>Extra XP while playing. Generally not needed due to the way XP scales per night, but can be a good boost.<li>Runestones. Not as reliable as chests but some can grant more energy per day.</ul><p>Some crap ones:<ul><li>Boosts for other seasons. I mean, winter is already the best, no use there.<li>Bow. The bow is very useless at the moment, it's not worth your experience.<li>More energy per bush. Not really worth hunting for bushes since you will have enough energy to do well.</ul><h2 id=turrets>Turrets</h2><p>Always build the highest tier, there's no point in anything lower than that. You will need to deal a lot of damage in a small area, which means space is a premium.<h2 id=boosters>Boosters</h2><p>If you're very early in the game, I recommend alternating both the flag and torch in a checkerboard pattern where the boosters should go in the pattern above. This way your towers will get extra speed and extra range, which works great.<p>When you're in mid-game (stone launchers, gears and campfires), I do not recommend using campfires. The issue is their range boost is way too long, and the turrets will miss quite a few shots. It's better to put all your power into fire speed for increased DPS, at least near the center. If you manage to build too far out and some of the turrets hardly ever shoot, you may put campfires there.<p>In end-game, of course alternate both of the highest tier upgrades. They are really good, and provide the best benefit / cost ratio.<h2 id=gathering-materials>Gathering Materials</h2><p>It is <strong>very</strong> important to use all your energy every day! Otherwise it will go to waste, and you will need a lot of materials.<p>As of 1.6, you can mine two things at once if they're close enough! I don't know if this is intended or a bug, but it sure is great.<p>Once you're in mid-game, your stone-based fort should stand pretty well against the nights on its own. After playing for a while you will notice, if your base can defend a boss, then it will have no issue carrying you through the nights until the next boss. You can (and should!) spend the nights gathering materials, but only when you're confident that the night won't run out.<p>Before the boss hits (every fifth night), come back to your base and use all of your materials. This is the next fort upgrade that will carry it the five next nights.<p>You may also speed up time during night, but make sure you use all your energy before hand. And also take care, in the current version of the game speeding up time only speeds up monster movement, not the fire rate or projectile speed of your turrets! This means they will miss more shots and can be pretty dangerous. If you're speeding up time, consider speeding it up for a little bit, then go back to normal until things are more calm, and repeat.<p>If you're in the end-game, try to rush for chests. They provide a huge amount of materials which is really helpful to upgrade all your tools early so you can make sure to get the most out of every rock left in the map.<p>In the end-game, after all stone has been collected, you don't really need to use all of your energy anymore. Just enough to have enough wood to build with the remaining stone. This will also be nice with the bow upgrades, which admitedly can get quite powerful, but it's best to have a strong fort first.<h2 id=season>Season</h2><p>In my opinion, winter is just the best of the seasons. You don't <em>really</em> need that much energy (it gets tiresome), or extra tree drops, or luck. Slower movement means your turrets will be able to shoot enemies for longer, dealing more damage over time, giving them more chance to take enemies out before they reach the shrine.<p>Feel free to re-roll the map a few times (play and exit, or even restart the game) until you get winter if you want to go for The Play.<h2 id=gear>Gear</h2><p>In my opinion, you really should rush for the best pickaxe you can afford. Stone is a limited resource that doesn't regrow like trees, so once you run out, it's over. Better to make the best use out of it with a good pickaxe!<p>You may also upgrade your greaves, we all known faster movement is a <em>really</em> nice quality of life improvement.<p>Of course, you will eventually upgrade your axe to chop wood (otherwise it's wasted energy, really), but it's not as much of a priority as the pickaxe.<p>Now, the bow is completely useless. Don't bother with it. Your energy is better spent gathering materials to build permanent turrets that deal constant damage while you're away, and the damage adds up with every extra turret you build.<p>With regards to items you carry (like sword, or helmet), look for these (from best to worst):<ul><li>Less minion life.<li>Chance to not consume energy.<li>+1 turret damage.<li>Extra energy.<li>+1 drop from trees or stones.<li>+1 free wood or stone per day.</ul><p>Less minion life, nothing to say. You will need it near end-game.<p>The chance to not consume energy is better the more energy you have. With a 25% chance not to consume energy, you can think of it as 1 extra energy for every 4 energy you have on average.<p>Turret damage is a tough one, it's <em>amazing</em> mid-game (it basically doubles your damage) but falls short once you unlock the cannon where you may prefer other items. Definitely recommended if you're getting started. You may even try to roll it on low tiers by dying on the second night, because it's that good.<p>Extra energy is really good, because it means you can get more materials before it gets too rough. Make sure you have built at least two beds in the first night! This extra energy will pay of for the many nights to come.<p>The problem with free wood or stone per day is that you have, often, five times as much energy per day. By this I mean you can get easily 5 stone every day, which means 5 extra stone, whereas the other would provide just 1 per night. On a good run, you will get around 50 free stone or 250 extra stone. It's a clear winner.<p>In end-game, more quality of life are revealing chests so that you can rush them early, if you like to hunt for them try to make better use of the slot.<h2 id=closing-words>Closing words</h2><p>I hope you enjoy the game as much as I do! Movement is sometimes janky and there's the occassional lag spikes, but despite this it should provide at least a few good hours of gameplay. Beware however a good run can take up to an hour!</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1719 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="220" - height="220" - viewBox="0 0 58.208332 58.208334" - version="1.1" - id="svg8" - inkscape:version="0.92.3 (2405546, 2018-03-11)" - sodipodi:docname="outpost-bad-pattern.svg"> - <defs - id="defs2" /> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="4.0000001" - inkscape:cx="48.149205" - inkscape:cy="127.57923" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - units="px" - inkscape:window-width="1366" - inkscape:window-height="739" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" /> - <metadata - id="metadata5"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title /> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-238.79165)"> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect815" - width="5.2916665" - height="5.2916665" - x="0" - y="291.70831" /> - <rect - y="291.70831" - x="5.2916665" - height="5.2916665" - width="5.2916665" - id="rect817" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect819" - width="5.2916665" - height="5.2916665" - x="10.583333" - y="291.70831" /> - <rect - y="291.70831" - x="15.875" - height="5.2916665" - width="5.2916665" - id="rect821" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect823" - width="5.2916665" - height="5.2916665" - x="21.166666" - y="291.70831" /> - <rect - y="291.70831" - x="26.458332" - height="5.2916665" - width="5.2916665" - id="rect825" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect827" - width="5.2916665" - height="5.2916665" - x="31.749998" - y="291.70831" /> - <rect - y="291.70831" - x="37.041664" - height="5.2916665" - width="5.2916665" - id="rect829" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect831" - width="5.2916665" - height="5.2916665" - x="42.333328" - y="291.70831" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect835" - width="5.2916665" - height="5.2916665" - x="47.624996" - y="291.70831" /> - <rect - y="291.70831" - x="52.91666" - height="5.2916665" - width="5.2916665" - id="rect837" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="286.41666" - x="0" - height="5.2916665" - width="5.2916665" - id="rect839" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect841" - width="5.2916665" - height="5.2916665" - x="5.2916665" - y="286.41666" /> - <rect - y="286.41666" - x="10.583333" - height="5.2916665" - width="5.2916665" - id="rect843" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect845" - width="5.2916665" - height="5.2916665" - x="15.875" - y="286.41666" /> - <rect - y="286.41666" - x="21.166666" - height="5.2916665" - width="5.2916665" - id="rect847" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect849" - width="5.2916665" - height="5.2916665" - x="26.458332" - y="286.41666" /> - <rect - y="286.41666" - x="31.749998" - height="5.2916665" - width="5.2916665" - id="rect851" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect853" - width="5.2916665" - height="5.2916665" - x="37.041664" - y="286.41666" /> - <rect - y="286.41666" - x="42.333328" - height="5.2916665" - width="5.2916665" - id="rect855" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="286.41666" - x="47.624996" - height="5.2916665" - width="5.2916665" - id="rect857" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect859" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="286.41666" /> - <rect - y="281.125" - x="0" - height="5.2916665" - width="5.2916665" - id="rect861" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect863" - width="5.2916665" - height="5.2916665" - x="5.2916665" - y="281.125" /> - <rect - y="281.125" - x="47.624996" - height="5.2916665" - width="5.2916665" - id="rect879" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect881" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="281.125" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect883" - width="5.2916665" - height="5.2916665" - x="0" - y="275.83334" /> - <rect - y="275.83334" - x="5.2916665" - height="5.2916665" - width="5.2916665" - id="rect885" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect901" - width="5.2916665" - height="5.2916665" - x="47.624996" - y="275.83334" /> - <rect - y="275.83334" - x="52.91666" - height="5.2916665" - width="5.2916665" - id="rect903" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="270.54169" - x="0" - height="5.2916665" - width="5.2916665" - id="rect905" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect907" - width="5.2916665" - height="5.2916665" - x="5.2916665" - y="270.54169" /> - <rect - y="270.54169" - x="47.624996" - height="5.2916665" - width="5.2916665" - id="rect923" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect925" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="270.54169" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect927" - width="5.2916665" - height="5.2916665" - x="0" - y="265.25003" /> - <rect - y="265.25003" - x="5.2916665" - height="5.2916665" - width="5.2916665" - id="rect929" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect945" - width="5.2916665" - height="5.2916665" - x="47.624996" - y="265.25003" /> - <rect - y="265.25003" - x="52.91666" - height="5.2916665" - width="5.2916665" - id="rect947" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect949" - width="5.2916665" - height="5.2916665" - x="0" - y="259.95837" /> - <rect - y="259.95837" - x="5.2916665" - height="5.2916665" - width="5.2916665" - id="rect951" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect967" - width="5.2916665" - height="5.2916665" - x="47.624996" - y="259.95837" /> - <rect - y="259.95837" - x="52.91666" - height="5.2916665" - width="5.2916665" - id="rect969" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="254.66672" - x="0" - height="5.2916665" - width="5.2916665" - id="rect971" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect973" - width="5.2916665" - height="5.2916665" - x="5.2916665" - y="254.66672" /> - <rect - y="254.66672" - x="47.624996" - height="5.2916665" - width="5.2916665" - id="rect989" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect991" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="254.66672" /> - <rect - y="249.37506" - x="0" - height="5.2916665" - width="5.2916665" - id="rect993" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect995" - width="5.2916665" - height="5.2916665" - x="5.2916665" - y="249.37506" /> - <rect - y="249.37506" - x="47.624996" - height="5.2916665" - width="5.2916665" - id="rect1011" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1013" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="249.37506" /> - <rect - y="244.0834" - x="0" - height="5.2916665" - width="5.2916665" - id="rect1015" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1017" - width="5.2916665" - height="5.2916665" - x="5.2916665" - y="244.0834" /> - <rect - y="244.0834" - x="10.583333" - height="5.2916665" - width="5.2916665" - id="rect1019" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1021" - width="5.2916665" - height="5.2916665" - x="15.875" - y="244.0834" /> - <rect - y="244.0834" - x="21.166666" - height="5.2916665" - width="5.2916665" - id="rect1023" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1025" - width="5.2916665" - height="5.2916665" - x="26.458332" - y="244.0834" /> - <rect - y="244.0834" - x="31.749998" - height="5.2916665" - width="5.2916665" - id="rect1027" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1029" - width="5.2916665" - height="5.2916665" - x="37.041664" - y="244.0834" /> - <rect - y="244.0834" - x="42.333328" - height="5.2916665" - width="5.2916665" - id="rect1031" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="244.0834" - x="47.624996" - height="5.2916665" - width="5.2916665" - id="rect1033" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1035" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="244.0834" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1037" - width="5.2916665" - height="5.2916665" - x="0" - y="238.79175" /> - <rect - y="238.79175" - x="5.2916665" - height="5.2916665" - width="5.2916665" - id="rect1039" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1041" - width="5.2916665" - height="5.2916665" - x="10.583333" - y="238.79175" /> - <rect - y="238.79175" - x="15.875" - height="5.2916665" - width="5.2916665" - id="rect1043" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1045" - width="5.2916665" - height="5.2916665" - x="21.166666" - y="238.79175" /> - <rect - y="238.79175" - x="26.458332" - height="5.2916665" - width="5.2916665" - id="rect1047" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1049" - width="5.2916665" - height="5.2916665" - x="31.749998" - y="238.79175" /> - <rect - y="238.79175" - x="37.041664" - height="5.2916665" - width="5.2916665" - id="rect1051" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1053" - width="5.2916665" - height="5.2916665" - x="42.333328" - y="238.79175" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1055" - width="5.2916665" - height="5.2916665" - x="47.624996" - y="238.79175" /> - <rect - y="238.79175" - x="52.91666" - height="5.2916665" - width="5.2916665" - id="rect1057" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <g - id="g1106"> - <rect - style="opacity:1;fill:#008080;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect937" - width="5.2916665" - height="5.2916665" - x="26.458332" - y="265.25003" /> - <text - id="text1101" - y="269.30844" - x="27.745592" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332;" - xml:space="preserve"><tspan - style="stroke-width:0.26458332;fill:#ffffff;" - y="269.30844" - x="27.745592" - id="tspan1099" - sodipodi:role="line">C</tspan></text> - </g> - <g - id="g1114" - transform="translate(-42.333332,26.458286)"> - <rect - y="265.25003" - x="26.458332" - height="5.2916665" - width="5.2916665" - id="rect1108" - style="opacity:1;fill:#008080;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="27.745592" - y="269.30844" - id="text1112"><tspan - sodipodi:role="line" - id="tspan1110" - x="27.745592" - y="269.30844" - style="fill:#ffffff;stroke-width:0.26458332">C</tspan></text> - </g> - <g - id="g1165" - transform="translate(35.6385,-11.263733)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1151" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1155" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1153" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(46.221833,-11.263733)" - id="g1173"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1167" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1171"><tspan - sodipodi:role="line" - id="tspan1169" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1181" - transform="translate(46.221832,-0.68041992)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1175" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1179" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1177" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(35.6385,-0.68041992)" - id="g1189"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1183" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1187"><tspan - sodipodi:role="line" - id="tspan1185" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(25.055168,-21.847046)" - id="g1197"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1191" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1195"><tspan - sodipodi:role="line" - id="tspan1193" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1205" - transform="translate(35.638501,-21.847046)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1199" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1203" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1201" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1213" - transform="translate(46.221832,-21.847046)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1207" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1211" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1209" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(56.805165,-21.847046)" - id="g1221"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1215" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1219"><tspan - sodipodi:role="line" - id="tspan1217" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(25.055168,-0.68041992)" - id="g1229"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1223" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1227"><tspan - sodipodi:role="line" - id="tspan1225" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1237" - transform="translate(25.055168,9.9028932)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1231" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1235" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1233" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(25.055169,-11.263733)" - id="g1245"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1239" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1243"><tspan - sodipodi:role="line" - id="tspan1241" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1253" - transform="translate(56.805163,-0.68041983)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1247" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1251" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1249" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(56.805163,9.9028933)" - id="g1261"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1255" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1259"><tspan - sodipodi:role="line" - id="tspan1257" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1269" - transform="translate(56.805164,-11.263733)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1263" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1267" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1265" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(46.221832,9.9028931)" - id="g1277"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1271" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1275"><tspan - sodipodi:role="line" - id="tspan1273" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1285" - transform="translate(35.6385,9.9028931)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1279" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1283" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1281" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1345" - transform="translate(30.346834,-16.555389)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1339" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1343" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1341" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(40.930167,-16.555389)" - id="g1353"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1347" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1351"><tspan - sodipodi:role="line" - id="tspan1349" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(51.513498,-16.555389)" - id="g1361"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1355" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1359"><tspan - sodipodi:role="line" - id="tspan1357" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(30.346833,-5.9720764)" - id="g1369"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1363" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1367"><tspan - sodipodi:role="line" - id="tspan1365" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1385" - transform="translate(51.513497,-5.9720764)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1379" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1383" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1381" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1393" - transform="translate(30.346833,4.6112366)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1387" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1391" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1389" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(40.930166,4.6112366)" - id="g1401"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1395" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1399"><tspan - sodipodi:role="line" - id="tspan1397" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(51.513497,4.6112366)" - id="g1409"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1403" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1407"><tspan - sodipodi:role="line" - id="tspan1405" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1458" - transform="translate(-1.4031647,9.9028864)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1452" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1456" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1454" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1474" - transform="translate(-2.8063296,10.797967)"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1460" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1464" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1462" - sodipodi:role="line">B</tspan></text> - </g> - <g - id="g1482" - transform="translate(28.94367,-26.243621)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1476" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1480"><tspan - sodipodi:role="line" - id="tspan1478" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(23.652005,-20.951965)" - id="g1490"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1484" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1488" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1486" - sodipodi:role="line">B</tspan></text> - </g> - <g - id="g1498" - transform="translate(34.235336,-20.951965)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1492" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1496"><tspan - sodipodi:role="line" - id="tspan1494" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(34.235335,-10.368652)" - id="g1506"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1500" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1504" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1502" - sodipodi:role="line">B</tspan></text> - </g> - <g - id="g1514" - transform="translate(28.943669,-15.660309)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1508" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1512"><tspan - sodipodi:role="line" - id="tspan1510" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(23.652005,-10.368652)" - id="g1522"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1516" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1520" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1518" - sodipodi:role="line">B</tspan></text> - </g> - <g - id="g1530" - transform="translate(28.943669,-5.0769958)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1524" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1528"><tspan - sodipodi:role="line" - id="tspan1526" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(23.652003,0.21466064)" - id="g1538"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1532" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1536" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1534" - sodipodi:role="line">B</tspan></text> - </g> - <g - id="g1546" - transform="translate(28.943669,5.5063172)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1540" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1544"><tspan - sodipodi:role="line" - id="tspan1542" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(34.235336,0.21466065)" - id="g1554"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1548" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1552" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1550" - sodipodi:role="line">B</tspan></text> - </g> - <g - id="g1562" - transform="translate(39.527002,-5.0769958)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1556" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1560"><tspan - sodipodi:role="line" - id="tspan1558" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(39.527002,5.5063171)" - id="g1570"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1564" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1568" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1566" - sodipodi:role="line">B</tspan></text> - </g> - <g - id="g1578" - transform="translate(44.818666,0.21466067)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1572" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1576"><tspan - sodipodi:role="line" - id="tspan1574" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(50.110332,5.5063174)" - id="g1586"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1580" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1584" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1582" - sodipodi:role="line">B</tspan></text> - </g> - <g - id="g1594" - transform="translate(55.401999,0.21466074)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1588" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1592"><tspan - sodipodi:role="line" - id="tspan1590" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(50.110334,-5.0769958)" - id="g1602"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1596" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1600" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1598" - sodipodi:role="line">B</tspan></text> - </g> - <g - id="g1610" - transform="translate(55.401999,-10.368652)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1604" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1608"><tspan - sodipodi:role="line" - id="tspan1606" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(44.818669,-10.368652)" - id="g1618"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1612" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1616" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1614" - sodipodi:role="line">B</tspan></text> - </g> - <g - id="g1626" - transform="translate(39.527003,-15.660309)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1620" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1624"><tspan - sodipodi:role="line" - id="tspan1622" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(50.110335,-15.660309)" - id="g1634"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1628" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1632" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1630" - sodipodi:role="line">B</tspan></text> - </g> - <g - id="g1642" - transform="translate(44.818667,-20.951965)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1636" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1640"><tspan - sodipodi:role="line" - id="tspan1638" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(55.402,-20.951965)" - id="g1650"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1644" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1648" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1646" - sodipodi:role="line">B</tspan></text> - </g> - <g - id="g1658" - transform="translate(50.110334,-26.243607)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1652" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1656"><tspan - sodipodi:role="line" - id="tspan1654" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(39.527003,-26.243622)" - id="g1666"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1660" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1664" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1662" - sodipodi:role="line">B</tspan></text> - </g> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1680" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="275.83334" /> - <rect - style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.46266627;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1093" - width="10.583333" - height="10.583333" - x="10.583333" - y="249.37508" /> - </g> -</svg>
@@ -1,1715 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="220" - height="220" - viewBox="0 0 58.208332 58.208334" - version="1.1" - id="svg8" - inkscape:version="0.92.3 (2405546, 2018-03-11)" - sodipodi:docname="outpos-best-pattern.svg"> - <defs - id="defs2" /> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="2.0000001" - inkscape:cx="132.5362" - inkscape:cy="94.229796" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - units="px" - inkscape:window-width="1366" - inkscape:window-height="739" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" /> - <metadata - id="metadata5"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title /> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-238.79165)"> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect815" - width="5.2916665" - height="5.2916665" - x="0" - y="291.70831" /> - <rect - y="291.70831" - x="5.2916665" - height="5.2916665" - width="5.2916665" - id="rect817" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect819" - width="5.2916665" - height="5.2916665" - x="10.583333" - y="291.70831" /> - <rect - y="291.70831" - x="15.875" - height="5.2916665" - width="5.2916665" - id="rect821" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect823" - width="5.2916665" - height="5.2916665" - x="21.166666" - y="291.70831" /> - <rect - y="291.70831" - x="26.458332" - height="5.2916665" - width="5.2916665" - id="rect825" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect827" - width="5.2916665" - height="5.2916665" - x="31.749998" - y="291.70831" /> - <rect - y="291.70831" - x="37.041664" - height="5.2916665" - width="5.2916665" - id="rect829" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect831" - width="5.2916665" - height="5.2916665" - x="42.333328" - y="291.70831" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect835" - width="5.2916665" - height="5.2916665" - x="47.624996" - y="291.70831" /> - <rect - y="291.70831" - x="52.91666" - height="5.2916665" - width="5.2916665" - id="rect837" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="286.41666" - x="0" - height="5.2916665" - width="5.2916665" - id="rect839" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="286.41666" - x="21.166666" - height="5.2916665" - width="5.2916665" - id="rect847" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect849" - width="5.2916665" - height="5.2916665" - x="26.458332" - y="286.41666" /> - <rect - y="286.41666" - x="31.749998" - height="5.2916665" - width="5.2916665" - id="rect851" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect859" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="286.41666" /> - <rect - y="281.125" - x="0" - height="5.2916665" - width="5.2916665" - id="rect861" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect881" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="281.125" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect883" - width="5.2916665" - height="5.2916665" - x="0" - y="275.83334" /> - <rect - y="275.83334" - x="52.91666" - height="5.2916665" - width="5.2916665" - id="rect903" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="270.54169" - x="0" - height="5.2916665" - width="5.2916665" - id="rect905" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect907" - width="5.2916665" - height="5.2916665" - x="5.2916665" - y="270.54169" /> - <rect - y="270.54169" - x="47.624996" - height="5.2916665" - width="5.2916665" - id="rect923" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect925" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="270.54169" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect927" - width="5.2916665" - height="5.2916665" - x="0" - y="265.25003" /> - <rect - y="265.25003" - x="5.2916665" - height="5.2916665" - width="5.2916665" - id="rect929" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect945" - width="5.2916665" - height="5.2916665" - x="47.624996" - y="265.25003" /> - <rect - y="265.25003" - x="52.91666" - height="5.2916665" - width="5.2916665" - id="rect947" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect949" - width="5.2916665" - height="5.2916665" - x="0" - y="259.95837" /> - <rect - y="259.95837" - x="5.2916665" - height="5.2916665" - width="5.2916665" - id="rect951" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect967" - width="5.2916665" - height="5.2916665" - x="47.624996" - y="259.95837" /> - <rect - y="259.95837" - x="52.91666" - height="5.2916665" - width="5.2916665" - id="rect969" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="254.66672" - x="0" - height="5.2916665" - width="5.2916665" - id="rect971" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect991" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="254.66672" /> - <rect - y="249.37506" - x="0" - height="5.2916665" - width="5.2916665" - id="rect993" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1013" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="249.37506" /> - <rect - y="244.0834" - x="0" - height="5.2916665" - width="5.2916665" - id="rect1015" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="244.0834" - x="21.166666" - height="5.2916665" - width="5.2916665" - id="rect1023" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1025" - width="5.2916665" - height="5.2916665" - x="26.458332" - y="244.0834" /> - <rect - y="244.0834" - x="31.749998" - height="5.2916665" - width="5.2916665" - id="rect1027" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1035" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="244.0834" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1037" - width="5.2916665" - height="5.2916665" - x="0" - y="238.79175" /> - <rect - y="238.79175" - x="5.2916665" - height="5.2916665" - width="5.2916665" - id="rect1039" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1041" - width="5.2916665" - height="5.2916665" - x="10.583333" - y="238.79175" /> - <rect - y="238.79175" - x="15.875" - height="5.2916665" - width="5.2916665" - id="rect1043" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1045" - width="5.2916665" - height="5.2916665" - x="21.166666" - y="238.79175" /> - <rect - y="238.79175" - x="26.458332" - height="5.2916665" - width="5.2916665" - id="rect1047" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1049" - width="5.2916665" - height="5.2916665" - x="31.749998" - y="238.79175" /> - <rect - y="238.79175" - x="37.041664" - height="5.2916665" - width="5.2916665" - id="rect1051" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1053" - width="5.2916665" - height="5.2916665" - x="42.333328" - y="238.79175" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1055" - width="5.2916665" - height="5.2916665" - x="47.624996" - y="238.79175" /> - <rect - y="238.79175" - x="52.91666" - height="5.2916665" - width="5.2916665" - id="rect1057" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <g - id="g1106"> - <rect - style="opacity:1;fill:#008080;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect937" - width="5.2916665" - height="5.2916665" - x="26.458332" - y="265.25003" /> - <text - id="text1101" - y="269.30844" - x="27.745592" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332;" - xml:space="preserve"><tspan - style="stroke-width:0.26458332;fill:#ffffff;" - y="269.30844" - x="27.745592" - id="tspan1099" - sodipodi:role="line">C</tspan></text> - </g> - <g - id="g1114" - transform="translate(-42.333332,26.458286)"> - <rect - y="265.25003" - x="26.458332" - height="5.2916665" - width="5.2916665" - id="rect1108" - style="opacity:1;fill:#008080;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="27.745592" - y="269.30844" - id="text1112"><tspan - sodipodi:role="line" - id="tspan1110" - x="27.745592" - y="269.30844" - style="fill:#ffffff;stroke-width:0.26458332">C</tspan></text> - </g> - <g - id="g1165" - transform="translate(35.6385,-11.263733)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1151" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1155" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1153" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1458" - transform="translate(-1.4031647,9.9028864)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1452" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1456" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1454" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1474" - transform="translate(-2.8063296,10.797967)"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1460" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1464" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1462" - sodipodi:role="line">B</tspan></text> - </g> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1680" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="275.83334" /> - <g - id="g1147" - transform="translate(40.930166,-11.263733)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1141" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1145" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1143" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(39.527002,-20.951965)" - id="g1415"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1409" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1413" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1411" - sodipodi:role="line">B</tspan></text> - </g> - <g - transform="translate(35.6385,-16.555389)" - id="g1431"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1425" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1429"><tspan - sodipodi:role="line" - id="tspan1427" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1439" - transform="translate(35.6385,-21.847031)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1433" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1437" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1435" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(40.930166,-21.847031)" - id="g1447"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1441" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1445"><tspan - sodipodi:role="line" - id="tspan1443" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1455" - transform="translate(46.221832,-21.847031)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1449" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1453" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1451" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(46.221832,-16.555375)" - id="g1463"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1457" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1461"><tspan - sodipodi:role="line" - id="tspan1459" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(46.221833,-0.68042051)" - id="g1555"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1549" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1553"><tspan - sodipodi:role="line" - id="tspan1551" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(51.513499,-0.68042051)" - id="g1563"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1557" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1561"><tspan - sodipodi:role="line" - id="tspan1559" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1571" - transform="translate(50.110335,-10.368653)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1565" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1569"><tspan - sodipodi:role="line" - id="tspan1567" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - id="g1579" - transform="translate(46.221833,-5.9720765)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1573" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1577" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1575" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(46.221833,-11.263719)" - id="g1587"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1581" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1585"><tspan - sodipodi:role="line" - id="tspan1583" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1595" - transform="translate(51.513499,-11.263719)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1589" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1593" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1591" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(56.805165,-0.68040601)" - id="g1603"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1597" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1601"><tspan - sodipodi:role="line" - id="tspan1599" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1611" - transform="translate(56.805165,-5.9720625)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1605" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1609" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1607" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1639" - transform="translate(35.638501,9.9028786)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1633" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1637" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1635" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1647" - transform="translate(40.930167,9.9028786)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1641" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1645" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1643" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(39.527003,0.21464608)" - id="g1655"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1649" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1653" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1651" - sodipodi:role="line">B</tspan></text> - </g> - <g - transform="translate(35.638501,4.6112226)" - id="g1663"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1657" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1661"><tspan - sodipodi:role="line" - id="tspan1659" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1671" - transform="translate(35.638501,-0.68041992)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1665" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1669" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1667" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(40.930167,-0.68041992)" - id="g1679"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1673" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1677"><tspan - sodipodi:role="line" - id="tspan1675" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1687" - transform="translate(46.221833,9.9028931)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1681" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1685" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1683" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(46.221833,4.6112366)" - id="g1695"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1689" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1693"><tspan - sodipodi:role="line" - id="tspan1691" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(56.805165,-11.263719)" - id="g1703"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1697" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1701"><tspan - sodipodi:role="line" - id="tspan1699" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(25.055168,-0.68041992)" - id="g1711"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1705" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1709"><tspan - sodipodi:role="line" - id="tspan1707" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(30.346834,-0.68041992)" - id="g1719"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1713" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1717"><tspan - sodipodi:role="line" - id="tspan1715" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1727" - transform="translate(28.94367,-10.368652)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1721" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1725"><tspan - sodipodi:role="line" - id="tspan1723" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - id="g1735" - transform="translate(25.055168,-5.9720759)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1729" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1733" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1731" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(25.055168,-11.263718)" - id="g1743"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1737" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1741"><tspan - sodipodi:role="line" - id="tspan1739" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1751" - transform="translate(30.346834,-11.263718)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1745" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1749" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1747" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(35.638501,-5.9720761)" - id="g1759"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1753" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1757"><tspan - sodipodi:role="line" - id="tspan1755" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(25.055167,4.6112372)" - id="g1862"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1856" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1860"><tspan - sodipodi:role="line" - id="tspan1858" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1870" - transform="translate(30.346834,9.9028791)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1864" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1868" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1866" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1910" - transform="translate(50.110335,0.21466067)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1904" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1908"><tspan - sodipodi:role="line" - id="tspan1906" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - id="g1918" - transform="translate(51.5135,9.9029076)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1912" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1916" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1914" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1934" - transform="translate(56.805166,4.6112655)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1928" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1932" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1930" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1974" - transform="translate(56.805162,-16.555375)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1968" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1972" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1970" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(50.110335,-20.951951)" - id="g1982"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1976" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1980" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1978" - sodipodi:role="line">B</tspan></text> - </g> - <g - transform="translate(51.513496,-21.84703)" - id="g1990"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1984" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1988"><tspan - sodipodi:role="line" - id="tspan1986" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g2054" - transform="translate(28.94367,-20.951964)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect2048" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text2052"><tspan - sodipodi:role="line" - id="tspan2050" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(30.346835,-21.847045)" - id="g2086"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect2080" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text2084"><tspan - sodipodi:role="line" - id="tspan2082" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1204" - transform="translate(28.943669,0.21466127)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1198" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1202"><tspan - sodipodi:role="line" - id="tspan1200" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(56.805162,-16.555375)" - id="g1356" /> - <g - transform="translate(56.805162,-21.847031)" - id="g1383"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1377" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1381"><tspan - sodipodi:role="line" - id="tspan1379" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <rect - y="249.37505" - x="47.624992" - height="5.2916665" - width="5.2916665" - id="rect1473" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="244.08339" - x="42.333328" - height="5.2916665" - width="5.2916665" - id="rect1475" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1477" - width="5.2916665" - height="5.2916665" - x="47.624992" - y="244.08339" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1479" - width="5.2916665" - height="5.2916665" - x="47.624996" - y="254.6667" /> - <rect - y="244.0834" - x="37.04166" - height="5.2916665" - width="5.2916665" - id="rect1481" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="286.41669" - x="37.041664" - height="5.2916665" - width="5.2916665" - id="rect1483" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="286.41669" - x="42.333332" - height="5.2916665" - width="5.2916665" - id="rect1485" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1487" - width="5.2916665" - height="5.2916665" - x="47.624996" - y="286.41669" /> - <rect - y="281.12503" - x="47.624996" - height="5.2916665" - width="5.2916665" - id="rect1489" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="275.83337" - x="47.624996" - height="5.2916665" - width="5.2916665" - id="rect1491" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="286.41666" - x="5.2916665" - height="5.2916665" - width="5.2916665" - id="rect1530" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1532" - width="5.2916665" - height="5.2916665" - x="10.583333" - y="286.41666" /> - <rect - y="286.41666" - x="15.875" - height="5.2916665" - width="5.2916665" - id="rect1534" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1536" - width="5.2916665" - height="5.2916665" - x="5.2916665" - y="281.125" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1538" - width="5.2916665" - height="5.2916665" - x="5.2916665" - y="275.83334" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1574" - width="5.2916665" - height="5.2916665" - x="5.2916665" - y="254.66673" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1576" - width="5.2916665" - height="5.2916665" - x="5.2916665" - y="249.37508" /> - <rect - y="244.08342" - x="5.2916665" - height="5.2916665" - width="5.2916665" - id="rect1578" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1580" - width="5.2916665" - height="5.2916665" - x="10.583333" - y="244.08342" /> - <rect - y="244.08342" - x="15.875" - height="5.2916665" - width="5.2916665" - id="rect1582" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <g - id="g1621" - transform="translate(25.055168,-21.847031)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1615" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1619" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1617" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(25.055167,-16.555374)" - id="g1629"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1623" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1627"><tspan - sodipodi:role="line" - id="tspan1625" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1649" - transform="translate(25.055167,9.9028791)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1643" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1647" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1645" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(56.805164,9.9029236)" - id="g1657"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1651" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1655"><tspan - sodipodi:role="line" - id="tspan1653" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - </g> -</svg>
@@ -1,252 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="195" - height="195" - viewBox="0 0 51.593749 51.593751" - version="1.1" - id="svg8" - inkscape:version="0.92.3 (2405546, 2018-03-11)" - sodipodi:docname="outpost-brand.svg"> - <defs - id="defs2"> - <linearGradient - inkscape:collect="always" - id="linearGradient1398"> - <stop - style="stop-color:#63e500;stop-opacity:1" - offset="0" - id="stop1394" /> - <stop - id="stop1406" - offset="0.25" - style="stop-color:#a6e928;stop-opacity:0.74901962" /> - <stop - id="stop1402" - offset="0.5" - style="stop-color:#72ff8f;stop-opacity:0.49803922" /> - <stop - style="stop-color:#c4eddd;stop-opacity:0.63921571" - offset="0.75" - id="stop1404" /> - <stop - style="stop-color:#bfffff;stop-opacity:0.78494626" - offset="1" - id="stop1396" /> - </linearGradient> - <linearGradient - inkscape:collect="always" - xlink:href="#linearGradient1398" - id="linearGradient1400" - x1="-5.5511151e-17" - y1="245.40623" - x2="51.59375" - y2="297" - gradientUnits="userSpaceOnUse" /> - </defs> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="2" - inkscape:cx="42.490073" - inkscape:cy="108.35271" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - units="px" - inkscape:window-width="1366" - inkscape:window-height="739" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" /> - <metadata - id="metadata5"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title /> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-245.40623)"> - <rect - style="opacity:1;fill:url(#linearGradient1400);fill-opacity:1;stroke:none;stroke-width:0.27449697;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1392" - width="51.59375" - height="51.59375" - x="-5.5511151e-17" - y="245.40623" /> - <rect - style="opacity:1;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:0.45617813;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1408" - width="13.229167" - height="13.229167" - x="19.182291" - y="264.58853" /> - <g - id="g847" - transform="translate(-1.7185197,1.9843955)"> - <path - d="m 32.098112,271.86455 -4.582718,2.64584 -4.582718,-2.64584 0,-5.29166 4.582718,-2.64584 4.582718,2.64584 z" - inkscape:randomized="0" - inkscape:rounded="1.04083e-17" - inkscape:flatsided="true" - sodipodi:arg2="1.0471976" - sodipodi:arg1="0.52359878" - sodipodi:r2="4.5827179" - sodipodi:r1="5.291667" - sodipodi:cy="269.21872" - sodipodi:cx="27.515394" - sodipodi:sides="6" - id="path821" - style="opacity:1;fill:#80ffff;fill-opacity:1;stroke:none;stroke-width:0.20531785;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - sodipodi:type="star" /> - <path - sodipodi:type="star" - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.04927629;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="path823" - sodipodi:sides="6" - sodipodi:cx="26.03373" - sodipodi:cy="267.41956" - sodipodi:r1="1.2700001" - sodipodi:r2="1.0998524" - sodipodi:arg1="0.52359878" - sodipodi:arg2="1.0471976" - inkscape:flatsided="true" - inkscape:rounded="1.04083e-17" - inkscape:randomized="0" - d="m 27.133582,268.05456 -1.099852,0.635 -1.099853,-0.635 0,-1.27 1.099853,-0.635 1.099852,0.635 z" /> - </g> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:5.30142689px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.36146089" - x="1.1195526" - y="252.34299" - id="text827"><tspan - sodipodi:role="line" - x="1.1195526" - y="252.34299" - style="stroke-width:0.36146089" - id="tspan833">Early to late-game</tspan></text> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:5.25529337px;line-height:1.25;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.35831544" - x="50.197937" - y="281.55966" - id="text837"><tspan - sodipodi:role="line" - id="tspan835" - x="50.197937" - y="281.55966" - style="text-align:end;text-anchor:end;stroke-width:0.35831544">• Gear</tspan><tspan - sodipodi:role="line" - x="50.197937" - y="288.12878" - style="text-align:end;text-anchor:end;stroke-width:0.35831544" - id="tspan839">• Materials</tspan><tspan - sodipodi:role="line" - x="50.197937" - y="294.69791" - style="text-align:end;text-anchor:end;stroke-width:0.35831544" - id="tspan841">• Gameplay tips</tspan></text> - <path - sodipodi:type="star" - style="opacity:1;fill:#999999;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="path849" - sodipodi:sides="6" - sodipodi:cx="12.083937" - sodipodi:cy="271.64813" - sodipodi:r1="4.1095676" - sodipodi:r2="3.55899" - sodipodi:arg1="1.0471976" - sodipodi:arg2="1.5707963" - inkscape:flatsided="true" - inkscape:rounded="1.04083e-17" - inkscape:randomized="0" - d="m 14.13872,275.20712 -4.109567,0 -2.054784,-3.55899 2.054784,-3.55899 4.109568,0 2.054783,3.55899 z" /> - <path - d="m 39.863412,262.76572 -4.109568,0 -2.054784,-3.55899 2.054784,-3.55899 4.109568,0 2.054784,3.55899 z" - inkscape:randomized="0" - inkscape:rounded="1.04083e-17" - inkscape:flatsided="true" - sodipodi:arg2="1.5707963" - sodipodi:arg1="1.0471976" - sodipodi:r2="3.55899" - sodipodi:r1="4.1095676" - sodipodi:cy="259.20674" - sodipodi:cx="37.808629" - sodipodi:sides="6" - id="path851" - style="opacity:1;fill:#999999;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - sodipodi:type="star" /> - <g - id="g864"> - <ellipse - ry="2.1047475" - rx="3.3675961" - cy="260.19028" - cx="24.789249" - id="path853" - style="opacity:1;fill:#a05a2c;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - id="rect855" - transform="matrix(0.26458333,0,0,0.26458333,0,245.40623)" - d="M 88.609375 37.419922 L 85.464844 38.599609 L 91.171875 53.828125 L 94.318359 52.648438 L 90.164062 41.568359 L 99.595703 45.033203 L 94.708984 58.070312 L 97.853516 59.25 L 103.5625 44.019531 L 103.51758 44.001953 L 103.875 43.027344 L 88.609375 37.419922 z " - style="opacity:1;fill:#c87137;fill-opacity:1;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - </g> - <g - id="g870" - transform="translate(-12.369271,0.13229167)"> - <ellipse - style="opacity:1;fill:#a05a2c;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="ellipse866" - cx="24.789249" - cy="260.19028" - rx="3.3675961" - ry="2.1047475" /> - <path - style="opacity:1;fill:#c87137;fill-opacity:1;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 88.609375,37.419922 -3.144531,1.179687 5.707031,15.228516 3.146484,-1.179687 -4.154297,-11.080079 9.431641,3.464844 -4.886719,13.037109 3.144532,1.179688 5.708984,-15.230469 -0.0449,-0.01758 0.35742,-0.974609 z" - transform="matrix(0.26458333,0,0,0.26458333,0,245.40623)" - id="path868" - inkscape:connector-curvature="0" /> - </g> - <g - transform="translate(14.022917,11.840104)" - id="g876"> - <ellipse - ry="2.1047475" - rx="3.3675961" - cy="260.19028" - cx="24.789249" - id="ellipse872" - style="opacity:1;fill:#a05a2c;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path874" - transform="matrix(0.26458333,0,0,0.26458333,0,245.40623)" - d="m 88.609375,37.419922 -3.144531,1.179687 5.707031,15.228516 3.146484,-1.179687 -4.154297,-11.080079 9.431641,3.464844 -4.886719,13.037109 3.144532,1.179688 5.708984,-15.230469 -0.0449,-0.01758 0.35742,-0.974609 z" - style="opacity:1;fill:#c87137;fill-opacity:1;stroke:none;stroke-width:0.99999994;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - </g> - </g> -</svg>
@@ -1,2019 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="220" - height="220" - viewBox="0 0 58.208332 58.208334" - version="1.1" - id="svg8" - inkscape:version="0.92.3 (2405546, 2018-03-11)" - sodipodi:docname="outpost-good-pattern.svg"> - <defs - id="defs2" /> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="5.6568544" - inkscape:cx="29.205714" - inkscape:cy="182.75107" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - units="px" - inkscape:window-width="1366" - inkscape:window-height="739" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" /> - <metadata - id="metadata5"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title /> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-238.79165)"> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect815" - width="5.2916665" - height="5.2916665" - x="0" - y="291.70831" /> - <rect - y="291.70831" - x="5.2916665" - height="5.2916665" - width="5.2916665" - id="rect817" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect819" - width="5.2916665" - height="5.2916665" - x="10.583333" - y="291.70831" /> - <rect - y="291.70831" - x="15.875" - height="5.2916665" - width="5.2916665" - id="rect821" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect823" - width="5.2916665" - height="5.2916665" - x="21.166666" - y="291.70831" /> - <rect - y="291.70831" - x="26.458332" - height="5.2916665" - width="5.2916665" - id="rect825" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect827" - width="5.2916665" - height="5.2916665" - x="31.749998" - y="291.70831" /> - <rect - y="291.70831" - x="37.041664" - height="5.2916665" - width="5.2916665" - id="rect829" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect831" - width="5.2916665" - height="5.2916665" - x="42.333328" - y="291.70831" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect835" - width="5.2916665" - height="5.2916665" - x="47.624996" - y="291.70831" /> - <rect - y="291.70831" - x="52.91666" - height="5.2916665" - width="5.2916665" - id="rect837" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="286.41666" - x="0" - height="5.2916665" - width="5.2916665" - id="rect839" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="286.41666" - x="21.166666" - height="5.2916665" - width="5.2916665" - id="rect847" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect849" - width="5.2916665" - height="5.2916665" - x="26.458332" - y="286.41666" /> - <rect - y="286.41666" - x="31.749998" - height="5.2916665" - width="5.2916665" - id="rect851" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect859" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="286.41666" /> - <rect - y="281.125" - x="0" - height="5.2916665" - width="5.2916665" - id="rect861" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect881" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="281.125" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect883" - width="5.2916665" - height="5.2916665" - x="0" - y="275.83334" /> - <rect - y="275.83334" - x="52.91666" - height="5.2916665" - width="5.2916665" - id="rect903" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="270.54169" - x="0" - height="5.2916665" - width="5.2916665" - id="rect905" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect907" - width="5.2916665" - height="5.2916665" - x="5.2916665" - y="270.54169" /> - <rect - y="270.54169" - x="47.624996" - height="5.2916665" - width="5.2916665" - id="rect923" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect925" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="270.54169" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect927" - width="5.2916665" - height="5.2916665" - x="0" - y="265.25003" /> - <rect - y="265.25003" - x="5.2916665" - height="5.2916665" - width="5.2916665" - id="rect929" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect945" - width="5.2916665" - height="5.2916665" - x="47.624996" - y="265.25003" /> - <rect - y="265.25003" - x="52.91666" - height="5.2916665" - width="5.2916665" - id="rect947" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect949" - width="5.2916665" - height="5.2916665" - x="0" - y="259.95837" /> - <rect - y="259.95837" - x="5.2916665" - height="5.2916665" - width="5.2916665" - id="rect951" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect967" - width="5.2916665" - height="5.2916665" - x="47.624996" - y="259.95837" /> - <rect - y="259.95837" - x="52.91666" - height="5.2916665" - width="5.2916665" - id="rect969" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="254.66672" - x="0" - height="5.2916665" - width="5.2916665" - id="rect971" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect991" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="254.66672" /> - <rect - y="249.37506" - x="0" - height="5.2916665" - width="5.2916665" - id="rect993" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1013" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="249.37506" /> - <rect - y="244.0834" - x="0" - height="5.2916665" - width="5.2916665" - id="rect1015" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="244.0834" - x="21.166666" - height="5.2916665" - width="5.2916665" - id="rect1023" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1025" - width="5.2916665" - height="5.2916665" - x="26.458332" - y="244.0834" /> - <rect - y="244.0834" - x="31.749998" - height="5.2916665" - width="5.2916665" - id="rect1027" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1035" - width="5.2916665" - height="5.2916665" - x="52.91666" - y="244.0834" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1037" - width="5.2916665" - height="5.2916665" - x="0" - y="238.79175" /> - <rect - y="238.79175" - x="5.2916665" - height="5.2916665" - width="5.2916665" - id="rect1039" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1041" - width="5.2916665" - height="5.2916665" - x="10.583333" - y="238.79175" /> - <rect - y="238.79175" - x="15.875" - height="5.2916665" - width="5.2916665" - id="rect1043" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1045" - width="5.2916665" - height="5.2916665" - x="21.166666" - y="238.79175" /> - <rect - y="238.79175" - x="26.458332" - height="5.2916665" - width="5.2916665" - id="rect1047" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1049" - width="5.2916665" - height="5.2916665" - x="31.749998" - y="238.79175" /> - <rect - y="238.79175" - x="37.041664" - height="5.2916665" - width="5.2916665" - id="rect1051" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1053" - width="5.2916665" - height="5.2916665" - x="42.333328" - y="238.79175" /> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1055" - width="5.2916665" - height="5.2916665" - x="47.624996" - y="238.79175" /> - <rect - y="238.79175" - x="52.91666" - height="5.2916665" - width="5.2916665" - id="rect1057" - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <g - id="g1106"> - <rect - style="opacity:1;fill:#008080;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect937" - width="5.2916665" - height="5.2916665" - x="26.458332" - y="265.25003" /> - <text - id="text1101" - y="269.30844" - x="27.745592" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332;" - xml:space="preserve"><tspan - style="stroke-width:0.26458332;fill:#ffffff;" - y="269.30844" - x="27.745592" - id="tspan1099" - sodipodi:role="line">C</tspan></text> - </g> - <g - id="g1114" - transform="translate(-42.333332,26.458286)"> - <rect - y="265.25003" - x="26.458332" - height="5.2916665" - width="5.2916665" - id="rect1108" - style="opacity:1;fill:#008080;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="27.745592" - y="269.30844" - id="text1112"><tspan - sodipodi:role="line" - id="tspan1110" - x="27.745592" - y="269.30844" - style="fill:#ffffff;stroke-width:0.26458332">C</tspan></text> - </g> - <g - id="g1165" - transform="translate(35.6385,-11.263733)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1151" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1155" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1153" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1458" - transform="translate(-1.4031647,9.9028864)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1452" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1456" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1454" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1474" - transform="translate(-2.8063296,10.797967)"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1460" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1464" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1462" - sodipodi:role="line">B</tspan></text> - </g> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1680" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="275.83334" /> - <g - id="g1147" - transform="translate(40.930166,-11.263733)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1141" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1145" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1143" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(39.527002,-20.951965)" - id="g1415"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1409" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1413" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1411" - sodipodi:role="line">B</tspan></text> - </g> - <g - transform="translate(35.6385,-16.555389)" - id="g1431"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1425" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1429"><tspan - sodipodi:role="line" - id="tspan1427" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1439" - transform="translate(35.6385,-21.847031)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1433" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1437" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1435" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(40.930166,-21.847031)" - id="g1447"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1441" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1445"><tspan - sodipodi:role="line" - id="tspan1443" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1455" - transform="translate(46.221832,-21.847031)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1449" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1453" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1451" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(46.221832,-16.555375)" - id="g1463"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1457" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1461"><tspan - sodipodi:role="line" - id="tspan1459" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(46.221833,-0.68042051)" - id="g1555"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1549" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1553"><tspan - sodipodi:role="line" - id="tspan1551" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(51.513499,-0.68042051)" - id="g1563"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1557" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1561"><tspan - sodipodi:role="line" - id="tspan1559" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1571" - transform="translate(50.110335,-10.368653)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1565" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1569"><tspan - sodipodi:role="line" - id="tspan1567" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - id="g1579" - transform="translate(46.221833,-5.9720765)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1573" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1577" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1575" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(46.221833,-11.263719)" - id="g1587"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1581" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1585"><tspan - sodipodi:role="line" - id="tspan1583" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1595" - transform="translate(51.513499,-11.263719)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1589" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1593" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1591" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(56.805165,-0.68040601)" - id="g1603"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1597" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1601"><tspan - sodipodi:role="line" - id="tspan1599" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1611" - transform="translate(56.805165,-5.9720625)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1605" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1609" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1607" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1639" - transform="translate(35.638501,9.9028786)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1633" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1637" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1635" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1647" - transform="translate(40.930167,9.9028786)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1641" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1645" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1643" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(39.527003,0.21464608)" - id="g1655"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1649" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1653" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1651" - sodipodi:role="line">B</tspan></text> - </g> - <g - transform="translate(35.638501,4.6112226)" - id="g1663"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1657" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1661"><tspan - sodipodi:role="line" - id="tspan1659" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1671" - transform="translate(35.638501,-0.68041992)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1665" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1669" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1667" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(40.930167,-0.68041992)" - id="g1679"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1673" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1677"><tspan - sodipodi:role="line" - id="tspan1675" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1687" - transform="translate(46.221833,9.9028931)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1681" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1685" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1683" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(46.221833,4.6112366)" - id="g1695"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1689" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1693"><tspan - sodipodi:role="line" - id="tspan1691" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(56.805165,-11.263719)" - id="g1703"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1697" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1701"><tspan - sodipodi:role="line" - id="tspan1699" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(25.055168,-0.68041992)" - id="g1711"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1705" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1709"><tspan - sodipodi:role="line" - id="tspan1707" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(30.346834,-0.68041992)" - id="g1719"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1713" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1717"><tspan - sodipodi:role="line" - id="tspan1715" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1727" - transform="translate(28.94367,-10.368652)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1721" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1725"><tspan - sodipodi:role="line" - id="tspan1723" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - id="g1735" - transform="translate(25.055168,-5.9720759)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1729" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1733" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1731" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(25.055168,-11.263718)" - id="g1743"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1737" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1741"><tspan - sodipodi:role="line" - id="tspan1739" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1751" - transform="translate(30.346834,-11.263718)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1745" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1749" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1747" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(35.638501,-5.9720761)" - id="g1759"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1753" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1757"><tspan - sodipodi:role="line" - id="tspan1755" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(30.346833,4.6112222)" - id="g1806"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1800" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1804"><tspan - sodipodi:role="line" - id="tspan1802" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(30.346834,15.194535)" - id="g1814"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1808" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1812"><tspan - sodipodi:role="line" - id="tspan1810" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1822" - transform="translate(19.763501,15.194535)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1816" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1820" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1818" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1830" - transform="translate(25.055167,15.194535)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1824" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1828" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1826" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(23.652003,5.5063032)" - id="g1838"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1832" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1836" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1834" - sodipodi:role="line">B</tspan></text> - </g> - <g - transform="translate(19.763501,9.9028793)" - id="g1846"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1840" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1844"><tspan - sodipodi:role="line" - id="tspan1842" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1854" - transform="translate(19.763501,4.6112372)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1848" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1852" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1850" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(25.055167,4.6112372)" - id="g1862"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1856" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1860"><tspan - sodipodi:role="line" - id="tspan1858" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1870" - transform="translate(30.346834,9.9028791)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1864" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1868" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1866" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1878" - transform="translate(62.096832,4.6112505)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1872" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1876" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1874" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1886" - transform="translate(62.096833,15.194563)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1880" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1884" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1882" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(51.5135,15.194563)" - id="g1894"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1888" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1892"><tspan - sodipodi:role="line" - id="tspan1890" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(56.805166,15.194563)" - id="g1902"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1896" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1900"><tspan - sodipodi:role="line" - id="tspan1898" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1910" - transform="translate(55.402002,5.5063315)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1904" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1908"><tspan - sodipodi:role="line" - id="tspan1906" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - id="g1918" - transform="translate(51.5135,9.9029076)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1912" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1916" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1914" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(51.5135,4.6112655)" - id="g1926"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1920" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1924"><tspan - sodipodi:role="line" - id="tspan1922" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1934" - transform="translate(56.805166,4.6112655)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1928" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1932" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1930" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(62.096833,9.9029074)" - id="g1942"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1936" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1940"><tspan - sodipodi:role="line" - id="tspan1938" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(62.096828,-27.138687)" - id="g1950"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1944" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1948"><tspan - sodipodi:role="line" - id="tspan1946" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(62.096829,-16.555375)" - id="g1958"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1952" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1956"><tspan - sodipodi:role="line" - id="tspan1954" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1966" - transform="translate(51.513496,-16.555375)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1960" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1964" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1962" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1974" - transform="translate(56.805162,-16.555375)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1968" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1972" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1970" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(55.401998,-26.243606)" - id="g1982"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1976" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1980" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1978" - sodipodi:role="line">B</tspan></text> - </g> - <g - transform="translate(51.513496,-21.84703)" - id="g1990"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1984" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1988"><tspan - sodipodi:role="line" - id="tspan1986" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1998" - transform="translate(51.513496,-27.138672)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1992" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1996" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1994" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(56.805162,-27.138672)" - id="g2006"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect2000" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text2004"><tspan - sodipodi:role="line" - id="tspan2002" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g2014" - transform="translate(62.096829,-21.84703)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2008" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text2012" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan2010" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g2022" - transform="translate(30.346834,-27.138702)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2016" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text2020" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan2018" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g2030" - transform="translate(30.346835,-16.55539)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2024" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text2028" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan2026" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(19.763502,-16.55539)" - id="g2038"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect2032" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text2036"><tspan - sodipodi:role="line" - id="tspan2034" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(25.055168,-16.55539)" - id="g2046"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect2040" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text2044"><tspan - sodipodi:role="line" - id="tspan2042" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g2054" - transform="translate(23.652004,-26.243621)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect2048" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text2052"><tspan - sodipodi:role="line" - id="tspan2050" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - id="g2062" - transform="translate(19.763502,-21.847045)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2056" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text2060" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan2058" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(19.763502,-27.138687)" - id="g2070"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect2064" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text2068"><tspan - sodipodi:role="line" - id="tspan2066" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g2078" - transform="translate(25.055168,-27.138687)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2072" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text2076" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan2074" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(30.346835,-21.847045)" - id="g2086"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect2080" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text2084"><tspan - sodipodi:role="line" - id="tspan2082" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <rect - style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2101" - width="15.875" - height="15.875" - x="5.2916665" - y="244.0834" /> - </g> -</svg>
@@ -1,3873 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="260" - height="260" - viewBox="0 0 68.791665 68.791667" - version="1.1" - id="svg8" - inkscape:version="0.92.3 (2405546, 2018-03-11)" - sodipodi:docname="outpost-trap-pattern.svg"> - <defs - id="defs2" /> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="1.4142136" - inkscape:cx="74.729176" - inkscape:cy="68.576247" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - units="px" - inkscape:window-width="1366" - inkscape:window-height="739" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" /> - <metadata - id="metadata5"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-228.20832)"> - <g - id="g1106" - transform="translate(5.2916728,-5.2916471)"> - <rect - style="opacity:1;fill:#008080;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect937" - width="5.2916665" - height="5.2916665" - x="26.458332" - y="265.25003" /> - <text - id="text1101" - y="269.30844" - x="27.745592" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="269.30844" - x="27.745592" - id="tspan1099" - sodipodi:role="line">C</tspan></text> - </g> - <g - id="g1114" - transform="translate(-42.333332,20.658538)"> - <rect - y="265.25003" - x="26.458332" - height="5.2916665" - width="5.2916665" - id="rect1108" - style="opacity:1;fill:#008080;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="27.745592" - y="269.30844" - id="text1112"><tspan - sodipodi:role="line" - id="tspan1110" - x="27.745592" - y="269.30844" - style="fill:#ffffff;stroke-width:0.26458332">C</tspan></text> - </g> - <g - id="g1165" - transform="translate(40.930173,-16.55538)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1151" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1155" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1153" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1458" - transform="translate(-1.4031647,4.103138)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1452" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1456" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1454" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1474" - transform="translate(-2.8063296,4.9982186)"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1460" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1464" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1462" - sodipodi:role="line">B</tspan></text> - </g> - <rect - style="opacity:1;fill:#80ff80;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1680" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="270.0336" /> - <g - id="g1147" - transform="translate(46.221839,-16.55538)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1141" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1145" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1143" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(44.818675,-26.243612)" - id="g1415"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1409" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1413" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1411" - sodipodi:role="line">B</tspan></text> - </g> - <g - transform="translate(40.930173,-21.847036)" - id="g1431"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1425" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1429"><tspan - sodipodi:role="line" - id="tspan1427" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1439" - transform="translate(40.930173,-27.138678)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1433" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1437" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1435" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(46.221839,-27.138678)" - id="g1447"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1441" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1445"><tspan - sodipodi:role="line" - id="tspan1443" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1455" - transform="translate(51.513505,-27.138678)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1449" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1453" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1451" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(51.513505,-21.847022)" - id="g1463"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1457" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1461"><tspan - sodipodi:role="line" - id="tspan1459" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(51.513506,-5.9720676)" - id="g1555"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1549" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1553"><tspan - sodipodi:role="line" - id="tspan1551" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(56.805172,-5.9720676)" - id="g1563"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1557" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1561"><tspan - sodipodi:role="line" - id="tspan1559" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1571" - transform="translate(55.402008,-15.6603)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1565" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1569"><tspan - sodipodi:role="line" - id="tspan1567" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - id="g1579" - transform="translate(51.513506,-11.263724)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1573" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1577" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1575" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(51.513506,-16.555366)" - id="g1587"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1581" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1585"><tspan - sodipodi:role="line" - id="tspan1583" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1595" - transform="translate(56.805172,-16.555366)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1589" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1593" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1591" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(62.096838,-5.9720531)" - id="g1603"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1597" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1601"><tspan - sodipodi:role="line" - id="tspan1599" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1611" - transform="translate(62.096838,-11.26371)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1605" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1609" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1607" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1639" - transform="translate(40.930174,4.6112315)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1633" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1637" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1635" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1647" - transform="translate(46.22184,4.6112315)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1641" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1645" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1643" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(44.818676,-5.077001)" - id="g1655"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1649" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1653" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1651" - sodipodi:role="line">B</tspan></text> - </g> - <g - transform="translate(40.930174,-0.68042446)" - id="g1663"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1657" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1661"><tspan - sodipodi:role="line" - id="tspan1659" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1671" - transform="translate(40.930174,-5.972067)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1665" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1669" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1667" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(46.22184,-5.972067)" - id="g1679"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1673" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1677"><tspan - sodipodi:role="line" - id="tspan1675" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1687" - transform="translate(51.513506,4.611246)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1681" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1685" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1683" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(51.513506,-0.68041046)" - id="g1695"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1689" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1693"><tspan - sodipodi:role="line" - id="tspan1691" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(62.096838,-16.555366)" - id="g1703"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1697" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1701"><tspan - sodipodi:role="line" - id="tspan1699" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(30.346841,-5.972067)" - id="g1711"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1705" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1709"><tspan - sodipodi:role="line" - id="tspan1707" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(35.638507,-5.972067)" - id="g1719"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1713" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1717"><tspan - sodipodi:role="line" - id="tspan1715" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1727" - transform="translate(34.235343,-15.660299)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1721" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1725"><tspan - sodipodi:role="line" - id="tspan1723" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - id="g1735" - transform="translate(30.346841,-11.263723)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1729" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1733" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1731" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(30.346841,-16.555365)" - id="g1743"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1737" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1741"><tspan - sodipodi:role="line" - id="tspan1739" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1751" - transform="translate(35.638507,-16.555365)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1745" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1749" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1747" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(40.930174,-11.263723)" - id="g1759"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1753" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1757"><tspan - sodipodi:role="line" - id="tspan1755" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(30.34684,-0.68040986)" - id="g1862"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1856" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1860"><tspan - sodipodi:role="line" - id="tspan1858" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1870" - transform="translate(35.638507,4.611232)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1864" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1868" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1866" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1910" - transform="translate(55.402008,-5.0769864)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1904" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1908"><tspan - sodipodi:role="line" - id="tspan1906" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - id="g1918" - transform="translate(56.805173,4.6112605)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1912" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1916" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1914" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1934" - transform="translate(62.096839,-0.68038156)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1928" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1932" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1930" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1974" - transform="translate(62.096835,-21.847022)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1968" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1972" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1970" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(55.402008,-26.243598)" - id="g1982"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1976" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1980" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1978" - sodipodi:role="line">B</tspan></text> - </g> - <g - transform="translate(56.805169,-27.138677)" - id="g1990"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1984" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1988"><tspan - sodipodi:role="line" - id="tspan1986" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g2054" - transform="translate(34.235343,-26.243611)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect2048" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text2052"><tspan - sodipodi:role="line" - id="tspan2050" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(35.638508,-27.138692)" - id="g2086"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect2080" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text2084"><tspan - sodipodi:role="line" - id="tspan2082" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1204" - transform="translate(34.235342,-5.0769858)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1198" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1202"><tspan - sodipodi:role="line" - id="tspan1200" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(56.805162,-16.555375)" - id="g1356" /> - <g - transform="translate(62.096835,-27.138678)" - id="g1383"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1377" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1381"><tspan - sodipodi:role="line" - id="tspan1379" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1621" - transform="translate(30.346841,-27.138678)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1615" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1619" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1617" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(30.34684,-21.847021)" - id="g1629"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1623" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1627"><tspan - sodipodi:role="line" - id="tspan1625" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1649" - transform="translate(30.34684,4.611232)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1643" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1647" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1645" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(62.096837,4.6112765)" - id="g1657"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1651" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1655"><tspan - sodipodi:role="line" - id="tspan1653" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1180" - transform="translate(44.818671,-36.826923)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1174" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1178"><tspan - sodipodi:role="line" - id="tspan1176" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - id="g1188" - transform="translate(55.402004,-36.826909)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1182" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1186"><tspan - sodipodi:role="line" - id="tspan1184" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(34.235339,-36.826922)" - id="g1196"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1190" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1194" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1192" - sodipodi:role="line">B</tspan></text> - </g> - <g - transform="translate(44.818675,5.5063269)" - id="g1205"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1199" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1203" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1201" - sodipodi:role="line">B</tspan></text> - </g> - <g - transform="translate(55.402008,5.5063409)" - id="g1213"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1207" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1211" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1209" - sodipodi:role="line">B</tspan></text> - </g> - <g - id="g1221" - transform="translate(34.235343,5.5063279)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1215" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1219"><tspan - sodipodi:role="line" - id="tspan1217" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(65.985339,-15.660269)" - id="g1229"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1223" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1227" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1225" - sodipodi:role="line">B</tspan></text> - </g> - <g - transform="translate(65.985339,-5.0769559)" - id="g1237"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1231" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1235" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1233" - sodipodi:role="line">B</tspan></text> - </g> - <g - transform="translate(65.985335,-36.826878)" - id="g1253"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1247" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1251" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1249" - sodipodi:role="line">B</tspan></text> - </g> - <g - id="g1261" - transform="translate(65.985339,5.5063719)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1255" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1259"><tspan - sodipodi:role="line" - id="tspan1257" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - id="g1269" - transform="translate(23.652009,-15.660314)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1263" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1267"><tspan - sodipodi:role="line" - id="tspan1265" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - id="g1277" - transform="translate(23.652009,-5.0770004)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1271" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1275"><tspan - sodipodi:role="line" - id="tspan1273" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(23.652009,-26.243612)" - id="g1285"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1279" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1283" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1281" - sodipodi:role="line">B</tspan></text> - </g> - <g - id="g1293" - transform="translate(23.652005,-36.826923)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1287" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1291"><tspan - sodipodi:role="line" - id="tspan1289" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(23.652009,5.5063269)" - id="g1301"> - <rect - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1295" - width="5.2916665" - height="5.2916665" - x="-13.06867" - y="275.61868" /> - <text - id="text1299" - y="279.67899" - x="-11.806991" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="279.67899" - x="-11.806991" - id="tspan1297" - sodipodi:role="line">B</tspan></text> - </g> - <g - id="g1355" - transform="translate(65.985337,-26.243597)"> - <rect - y="275.61868" - x="-13.06867" - height="5.2916665" - width="5.2916665" - id="rect1349" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-11.806991" - y="279.67899" - id="text1353"><tspan - sodipodi:role="line" - id="tspan1351" - x="-11.806991" - y="279.67899" - style="fill:#ffffff;stroke-width:0.26458332">B</tspan></text> - </g> - <g - transform="translate(40.93017,-37.722004)" - id="g1425"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1419" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1423"><tspan - sodipodi:role="line" - id="tspan1421" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1433" - transform="translate(46.221836,-37.722004)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1427" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1431" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1429" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(51.513502,-37.722004)" - id="g1441"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1435" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1439"><tspan - sodipodi:role="line" - id="tspan1437" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1449" - transform="translate(56.805166,-37.722003)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1443" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1447" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1445" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1457" - transform="translate(35.638505,-37.722018)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1451" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1455" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1453" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1465" - transform="translate(62.096832,-37.722004)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1459" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1463" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1461" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(30.346838,-37.722004)" - id="g1473"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1467" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1471"><tspan - sodipodi:role="line" - id="tspan1469" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(67.388499,-37.722004)" - id="g1481"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1475" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1479"><tspan - sodipodi:role="line" - id="tspan1477" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1489" - transform="translate(72.680166,-37.722004)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1483" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1487" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1485" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1497" - transform="translate(19.763503,-37.722004)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1491" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1495" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1493" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(25.05517,-37.722004)" - id="g1505"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1499" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1503"><tspan - sodipodi:role="line" - id="tspan1501" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1513" - transform="translate(19.763503,-11.263736)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1507" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1511" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1509" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(19.763503,-16.555392)" - id="g1521"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1515" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1519"><tspan - sodipodi:role="line" - id="tspan1517" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1529" - transform="translate(19.763503,-21.847034)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1523" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1527" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1525" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1537" - transform="translate(19.763502,-5.9720789)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1531" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1535" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1533" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(19.763503,-32.430347)" - id="g1545"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1539" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1543"><tspan - sodipodi:role="line" - id="tspan1541" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1553" - transform="translate(19.763502,-27.13869)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1547" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1551" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1549" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(19.763502,-0.68043706)" - id="g1561"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1555" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1559"><tspan - sodipodi:role="line" - id="tspan1557" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(19.763508,4.6112457)" - id="g1569"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1563" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1567"><tspan - sodipodi:role="line" - id="tspan1565" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(19.763507,9.9029029)" - id="g1577"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1571" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1575"><tspan - sodipodi:role="line" - id="tspan1573" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1585" - transform="translate(19.763507,15.194545)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1579" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1583" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1581" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1593" - transform="translate(40.930174,15.194545)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1587" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1591" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1589" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(46.22184,15.194545)" - id="g1601"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1595" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1599"><tspan - sodipodi:role="line" - id="tspan1597" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1609" - transform="translate(51.513506,15.194545)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1603" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1607" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1605" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(56.80517,15.194546)" - id="g1617"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1611" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1615"><tspan - sodipodi:role="line" - id="tspan1613" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(35.638509,15.194531)" - id="g1625"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1619" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1623"><tspan - sodipodi:role="line" - id="tspan1621" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(62.096836,15.194545)" - id="g1633"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1627" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1631"><tspan - sodipodi:role="line" - id="tspan1629" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1641" - transform="translate(30.346842,15.194545)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1635" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1639" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1637" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1650" - transform="translate(67.388503,15.194545)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1644" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1648" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1646" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(72.68017,15.194545)" - id="g1658"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1652" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1656"><tspan - sodipodi:role="line" - id="tspan1654" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1666" - transform="translate(25.055174,15.194545)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1660" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1664" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1662" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(72.680166,-11.263691)" - id="g1674"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1668" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1672"><tspan - sodipodi:role="line" - id="tspan1670" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1682" - transform="translate(72.680166,-16.555347)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1676" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1680" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1678" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(72.680166,-21.846989)" - id="g1690"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1684" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1688"><tspan - sodipodi:role="line" - id="tspan1686" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(72.680165,-5.972034)" - id="g1698"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1692" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1696"><tspan - sodipodi:role="line" - id="tspan1694" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1706" - transform="translate(72.680166,-32.430302)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1700" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1704" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1702" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(72.680165,-27.138645)" - id="g1714"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1708" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1712"><tspan - sodipodi:role="line" - id="tspan1710" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1722" - transform="translate(72.680165,-0.68039216)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1716" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1720" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1718" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1730" - transform="translate(72.680171,4.6112906)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1724" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1728" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1726" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1738" - transform="translate(72.68017,9.9029479)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1732" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1736" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1734" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1746" - transform="translate(67.388501,-5.9720201)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1740" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1744" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1742" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1754" - transform="translate(67.388501,-16.555333)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1748" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1752" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1750" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1762" - transform="translate(67.388498,-27.138645)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1756" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1760" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1758" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1770" - transform="translate(67.3885,4.6113095)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1764" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1768" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1766" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(25.055172,-5.9720656)" - id="g1778"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1772" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1776"><tspan - sodipodi:role="line" - id="tspan1774" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(25.055172,-16.555379)" - id="g1786"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1780" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1784"><tspan - sodipodi:role="line" - id="tspan1782" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(25.055169,-27.138691)" - id="g1794"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1788" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1792"><tspan - sodipodi:role="line" - id="tspan1790" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(25.055171,4.6112639)" - id="g1802"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1796" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1800"><tspan - sodipodi:role="line" - id="tspan1798" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(40.93017,-32.430333)" - id="g1810"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1804" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1808"><tspan - sodipodi:role="line" - id="tspan1806" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - transform="translate(51.513502,-32.430333)" - id="g1818"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1812" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1816"><tspan - sodipodi:role="line" - id="tspan1814" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1826" - transform="translate(62.096832,-32.430333)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1820" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1824" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1822" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(30.346838,-32.430333)" - id="g1834"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1828" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1832"><tspan - sodipodi:role="line" - id="tspan1830" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1842" - transform="translate(40.930171,9.9028899)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1836" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1840" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1838" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1850" - transform="translate(51.513503,9.9028899)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1844" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1848" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1846" - sodipodi:role="line">S</tspan></text> - </g> - <g - transform="translate(62.096833,9.9028899)" - id="g1858"> - <rect - y="271.22211" - x="-14.471835" - height="5.2916665" - width="5.2916665" - id="rect1852" - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-13.077519" - y="275.28052" - id="text1856"><tspan - sodipodi:role="line" - id="tspan1854" - x="-13.077519" - y="275.28052" - style="fill:#000000;stroke-width:0.26458332">S</tspan></text> - </g> - <g - id="g1866" - transform="translate(30.346839,9.9028899)"> - <rect - style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1860" - width="5.2916665" - height="5.2916665" - x="-14.471835" - y="271.22211" /> - <text - id="text1864" - y="275.28052" - x="-13.077519" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#000000;stroke-width:0.26458332" - y="275.28052" - x="-13.077519" - id="tspan1862" - sodipodi:role="line">S</tspan></text> - </g> - <g - id="g1882"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1868" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text1872" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan1870" - sodipodi:role="line">T</tspan></text> - </g> - <g - id="g1890" - transform="translate(15.875006,0.5080826)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect1884" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text1888"><tspan - sodipodi:role="line" - id="tspan1886" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - transform="translate(15.875006,-4.7835739)" - id="g1898"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1892" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text1896" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan1894" - sodipodi:role="line">T</tspan></text> - </g> - <g - transform="translate(15.875001,-10.075243)" - id="g1906"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1900" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text1904" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan1902" - sodipodi:role="line">T</tspan></text> - </g> - <g - id="g1914" - transform="translate(15.875001,-15.366899)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect1908" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text1912"><tspan - sodipodi:role="line" - id="tspan1910" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - transform="translate(15.875007,-20.658529)" - id="g1922"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1916" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text1920" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan1918" - sodipodi:role="line">T</tspan></text> - </g> - <g - id="g1930" - transform="translate(15.875007,-25.950185)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect1924" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text1928"><tspan - sodipodi:role="line" - id="tspan1926" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - id="g1938" - transform="translate(15.875002,-31.241854)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect1932" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text1936"><tspan - sodipodi:role="line" - id="tspan1934" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - transform="translate(15.875002,-36.53351)" - id="g1946"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1940" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text1944" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan1942" - sodipodi:role="line">T</tspan></text> - </g> - <g - transform="translate(15.875006,-41.82514)" - id="g1954"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1948" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text1952" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan1950" - sodipodi:role="line">T</tspan></text> - </g> - <g - transform="translate(15.875001,-47.116809)" - id="g1962"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1956" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text1960" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan1958" - sodipodi:role="line">T</tspan></text> - </g> - <g - id="g1970" - transform="translate(15.875001,-52.408465)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect1964" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text1968"><tspan - sodipodi:role="line" - id="tspan1966" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - id="g1978" - transform="translate(15.875001,-57.700121)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect1972" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text1976"><tspan - sodipodi:role="line" - id="tspan1974" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - transform="translate(15.875001,-62.991777)" - id="g1986"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect1980" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text1984" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan1982" - sodipodi:role="line">T</tspan></text> - </g> - <g - id="g1994" - transform="translate(21.166669,-62.991779)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect1988" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text1992"><tspan - sodipodi:role="line" - id="tspan1990" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - id="g2002" - transform="translate(26.458337,-62.991779)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect1996" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text2000"><tspan - sodipodi:role="line" - id="tspan1998" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - transform="translate(31.750005,-62.991781)" - id="g2010"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2004" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2008" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2006" - sodipodi:role="line">T</tspan></text> - </g> - <g - id="g2018" - transform="translate(37.041671,-62.991781)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect2012" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text2016"><tspan - sodipodi:role="line" - id="tspan2014" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - transform="translate(42.333339,-62.991783)" - id="g2026"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2020" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2024" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2022" - sodipodi:role="line">T</tspan></text> - </g> - <g - transform="translate(47.625007,-62.991783)" - id="g2034"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2028" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2032" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2030" - sodipodi:role="line">T</tspan></text> - </g> - <g - id="g2042" - transform="translate(52.916675,-62.991785)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect2036" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text2040"><tspan - sodipodi:role="line" - id="tspan2038" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - id="g2050" - transform="translate(58.208329,-62.991777)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect2044" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text2048"><tspan - sodipodi:role="line" - id="tspan2046" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - transform="translate(63.499997,-62.991779)" - id="g2058"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2052" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2056" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2054" - sodipodi:role="line">T</tspan></text> - </g> - <g - transform="translate(68.791665,-62.991779)" - id="g2066"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2060" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2064" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2062" - sodipodi:role="line">T</tspan></text> - </g> - <g - id="g2074" - transform="translate(74.083333,-62.991781)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect2068" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text2072"><tspan - sodipodi:role="line" - id="tspan2070" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - transform="translate(79.374999,-62.991781)" - id="g2082"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2076" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2080" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2078" - sodipodi:role="line">T</tspan></text> - </g> - <g - transform="translate(79.375008,0.5081533)" - id="g2134"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2128" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2132" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2130" - sodipodi:role="line">T</tspan></text> - </g> - <g - id="g2142" - transform="translate(79.375008,-4.7835032)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect2136" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text2140"><tspan - sodipodi:role="line" - id="tspan2138" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - id="g2150" - transform="translate(79.375003,-10.075172)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect2144" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text2148"><tspan - sodipodi:role="line" - id="tspan2146" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - transform="translate(79.375003,-15.366828)" - id="g2158"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2152" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2156" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2154" - sodipodi:role="line">T</tspan></text> - </g> - <g - id="g2166" - transform="translate(79.375009,-20.658458)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect2160" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text2164"><tspan - sodipodi:role="line" - id="tspan2162" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - transform="translate(79.375009,-25.950114)" - id="g2174"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2168" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2172" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2170" - sodipodi:role="line">T</tspan></text> - </g> - <g - transform="translate(79.375004,-31.241783)" - id="g2182"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2176" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2180" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2178" - sodipodi:role="line">T</tspan></text> - </g> - <g - id="g2190" - transform="translate(79.375004,-36.533439)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect2184" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text2188"><tspan - sodipodi:role="line" - id="tspan2186" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - id="g2198" - transform="translate(79.375008,-41.825069)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect2192" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text2196"><tspan - sodipodi:role="line" - id="tspan2194" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - id="g2206" - transform="translate(79.375003,-47.116738)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect2200" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text2204"><tspan - sodipodi:role="line" - id="tspan2202" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - transform="translate(79.375003,-52.408394)" - id="g2214"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2208" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2212" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2210" - sodipodi:role="line">T</tspan></text> - </g> - <g - transform="translate(79.375003,-57.70005)" - id="g2222"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2216" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2220" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2218" - sodipodi:role="line">T</tspan></text> - </g> - <g - transform="translate(21.166672,0.5080826)" - id="g2382"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2376" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2380" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2378" - sodipodi:role="line">T</tspan></text> - </g> - <g - transform="translate(26.45834,0.5080826)" - id="g2390"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2384" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2388" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2386" - sodipodi:role="line">T</tspan></text> - </g> - <g - id="g2398" - transform="translate(31.750008,0.5080806)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect2392" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text2396"><tspan - sodipodi:role="line" - id="tspan2394" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - transform="translate(37.041674,0.5080806)" - id="g2406"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2400" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2404" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2402" - sodipodi:role="line">T</tspan></text> - </g> - <g - id="g2414" - transform="translate(42.333342,0.5080786)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect2408" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text2412"><tspan - sodipodi:role="line" - id="tspan2410" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - id="g2422" - transform="translate(47.62501,0.5080786)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect2416" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text2420"><tspan - sodipodi:role="line" - id="tspan2418" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - transform="translate(52.916678,0.5080766)" - id="g2430"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2424" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2428" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2426" - sodipodi:role="line">T</tspan></text> - </g> - <g - transform="translate(58.208332,0.5080846)" - id="g2438"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2432" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2436" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2434" - sodipodi:role="line">T</tspan></text> - </g> - <g - id="g2446" - transform="translate(63.5,0.5080826)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect2440" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text2444"><tspan - sodipodi:role="line" - id="tspan2442" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - id="g2454" - transform="translate(68.791668,0.5080826)"> - <rect - y="291.20023" - x="-15.875" - height="5.2916665" - width="5.2916665" - id="rect2448" - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - x="-14.414366" - y="295.26053" - id="text2452"><tspan - sodipodi:role="line" - id="tspan2450" - x="-14.414366" - y="295.26053" - style="fill:#ffffff;stroke-width:0.26458332">T</tspan></text> - </g> - <g - transform="translate(74.083336,0.5080806)" - id="g2462"> - <rect - style="opacity:1;fill:#803300;fill-opacity:1;stroke:none;stroke-width:4.39278936;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect2456" - width="5.2916665" - height="5.2916665" - x="-15.875" - y="291.20023" /> - <text - id="text2460" - y="295.26053" - x="-14.414366" - style="font-style:normal;font-weight:normal;font-size:3.88055563px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" - xml:space="preserve"><tspan - style="fill:#ffffff;stroke-width:0.26458332" - y="295.26053" - x="-14.414366" - id="tspan2458" - sodipodi:role="line">T</tspan></text> - </g> - </g> -</svg>
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Data Mining, Warehousing and Information Retrieval | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Data Mining, Warehousing and Information Retrieval</h1><div class=time><p>2020-07-03</div><p>During university, there were a few subjects where I had to write blog posts for (either as evaluable tasks or just for fun). I thought it was really fun and I wanted to preserve that work here, with the hopes it's interesting to someone.<p>The posts series were auto-generated from the original HTML files and manually anonymized later.<ul><li><a href=/blog/mdad>Data Mining and Data Warehousing</a><li><a href=/blog/ribw>Information Retrieval and Web Search</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,167 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Writing our own Cheat Engine: Introduction | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Writing our own Cheat Engine: Introduction</h1><div class=time><p>2021-02-07<p>last updated 2021-02-19</div><p>This is part 1 on the <em>Writing our own Cheat Engine</em> series:<ul><li>Part 1: Introduction<li><a href=/blog/woce-2>Part 2: Exact Value scanning</a><li><a href=/blog/woce-3>Part 3: Unknown initial value</a><li><a href=/blog/woce-4>Part 4: Floating points</a><li><a href=/blog/woce-5>Part 5: Code finder</a><li><a href=/blog/woce-6>Part 6: Pointers</a></ul><p><a href=https://cheatengine.org/>Cheat Engine</a> is a tool designed to modify single player games and contains other useful tools within itself that enable its users to debug games or other applications. It comes with a memory scanner, (dis)assembler, inspection tools and a handful other things. In this series, we will be writing our own tiny Cheat Engine capable of solving all steps of the tutorial, and diving into how it all works underneath.<p>Needless to say, we're doing this for private and educational purposes only. One has to make sure to not violate the EULA or ToS of the specific application we're attaching to. This series, much like cheatengine.org, does not condone the illegal use of the code shared.<p>Cheat Engine is a tool for Windows, so we will be developing for Windows as well. However, you can also <a href=https://stackoverflow.com/q/12977179/4759433>read memory from Linux-like systems</a>. <a href=https://github.com/scanmem/scanmem>GameConqueror</a> is a popular alternative to Cheat Engine on Linux systems, so if you feel adventurous, you could definitely follow along too! The techniques shown in this series apply regardless of how we read memory from a process. You will learn a fair bit about doing FFI in Rust too.<p>We will be developing the application in Rust, because it enables us to interface with the Windows API easily, is memory safe (as long as we're careful with <code>unsafe</code>!), and is speedy (we will need this for later steps in the Cheat Engine tutorial). You could use any language of your choice though. For example, <a href=https://lonami.dev/blog/ctypes-and-windows/>Python also makes it relatively easy to use the Windows API</a>. You don't need to be a Rust expert to follow along, but this series assumes some familiarity with C-family languages. Slightly advanced concepts like the use of <code>unsafe</code> or the <code>MaybeUninit</code> type will be briefly explained. What a <code>fn</code> is or what <code>let</code> does will not be explained.<p><a href=https://github.com/cheat-engine/cheat-engine/>Cheat Engine's source code</a> is mostly written in Pascal and C. And it's <em>a lot</em> of code, with a very flat project structure, and files ranging in the thousand lines of code each. It's daunting<sup class=footnote-reference><a href=#1>1</a></sup>. It's a mature project, with a lot of knowledge encoded in the code base, and a lot of features like distributed scanning or an entire disassembler. Unfortunately, there's not a lot of comments. For these reasons, I'll do some guesswork when possible as to how it's working underneath, rather than actually digging into what Cheat Engine is actually doing.<p>With that out of the way, let's get started!<h2 id=welcome-to-the-cheat-engine-tutorial>Welcome to the Cheat Engine Tutorial</h2><details open><summary>Cheat Engine Tutorial: Step 1</summary> <blockquote><p>This tutorial will teach you the basics of cheating in video games. It will also show you foundational aspects of using Cheat Engine (or CE for short). Follow the steps below to get started.<ol><li>Open Cheat Engine if it currently isn't running.<li>Click on the "Open Process" icon (it's the top-left icon with the computer on it, below "File".).<li>With the Process List window now open, look for this tutorial's process in the list. It will look something like > "00001F98-Tutorial-x86_64.exe" or "0000047C-Tutorial-i386.exe". (The first 8 numbers/letters will probably be different.)<li>Once you've found the process, click on it to select it, then click the "Open" button. (Don't worry about all the > other buttons right now. You can learn about them later if you're interested.)</ol><p>Congratulations! If you did everything correctly, the process window should be gone with Cheat Engine now attached to the > tutorial (you will see the process name towards the top-center of CE).<p>Click the "Next" button below to continue, or fill in the password and click the "OK" button to proceed to that step.)<p>If you're having problems, simply head over to forum.cheatengine.org, then click on "Tutorials" to view beginner-friendly > guides!</blockquote></details><h2 id=enumerating-processes>Enumerating processes</h2><p>Our first step is attaching to the process we want to work with. But we need a way to find that process in the first place! Having to open the task manager, look for the process we care about, noting down the process ID (PID), and slapping it in the source code is not satisfying at all. Instead, let's enumerate all the processes from within the program, and let the user select one by typing its name.<p>From a quick <a href=https://ddg.gg/winapi%20enumerate%20all%20processes>DuckDuckGo search</a>, we find an official tutorial for <a href=https://docs.microsoft.com/en-us/windows/win32/psapi/enumerating-all-processes>Enumerating All Processes</a>, which leads to the <a href=https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocesses><code>EnumProcesses</code></a> call. Cool! Let's slap in the <a href=https://crates.io/crates/winapi><code>winapi</code></a> crate on <code>Cargo.toml</code>, because I don't want to write all the definitions by myself:<pre><code class=language-toml data-lang=toml>[dependencies] -winapi = { version = "0.3.9", features = ["psapi"] } -</code></pre><p>Because <a href=https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocesses><code>EnumProcesses</code></a> is in <code>Psapi.h</code> (you can see this in the online page of its documentation), we know we'll need the <code>psapi</code> crate feature. Another option is to search for it in the <a href=https://docs.rs/winapi/><code>winapi</code> documentation</a> and noting down the parent module where its stored.<p>The documentation for the method has the following remark:<blockquote><p>It is a good idea to use a large array, because it is hard to predict how many processes there will be at the time you call <strong>EnumProcesses</strong>.</blockquote><p><em>Sidenote: reading the documentation for the methods we'll use from the Windows API is extremely important. There's a lot of gotchas involved, so we need to make sure we're extra careful.</em><p>1024 is a pretty big number, so let's go with that:<pre><code class=language-rust data-lang=rust>use std::io; -use std::mem; -use winapi::shared::minwindef::{DWORD, FALSE}; - -pub fn enum_proc() -> io::Result<Vec<u32>> { - let mut pids = Vec::<DWORD>::with_capacity(1024); - let mut size = 0; - // SAFETY: the pointer is valid and the size matches the capacity. - if unsafe { - winapi::um::psapi::EnumProcesses( - pids.as_mut_ptr(), - (pids.capacity() * mem::size_of::<DWORD>()) as u32, - &mut size, - ) - } == FALSE - { - return Err(io::Error::last_os_error()); - } - - todo!() -} -</code></pre><p>We allocate enough space<sup class=footnote-reference><a href=#2>2</a></sup> for 1024 <code>pids</code> in a vector<sup class=footnote-reference><a href=#3>3</a></sup>, and pass a mutable pointer to the contents to <code>EnumProcesses</code>. Note that the size of the array is in <em>bytes</em>, not items, so we need to multiply the capacity by the size of <code>DWORD</code>. The API likes to use <code>u32</code> for sizes, unlike Rust which uses <code>usize</code>, so we need a cast.<p>Last, we need another mutable variable where the amount of bytes written is stored, <code>size</code>.<blockquote><p>If the function fails, the return value is zero. To get extended error information, call <a href=https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror><code>GetLastError</code></a>.</blockquote><p>That's precisely what we do. If it returns false (zero), we return the last OS error. Rust provides us with <a href=https://doc.rust-lang.org/stable/std/io/struct.Error.html#method.last_os_error><code>std::io::Error::last_os_error</code></a>, which essentially makes that same call but returns a proper <code>io::Error</code> instance. Cool!<blockquote><p>To determine how many processes were enumerated, divide the <em>lpcbNeeded</em> value by <code>sizeof(DWORD)</code>.</blockquote><p>Easy enough:<pre><code class=language-rust data-lang=rust>let count = size as usize / mem::size_of::<DWORD>(); -// SAFETY: the call succeeded and count equals the right amount of items. -unsafe { pids.set_len(count) }; -Ok(pids) -</code></pre><p>Rust doesn't know that the memory for <code>count</code> items were initialized by the call, but we do, so we make use of the <a href=https://doc.rust-lang.org/stable/std/vec/struct.Vec.html#method.set_len><code>Vec::set_len</code></a> call to indicate this. The Rust documentation even includes a FFI similar to our code!<p>Let's give it a ride:<pre><code class=language-rust data-lang=rust>fn main() { - dbg!(enum_proc().unwrap().len()); -} -</code></pre><pre><code>>cargo run - Compiling memo v0.1.0 - Finished dev [unoptimized + debuginfo] target(s) in 0.20s - Running `target\debug\memo.exe` -[src\main.rs:27] enum_proc().unwrap().len() = 178 -</code></pre><p>It works! But currently we only have a bunch of process identifiers, with no way of knowing which process they refer to.<blockquote><p>To obtain process handles for the processes whose identifiers you have just obtained, call the <a href=https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess><code>OpenProcess</code></a> function.</blockquote><p>Oh!<h2 id=opening-a-process>Opening a process</h2><p>The documentation for <code>OpenProcess</code> also contains the following:<blockquote><p>When you are finished with the handle, be sure to close it using the <a href=https://lonami.dev/blog/woce-1/closehandle><code>CloseHandle</code></a> function.</blockquote><p>This sounds to me like the perfect time to introduce a custom <code>struct Process</code> with an <code>impl Drop</code>! We're using <code>Drop</code> to cleanup resources, not behaviour, so it's fine. <a href=https://internals.rust-lang.org/t/pre-rfc-leave-auto-trait-for-reliable-destruction/13825>Using <code>Drop</code> to cleanup behaviour is a bad idea</a>. But anyway, let's get back to the code:<pre><code class=language-rust data-lang=rust>use std::ptr::NonNull; -use winapi::ctypes::c_void; - -pub struct Process { - pid: u32, - handle: NonNull<c_void>, -} - -impl Process { - pub fn open(pid: u32) -> io::Result<Self> { - todo!() - } -} - -impl Drop for Process { - fn drop(&mut self) { - todo!() - } -} -</code></pre><p>For <code>open</code>, we'll want to use <code>OpenProcess</code> (and we also need to add the <code>processthreadsapi</code> feature to the <code>winapi</code> dependency in <code>Cargo.toml</code>). It returns a <code>HANDLE</code>, which is a nullable mutable pointer to <code>c_void</code>. If it's null, the call failed, and if it's non-null, it succeeded and we have a valid handle. This is why we use Rust's <a href=https://doc.rust-lang.org/stable/std/ptr/struct.NonNull.html><code>NonNull</code></a>:<pre><code class=language-rust data-lang=rust>// SAFETY: the call doesn't have dangerous side-effects. -NonNull::new(unsafe { winapi::um::processthreadsapi::OpenProcess(0, FALSE, pid) }) - .map(|handle| Self { pid, handle }) - .ok_or_else(io::Error::last_os_error) -</code></pre><p><code>NonNull</code> will return <code>Some</code> if the pointer is non-null. We map the non-null pointer to a <code>Process</code> instance with <code>Self { .. }</code>. <code>ok_or_else</code> converts the <code>Option</code> to a <code>Result</code> with the error builder function we provide if it was <code>None</code>.<p>The first parameter is a bitflag of permissions we want to have. For now, we can leave it as zero (all bits unset, no specific permissions granted). The second one is whether we want to inherit the handle, which we don't, and the third one is the process identifier. Let's close the resource handle on <code>Drop</code> (after adding <code>handleapi</code> to the crate features):<pre><code class=language-rust data-lang=rust>// SAFETY: the handle is valid and non-null. -unsafe { winapi::um::handleapi::CloseHandle(self.handle.as_mut()) }; -</code></pre><p><code>CloseHandle</code> can actually fail (for example, on double-close), but given our invariants, it won't. You could add an <code>assert!</code> to panic if this is not the case.<p>We can now open processes, and they will be automatically closed on <code>Drop</code>. Does any of this work though?<pre><code class=language-rust data-lang=rust>fn main() { - let mut success = 0; - let mut failed = 0; - enum_proc().unwrap().into_iter().for_each(|pid| match Process::open(pid) { - Ok(_) => success += 1, - Err(_) => failed += 1, - }); - - eprintln!("Successfully opened {}/{} processes", success, success + failed); -} -</code></pre><pre><code>>cargo run - Compiling memo v0.1.0 - Finished dev [unoptimized + debuginfo] target(s) in 0.36s - Running `target\debug\memo.exe` -Successfully opened 0/191 processes -</code></pre><p>…nope. Maybe the documentation for <code>OpenProcess</code> says something?<blockquote><p><code>dwDesiredAccess</code><p>The access to the process object. This access right is checked against the security descriptor for the process. This parameter can be <strong>one or more</strong> of the process access rights.</blockquote><p>One or more, but we're setting zero permissions. I told you, reading the documentation is important<sup class=footnote-reference><a href=#4>4</a></sup>! The <a href=https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights>Process Security and Access Rights</a> page lists all possible values we could use. <code>PROCESS_QUERY_INFORMATION</code> seems to be appropriated:<blockquote><p>Required to retrieve certain information about a process, such as its token, exit code, and priority class</blockquote><pre><code class=language-rust data-lang=rust>OpenProcess(winapi::um::winnt::PROCESS_QUERY_INFORMATION, ...) -</code></pre><p>Does this fix it?<pre><code class=language-rust data-lang=rust>>cargo run - Compiling memo v0.1.0 - Finished dev [unoptimized + debuginfo] target(s) in 0.36s - Running `target\debug\memo.exe` -Successfully opened 69/188 processes -</code></pre><p><em>Nice</em>. It does solve it. But why did we only open 69 processes out of 188? Does it help if we run our code as administrator? Let's search for <code>cmd</code> in the Windows menu and right click to Run as administrator, then <code>cd</code> into our project and try again:<pre><code>>cargo run - Finished dev [unoptimized + debuginfo] target(s) in 0.01s - Running `target\debug\memo.exe` -Successfully opened 77/190 processes -</code></pre><p>We're able to open a few more, so it does help. In general, we'll want to run as administrator, so normal programs can't sniff on what we're doing, and so that we have permission to do more things.<h2 id=getting-the-name-of-a-process>Getting the name of a process</h2><p>We're not done enumerating things just yet. To get the "name" of a process, we need to enumerate the modules that it has loaded, and only then can we get the module base name. The first module is the program itself, so we don't need to enumerate <em>all</em> modules, just the one is enough.<p>For this we want <a href=https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocessmodules><code>EnumProcessModules</code></a> and <a href=https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmodulebasenamea><code>GetModuleBaseNameA</code></a>. I'm using the ASCII variant of <code>GetModuleBaseName</code> because I'm too lazy to deal with UTF-16 of the <code>W</code> (wide, unicode) variants.<pre><code class=language-rust data-lang=rust>use std::mem::MaybeUninit; -use winapi::shared::minwindef::HMODULE; - -pub fn name(&self) -> io::Result<String> { - let mut module = MaybeUninit::<HMODULE>::uninit(); - let mut size = 0; - // SAFETY: the pointer is valid and the size is correct. - if unsafe { - winapi::um::psapi::EnumProcessModules( - self.handle.as_ptr(), - module.as_mut_ptr(), - mem::size_of::<HMODULE>() as u32, - &mut size, - ) - } == FALSE - { - return Err(io::Error::last_os_error()); - } - - // SAFETY: the call succeeded, so module is initialized. - let module = unsafe { module.assume_init() }; - todo!() -} -</code></pre><p><code>EnumProcessModules</code> takes a pointer to an array of <code>HMODULE</code>. We could use a <code>Vec</code> of capacity one to hold the single module, but in memory, a pointer a single item can be seen as a pointer to an array of items. <code>MaybeUninit</code> helps us reserve enough memory for the one item we need.<p>With the module handle, we can retrieve its base name:<pre><code class=language-rust data-lang=rust>let mut buffer = Vec::<u8>::with_capacity(64); -// SAFETY: the handle, module and buffer are all valid. -let length = unsafe { - winapi::um::psapi::GetModuleBaseNameA( - self.handle.as_ptr(), - module, - buffer.as_mut_ptr().cast(), - buffer.capacity() as u32, - ) -}; -if length == 0 { - return Err(io::Error::last_os_error()); -} - -// SAFETY: the call succeeded and length represents bytes. -unsafe { buffer.set_len(length as usize) }; -Ok(String::from_utf8(buffer).unwrap()) -</code></pre><p>Similar to how we did with <code>EnumProcesses</code>, we create a buffer that will hold the ASCII string of the module's base name<sup class=footnote-reference><a href=#5>5</a></sup>. The call wants us to pass a pointer to a mutable buffer of <code>i8</code>, but Rust's <code>String::from_utf8</code> wants a <code>Vec<u8></code>, so instead we declare a buffer of <code>u8</code> and <code>.cast()</code> the pointer in the call. You could also do this with <code>as _</code>, and Rust would infer the right type, but <code>cast</code> is neat.<p>We <code>unwrap</code> the creation of the UTF-8 string because the buffer should contain only ASCII characters (which are also valid UTF-8). We could use the <code>unsafe</code> variant to create the string, but what if somehow it contains non-ASCII characters? The less <code>unsafe</code>, the better.<p>Let's see it in action:<pre><code class=language-rust data-lang=rust>fn main() { - enum_proc() - .unwrap() - .into_iter() - .for_each(|pid| match Process::open(pid) { - Ok(proc) => match proc.name() { - Ok(name) => println!("{}: {}", pid, name), - Err(e) => println!("{}: (failed to get name: {})", pid, e), - }, - Err(e) => eprintln!("failed to open {}: {}", pid, e), - }); -} -</code></pre><pre><code>>cargo run - Compiling memo v0.1.0 - Finished dev [unoptimized + debuginfo] target(s) in 0.32s - Running `target\debug\memo.exe` -failed to open 0: The parameter is incorrect. (os error 87) -failed to open 4: Access is denied. (os error 5) -... -failed to open 5940: Access is denied. (os error 5) -5608: (failed to get name: Access is denied. (os error 5)) -... -1704: (failed to get name: Access is denied. (os error 5)) -failed to open 868: Access is denied. (os error 5) -... -</code></pre><p>That's not good. What's up with that? Maybe…<blockquote><p>The handle must have the <code>PROCESS_QUERY_INFORMATION</code> and <code>PROCESS_VM_READ</code> access rights.</blockquote><p>…I should've read the documentation. Okay, fine:<pre><code class=language-rust data-lang=rust>use winapi::um::winnt; -OpenProcess(winnt::PROCESS_QUERY_INFORMATION | winnt::PROCESS_VM_READ, ...) -</code></pre><pre><code>>cargo run - Compiling memo v0.1.0 (C:\Users\L\Desktop\memo) - Finished dev [unoptimized + debuginfo] target(s) in 0.35s - Running `target\debug\memo.exe` -failed to open 0: The parameter is incorrect. (os error 87) -failed to open 4: Access is denied. (os error 5) -... -9348: cheatengine-x86_64.exe -3288: Tutorial-x86_64.exe -8396: cmd.exe -4620: firefox.exe -7964: cargo.exe -10052: cargo.exe -5756: memo.exe -</code></pre><p>Hooray 🎉! There's some processes we can't open, but that's because they're system processes. Security works!<h2 id=finale>Finale</h2><p>That was a fairly long post when all we did was print a bunch of pids and their corresponding name. But in all fairness, we also laid out a good foundation for what's coming next.<p>You can <a href=https://github.com/lonami/memo>obtain the code for this post</a> over at my GitHub. At the end of every post, the last commit will be tagged, so you can <code>git checkout step1</code> to see the final code for any blog post.<p>In the <a href=/blog/woce-2>next post</a>, we'll tackle the second step of the tutorial: Exact Value scanning.<h3 id=footnotes>Footnotes</h3><div class=footnote-definition id=1><sup class=footnote-definition-label>1</sup><p>You could say I simply love reinventing the wheel, which I do, but in this case, the codebase contains <em>far</em> more features than we're interested in. The (apparent) lack of structure and documentation regarding the code, along with the unfortunate <a href=https://github.com/cheat-engine/cheat-engine/issues/60>lack of license</a> for the source code, make it a no-go. There's a license, but I think that's for the distributed program itself.</div><div class=footnote-definition id=2><sup class=footnote-definition-label>2</sup><p>If it turns out that there are more than 1024 processes, our code will be unaware of those extra processes. The documentation suggests to perform the call again with a larger buffer if <code>count == provided capacity</code>, but given I have under 200 processes on my system, it seems unlikely we'll reach this limit. If you're worried about hitting this limit, simply use a larger limit or retry with a larger vector.</div><div class=footnote-definition id=3><sup class=footnote-definition-label>3</sup><p>C code would likely use <a href=https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globalalloc><code>GlobalAlloc</code></a> here, but Rust's <code>Vec</code> handles the allocation for us, making the code both simpler and more idiomatic. In general, if you see calls to <code>GlobalAlloc</code> when porting some code to Rust, you can probably replace it with a <code>Vec</code>.</div><div class=footnote-definition id=4><sup class=footnote-definition-label>4</sup><p>This will be a recurring theme.</div><div class=footnote-definition id=5><sup class=footnote-definition-label>5</sup><p>…and similar to <code>EnumProcesses</code>, if the name doesn't fit in our buffer, the result will be truncated.</div></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,259 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Writing our own Cheat Engine: Exact Value scanning | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Writing our own Cheat Engine: Exact Value scanning</h1><div class=time><p>2021-02-12<p>last updated 2021-02-19</div><p>This is part 2 on the <em>Writing our own Cheat Engine</em> series:<ul><li><a href=/blog/woce-1>Part 1: Introduction</a> (start here if you're new to the series!)<li>Part 2: Exact Value scanning<li><a href=/blog/woce-3>Part 3: Unknown initial value</a><li><a href=/blog/woce-4>Part 4: Floating points</a><li><a href=/blog/woce-5>Part 5: Code finder</a><li><a href=/blog/woce-6>Part 6: Pointers</a></ul><p>In the introduction, we spent a good deal of time enumerating all running processes just so we could find out the pid we cared about. With the pid now in our hands, we can do pretty much anything to its corresponding process.<p>It's now time to read the process' memory and write to it. If our process was a single-player game, this would enable us to do things like setting a very high value on the player's current health pool, making us invincible. This technique will often not work for multi-player games, because the server likely knows your true current health (the most you could probably do is make the client render an incorrect value). However, if the server is crappy and it trusts the client, then you're still free to mess around with your current health.<p>Even if we don't want to write to the process' memory, reading is still very useful. Maybe you could enhance your experience by making a custom overlay that displays useful information, or something that makes noise if it detects the life is too low, or even simulating a keyboard event to automatically recover some mana when you're running low.<p>Be warned about anti-cheat systems. Anything beyond a basic game is likely to have some protection measures in place, making the analysis more difficult (perhaps the values are scrambled in memory), or even pinging the server if it detects something fishy.<p><strong>I am not responsible for any bans!</strong> Use your brain before messing with online games, and don't ruin the fun for everyone else. If you get caught for cheating, I don't want to know about it.<p>Now that all <a href=https://www.urbandictionary.com/define.php?term=script%20kiddie>script kiddies</a> have left the room, let's proceed with the post.<h2 id=exact-value-scanning>Exact Value scanning</h2><details open><summary>Cheat Engine Tutorial: Step 2</summary> <blockquote><p>Now that you have opened the tutorial with Cheat Engine let's get on with the next step.<p>You can see at the bottom of this window is the text Health: xxx. Each time you click 'Hit me' your health gets decreased.<p>To get to the next step you have to find this value and change it to 1000<p>To find the value there are different ways, but I'll tell you about the easiest, 'Exact Value': First make sure value type is set to at least 2-bytes or 4-bytes. 1-byte will also work, but you'll run into an easy to fix problem when you've found the address and want to change it. The 8-byte may perhaps works if the bytes after the address are 0, but I wouldn't take the bet. Single, double, and the other scans just don't work, because they store the value in a different way.<p>When the value type is set correctly, make sure the scantype is set to 'Exact Value'. Then fill in the number your health is in the value box. And click 'First Scan'. After a while (if you have a extremely slow pc) the scan is done and the results are shown in the list on the left<p>If you find more than 1 address and you don't know for sure which address it is, click 'Hit me', fill in the new health value into the value box, and click 'Next Scan'. Repeat this until you're sure you've found it. (that includes that there's only 1 address in the list.....)<p>Now double click the address in the list on the left. This makes the address pop-up in the list at the bottom, showing you the current value. Double click the value, (or select it and press enter), and change the value to 1000.<p>If everything went ok the next button should become enabled, and you're ready for the next step.<p>Note: If you did anything wrong while scanning, click "New Scan" and repeat the scanning again. Also, try playing around with the value and click 'hit me'</blockquote></details><h2 id=our-first-scan>Our First Scan</h2><p>The Cheat Engine tutorial talks about "value types" and "scan types" like "exact value".<p>The <strong>value types</strong> will help us narrow down <em>what</em> we're looking for. For example, the integer type <code>i32</code> is represented in memory as 32 bits, or 4 bytes. However, <code>f32</code> is <em>also</em> represented by 4 bytes, and so is <code>u32</code>. Or perhaps the 4 bytes represent RGBA values of a color! So any 4 bytes in memory can be interpreted in many ways, and it's up to us to decide which way we interpret the bytes in.<p>When programming, numbers which are 32-bit wide are common, as they're a good (and fast) size to work with. Scanning for this type is often a good bet. For positive numbers, <code>i32</code> is represented the same as <code>u32</code> in memory, so even if the value turns out to not be signed, the scan is likely to work. Focusing on <code>i32</code> will save us from scanning for <code>f32</code> or even other types, like interpreting 8 bytes for <code>i64</code>, <code>f64</code>, or less bytes like <code>i16</code>.<p>The <strong>scan types</strong> will help us narrow down <em>how</em> we're looking for a value. Scanning for an exact value means what you think it does: interpret all 4 bytes in the process' memory as our value type, and check if they exactly match our value. This will often yield a lot of candidates, but it will be enough to get us started. Variations of the exact scan include checking for all values below a threshold, above, in between, or even just… unknown.<p>What's the point of scanning for unknown values if <em>everything</em> in memory is unknown? Sometimes you don't have a concrete value. Maybe your health pool is a bar and it nevers tell you how much health you actually have, just a visual indicator of your percentage left, even if the health is not stored as a percentage. As we will find later on, scanning for unknown values is more useful than it might appear at first.<p>We can access the memory of our own program by guessing random pointers and trying to read from them. But Windows isolates the memory of each program, so no pointer we could ever guess will let us read from the memory of another process. Luckily for us, searching for "read process memory winapi" leads us to the <a href=https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-readprocessmemory><code>ReadProcessMemory</code></a> function. Spot on.<pre><code class=language-rust data-lang=rust>pub fn read_memory(&self, addr: usize, n: usize) -> io::Result<Vec<u8>> { - todo!() -} -</code></pre><p>Much like trying to dereference a pointer pointing to released memory or even null, reading from an arbitrary address can fail for the same reasons (and more). We will want to signal this with <code>io::Result</code>. It's funny to note that, even though we're doing something that seems wildly unsafe (reading arbitrary memory, even if the other process is mutating it at the same time), the function is perfectly safe. If we cannot read something, it will return <code>Err</code>, but if it succeeds, it has taken a snapshot of the memory of the process, and the returned value will be correctly initialized.<p>The function will be defined inside our <code>impl Process</code>, since it conveniently holds an open handle to the process in question. It takes <code>&self</code>, because we do not need to mutate anything in the <code>Process</code> instance. After adding the <code>memoryapi</code> feature to <code>Cargo.toml</code>, we can perform the call:<pre><code class=language-rust data-lang=rust>let mut buffer = Vec::<u8>::with_capacity(n); -let mut read = 0; - -// SAFETY: the buffer points to valid memory, and the buffer size is correctly set. -if unsafe { - winapi::um::memoryapi::ReadProcessMemory( - self.handle.as_ptr(), - addr as *const _, - buffer.as_mut_ptr().cast(), - buffer.capacity(), - &mut read, - ) -} == FALSE -{ - Err(io::Error::last_os_error()) -} else { - // SAFETY: the call succeeded and `read` contains the amount of bytes written. - unsafe { buffer.set_len(read as usize) }; - Ok(buffer) -} -</code></pre><p>Great! But the address space is somewhat large. 64 bits large. Eighteen quintillion, four hundred and forty-six quadrillion, seven hundred and forty-four trillion, seventy-three billion, seven hundred and nine million, five hundred and fifty-one thousand, six hundred and sixteen<sup class=footnote-reference><a href=#1>1</a></sup> large. You gave up reading that, didn't you? Anyway, 18'446'744'073'709'551'616 is a <em>big</em> number.<p>I am not willing to wait for the program to scan over so many values. I don't even have 16 <a href=https://en.wikipedia.org/wiki/Orders_of_magnitude_(data)>exbibytes</a> of RAM installed on my laptop yet<sup class=footnote-reference><a href=#2>2</a></sup>! What's up with that?<h2 id=memory-regions>Memory regions</h2><p>The program does not actually have all that memory allocated (surprise!). Random-guessing an address is extremely likely to point out to invalid memory. Reading from the start of the address space all the way to the end would not be any better. And we <strong>need</strong> to do better.<p>We need to query for the memory regions allocated to the program. For this purpose we can use <a href=https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualqueryex><code>VirtualQueryEx</code></a>.<blockquote><p>Retrieves information about a range of pages within the virtual address space of a specified process.</blockquote><p>We have enumerated things before, and this function is not all that different.<pre><code class=language-rust data-lang=rust>fn memory_regions(&self) -> io::Result<winapi::um::winnt::MEMORY_BASIC_INFORMATION> { - let mut info = MaybeUninit::uninit(); - - // SAFETY: the info structure points to valid memory. - let written = unsafe { - winapi::um::memoryapi::VirtualQueryEx( - self.handle.as_ptr(), - std::ptr::null(), - info.as_mut_ptr(), - mem::size_of::<winapi::um::winnt::MEMORY_BASIC_INFORMATION>(), - ) - }; - if written == 0 { - Err(io::Error::last_os_error()) - } else { - // SAFETY: a non-zero amount was written to the structure - Ok(unsafe { info.assume_init() }) - } -} -</code></pre><p>We start with a base address of zero<sup class=footnote-reference><a href=#3>3</a></sup> (<code>std::ptr::null()</code>), and ask the function to tell us what's in there. Let's try it out, with the <code>impl-debug</code> crate feature in <code>Cargo.toml</code>:<pre><code class=language-rust data-lang=rust>dbg!(process.memory_regions()); -</code></pre><pre><code>>cargo run -Compiling memo v0.1.0 - -error[E0277]: `winapi::um::winnt::MEMORY_BASIC_INFORMATION` doesn't implement `std::fmt::Debug` - --> src\main.rs:185:5 - | -185 | dbg!(process.memory_regions()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `winapi::um::winnt::MEMORY_BASIC_INFORMATION` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` -</code></pre><p>That's annoying. It seems not everything has an <code>impl std::fmt::Debug</code>, and <a href=https://github.com/retep998/winapi-rs/issues/548#issuecomment-355278090>you're supposed to send a PR</a> if you want it to have debug, even if the <code>impl-debug</code> feature is set. I'm surprised they don't auto-generate all of this and have to rely on manually adding <code>Debug</code> as needed? Oh well, let's get rid of the feature and print it out ourselves:<pre><code>eprintln!( - "Region: - BaseAddress: {:?} - AllocationBase: {:?} - AllocationProtect: {:?} - RegionSize: {:?} - State: {:?} - Protect: {:?} - Type: {:?}", - region.BaseAddress, - region.AllocationBase, - region.AllocationProtect, - region.RegionSize, - region.State, - region.Protect, - region.Type, -); -</code></pre><p>Hopefully we don't need to do this often:<pre><code>>cargo run - Compiling memo v0.1.0 - Finished dev [unoptimized + debuginfo] target(s) in 0.60s - Running `target\debug\memo.exe` - -Region: - BaseAddress: 0x0 - AllocationBase: 0x0 - AllocationProtect: 0 - RegionSize: 65536 - State: 65536 - Protect: 1 - Type: 0 -</code></pre><p>Awesome! There is a region at <code>null</code>, and the <code>AllocationProtect</code> of zero indicates that "the caller does not have access" when the region was created. However, <code>Protect</code> is <code>1</code>, and that is the <em>current</em> protection level. A value of one indicates <a href=https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants><code>PAGE_NOACCESS</code></a>:<blockquote><p>Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed region results in an access violation.</blockquote><p>Now that we know that the first region starts at 0 and has a size of 64 KiB, we can simply query for the page at <code>(current base + current size)</code> to fetch the next region. Essentially, we want to loop until it fails, after which we'll know there are no more pages<sup class=footnote-reference><a href=#4>4</a></sup>:<pre><code class=language-rust data-lang=rust>pub fn memory_regions(&self) -> Vec<winapi::um::winnt::MEMORY_BASIC_INFORMATION> { - let mut base = 0; - let mut regions = Vec::new(); - let mut info = MaybeUninit::uninit(); - - loop { - // SAFETY: the info structure points to valid memory. - let written = unsafe { - winapi::um::memoryapi::VirtualQueryEx( - self.handle.as_ptr(), - base as *const _, - info.as_mut_ptr(), - mem::size_of::<winapi::um::winnt::MEMORY_BASIC_INFORMATION>(), - ) - }; - if written == 0 { - break regions; - } - // SAFETY: a non-zero amount was written to the structure - let info = unsafe { info.assume_init() }; - base = info.BaseAddress as usize + info.RegionSize; - regions.push(info); - } -} -</code></pre><p><code>RegionSize</code> is:<blockquote><p>The size of the region beginning at the base address in which all pages have identical attributes, in bytes.</blockquote><p>…which also hints that the value we want is "base address", not the "allocation base". With these two values, we can essentially iterate over all the page ranges:<pre><code class=language-rust data-lang=rust>dbg!(process.memory_regions().len()); -</code></pre><pre><code>>cargo run - Compiling memo v0.1.0 - Finished dev [unoptimized + debuginfo] target(s) in 0.63s - Running `target\debug\memo.exe` - -[src\main.rs:189] process.memory_regions().len() = 367 -</code></pre><p>That's a lot of pages!<h2 id=protection-levels>Protection levels</h2><p>Let's try to narrow the amount of pages down. How many pages aren't <code>PAGE_NOACCESS</code>?<pre><code class=language-rust data-lang=rust>dbg!(process - .memory_regions() - .into_iter() - .filter(|p| p.Protect != winapi::um::winnt::PAGE_NOACCESS) - .count()); -</code></pre><pre><code>295 -</code></pre><p>Still a fair bit! Most likely, there are just a few interleaved <code>NOACCESS</code> pages, and the rest are allocated each with different protection levels. How much memory do we need to scan through?<pre><code class=language-rust data-lang=rust>dbg!(process - .memory_regions() - .into_iter() - .filter(|p| p.Protect != winapi::um::winnt::PAGE_NOACCESS) - .map(|p| p.RegionSize) - .sum::<usize>()); -</code></pre><pre><code>4480434176 -</code></pre><p>Wait, what? What do you mean over 4 GiB? The Task Manager claims that the Cheat Engine Tutorial is only using 2.1 MB worth of RAM! Perhaps we can narrow down the <a href=https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants>protection levels</a> a bit more. If you look at the scan options in Cheat Engine, you will notice the "Memory Scan Options" groupbox. By default, it only scans for memory that is writable, and doesn't care if it's executable or not:<pre><code class=language-rust data-lang=rust>let mask = winnt::PAGE_EXECUTE_READWRITE - | winnt::PAGE_EXECUTE_WRITECOPY - | winnt::PAGE_READWRITE - | winnt::PAGE_WRITECOPY; - -dbg!(process - .memory_regions() - .into_iter() - .filter(|p| (p.Protect & mask) != 0) - .map(|p| p.RegionSize) - .sum::<usize>()); -</code></pre><p>Each memory protection level has its own bit, so we can OR them all together to have a single mask. When ANDing this mask with the protection level, if any bit is set, it will be non-zero, meaning we want to keep this region.<p>Don't ask me why there isn't a specific bit for "write", "read", "execute", and there are only bits for combinations. I guess this way Windows forbids certain combinations.<pre><code>2580480 -</code></pre><p>Hey, that's close to the value shown by the Task Manager! A handfull of megabytes is a lot more manageable than 4 entire gigabytes.<h2 id=actually-running-our-first-scan>Actually running our First Scan</h2><p>Okay, we have all the memory regions from which the program can read, write, or execute. Now we also can read the memory in these regions:<pre><code class=language-rust data-lang=rust>let regions = process - .memory_regions() - .into_iter() - .filter(|p| (p.Protect & mask) != 0) - .collect::<Vec<_>>(); - -println!("Scanning {} memory regions", regions.len()); - -regions.into_iter().for_each(|region| { - match process.read_memory(region.BaseAddress as _, region.RegionSize) { - Ok(memory) => todo!(), - Err(err) => eprintln!( - "Failed to read {} bytes at {:?}: {}", - region.RegionSize, region.BaseAddress, err, - ), - } -}) -</code></pre><p>All that's left is for us to scan for a target value. To do this, we want to iterate over all the <a href=https://doc.rust-lang.org/stable/std/primitive.slice.html#method.windows><code>slice::windows</code></a> of size equal to the size of our scan type.<pre><code class=language-rust data-lang=rust>let target: i32 = ...; -let target = target.to_ne_bytes(); - -// -snip- - -// inside the Ok match, replacing the todo!() -- this is where the first scan happens -Ok(memory) => memory - .windows(target.len()) - .enumerate() - .for_each(|(offset, window)| { - if window == target { - println!( - "Found exact value at [{:?}+{:x}]", - region.BaseAddress, offset - ); - } - }) -</code></pre><p>We convert the 32-bit exact target value to its memory representation as a byte array in <a href=https://doc.rust-lang.org/stable/std/primitive.i32.html#method.to_ne_bytes>native byte order</a>. This way we can compare the target bytes with the window bytes. Another option is to interpret the window bytes as an <code>i32</code> with <code>from_be_bytes</code>, but <code>slice::windows</code> gives us slices of type <code>&[u8]</code>, and <code>from_be_bytes</code> wants an <code>[u8; 4]</code> array, so it's a bit more annoying to convert.<p>This is enough to find the value in the process' memory!<pre><code>Found exact value at [0x10000+aec] -Failed to read 12288 bytes at 0x13f8000: Only part of a ReadProcessMemory or WriteProcessMemory request was completed. (os error 299) -Found exact value at [0x14f0000+3188] -Found exact value at [0x14f0000+ac74] -... -Found exact value at [0x10030e000+1816] -Found exact value at [0x7ff8f7b93000+441a] -... -Found exact value at [0x7ff8fb381000+4023] -</code></pre><p>The tutorial starts out with health "100", which is what I scanned. Apparently, there are nearly a hundred of <code>100</code>-valued integers stored in the memory of the tutorial.<p>Attentive readers will notice that some values are located at an offset modulo 4. In Cheat Engine, this is known as "Fast Scan", which is enabled by default with an alignment of 4. Most of the time, values are aligned in memory, and this alignment often corresponds with the size of the type itself. For 4-byte integers, it's common that they're 4-byte aligned.<p>We can perform a fast scan ourselves with <a href=https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.step_by><code>step_by</code></a><sup class=footnote-reference><a href=#5>5</a></sup>:<pre><code class=language-rust data-lang=rust>memory - .windows(target.len()) - .enumerate() - .step_by(4) - .for_each(...) -</code></pre><p>As a bonus, over half the addresses are gone, so we have less results to worry about<sup class=footnote-reference><a href=#6>6</a></sup>.<h2 id=next-scan>Next Scan</h2><p>The first scan gave us way too many results. We have no way to tell which is the correct one, as they all have the same value. What we need to do is a <em>second</em> scan at the <em>locations we just found</em>. This way, we can get a second reading, and compare it against a new value. If it's the same, we're on good track, and if not, we can discard a location. Repeating this process lets us cut the hundreds of potential addresses to just a handful of them.<p>For example, let's say we're scanning our current health of <code>100</code> in a game. This gives us over a hundred addresses that point to the value of <code>100</code>. If we go in-game and get hit<sup class=footnote-reference><a href=#7>7</a></sup> by some enemy and get our health down to, say, <code>99</code> (we have a lot of defense), we can then read the memory at the hundred memory locations we found before. If this second reading is not <code>99</code>, we know the address does not actually point to our health pool and it just happened to also contain a <code>100</code> on the first scan. This address can be removed from the list of potential addresses pointing to our health.<p>Let's do that:<pre><code class=language-rust data-lang=rust>// new vector to hold the locations, before getting into `memory.windows`' for-each -let mut locations = Vec::with_capacity(regions.len()); - -// -snip- - -// updating the `println!("Found exact value...")` to store the location instead. -if window == target { - locations.push(region.BaseAddress as usize + offset); -} - -// -snip- - -// performing a second scan on the locations the first scan found. -let target: i32 = ...; -let target = target.to_ne_bytes(); -locations.retain(|addr| match process.read_memory(*addr, target.len()) { - Ok(memory) => memory == target, - Err(_) => false, -}); - -println!("Now have {} locations", locations.len()); -</code></pre><p>We create a vector to store all the locations the first scan finds, and then retain those that match a second target value. You may have noticed that we perform a memory read, and thus a call to the Windows API, for every single address. With a hundred locations to read from, this is not a big deal, but oftentimes you will have tens of thousands of addresses. For the time being, we will not worry about this inefficiency, but we will get back to it once it matters:<pre><code>Scanning 98 memory regions -Which exact value to scan for?: 100 -Failed to read 12288 bytes at 0x13f8000: Only part of a ReadProcessMemory or WriteProcessMemory request was completed. (os error 299) -... -Found 49 locations -Which exact value to scan for next?: 99 -Now have 1 locations -</code></pre><p>Sweet! In a real-world scenario, you will likely need to perform these additional scans a couple of times, and even then, there may be more than one value left no matter what.<p>For good measure, we'll wrap our <code>retain</code> in a <code>while</code> loop<sup class=footnote-reference><a href=#8>8</a></sup>:<pre><code class=language-rust data-lang=rust>while locations.len() != 1 { - let target: i32 = ...; - let target = target.to_ne_bytes(); - locations.retain(...); -} -</code></pre><h2 id=modifying-memory>Modifying memory</h2><p>Now that we have very likely locations pointing to our current health in memory, all that's left is writing our new desired value to gain infinite health<sup class=footnote-reference><a href=#9>9</a></sup>. Much like how we're able to read memory with <code>ReadProcessMemory</code>, we can write to it with <a href=https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory><code>WriteProcessMemory</code></a>. Its usage is straightforward:<pre><code class=language-rust data-lang=rust>pub fn write_memory(&self, addr: usize, value: &[u8]) -> io::Result<usize> { - let mut written = 0; - - // SAFETY: the input value buffer points to valid memory. - if unsafe { - winapi::um::memoryapi::WriteProcessMemory( - self.handle.as_ptr(), - addr as *mut _, - value.as_ptr().cast(), - value.len(), - &mut written, - ) - } == FALSE - { - Err(io::Error::last_os_error()) - } else { - Ok(written) - } -} -</code></pre><p>Similar to how writing to a file can return short, writing to a memory location could also return short. Here we mimic the API for writing files and return the number of bytes written. The documentation indicates that we could actually ignore the amount written by passing <code>ptr::null_mut()</code> as the last parameter, but it does no harm to retrieve the written count as well.<pre><code class=language-rust data-lang=rust>let new_value: i32 = ...; -locations - .into_iter() - .for_each(|addr| match process.write_memory(addr, &new_value) { - Ok(n) => eprintln!("Written {} bytes to [{:x}]", n, addr), - Err(e) => eprintln!("Failed to write to [{:x}]: {}", addr, e), - }); -</code></pre><p>And just like that:<pre><code>Now have 1 location(s) -Enter new memory value: 1000 -Failed to write to [15d8b90]: Access is denied. (os error 5) -</code></pre><p>…oh noes. Oh yeah. The documentation, which I totally didn't forget to read, mentions:<blockquote><p>The handle must have <code>PROCESS_VM_WRITE</code> and <code>PROCESS_VM_OPERATION</code> access to the process.</blockquote><p>We currently open our process with <code>PROCESS_QUERY_INFORMATION</code> and <code>PROCESS_VM_READ</code>, which is enough for reading, but not for writing. Let's adjust <code>OpenProcess</code> to accomodate for our new requirements:<pre><code class=language-rust data-lang=rust>winapi::um::processthreadsapi::OpenProcess( - winnt::PROCESS_QUERY_INFORMATION - | winnt::PROCESS_VM_READ - | winnt::PROCESS_VM_WRITE - | winnt::PROCESS_VM_OPERATION, - FALSE, - pid, -) -</code></pre><p>Behold:<pre><code>Now have 1 location(s) -Enter new memory value: 1000 -Written 4 bytes to [15d8b90] -</code></pre><p><img src=https://user-images.githubusercontent.com/6297805/107829541-3f4f2d00-6d8a-11eb-87c4-e2f2d505afbc.png alt="Tutorial complete with memo"><p>Isn't that active <em>Next</em> button just beautiful?<h2 id=finale>Finale</h2><p>This post somehow ended up being longer than part one, but look at what we've achieved! We completed a step of the Cheat Engine Tutorial <em>without using Cheat Engine</em>. Just pure Rust. Figuring out how a program works and reimplementing it yourself is a great way to learn what it's doing behind the scenes. And now that this code is yours, you can extend it as much as you like, without being constrained by Cheat Engine's UI. You can automate it as much as you want.<p>And we're not even done. The current tutorial has nine steps, and three additional graphical levels.<p>In the <a href=/blog/woce-3>next post</a>, we'll tackle the third step of the tutorial: Unknown initial value. This will pose a challenge, because with just 2 MiB of memory, storing all the 4-byte aligned locations would require 524288 addresses (<code>usize</code>, 8 bytes). This adds up to twice as much memory as the original program (4 MiB), but that's not our main concern, having to perform over five hundred thousand API calls is!<p>Remember that you can <a href=https://github.com/lonami/memo>obtain the code for this post</a> over at my GitHub. You can run <code>git checkout step2</code> after cloning the repository to get the right version of the code.<h3 id=footnotes>Footnotes</h3><div class=footnote-definition id=1><sup class=footnote-definition-label>1</sup><p>I did in fact use an online tool to spell it out for me.</div><div class=footnote-definition id=2><sup class=footnote-definition-label>2</sup><p>16 GiB is good enough for my needs. I don't think I'll ever upgrade to 16 EiB.</div><div class=footnote-definition id=3><sup class=footnote-definition-label>3</sup><p>Every address we query should have a corresponding region, even if it's not allocated or we do not have access. This is why we can query for the memory address zero to get its corresponding region.</div><div class=footnote-definition id=4><sup class=footnote-definition-label>4</sup><p>Another option is to <a href=https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsysteminfo><code>GetSystemInfo</code></a> to determine the <code>lpMinimumApplicationAddress</code> and <code>lpMaximumApplicationAddress</code> and only work within bounds.</div><div class=footnote-definition id=5><sup class=footnote-definition-label>5</sup><p>Memory regions are page-aligned, which is a large power of two. Our alignment of 4 is much lower than this, so we're guaranteed to start off at an aligned address.</div><div class=footnote-definition id=6><sup class=footnote-definition-label>6</sup><p>If it turns out that the value was actually misaligned, we will miss it. You will notice this if, after going through the whole process, there are no results. It could mean that either the value type is wrong, or the value type is misaligned. In the worst case, the value is not stored directly but is rather computed with something like <code>maximum - stored</code>, or XORed with some magic value, or a myriad other things.</div><div class=footnote-definition id=7><sup class=footnote-definition-label>7</sup><p>You could do this without getting hit, and just keep on repeating the scan for the same value over and over again. This does work, but the results are suboptimal, because there are also many other values that didn't change. Scanning for a changed value is a better option.</div><div class=footnote-definition id=8><sup class=footnote-definition-label>8</sup><p>You could actually just go ahead and try to modify the memory at the hundred addresses you just found, although don't be surprised if the program starts to misbehave!</div><div class=footnote-definition id=9><sup class=footnote-definition-label>9</sup><p>Okay, we cannot fit infinity in an <code>i32</code>. However, we can fit sufficiently large numbers. Like <code>1000</code>, which is enough to complete the tutorial.</div></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,213 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Writing our own Cheat Engine: Unknown initial value | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Writing our own Cheat Engine: Unknown initial value</h1><div class=time><p>2021-02-19</div><p>This is part 3 on the <em>Writing our own Cheat Engine</em> series:<ul><li><a href=/blog/woce-1>Part 1: Introduction</a> (start here if you're new to the series!)<li><a href=/blog/woce-2>Part 2: Exact Value scanning</a><li>Part 3: Unknown initial value<li><a href=/blog/woce-4>Part 4: Floating points</a><li><a href=/blog/woce-5>Part 5: Code finder</a><li><a href=/blog/woce-6>Part 6: Pointers</a></ul><p>In part 2 we left off with a bit of a cliff-hanger. Our little program is now able to scan for an exact value, remember the couple hundred addresses pointing to said value, and perform subsequent scans to narrow the list of addresses down until we're left with a handful of them.<p>However, it is not always the case that you have an exact value to work with. The best you can do in these cases is guess what the software might be storing. For example, it could be a floating point for your current movement speed in a game, or an integer for your current health.<p>The problem with this is that there are far too many possible locations storing our desired value. If you count misaligned locations, this means there is a different location to address every single byte in memory. A program with one megabyte of memory already has a <em>million</em> of addresses. Clearly, we need to do better than performing one million memory reads<sup class=footnote-reference><a href=#1>1</a></sup>.<p>This post will shift focus a bit from using <code>winapi</code> to possible techniques to perform the various scans.<h2 id=unknown-initial-value>Unknown initial value</h2><details open><summary>Cheat Engine Tutorial: Step 3</summary> <blockquote><p>Ok, seeing that you've figured out how to find a value using exact value let's move on to the next step.<p>First things first though. Since you are doing a new scan, you have to click on New Scan first, to start a new scan. (You may think this is straighforward, but you'd be surprised how many people get stuck on that step) I won't be explaining this step again, so keep this in mind Now that you've started a new scan, let's continue<p>In the previous test we knew the initial value so we could do a exact value, but now we have a status bar where we don't know the starting value. We only know that the value is between 0 and 500. And each time you click 'hit me' you lose some health. The amount you lose each time is shown above the status bar.<p>Again there are several different ways to find the value. (like doing a decreased value by... scan), but I'll only explain the easiest. "Unknown initial value", and decreased value. Because you don't know the value it is right now, a exact value wont do any good, so choose as scantype 'Unknown initial value', again, the value type is 4-bytes. (most windows apps use 4-bytes)click first scan and wait till it's done.<p>When it is done click 'hit me'. You'll lose some of your health. (the amount you lost shows for a few seconds and then disappears, but you don't need that) Now go to Cheat Engine, and choose 'Decreased Value' and click 'Next Scan' When that scan is done, click hit me again, and repeat the above till you only find a few.<p>We know the value is between 0 and 500, so pick the one that is most likely the address we need, and add it to the list. Now change the health to 5000, to proceed to the next step.</blockquote></details><h2 id=dense-memory-locations>Dense memory locations</h2><p>The key thing to notice here is that, when we read memory from another process, we do so over <em>entire regions</em>. A memory region is represented by a starting offset, a size, and a bunch of other things like protection level.<p>When running the first scan for an unknown value, all we need to remember is the starting offset and size for every single region. All the candidate locations that could point to our value fall within this range, so it is enough for us to store the range definition, and not every location within it.<p>To gain a better understanding of what this means, let's come up with a more specific scenario. With our current approach of doing things, we store an address (<code>usize</code>) for every location pointing to our desired value. In the case of unknown values, all locations are equally valid, since we don't know what value they should point to yet, and any value they point to is good. With this representation, we would end up with a very large vector:<pre><code class=language-rust data-lang=rust>let locations = vec![0x2000, 0x2001, ..., 0x20ff, 0x2100]; -</code></pre><p>This representation is dense. Every single number in the range <code>0x2000..=0x2100</code> is present. So why bother storing the values individually when the range is enough?:<pre><code class=language-rust data-lang=rust>let locations = EntireRegion { range: 0x2000..=0x2100 }; -</code></pre><p>Much better! With two <code>usize</code>, one for the starting location and another for the end, we can indicate that we care about all the locations falling in that range.<p>In fact, some accessible memory regions immediately follow eachother, so we could even compact this further and merge regions which are together. But due to their potential differences with regards to protection levels, we will not attempt to merge regions.<p>We don't want to get rid of the old way of storing locations, because once we start narrowing them down, we will want to go back to storing just a few candidates. To keep things tidy, let's introduce a new <code>enum</code> representing either possibility:<pre><code class=language-rust data-lang=rust>use std::ops::Range; - -pub enum CandidateLocations { - Discrete { - locations: Vec<usize>, - }, - Dense { - range: Range<usize>, - } -} -</code></pre><p>Let's also introduce another <code>enum</code> to perform the different scan types. For the time being, we will only worry about looking for <code>i32</code> in memory:<pre><code class=language-rust data-lang=rust>pub enum Scan { - Exact(i32), - Unknown, -} -</code></pre><h2 id=storing-scanned-values>Storing scanned values</h2><p>When scanning for exact values, it's not necessary to store the value found. We already know they're all the same, for example, value <code>42</code>. However, if the value is unknown, we do need to store it so that we can compare it in a subsequent scan to see if the value is the same or it changed. This means the value can be "any within" the read memory chunk:<pre><code class=language-rust data-lang=rust>pub enum Value { - Exact(i32), - AnyWithin(Vec<u8>), -} -</code></pre><p>For every region in memory, there will be some candidate locations and a value (or value range) we need to compare against in subsequent scans:<pre><code class=language-rust data-lang=rust>pub struct Region { - pub info: winapi::um::winnt::MEMORY_BASIC_INFORMATION, - pub locations: CandidateLocations, - pub value: Value, -} -</code></pre><p>With all the data structures needed setup, we can finally refactor our old scanning code into a new method capable of dealing with all these cases. For brevity, I will omit the exact scan, as it remains mostly unchanged:<pre><code class=language-rust data-lang=rust>use winapi::um::winnt::MEMORY_BASIC_INFORMATION; - -... - -// inside `impl Process` -pub fn scan_regions(&self, regions: &[MEMORY_BASIC_INFORMATION], scan: Scan) -> Vec<Region> { - regions - .iter() - .flat_map(|region| match scan { - Scan::Exact(n) => todo!("old scan implementation"), - Scan::Unknown => { - let base = region.BaseAddress as usize; - match self.read_memory(region.BaseAddress as _, region.RegionSize) { - Ok(memory) => Some(Region { - info: region.clone(), - locations: CandidateLocations::Dense { - range: base..base + region.RegionSize, - }, - value: Value::AnyWithin(memory), - }), - Err(_) => None, - } - } - }) - .collect() -} -</code></pre><p>Time to try it out!<pre><code class=language-rust data-lang=rust>impl CandidateLocations { - pub fn len(&self) -> usize { - match self { - CandidateLocations::Discrete { locations } => locations.len(), - CandidateLocations::Dense { range } => range.len(), - } - } -} - -... - -fn main() { - // -snip- - - println!("Scanning {} memory regions", regions.len()); - let last_scan = process.scan_regions(&regions, Scan::Unknown); - println!( - "Found {} locations", - last_scan.iter().map(|r| r.locations.len()).sum::<usize>() - ); -} -</code></pre><pre><code>Scanning 88 memory regions -Found 3014656 locations -</code></pre><p>If we consider misaligned locations, there is a lot of potential addresses where we could look for. Running the same scan on Cheat Engine yields <code>2,449,408</code> addresses, which is pretty close. It's probably skipping some additional regions that we are considering. Emulating Cheat Engine to perfection is not a concern for us at the moment, so I'm not going to investigate what regions it actually uses.<h2 id=comparing-scanned-values>Comparing scanned values</h2><p>Now that we have performed the initial scan and have stored all the <code>CandidateLocations</code> and <code>Value</code>, we can re-implement the "next scan" step to handle any variant of our <code>Scan</code> enum. This enables us to mix-and-match any <code>Scan</code> mode in any order. For example, one could perform an exact scan, then one for decreased values, or start with unknown scan and scan for unchanged values.<p>The tutorial suggests using "decreased value" scan, so let's start with that:<pre><code class=language-rust data-lang=rust>pub enum Scan { - Exact(i32), - Unknown, - Decreased, // new! -} -</code></pre><p>Other scanning modes, such as decreased by a known amount rather than any decrease, increased, unchanged, changed and so on, are not very different from the "decreased" scan, so I won't bore you with the details.<p>I will use a different method to perform a "rescan", since the first one is a bit more special in that it doesn't start with any previous values:<pre><code class=language-rust data-lang=rust>pub fn rescan_regions(&self, regions: &[Region], scan: Scan) -> Vec<Region> { - regions - .iter() - .flat_map(|region| match scan { - Scan::Decreased => { - let mut locations = Vec::new(); - match region.locations { - CandidateLocations::Dense { range } => { - match self.read_memory(range.start, range.end - range.start) { - Ok(memory) => match region.value { - Value::AnyWithin(previous) => { - memory - .windows(4) - .zip(previous.windows(4)) - .enumerate() - .step_by(4) - .for_each(|(offset, (new, old))| { - let new = i32::from_ne_bytes([ - new[0], new[1], new[2], new[3], - ]); - let old = i32::from_ne_bytes([ - old[0], old[1], old[2], old[3], - ]); - if new < old { - locations.push(range.start + offset); - } - }); - - Some(Region { - info: region.info.clone(), - locations: CandidateLocations::Discrete { locations }, - value: Value::AnyWithin(memory), - }) - } - _ => todo!(), - }, - _ => todo!(), - } - } - _ => todo!(), - } - } - _ => todo!(), - }) - .collect() -} -</code></pre><p>If you've skimmed over that, I do not blame you. Here's the summary: for every existing region, when executing the scan mode "decreased", if the previous locations were dense, read the entire memory region. On success, if the previous values were a chunk of memory, iterate over the current and old memory at the same time, and for every aligned <code>i32</code>, if the new value is less, store it.<p>It's also making me ill. Before I leave a mess on the floor, does it work?<pre><code class=language-rust data-lang=rust>std::thread::sleep(std::time::Duration::from_secs(10)); -let last_scan = process.rescan_regions(&last_scan, Scan::Decreased); -println!( - "Found {} locations", - last_scan.iter().map(|r| r.locations.len()).sum::<usize>() -); -</code></pre><pre><code class=language-rust data-lang=rust>Found 3014656 locations -Found 177 locations -</code></pre><p>Okay, great, let's clean up this mess…<h2 id=refactoring>Refactoring</h2><p>Does it also make you uncomfortable to be writing something that you know will end up <em>huge</em> unless you begin refactoring other parts right now? I definitely feel that way. But I think it's good discipline to push through with something that works first, even if it's nasty, before going on a tangent. Now that we have the basic implementation working, let's take on this monster before it eats us alive.<p>First things first, that method is inside an <code>impl</code> block. The deepest nesting level is 13. I almost have to turn around my chair to read the entire thing out!<p>Second, we're nesting four matches. Three of them we care about: scan, candidate location, and value. If each of these <code>enum</code> has <code>S</code>, <code>C</code> and <code>V</code> variants respectively, writing each of these by hand will require <code>S * C * V</code> different implementations! Cheat Engine offers 10 different scans, I can think of at least 3 different ways to store candidate locations, and another 3 ways to store the values found. That's <code>10 * 3 * 3 = 90</code> different combinations. I am not willing to write out all these<sup class=footnote-reference><a href=#2>2</a></sup>, so we need to start introducing some abstractions. Just imagine what a monster function you would end with! The horror!<p>Third, why is the scan being executed in the process? This is something that should be done in the <code>impl Scan</code> instead!<p>Let's begin the cleanup:<pre><code class=language-rust data-lang=rust>pub fn rescan_regions(&self, regions: &[Region], scan: Scan) -> Vec<Region> { - todo!() -} -</code></pre><p>I already feel ten times better.<p>Now, this method will unconditionally read the entire memory region, even if the scan or the previous candidate locations don't need it<sup class=footnote-reference><a href=#3>3</a></sup>. In the worst case with a single discrete candidate location, we will be reading a very large chunk of memory when we could have read just the 4 bytes needed for the <code>i32</code>. On the bright side, if there <em>are</em> more locations in this memory region, we will get read of them at the same time<sup class=footnote-reference><a href=#4>4</a></sup>. So even if we're moving more memory around all the time, it isn't <em>too</em> bad.<pre><code class=language-rust data-lang=rust>regions - .iter() - .flat_map( - |region| match self.read_memory(region.info.BaseAddress as _, region.info.RegionSize) { - Ok(memory) => todo!(), - Err(err) => { - eprintln!( - "Failed to read {} bytes at {:?}: {}", - region.info.RegionSize, region.info.BaseAddress, err, - ); - None - } - }, - ) - .collect() -</code></pre><p>Great! If reading memory succeeds, we want to rerun the scan:<pre><code class=language-rust data-lang=rust>Ok(memory) => Some(scan.rerun(region, memory)), -</code></pre><p>The rerun will live inside <code>impl Scan</code>:<pre><code class=language-rust data-lang=rust>pub fn rerun(&self, region: &Region, memory: Vec<u8>) -> Region { - match self { - Scan::Exact(_) => self.run(region.info.clone(), memory), - Scan::Unknown => region.clone(), - Scan::Decreased => todo!(), - } -} -</code></pre><p>An exact scan doesn't care about any previous values, so it behaves like a first scan. The first scan is done by the <code>run</code> function (it contains the implementation factored out of the <code>Process::scan_regions</code> method), which only needs the region information and the current memory chunk we just read.<p>The unknown scan leaves the region unchanged: any value stored is still valid, because it is unknown what we're looking for.<p>The decreased scan will have to iterate over all the candidate locations, and compare them with the current memory chunk. But this time, we'll abstract this iteration too:<pre><code class=language-rust data-lang=rust>impl Region { - fn iter_locations<'a>( - &'a self, - new_memory: &'a [u8], - ) -> impl Iterator<Item = (usize, i32, i32)> + 'a { - match &self.locations { - CandidateLocations::Dense { range } => range.clone().step_by(4).map(move |addr| { - let old = self.value_at(addr); - let new = i32::from_ne_bytes([ - new_memory[0], - new_memory[1], - new_memory[2], - new_memory[3], - ]); - (addr, old, new) - }), - _ => todo!(), - } - } -} -</code></pre><p>For a dense candidate location, we iterate over all the 4-aligned addresses (fast scan for <code>i32</code> values), and yield <code>(current address, old value, new value)</code>. This way, the <code>Scan</code> can do anything it wants with the old and new values, and if it finds a match, it can use the address.<p>The <code>value_at</code> method will deal with all the <code>Value</code> variants:<pre><code class=language-rust data-lang=rust>fn value_at(&self, addr: usize) -> i32 { - match &self.value { - Value::AnyWithin(chunk) => { - let base = addr - self.info.BaseAddress as usize; - let bytes = &chunk[base..base + 4]; - i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) - } - _ => todo!(), - } -} -</code></pre><p>This way, <code>iter_locations</code> can easily use any value type. With this, we have all <code>enum</code> covered: <code>Scan</code> in <code>rerun</code>, <code>CandidateLocation</code> in <code>iter_locations</code>, and <code>Value</code> in <code>value_at</code>. Now we can add as many variants as we want, and we will only need to update a single <code>match</code> arm for each of them. Let's implement <code>Scan::Decreased</code> and try it out:<pre><code class=language-rust data-lang=rust>pub fn rerun(&self, region: &Region, memory: Vec<u8>) -> Region { - match self { - Scan::Decreased => Region { - info: region.info.clone(), - locations: CandidateLocations::Discrete { - locations: region - .iter_locations(&memory) - .flat_map(|(addr, old, new)| if new < old { Some(addr) } else { None }) - .collect(), - }, - value: Value::AnyWithin(memory), - },, - } -} -</code></pre><pre><code>Found 3014656 locations -Found 223791 locations -</code></pre><p>Hmm… before we went down from <code>3014656</code> to <code>177</code> locations, and now we went down to <code>223791</code>. Where did we go wrong?<p>After spending several hours on this, I can tell you where we went wrong. <code>iter_locations</code> is always accessing the memory range <code>0..4</code>, and not the right address. Here's the fix:<pre><code class=language-rust data-lang=rust>CandidateLocations::Dense { range } => range.clone().step_by(4).map(move |addr| { - let old = self.value_at(addr); - let base = addr - self.info.BaseAddress as usize; - let bytes = &new_memory[base..base + 4]; - let new = i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); - (addr, old, new) -}), -</code></pre><h2 id=going-beyond>Going beyond</h2><p>Let's take a look at other possible <code>Scan</code> types. Cheat Engine supports the following initial scan types:<ul><li>Exact Value<li>Bigger than…<li>Smaller than…<li>Value between…<li>Unknown initial value</ul><p>"Bigger than" and "Smaller than" can both be represented by "Value between", so it's pretty much just three.<p>For subsequent scans, in addition to the scan types described above, we find:<ul><li>Increased value<li>Increased value by…<li>Decreased value<li>Decreased value by…<li>Changed value<li>Unchanged value</ul><p>Not only does Cheat Engine provide all of these scans, but all of them can also be negated. For example, "find values that were not increased by 7". One could imagine to also support things like "increased value by range". For the increased and decreased scans, Cheat Engine also supports "at least xx%", so that if the value changed within the specified percentage interval, it will be considered.<p>What about <code>CandidateLocations</code>? I can't tell you how Cheat Engine stores these, but I can tell you that <code>CandidateLocations::Discrete</code> can still be quite inefficient. Imagine you've started with a scan for unknown values and then ran a scan for unchanged valueus. Most values in memory will have been unchanged, but with our current implementation, we are now storing an entire <code>usize</code> address for each of these. One option would be to introduce <code>CandidateLocations::Sparse</code>, which would be a middle ground. You could implement it like <code>Dense</code> and include a vector of booleans telling you which values to consider, or go smaller and use a bitstring or bit vector. You could use a sparse vector data structure.<p><code>Value</code> is very much like <code>CandidateLocations</code>, except that it stores a value to compare against and not an address. Here we can either have an exact value, or an older copy of the memory. Again, keeping a copy of the entire memory chunk when all we need is a handful of values is inefficient. You could keep a mapping from addresses to values if you don't have too many. Or you could shrink and fragment the copied memory in a more optimal way. There's a lot of room for improvement!<p>What if, despite all of the efforts above, we still don't have enough RAM to store all this information? The Cheat Engine Tutorial doesn't use a lot of memory, but as soon as you try scanning bigger programs, like games, you may find yourself needing several gigabytes worth of memory to remember all the found values in order to compare them in subsequent scans. You may even need to consider dumping all the regions to a file and read from it to run the comparisons. For example, running a scan for "unknown value" in Cheat Engine brings its memory up by the same amount of memory used by the process scanned (which makes sense), but as soon as I ran a scan for "unchanged value" over the misaligned values, Cheat Engine's disk usage skyrocketed to 1GB/s (!) for several seconds on my SSD. After it finished, memory usage went down to normal. It was very likely writing out all candidate locations to disk.<h2 id=finale>Finale</h2><p>There is a lot of things to learn from Cheat Engine just by observing its behaviour, and we're only scratching its surface.<p>In the <a href=/blog/woce-4>next post</a>, we'll tackle the fourth step of the tutorial: Floating points. So far, we have only been working with <code>i32</code> for simplicity. We will need to update our code to be able to account for different data types, which will make it easy to support other types like <code>i16</code>, <code>i64</code>, or even strings, represented as an arbitrary sequence of bytes.<p>As usual, you can <a href=https://github.com/lonami/memo>obtain the code for this post</a> over at my GitHub. You can run <code>git checkout step3</code> after cloning the repository to get the right version of the code. This version is a bit cleaner than the one presented in the blog, and contains some of the things described in the <a href=https://lonami.dev/blog/woce-3/#going-beyond>Going beyond</a> section. Until next time!<h3 id=footnotes>Footnotes</h3><div class=footnote-definition id=1><sup class=footnote-definition-label>1</sup><p>Well, technically, we will perform a million memory reads<sup class=footnote-reference><a href=#5>5</a></sup>. The issue here is the million calls to <code>ReadProcessMemory</code>, not reading memory per se.</div><div class=footnote-definition id=2><sup class=footnote-definition-label>2</sup><p>Not currently. After a basic implementation works, writing each implementation by hand and fine-tuning them by treating each of them as a special case could yield significant speed improvements. So although it would be a lot of work, this option shouldn't be ruled out completely.</div><div class=footnote-definition id=3><sup class=footnote-definition-label>3</sup><p>You could ask the candidate locations where one should read, which would still keep the code reasonably simple.</div><div class=footnote-definition id=4><sup class=footnote-definition-label>4</sup><p>You could also optimize for this case by determining both the smallest and largest address, and reading enough to cover them both. Or apply additional heuristics to only do so if the ratio of the size you're reading compared to the size you need isn't too large and abort the joint read otherwise. There is a lot of room for optimization here.</div><div class=footnote-definition id=5><sup class=footnote-definition-label>5</sup><p>(A footnote in a footnote?) The machine registers, memory cache and compiler will all help lower this cost, so the generated executable might not actually need that many reads from RAM. But that's getting way too deep into the details now.</div></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,198 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Writing our own Cheat Engine: Floating points | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Writing our own Cheat Engine: Floating points</h1><div class=time><p>2021-02-28</div><p>This is part 4 on the <em>Writing our own Cheat Engine</em> series:<ul><li><a href=/blog/woce-1>Part 1: Introduction</a> (start here if you're new to the series!)<li><a href=/blog/woce-2>Part 2: Exact Value scanning</a><li><a href=/blog/woce-3>Part 3: Unknown initial value</a><li>Part 4: Floating points<li><a href=/blog/woce-5>Part 5: Code finder</a><li><a href=/blog/woce-6>Part 6: Pointers</a></ul><p>In part 3 we did a fair amount of plumbing in order to support scan modes beyond the trivial "exact value scan". As a result, we have abstracted away the <code>Scan</code>, <code>CandidateLocations</code> and <code>Value</code> types as a separate <code>enum</code> each. Scanning for changed memory regions in an opened process can now be achieved with three lines of code:<pre><code class=language-rust data-lang=rust>let regions = process.memory_regions(); -let first_scan = process.scan_regions(&regions, Scan::InRange(0, 500)); -let second_scan = process.rescan_regions(&first_scan, Scan::DecreasedBy(7)); -</code></pre><p>How's that for programmability? No need to fire up Cheat Engine's GUI anymore!<p>The <code>first_scan</code> in the example above remembers all the found <code>Value</code> within the range specified by <code>Scan</code>. Up until now, we have only worked with <code>i32</code>, so that's the type the scans expect and what they work with.<p>Now it's time to introduce support for different types, like <code>f32</code>, <code>i64</code>, or even more atypical ones, like arbitrary sequences of bytes (think of strings) or even numbers in big-endian.<p>Tighten your belt, because this post is quite the ride. Let's get right into it!<h2 id=floating-points>Floating points</h2><details open><summary>Cheat Engine Tutorial: Step 4</summary> <blockquote><p>In the previous tutorial we used bytes to scan, but some games store information in so called 'floating point' notations. (probably to prevent simple memory scanners from finding it the easy way). A floating point is a value with some digits behind the point. (like 5.12 or 11321.1)<p>Below you see your health and ammo. Both are stored as Floating point notations, but health is stored as a float and ammo is stored as a double. Click on hit me to lose some health, and on shoot to decrease your ammo with 0.5<p>You have to set BOTH values to 5000 or higher to proceed.<p>Exact value scan will work fine here, but you may want to experiment with other types too.<p>Hint: It is recommended to disable "Fast Scan" for type double</blockquote></details><h2 id=generic-values>Generic values</h2><p>The <code>Value</code> enumeration holds scanned values, and is currently hardcoded to store <code>i32</code>. The <code>Scan</code> type also holds a value, the value we want to scan for. Changing it to support other types is trivial:<pre><code class=language-rust data-lang=rust>pub enum Scan<T> { - Exact(T), - Unknown, - Decreased, - // ...other variants... -} - -pub enum Value<T> { - Exact(T), - AnyWithin(Vec<u8>), -} -</code></pre><p><code>AnyWithin</code> is the raw memory, and <code>T</code> can be interpreted from any sequence of bytes thanks to our friend <a href=https://doc.rust-lang.org/stable/std/mem/fn.transmute.html><code>mem::transmute</code></a>. This change alone is enough to store an arbitrary <code>T</code>! So we're done now? Not really, no.<p>First of all, we need to update all the places where <code>Scan</code> or <code>Value</code> are used. Our first stop is the scanned <code>Region</code>, which holds the found <code>Value</code>:<pre><code class=language-rust data-lang=rust>pub struct Region<T> { - pub info: MEMORY_BASIC_INFORMATION, - pub locations: CandidateLocations, - pub value: Value<T>, -} -</code></pre><p>Then, we need to update everywhere <code>Region</code> is used, and on and on… All in all this process is just repeating <code>cargo check</code>, letting the compiler vent on you, and taking good care of it by fixing the errors. It's quite reassuring to know you will not miss a single place. Thank you, compiler!<p>But wait, how could scanning for a decreased value work for any <code>T</code>? The type is not <code>Ord</code>, we should add some trait bounds. And also, what happens if the type is not <code>Copy</code>? It could implement <code>Drop</code><sup class=footnote-reference><a href=#1>1</a></sup>, and we will be transmuting from raw bytes, which would trigger the <code>Drop</code> implementation when we're done with the value! Not memory safe at all! And how could we possibly cast raw memory to the type without knowing its siz– oh nevermind, <a href=https://doc.rust-lang.org/stable/std/marker/trait.Sized.html><code>T</code> is already <code>Sized</code> by default</a>. But anyway, we need the other bounds.<p>In order to not repeat ourselves, we will implement a new <code>trait</code>, let's say <code>Scannable</code>, which requires all other bounds:<pre><code class=language-rust data-lang=rust>pub trait Scannable: Copy + PartialEq + PartialOrd {} - -impl<T: Copy + PartialEq + PartialOrd> Scannable for T {} -</code></pre><p>And fix our definitions:<pre><code class=language-rust data-lang=rust>pub enum Scan<T: Scannable> { ... } -pub enum Value<T: Scannable> { ... } -pub struct Region<T: Scannable> { ... } - -// ...and the many other places referring to T -</code></pre><p>Every type which is <code>Copy</code>, <code>PartialEq</code> and <code>PartialOrd</code> can be scanned over<sup class=footnote-reference><a href=#2>2</a></sup>, because we <code>impl Scan for T</code> where the bounds are met. Unfortunately, we cannot require <code>Eq</code> or <code>Ord</code> because the floating point types do not implement it.<h2 id=transmuting-memory>Transmuting memory</h2><p>Also known as reinterpreting a bunch of bytes as something else, or perhaps it stands for "summoning the demon":<blockquote><p><code>transmute</code> is <strong>incredibly</strong> unsafe. There are a vast number of ways to cause <a href=https://doc.rust-lang.org/stable/reference/behavior-considered-undefined.html>undefined behavior</a> with this function. <code>transmute</code> should be the absolute last resort.</blockquote><p>Types like <code>i32</code> define methods such as <a href=https://doc.rust-lang.org/stable/std/primitive.i32.html#method.from_ne_bytes><code>from_ne_bytes</code></a> and <a href=https://doc.rust-lang.org/stable/std/primitive.i32.html#method.to_ne_bytes><code>to_ne_bytes</code></a> which convert raw bytes from and into its native representation. This is all really nice, but unfortunately, there's no standard trait in the Rust's standard library to "interpret a type <code>T</code> as the byte sequence of its native representation". <code>transmute</code>, however, does exist, and similar to any other <code>unsafe</code> function, it's safe to call <strong>as long as we respect its invariants</strong>. What are these invariants<sup class=footnote-reference><a href=#3>3</a></sup>?<blockquote><p>Both types must have the same size</blockquote><p>Okay, we can just assert that the window length matches the type's length. What else?<blockquote><p>Neither the original, nor the result, may be an <a href=https://doc.rust-lang.org/nomicon/what-unsafe-does.html>invalid value</a>.</blockquote><p>What's an invalid value?<blockquote><ul><li>a <code>bool</code> that isn't 0 or 1<li>an <code>enum</code> with an invalid discriminant<li>a null <code>fn</code> pointer<li>a <code>char</code> outside the ranges [0x0, 0xD7FF] and [0xE000, 0x10FFFF]<li>a <code>!</code> (all values are invalid for this type)<li>an integer (<code>i*</code>/<code>u*</code>), floating point value (<code>f*</code>), or raw pointer read from uninitialized memory, or uninitialized memory in a <code>str</code>.<li>a reference/<code>Box</code> that is dangling, unaligned, or points to an invalid value.<li>a wide reference, <code>Box</code>, or raw pointer that has invalid metadata: <ul><li><code>dyn Trait</code> metadata is invalid if it is not a pointer to a vtable for <code>Trait</code> that matches the actual dynamic trait the pointer or reference points to<li>slice metadata is invalid if the length is not a valid <code>usize</code> (i.e., it must not be read from uninitialized memory)</ul><li>a type with custom invalid values that is one of those values, such as a <code>NonNull</code> that is null. (Requesting custom invalid values is an unstable feature, but some stable libstd types, like <code>NonNull</code>, make use of it.)</ul></blockquote><p>Okay, that's actually an awful lot. Types like <code>bool</code> implement all the trait bounds we defined, and it would be insta-UB to ever try to cast them from arbitrary bytes. The same goes for <code>char</code>, and all <code>enum</code> are out of our control, too. At least we're safe on the "memory is initialized" front.<p>Dang it, I really wanted to use <code>transmute</code>! But if we were to use it for arbitrary types, it would trigger undefined behaviour sooner than later.<p>We have several options here:<ul><li>Make it an <code>unsafe trait</code>. Implementors will be responsible for ensuring that the type they're implementing it for can be safely transmuted from and into.<li><a href=https://rust-lang.github.io/api-guidelines/future-proofing.html>Seal the <code>trait</code></a> and implement it only for types we know are safe<sup class=footnote-reference><a href=#4>4</a></sup>, like <code>i32</code>.<li>Add methods to the <code>trait</code> definition that do the conversion of the type into its native representation.</ul><p>We will go with the first option<sup class=footnote-reference><a href=#5>5</a></sup>, because I really want to use <code>transmute</code>, and I want users to be able to implement the trait on their own types.<p>In any case, we need to change our <code>impl</code> to something more specific, in order to prevent it from automatically implementing the trait for types for which their memory representation has invalid values. So we get rid of this:<pre><code class=language-rust data-lang=rust>pub trait Scannable: Copy + PartialEq + PartialOrd {} - -impl<T: Copy + PartialEq + PartialOrd> Scannable for T {} -</code></pre><p>And replace it with this:<pre><code class=language-rust data-lang=rust>pub unsafe trait Scannable: Copy + PartialEq + PartialOrd {} - -macro_rules! impl_many { - ( unsafe impl $trait:tt for $( $ty:ty ),* ) => { - $( unsafe impl $trait for $ty {} )* - }; -} - -// SAFETY: all these types respect `Scannable` invariants. -impl_many!(unsafe impl Scannable for i8, u8, i16, u16, i32, u32, i64, u64, f32, f64); -</code></pre><p>Making a small macro for things like these is super useful. You could of course write <code>unsafe impl Scannable for T</code> for all ten <code>T</code> as well, but that introduces even more <code>unsafe</code> to read. Last but not least, let's replace the hardcoded <code>i32::from_ne_bytes</code> and <code>i32::to_ne_bytes</code> with <code>mem::transmute</code>.<p>All the <code>windows(4)</code> need to be replaced with <code>windows(mem::size_of::<T>())</code> because the size may no longer be <code>4</code>. All the <code>i32::from_ne_bytes(...)</code> need to be replaced with <code>mem::transmute::<_, T>(...)</code>. We explicitly write out <code>T</code> to make sure the compiler doesn't accidentally infer something we didn't intend.<p>And… it doesn't work at all. We're working with byte slices of arbitrary length. We cannot transmute a <code>&[]</code> type, which is 16 bytes (8 for the pointer and 8 for the length), to our <code>T</code>. My plan to use transmute can't possibly work here. Sigh.<h2 id=not-quite-transmuting-memory>Not quite transmuting memory</h2><p>Okay, we can't transmute, because we don't have a sized value, we only have a slice of bytes pointing somewhere else. What we <em>could</em> do is reinterpret the pointer to those bytes as a different type, and then dereference it! This is still a form of "transmutation", just without using <code>transmute</code>.<pre><code class=language-rust data-lang=rust>let value = unsafe { *(window.as_ptr() as *const T) }; -</code></pre><p>Woop! You can compile this and test it out on the step 2 and 3 of the tutorial, using <code>i32</code>, and it will still work! Something troubles me, though. Can you see what it is?<p>When we talked about invalid values, it had a note about unaligned references:<blockquote><p>a reference/<code>Box</code> that is dangling, unaligned, or points to an invalid value.</blockquote><p>Our <code>window</code> is essentially a reference to <code>T</code>. The only difference is we're working at the pointer level, but they're pretty much references. Let's see what the documentation for <a href=https://doc.rust-lang.org/std/primitive.pointer.html><code>pointer</code></a> has to say as well, since we're dereferencing pointers:<blockquote><p>when a raw pointer is dereferenced (using the <code>*</code> operator), it must be non-null and aligned.</blockquote><p>It must be aligned. The only reason why our data is aligned is because we are also performing a "fast scan", so we only look at aligned locations. This is a time bomb waiting to blow up. Is there any other way to <a href=https://doc.rust-lang.org/std/ptr/fn.read.html><code>read</code></a> from a pointer which is safer?<blockquote><p><code>src</code> must be properly aligned. Use <a href=https://doc.rust-lang.org/std/ptr/fn.read_unaligned.html><code>read_unaligned</code></a> if this is not the case.</blockquote><p>Bingo! Both <code>read</code> and <code>read_unaligned</code>, unlike dereferencing the pointer, will perform a copy, but if it can make the code less prone to blowing up, I'll take it<sup class=footnote-reference><a href=#6>6</a></sup>. Let's change the code one more time:<pre><code class=language-rust data-lang=rust>let current = unsafe { window.as_ptr().cast::<T>().read_unaligned() }; -</code></pre><p>I prefer to avoid type annotations in variables where possible, which is why I use the <a href=https://www.reddit.com/r/rust/comments/3fimgp/why_double_colon_rather_that_dot/ctozkd0/>turbofish</a> so often. You can get rid of the cast and use a type annotation instead, but make sure the type is known, otherwise it will think it's <code>u8</code> because <code>window</code> is a <code>&[u8]</code>.<p>Now, this is all cool and good. You can replace <code>i32</code> with <code>f32</code> for <code>T</code> and you'll be able to get halfway done with the step 4 of Cheat Engine's tutorial. Unfortunately, as it is, this code is not enough to complete step 4 with exact scans<sup class=footnote-reference><a href=#7>7</a></sup>. You see, comparing floating point values is not as simple as checking for bitwise equality. We were actually really lucky that the <code>f32</code> part works! But the values in the <code>f64</code> part are not as precise as our inputs, so our exact scan fails.<p>Using a fixed type parameter is pretty limiting as well. On the one hand, it is nice that, if you scan for <code>i32</code>, the compiler statically guarantees that subsequent scans will also happen on <code>i32</code> and thus be compatible. On the other, this requires us to know the type at compile time, which for an interactive program, is not possible. While we <em>could</em> create different methods for each supported type and, at runtime, decide to which we should jump, I am not satisfied with that solution. It also means we can't switch from scanning an <code>u32</code> to an <code>i32</code>, for whatever reason.<p>So we need to work around this once more.<h2 id=rethinking-the-scans>Rethinking the scans</h2><p>What does our scanning function need, really? It needs a way to compare two chunks of memory as being equal or not (as we have seen, this isn't trivial with types such as floating point numbers) and, for other types of scans, it needs to be able to produce an ordering, or calculate a difference.<p>Instead of having a our trait require the bounds <code>PartialEq</code> and <code>PartialOrd</code>, we can define our own methods to compare <code>Self</code> with <code>&[u8]</code>. It still should be <code>Clone</code>, so we can pass it around without worrying about lifetimes:<pre><code class=language-rust data-lang=rust>// Callers must `assert_eq!(memory.len(), mem::size_of::<Self>())`. -unsafe fn eq(&self, memory: &[u8]) -> bool; -unsafe fn cmp(&self, memory: &[u8]) -> Ordering; -</code></pre><p>This can be trivially implemented for all integer types:<pre><code class=language-rust data-lang=rust>macro_rules! impl_scannable_for_int { - ( $( $ty:ty ),* ) => { - $( - // SAFETY: caller is responsible to `assert_eq!(memory.len(), mem::size_of::<T>())` - impl Scannable for $ty { - unsafe fn eq(&self, memory: &[u8]) -> bool { - let other = unsafe { memory.as_ptr().cast::<$ty>().read_unaligned() }; - *self == other - } - - unsafe fn cmp(&self, memory: &[u8]) -> Ordering { - let other = unsafe { memory.as_ptr().cast::<$ty>().read_unaligned() }; - <$ty as Ord>::cmp(self, &other) - } - } - )* - }; -} - -impl_scannable_for_int!(i8, u8, i16, u16, i32, u32, i64, u64); -</code></pre><p>The funny <code><$ty as Ord></code> is because I decided to call the method <code>Scannable::cmp</code>, so I have to disambiguate between it and <code>Ord::cmp</code>. We can go ahead and update the code using <code>Scannable</code> to use these new functions instead.<p>Now, you may have noticed I only implemented it for the integer types. That's because floats need some extra care. Unfortunately, floating point types do not have any form of "precision" embedded in them, so we can't accurately say "compare these floats to the precision level the user specified". What we can do, however, is drop a few bits from the mantissa, so "relatively close" quantities are considered equal. It's definitely not as good as comparing floats to the user's precision, but it will get the job done.<p>I'm going to arbitrarily say that we are okay comparing with "half" the precision. We can achieve that by masking half of the bits from the mantissa to zero:<pre><code class=language-rust data-lang=rust> -macro_rules! impl_scannable_for_float { - ( $( $ty:ty : $int_ty:ty ),* ) => { - $( - #[allow(unused_unsafe)] // mind you, it is necessary - impl Scannable for $ty { - unsafe fn eq(&self, memory: &[u8]) -> bool { - const MASK: $int_ty = !((1 << (<$ty>::MANTISSA_DIGITS / 2)) - 1); - - // SAFETY: caller is responsible to `assert_eq!(memory.len(), mem::size_of::<T>())` - let other = unsafe { memory.as_ptr().cast::<$ty>().read_unaligned() }; - let left = <$ty>::from_bits(self.to_bits() & MASK); - let right = <$ty>::from_bits(other.to_bits() & MASK); - left == right - } - - ... - } - )* - }; -} - -impl_scannable_for_float!(f32: u32, f64: u64); -</code></pre><p>You may be wondering what's up with that weird <code>MASK</code>. Let's visualize it with a <a href=https://en.wikipedia.org/wiki/Bfloat16_floating-point_format><code>f16</code></a>. This type has 16 bits, 1 for sign, 5 for exponent, and 10 for the mantissa:<pre><code>S EEEEE MMMMMMMMMM -</code></pre><p>If we substitute the constant with the numeric value and operate:<pre><code class=language-rust data-lang=rust>!((1 << (10 / 2)) - 1) -!((1 << 5) - 1) -!(0b00000000_00100000 - 1) -!(0b00000000_00011111) -0b11111111_11100000 -</code></pre><p>So effectively, half of the mantisssa bit will be masked to 0. For the <code>f16</code> example, this makes us lose 5 bits of precision. Comparing two floating point values with their last five bits truncated is equivalent to checking if they are "roughly equal"!<p>When Cheat Engine scans for floating point values, several additional settings show, and one such option is "truncated". I do not know if it behaves like this, but it might.<p>Let's try this out:<pre><code class=language-rust data-lang=rust>#[test] -fn f32_roughly_eq() { - let left = 0.25f32; - let right = 0.25000123f32; - let memory = unsafe { mem::transmute::<_, [u8; 4]>(right) }; - assert_ne!(left, right); - assert!(unsafe { Scannable::eq(&left, &memory) }); -} -</code></pre><pre><code>>cargo test f32_roughly_eq - -running 1 test -test scan::candidate_location_tests::f32_roughly_eq ... ok -</code></pre><p>Huzzah! The <code>assert_ne!</code> makes sure that a normal comparision would fail, and then we <code>assert!</code> that our custom one passes the test. When the user performs an exact scan, the code will be more tolerant to the user's less precise inputs, which overall should result in a nicer experience.<h2 id=dynamically-sized-scans>Dynamically sized scans</h2><p>The second problem we need to solve is the possibility of the size not being known at compile time<sup class=footnote-reference><a href=#8>8</a></sup>. While we can go as far as scanning over strings of a known length, this is rather limiting, because we need to know the length at compile time<sup class=footnote-reference><a href=#9>9</a></sup>. Heap allocated objects are another problem, because we don't want to compare the memory representation of the stack object, but likely the memory where they point to (such as <code>String</code>).<p>Instead of using <code>mem::size_of</code>, we can add a new method to our <code>Scannable</code>, <code>size</code>, which will tell us the size required of the memory view we're comparing against:<pre><code class=language-rust data-lang=rust>unsafe impl Scannable { - ... - - fn size(&self) -> usize; -} -</code></pre><p>It is <code>unsafe</code> to implement, because we are relying on the returned value to be truthful and unchanging. It should be safe to call, because it cannot have any invariants. Unfortunately, signaling "unsafe to implement" is done by marking the entire trait as <code>unsafe</code>, since "unsafe to call" is reserved for <code>unsafe fn</code>, and even though the rest of methods are not necessarily unsafe to implement, they're treated as such.<p>At the moment, <code>Scannable</code> cannot be made into a trait object because it is <a href=https://doc.rust-lang.org/stable/error-index.html#E0038>not object safe</a>. This is caused by the <code>Clone</code> requirement on all <code>Scannable</code> object, which in turn needs the types to be <code>Sized</code> because <code>clone</code> returns <code>Self</code>. Because of this, the size must be known.<p>However, we <em>can</em> move the <code>Clone</code> requirement to the methods that need it! This way, <code>Scannable</code> can remain object safe, enabling us to do the following:<pre><code class=language-rust data-lang=rust>unsafe impl<T: AsRef<dyn Scannable> + AsMut<dyn Scannable>> Scannable for T { - unsafe fn eq(&self, memory: &[u8]) -> bool { - self.as_ref().eq(memory) - } - - unsafe fn cmp(&self, memory: &[u8]) -> Ordering { - self.as_ref().cmp(memory) - } - - fn mem_view(&self) -> &[u8] { - self.as_ref().mem_view() - } - - fn size(&self) -> usize { - self.as_ref().size() - } -} -</code></pre><p>Any type which can be interpreted as a reference to <code>Scannable</code> is also a scannable! This enables us to perform scans over <code>Box<dyn i32></code>, where the type is known at runtime! Or rather, it would, if <code>Box<dyn T></code> implemented <code>Clone</code>, which it can't<sup class=footnote-reference><a href=#10>10</a></sup> because that's what prompted this entire issue. Dang it! I can't catch a breath today!<p>Okay, let's step back. Why did we need our scannables to be clone in the first place? When we perform exact scans, we store the original value in the region, which we don't own, so we clone it. But what if we <em>did</em> own the value? Instead of taking the <code>Scan</code> by reference, which holds <code>T: Scannable</code>, we could take it by value. If we get rid of all the <code>Clone</code> bounds and update <code>Scan::run</code> to take <code>self</code>, along with updating all the things that take a <code>Region</code> to take them by value as well, it should all work out.<p>But it does not. If we take <code>Scan</code> by value, with it not being <code>Clone</code>, we simply can't use it to scan over multiple regions. After the first region, we have lost the <code>Scan</code>.<p>Let's take a second step back. We are scanning memory, and we want to compare memory, but we want to treat the memory with different semantics (for example, if we treat it as <code>f32</code>, we want to check for rough equality). Instead of storing the <em>value</em> itself, we could store its <em>memory representation</em>, and when we compare memory representations, we can do so under certain semantics.<p>First off, let's revert getting rid of all <code>Clone</code>. Wherever we stored a <code>T</code>, we will now store a <code>Vec<u8></code>. We will still use a type parameter to represent the "implementations of <code>Scannable</code>". For this to work, our definitions need to use <code>T</code> somewhere, or else the compiler refuses to compile the code with error <a href=https://doc.rust-lang.org/stable/error-index.html#E0392>E0392</a>. For this, I will stick a <a href=https://doc.rust-lang.org/stable/std/marker/struct.PhantomData.html><code>PhantomData</code></a> in the <code>Exact</code> variant. It's a bit pointless to include it in all variants, and <code>Exact</code> seems the most appropriated:<pre><code class=language-rust data-lang=rust>pub enum Scan<T: Scannable> { - Exact(Vec<u8>, PhantomData<T>), - Unknown, - ... -} -</code></pre><p>This keeps in line with <code>Value</code>:<pre><code class=language-rust data-lang=rust>pub enum Value<T: Scannable> { - Exact(Vec<u8>, PhantomData<T>), - ... -} -</code></pre><p>Our <code>Scannable</code> will no longer work on <code>T</code> and <code>&[u8]</code>. Instead, it will work on two <code>&[u8]</code>. We will also need a way to interpret a <code>T</code> as <code>&[u8]</code>, which we can achieve with a new method, <code>mem_view</code>. This method interprets the raw memory representation of <code>self</code> as its raw bytes. It also lets us get rid of <code>size</code>, because we can simply do <code>mem_view().len()</code>. It's still <code>unsafe</code> to implement, because it should return the same length every time:<pre><code class=language-rust data-lang=rust>pub unsafe trait Scannable { - // Callers must `assert_eq!(left.len(), right.len(), self.mem_view().len())`. - unsafe fn eq(left: &[u8], right: &[u8]) -> bool; - unsafe fn cmp(left: &[u8], right: &[u8]) -> Ordering; - fn mem_view(&self) -> &[u8]; -} -</code></pre><p>But now we can't use it in trait object, so the following no longer works:<pre><code class=language-rust data-lang=rust>unsafe impl<T: AsRef<dyn Scannable> + AsMut<dyn Scannable>> Scannable for T { - ... -} -</code></pre><p>Ugh! Well, to be fair, we no longer have a "scannable" at this point. It's more like a scan mode that tells us how memory should be compared according to a certain type. Let's split the trait into two: one for the scan mode, and other for "things which are scannable":<pre><code class=language-rust data-lang=rust>pub trait ScanMode { - unsafe fn eq(left: &[u8], right: &[u8]) -> bool; - unsafe fn cmp(left: &[u8], right: &[u8]) -> Ordering; -} - -pub unsafe trait Scannable { - type Mode: ScanMode; - - fn mem_view(&self) -> &[u8]; -} -</code></pre><p>Note that we have an associated <code>type Mode</code> which contains the corresponding <code>ScanMode</code>. If we used a trait bound such as <code>Scannable: ScanMode</code>, we'd be back to square one: it would inherit the method definitions that don't use <code>&self</code> and thus cannot be used as trait objects.<p>With these changes, it is possible to implement <code>Scannable</code> for any <code>dyn Scannable</code>:<pre><code class=language-rust data-lang=rust>unsafe impl<T: ScanMode + AsRef<dyn Scannable<Mode = Self>>> Scannable for T { - type Mode = Self; - - fn mem_view(&self) -> &[u8] { - self.as_ref().mem_view() - } -} -</code></pre><p>We do have to adjust a few places of the code to account for both <code>Scannable</code> and <code>ScanMode</code>, but all in all, it's pretty straightforward. Things like <code>Value</code> don't need to store the <code>Scannable</code> anymore, just a <code>Vec<u8></code>. It also doesn't need the <code>ScanMode</code>, because it's not going to be scanning anything on its own. This applies transitively to <code>Region</code> which was holding a <code>Value</code>.<p><code>Value</code> <em>does</em> need to be updated to store the size of the region we are scanning for, however, because we need that information when running a subsequent scan. For all <code>Scan</code> that don't have a explicit thing to scan for (like <code>Decreased</code>), the <code>size</code> also needs to be stored in them.<p>Despite all our efforts, we're still unable to return an <code>Scannable</code> chosen at runtime.<pre><code class=language-rust data-lang=rust>fn prompt_user_for_scan() -> Scan<Box<dyn Scannable<Mode = ???>>> { - todo!() -} -</code></pre><p>As far as I can tell, there's simply no way to specify that type. We want to return a type which is scannable, which has itself (which is also a <code>ScanMode</code>) as the corresponding mode. Even if we just tried to return the mode, we simply can't, because it's not object-safe. Is this the end of the road?<h2 id=specifying-the-scan-mode>Specifying the scan mode</h2><p>We need a way to pass an arbitrary scan mode to our <code>Scan</code>. This scan mode should go in tandem with <code>Scannable</code> types, because it would be unsafe otherwise. We've seen that using a type just doesn't cut it. What else can we do?<p>Using an enumeration is a no-go, because I want users to be able to extend it further. I also would like to avoid having to update the <code>enum</code> and all the matches every time I come up with a different type combination. And it could get pretty complicated if I ever built something dynamically, such as letting the user combine different scans in one pass.<p>So what if we make <code>Scannable</code> return a value that implements the functions we need?<pre><code class=language-rust data-lang=rust>pub struct ScanMode { - eq: unsafe fn(left: &[u8], right: &[u8]) -> bool, - cmp: unsafe fn(left: &[u8], right: &[u8]) -> Ordering, -} -</code></pre><p>It's definitely… non-conventional. But hey, now we're left with the <code>Scannable</code> trait, which is object-safe, and does not have any type parameters!<pre><code class=language-rust data-lang=rust>pub unsafe trait Scannable { - fn mem_view(&self) -> &[u8]; - fn scan_mode(&self) -> ScanMode; -} -</code></pre><p>It is a bit weird, but defining local functions and using those in the returned value is a nice way to keep things properly scoped:<pre><code class=language-rust data-lang=rust>macro_rules! impl_scannable_for_int { - ( $( $ty:ty ),* ) => { - $( - unsafe impl Scannable for $ty { - fn mem_view(&self) -> &[u8] { - unsafe { std::slice::from_raw_parts(self as *const _ as *const u8, mem::size_of::<$ty>()) } - } - - fn scan_mode(&self) -> ScanMode { - unsafe fn eq(left: &[u8], right: &[u8]) -> bool { - ... - } - - unsafe fn cmp(left: &[u8], right: &[u8]) -> Ordering { - ... - } - - ScanMode { eq, cmp } - } - } - )* - }; -} -</code></pre><p>Our <code>Scan</code> needs to store the <code>Scannable</code> type, and not just the memory, once again. For variants that don't need any value, they can store the <code>ScanMode</code> and size instead.<p>Does this solution work? Yes! It's possible to return a <code>Box<dyn Scannable></code> from a function, and underneath, it may be using any type which is <code>Scannable</code>. Is this the best solution? Well, that's hard to say. This is <em>one</em> of the possible solutions.<p>We have been going around in circles for quite some time now, so I'll leave it there. It's a solution, which may not be pretty, but it works. With these changes, the code is capable of completing all of the steps in the Cheat Engine tutorial up until point!<h2 id=finale>Finale</h2><p>If there's one lesson to learn from this post, it's that there is often no single correct solution to a problem. We could have approached the scan types in many, many ways (and we tried quite a few!), but in the end, choosing one option or the other comes down to your (sometimes self-imposed) requirements.<p>You may <a href=https://github.com/lonami/memo>obtain the code for this post</a> over at my GitHub. You can run <code>git checkout step4</code> after cloning the repository to get the right version of the code. The code has gone through a lot of iterations, and I'd still like to polish it a bit more, so it might slightly differ from the code presented in this entry.<p>If you feel adventurous, Cheat Engine has different options for scanning floating point types: "rounded (default)", "rounded (extreme)", and truncated. Optionally, it can scan for "simple values only". You could go ahead and toy around with these!<p>We didn't touch on types with different lengths, such as strings. You could support UTF-8, UTF-16, or arbitrary byte sequences. This post also didn't cover scanning for multiple things at once, known as "groupscan commands", although from what I can tell, these are just a nice way to scan for arbitrary byte sequences.<p>We also didn't look into supporting different the same scan with different alignments. All these things may be worth exploring depending on your requirements. You could even get rid of such genericity and go with something way simpler. Supporting <code>i32</code>, <code>f32</code> and <code>f64</code> is enough to complete the Cheat Engine tutorial. But I wanted something more powerful, although my solution currently can't scan for a sequence such as "exact type, unknown, exact matching the unknown". So yeah.<p>In the <a href=/blog/woce-5>next post</a>, we'll tackle the fifth step of the tutorial: Code finder. Cheat Engine attaches its debugger to the process for this one, and then replaces the instruction that performs the write with a different no-op so that nothing is written anymore. This will be quite the challenge!<h3 id=footnotes>Footnotes</h3><div class=footnote-definition id=1><sup class=footnote-definition-label>1</sup><p><a href=https://doc.rust-lang.org/stable/std/ops/trait.Drop.html#copy-and-drop-are-exclusive><code>Copy</code> and <code>Drop</code> are exclusive</a>. See also <a href=https://doc.rust-lang.org/stable/error-index.html#E0184>E0184</a>.</div><div class=footnote-definition id=2><sup class=footnote-definition-label>2</sup><p>If you added more scan types that require additional bounds, make sure to add them too. For example, the "decreased by" scan requires the type to <code>impl Sub</code>.</div><div class=footnote-definition id=3><sup class=footnote-definition-label>3</sup><p>This is a good time to remind you to read the documentation. It is of special importance when dealing with <code>unsafe</code> methods; I recommend reading it a couple times.</div><div class=footnote-definition id=4><sup class=footnote-definition-label>4</sup><p>Even with this option, it would not be a bad idea to make the trait <code>unsafe</code>.</div><div class=footnote-definition id=5><sup class=footnote-definition-label>5</sup><p>Not for long. As we will find out later, this approach has its limitations.</div><div class=footnote-definition id=6><sup class=footnote-definition-label>6</sup><p>We can still perform the pointer dereference when we know it's aligned. This would likely be an optimization, although it would definitely complicate the code more.</div><div class=footnote-definition id=7><sup class=footnote-definition-label>7</sup><p>It <em>would</em> work if you scanned for unknown values and then checked for decreased values repeatedly. But we can't just leave exact scan broken!</div><div class=footnote-definition id=8><sup class=footnote-definition-label>8</sup><p>Unfortunately, this makes some optimizations harder or even impossible to perform. Providing specialized functions for types where the size is known at compile time could be worth doing. Programming is all tradeoffs.</div><div class=footnote-definition id=9><sup class=footnote-definition-label>9</sup><p><a href=https://blog.rust-lang.org/2021/02/26/const-generics-mvp-beta.html>Rust 1.51</a>, which was not out at the time of writing, would make it a lot easier to allow scanning for fixed-length sequences of bytes, thanks to const generics.</div><div class=footnote-definition id=10><sup class=footnote-definition-label>10</sup><p>Workarounds do exist, such as <a href=https://crates.io/crates/dyn-clone>dtolnay's <code>dyn-clone</code></a>. But I would rather not go that route.</div></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,376 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Writing our own Cheat Engine: Code finder | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Writing our own Cheat Engine: Code finder</h1><div class=time><p>2021-03-06</div><p>This is part 5 on the <em>Writing our own Cheat Engine</em> series:<ul><li><a href=/blog/woce-1>Part 1: Introduction</a> (start here if you're new to the series!)<li><a href=/blog/woce-2>Part 2: Exact Value scanning</a><li><a href=/blog/woce-3>Part 3: Unknown initial value</a><li><a href=/blog/woce-4>Part 4: Floating points</a><li>Part 5: Code finder<li><a href=/blog/woce-6>Part 6: Pointers</a></ul><p>In part 4 we spent a good deal of time trying to make our scans generic, and now we have something that works<sup class=footnote-reference><a href=#1>1</a></sup>! Now that the scanning is fairly powerful and all covered, the Cheat Engine tutorial shifts focus into slightly more advanced techniques that you will most certainly need in anything bigger than a toy program.<p>It's time to write our very own <strong>debugger</strong> in Rust!<h2 id=code-finder>Code finder</h2><details open><summary>Cheat Engine Tutorial: Step 5</summary> <blockquote><p>Sometimes the location something is stored at changes when you restart the game, or even while you're playing… In that case you can use 2 things to still make a table that works. In this step I'll try to describe how to use the Code Finder function.<p>The value down here will be at a different location each time you start the tutorial, so a normal entry in the address list wouldn't work. First try to find the address. (You've got to this point so I assume you know how to.)<p>When you've found the address, right-click the address in Cheat Engine and choose "Find out what writes to this address". A window will pop up with an empty list.<p>Then click on the Change value button in this tutorial, and go back to Cheat Engine. If everything went right there should be an address with assembler code there now.<p>Click it and choose the replace option to replace it with code that does nothing. That will also add the code address to the code list in the advanced options window. (Which gets saved if you save your table.)<p>Click on stop, so the game will start running normal again, and close to close the window. Now, click on Change value, and if everything went right the Next button should become enabled.<p>Note: When you're freezing the address with a high enough speed it may happen that next becomes visible anyhow</blockquote></details><h2 id=baby-steps-to-debugging>Baby steps to debugging</h2><p>Although I have used debuggers before, I have never had a need to write one myself so it's time for some research.<p>Searching on DuckDuckGo, I can find entire series to <a href=http://system.joekain.com/debugger/>Writing a Debugger</a>. We would be done by now if only that series wasn't written for Linux. The Windows documentation contains a section called <a href=https://docs.microsoft.com/en-us/windows/win32/debug/creating-a-basic-debugger>Creating a Basic Debugger</a>, but as far as I can tell, it only teaches you the <a href=https://docs.microsoft.com/en-us/windows/win32/debug/debugging-functions>functions</a> needed to configure the debugging loop. Which mind you, we will need, but in due time.<p>According to <a href=https://www.gironsec.com/blog/2013/12/writing-your-own-debugger-windows-in-c/>Writing your own windows debugger in C</a>, the steps needed to write a debugger are:<ul><li><a href=https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-suspendthread><code>SuspendThread(proc)</code></a>. It makes sense that we need to pause all the threads<sup class=footnote-reference><a href=#2>2</a></sup> before messing around with the code the program is executing, or things are very prone to go wrong.<li><a href=https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadcontext><code>GetThreadContext(proc)</code></a>. This function retrieves the appropriate context of the specified thread and is highly processor specific. It basically takes a snapshot of all the registers. Think of registers like extremely fast, but also extremely limited, memory the processor uses.<li><a href=https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-debugbreakprocess><code>DebugBreakProcess</code></a>. Essentially <a href=https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/x86-instructions#miscellaneous>writes out the 0xCC opcode</a>, <code>int 3</code> in assembly, also known as software breakpoint. It's written wherever the Register Instruction Pointer (RIP<sup class=footnote-reference><a href=#3>3</a></sup>) currently points to, so in essence, when the thread resumes, it will immediately <a href=https://stackoverflow.com/q/3915511/>trigger the breakpoint</a>.<li><a href=https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-continuedebugevent><code>ContinueDebugEvent</code></a>. Presumably continues debugging.</ul><p>There are pages documenting <a href=https://docs.microsoft.com/en-us/windows/win32/debug/debugging-events>all of the debug events</a> that our debugger will be able to handle.<p>Okay, nice! Software breakpoints seem to be done by writing out memory to the region where the program is reading instructions from. We know how to write memory, as that's what all the previous posts have been doing to complete the corresponding tutorial steps. After the breakpoint is executed, all we need to do is <a href=https://stackoverflow.com/q/3747852/>restore the original memory back</a> so that the next time the program executes the code it sees no difference.<p>But a software breakpoint will halt execution when the code executes the interrupt instruction. This step of the tutorial wants us to find <em>what writes to a memory location</em>. Where should we place the breakpoint to detect such location? Writing out the instruction to the memory we want to break in won't do; it's not an instruction, it's just data.<p>The name may have given it away. If we're talking about software breakpoints, it makes sense that there would exist such a thing as <a href=https://en.wikipedia.org/wiki/Breakpoint#Hardware><em>hardware</em> breakpoints</a>. Because they're tied to the hardware, they're highly processor-specific, but luckily for us, the processor on your usual desktop computer probably has them! Even the <a href=https://interrupt.memfault.com/blog/cortex-m-breakpoints>cortex-m</a> does. The wikipedia page also tells us the name of the thing we're looking for, watchpoints:<blockquote><p>Other kinds of conditions can also be used, such as the reading, writing, or modification of a specific location in an area of memory. This is often referred to as a conditional breakpoint, a data breakpoint, or a watchpoint.</blockquote><p>A breakpoint that triggers when a specific memory location is written to is exactly what we need, and <a href=https://stackoverflow.com/a/19109153/>x86 has debug registers D0 to D3 to track memory addresses</a>. As far as I can tell, there is no API in specific to mess with the registers. But we don't need any of that! We can just go ahead and <a href=https://doc.rust-lang.org/stable/unstable-book/library-features/asm.html>write some assembly by hand</a> to access these registers. At the time of writing, inline assembly is unstable, so we need a nightly compiler. Run <code>rustup toolchain install nightly</code> if you haven't yet, and execute the following code with <code>cargo +nightly run</code>:<pre><code class=language-rust data-lang=rust>#![feature(asm)] // top of the file - -fn main() { - let x: u64 = 123; - unsafe { - asm!("mov dr7, {}", in(reg) x); - } -} - -</code></pre><p><code>dr7</code> stands is the <a href=https://en.wikipedia.org/wiki/X86_debug_register>debug control register</a>, and running this we get…<pre><code>>cargo +nightly run - Compiling memo v0.1.0 - Finished dev [unoptimized + debuginfo] target(s) in 0.74s - Running `target\debug\memo.exe` -error: process didn't exit successfully: `target\debug\memo.exe` (exit code: 0xc0000096, STATUS_PRIVILEGED_INSTRUCTION) -</code></pre><p>…an exception! In all fairness, I have no idea what that code would have done. So maybe the <code>STATUS_PRIVILEGED_INSTRUCTION</code> is just trying to protect us. Can we read from the register instead, and see it's default value?<pre><code class=language-rust data-lang=rust>let x: u64; -unsafe { - asm!("mov {}, dr7", out(reg) x); -} -assert_eq!(x, 5); -</code></pre><pre><code>>cargo +nightly run -... -error: process didn't exit successfully: `target\debug\memo.exe` (exit code: 0xc0000096, STATUS_PRIVILEGED_INSTRUCTION) -</code></pre><p>Nope. Okay, it seems directly reading from or writing to the debug register is a ring-0 thing. Surely there's a way around this. But first we should figure out how to enumerate and pause all the threads.<h2 id=pausing-all-the-threads>Pausing all the threads</h2><p>It seems there is no straightforward way to enumerate the threads. One has to <a href=https://stackoverflow.com/a/1206915/>create a "toolhelp"</a> and poll the entries. I won't bore you with the details. Let's add <code>tlhelp32</code> to the crate features of <code>winapi</code> and try it out:<pre><code class=language-rust data-lang=rust> -#[derive(Debug)] -pub struct Toolhelp { - handle: winapi::um::winnt::HANDLE, -} - -impl Drop for Toolhelp { - fn drop(&mut self) { - unsafe { winapi::um::handleapi::CloseHandle(self.handle) }; - } -} - -pub fn enum_threads(pid: u32) -> io::Result<Vec<u32>> { - const ENTRY_SIZE: u32 = mem::size_of::<winapi::um::tlhelp32::THREADENTRY32>() as u32; - - // size_of(dwSize + cntUsage + th32ThreadID + th32OwnerProcessID) - const NEEDED_ENTRY_SIZE: u32 = 4 * mem::size_of::<DWORD>() as u32; - - // SAFETY: it is always safe to attempt to call this function. - let handle = unsafe { - winapi::um::tlhelp32::CreateToolhelp32Snapshot(winapi::um::tlhelp32::TH32CS_SNAPTHREAD, 0) - }; - if handle == winapi::um::handleapi::INVALID_HANDLE_VALUE { - return Err(io::Error::last_os_error()); - } - let toolhelp = Toolhelp { handle }; - - let mut result = Vec::new(); - let mut entry = winapi::um::tlhelp32::THREADENTRY32 { - dwSize: ENTRY_SIZE, - cntUsage: 0, - th32ThreadID: 0, - th32OwnerProcessID: 0, - tpBasePri: 0, - tpDeltaPri: 0, - dwFlags: 0, - }; - - // SAFETY: we have a valid handle, and point to memory we own with the right size. - if unsafe { winapi::um::tlhelp32::Thread32First(toolhelp.handle, &mut entry) } != FALSE { - loop { - if entry.dwSize >= NEEDED_ENTRY_SIZE && entry.th32OwnerProcessID == pid { - result.push(entry.th32ThreadID); - } - - entry.dwSize = ENTRY_SIZE; - // SAFETY: we have a valid handle, and point to memory we own with the right size. - if unsafe { winapi::um::tlhelp32::Thread32Next(toolhelp.handle, &mut entry) } == FALSE { - break; - } - } - } - - Ok(result) -} -</code></pre><p>Annoyingly, invalid handles returned by <a href=https://docs.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot><code>CreateToolhelp32Snapshot</code></a>, are <code>INVALID_HANDLE_VALUE</code> (which is -1), not null. But that's not a big deal, we simply can't use <code>NonNull</code> here. The function ignores the process identifier when using <code>TH32CS_SNAPTHREAD</code>, used to include all threads, and we need to compare the process identifier ourselves.<p>In summary, we create a "toolhelp" (wrapped in a helper <code>struct</code> so that whatever happens, <code>Drop</code> will clean it up), initialize a thread enntry (with everything but the structure size to zero) and call <code>Thread32First</code> the first time, <code>Thread32Next</code> subsequent times. It seems to work all fine!<pre><code class=language-rust data-lang=rust>dbg!(process::enum_threads(pid)); -</code></pre><pre><code>[src\main.rs:46] process::enum_threads(pid) = Ok( - [ - 10560, - ], -) -</code></pre><p>According to this, the Cheat Engine tutorial is only using one thread. Good to know. Much like processes, threads need to be opened before we can use them, with <a href=https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openthread><code>OpenThread</code></a>:<pre><code class=language-rust data-lang=rust>pub struct Thread { - tid: u32, - handle: NonNull<c_void>, -} - -impl Thread { - pub fn open(tid: u32) -> io::Result<Self> { - // SAFETY: the call doesn't have dangerous side-effects - NonNull::new(unsafe { - winapi::um::processthreadsapi::OpenThread( - winapi::um::winnt::THREAD_SUSPEND_RESUME, - FALSE, - tid, - ) - }) - .map(|handle| Self { tid, handle }) - .ok_or_else(io::Error::last_os_error) - } - - pub fn tid(&self) -> u32 { - self.tid - } -} - -impl Drop for Thread { - fn drop(&mut self) { - unsafe { winapi::um::handleapi::CloseHandle(self.handle.as_mut()) }; - } -} -</code></pre><p>Just your usual RAII pattern. The thread is opened with permission to suspend and resume it. Let's try to pause the handles with <a href=https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-suspendthread><code>SuspendThread</code></a> to make sure that this thread is actually the one we're looking for:<pre><code class=language-rust data-lang=rust>pub fn suspend(&mut self) -> io::Result<usize> { - // SAFETY: the handle is valid. - let ret = unsafe { - winapi::um::processthreadsapi::SuspendThread(self.handle.as_ptr()) - }; - if ret == -1i32 as u32 { - Err(io::Error::last_os_error()) - } else { - Ok(ret as usize) - } -} - -pub fn resume(&mut self) -> io::Result<usize> { - // SAFETY: the handle is valid. - let ret = unsafe { - winapi::um::processthreadsapi::ResumeThread(self.handle.as_ptr()) - }; - if ret == -1i32 as u32 { - Err(io::Error::last_os_error()) - } else { - Ok(ret as usize) - } -} -</code></pre><p>Both suspend and resume return the previous "suspend count". It's kind of like a barrier or semaphore where the thread only runs if the suspend count is zero. Trying it out:<pre><code class=language-rust data-lang=rust>let mut threads = thread::enum_threads(pid) - .unwrap() - .into_iter() - .map(Thread::open) - .collect::<Result<Vec<_>, _>>() - .unwrap(); - -threads - .iter_mut() - .for_each(|thread| { - println!("Pausing thread {} for 10 seconds…", thread.tid()); - thread.suspend().unwrap(); - - std::thread::sleep(std::time::Duration::from_secs(10)); - - println!("Wake up, {}!", thread.tid()); - thread.resume().unwrap(); - }); -</code></pre><p>If you run this code with the process ID of the Cheat Engine tutorial, you will see that the tutorial window freezes for ten seconds! Because the main and only thread is paused, it cannot process any window events, so it becomes unresponsive. It is now "safe" to mess around with the thread context.<h2 id=setting-hardware-breakpoints>Setting hardware breakpoints</h2><p>I'm definitely not the first person to wonder <a href=https://social.msdn.microsoft.com/Forums/en-US/0cb3360d-3747-42a7-bc0e-668c5d9ee1ee/how-to-set-a-hardware-breakpoint>How to set a hardware breakpoint?</a>. This is great, because it means I don't need to ask that question myself. It appears we need to change the debug register <em>via the thread context</em>.<p>One has to be careful to use the right context structure. Confusingly enough, <a href=https://stackoverflow.com/q/17504174/><code>WOW64_CONTEXT</code></a> is 32 bits, not 64. <code>CONTEXT</code> alone seems to be the right one:<pre><code class=language-rust data-lang=rust>pub fn get_context(&self) -> io::Result<winapi::um::winnt::CONTEXT> { - let context = MaybeUninit::<winapi::um::winnt::CONTEXT>::zeroed(); - // SAFETY: it's a C struct, and all-zero is a valid bit-pattern for the type. - let mut context = unsafe { context.assume_init() }; - context.ContextFlags = winapi::um::winnt::CONTEXT_ALL; - - // SAFETY: the handle is valid and structure points to valid memory. - if unsafe { - winapi::um::processthreadsapi::GetThreadContext(self.handle.as_ptr(), &mut context) - } == FALSE - { - Err(io::Error::last_os_error()) - } else { - Ok(context) - } -} -</code></pre><p>Trying it out:<pre><code class=language-rust data-lang=rust>thread.suspend().unwrap(); - -let context = thread.get_context().unwrap(); -println!("Dr0: {:016x}", context.Dr0); -println!("Dr7: {:016x}", context.Dr7); -println!("Dr6: {:016x}", context.Dr6); -println!("Rax: {:016x}", context.Rax); -println!("Rbx: {:016x}", context.Rbx); -println!("Rcx: {:016x}", context.Rcx); -println!("Rip: {:016x}", context.Rip); -</code></pre><pre><code>Dr0: 0000000000000000 -Dr7: 0000000000000000 -Dr6: 0000000000000000 -Rax: 0000000000001446 -Rbx: 0000000000000000 -Rcx: 0000000000000000 -Rip: 00007ffda4259904 -</code></pre><p>Looks about right! Hm, I wonder what happens if I use Cheat Engine to add the watchpoint on the memory location we care about?<pre><code>Dr0: 000000000157e650 -Dr7: 00000000000d0001 -</code></pre><p>Look at that! The debug registers changed! DR0 contains the location we want to watch for writes, and the debug control register DR7 changed. Cheat Engine sets the same values on all threads (for some reason I now see more than one thread printed for the tutorial, not sure what's up with that; maybe the single-thread is the weird one out).<p>Hmm, what happens if I watch for access instead of write?<pre><code>Dr0: 000000000157e650 -Dr7: 00000000000f0001 -</code></pre><p>What if I set both?<pre><code>Dr0: 000000000157e650 -Dr7: 0000000000fd0005 -</code></pre><p>Most intriguing! This was done by telling Cheat Engine to find "what writes" to the address, then "what accesses" the address. I wonder if the order matters?<pre><code>Dr0: 000000000157e650 -Dr7: 0000000000df0005 -</code></pre><p>"What accesses" and then "what writes" does change it. Very well! We're only concerned in a single breakpoint, so we won't worry about this, but it's good to know that we can inspect what Cheat Engine is doing. It's also interesting to see how Cheat Engine is using hardware breakpoints and not software breakpoints.<p>For simplicity, our code is going to assume that we're the only ones messing around with the debug registers, and that there will only be a single debug register in use. Make sure to add <code>THREAD_SET_CONTEXT</code> to the permissions when opening the thread handle:<pre><code class=language-rust data-lang=rust>pub fn set_context(&self, context: &winapi::um::winnt::CONTEXT) -> io::Result<()> { - // SAFETY: the handle is valid and structure points to valid memory. - if unsafe { - winapi::um::processthreadsapi::SetThreadContext(self.handle.as_ptr(), context) - } == FALSE - { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } -} - -pub fn watch_memory_write(&self, addr: usize) -> io::Result<()> { - let mut context = self.get_context()?; - context.Dr0 = addr as u64; - context.Dr7 = 0x00000000000d0001; - self.set_context(&context)?; - todo!() -} -</code></pre><p>If we do this (and temporarily get rid of the <code>todo!()</code>), trying to change the value in the Cheat Engine tutorial will greet us with a warm message:<blockquote><p><strong>Tutorial-x86_64</strong><p>External exception 80000004.<p>Press OK to ignore and risk data corruption.<br> Press Abort to kill the program.<p><kbd>OK</kbd> <kbd>Abort</kbd></blockquote><p>There is no debugger attached yet that could possibly handle this exception, so the exception just propagates. Let's fix that.<h2 id=handling-debug-events>Handling debug events</h2><p>Now that we've succeeded on setting breakpoints, we can actually follow the steps described in <a href=https://docs.microsoft.com/en-us/windows/win32/debug/creating-a-basic-debugger>Creating a Basic Debugger</a>. It starts by saying that we should use <a href=https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-debugactiveprocess><code>DebugActiveProcess</code></a> to attach our processor, the debugger, to the process we want to debug, the debuggee. This function lives under the <code>debugapi</code> header, so add it to <code>winapi</code> features:<pre><code class=language-rust data-lang=rust>pub struct DebugToken { - pid: u32, -} - -pub fn debug(pid: u32) -> io::Result<DebugToken> { - if unsafe { winapi::um::debugapi::DebugActiveProcess(pid) } == FALSE { - return Err(io::Error::last_os_error()); - }; - let token = DebugToken { pid }; - if unsafe { winapi::um::winbase::DebugSetProcessKillOnExit(FALSE) } == FALSE { - return Err(io::Error::last_os_error()); - }; - Ok(token) -} - -impl Drop for DebugToken { - fn drop(&mut self) { - unsafe { winapi::um::debugapi::DebugActiveProcessStop(self.pid) }; - } -} -</code></pre><p>Once again, we create a wrapper <code>struct</code> with <code>Drop</code> to stop debugging the process once the token is dropped. The call to <code>DebugSetProcessKillOnExit</code> in our <code>debug</code> method ensures that, if our process (the debugger) dies, the process we're debugging (the debuggee) stays alive. We don't want to be restarting the entire Cheat Engine tutorial every time our Rust code crashes!<p>With the debugger attached, we can wait for debug events. We will put this method inside of <code>impl DebugToken</code>, so that the only way you can call it is if you successfully attached to another process:<pre><code class=language-rust data-lang=rust>impl DebugToken { - pub fn wait_event( - &self, - timeout: Option<Duration>, - ) -> io::Result<winapi::um::minwinbase::DEBUG_EVENT> { - let mut result = MaybeUninit::uninit(); - let timeout = timeout - .map(|d| d.as_millis().try_into().ok()) - .flatten() - .unwrap_or(winapi::um::winbase::INFINITE); - - // SAFETY: can only wait for events with a token, so the debugger is active. - if unsafe { winapi::um::debugapi::WaitForDebugEvent(result.as_mut_ptr(), timeout) } == FALSE - { - Err(io::Error::last_os_error()) - } else { - // SAFETY: the call returned non-zero, so the structure is initialized. - Ok(unsafe { result.assume_init() }) - } - } -} -</code></pre><p><code>WaitForDebugEvent</code> wants a timeout in milliseconds, so our function lets the user pass the more Rusty <code>Duration</code> type. <code>None</code> will indicate "there is no timeout", i.e., it's infinite. If the duration is too large to fit in the <code>u32</code> (<code>try_into</code> fails), it will also be infinite.<p>If we attach the debugger, set the hardware watchpoint, and modify the memory location from the tutorial, an event with <code>dwDebugEventCode = 3</code> will be returned! Now, back to the page with the <a href=https://docs.microsoft.com/en-us/windows/win32/debug/debugging-events>Debugging Events</a>… Gah! It only has the name of the constants, not the values. Well, good thing <a href=https://docs.rs/>docs.rs</a> has a source view! We can just check the values in the <a href=https://docs.rs/winapi/0.3.9/src/winapi/um/minwinbase.rs.html#203-211>source code for <code>winapi</code></a>:<pre><code class=language-rust data-lang=rust>pub const EXCEPTION_DEBUG_EVENT: DWORD = 1; -pub const CREATE_THREAD_DEBUG_EVENT: DWORD = 2; -pub const CREATE_PROCESS_DEBUG_EVENT: DWORD = 3; -pub const EXIT_THREAD_DEBUG_EVENT: DWORD = 4; -pub const EXIT_PROCESS_DEBUG_EVENT: DWORD = 5; -pub const LOAD_DLL_DEBUG_EVENT: DWORD = 6; -pub const UNLOAD_DLL_DEBUG_EVENT: DWORD = 7; -pub const OUTPUT_DEBUG_STRING_EVENT: DWORD = 8; -pub const RIP_EVENT: DWORD = 9; -</code></pre><p>So, we've got a <code>CREATE_PROCESS_DEBUG_EVENT</code>:<blockquote><p>Generated whenever a new process is created in a process being debugged or whenever the debugger begins debugging an already active process. The system generates this debugging event before the process begins to execute in user mode and before the system generates any other debugging events for the new process.</blockquote><p>It makes sense that this is our first event. By the way, if you were trying this out with a <code>sleep</code> lying around in your code, you may have noticed that the window froze until the debugger terminated. That's because:<blockquote><p>When the system notifies the debugger of a debugging event, it also suspends all threads in the affected process. The threads do not resume execution until the debugger continues the debugging event by using <a href=https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-continuedebugevent><code>ContinueDebugEvent</code></a>.</blockquote><p>Let's call <code>ContinueDebugMethod</code> but also wait on more than one event and see what happens:<pre><code class=language-rust data-lang=rust>for _ in 0..10 { - let event = debugger.wait_event(None).unwrap(); - println!("Got {}", event.dwDebugEventCode); - debugger.cont(event, true).unwrap(); -} -</code></pre><pre><code>Got 3 -Got 6 -Got 6 -Got 6 -Got 6 -Got 6 -Got 6 -Got 6 -Got 6 -Got 6 -</code></pre><p>That's a lot of <code>LOAD_DLL_DEBUG_EVENT</code>. Pumping it up to one hundred and also showing the index we get the following:<pre><code>0. Got 3 -1. Got 6 -... -40. Got 6 -41. Got 2 -42. Got 1 -43. Got 4 -</code></pre><p>In order, we got:<ul><li>One <code>CREATE_PROCESS_DEBUG_EVENT</code>.<li>Forty <code>LOAD_DLL_DEBUG_EVENT</code>.<li>One <code>CREATE_THREAD_DEBUG_EVENT</code>.<li>One <code>EXCEPTION_DEBUG_EVENT</code>.<li>One <code>EXIT_THREAD_DEBUG_EVENT</code>.</ul><p>And, if after all this, you change the value in the Cheat Engine tutorial (thus triggering our watch point), we get <code>EXCEPTION_DEBUG_EVENT</code>!<blockquote><p>Generated whenever an exception occurs in the process being debugged. Possible exceptions include attempting to access inaccessible memory, executing breakpoint instructions, attempting to divide by zero, or any other exception noted in Structured Exception Handling.</blockquote><p>If we print out all the fields in the <a href=https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-exception_debug_info><code>EXCEPTION_DEBUG_INFO</code></a> structure:<pre><code>Watching writes to 10e3a0 for 10s -First chance: 1 -ExceptionCode: 2147483652 -ExceptionFlags: 0 -ExceptionRecord: 0x0 -ExceptionAddress: 0x10002c5ba -NumberParameters: 0 -ExceptionInformation: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] -</code></pre><p>The <code>ExceptionCode</code>, which is <code>0x80000004</code>, corresponds with <code>EXCEPTION_SINGLE_STEP</code>:<blockquote><p>A trace trap or other single-instruction mechanism signaled that one instruction has been executed.</blockquote><p>The <code>ExceptionAddress</code> is supposed to be "the address where the exception occurred". Very well! I have already completed this step of the tutorial, and I know the instruction is <code>mov [rax],edx</code> (or, as Cheat Engine shows, the bytes <code>89 10</code> in hexadecimal). The opcode for the <code>nop</code> instruction is <code>90</code> in hexadecimal, so if we replace two bytes at this address, we should be able to complete the tutorial.<p>Note that we also need to flush the instruction cache, as noted in the Windows documentation:<blockquote><p>Debuggers frequently read the memory of the process being debugged and write the memory that contains instructions to the instruction cache. After the instructions are written, the debugger calls the <a href=https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-flushinstructioncache><code>FlushInstructionCache</code></a> function to execute the cached instructions.</blockquote><p>So we add a new method to <code>impl Process</code>:<pre><code class=language-rust data-lang=rust>/// Flushes the instruction cache. -/// -/// Should be called when writing to memory regions that contain code. -pub fn flush_instruction_cache(&self) -> io::Result<()> { - // SAFETY: the call doesn't have dangerous side-effects. - if unsafe { - winapi::um::processthreadsapi::FlushInstructionCache( - self.handle.as_ptr(), - ptr::null(), - 0, - ) - } == FALSE - { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } -} -</code></pre><p>And write some quick and dirty code to get this done:<pre><code class=language-rust data-lang=rust>let addr = ...; -println!("Watching writes to {:x} for 10s", addr); -threads.iter_mut().for_each(|thread| { - thread.watch_memory_write(addr).unwrap(); -}); -loop { - let event = debugger.wait_event(None).unwrap(); - if event.dwDebugEventCode == 1 { - let exc = unsafe { event.u.Exception() }; - if exc.ExceptionRecord.ExceptionCode == 2147483652 { - let addr = exc.ExceptionRecord.ExceptionAddress as usize; - match process.write_memory(addr, &[0x90, 0x90]) { - Ok(_) => eprintln!("Patched [{:x}] with NOP", addr), - Err(e) => eprintln!("Failed to patch [{:x}] with NOP: {}", addr, e), - }; - process.flush_instruction_cache().unwrap(); - debugger.cont(event, true).unwrap(); - break; - } - } - debugger.cont(event, true).unwrap(); -} -</code></pre><p>Although it seems to work:<pre><code>Watching writes to 15103f0 for 10s -Patched [10002c5ba] with NOP -</code></pre><p>It really doesn't:<blockquote><p><strong>Tutorial-x86_64</strong><p>Access violation.<p>Press OK to ignore and risk data corruption.<br> Press Abort to kill the program.<p><kbd>OK</kbd> <kbd>Abort</kbd></blockquote><p>Did we write memory somewhere we shouldn't? The documentation does mention "segment-relative" and "linear virtual addresses":<blockquote><p><code>GetThreadSelectorEntry</code> returns the descriptor table entry for a specified selector and thread. Debuggers use the descriptor table entry to convert a segment-relative address to a linear virtual address. The <code>ReadProcessMemory</code> and <code>WriteProcessMemory</code> functions require linear virtual addresses.</blockquote><p>But nope! This isn't the problem. The problem is that the <code>ExceptionRecord.ExceptionAddress</code> is <em>after</em> the execution happened, so it's already 2 bytes beyond where it should be. We were accidentally writing out the first half of the next instruction, which, yeah, could not end good.<p>So does it work if I do this instead?:<pre><code class=language-rust data-lang=rust>process.write_memory(addr - 2, &[0x90, 0x90]) -// ^^^ new -</code></pre><p>This totally does work. Step 5: complete 🎉<h2 id=properly-patching-instructions>Properly patching instructions</h2><p>You may not be satisfied at all with our solution. Not only are we hardcoding some magic constants to set hardware watchpoints, we're also relying on knowledge specific to the Cheat Engine tutorial (insofar that we're replacing two bytes worth of instruction with NOPs).<p>Properly supporting more than one hardware breakpoint, along with supporting different types of breakpoints, is definitely doable. The meaning of the bits for the debug registers is well defined, and you can definitely study that to come up with <a href=https://github.com/mmorearty/hardware-breakpoints>something more sophisticated</a> and support multiple different breakpoints. But for now, that's out of the scope of this series. The tutorial only wants us to use an on-write watchpoint, and our solution is fine and portable for that use case.<p>However, relying on the size of the instructions is pretty bad. The instructions x86 executes are of variable length, so we can't possibly just look back until we find the previous instruction, or even naively determine its length. A lot of unrelated sequences of bytes are very likely instructions themselves. We need a disassembler. No, we're not writing our own<sup class=footnote-reference><a href=#4>4</a></sup>.<p>Searching on <a href=https://crates.io>crates.io</a> for "disassembler" yields a few results, and the first one I've found is <a href=https://crates.io/crates/iced-x86>iced-x86</a>. I like the name, it has a decent amount of GitHub stars, and it was last updated less than a month ago. I don't know about you, but I think we've just hit a jackpot!<p>It's quite heavy though, so I will add it behind a feature gate, and users that want it may opt into it:<pre><code class=language-toml data-lang=toml>[features] -patch-nops = ["iced-x86"] - -[dependencies] -iced-x86 = { version = "1.10.3", optional = true } -</code></pre><p>You can make use of it with <code>cargo run --features=patch-nops</code>. I don't want to turn this blog post into a tutorial for <code>iced-x86</code>, but in essence, we need to make use of its <code>Decoder</code>. Here's the plan:<ol><li>Find the memory region corresponding to the address we want to patch.<li>Read the entire region.<li>Decode the read bytes until the instruction pointer reaches our address.<li>Because we just parsed the previous instruction, we know its length, and can be replaced with NOPs.</ol><pre><code class=language-rust data-lang=rust>#[cfg(feature = "patch-nops")] -pub fn nop_last_instruction(&self, addr: usize) -> io::Result<()> { - use iced_x86::{Decoder, DecoderOptions, Formatter, Instruction, NasmFormatter}; - - let region = self - .memory_regions() - .into_iter() - .find(|region| { - let base = region.BaseAddress as usize; - base <= addr && addr < base + region.RegionSize - }) - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "no matching region found"))?; - - let bytes = self.read_memory(region.BaseAddress as usize, region.RegionSize)?; - - let mut decoder = Decoder::new(64, &bytes, DecoderOptions::NONE); - decoder.set_ip(region.BaseAddress as _); - - let mut instruction = Instruction::default(); - while decoder.can_decode() { - decoder.decode_out(&mut instruction); - if instruction.next_ip() as usize == addr { - return self - .write_memory(instruction.ip() as usize, &vec![0x90; instruction.len()]) - .map(drop); - } - } - - Err(io::Error::new( - io::ErrorKind::Other, - "no matching instruction found", - )) -} -</code></pre><p>Pretty straightforward! We can set the "instruction pointer" of the decoder so that it matches with the address we're reading from. The <code>next_ip</code> method comes in really handy. Overall, it's a bit inefficient, because we could reuse the regions retrieved previously, but other than that, there is not much room for improvement.<p>With this, we are no longer hardcoding the instruction size or guessing which instruction is doing what. You may wonder, what if the region does not start with valid executable code? It could be possible that the instructions are in some memory region with garbage except for a very specific location with real code. I don't know how Cheat Engine handles this, but I think it's reasonable to assume that the region starts with valid code.<p>As far as I can tell (after having asked a bit around), the encoding is usually self synchronizing (similar to UTF-8), so eventually we should end up with correct instructions. But someone can still intentionally write real code between garbage data which we would then disassemble incorrectly. This is a problem on all variable-length ISAs. Half a solution is to <a href=https://stackoverflow.com/q/3983735/>start at the entry point</a>, decode all instructions, and follow the jumps. The other half would be correctly identifying jumps created just to trip a disassembler up, and jumps pointing to dynamically-calculated addresses!<h2 id=finale>Finale</h2><p>That was quite a deep dive! We have learnt about the existence of the various breakpoint types (software, hardware, and even behaviour, such as watchpoints), how to debug a separate process, and how to correctly update the code other process is running on-the-fly. The <a href=https://github.com/lonami/memo>code for this post</a> is available over at my GitHub. You can run <code>git checkout step5</code> after cloning the repository to get the right version of the code.<p>Although we've only talked about <em>setting</em> breakpoints, there are of course <a href=https://reverseengineering.stackexchange.com/a/16547>ways of detecting them</a>. There's <a href=https://www.codeproject.com/Articles/30815/An-Anti-Reverse-Engineering-Guide>entire guides about it</a>. Again, we currently hardcode the fact we want to add a single watchpoint using the first debug register. A proper solution here would be to actually calculate the needs that need to be set, as well as keeping track of how many breakpoints have been added so far.<p>Hardware breakpoints are also limited, since they're simply a bunch of registers, and our machine does not have infinite registers. How are other debuggers like <code>gdb</code> able to create a seemingly unlimited amount of breakpoints? Well, the GDB wiki actually has a page on <a href=https://sourceware.org/gdb/wiki/Internals%20Watchpoints>Internals Watchpoints</a>, and it's really interesting! <code>gdb</code> essentially single-steps through the entire program and tests the expressions after every instruction:<blockquote><p>Software watchpoints are very slow, since GDB needs to single-step the program being debugged and test the value of the watched expression(s) after each instruction.</blockquote><p>However, that's not the only way. One could <a href=https://stackoverflow.com/a/7805842/>change the protection level</a> of the region of interest (for example, remove the write permission), and when the program tries to write there, it will fail! In any case, the GDB wiki is actually a pretty nice resource. It also has a section on <a href=https://sourceware.org/gdb/wiki/Internals/Breakpoint%20Handling>Breakpoint Handling</a>, which contains some additional insight.<p>With regards to code improvements, <code>DebugToken::wait_event</code> could definitely be both nicer and safer to use, with a custom <code>enum</code>, so the user does not need to rely on magic constants or having to resort to <code>unsafe</code> access to get the right <code>union</code> variant.<p>In the <a href=/blog/woce-6>next post</a>, we'll tackle the sixth step of the tutorial: Pointers. It reuses the debugging techniques presented here to backtrack where the pointer for our desired value is coming from, so here we will need to actually <em>understand</em> what the instructions are doing, not just patching them out!<h3 id=footnotes>Footnotes</h3><div class=footnote-definition id=1><sup class=footnote-definition-label>1</sup><p>I'm not super happy about the design of it all, but we won't actually need anything beyond scanning for integers for the rest of the steps so it doesn't really matter.</div><div class=footnote-definition id=2><sup class=footnote-definition-label>2</sup><p>There seems to be a way to pause the entire process in one go, with the <a href=https://stackoverflow.com/a/4062698/>undocumented <code>NtSuspendProcess</code></a> function!</div><div class=footnote-definition id=3><sup class=footnote-definition-label>3</sup><p>It really is called that. The naming went from "IP" (instruction pointer, 16 bits), to "EIP" (extended instruction pointer, 32 bits) and currently "RIP" (64 bits). The naming convention for upgraded registers is the same (RAX, RBX, RCX, and so on). The <a href=https://wiki.osdev.org/CPU_Registers_x86_64>OS Dev wiki</a> is a great resource for this kind of stuff.</div><div class=footnote-definition id=4><sup class=footnote-definition-label>4</sup><p>Well, we don't need an entire disassembler. Knowing the length of each instruction is enough, but that on its own is also a lot of work.</div></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,283 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Writing our own Cheat Engine: Pointers | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Writing our own Cheat Engine: Pointers</h1><div class=time><p>2021-03-13</div><p>This is part 6 on the <em>Writing our own Cheat Engine</em> series:<ul><li><a href=/blog/woce-1>Part 1: Introduction</a> (start here if you're new to the series!)<li><a href=/blog/woce-2>Part 2: Exact Value scanning</a><li><a href=/blog/woce-3>Part 3: Unknown initial value</a><li><a href=/blog/woce-4>Part 4: Floating points</a><li><a href=/blog/woce-5>Part 5: Code finder</a><li>Part 6: Pointers</ul><p>In part 5 we wrote our very own debugger. We learnt that Cheat Engine is using hardware breakpoints to watch memory change, and how to do the same ourselves. We also learnt that hardware points are not the only way to achieve the effect of watchpoints, although they certainly are the fastest and cleanest approach.<p>In this post, we will be reusing some of that knowledge to find out a closely related value, the <em>pointer</em> that points to the real value<sup class=footnote-reference><a href=#1>1</a></sup>. As a quick reminder, a pointer is nothing but an <code>usize</code><sup class=footnote-reference><a href=#2>2</a></sup> representing the address of another portion of memory, in this case, the actual value we will be scanning for. A pointer is a value that, well, points elsewhere. In Rust we normally use reference instead, which are safer (typed and their lifetime is tracked) than pointers, but in the end we can achieve the same with both.<p>Why care about pointers? It turns out that things, such as your current health in-game, are very unlikely to end up in the same memory position when you restart the game (or even change to another level, or even during gameplay). So, if you perform a scan and find that the address where your health is stored is <code>0x73AABABE</code>, you might be tempted to save it and reuse it next time you launch the game. Now you don't need to scan for it again! Alas, as soon as you restart the game, the health is now stored at <code>0x5AADBEEF</code>.<p>Not all hope is lost! The game must <em>somehow</em> have a way to reliably find this value, and the way it's done is with pointers. There will always be some base address that holds a pointer, and the game code knows where to find this pointer. If we are also able to find the pointer at said base address, and follow it ourselves ("dereferencing" it), we can perform the same steps the game is doing, and reliably find the health no matter how much we restart the game<sup class=footnote-reference><a href=#3>3</a></sup>.<h2 id=code-finder>Code finder</h2><details open><summary>Cheat Engine Tutorial: Step 6</summary> <blockquote><p>In the previous step I explained how to use the Code finder to handle changing locations. But that method alone makes it difficult to find the address to set the values you want. That's why there are pointers:<p>At the bottom you'll find 2 buttons. One will change the value, and the other changes the value AND the location of the value. For this step you don't really need to know assembler, but it helps a lot if you do.<p>First find the address of the value. When you've found it use the function to find out what accesses this address.<p>Change the value again, and a item will show in the list. Double click that item. (or select and click on more info) and a new window will open with detailed information on what happened when the instruction ran.<p>If the assembler instruction doesn't have anything between a '[' and ']' then use another item in the list. If it does it will say what it think will be the value of the pointer you need.<p>Go back to the main cheat engine window (you can keep this extra info window open if you want, but if you close it, remember what is between the [ and ]) and do a 4 byte scan in hexadecimal for the value the extra info told you. When done scanning it may return 1 or a few hundred addresses. Most of the time the address you need will be the smallest one. Now click on manually add and select the pointer checkbox.<p>The window will change and allow you to type in the address of a pointer and a offset. Fill in as address the address you just found. If the assembler instruction has a calculation (e.g: [esi+12]) at the end then type the value in that's at the end. else leave it 0. If it was a more complicated instruction look at the calculation.<p>Example of a more complicated instruction:<p>[EAX*2+EDX+00000310] eax=4C and edx=00801234.<p>In this case EDX would be the value the pointer has, and EAX*2+00000310 the offset, so the offset you'd fill in would be 2*4C+00000310=3A8. (this is all in hex, use calc.exe from windows in scientific mode to calculate).<p>Back to the tutorial, click OK and the address will be added, If all went right the address will show P->xxxxxxx, with xxxxxxx being the address of the value you found. If thats not right, you've done something wrong. Now, change the value using the pointer you added in 5000 and freeze it. Then click Change pointer, and if all went right the next button will become visible.<p><em>extra</em>: And you could also use the pointer scanner to find the pointer to this address.</blockquote></details><h2 id=on-access-watchpoints>On-access watchpoints</h2><p>Last time we managed to learn how hardware breakpoints were being set by observing Cheat Engine's behaviour. I think it's now time to handle this properly instead. We'll check out the <a href=https://wiki.osdev.org/CPU_Registers_x86#Debug_Registers>CPU Registers x86 page on OSDev</a> to learn about it:<ul><li>DR0, DR1, DR2 and DR3 can hold a memory address each. This address will be used by the breakpoint.<li>DR4 is actually an <a href=https://en.wikipedia.org/wiki/X86_debug_register>obsolete synonym</a> for DR6.<li>DR5 is another obsolete synonym, this time for DR7.<li>DR6 is debug status. The four lowest bits indicate which breakpoint was hit, and the four highest bits contain additional information. We should make sure to clear this ourselves when a breakpoint is hit.<li>DR7 is debug control, which we need to study more carefully.</ul><p>Each debug register DR0 through DR3 has two corresponding bits in DR7, starting from the lowest-order bit, to indicate whether the corresponding register is a <strong>L</strong>ocal or <strong>G</strong>lobal breakpoint. So it looks like this:<pre><code> Meaning: [ .. .. | G3 | L3 | G2 | L2 | G1 | L1 | G0 | L0 ] -Bit-index: 31-08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 | 00 -</code></pre><p>Cheat Engine was using local breakpoints, because the zeroth bit was set. Probably because we don't want these breakpoints to infect other programs! Because we were using only one breakpoint, only the lowermost bit was being set. The local 1st, 2nd and 3rd bits were unset.<p>Now, each debug register DR0 through DR4 has four additional bits in DR7, two for the <strong>C</strong>ondition and another two for the <strong>S</strong>ize:<pre><code> Meaning: [ S3 | C3 | S2 | C2 | S1 | C1 | S0 | C0 | .. .. ] -Bit-index: 31 30 | 29 28 | 27 26 | 25 24 | 23 22 | 21 20 | 19 18 | 17 16 | 15-00 -</code></pre><p>The two bits of the condition mean the following:<ul><li><code>00</code> execution breakpoint.<li><code>01</code> write watchpoint.<li><code>11</code> read/write watchpoint.<li><code>10</code> unsupported I/O read/write.</ul><p>When we were using Cheat Engine to add write watchpoints, the bits 17 and 16 were indeed set to <code>01</code>, and the bits 19 and 18 were set to <code>11</code>. Hm, but <em>11<sub>2</sub> = 3<sub>10</sub></em> , and yet, we were watching writes to 4 bytes. So what's up with this? Is there a different mapping for the size which isn't documented at the time of writing? Seems we need to learn from Cheat Engine's behaviour one more time.<p>For reference, this is what DR7 looked like when we added a single write watchpoint:<pre><code>hex: 000d_0001 -bin: 00000000_00001101_00000000_00000001 -</code></pre><p>And this is the code I will be using to check the breakpoints of different sizes:<pre><code>thread::enum_threads(pid) - .unwrap() - .into_iter() - .for_each(|tid| { - let thread = thread::Thread::open(tid).unwrap(); - let ctx = thread.get_context().unwrap(); - eprintln!("hex: {:08x}", ctx.Dr7); - eprintln!("bin: {:032b}", ctx.Dr7); - }); -</code></pre><p>Let's compare this to watchpoints for sizes 1, 2, 4 and 8 bytes:<pre><code>1 byte -hex: 0001_0401 -bin: 00000000_00000001_00000100_00000001 - -2 bytes -hex: 0005_0401 -bin: 00000000_00000101_00000100_00000001 - -4 bytes -hex: 000d_0401 -bin: 00000000_00001101_00000100_00000001 - -8 bytes -hex: 0009_0401 -bin: 00000000_00001001_00000100_00000001 - ^ wut? -</code></pre><p>I have no idea what's up with that stray tenth bit. Its use does not seem documented, and things worked fine without it, so we'll ignore it. The lowest bit is set to indicate we're using DR0, bits 17 and 16 represent the write watchpoint, and the size seems to be as follows:<ul><li><code>00</code> for a single byte.<li><code>01</code> for two bytes (a "word").<li><code>11</code> for four bytes (a "double word").<li><code>10</code> for eight bytes (a "quadruple word").</ul><p>Doesn't make much sense if you ask me, but we'll roll with it. Just to confirm, this is what the "on-access" breakpoint looks like according to Cheat Engine:<pre><code>hex: 000f_0401 -bin: 00000000_00001111_00000100_00000001 -</code></pre><p>So it all checks out! The bit pattern is <code>11</code> for read/write (technically, a write is also an access). Let's implement this!<h2 id=proper-breakpoint-handling>Proper breakpoint handling</h2><p>The first thing we need to do is represent the possible breakpoint conditions:<pre><code class=language-rust data-lang=rust>#[repr(u8)] -pub enum Condition { - Execute = 0b00, - Write = 0b01, - Access = 0b11, -} -</code></pre><p>And also the legal breakpoint sizes:<pre><code class=language-rust data-lang=rust>#[repr(u8)] -pub enum Size { - Byte = 0b00, - Word = 0b01, - DoubleWord = 0b11, - QuadWord = 0b10, -} -</code></pre><p>We are using <code>#[repr(u8)]</code> so that we can convert a given variant into the corresponding bit pattern. With the right types defined in order to set a breakpoint, we can start implementing the method that will set them (inside <code>impl Thread</code>):<pre><code class=language-rust data-lang=rust>pub fn add_breakpoint(&self, addr: usize, cond: Condition, size: Size) -> io::Result<Breakpoint> { - let mut context = self.get_context()?; - todo!() -} -</code></pre><p>First, let's try finding an "open spot" where we could set our breakpoint. We will "slide" a the <code>0b11</code> bitmask over the lowest eight bits, and if and only if both the local and global bits are unset, then we're free to set a breakpoint at this index<sup class=footnote-reference><a href=#4>4</a></sup>:<pre><code class=language-rust data-lang=rust>let index = (0..4) - .find_map(|i| ((context.Dr7 & (0b11 << (i * 2))) == 0).then(|| i)) - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "no debug register available"))?; -</code></pre><p>Once an <code>index</code> is found, we can set the address we want to watch in the corresponding register and update the debug control bits:<pre><code class=language-rust data-lang=rust>let addr = addr as u64; -match index { - 0 => context.Dr0 = addr, - 1 => context.Dr1 = addr, - 2 => context.Dr2 = addr, - 3 => context.Dr3 = addr, - _ => unreachable!(), -} - -let clear_mask = !((0b1111 << (16 + index * 4)) | (0b11 << (index * 2))); -context.Dr7 &= clear_mask; - -context.Dr7 |= 1 << (index * 2); - -let sc = (((size as u8) << 2) | (cond as u8)) as u64; -context.Dr7 |= sc << (16 + index * 4); - -self.set_context(&context)?; -Ok(Breakpoint { - thread: self, - clear_mask, -}) -</code></pre><p>Note that we're first creating a "clear mask". We switch on all the bits that we may use for this breakpoint, and then negate. Effectively, <code>Dr7 & clear_mask</code> will make sure we don't leave any bit high on accident. We apply the mask before OR-ing the rest of bits to also clear any potential garbage on the size and condition bits. Next, we set the bit to enable the new local breakpoint, and also store the size and condition bits at the right location.<p>With the context updated, we can set it back and return the <code>Breakpoint</code>. It stores the <code>thread</code> and the <code>clear_mask</code> so that it can clean up on <code>Drop</code>. We are technically relying on <code>Drop</code> to run behaviour here, but the cleanup is done on a best-effort basis. If the user intentionally forgets the <code>Breakpoint</code>, maybe they want the <code>Breakpoint</code> to forever be set.<p>This logic is begging for a testcase though; I'll split it into a new <code>Breakpoint::update_dbg_control</code> method and test that out:<pre><code class=language-rust data-lang=rust> -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn brk_add_one() { - // DR7 starts with garbage which should be respected. - let (clear_mask, dr, dr7) = - Breakpoint::update_dbg_control(0x1700, Condition::Write, Size::DoubleWord).unwrap(); - - assert_eq!(clear_mask, 0xffff_ffff_fff0_fffc); - assert_eq!(dr, DebugRegister::Dr0); - assert_eq!(dr7, 0x0000_0000_000d_1701); - } - - #[test] - fn brk_add_two() { - let (clear_mask, dr, dr7) = Breakpoint::update_dbg_control( - 0x0000_0000_000d_0001, - Condition::Write, - Size::DoubleWord, - ) - .unwrap(); - - assert_eq!(clear_mask, 0xffff_ffff_ff0f_fff3); - assert_eq!(dr, DebugRegister::Dr1); - assert_eq!(dr7, 0x0000_0000_00dd_0005); - } - - #[test] - fn brk_try_add_when_max() { - assert!(Breakpoint::update_dbg_control( - 0x0000_0000_dddd_0055, - Condition::Write, - Size::DoubleWord - ) - .is_none()); - } -} -</code></pre><pre><code>running 3 tests -test thread::tests::brk_add_one ... ok -test thread::tests::brk_add_two ... ok -test thread::tests::brk_try_add_when_max ... ok -</code></pre><p>Very good! With proper breakpoint handling usable, we can continue.<h2 id=inferring-the-pointer-value>Inferring the pointer value</h2><p>After scanning memory for the location we're looking for (say, our current health), we then add an access watchpoint, and wait for an exception to occur. As a reminder, here's the page with the <a href=https://docs.microsoft.com/en-us/windows/win32/debug/debugging-events>Debugging Events</a>:<pre><code class=language-rust data-lang=rust>let addr = ...; -let mut threads = ...; - -let _watchpoints = threads - .iter_mut() - .map(|thread| { - thread - .add_breakpoint(addr, thread::Condition::Access, thread::Size::DoubleWord) - .unwrap() - }) - .collect::<Vec<_>>(); - -loop { - let event = debugger.wait_event(None).unwrap(); - if event.dwDebugEventCode == winapi::um::minwinbase::EXCEPTION_DEBUG_EVENT { - let exc = unsafe { event.u.Exception() }; - if exc.ExceptionRecord.ExceptionCode == winapi::um::minwinbase::EXCEPTION_SINGLE_STEP { - todo!(); - } - } - debugger.cont(event, true).unwrap(); -} -</code></pre><p>Now, inside the <code>todo!()</code> we will want to do a few things, namely printing out the instructions "around this location" and dumping the entire thread context on screen. To print the instructions, we need to import <code>iced_x86</code> again, iterate over all memory regions to find the region where the exception happened, read the corresponding bytes, decode the instructions, and when we find the one with a corresponding instruction pointer, print "around it":<pre><code class=language-rust data-lang=rust>use iced_x86::{Decoder, DecoderOptions, Formatter, Instruction, NasmFormatter}; - -let addr = exc.ExceptionRecord.ExceptionAddress as usize; -let region = process - .memory_regions() - .into_iter() - .find(|region| { - let base = region.BaseAddress as usize; - base <= addr && addr < base + region.RegionSize - }) - .unwrap(); - -let bytes = process - .read_memory(region.BaseAddress as usize, region.RegionSize) - .unwrap(); - -let mut decoder = Decoder::new(64, &bytes, DecoderOptions::NONE); -decoder.set_ip(region.BaseAddress as _); - -let mut formatter = NasmFormatter::new(); -let mut output = String::new(); - -let instructions = decoder.into_iter().collect::<Vec<_>>(); -for (i, ins) in instructions.iter().enumerate() { - if ins.next_ip() as usize == addr { - let low = i.saturating_sub(5); - let high = (i + 5).min(instructions.len()); - for j in low..high { - let ins = &instructions[j]; - print!("{} {:016X} ", if j == i { ">>>" } else { " " }, ins.ip()); - let k = (ins.ip() - region.BaseAddress as usize as u64) as usize; - let instr_bytes = &bytes[k..k + ins.len()]; - for b in instr_bytes.iter() { - print!("{:02X}", b); - } - if instr_bytes.len() < 10 { - for _ in 0..10usize.saturating_sub(instr_bytes.len()) { - print!(" "); - } - } - - output.clear(); - formatter.format(ins, &mut output); - println!(" {}", output); - } - break; - } -} -debugger.cont(event, true).unwrap(); -break; -</code></pre><p>The result is pretty fancy:<pre><code> 000000010002CAAC 48894DF0 mov [rbp-10h],rcx - 000000010002CAB0 488955F8 mov [rbp-8],rdx - 000000010002CAB4 48C745D800000000 mov qword [rbp-28h],0 - 000000010002CABC 90 nop - 000000010002CABD 488B050CA02D00 mov rax,[rel 100306AD0h] ->>> 000000010002CAC4 8B00 mov eax,[rax] - 000000010002CAC6 8945EC mov [rbp-14h],eax - 000000010002CAC9 B9E8030000 mov ecx,3E8h - 000000010002CACE E88D2FFEFF call 000000010000FA60h - 000000010002CAD3 8945E8 mov [rbp-18h],eax -</code></pre><p>Cool! So <code>rax</code> is holding an address, meaning it's a pointer, and the value it reads (dereferences) is stored back into <code>eax</code> (because it does not need <code>rax</code> anymore). Alas, the current thread context has the register state <em>after</em> the instruction was executed, and <code>rax</code> no longer contains the address at this point. However, notice how the previous instruction writes a fixed value to <code>rax</code>, and then that value is used to access memory, like so:<pre><code class=language-rust data-lang=rust>let eax = memory[memory[0x100306AD0]]; -</code></pre><p>The value at <code>memory[0x100306AD0]</code> <em>is</em> the pointer! No offsets are used, because nothing is added to the pointer after it's read. This means that, if we simply scan for the address we were looking for, we should find out where the pointer is stored:<pre><code class=language-rust data-lang=rust>let addr = ...; -let scan = process.scan_regions(&regions, Scan::Exact(addr as u64)); - -scan.into_iter().for_each(|region| { - region.locations.iter().for_each(|ptr_addr| { - println!("[{:x}] = {:x}", ptr_addr, addr); - }); -}); -</code></pre><p>And just like that:<pre><code>[100306ad0] = 15de9f0 -</code></pre><p>Notice how the pointer address found matches with the offset used by the instructions:<pre><code> 000000010002CABD 488B050CA02D00 mov rax,[rel 100306AD0h] - this is the same as the value we just found ^^^^^^^^^^ -</code></pre><p>Very interesting indeed. We were actually very lucky to have only found a single memory location containing the pointer value, <code>0x15de9f0</code>. Cheat Engine somehow knows that this value is always stored at <code>0x100306ad0</code> (or rather, at <code>Tutorial-x86_64.exe+306AD0</code>), because the address shows green. How does it do this?<h2 id=base-addresses>Base addresses</h2><p>Remember back in <a href=/blog/woce-2>part 2</a> when we introduced the memory regions? They're making a comeback! A memory region contains both the current memory protection option <em>and</em> the protection level when the region was created. If we try printing out the protection levels for both the memory region containing the value, and the memory region containing the pointer, this is what we get (the addresses differ from the ones previously because I restarted the tutorial):<pre><code>Region holding the value: - BaseAddress: 0xb0000 - AllocationBase: 0xb0000 - AllocationProtect: 0x4 - RegionSize: 1007616 - State: 4096 - Protect: 4 - Type: 0x20000 - -Region holding the pointer: - BaseAddress: 0x100304000 - AllocationBase: 0x100000000 - AllocationProtect: 0x80 - RegionSize: 28672 - State: 4096 - Protect: 4 - Type: 0x1000000 -</code></pre><p>Interesting! According to the <a href=https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-memory_basic_information><code>MEMORY_BASIC_INFORMATION</code> page</a>, the type for the first region is <code>MEM_PRIVATE</code>, and the type for the second region is <code>MEM_IMAGE</code> which:<blockquote><p>Indicates that the memory pages within the region are mapped into the view of an image section.</blockquote><p>The protection also changes from <code>PAGE_EXECUTE_WRITECOPY</code> to simply <code>PAGE_READWRITE</code>, but I don't think it's relevant. Neither the type seems to be much more relevant. In <a href=/blog/woce-2>part 2</a> we also mentioned the concept of "base address", but decided against using it, because starting to look for regions at address zero seemed to work fine. However, it would make sense that fixed "addresses" start at some known "base". Let's try getting the <a href=https://stackoverflow.com/a/26573045/4759433>base address for all loaded modules</a>. Currently, we only get the address for the base module, in order to retrieve its name, but now we need them all:<pre><code class=language-rust data-lang=rust>pub fn enum_modules(&self) -> io::Result<Vec<winapi::shared::minwindef::HMODULE>> { - let mut size = 0; - if unsafe { - winapi::um::psapi::EnumProcessModules( - self.handle.as_ptr(), - ptr::null_mut(), - 0, - &mut size, - ) - } == FALSE - { - return Err(io::Error::last_os_error()); - } - - let mut modules = Vec::with_capacity(size as usize / mem::size_of::<HMODULE>()); - if unsafe { - winapi::um::psapi::EnumProcessModules( - self.handle.as_ptr(), - modules.as_mut_ptr(), - (modules.capacity() * mem::size_of::<HMODULE>()) as u32, - &mut size, - ) - } == FALSE - { - return Err(io::Error::last_os_error()); - } - - unsafe { - modules.set_len(size as usize / mem::size_of::<HMODULE>()); - } - - Ok(modules) -} -</code></pre><p>The first call is used to retrieve the correct <code>size</code>, then we allocate just enough, and make the second call. The returned type are pretty much memory addresses, so let's see if we can find regions that contain them:<pre><code class=language-rust data-lang=rust>let mut bases = 0; -let modules = process.enum_modules().unwrap(); -let regions = process.memory_regions(); -regions.iter().for_each(|region| { - if modules.iter().any(|module| { - let base = region.AllocationBase as usize; - let addr = *module as usize; - base <= addr && addr < base + region.RegionSize - }) { - bases += 1; - } -}); - -println!( - "{}/{} regions have a module address within them", - bases, - regions.len() -); -</code></pre><pre><code>41/353 regions have a module address within them -</code></pre><p>Exciting stuff! It appears <code>base == addr</code> also does the trick<sup class=footnote-reference><a href=#5>5</a></sup>, so now we could build a <code>bases: HashSet<usize></code> and simply check if <code>bases.contains(&region.AllocationBase as usize)</code> to determine whether <code>region</code> is a base address or not<sup class=footnote-reference><a href=#6>6</a></sup>. So there we have it! The address holding the pointer value does fall within one of these "base regions". You can also get the name from one of these module addresses, and print it in the same way as Cheat Engine does it (such as <code>Tutorial-x86_64.exe+306AD0</code>).<h2 id=finale>Finale</h2><p>So, there's no "automated" solution to all of this? That's the end? Well, yes, once you have a pointer you can dereference it once and then write to the given address to complete the tutorial step! I can understand how this would feel a bit underwhelming, but in all fairness, we were required to pretty-print assembly to guess what pointer address we could potentially need to look for. There is an <a href=https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-2a-manual.html>stupidly large amount of instructions</a>, and I'm sure a lot of them can access memory, so automating that would be rough. We were lucky that the instructions right before the one that hit the breakpoint were changing the memory address, but you could imagine this value coming from somewhere completely different. It could also be using a myriad of different techniques to apply the offset. I would argue manual intervention is a must here<sup class=footnote-reference><a href=#7>7</a></sup>.<p>We have learnt how to pretty-print instructions, and had a very gentle introduction to figuring out what we may need to look for. The code to retrieve the loaded modules, and their corresponding regions, will come in handy later on. Having access to this information lets us know when to stop looking for additional pointers. As soon as a pointer is found within a memory region corresponding to a base module, we're done! Also, I know the title doesn't really much the contents of this entry (sorry about that), but I'm just following the convention of calling it whatever the Cheat Engine tutorial calls them.<p>The <a href=https://github.com/lonami/memo>code for this post</a> is available over at my GitHub. You can run <code>git checkout step6</code> after cloning the repository to get the right version of the code, although you will have to <code>checkout</code> to individual commits if you want to review, for example, how the instructions were printed out. Only the code necessary to complete the step is included at the <code>step6</code> tag.<p>In the next post, we'll tackle the sixth step of the tutorial: Code Injection. This will be pretty similar to part 5, but instead of writing out a simple NOP instruction, we will have to get a bit more creative.<h3 id=footnotes>Footnotes</h3><div class=footnote-definition id=1><sup class=footnote-definition-label>1</sup><p>This will only be a gentle introduction to pointers. Part 8 of this series will have to rely on more advanced techniques.</div><div class=footnote-definition id=2><sup class=footnote-definition-label>2</sup><p>Kind of. The size of a pointer isn't necessarily the size as <code>usize</code>, although <code>usize</code> is guaranteed to be able of representing every possible address. For our purposes, we can assume a pointer is as big as <code>usize</code>.</div><div class=footnote-definition id=3><sup class=footnote-definition-label>3</sup><p>Game updates are likely to pull more code and shuffle stuff around. This is unfortunately a difficult problem to solve. But storing a pointer which is usable across restarts for as long as the game doesn't update is still a pretty darn big improvement over having to constantly scan for the locations we care about. Although if you're smart enough to look for certain unique patterns, even if the code is changed, finding those patterns will give you the new updated address, so it's not <em>impossible</em>.</div><div class=footnote-definition id=4><sup class=footnote-definition-label>4</sup><p><code>bool::then</code> is a pretty recent addition at the time of writing (1.50.0), so make sure you <code>rustup update</code> if it's erroring out!</div><div class=footnote-definition id=5><sup class=footnote-definition-label>5</sup><p>I wasn't sure if there would be some metadata before the module base address but within the region, so I went with the range check. What <em>is</em> important however is using <code>AllocationBase</code>, not <code>BaseAddress</code>. They're different, and this did bite me.</div><div class=footnote-definition id=6><sup class=footnote-definition-label>6</sup><p>As usual, I have no idea if this is how Cheat Engine is doing it, but it seems reasonable.</div><div class=footnote-definition id=6><sup class=footnote-definition-label>6</sup><p>But nothing's stopping you from implementing some heuristics to get the job done for you. If you run some algorithm in your head to find what the pointer value could be, you can program it in Rust as well, although I don't think it's worth the effort.</div></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> WorldEdit Commands | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>WorldEdit Commands</h1><div class=time><p>2018-07-11</div><p><a href=https://dev.bukkit.org/projects/worldedit>WorldEdit</a> is an extremely powerful tool for modifying entire worlds within <a href=https://minecraft.net>Minecraft</a>, which can be used as either a mod for your single-player worlds or as a plugin for your <a href=https://getbukkit.org/>Bukkit</a> servers.<p>This command guide was written for Minecraft 1.12.1, version <a href=https://dev.bukkit.org/projects/worldedit/files/2460562>6.1.7.3</a>, but should work for newer versions too. All WorldEdit commands can be used with a double slash (<code>//</code>) so they don't conlict with built-in commands. This means you can get a list of all commands with <code>//help</code>. Let's explore different categories!<h2 id=movement>Movement</h2><p>In order to edit a world properly you need to learn how to move in said world properly. There are several straightforward commands that let you move:<ul><li><code>//ascend</code> goes up one floor.<li><code>//descend</code> goes down one floor.<li><code>//thru</code> let's you pass through walls.<li><code>//jumpto</code> to go wherever you are looking.</ul><h2 id=information>Information</h2><p>Knowing your world properly is as important as knowing how to move within it, and will also let you change the information in said world if you need to.<ul><li><code>//biomelist</code> shows all known biomes.<li><code>//biomeinfo</code> shows the current biome.<li><code>//setbiome</code> lets you change the biome.</ul><h2 id=blocks>Blocks</h2><p>You can act over all blocks in a radius around you with quite a few commands. Some won't actually act over the entire range you specify, so 100 is often a good number.<h3 id=filling>Filling</h3><p>You can fill pools with <code>//fill water 100</code> or caves with <code>//fillr water 100</code>, both of which act below your feet.<h3 id=fixing>Fixing</h3><p>If the water or lava is buggy use <code>//fixwater 100</code> or <code>//fixlava 100</code> respectively.<p>Some creeper removed the snow or the grass? Fear not, you can use <code>//snow 10</code> or <code>//grass 10</code>.<h3 id=emptying>Emptying</h3><p>You can empty a pool completely with <code>//drain 100</code>, remove the snow with <code>//thaw 10</code>, and remove fire with <code>//ex 10</code>.<h3 id=removing>Removing</h3><p>You can remove blocks above and below you in some area with the <code>//removeabove N</code> and <code>//removebelow N</code>. You probably want to set a limit though, or you could fall off the world with <code>//removebelow 1 10</code> for radius and depth. You can also remove near blocks with <code>//removenear block 10</code>.<h3 id=shapes>Shapes</h3><p>Making a cylinder (or circle) can be done with through <code>//cyl stone 10</code>, a third argument for the height. The radius can be comma-separated to make a ellipses instead, such as <code>//cyl stone 5,10</code>.<p>Spheres are done with <code>//sphere stone 5</code>. This will build one right at your center, so you can raise it to be on your feet with <code>//sphere stone 5 yes</code>. Similar to cylinders, you can comma separate the radius <code>x,y,z</code>.<p>Pyramids can be done with <code>//pyramic stone 5</code>.<p>All these commands can be prefixed with "h" to make them hollow. For instance, <code>//hsphere stone 10</code>.<h2 id=regions>Regions</h2><h3 id=basics>Basics</h3><p>Operating over an entire region is really important, and the first thing you need to work comfortably with them is a tool to make selections. The default wooden-axe tool can be obtained with <code>//wand</code>, but you must be near the blocks to select. You can use a different tool, like a golden axe, to use as your "far wand" (wand usable over distance). Once you have one in your hand type <code>//farwand</code> to use it as your "far wand". You can select the two corners of your region with left and right click. If you have selected the wrong tool, use <code>//none</code> to clear it.<p>If there are no blocks but you want to use your current position as a corner, use <code>//pos1</code> or 2.<p>If you made a region too small, you can enlarge it with <code>//expand 10 up</code>, or <code>//expand vert</code> for the entire vertical range, etc., or make it smaller with <code>//contract 10 up</code> etc., or <code>//inset</code> it to contract in both directions. You can use short-names for the cardinal directions (NSEW).<p>Finally, if you want to move your selection, you can <code>//shift 1 north</code> it to wherever you need.<h3 id=information-1>Information</h3><p>You can get the <code>//size</code> of the selection or even <code>//count torch</code> in some area. If you want to count all blocks, get their distribution <code>//distr</code>.<h3 id=filling-1>Filling</h3><p>With a region selected, you can <code>//set</code> it to be any block! For instance, you can use <code>//set air</code> to clear it entirely. You can use more than one block evenly by separting them with a comma <code>//set stone,dirt</code>, or with a custom chance <code>//set 20%stone,80%dirt</code>.<p>You can use <code>//replace from to</code> instead if you don't want to override all blocks in your selection.<p>You can make an hollow set with <code>//faces</code>, and if you just want the walls, use <code>//walls</code>.<h3 id=cleaning>Cleaning</h3><p>If someone destroyed your wonderful snow landscape, fear not, you can use <code>//overlay snow</code> over it (although for this you actually have <code>//snow N</code> and its opposite <code>//thaw</code>).<p>If you set some rough area, you can always <code>//smooth</code> it, even more than one time with <code>//smooth 3</code>. You can get your dirt and stone back with <code>//naturalize</code> and put some plants with <code>//flora</code> or <code>//forest</code>, both of which support a density or even the type for the trees. If you already have the dirt use <code>//green</code> instead. If you want some pumpkins, with <code>//pumpkins</code>.<h3 id=moving>Moving</h3><p>You can repeat an entire selection many times by stacking them with <code>//stack N DIR</code>. This is extremely useful to make things like corridors or elevators. For instance, you can make a small section of the corridor, select it entirely, and then repeat it 10 times with <code>//stack 10 north</code>. Or you can make the elevator and then <code>//stack 10 up</code>. If you need to also copy the air use <code>//stackair</code>.<p>Finally, if you don't need to repeat it and simply move it just a bit towards the right direction, you can use <code>//move N</code>. The default direction is "me" (towards where you are facing) but you can set one with <code>//move 1 up</code> for example.<h3 id=selecting>Selecting</h3><p>You can not only select cuboids. You can also select different shapes, or even just points:<ul><li><code>//sel cuboid</code> is the default.<li><code>//sel extend</code> expands the default.<li><code>//sel poly</code> first point with left click and right click to add new points.<li><code>//sel ellipsoid</code> first point to select the center and right click to select the different radius.<li><code>//sel sphere</code> first point to select the center and one more right click for the radius.<li><code>//sel cyl</code> for cylinders, first click being the center.<li><code>//sel convex</code> for convex shapes. This one is extremely useful for <code>//curve</code>.</ul><h2 id=brushes>Brushes</h2><p>Brushes are a way to paint in 3D without first bothering about making a selection, and there are spherical and cylinder brushes with e.g. <code>//brush sphere stone 2</code>, or the shorter form <code>//br s stone</code>. For cylinder, one must use <code>cyl</code> instead <code>sphere</code>.<p>There also exists a brush to smooth the terrain which can be enabled on the current item with <code>//br smooth</code>, which can be used with right-click like any other brush.<h2 id=clipboard>Clipboard</h2><p>Finally, you can copy and cut things around like you would do with normal text with <code>//copy</code> and <code>//cut</code>. The copy is issued from wherever you issue the command, so when you use <code>//paste</code>, remember that if you were 4 blocks apart when copying, it will be 4 blocks apart when pasting.<p>The contents of the clipboard can be flipped to wherever you are looking via <code>//flip</code>, and can be rotated via the <code>//rotate 90</code> command (in degrees).<p>To remove the copy use <code>//clearclipboard</code>.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,222 +0,0 @@
-""" -hacky script to convert saved wordpress sites to markdown for use in https://github.com/expectocode/pagong -""" -import bs4 -import os -import sys -import re -from pathlib import Path -import urllib.parse -import dateutil.parser -import shutil - -def header(tag_name): - if m := re.match(r'h([1-6])', tag_name): - return int(m[1]) - -def rewrite_img_src(src): - if '//' in src: - return src - else: - return src.split('/')[-1] - -def handle(tag, pre=False, list_ty=None): - if isinstance(tag, bs4.NavigableString): - tag = str(tag) - if pre: - yield tag - else: - value = re.sub(r'\s+', ' ', tag) - if not value.isspace(): - yield value - return - - if tag.name == 'div': - pass - elif level := header(tag.name): - yield '\n\n' + '#' * level + ' ' - elif tag.name == 'p': - pass - elif tag.name == 'em': - yield '_' - elif tag.name == 'strong': - yield '**' - elif tag.name == 'a': - yield '[' - elif tag.name == 'code': - if not pre: - yield '`' - elif tag.name == 'ul': - list_ty = list_ty or [] - list_ty.append(None) - elif tag.name == 'li': - if not list_ty[-1]: - yield '\n* ' - else: - yield f'\n{list_ty[-1]}. ' - list_ty[-1] += 1 - elif tag.name == 'pre': - pre = True - yield '\n```\n' - elif tag.name == 'figure': - yield '\n' - elif tag.name == 'img': - yield f'![{tag["alt"]}]({rewrite_img_src(tag["src"])})' - elif tag.name == 'hr': - yield '\n\n----------\n\n' - elif tag.name == 'ol': - list_ty = list_ty or [] - list_ty.append(1) - elif tag.name == 'br': - yield '\n' - elif tag.name == 'table': - # bruh i ain't gonna parse tables - yield tag.prettify() - return - elif tag.name == 'blockquote': - yield '\n> ' - elif tag.name == 's': - yield '~~' - elif tag.name == 'figcaption': - yield '\n_' - elif tag.name == 'video': - yield f'<video controls="controls" src="{rewrite_img_src(tag["src"])}"></video>' - elif tag.name == 'cite': - yield f'-- ' - elif tag.name in ('sub', 'sup'): - yield f'<{tag.name}>' - else: - print('wtf is', tag.name) - quit() - - for child in tag.children: - yield from handle(child, pre=pre, list_ty=list_ty) - - if tag.name == 'div': - pass - elif header(tag.name): - yield '\n\n' - elif tag.name == 'p': - yield '\n\n' - elif tag.name == 'em': - yield '_' - elif tag.name == 'strong': - yield '**' - elif tag.name == 'a': - yield f']({tag["href"]})' - elif tag.name == 'code': - if not pre: - yield '`' - elif tag.name == 'ul': - list_ty.pop() - yield '\n' - elif tag.name == 'li': - pass - elif tag.name == 'pre': - yield '\n```\n\n' - elif tag.name == 'figure': - yield '\n\n' - elif tag.name == 'img': - pass - elif tag.name == 'hr': - pass - elif tag.name == 'ol': - list_ty.pop() - yield '\n' - elif tag.name == 'br': - pass - elif tag.name == 'table': - pass - elif tag.name == 'blockquote': - yield '\n' - elif tag.name == 's': - yield '~~' - elif tag.name == 'figcaption': - yield '_\n' - elif tag.name == 'video': - pass - elif tag.name == 'cite': - pass - elif tag.name in ('sub', 'sup'): - yield f'</{tag.name}>' - - -def iter_local_img(file: Path, tag): - if isinstance(tag, bs4.NavigableString): - return - - if tag.name == 'img': - src = tag["src"] - if '//' not in src: - f = file.parent / urllib.parse.unquote(src) - if f.is_file(): - yield f, rewrite_img_src(src) - - for child in tag.children: - yield from iter_local_img(file, child) - - -def main(): - try: - indir = Path(sys.argv[1]) - outroot = Path(sys.argv[2]) - except IndexError: - print('usage:', sys.argv[0], '<IN DIR>', '<OUT DIR>') - exit(1) - - outroot.mkdir(exist_ok=True) - - for file in indir.iterdir(): - if not file.is_file() or not file.name.endswith('.html'): - continue - - with file.open(encoding='utf-8') as fd: - soup = bs4.BeautifulSoup(fd.read(), 'html.parser') - - name = soup.find('link', rel='canonical') - if name: - name = name['href'] - else: - name = soup.find(id='cancel-comment-reply-link')['href'].split('#')[0] - name = name.rstrip('/').split('/')[-1] - - outdir = outroot / name - title = soup.find(class_='entry-title').text - _author = soup.find(class_='entry-author').text # i'd rather not write this - published = dateutil.parser.isoparse(soup.find(class_='published')['datetime']).replace(' ', 'T') # ISO 8601 - updated = dateutil.parser.isoparse(soup.find(class_='updated')['datetime']).replace(' ', 'T') - content = soup.find(class_='entry-content') - - outdir.mkdir(exist_ok=True) - with open(outdir / 'post.md', 'w', encoding='utf-8') as fd: - fd.write(f'''```meta -title: {title} -published: {published} -updated: {updated} -``` -''') - - # hacky way to avoid the excessive amount of newlines except in pre blocks - lines = ''.join(handle(content)).split('\n') - pre = False - empty = False - for line in lines: - if line.startswith('```'): - fd.write(line) - fd.write('\n') - pre = not pre - continue - - if not line or line.isspace(): - empty = True - else: - if empty: - fd.write('\n') - empty = False - fd.write(line) - fd.write('\n') - - for src, dst in iter_local_img(file, content): - shutil.copy(src, outdir / dst) - -main()
@@ -1,206 +0,0 @@
- -<!doctype html> -<html lang="en"> -<head> - <meta charset="utf-8"> - <title>Documentation for the dmssproject Ecore meta-model</title> - <meta name="description" content="dmssproject Documentation"> - <link rel="stylesheet" href="css/styles.css?v=1.0"> - <style> - .details { - font-family: calibri; - color: black; - } - - .invariant { - font-style: italic; - color: blue; - } - - .cls { - background-color: LightGoldenRodYellow; - } - - .abstract { - background-color: LightGrey; - } - </style> -</head> -<body> - <span class="details"> - <p>Authors : <i>(censored for privacy reasons)</i></p> - <p>Last updated : 06/05/2019</p> - <p>Name: dmssproject</p> - <p>NS Prefix: dmssproject</p> - <p>NS URI: http://www.example.org/dmssproject</p> - </span> - -<span class="cls">EClass: Workflow</span> -<p>Description: In this EClassifier we set the authors of the project and where the meta-model was last updated.</p> -<ul> - <br/> - <li>(0...*) <b>actors</b> : Actor (Composition) </li> - <br/> - <li>invariant <b>SingleBeginTag</b> : <span class="invariant"> - self.actors.tasks->selectByType(StartTask)->size() = 1</span></li> - <li>invariant <b>SingleEngTag</b> : <span class="invariant"> - self.actors.tasks->selectByType(EndTask)->size() = 1</span></li> - -</ul> -<span class="cls">EClass: Actor</span> -<p>Description: In this EClassifier we have the Actor class and its attributes</p> -<ul> - <li>(0...1) <b>name</b> : EString </li> - <br/> - <li>(1...*) <b>tasks</b> : Task (Composition) </li> - <li>(0...*) <b>information</b> : Information (Composition) </li> - <br/> - -</ul> -<span class="abstract">EClass: Task</span> (abstract) <br/> -<p>Description: In this EClassifier we have the Task class and its attributes</p> -<ul> - <br/> - <li>(0...1) <b>succesor#<i>predecessor</i></b> : Task </li> - <li>(0...1) <b>predecessor#<i>succesor</i></b> : Task </li> - <br/> - <li>invariant <b>NoDestinationStart</b> : <span class="invariant"> - self.succesor->selectByType(StartTask)->isEmpty()</span></li> - <li>invariant <b>NoSourceEnd</b> : <span class="invariant"> - self.predecessor->selectByType(EndTask)->isEmpty()</span></li> - <li>invariant <b>NoSelfLink</b> : <span class="invariant"> - not self.predecessor->includes(self) and not self.succesor->includes(self)</span></li> - -</ul> -<span class="abstract">EClass: IntermediateTask</span> (abstract) <br/> -<p>Description: In this EClassifier we have the IntermediateTask class and its attributes</p> -<ul> - <li>(0...1) <b>name</b> : EString </li> - <br/> - <li>(0...1) <b>succesor#<i>predecessor</i></b> : Task </li> - <li>(0...1) <b>predecessor#<i>succesor</i></b> : Task </li> - <br/> - -</ul> -<span class="cls">EClass: StartTask</span> -<p>Description: In this EClassifier we have the Start Task class and its attributes</p> -<ul> - <br/> - <li>(0...1) <b>succesor#<i>predecessor</i></b> : Task </li> - <li>(0...1) <b>predecessor#<i>succesor</i></b> : Task </li> - <br/> - -</ul> -<span class="cls">EClass: EndTask</span> -<p>Description: In this EClassifier we have the End Task class and its attributes</p> -<ul> - <br/> - <li>(0...1) <b>succesor#<i>predecessor</i></b> : Task </li> - <li>(0...1) <b>predecessor#<i>succesor</i></b> : Task </li> - <br/> - -</ul> -<span class="cls">EClass: UserTask</span> -<p>Description: In this EClassifier we have the User Task class and its attributes</p> -<ul> - <li>(0...1) <b>name</b> : EString </li> - <br/> - <li>(0...1) <b>succesor#<i>predecessor</i></b> : Task </li> - <li>(0...1) <b>predecessor#<i>succesor</i></b> : Task </li> - <li>(0...*) <b>attachedData</b> : Information </li> - <br/> - -</ul> -<span class="cls">EClass: ServiceTask</span> -<p>Description: In this EClassifier we have the Service Task class and its attributes</p> -<ul> - <li>(0...1) <b>name</b> : EString </li> - <br/> - <li>(0...1) <b>succesor#<i>predecessor</i></b> : Task </li> - <li>(0...1) <b>predecessor#<i>succesor</i></b> : Task </li> - <li>(0...*) <b>producedData</b> : Information </li> - <br/> - -</ul> -<span class="cls">EClass: SendMessageTask</span> -<p>Description: In this EClassifier we have the Send Message Task class and its attributes</p> -<ul> - <li>(0...1) <b>name</b> : EString </li> - <br/> - <li>(0...1) <b>succesor#<i>predecessor</i></b> : Task </li> - <li>(0...1) <b>predecessor#<i>succesor</i></b> : Task </li> - <li>(0...*) <b>requiredData</b> : Information </li> - <br/> - <li>invariant <b>SendSuccededByRecv</b> : <span class="invariant"> - self.succesor.oclIsKindOf(RecvMessageTask)</span></li> - <li>invariant <b>SendRecvInDifferentActors</b> : <span class="invariant"> - self.oclContainer() <> self.succesor.oclContainer()</span></li> - -</ul> -<span class="cls">EClass: RecvMessageTask</span> -<p>Description: In this EClassifier we have the Receive Message Task class and its attributes</p> -<ul> - <li>(0...1) <b>name</b> : EString </li> - <br/> - <li>(0...1) <b>succesor#<i>predecessor</i></b> : Task </li> - <li>(0...1) <b>predecessor#<i>succesor</i></b> : Task </li> - <br/> - -</ul> -<span class="abstract">EClass: Information</span> (abstract) <br/> -<p>Description: In this EClassifier we have the Information class and its attributes</p> -<ul> - <li>(0...1) <b>name</b> : EString </li> - <li>(0...1) <b>optional</b> : EBoolean </li> - <br/> - <br/> - -</ul> -<span class="cls">EClass: Form</span> -<p>Description: In this EClassifier we have the Form class and its attributes</p> -<ul> - <li>(0...1) <b>name</b> : EString </li> - <li>(0...1) <b>optional</b> : EBoolean </li> - <br/> - <li>(0...*) <b>subfields</b> : Information </li> - <br/> - -</ul> -<span class="cls">EClass: Text</span> -<p>Description: In this EClassifier we have the Text class and its attributes</p> -<ul> - <li>(0...1) <b>name</b> : EString </li> - <li>(0...1) <b>optional</b> : EBoolean </li> - <li>(0...1) <b>value</b> : EString </li> - <br/> - <br/> - -</ul> -<span class="cls">EClass: Number</span> -<p>Description: In this EClassifier we have the Number class and its attributes</p> -<ul> - <li>(0...1) <b>name</b> : EString </li> - <li>(0...1) <b>optional</b> : EBoolean </li> - <li>(0...1) <b>value</b> : EInt </li> - <br/> - <br/> - -</ul> -<span class="cls">EClass: Resource</span> -<p>Description: In this EClassifier we have the Resource class and its attributes</p> -<ul> - <li>(0...1) <b>name</b> : EString </li> - <li>(0...1) <b>optional</b> : EBoolean </li> - <li>(0...1) <b>uri</b> : EString </li> - <li>(0...1) <b>type</b> : ResourceType </li> - <br/> - <br/> - -</ul> -<span class="cls">EENum: ResourceType</span> = { 0 : DOCUMENT, 1 : TEXT, 2 : PHOTO, 3 : VIDEO } -<p>Description: EENum -</p> - - -</body> -</html>
@@ -1,85 +0,0 @@
-<!DOCTYPE> -<html> -<head> - <title>Donate to Lonami</title> - <link href="https://fonts.googleapis.com/css?family=Montserrat|Ubuntu" - rel="stylesheet"> - - <style> - body { - background: linear-gradient(0deg, #293b5f, #f59e39, #7fc7e8); - background-size: 600% 600%; - animation: BgGradient 120s ease infinite; - } - - /* Thanks https://www.gradient-animator.com */ - @keyframes BgGradient { - 0%{background-position:45% 0%} - 50%{background-position:45% 90%} - 100%{background-position:45% 0%} - } - - #main { - max-width: 600px; - margin: 100px auto; - background-color: rgba(255, 255, 255, 0.2); - border-radius: 6px; - padding: 20px; - box-shadow: 6px 6px 12px rgba(0, 0, 0, 0.1); - } - - input { - width: 100%; - padding: 2px; - } - - h1, h2, h3 { - font-family: 'Montserrat', sans-serif; - } - - p, input, button { - font-family: 'Ubuntu', sans-serif; - } - - a { - text-decoration: none; - color: #00f; - transition: color 300ms; - } - - a:hover { - color: #07f; - } - </style> -</head> -<body> -<div id="main"> - <h1>Donate to Lonami</h1> - <p>First, thank you for liking my work! If you have no money to spend, - feel free to join the - <a href="https://t.me/LonamiWebs" target="_blank">LonamiWebs</a> Telegram - group. I appreciate when someone tells me they like what I do!</p> - - <h3><a href="https://www.paypal.me/lonamiwebs" target="_blank">Link to PayPal.me</a></h3> -</div> -<script> -bitcoinAddr = document.getElementById('bitcoinAddr'); -copyBtcAddr = document.getElementById('copyBtcAddr'); - -function copyBitcoinAddr() { - bitcoinAddr.select(); - - try { - var ok = document.execCommand('copy'); - copyBtcAddr.innerHTML = ok ? 'Copied!' : 'Copying failed :('; - } catch (err) { - copyBtcAddr.innerHTML = 'Not supported :('; - } - - setTimeout(function() { - copyBtcAddr.innerHTML = 'Copy'; - }, 2000); -} -</script> -</body> -</html>
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Apuntes de bachillerato de Filosofía | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog>blog</a><li><a href=/golb class=selected>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Apuntes de bachillerato de Filosofía</h1><div class=time><p>2016-06-21</div><p>Hay asignaturas que merecen la pena, y una de ellas es la filosofía. De verdad. Aprendes un montón de cosas y abres tu mente, comparas muchos puntos de vista y te das cuenta de grandes cosas. Por eso, quiero compartir mis apuntes con todo aquel interesado.<p>Personalmente, mi autor favorito es Friedrich Nietzsche (se pronuncia <em>/niche/</em>). Está en el tercer trimestre, el más interesante, aunque si prefieres algo de contexto, te recomiendo leerlo todo. Puedes leerlo como si fuera un libro cualquiera:<ul><li>Descargar en .pdf <ul><li><a href=https://lonami.dev/golb/filosofia/filo_trimestre1.pdf>Filosofía - Primer trimestre</a><li><a href=https://lonami.dev/golb/filosofia/filo_trimestre2.pdf>Filosofía - Segundo trimestre</a><li><a href=https://lonami.dev/golb/filosofia/filo_trimestre3.pdf>Filosofía - Tercer trimestre</a></ul><li>Descargar en .odt (lo puedes editar) <ul><li><a href=https://lonami.dev/golb/filosofia/filo_trimestre1.odt>Filosofía - Primer trimestre</a><li><a href=https://lonami.dev/golb/filosofia/filo_trimestre2.odt>Filosofía - Segundo trimestre</a><li><a href=https://lonami.dev/golb/filosofia/filo_trimestre3.odt>Filosofía - Tercer trimestre</a></ul></ul><p>Nota: Hay algunas palabras un tanto soez. ¡Añaden emoción y no son para tanto, a lo sumo dos o tres! :)</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Lonami's Golb </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog>blog</a><li><a href=/golb class=selected>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>My Golb</h1><p>Welcome to my golb!<p>It's like my blog, but with things that are a bit more… personal? Random? Spanish? Yeah!<hr><ul><li><a href=https://lonami.dev/golb/making-a-difference/>Making a Difference</a><li><a href=https://lonami.dev/golb/sentences/>Sentences</a><li><a href=https://lonami.dev/golb/filosofia/>Apuntes de bachillerato de Filosofía</a><li><a href=https://lonami.dev/golb/reflexion-ia/>Reflexión sobre la Inteligencia artificial</a><li><a href=https://lonami.dev/golb/inteligencia-artificial/>Inteligencia artificial</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,893 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="480" - height="230" - viewBox="0 0 480 230" - id="svg2" - version="1.1" - inkscape:version="0.91 r13725" - sodipodi:docname="bayesian_network.svg"> - <defs - id="defs4"> - <marker - inkscape:isstock="true" - style="overflow:visible" - id="marker5391" - refX="0" - refY="0" - orient="auto" - inkscape:stockid="Arrow1Lend"> - <path - transform="matrix(-0.8,0,0,-0.8,-10,0)" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" - d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z" - id="path5393" - inkscape:connector-curvature="0" /> - </marker> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0" - refX="0" - id="marker5075" - style="overflow:visible" - inkscape:isstock="true" - inkscape:collect="always"> - <path - id="path5077" - d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" - transform="matrix(-0.8,0,0,-0.8,-10,0)" - inkscape:connector-curvature="0" /> - </marker> - <marker - inkscape:stockid="Arrow1Lend" - orient="auto" - refY="0" - refX="0" - id="Arrow1Lend" - style="overflow:visible" - inkscape:isstock="true" - inkscape:collect="always"> - <path - id="path4680" - d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z" - style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" - transform="matrix(-0.8,0,0,-0.8,-10,0)" - inkscape:connector-curvature="0" /> - </marker> - </defs> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="0.7" - inkscape:cx="111.75179" - inkscape:cy="243.66335" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - inkscape:window-width="1366" - inkscape:window-height="745" - inkscape:window-x="-8" - inkscape:window-y="-8" - inkscape:window-maximized="1" - units="px" /> - <metadata - id="metadata7"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-822.36216)"> - <g - id="g5777"> - <rect - style="opacity:1;fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:1.38805318;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect4331" - width="160.26018" - height="86.903847" - x="319.04575" - y="823.05615" - ry="18.212736" /> - <path - sodipodi:nodetypes="ssscs" - inkscape:connector-curvature="0" - id="path4470" - d="m 449.26648,872.37344 c -0.81362,1.4053 -2.61563,1.88679 -4.02487,1.07544 -1.40926,-0.81135 -1.89209,-2.6083 -1.07846,-4.0136 0.81362,-1.4053 6.15631,-4.75688 6.15631,-4.75688 0,0 -0.23935,6.28974 -1.05298,7.69504 z" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 447.79543,882.40525 c -0.55786,0.96354 -1.7934,1.29367 -2.75965,0.73737 -0.96624,-0.5563 -1.2973,-1.78837 -0.73944,-2.75191 0.55786,-0.96354 4.22106,-3.26154 4.22106,-3.26154 0,0 -0.16411,4.31254 -0.72197,5.27608 z" - id="path4472" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ssscs" /> - <path - sodipodi:nodetypes="ssscs" - inkscape:connector-curvature="0" - id="path4474" - d="m 454.61642,879.47845 c -0.61483,1.06195 -1.97655,1.4258 -3.04149,0.81269 -1.06494,-0.61312 -1.4298,-1.97104 -0.81497,-3.03299 0.61483,-1.06195 4.65218,-3.59466 4.65218,-3.59466 0,0 -0.18086,4.75301 -0.79572,5.81496 z" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 455.97486,893.5466 c -0.7587,1.31045 -2.43906,1.75944 -3.75318,1.00285 -1.31413,-0.75658 -1.76437,-2.43224 -1.00567,-3.74268 0.7587,-1.31045 5.74076,-4.4358 5.74076,-4.4358 0,0 -0.22318,5.86519 -0.98191,7.17563 z" - id="path4476" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ssscs" /> - <path - sodipodi:nodetypes="ssscs" - inkscape:connector-curvature="0" - id="path4478" - d="m 473.94035,882.58467 c -0.50293,0.86868 -1.61683,1.16631 -2.48795,0.66478 -0.87112,-0.50154 -1.16958,-1.61232 -0.66665,-2.481 0.50294,-0.86868 3.8055,-2.94045 3.8055,-2.94045 0,0 -0.14794,3.88799 -0.6509,4.75667 z" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 473.94035,882.58467 c -0.50293,0.86868 -1.61683,1.16631 -2.48795,0.66478 -0.87112,-0.50154 -1.16958,-1.61232 -0.66665,-2.481 0.50294,-0.86868 3.8055,-2.94045 3.8055,-2.94045 0,0 -0.14794,3.88799 -0.6509,4.75667 z" - id="path4480" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ssscs" /> - <path - sodipodi:nodetypes="ssscs" - inkscape:connector-curvature="0" - id="path4405" - d="m 378.22829,839.53921 c -0.81362,1.40531 -2.61563,1.8868 -4.02487,1.07544 -1.40926,-0.81135 -1.89209,-2.6083 -1.07847,-4.0136 0.81363,-1.4053 6.15632,-4.75688 6.15632,-4.75688 0,0 -0.23935,6.28974 -1.05298,7.69504 z" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 376.75723,849.57102 c -0.55785,0.96354 -1.79339,1.29367 -2.75964,0.73737 -0.96625,-0.5563 -1.2973,-1.78837 -0.73944,-2.75191 0.55786,-0.96354 4.22105,-3.26154 4.22105,-3.26154 0,0 -0.1641,4.31254 -0.72197,5.27608 z" - id="path4408" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ssscs" /> - <path - sodipodi:nodetypes="ssscs" - inkscape:connector-curvature="0" - id="path4410" - d="m 383.57823,846.64422 c -0.61483,1.06195 -1.97656,1.4258 -3.04149,0.81269 -1.06494,-0.61312 -1.42981,-1.97104 -0.81497,-3.03299 0.61483,-1.06195 4.65218,-3.59466 4.65218,-3.59466 0,0 -0.18087,4.75301 -0.79572,5.81496 z" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 384.93667,860.71237 c -0.7587,1.31045 -2.43907,1.75944 -3.75319,1.00285 -1.31413,-0.75658 -1.76437,-2.43224 -1.00567,-3.74268 0.7587,-1.31045 5.74076,-4.43579 5.74076,-4.43579 0,0 -0.22317,5.86518 -0.9819,7.17562 z" - id="path4412" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ssscs" /> - <path - sodipodi:nodetypes="ssscs" - inkscape:connector-curvature="0" - id="path4414" - d="m 402.90216,849.75044 c -0.50294,0.86868 -1.61684,1.16631 -2.48796,0.66478 -0.87111,-0.50154 -1.16957,-1.61231 -0.66664,-2.481 0.50294,-0.86868 3.80549,-2.94044 3.80549,-2.94044 0,0 -0.14793,3.88798 -0.65089,4.75666 z" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 402.90216,849.75044 c -0.50294,0.86868 -1.61684,1.16631 -2.48796,0.66478 -0.87111,-0.50154 -1.16957,-1.61231 -0.66664,-2.481 0.50294,-0.86868 3.80549,-2.94044 3.80549,-2.94044 0,0 -0.14793,3.88798 -0.65089,4.75666 z" - id="path4416" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ssscs" /> - <path - id="path4391" - d="m 383.19525,823.05708 a 27.030959,26.955273 0 0 0 -1.16873,7.76705 27.030959,26.955273 0 0 0 27.03007,26.95438 27.030959,26.955273 0 0 0 4.45589,-0.37525 27.030959,26.955273 0 0 0 25.92306,19.37197 27.030959,26.955273 0 0 0 20.04557,-8.91663 27.030959,26.955273 0 0 0 19.08392,7.89016 27.030959,26.955273 0 0 0 0.74065,-0.0357 l 0,-34.44348 c 0,-10.08985 -8.14542,-18.21247 -18.26361,-18.21247 l -77.84682,0 z" - style="opacity:1;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - inkscape:connector-curvature="0" /> - <path - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 392.60914,864.40831 c -0.81363,1.4053 -2.61563,1.88679 -4.02487,1.07544 -1.40926,-0.81135 -1.89209,-2.6083 -1.07847,-4.0136 0.81363,-1.40531 6.15632,-4.75688 6.15632,-4.75688 0,0 -0.23936,6.28974 -1.05298,7.69504 z" - id="path4428" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ssscs" /> - <path - sodipodi:nodetypes="ssscs" - inkscape:connector-curvature="0" - id="path4430" - d="m 391.13808,874.44012 c -0.55786,0.96354 -1.79339,1.29367 -2.75964,0.73737 -0.96625,-0.5563 -1.2973,-1.78837 -0.73944,-2.75192 0.55785,-0.96354 4.22105,-3.26154 4.22105,-3.26154 0,0 -0.1641,4.31255 -0.72197,5.27609 z" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 397.95908,871.51332 c -0.61483,1.06195 -1.97656,1.4258 -3.0415,0.81268 -1.06494,-0.61311 -1.4298,-1.97103 -0.81497,-3.03298 0.61483,-1.06196 4.65218,-3.59466 4.65218,-3.59466 0,0 -0.18086,4.75301 -0.79571,5.81496 z" - id="path4432" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ssscs" /> - <path - sodipodi:nodetypes="ssscs" - inkscape:connector-curvature="0" - id="path4434" - d="m 399.31751,885.58147 c -0.7587,1.31044 -2.43906,1.75943 -3.75318,1.00285 -1.31413,-0.75659 -1.76437,-2.43224 -1.00567,-3.74269 0.7587,-1.31044 5.74076,-4.43579 5.74076,-4.43579 0,0 -0.22318,5.86519 -0.98191,7.17563 z" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 417.283,874.61953 c -0.50293,0.86869 -1.61683,1.16632 -2.48795,0.66478 -0.87112,-0.50153 -1.16958,-1.61231 -0.66665,-2.48099 0.50295,-0.86868 3.8055,-2.94045 3.8055,-2.94045 0,0 -0.14794,3.88798 -0.6509,4.75666 z" - id="path4436" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ssscs" /> - <path - sodipodi:nodetypes="ssscs" - inkscape:connector-curvature="0" - id="path4438" - d="m 417.283,874.61953 c -0.50293,0.86869 -1.61683,1.16632 -2.48795,0.66478 -0.87112,-0.50153 -1.16958,-1.61231 -0.66665,-2.48099 0.50295,-0.86868 3.8055,-2.94045 3.8055,-2.94045 0,0 -0.14794,3.88798 -0.6509,4.75666 z" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - sodipodi:nodetypes="ssscs" - inkscape:connector-curvature="0" - id="path4442" - d="m 410.37705,882.71132 c -0.81363,1.4053 -2.61563,1.88679 -4.02487,1.07544 -1.40926,-0.81135 -1.89209,-2.60831 -1.07847,-4.01361 0.81363,-1.4053 6.15632,-4.75688 6.15632,-4.75688 0,0 -0.23936,6.28974 -1.05298,7.69505 z" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 408.90599,892.74312 c -0.55786,0.96354 -1.79339,1.29368 -2.75964,0.73738 -0.96625,-0.5563 -1.2973,-1.78838 -0.73944,-2.75192 0.55785,-0.96354 4.22105,-3.26154 4.22105,-3.26154 0,0 -0.1641,4.31254 -0.72197,5.27608 z" - id="path4444" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ssscs" /> - <path - sodipodi:nodetypes="ssscs" - inkscape:connector-curvature="0" - id="path4446" - d="m 415.72699,889.81632 c -0.61483,1.06196 -1.97656,1.42581 -3.0415,0.81269 -1.06494,-0.61312 -1.4298,-1.97103 -0.81497,-3.03299 0.61483,-1.06195 4.65218,-3.59466 4.65218,-3.59466 0,0 -0.18086,4.75301 -0.79571,5.81496 z" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 417.08542,903.88448 c -0.7587,1.31044 -2.43906,1.75943 -3.75318,1.00285 -1.31413,-0.75659 -1.76437,-2.43225 -1.00567,-3.74269 0.7587,-1.31045 5.74076,-4.43579 5.74076,-4.43579 0,0 -0.22318,5.86518 -0.98191,7.17563 z" - id="path4448" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ssscs" /> - <path - sodipodi:nodetypes="ssscs" - inkscape:connector-curvature="0" - id="path4450" - d="m 435.05091,892.92254 c -0.50293,0.86868 -1.61683,1.16631 -2.48795,0.66478 -0.87112,-0.50153 -1.16958,-1.61231 -0.66665,-2.48099 0.50295,-0.86869 3.8055,-2.94045 3.8055,-2.94045 0,0 -0.14794,3.88798 -0.6509,4.75666 z" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 435.05091,892.92254 c -0.50293,0.86868 -1.61683,1.16631 -2.48795,0.66478 -0.87112,-0.50153 -1.16958,-1.61231 -0.66665,-2.48099 0.50295,-0.86869 3.8055,-2.94045 3.8055,-2.94045 0,0 -0.14794,3.88798 -0.6509,4.75666 z" - id="path4452" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ssscs" /> - <path - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 423.92847,879.86919 c -0.81363,1.4053 -2.61563,1.88679 -4.02487,1.07544 -1.40926,-0.81135 -1.89209,-2.60831 -1.07847,-4.01361 0.81363,-1.4053 6.15632,-4.75688 6.15632,-4.75688 0,0 -0.23936,6.28974 -1.05298,7.69505 z" - id="path4456" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ssscs" /> - <path - sodipodi:nodetypes="ssscs" - inkscape:connector-curvature="0" - id="path4458" - d="m 422.45741,889.90099 c -0.55786,0.96354 -1.79339,1.29368 -2.75964,0.73738 -0.96625,-0.5563 -1.2973,-1.78838 -0.73944,-2.75192 0.55785,-0.96354 4.22105,-3.26154 4.22105,-3.26154 0,0 -0.1641,4.31254 -0.72197,5.27608 z" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 429.27841,886.97419 c -0.61483,1.06196 -1.97656,1.42581 -3.0415,0.81269 -1.06493,-0.61312 -1.4298,-1.97103 -0.81497,-3.03299 0.61483,-1.06195 4.65218,-3.59466 4.65218,-3.59466 0,0 -0.18086,4.75301 -0.79571,5.81496 z" - id="path4460" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ssscs" /> - <path - sodipodi:nodetypes="ssscs" - inkscape:connector-curvature="0" - id="path4462" - d="m 430.63684,901.04235 c -0.7587,1.31044 -2.43906,1.75943 -3.75318,1.00285 -1.31413,-0.75659 -1.76437,-2.43225 -1.00567,-3.74269 0.7587,-1.31044 5.74076,-4.43579 5.74076,-4.43579 0,0 -0.22318,5.86518 -0.98191,7.17563 z" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 448.60233,890.08041 c -0.50293,0.86868 -1.61683,1.16632 -2.48795,0.66478 -0.87112,-0.50153 -1.16958,-1.61231 -0.66664,-2.48099 0.50294,-0.86869 3.80549,-2.94045 3.80549,-2.94045 0,0 -0.14793,3.88798 -0.6509,4.75666 z" - id="path4464" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ssscs" /> - <path - sodipodi:nodetypes="ssscs" - inkscape:connector-curvature="0" - id="path4466" - d="m 448.60233,890.08041 c -0.50293,0.86868 -1.61683,1.16632 -2.48795,0.66478 -0.87112,-0.50153 -1.16958,-1.61231 -0.66664,-2.48099 0.50294,-0.86869 3.80549,-2.94045 3.80549,-2.94045 0,0 -0.14793,3.88798 -0.6509,4.75666 z" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#00ccff;fill-opacity:1;stroke:#000000;stroke-width:1.9121151;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect4333" - width="305.96002" - height="86.380524" - x="113.66848" - y="965.02557" - ry="18.10306" /> - <path - inkscape:connector-curvature="0" - id="path4370" - d="m 118.77951,1001.0521 c 0,0 -3.178,5.2584 -5.11094,10.0106 l 0,22.2409 c 0,10.0291 8.09684,18.1033 18.1541,18.1033 l 269.65155,0 c 10.05726,0 18.15411,-8.0742 18.15411,-18.1033 l 0,-3.1668 c -0.43795,-1.6775 -0.89918,-3.7771 -0.9756,-5.589 0.0131,-0.2181 0.0183,-0.453 0.0418,-0.6472 0.12229,-1.0125 0.52123,-2.3698 0.93379,-3.6036 l 0,-2.8888 c -1.52518,2.1843 -3.04758,4.5989 -3.76502,6.4924 -0.39102,1.0319 -0.71148,2.1643 -0.98555,3.2978 l -0.81632,-4.5824 -1.20655,8.9027 c -0.57242,-3.0292 -1.20135,-7.4171 -0.85415,-10.9557 0.10051,-1.0241 0.36666,-2.2535 0.69088,-3.5301 1.50735,-3.2279 3.42853,-6.2263 3.42853,-6.2263 0,0 -1.20555,1.4921 -2.63212,3.397 1.27942,-4.1111 2.88896,-8.0172 2.88896,-8.0172 0,0 -3.51145,5.8176 -5.39565,10.7472 -1.22277,1.7299 -2.49455,3.8123 -2.84118,5.4262 -0.0633,0.2949 -0.11053,0.6117 -0.14336,0.9431 -1.6219,4.5766 -2.09853,10.7532 -2.09853,10.7532 0,0 -0.30446,-1.1813 -0.65704,-2.9663 -0.0312,-0.9478 -0.0192,-1.8499 0.0677,-2.5691 0.27915,-2.3111 2.05871,-6.6751 2.05871,-6.6751 0,0 -1.96311,2.0987 -2.94869,3.816 -0.21812,-2.0648 -0.32851,-4.2196 -0.14137,-6.127 0.48903,-4.9842 4.37626,-14.3746 4.37626,-14.3746 0,0 -5.48674,9.0358 -6.43497,14.1184 -0.68266,3.6593 -0.77998,8.4116 -0.58536,13.3323 l -0.95966,-5.3746 -1.2862,9.4984 c 0,0 -2.22415,-6.6463 -1.80188,-10.0106 0.5151,-4.1039 5.14878,-11.2952 5.14878,-11.2952 0,0 -6.2743,7.5585 -7.982,12.0655 -0.4087,1.0786 -0.744,2.2659 -1.02536,3.4507 -0.81419,-3.2131 -1.81375,-7.8563 -1.86759,-11.3984 1.30668,-4.6754 3.40863,-9.7664 3.40863,-9.7664 0,0 -0.48524,0.8099 -1.16275,2.0112 0.49058,-0.9801 0.90591,-1.7551 0.90591,-1.7551 0,0 -4.68942,5.3862 -5.40761,8.73 -0.18297,0.8519 -0.22749,1.8736 -0.18516,2.9682 -0.25591,0.7688 -0.46367,1.5032 -0.58735,2.1662 -0.50191,2.6902 -0.68006,5.9798 -0.66898,9.4824 l -0.87406,0.272 c 0,0 -0.86519,-5.8344 -0.51567,-8.728 0.27915,-2.3111 2.05871,-6.675 2.05871,-6.675 0,0 -2.66743,2.845 -3.3469,4.6201 -0.31086,0.8121 -0.53229,1.85 -0.71279,2.9484 -0.74346,-3.4279 -1.38439,-7.4115 -1.08908,-10.1357 0.29798,-2.7489 3.09006,-7.7015 3.09006,-7.7015 0,0 -2.70166,3.1101 -4.31654,6.0714 -0.65726,0.8509 -1.35545,1.7709 -2.16224,2.9087 0.17926,-0.4669 0.30064,-0.7644 0.30064,-0.7644 0,0 -2.66742,2.8451 -3.3469,4.6202 -0.21525,0.5623 -0.38738,1.238 -0.53757,1.9556 -0.18649,0.3888 -0.35691,0.7698 -0.49179,1.1258 -0.48483,1.2795 -0.86653,2.7056 -1.17469,4.1059 l -1.14284,-6.417 -0.97561,7.2111 c -0.13564,-1.4199 -0.17501,-2.7552 -0.0537,-3.8736 0.29798,-2.7489 3.08807,-7.7015 3.08807,-7.7015 0,0 -2.08686,2.4164 -3.69136,5.0152 0.0112,-0.5733 0.0347,-1.1375 0.0876,-1.6777 0.22492,-2.2924 1.17056,-5.5004 2.12442,-8.315 0.83423,-1.4659 1.47933,-2.4679 1.47933,-2.4679 0,0 -0.44556,0.5453 -1.10701,1.3858 0.97949,-2.7804 1.87952,-4.9795 1.87952,-4.9795 0,0 -2.45762,4.0742 -4.37029,8.2893 -1.7159,2.3871 -3.56755,5.2198 -4.38223,7.3699 -1.6434,4.3373 -2.16081,10.2275 -2.21799,10.9259 -0.0119,0.069 -0.0244,0.1351 -0.0359,0.2045 -0.0354,-0.1373 -1.66945,-6.5112 -1.70431,-12.0894 0.64553,-1.105 1.12692,-1.8643 1.12692,-1.8643 0,0 -0.45394,0.5539 -1.12095,1.4017 0.007,-0.6557 0.0313,-1.3016 0.0915,-1.9159 0.48902,-4.9842 4.37625,-14.3766 4.37625,-14.3766 0,0 -5.48873,9.0378 -6.43695,14.1205 -0.31067,1.6652 -0.4989,3.5616 -0.59731,5.591 -1.69097,2.3615 -3.49183,5.1314 -4.29264,7.2449 -0.50238,1.3259 -0.89124,2.8084 -1.20456,4.2568 -0.92135,-3.7544 -1.97676,-9.0115 -1.63264,-12.3972 0.40415,-1.4258 0.86886,-2.8813 1.33199,-4.2151 0.84093,-1.9138 1.76206,-3.5618 1.76206,-3.5618 0,0 -0.33054,0.3851 -0.81234,0.9828 0.58954,-1.5632 1.06918,-2.7797 1.06918,-2.7797 0,0 -1.37726,2.2695 -2.87106,5.1721 -1.20757,1.7161 -2.44938,3.7623 -2.7914,5.3548 -0.09,0.419 -0.13926,0.8883 -0.16923,1.3778 -0.26355,0.7863 -0.47703,1.5391 -0.60328,2.2158 -0.57487,3.0814 -0.73461,6.9412 -0.65704,11.0192 l -0.88799,0.276 c 0,0 -0.8652,-5.8344 -0.51567,-8.728 0.27914,-2.3111 2.0607,-6.6751 2.0607,-6.6751 0,0 -2.66743,2.8451 -3.3469,4.6202 -0.23377,0.6107 -0.41381,1.3585 -0.57143,2.1482 -0.56271,-2.9494 -0.96073,-6.0585 -0.71677,-8.3091 0.29798,-2.7489 3.09007,-7.7015 3.09007,-7.7015 0,0 -4.68743,5.3841 -5.40562,8.728 -0.37064,1.7257 -0.1841,4.1419 0.18119,6.4626 l -1.08311,7.9875 -0.39025,0.1211 c -0.45834,-1.4848 -1.46667,-5.0062 -1.58286,-7.7611 0.0131,-0.2182 0.0183,-0.453 0.0418,-0.6473 0.22309,-1.847 1.36805,-4.9055 1.83373,-6.0993 1.48757,-3.1283 3.31504,-5.9682 3.31504,-5.9682 0,0 -6.27229,7.5605 -7.98,12.0675 -0.39102,1.032 -0.71148,2.1643 -0.98555,3.2978 l -0.81632,-4.5824 -1.20655,8.9008 c -0.57231,-3.0292 -1.20127,-7.4159 -0.85415,-10.9537 0.10072,-1.0262 0.36768,-2.2587 0.69287,-3.5381 1.50686,-3.2251 3.42654,-6.2184 3.42654,-6.2184 0,0 -1.20495,1.4933 -2.63212,3.3991 1.27951,-4.1119 2.89096,-8.0192 2.89096,-8.0192 0,0 -4.46061,7.3545 -6.0288,12.5261 -0.15867,0.2517 -0.3074,0.4947 -0.45794,0.7446 -0.41228,-2.4864 -0.66964,-4.9742 -0.46589,-6.8538 0.29798,-2.7489 3.09006,-7.7015 3.09006,-7.7015 0,0 -4.68743,5.3861 -5.40562,8.73 -0.56135,2.6136 0.14706,6.784 0.83423,9.8279 -0.49151,1.4202 -0.87674,2.9618 -1.17072,4.4435 l -1.98106,0.6174 c 0,0 -0.86518,-5.8344 -0.51567,-8.728 0.27916,-2.3111 2.0607,-6.675 2.0607,-6.675 0,0 -0.37851,0.4198 -0.84021,0.9589 1.30876,-4.4219 3.15776,-8.9166 3.15776,-8.9166 0,0 -5.48874,9.0358 -6.43697,14.1185 -0.52346,2.8059 -0.69649,6.2645 -0.66698,9.9371 l -1.9074,-10.7075 -1.2862,9.4984 c 0,0 -2.22414,-6.6462 -1.80187,-10.0106 0.5151,-4.1039 5.14679,-11.2972 5.14679,-11.2972 0,0 -6.27231,7.5606 -7.98001,12.0676 -0.40912,1.0797 -0.74384,2.2686 -1.02537,3.4546 -0.81433,-3.213 -1.81357,-7.8586 -1.86758,-11.4024 1.30667,-4.6754 3.40863,-9.7664 3.40863,-9.7664 0,0 -0.48695,0.8136 -1.16674,2.0192 0.49272,-0.9849 0.9099,-1.763 0.9099,-1.763 0,0 -4.68942,5.3841 -5.40761,8.728 -0.18307,0.8524 -0.22762,1.8748 -0.18516,2.9702 -0.25591,0.7687 -0.46367,1.5011 -0.58735,2.1641 -0.5019,2.6903 -0.68205,5.9819 -0.67098,9.4845 l -0.87207,0.272 c 0,0 -0.86518,-5.8344 -0.51567,-8.728 0.27916,-2.3111 2.05872,-6.6751 2.05872,-6.6751 0,0 -2.66744,2.845 -3.34691,4.6201 -0.23375,0.6107 -0.41182,1.3586 -0.56943,2.1483 -0.56272,-2.9494 -0.96073,-6.0585 -0.71677,-8.3091 0.29798,-2.7489 3.09006,-7.7015 3.09006,-7.7015 0,0 -4.68941,5.3841 -5.4076,8.728 -0.37065,1.7257 -0.18213,4.1418 0.18317,6.4626 l -1.08311,7.9874 -0.38825,0.1212 c -0.45785,-1.4829 -1.46858,-5.0044 -1.58485,-7.7611 0.0131,-0.2185 0.0183,-0.4548 0.0418,-0.6493 0.21906,-1.8135 1.33361,-4.8146 1.81581,-6.0556 1.49105,-3.1446 3.33296,-6.0099 3.33296,-6.0099 0,0 -6.27229,7.5585 -7.97999,12.0655 -0.3912,1.0325 -0.71141,2.1658 -0.98556,3.2998 l -0.81632,-4.5824 -1.20655,8.9027 c -0.55706,-2.9479 -1.16175,-7.1774 -0.87406,-10.6658 0.78125,-1.7165 1.56494,-3.1151 1.56494,-3.1151 0,0 -0.63266,0.767 -1.39969,1.7531 0.12618,-0.7605 0.32803,-1.6015 0.54554,-2.4579 1.50735,-3.228 3.42853,-6.2264 3.42853,-6.2264 0,0 -1.20516,1.4886 -2.63013,3.3911 1.27912,-4.1089 2.88698,-8.0112 2.88698,-8.0112 0,0 -4.4619,7.3583 -6.02881,12.5301 -0.92604,1.4693 -1.74188,2.9253 -2.20803,4.1555 -0.39399,1.0399 -0.7162,2.1815 -0.99153,3.3237 -0.37208,0.4075 -1.42336,1.5874 -2.27972,2.855 -0.47912,-2.8827 -0.88555,-6.5175 -0.59132,-9.5162 0.48902,-4.9842 4.37624,-14.3766 4.37624,-14.3766 0,0 -5.48674,9.0378 -6.43497,14.1205 -0.064,0.343 -0.10315,0.7306 -0.15728,1.092 -1.4924,1.8551 -5.8862,7.4745 -7.30904,11.2297 -0.83983,2.2165 -1.38495,4.8778 -1.73019,7.0542 -0.31999,-1.0785 -1.46708,-5.1044 -2.19211,-9.5122 0.0159,-0.2964 0.0337,-0.5935 0.0617,-0.8796 0.48902,-4.9842 4.37625,-14.3746 4.37625,-14.3746 0,0 -3.01515,4.9697 -4.96161,9.6115 0.008,-0.3992 0.0308,-0.7804 0.0697,-1.1396 0.29799,-2.7489 3.09006,-7.7015 3.09006,-7.7015 0,0 -4.68941,5.3841 -5.40761,8.728 -0.42923,1.9985 -0.10448,4.9101 0.37431,7.5387 -0.20327,2.0287 -0.279,4.2629 -0.27077,6.5976 l -2.41909,0.7525 c 0,0 -0.86518,-5.8344 -0.51568,-8.728 0.27916,-2.3111 2.05871,-6.6751 2.05871,-6.6751 0,0 -2.15663,2.3115 -3.0801,4.0682 0.31859,-2.756 3.0801,-7.6618 3.0801,-7.6618 0,0 -4.68742,5.3841 -5.40561,8.728 -0.54841,2.5534 0.11873,6.5889 0.79044,9.6135 -0.0192,1.2438 -0.0179,2.1959 -0.0179,2.1959 l -1.98903,-11.1641 c 0.24803,-0.6578 0.44399,-1.1575 0.44399,-1.1575 0,0 -2.54004,2.7168 -3.28716,4.497 -0.28365,-1.5325 -0.46207,-3.0817 -0.31658,-4.2409 0.5151,-4.1039 5.14878,-11.2952 5.14878,-11.2952 0,0 -6.27231,7.5586 -7.98001,12.0655 -1.74579,4.6075 -2.24188,11.184 -2.24188,11.184 0,0 -0.62044,-2.4402 -1.11697,-5.6049 -0.0404,-0.6247 -0.0519,-1.2204 0.0119,-1.7293 0.5151,-4.1039 5.14877,-11.2952 5.14877,-11.2952 0,0 -3.24703,3.9268 -5.70625,7.8068 -0.0599,-1.2731 -0.0723,-2.5362 0.0418,-3.6989 0.48902,-4.9842 4.37626,-14.3766 4.37626,-14.3766 0,0 -5.48675,9.0378 -6.43497,14.1205 -0.50077,2.6842 -0.68111,5.9649 -0.67097,9.4586 -0.17465,0.5554 -0.33276,1.1169 -0.47785,1.6916 -0.76639,-2.9529 -1.7699,-7.3677 -1.9731,-10.9874 1.30864,-4.6465 3.37875,-9.6632 3.37875,-9.6632 0,0 -1.47315,2.4361 -3.02435,5.4799 0.80382,-2.7501 2.76752,-6.2482 2.76752,-6.2482 0,0 -4.68744,5.3841 -5.40562,8.728 -0.23148,1.0778 -0.2455,2.4279 -0.13539,3.8537 -0.2778,0.8188 -0.50613,1.605 -0.63712,2.3071 -0.4532,2.4292 -0.6545,5.3378 -0.68093,8.458 l -0.8641,0.27 c 0,0 -0.86518,-5.8344 -0.51568,-8.728 0.27916,-2.3111 2.06071,-6.6751 2.06071,-6.6751 0,0 -2.66743,2.8471 -3.3469,4.6222 -1.47683,3.8581 -1.28819,12.3216 -1.28819,12.3216 l -2.05871,-11.5533 -1.28819,9.4984 c 0,0 -2.22415,-6.6462 -1.80187,-10.0106 0.5151,-4.1039 5.14877,-11.2952 5.14877,-11.2952 0,0 -6.2723,7.5586 -7.98,12.0655 -0.40913,1.0798 -0.74384,2.2686 -1.02537,3.4547 -0.8144,-3.2133 -1.81372,-7.8585 -1.86758,-11.4024 1.30668,-4.6754 3.40863,-9.7664 3.40863,-9.7664 0,0 -0.47863,0.7956 -1.14683,1.9795 0.48279,-0.9628 0.88799,-1.7234 0.88799,-1.7234 0,0 -4.68743,5.3862 -5.40561,8.73 -0.18319,0.8529 -0.22975,1.8761 -0.18716,2.9722 -0.25527,0.7673 -0.46188,1.5003 -0.58536,2.1622 -0.5019,2.6903 -0.68204,5.9798 -0.67097,9.4825 l -0.87406,0.272 c 0,0 -0.86319,-5.8345 -0.51368,-8.7281 0.27915,-2.3111 2.05871,-6.675 2.05871,-6.675 0,0 -2.66743,2.845 -3.3469,4.6201 -0.23339,0.6098 -0.41394,1.3561 -0.57142,2.1443 -0.56228,-2.9481 -0.95862,-6.0556 -0.71478,-8.3051 0.29799,-2.7489 3.08807,-7.7015 3.08807,-7.7015 0,0 -4.68742,5.3841 -5.40561,8.728 -0.37064,1.7257 -0.1841,4.1419 0.18118,6.4626 l -1.08112,7.9854 -0.39024,0.1231 c -0.45866,-1.4858 -1.46788,-5.0115 -1.58286,-7.767 0.0131,-0.2159 0.0186,-0.4489 0.0418,-0.6413 0.21905,-1.8136 1.33361,-4.8166 1.81581,-6.0576 1.49104,-3.1446 3.33296,-6.0099 3.33296,-6.0099 0,0 -6.27429,7.5605 -7.98199,12.0675 -0.39123,1.0325 -0.7094,2.1657 -0.98356,3.2998 l -0.81831,-4.5844 -1.20656,8.9067 c -0.57259,-3.0293 -1.19949,-7.4196 -0.85215,-10.9597 0.10048,-1.024 0.36666,-2.2535 0.69088,-3.5301 1.50734,-3.2279 3.42853,-6.2263 3.42853,-6.2263 0,0 -1.20694,1.4932 -2.63411,3.3991 1.27951,-4.1119 2.89095,-8.0193 2.89095,-8.0193 0,0 -4.46424,7.3603 -6.03079,12.5322 -0.92543,1.4684 -1.74213,2.9239 -2.20804,4.1535 -1.74579,4.6075 -2.23989,11.184 -2.23989,11.184 0,0 -2.20854,-8.5302 -1.62069,-14.5215 0.48903,-4.9842 4.37625,-14.3746 4.37625,-14.3746 0,0 -5.48674,9.0358 -6.43497,14.1185 -0.61057,3.2727 -0.75918,7.4167 -0.64509,11.7756 -0.65467,-3.2038 -1.16918,-6.7247 -0.89994,-9.2085 0.29799,-2.7489 3.08807,-7.7015 3.08807,-7.7015 0,0 -4.68742,5.3842 -5.40561,8.728 -0.96076,4.4733 1.80187,13.6063 1.80187,13.6063 l -4.11942,1.2845 c 0,0 -0.86319,-5.8344 -0.51368,-8.728 0.27915,-2.3111 2.05871,-6.675 2.05871,-6.675 0,0 -2.66743,2.845 -3.3469,4.6201 -1.47683,3.8582 -1.2862,12.3217 -1.2862,12.3217 l -2.0607,-11.5513 -1.2862,9.4983 c 0,0 -2.22415,-6.6482 -1.80187,-10.0126 0.5151,-4.1038 5.14877,-11.2952 5.14877,-11.2952 0,0 -6.27429,7.5586 -7.98199,12.0656 -0.52252,1.379 -0.91963,2.9268 -1.23841,4.4295 -0.738,-3.4148 -1.37078,-7.3673 -1.07714,-10.0761 0.29798,-2.7489 3.08807,-7.7016 3.08807,-7.7016 0,0 -1.85965,2.1396 -3.41659,4.5586 1.30885,-4.4238 3.15975,-8.9226 3.15975,-8.9226 0,0 -4.61258,7.6014 -6.10646,12.7823 -0.46678,0.5091 -2.55592,2.8349 -3.16173,4.4176 -1.10212,2.8793 -1.27482,8.3127 -1.29217,10.8902 -0.17383,-0.595 -0.37492,-1.2953 -0.72872,-2.6823 l -1.32601,-7.4375 -0.0916,0.673 c -0.38427,-2.3932 -0.61828,-4.7679 -0.4221,-6.5777 0.0932,-0.8601 0.43862,-1.9357 0.85813,-3.008 0.94295,-1.6852 1.71626,-2.8967 1.71626,-2.8967 0,0 -0.40771,0.4926 -1.03533,1.2885 0.77676,-1.7029 1.54901,-3.0854 1.54901,-3.0854 0,0 -4.11962,4.732 -5.22642,8.0788 -0.78676,1.1573 -1.52857,2.3459 -2.15429,3.4805 0.573,-1.7557 1.20258,-3.3455 1.20258,-3.3455 0,0 -2.66743,2.847 -3.3469,4.6221 -1.35907,3.5506 -1.30516,10.7151 -1.28819,11.8809 -0.0541,-0.2152 -0.15697,-0.6411 -0.35042,-1.5407 l -0.79424,-4.4494 c -0.38098,-2.6283 -0.65005,-5.635 -0.3982,-8.2018 0.48902,-4.9842 4.37625,-14.3766 4.37625,-14.3766 0,0 -1.81852,3.0193 -3.54401,6.5341 -1.5478,1.9282 -5.86168,7.4705 -7.26722,11.18 -0.1745,0.4606 -0.32955,0.9501 -0.47983,1.4414 -0.83802,-3.6364 -1.65093,-8.1802 -1.32403,-11.1959 0.29798,-2.7489 3.09006,-7.7015 3.09006,-7.7015 0,0 -0.78672,0.9471 -1.70829,2.1582 0.52368,-1.376 0.93578,-2.4163 0.93578,-2.4163 0,0 -1.41195,2.3295 -2.9268,5.2832 -0.72578,1.1767 -1.35092,2.3822 -1.63064,3.4269 -0.85433,1.9259 -1.58703,3.8532 -1.87753,5.4103 -0.45434,2.4354 -0.6553,5.3528 -0.68093,8.4819 -0.23152,-1.8662 -0.60066,-5.3836 -0.35042,-7.4554 0.27915,-2.3111 2.0607,-6.675 2.0607,-6.675 0,0 -2.66743,2.847 -3.3469,4.6221 -1.47683,3.8582 -1.28819,12.3216 -1.28819,12.3216 l -2.05871,-11.5533 -1.2862,9.5004 c 0,0 -2.22614,-6.6482 -1.80386,-10.0126 0.5151,-4.1039 5.14877,-11.2952 5.14877,-11.2952 0,0 -6.2723,7.5586 -7.98,12.0655 -0.40913,1.0798 -0.74384,2.2687 -1.02537,3.4547 -0.8144,-3.2132 -1.81372,-7.8585 -1.86758,-11.4024 1.30668,-4.6754 3.40863,-9.7664 3.40863,-9.7664 0,0 -0.48524,0.8099 -1.16276,2.0113 0.49059,-0.9802 0.90392,-1.7552 0.90392,-1.7552 0,0 -4.68742,5.3862 -5.40561,8.73 -0.18319,0.853 -0.22975,1.8761 -0.18716,2.9722 -0.25527,0.7673 -0.46188,1.5004 -0.58536,2.1622 -0.5019,2.6903 -0.68204,5.9799 -0.67097,9.4825 l -0.87406,0.272 c 0,0 -0.86319,-5.8345 -0.51368,-8.728 0.27915,-2.3111 2.05871,-6.6751 2.05871,-6.6751 0,0 -2.66743,2.845 -3.3469,4.6201 -0.23375,0.6107 -0.41181,1.3586 -0.56943,2.1483 -0.56271,-2.9494 -0.96073,-6.0585 -0.71677,-8.3091 0.29799,-2.7489 3.08807,-7.7015 3.08807,-7.7015 0,0 -4.68742,5.3841 -5.40561,8.728 -0.37064,1.7257 -0.1841,4.1419 0.18118,6.4626 l -1.08112,7.9855 -0.39024,0.1231 c -0.45834,-1.4848 -1.46667,-5.0062 -1.58286,-7.7612 0.0131,-0.2181 0.0183,-0.4529 0.0418,-0.6472 0.21905,-1.8135 1.33361,-4.8166 1.81581,-6.0576 1.49104,-3.1446 3.33296,-6.0099 3.33296,-6.0099 0,0 -6.2723,7.5605 -7.98,12.0675 -0.39123,1.0325 -0.71139,2.1657 -0.98555,3.2998 l -0.81831,-4.5844 -1.20656,8.9067 c -0.57258,-3.0293 -1.19949,-7.4196 -0.85215,-10.9596 0.10048,-1.0241 0.36666,-2.2536 0.69088,-3.5301 1.50734,-3.228 3.42853,-6.2264 3.42853,-6.2264 0,0 -1.2063,1.4913 -2.63212,3.3951 1.27933,-4.1105 2.88896,-8.0152 2.88896,-8.0152 z m 42.10605,15.1727 c -0.99577,2.161 -1.87499,4.3819 -2.20206,6.135 -0.5064,2.7144 -0.6858,6.0398 -0.67098,9.5778 -0.55442,-2.1777 -1.10455,-4.9181 -0.87405,-6.7544 0.3296,-2.6261 2.33788,-6.497 3.74709,-8.9584 z m 220.4535,0.6671 c -0.42619,2.2552 0.0186,5.5804 0.58137,8.3647 l -0.77251,5.6903 -0.86211,0.27 c -0.62836,-2.2131 -1.51998,-5.8276 -1.23642,-8.0867 0.22377,-1.7828 1.2289,-4.1327 2.28967,-6.2383 z m -97.23138,0.2184 c -0.71166,5.0963 -0.51995,11.8041 0.006,18.1946 l -1.34593,-7.5546 -1.28818,9.4984 c 0,0 -2.22415,-6.6482 -1.80188,-10.0126 0.3883,-3.0937 3.11014,-7.9263 4.43002,-10.1258 z m -107.288,4.231 c -0.0388,0.1683 -0.0855,0.3435 -0.11548,0.5043 -0.70059,3.7553 -0.78731,8.6607 -0.57142,13.7194 l -0.45993,0.143 c 0,0 -0.86319,-5.8345 -0.51368,-8.728 0.19673,-1.6287 1.1212,-4.2169 1.66051,-5.6387 z m 112.23369,2.0768 c -0.79563,3.6291 -1.06121,7.0443 -1.06121,7.0443 0,0 -0.2089,-0.8354 -0.47386,-2.0867 0.31634,-1.6095 1.0613,-3.7072 1.53507,-4.9576 z m -138.68238,0.274 c 0.58178,3.6926 1.6187,7.1396 1.6187,7.1396 l -1.09506,0.3415 c -0.35541,-2.3538 -0.63183,-5.0473 -0.52364,-7.4811 z m -25.13461,0.7068 c 0.28153,1.0881 0.49377,1.8107 0.49377,1.8107 l -0.16326,0.05 -0.33051,-1.8603 z m 42.31511,0.131 c 0.29728,4.1591 1.61656,8.7681 1.8397,9.5262 l -0.0557,0.4189 -0.29467,0.091 c -0.46221,-1.4636 -1.96617,-6.5038 -1.60476,-9.3832 0.0262,-0.2084 0.0692,-0.4302 0.11548,-0.6532 z m 95.01138,1.0603 -1.16275,8.5771 c 0,0 -0.13745,-0.4184 -0.32454,-1.0404 -0.0305,-0.9418 -0.0186,-1.8382 0.0677,-2.5533 0.17063,-1.4126 0.87664,-3.5024 1.4196,-4.9834 z m 42.36888,0.3673 c 0.27938,1.0787 0.48979,1.7928 0.48979,1.7928 l -0.16128,0.05 -0.32851,-1.8425 z m 56.46726,0.012 c -0.0749,4.1838 0.17489,8.7786 0.56545,13.1575 l -0.16326,1.2052 c 0,0 -2.22415,-6.6482 -1.80187,-10.0126 0.15799,-1.2589 0.71924,-2.8069 1.39968,-4.3501 z m -228.19059,0.3852 c 0.0994,0.3532 0.24489,0.8835 0.24489,0.8835 l -0.1095,0.034 c -0.0449,-0.3081 -0.0944,-0.5994 -0.13539,-0.9173 z m 87.28026,0.6194 c 0.28085,1.0851 0.49377,1.8048 0.49377,1.8048 l -0.16326,0.05 -0.33051,-1.8544 z m 92.41112,0.9213 c 0.0994,0.3532 0.2449,0.8835 0.2449,0.8835 l -0.10952,0.034 c -0.0448,-0.308 -0.0944,-0.5994 -0.13538,-0.9173 z m -84.43908,1.0265 c 0.0994,0.3532 0.24489,0.8835 0.24489,0.8835 l -0.1095,0.034 c -0.0449,-0.3081 -0.0944,-0.5994 -0.13539,-0.9173 z m 162.70003,2.5671 c 0.0994,0.3532 0.24489,0.8836 0.24489,0.8836 l -0.1095,0.034 c -0.0448,-0.3081 -0.0944,-0.5994 -0.13539,-0.9173 z m -144.33687,0.415 c 0.244,0.9261 0.4181,1.497 0.4181,1.497 l -0.25485,0.079 c -0.0658,-0.5142 -0.11216,-1.0465 -0.16325,-1.5764 z m 98.78238,2.7796 c 0.27983,1.0807 0.49178,1.7968 0.49178,1.7968 l -0.16327,0.052 -0.32851,-1.8484 z" - style="fill:#00aa00;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <text - transform="scale(1.0014029,0.99859906)" - sodipodi:linespacing="125%" - id="text4234" - y="1024.4113" - x="265.60812" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40.71892548px;line-height:125%;font-family:Aileron;-inkscape-font-specification:Aileron;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - y="1024.4113" - x="265.60812" - id="tspan4236" - sodipodi:role="line">Hierba húmeda</tspan></text> - <rect - ry="18.159618" - y="823.18292" - x="0.8209275" - height="86.650398" - width="224.88033" - id="rect4329" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:1.641855;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - transform="scale(1.0014029,0.99859906)" - sodipodi:linespacing="125%" - id="text4230" - y="882.40289" - x="397.9498" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40.71892548px;line-height:125%;font-family:Aileron;-inkscape-font-specification:Aileron;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - y="882.40289" - x="397.9498" - id="tspan4232" - sodipodi:role="line">Lluvia</tspan></text> - <path - id="rect4512" - d="m 0.82020767,891.66605 0,0.008 c 0,10.06043 8.12317003,18.15887 18.21185033,18.15887 l 188.457832,0 c 10.08868,0 18.21185,-8.09844 18.21185,-18.15887 l 0,-0.008 -224.88153233,0 z" - style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - inkscape:connector-curvature="0" /> - <rect - ry="2.0241551" - y="884.09607" - x="103.97713" - height="9.4393644" - width="18.567665" - id="rect4517" - style="opacity:1;fill:#808000;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="874.37567" - cx="47.838871" - id="path4519" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4521" - cx="177.44792" - cy="883.78241" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="876.44196" - cx="184.28316" - id="circle4523" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4525" - cx="33.469151" - cy="878.19641" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4527" - cx="30.643055" - cy="887.11475" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="874.6228" - cx="74.616142" - id="circle4529" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4531" - cx="19.679264" - cy="891.38293" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="855.85492" - cx="87.801125" - id="circle4533" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="865.89838" - cx="43.000645" - id="circle4535" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4537" - cx="61.099461" - cy="882.01807" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="882.133" - cx="163.95946" - id="circle4539" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4541" - cx="82.706062" - cy="845.0896" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4543" - cx="94.593269" - cy="872.27783" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="873.59332" - cx="49.030384" - id="circle4545" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4547" - cx="178.4698" - cy="875.96783" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="879.55194" - cx="116.26012" - id="circle4549" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="890.22119" - cx="44.396305" - id="circle4551" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4553" - cx="45.705967" - cy="879.98785" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="840.94855" - cx="143.11592" - id="circle4555" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4557" - cx="53.901478" - cy="884.62781" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4559" - cx="209.43462" - cy="871.9281" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="879.08624" - cx="201.1263" - id="circle4561" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4563" - cx="43.964039" - cy="889.69244" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="881.84027" - cx="49.856373" - id="circle4565" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="873.57916" - cx="16.237385" - id="circle4567" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4569" - cx="133.77803" - cy="884.23138" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="878.41895" - cx="176.10667" - id="circle4571" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4573" - cx="174.37386" - cy="883.27612" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4575" - cx="199.62434" - cy="887.39838" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="870.50201" - cx="77.127922" - id="circle4577" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4579" - cx="136.62419" - cy="857.6756" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="859.68036" - cx="102.93819" - id="circle4581" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="862.31866" - cx="123.20824" - id="circle4583" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4585" - cx="149.65666" - cy="870.02258" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="884.09174" - cx="201.19298" - id="circle4587" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4589" - cx="107.67751" - cy="844.36407" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4591" - cx="35.460438" - cy="885.48853" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="862.33563" - cx="47.575352" - id="circle4593" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4595" - cx="159.09386" - cy="888.82471" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="882.99445" - cx="213.20641" - id="circle4597" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="881.94281" - cx="192.40041" - id="circle4599" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4601" - cx="183.87141" - cy="864.2027" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="843.72821" - cx="139.69835" - id="circle4603" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4605" - cx="193.07372" - cy="892.599" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4607" - cx="97.447273" - cy="875.99255" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="874.26556" - cx="190.96544" - id="circle4609" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4611" - cx="15.090428" - cy="890.4538" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="873.85785" - cx="69.627007" - id="circle4613" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="875.13007" - cx="179.52736" - id="circle4615" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4617" - cx="37.392857" - cy="887.22388" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="844.81134" - cx="124.71972" - id="circle4619" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4621" - cx="143.52824" - cy="869.26886" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4623" - cx="121.59261" - cy="868.32147" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="846.16119" - cx="138.12941" - id="circle4625" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4627" - cx="160.31245" - cy="842.13684" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="847.33124" - cx="161.41139" - id="circle4629" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="889.40601" - cx="175.26646" - id="circle4631" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4633" - cx="108.81359" - cy="868.89734" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="888.61377" - cx="50.881107" - id="circle4635" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4637" - cx="155.37907" - cy="863.96887" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4639" - cx="117.3785" - cy="882.84583" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="852.88269" - cx="148.6971" - id="circle4641" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4643" - cx="145.36333" - cy="860.38068" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="844.05261" - cx="109.7109" - id="circle4645" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="864.93878" - cx="204.14308" - id="circle4647" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4649" - cx="166.79225" - cy="888.90369" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="888.47974" - cx="105.70071" - id="circle4651" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4653" - cx="184.02208" - cy="883.57819" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4655" - cx="115.05871" - cy="870.26837" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="873.42896" - cx="23.000599" - id="circle4657" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="circle4659" - cx="149.80882" - cy="873.91669" /> - <ellipse - ry="1.0910468" - rx="1.0941103" - cy="854.1792" - cx="152.18968" - id="circle4661" - style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - transform="scale(1.0014029,0.99859906)" - sodipodi:linespacing="125%" - id="text4226" - y="882.40289" - x="111.31236" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40.71892548px;line-height:125%;font-family:Aileron;-inkscape-font-specification:Aileron;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - y="882.40289" - x="111.31237" - id="tspan4228" - sodipodi:role="line">Rociador</tspan></text> - <path - inkscape:connector-curvature="0" - id="path4671" - d="m 299.64598,868.04839 -59.21066,0" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.01797318px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow1Lend)" /> - <path - sodipodi:nodetypes="cc" - inkscape:connector-curvature="0" - id="path5073" - d="M 365.03515,922.47237 345.4699,955.33214" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.01797318px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker5075)" /> - <path - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.01797318px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker5391)" - d="m 164.75456,922.47237 19.56524,32.85977" - id="path5389" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cc" /> - </g> - </g> -</svg>
@@ -1,638 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="1400" - height="800" - viewBox="0 0 1400 800.00001" - id="svg2" - version="1.1" - inkscape:version="0.91 r13725" - sodipodi:docname="decission_tree.svg"> - <defs - id="defs4" /> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="0.35" - inkscape:cx="666.52844" - inkscape:cy="513.1507" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - inkscape:window-width="1366" - inkscape:window-height="745" - inkscape:window-x="-8" - inkscape:window-y="-8" - inkscape:window-maximized="1" - units="px" /> - <metadata - id="metadata7"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-252.36216)"> - <g - id="g4537"> - <path - inkscape:connector-curvature="0" - id="path4335" - d="m 619.45408,462.04855 -4.84255,150.25129" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.96893674px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4271" - d="m 352.68783,613.19378 35.22036,141.00547" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.96893674px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4249" - d="m 616.84054,301.80669 1.95669,152.75593" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.96893674px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4251" - d="M 597.27367,462.39626 356.6012,587.73445" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.96893674px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4253" - d="M 648.14753,454.56262 871.20983,575.984" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.96893674px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4255" - d="M 916.21362,587.73445 1025.7881,856.03654" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.96893674px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4257" - d="M 904.4735,558.35832 1113.839,679.77969" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.96893674px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4259" - d="m 1166.6695,705.23902 125.228,127.29661" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.96893674px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4261" - d="m 1139.2759,720.90629 58.7006,283.96941" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.96893674px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4263" - d="M 642.27747,652.36196 830.1194,771.82493" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.96893674px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4265" - d="m 605.10042,634.73628 3.91337,186.04889" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.96893674px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4267" - d="M 648.14753,832.53563 789.02898,955.91542" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.96893674px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4269" - d="M 597.27367,832.53563 485.74253,977.45792" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.96893674px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4273" - d="M 319.42411,601.44332 100.27526,762.03289" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.96893674px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4275" - d="M 315.51075,628.86105 188.32614,899.12154" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.96893674px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <ellipse - ry="49.939438" - rx="96.855988" - cy="302.78589" - cx="621.7323" - id="path4155" - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.96893674;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <text - transform="scale(0.99956011,1.0004401)" - sodipodi:linespacing="125%" - id="text4147" - y="317.12897" - x="622.75885" - style="font-style:normal;font-weight:normal;font-size:37.14643478px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron" - y="317.12897" - x="622.75885" - id="tspan4149" - sodipodi:role="line">Consulta</tspan></text> - <rect - y="401.80957" - x="535.74707" - height="103.86034" - width="168.79756" - id="rect4157" - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.96893674;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.96893674;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect4159" - width="168.79756" - height="103.86034" - x="263.76752" - y="548.69025" /> - <rect - y="580.02478" - x="527.92023" - height="103.86034" - width="168.79756" - id="rect4161" - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.96893674;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.96893674;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect4163" - width="168.79756" - height="103.86034" - x="790.11627" - y="519.31409" /> - <rect - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.96893674;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="rect4165" - width="168.79756" - height="103.86034" - x="527.92023" - y="775.86572" /> - <ellipse - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.96893674;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="ellipse4167" - cx="484.76419" - cy="962.7699" - rx="96.855988" - ry="49.939438" /> - <ellipse - ry="49.939438" - rx="96.855988" - cy="962.7699" - cx="791.96405" - id="ellipse4169" - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.96893674;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.96893674;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="ellipse4171" - cx="838.92456" - cy="774.76257" - rx="96.855988" - ry="49.939438" /> - <ellipse - ry="49.939438" - rx="96.855988" - cy="764.97052" - cx="97.340225" - id="ellipse4173" - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.96893674;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.96893674;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="ellipse4175" - cx="369.31967" - cy="761.05371" - rx="96.855988" - ry="49.939438" /> - <ellipse - ry="49.939438" - rx="96.855988" - cy="898.14233" - cx="191.26115" - id="ellipse4177" - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.96893674;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - ry="49.939438" - rx="96.855988" - cy="878.55829" - cx="1034.5933" - id="ellipse4179" - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.96893674;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <rect - y="642.69391" - x="1032.7455" - height="103.86034" - width="168.79756" - id="rect4183" - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.96893674;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.96893674;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="ellipse4185" - cx="1196.9982" - cy="1001.9381" - rx="96.855988" - ry="49.939438" /> - <ellipse - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.96893674;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="ellipse4187" - cx="1302.6593" - cy="845.26532" - rx="96.855988" - ry="49.939438" /> - <text - transform="scale(0.99956011,1.0004401)" - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:37.14643478px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - x="618.84381" - y="467.86017" - id="text4189" - sodipodi:linespacing="125%"><tspan - sodipodi:role="line" - id="tspan4191" - x="618.84381" - y="467.86017" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron">Color</tspan></text> - <text - transform="scale(0.99956011,1.0004401)" - sodipodi:linespacing="125%" - id="text4193" - y="589.22815" - x="869.40991" - style="font-style:normal;font-weight:normal;font-size:37.14643478px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron" - y="589.22815" - x="869.40991" - id="tspan4195" - sodipodi:role="line">Tamaño</tspan></text> - <text - transform="scale(0.99956011,1.0004401)" - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:37.14643478px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - x="1121.9336" - y="708.63855" - id="text4197" - sodipodi:linespacing="125%"><tspan - sodipodi:role="line" - id="tspan4199" - x="1121.9336" - y="708.63855" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron">Sabor</tspan></text> - <text - transform="scale(0.99956011,1.0004401)" - sodipodi:linespacing="125%" - id="text4201" - y="647.95453" - x="607.09851" - style="font-style:normal;font-weight:normal;font-size:37.14643478px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron" - y="647.95453" - x="607.09851" - id="tspan4203" - sodipodi:role="line">Forma</tspan></text> - <text - transform="scale(0.99956011,1.0004401)" - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:37.14643478px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - x="348.70215" - y="616.63379" - id="text4205" - sodipodi:linespacing="125%"><tspan - sodipodi:role="line" - id="tspan4207" - x="348.70215" - y="616.63379" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron">Tamaño</tspan></text> - <text - transform="scale(0.99956011,1.0004401)" - sodipodi:linespacing="125%" - id="text4209" - y="781.06781" - x="100.09353" - style="font-style:normal;font-weight:normal;font-size:37.14643478px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron" - y="781.06781" - x="100.09353" - id="tspan4211" - sodipodi:role="line">Sandía</tspan></text> - <text - transform="scale(0.99956011,1.0004401)" - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:37.14643478px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - x="366.32004" - y="771.28009" - id="text4213" - sodipodi:linespacing="125%"><tspan - sodipodi:role="line" - id="tspan4215" - x="366.32004" - y="771.28009" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron">Uvas</tspan></text> - <text - transform="scale(0.99956011,1.0004401)" - sodipodi:linespacing="125%" - id="text4217" - y="910.26599" - x="192.09828" - style="font-style:normal;font-weight:normal;font-size:37.14643478px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron" - y="910.26599" - x="192.09828" - id="tspan4219" - sodipodi:role="line">Manzana</tspan></text> - <text - transform="scale(0.99956011,1.0004401)" - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:37.14643478px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - x="607.09845" - y="839.79425" - id="text4221" - sodipodi:linespacing="125%"><tspan - sodipodi:role="line" - id="tspan4223" - x="607.09845" - y="839.79425" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron">Tamaño</tspan></text> - <text - transform="scale(0.99956011,1.0004401)" - sodipodi:linespacing="125%" - id="text4225" - y="972.90747" - x="479.85785" - style="font-style:normal;font-weight:normal;font-size:37.14643478px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron" - y="972.90747" - x="479.85785" - id="tspan4227" - sodipodi:role="line">Pomelo</tspan></text> - <text - transform="scale(0.99956011,1.0004401)" - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:37.14643478px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - x="834.17401" - y="790.85553" - id="text4229" - sodipodi:linespacing="125%"><tspan - sodipodi:role="line" - id="tspan4231" - x="834.17401" - y="790.85553" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron">Plátano</tspan></text> - <text - transform="scale(0.99956011,1.0004401)" - sodipodi:linespacing="125%" - id="text4233" - y="972.90747" - x="783.27777" - style="font-style:normal;font-weight:normal;font-size:37.14643478px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron" - y="972.90747" - x="783.27777" - id="tspan4235" - sodipodi:role="line">Limón</tspan></text> - <text - transform="scale(0.99956011,1.0004401)" - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:37.14643478px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - x="1033.8439" - y="890.69049" - id="text4237" - sodipodi:linespacing="125%"><tspan - sodipodi:role="line" - id="tspan4239" - x="1033.8439" - y="890.69049" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron">Manzana</tspan></text> - <text - transform="scale(0.99956011,1.0004401)" - sodipodi:linespacing="125%" - id="text4241" - y="859.36969" - x="1303.9855" - style="font-style:normal;font-weight:normal;font-size:37.14643478px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron" - y="859.36969" - x="1303.9855" - id="tspan4243" - sodipodi:role="line">Uva</tspan></text> - <text - transform="scale(0.99956011,1.0004401)" - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:37.14643478px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - x="1192.4053" - y="1012.0584" - id="text4245" - sodipodi:linespacing="125%"><tspan - sodipodi:role="line" - id="tspan4247" - x="1192.4053" - y="1012.0584" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron">Cereza</tspan></text> - <text - transform="matrix(0.81157269,-0.58401192,0.58349823,0.81228716,0,0)" - sodipodi:linespacing="125%" - id="text4277" - y="668.25781" - x="-229.80688" - style="font-style:normal;font-weight:normal;font-size:21.05917931px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron" - y="668.25781" - x="-229.80688" - id="tspan4279" - sodipodi:role="line">Grande</tspan></text> - <text - transform="matrix(0.34225715,0.93996493,-0.93913815,0.34255846,0,0)" - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:26.32293129px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - x="1005.8774" - y="-676.61884" - id="text4289" - sodipodi:linespacing="125%"><tspan - sodipodi:role="line" - id="tspan4291" - x="1005.8774" - y="-676.61884" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron">Mediano</tspan></text> - <text - transform="matrix(0.9160891,0.40022848,-0.39987645,0.91689559,0,0)" - sodipodi:linespacing="125%" - id="text4293" - y="149.85788" - x="1174.7745" - style="font-style:normal;font-weight:normal;font-size:20.7318058px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron" - y="149.85788" - x="1174.7745" - id="tspan4295" - sodipodi:role="line">Pequeño</tspan></text> - <text - transform="matrix(0.88742856,-0.46039514,0.45999018,0.88820982,0,0)" - sodipodi:linespacing="125%" - id="text4307" - y="674.89526" - x="185.33745" - style="font-style:normal;font-weight:normal;font-size:21.76940536px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron" - y="674.89526" - x="185.33745" - id="tspan4309" - sodipodi:role="line">Verde</tspan></text> - <text - transform="scale(0.99956011,1.0004401)" - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:20.46263123px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - x="572.30908" - y="554.45374" - id="text4311" - sodipodi:linespacing="125%"><tspan - sodipodi:role="line" - id="tspan4313" - x="572.30908" - y="554.45374" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron">Amarillo</tspan></text> - <text - transform="matrix(0.94064864,0.33838128,-0.33808364,0.94147675,0,0)" - sodipodi:linespacing="125%" - id="text4315" - y="216.6124" - x="871.52502" - style="font-style:normal;font-weight:normal;font-size:26.06164169px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron" - y="216.6124" - x="871.52502" - id="tspan4317" - sodipodi:role="line">Rojo</tspan></text> - <text - transform="matrix(0.70679573,0.70741797,-0.70679573,0.70741797,0,0)" - sodipodi:linespacing="125%" - id="text4319" - y="-344.22678" - x="1420.978" - style="font-style:normal;font-weight:normal;font-size:24.88329887px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron" - y="-344.22678" - x="1420.978" - id="tspan4321" - sodipodi:role="line">Agrio</tspan></text> - <text - transform="matrix(0.2695615,0.96337374,-0.96252637,0.26979881,0,0)" - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:26.99650192px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - x="1189.4249" - y="-900.60632" - id="text4323" - sodipodi:linespacing="125%"><tspan - sodipodi:role="line" - id="tspan4325" - x="1189.4249" - y="-900.60632" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron">Dulce</tspan></text> - <text - transform="matrix(0.89536218,0.44474125,-0.44435006,0.89615043,0,0)" - sodipodi:linespacing="125%" - id="text4327" - y="298.39502" - x="986.5733" - style="font-style:normal;font-weight:normal;font-size:23.97977066px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron" - y="298.39502" - x="986.5733" - id="tspan4329" - sodipodi:role="line">Delgado</tspan></text> - <text - transform="matrix(0.99955996,5.4236004e-4,-5.4188299e-4,1.0004399,0,0)" - sodipodi:linespacing="125%" - id="text4331" - y="739.12512" - x="558.90198" - style="font-style:normal;font-weight:normal;font-size:22.1249485px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron" - y="739.12512" - x="558.90198" - id="tspan4333" - sodipodi:role="line">Redondo</tspan></text> - <text - transform="scale(0.99956011,1.0004401)" - sodipodi:linespacing="125%" - id="text4337" - y="902.26685" - x="498.11777" - style="font-style:normal;font-weight:normal;font-size:19.15585709px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron" - y="902.26685" - x="498.11777" - id="tspan4339" - sodipodi:role="line">Mediano</tspan></text> - <text - transform="matrix(0.74827729,0.66330624,-0.66272279,0.74893605,0,0)" - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:18.89144516px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - x="1134.3363" - y="177.04033" - id="text4341" - sodipodi:linespacing="125%"><tspan - sodipodi:role="line" - id="tspan4343" - x="1134.3363" - y="177.04033" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron">Pequeño</tspan></text> - <text - transform="matrix(0.99953094,-0.00764342,0.0076367,1.0004109,0,0)" - sodipodi:linespacing="125%" - id="text4345" - y="689.33502" - x="422.7298" - style="font-style:normal;font-weight:normal;font-size:23.94909096px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron" - y="689.33502" - x="422.7298" - id="tspan4347" - sodipodi:role="line">Pequeño</tspan></text> - <text - transform="matrix(0.41556212,-0.90988147,0.90908115,0.41592797,0,0)" - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:23.52765274px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - x="-594.17133" - y="537.99622" - id="text4349" - sodipodi:linespacing="125%"><tspan - sodipodi:role="line" - id="tspan4351" - x="-594.17133" - y="537.99622" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Aileron;-inkscape-font-specification:Aileron">Mediano</tspan></text> - </g> - </g> -</svg>
@@ -1,124 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="600" - height="427" - viewBox="0 0 600.00001 427.00001" - id="svg2" - version="1.1" - inkscape:version="0.91 r13725" - sodipodi:docname="drawing.svg"> - <defs - id="defs4" /> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="0.7" - inkscape:cx="294.66008" - inkscape:cy="242.60004" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - inkscape:window-width="1366" - inkscape:window-height="745" - inkscape:window-x="-8" - inkscape:window-y="-8" - inkscape:window-maximized="1" - units="px" /> - <metadata - id="metadata7"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-625.36216)"> - <g - id="g4201" - transform="matrix(1.0858252,0,0,1.1801734,-100.88655,293.00672)"> - <path - inkscape:connector-curvature="0" - id="path4147" - d="m 138.3909,281.61581 0,314.15745 507.09658,0" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <text - transform="matrix(0,-1,1,0,0,0)" - sodipodi:linespacing="125%" - id="text4149" - y="118.0612" - x="-436.6539" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:33.98496246px;line-height:125%;font-family:aileron;-inkscape-font-specification:aileron;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#800141;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve"><tspan - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:aileron;-inkscape-font-specification:aileron;fill:#800141;fill-opacity:1" - y="118.0612" - x="-436.6539" - id="tspan4151" - sodipodi:role="line">Progreso humano</tspan></text> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:33.98496246px;line-height:125%;font-family:aileron;-inkscape-font-specification:aileron;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#800141;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - x="397.81177" - y="636.76599" - id="text4153" - sodipodi:linespacing="125%"><tspan - sodipodi:role="line" - id="tspan4155" - x="397.81177" - y="636.76599" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:aileron;-inkscape-font-specification:aileron;fill:#800141;fill-opacity:1">Tiempo</tspan></text> - <path - sodipodi:nodetypes="ccccccccccccccccccc" - inkscape:connector-curvature="0" - id="path4157" - d="m 137.9028,591.22079 c 13.7989,-0.22636 27.92645,1.01015 40.71429,-2.14286 27.72067,-2.86282 57.26327,-0.28419 85.43004,-2.14285 15.99648,-0.45263 32.42354,0.91867 48.14139,-0.71429 5.72845,-1.0112 11.1394,-1.56887 17.14285,-1.42857 7.21766,-2.25049 14.7175,-3.24615 22.85715,-2.85714 8.67613,-1.09828 17.33661,-0.73193 25.71428,-2.85715 19.64199,-2.40443 40.40392,-1.11356 60.71429,-1.42857 15.21116,-4.97803 34.05281,-2.08855 50.97128,-2.85714 9.30387,1.57985 16.53986,-3.01249 25.20766,-3.57143 9.54217,-0.39466 19.76332,0.84988 28.82106,-0.71428 5.81038,-3.79658 15.43012,-5.01928 21.89322,-7.85715 5.197,0.83489 10.91718,-1.37604 19.28572,-2.14285 9.6979,2.47098 12.57335,-5.29998 13.82106,-10.71429 1.17819,-8.67754 2.44634,-16.6896 4.28571,-25 0.40885,-3.66551 1.55773,-10.04969 3.79613,-19.50668 1.97168,-4.00856 2.92821,-46.54217 6.20387,-72.63618 4.39723,-17.97642 0.56595,-38.8502 2.98282,-58.188 1.18644,-21.31693 3.9398,-42.57798 3.44576,-63.95485" - style="fill:none;fill-rule:evenodd;stroke:#31c700;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4162" - d="m 612.42837,319.45866 c 0.92593,-0.89675 1.13939,-2.28735 2.09536,-3.16156 0.71531,-1.36676 1.61726,-2.62787 2.19515,-4.06343 0.74302,-1.66435 1.82927,-3.1575 2.41751,-4.89341 0.25358,-0.57085 0.49901,-1.82642 0.91484,-0.66484 0.65592,1.06746 1.06559,2.26602 1.26276,3.49816 0.25904,1.12137 0.40522,2.33952 1.15854,3.26189 0.34204,1.49848 0.96357,3.0829 2.37272,3.87918 0.27103,0.17045 0.55635,0.32338 0.79737,0.53683" - style="fill:none;fill-rule:evenodd;stroke:#31c700;stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - sodipodi:nodetypes="ccccc" - inkscape:connector-curvature="0" - id="path4169" - d="m 580.26888,540.41722 c -1.29504,-0.0304 -2.76029,1.10183 -2.4902,2.49837 0.42493,1.6662 2.66317,2.52501 4.07385,1.51711 0.98406,-0.75056 0.86948,-2.24494 0.16896,-3.14875 -0.43502,-0.6157 -1.07433,-0.94235 -1.88115,-0.85634" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4171" - d="m 580.35816,545.14937 c 0.7349,2.44499 0.13044,5.01896 0.4795,7.50992 -0.0449,1.5878 0.23077,3.2213 -0.25634,4.76684" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4173" - d="m 578.21531,552.96187 c 0.60692,-1.16446 2.20743,-1.91413 3.38778,-1.12902 0.46439,0.25767 0.66163,0.74584 0.98701,1.129" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path4179" - d="m 578.46915,559.77056 c 0.66126,-0.86601 1.0905,-2.05193 2.14966,-2.51299 0.84539,-0.2873 1.26127,0.66796 1.91512,0.94024 0.13563,0.0734 0.26977,0.14971 0.39917,0.23366" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - </g> - </g> -</svg>
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Inteligencia artificial | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog>blog</a><li><a href=/golb class=selected>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Inteligencia artificial</h1><div class=time><p>2016-02-24<p>last updated 2016-03-05</div><h2 id=indice>Índice</h2><ul><li><a href=https://lonami.dev/golb/inteligencia-artificial/#qu%C3%A9_es>Qué es</a><li><a href=https://lonami.dev/golb/inteligencia-artificial/#en_qu%C3%A9_consiste>En qué consiste</a><li><a href=https://lonami.dev/golb/inteligencia-artificial/#l%C3%ADmites>Límites</a><li><a href=https://lonami.dev/golb/inteligencia-artificial/#tipos_de_inteligencia_artificial>Tipos de inteligencia artificial</a><li><a href=https://lonami.dev/golb/inteligencia-artificial/#aplicaciones_pr%C3%A1cticas>Aplicaciones prácticas</a><li><a href=https://lonami.dev/golb/inteligencia-artificial/#implicaciones_%C3%A9ticas>Implicaciones éticas</a><li><a href=https://lonami.dev/golb/inteligencia-artificial/#ejemplos>Ejemplos</a><li><a href=https://lonami.dev/golb/inteligencia-artificial/#conceptos>Conceptos</a><li><a href=https://lonami.dev/golb/inteligencia-artificial/#fuentes>Fuentes</a></ul><h2 id=que-es>Qué es</h2><p>La inteligencia artificial es una rama apasionante que tiene su origen en la <strong>informática</strong> y se basa en el concepto de conseguir <em>emular</em><sup class=footnote-reference><a href=#1>1</a></sup> al cerebro humano, mediante el desarrollo un programa que sea capaz de <strong>aprender y mejorar por sí sólo</strong> (normalmente bajo algún tipo de supervisión)<p>Fue un concepto acuñado por <em>John McCarthy</em> en un congreso de informática de 1956, y desde entonces este campo ha crecido de manera exponencial con unas buenas previsiones de futuro.<p><img src=https://lonami.dev/golb/inteligencia-artificial/human_progress_edge.svg alt="Progreso humano en la inteligencia artificial"><p><em>Progreso humano en la inteligencia artificial. <a href=http://waitbutwhy.com/2015/01/artificial-intelligence-revolution-1>Fuente</a></em><h2 id=en-que-consiste>En qué consiste</h2><p>La inteligencia artificial no consiste en escribir unas pautas fijas y finitas al igual que hacen la gran mayoría de programas, en los cuales introduces unos datos y producen siempre la misma salida, una salida predecible, programada e invariable, que además, tiene sus límites, ya que si introduces datos para los que la aplicación no está programada, esta aplicación no será capaz de manejarlos. No los entenderá y no producirá ningún resultado.<p>La inteligencia artificial consiste en dar un paso <strong>más allá</strong>. Una inteligencia artificial <em>entrenada</em> es capaz de manejar datos para los cuales no ha sido programada de manera explícita<sup class=footnote-reference><a href=#2>2</a></sup><h2 id=limites>Límites</h2><p>Actualmente, la inteligencia artificial se ve limitada por la velocidad y capacidad de los dispositivos (ordenadores, teléfonos inteligentes).<p>A día de hoy, ya hemos conseguido emular el cerebro de un gusano de un milímetro de longitud, que consiste de un total de trescientas dos neuronas. El <strong>cerebro humano</strong> contiene unas <strong>cien mil millones de neuronas</strong>.<p><img src=https://lonami.dev/golb/inteligencia-artificial/exponential_grow.gif alt="Progreso en la velocidad de los dispositivos"><p><em>Crecimiento en la velocidad de procesado de los dispositivos. <a href=http://waitbutwhy.com/2015/01/artificial-intelligence-revolution-1>Fuente</a></em><p>Comparado con las neuronas de un cerebro humano (cuya velocidad<sup class=footnote-reference><a href=#3>3</a></sup> máxima oscilan entre los 200Hz), los procesadores de hoy en día (mucho más lentos que los que tendremos dentro de algunos años) ya tienen una velocidad superior a los 2Ghz, es decir, <strong>10 millones de veces</strong> más rápidos que las neuronas. Y la comunicación interna del cerebro, que oscila entre los 120m/s, queda infinitamente distante de la velocidad de los ordenadores que se comunican de manera óptica a la <strong>velocidad de la luz</strong>.<p>Además de todo esto, la capacidad de los dispositivos puede ser <strong>ampliada</strong>, a diferencia del cerebro que tiene un tamaño ya determinado. Y, por último, un procesador puede estar <strong>trabajando sin parar</strong> nunca, sin cansarse.<h2 id=tipos-de-inteligencia-artificial>Tipos de inteligencia artificial</h2><h3 id=segun-el-tipo-de-aprendizaje>Según el tipo de aprendizaje</h3><ul><li><strong>Aprendizaje supervisado</strong>: se le presenta una entrada de datos y produce una salida de los datos procesados, y un "tutor" es el que determina si la salida es correcta o no.<li><strong>Aprendizaje sin supervisar</strong>: se le presenta una entrada de datos sin presentarle ningún otro tipo de información, para que encuentre la estructura de los datos por sí sóla.<li><strong>Aprendizaje por refuerzo</strong>: un ordenador interactua con un entorno variable en el que debe llevar a cabo una tarea concreta, sin que un tutor le indique cómo explícitamente.</ul><h3 id=segun-la-forma-de-llevarlo-a-cabo-principales-metodos>Según la forma de llevarlo a cabo (principales métodos)</h3><ul><li><p><strong>Aprendizaje por árbol de decisiones</strong>. Este aprendizaje usa un árbol de decisiones, que almacena observaciones y conclusiones.</p> <p><img src=https://lonami.dev/golb/inteligencia-artificial/decision_tree.svg alt="Árbol de decisiones"><li><p><strong>Aprendizaje por asociación de reglas</strong>. Utilizado para descubrir relaciones en grandes bases de datos<sup class=footnote-reference><a href=#4>4</a></sup>.<li><p><strong>Red neuronal artificial (RNA)</strong>. Inspirado en redes neuronales biológicas**. Los cálculos se estructuran en un grupo de neuronas artificiales interconectadas.<li><p><strong>Programación lógica inductiva (PLI)</strong>. Se aproxima de manera hipotética, dado un transfondo lógico y una entrada, a una solución que no se le había presentado antes.<li><p><strong>Máquinas de soporte vectorial (MSV)</strong>. Se usan para clasificar y problemas que necesitan de regresión<sup class=footnote-reference><a href=#5>5</a></sup>. Dado una serie de ejemplos, una entrada será clasificada de una forma u otra.<li><p><em><strong>Clustering</strong></em>. Este tipo de análisis consiste en asignar observaciones a ciertas subcategorías (denominadas <em>clústeres</em>), para que aquellas que están en el mismo <em>clúster</em> sean similares**. Este tipo de aprendizaje es una técnica común en análisis estadístico.<li><p><strong>Redes bayesianas</strong>. Es un modelo probabilístico que organiza variables al azar según unas determinadas condiciones mediante un gráfico**. Un ejemplo de red bayesiana es el siguiente:</p> <p><img src=https://lonami.dev/golb/inteligencia-artificial/bayesian_network.svg alt="Red bayesiana"><li><p><strong>Algoritmos genéticos</strong>. Imita el proceso evolutivo de la selección natural, y usa métodos como la mutación para generar nuevos "genotipos" que, con algo de suerte, serán mejores en encontrar la solución correcta.</ul><h2 id=aplicaciones-practicas>Aplicaciones prácticas</h2><p>La inteligencia artificial ya se encuentra desde hace algún tiempo entre nosotros, como por ejemplo el archiconocido <strong>buscador Google</strong>, que filtra los resultados más relevantes mediante una inteligencia artificial. Otros ejemplos son el reconocimiento de caracteres a partir de una foto, o incluso reconocimiento del habla con <strong>asistentes virtuales como Cortana o Siri</strong>, en los videojuegos, en bolsa, en los <strong>hospitales</strong>, industria pesada, transportes, juguetes, música, aviación, robótica, filtros anti-spam... y un largo etcétera.<h2 id=implicaciones-eticas>Implicaciones éticas</h2><p>Una vez tengamos la tecnología necesaria para recrear un cerebro humano, si enseñáramos a esta inteligencia artificial al igual que un humano, ¿llegaría a tener <strong>sentimientos</strong>? ¿Sería consciente de su existencia? ¿Podría sentir felicidad, alegría, tristeza, enfado? ¿Tendría <strong>creatividad</strong>? ¿Derecho a propiedad? Si la respuesta es que sí, y es la respuesta más lógica, significa que, en realidad, los sentimientos no son nada más que una manera de entender las cosas. No tienen valor por sí mismos. Seríamos capaces de recrearlos, y tendrían el mismo valor que un sentimiento humano, aunque esa inteligencia viviera dentro de un ordenador. Y acabar con esta inteligencia sería acabar con esta vida, <strong>una vida</strong> casi, por no decir enteramente, <strong>humana</strong>. Además, todo esto implicaría que todo comportamiento humano es predecible. Por último, si esta inteligencia es realmente como un humano, al utilizarla, ¿la estaríamos esclavizando al obligarla a trabajar para nosotros? ¿En qué momento dejaremos de llamarlos "ordenadores" y comenzaremos a tratarles como "humanos"? ¿Será la humanidad capaz de adaptarse al cambio?<h2 id=ejemplos>Ejemplos</h2><p>En el siguiente algorítmo genético podemos ver como una figura aprende a saltar, obedeciendo a las leyes físicas (ver en <a href=https://youtu.be/Gl3EjiVlz_4>YouTube</a>):</p><iframe width=420 height=315 src=https://www.youtube.com/embed/Gl3EjiVlz_4 frameborder=0 allowfullscreen></iframe><p>Por el contrario, en el siguiente ejemplo, un algorítmo genético aprende a "luchar" contra otra figura: (ver en <a href=https://youtu.be/u2t77mQmJiY>YouTube</a>):</p><iframe width=560 height=315 src=https://www.youtube.com/embed/u2t77mQmJiY frameborder=0 allowfullscreen></iframe><p>Estos cuatro increíbles ejemplos siguientes muestran un proceso evolutivo similar al sufrido por cualquier tipo de ser (ver en <a href=https://youtu.be/GOFws_hhZs8>YouTube</a>):</p><iframe width=560 height=315 src=https://www.youtube.com/embed/GOFws_hhZs8 frameborder=0 allowfullscreen></iframe><iframe width=560 height=315 src=https://www.youtube.com/embed/31dsH2Fs1IQ frameborder=0 allowfullscreen></iframe><iframe width=560 height=315 src=https://www.youtube.com/embed/IVcvvqxtNwE frameborder=0 allowfullscreen></iframe><iframe width=560 height=315 src=https://www.youtube.com/embed/KrTbJUJsDSw frameborder=0 allowfullscreen></iframe><h2 id=conceptos>Conceptos</h2><div class=footnote-definition id=1><sup class=footnote-definition-label>1</sup><p><strong>Emular</strong>. Tratar de imitar un modelo, aproximarse a este. Copiar su comportamiento o incluso mejorarlo.</div><div class=footnote-definition id=2><sup class=footnote-definition-label>2</sup><p><strong>Explícito</strong>. Suceso que ocurre de manera previamente avisada de una forma directa, anticipado <em>sin rodeos</em>.</div><div class=footnote-definition id=3><sup class=footnote-definition-label>3</sup><p><strong>Velocidad (en hercios)</strong>. Número de cálculos realizados por segundo. Un procesador con una velocidad de 100Hz es capaz de realizar 100 cálculos por segundo.</div><div class=footnote-definition id=4><sup class=footnote-definition-label>4</sup><p><strong>Base de datos</strong>. Lugar en el que se almacena de manera estructurada una información, como por ejemplo un censo que indique el nombre de las personas, sus apellidos, edad, etcétera.</div><div class=footnote-definition id=5><sup class=footnote-definition-label>5</sup><p><strong>Regresión</strong>. Las pruebas de regresión consisten en someter a un programa a una serie de pruebas para descubrir fallos en este cometidos accidentalmente con anterioridad en versiones anteriores.</div><h2 id=fuentes>Fuentes</h2><ul><li><a href=http://waitbutwhy.com/2015/01/artificial-intelligence-revolution-1>Evolución de la inteligencia artificial - Wait but why</a><li><a href=https://en.wikipedia.org/wiki/Machine_learning><em>Machine learning</em> - Wikipedia</a></ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Making a Difference | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog>blog</a><li><a href=/golb class=selected>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Making a Difference</h1><div class=time><p>2020-08-24</div><p>When I've thought about what "making a difference" means, I've always seen it as having to do something at very large scales. Something that changes everyone's lives. But I've realized that it doesn't need the case.<p>I'm thinking about certain people. I'm thinking about middle-school.<p>I'm thinking about my math teacher, who I remember saying that if he made a student fail with a grade very close to passing, then he would be a bad teacher because he could just "let them pass". But if he just passed that one student, he would fail as a teacher, because it's his job to get people to actually <em>learn</em> his subject. He didn't want to be mean, he was just trying to have everybody properly learn the subject. That made a difference on me, but I never got the chance to thank him.<p>I'm thinking about my English teacher, who has had to put up with a lot of stupidity from the rest of students, making the class not enjoyable. But I thought she was nice, and she thought I was nice. I remember of a day when she was grading my assignement and debating what grade I should get. I thought to myself, she should just grade whatever she considered fair. But she went something along the lines of "what the heck, you deserve it", and graded in my favour. I think of her as an honest person who also just wants to make other people learn, despite the other students not liking her much. I never got a chance to thank her.<p>I'm thinking about my philosophy teacher, who was a nice chap. He tried to make the lectures fun and had some really interesting ways of thinking. He was nice to talk to overall, but I never got to thank him for what he taught us.<p>I'm thinking about one of my lecturers at university who has let me express my feelings to her and helped me make the last push I needed to finish my university degree (I was really dreading some subjects and considering dropping out, but those days are finally over).<p>I'm thinking about all the people who has been in a long-distance relationship with me. None of the three I've had have worked out in the long-term so far, and I'm in a need of a break from those. But they were really invaluable to help me grow and learn a lot about how things actually work. I'm okay with the first two people now, maybe the third one can be my friend once more in the future as well. I'm sure I've told them how important they have been to me and my life.<p>I'm thinking about all the people who I've met online and have had overall a healthy relation, sharing interesting things between each other, playtime, thoughts, and other various lessons.<p>What I'm trying to get across is that you may be more impactful than you think you really are. And even if people don't say it, some are extremely thankful of your interactions with them. You can see this post as a sort of a "call for action" to be more thankful to the people that have affected you in important ways. If people take things for granted because they Just Work, the person who made those things should be proud of this achievement.<p>Thanks to all of them, to everyone who has shared good moments with me, and to all the people who enjoy the things I make.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Reflexión sobre la Inteligencia artificial | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog>blog</a><li><a href=/golb class=selected>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Reflexión sobre la Inteligencia artificial</h1><div class=time><p>2016-06-13</div><blockquote><p>Nota: esta reflexión ha sido sacada de una conversación en Telegram, aunque ha sido lo más adaptada posible a formato de blog.</blockquote><h2 id=conversacion-del-12-03-16>Conversación del 12.03.16</h2><p>Pienso que para conseguir una verdadera inteligencia artificial debemos abstraernos mucho. Es decir, siempre hay una pequeña parte de <em>Pero es que el ser humano, los sentimientos, tal, cual</em>... Igual simplemente, absolutamente todo esté programado. Cuando actúas de manera que no sabes por qué por ejemplo, seguramente sea una serie de estímulos adecuados que producen esa respuesta porque se ha formado ese camino de neuronas en tu mente. Por ejemplo, el arco reflejo, que es un arco innato.<ul><li><em>Sensación de quemar → retirar</em><li><em><a href=https://es.wikipedia.org/wiki/Condicionamiento_cl%C3%A1sico>Un estímulo X → una respuesta Y</a></em></ul><p>Es una ida y vuelta instantanea entre sensación y respuesta. El cerebro simplemente es capaz de trabajar con combinaciones más complejas, como por ejemplo la suma, este número con este otro → sale otro número, y se le añade a otro... Hay una especie de <a href=https://es.wikipedia.org/wiki/Recursi%C3%B3n>recursión</a> también, aunque en realidad es que es el mismo estímulo el que <a href=https://es.wikipedia.org/wiki/M%C3%A9todo_%28inform%C3%A1tica%29>"llama"</a> a un determinado camino.<p>Pero lo verdaderamente impresionante es la consciencia, igual no tenemos consciencia de verdad, igual es como lo sentimos. Siempre nos han hablado de la consciencia pero nadie ha sabido probarla, por lo que sólo tenemos una idea, un concepto. Es aún más impresionante es el hecho de recordar y <a href=https://es.wikipedia.org/wiki/Memoria_de_trabajo>trabajar</a> con la información.<p>Cuando hablamos de consciencia, estamos simplemente tratando información sobre esa misma información, ¿cómo cojones sentimos lo que pensamos? Yo sé que estoy pensando porque hemos definido <em>pensar</em> como este proceso. ¿Pero cómo coño entiendo yo eso? Es decir, ¿cómo me doy cuenta? por qué lo situo en mi cabeza? Probablemente, aunque el cerebro esté trabajando con todo eso, la sensación sea externa a nosotros, es decir, ocurre en mi cabeza. ¿Pero de verdad lo siento en mi cabeza?<p>Me estoy rayando.<p>El verdadero problema está en saber cómo sabemos que estamos pensando. El cerebro se compone de neuronas y conexiones, esa es la base a parte de donde se encuentra todo y tal la base es esa, y las sensaciones táctiles son igual de complejas, las procesa mi cerebro pero las siento en mi mano. ¿Será cosa de costumbre? Yo siento algo y lo situo ahí. Sin embargo sentimos ahí y no por encima o por debajo de, vamos a poner, los dedos de la mano, lo siento justo ahí. ¿Qué coño es realmente la <a href=https://es.wikipedia.org/wiki/Memoria_a_corto_plazo>memoria a corto plazo</a>? (porque lo de la <a href=https://es.wikipedia.org/wiki/Memoria_a_largo_plazo>memoria a largo plazo</a> se traslada ahí cuando la necesitamos para trabajar con ella, por eso es <em>MCP</em> o memoria de trabajo según ciertas teorías).<p>En fin.</main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,1 +0,0 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Sentences | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog>blog</a><li><a href=/golb class=selected>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Sentences</h1><div class=time><p>2018-01-31</div><blockquote><p>Don't know English? <a href=https://lonami.dev/golb/sentences/spanish.html>Read the Spanish version instead</a>.</blockquote><p>Just a few sentences that I've been gathering among the years and I think are worthy of being kept somewhere.<ul><li>So far, you've survived 100% of your worst days. You're doing great<li>Money is not a concern, perfection is<li>The only limit to our realization of tomorrow will be our doubts today<li>Not all cultures deserve respect.<li>It's not the same knowing that you're not free that not being free.<li>Being alive is not a biolgical matter, rather mental.<li>Every mountain starts off as a grain of sand.<li>Time goes against desire.<li>The only way to respect is fear.<li>For those who have nothing I have so much.<li>One isn't what they think, it's what they do.<li>When your goal seems impossible, don't change the goal. Find new ways to get to it.<li>Be the change you wish to see in the world.<li>Tell me something, I'll forget. Show me something, I'll remember. But make me part of it and I'll understand it.<li>If you want something to happen, go and do it. Or wait sitting like a stupid until it happens that it never happens.<li>I'd rather be happy than be right.<li>The engines don’t move the ship at all. The ship stays where it is and the engines move the universe around it.<li>If I never try getting there, I'll never get there.</ul></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
@@ -1,26 +0,0 @@
-+++ -title = "Frases" -date = 2018-01-31 -updated = 2018-01-31 -+++ - -Simplemente algunas frases que se han ido amontonando a lo largo de los años y que creo que merecen persistir en algún lugar. - -* Por el momento, has sobrevivido el 100% de tus peores días. Lo estás haciendo genial. -* El dinero no es un problema, pero la perfección sí. -* El único límite a nuestro desarrollo mañana serán nuestras dudas de hoy. -* No todas las culturas merecen respeto. -* No es lo mismo saber que no eres libre que no ser libre. -* Estar vivo ya no es tanto una cuestión biológica como mental. -* Toda montaña empieza con un grano de arena. -* El tiempo va contra el deseo. -* La única vía para el respeto es el miedo. -* Para los que no quieren nada tengo mucho. -* Uno no es lo que piensa. Es lo que hace. -* Cuando el objetivo te parezca imposible, no cambies de objetivo. Busca nuevas formas de llegar a él. -* Sé el cambio que deseas ver en el mundo. -* Cuéntame algo, lo olvidaré. Muéstrame algo, podré recordarlo. Sin embargo, implícame en ello y lo comprenderé. -* Si quieres que pase, ve y hazlo. O espera sentado como un subnormal a que pase para que no pase nunca. -* Prefiero ser feliz a llevar la razón. -* Los motores no mueven la nave en absoluto. La nave se queda donde está y los motores mueven el universo a su alrededor. -* Si nunca aspiro a llegar, nunca llegaré
@@ -1,13 +1,13 @@
/* TEAM */ - Chef, UI Developer, Illustrator, Standart Man & Web Designer: Lonami Exo - Contact: https://lonami.dev/contact - From: Spain + Web Designer: Marco Andronaco + Contact: andronacomarco@gmail.com + From: Italy /* SITE */ - Last update: 2019/01/16 + Last update: 09/04/2021 Language: English Doctype: HTML5 Standards: HTML5, CSS3 - IDE: nvim, git, Inkscape - Software: nvim, git, Inkscape + IDE: geany, vscode + Software: zola, git
@@ -1,14 +1,5 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Lonami's Site </title><link rel=stylesheet href=/style.css><style> -.golb { - transform: scaleY(-1); - transition: transform 300ms; -} - -.golb:hover { - transform: scaleY(1); -} -</style><body><article><nav class=sections><ul class=left><li><a href=/ class=selected>lonami's site</a><li><a href=/blog>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1>Lonami's Site</h1><p>Welcome to my personal website! This page has had several redesigns over time, but now I've decided to let it be as minimalist as possible (proud to be under 32KB!).<h2 id=about>About me</h2><p>Spanish male <span id=age><noscript>born in 1998</noscript></span>. I have been programming <span id=programming><noscript>since 2012</noscript></span> and it is my passion.<p>I enjoy nature, taking pictures, playing video-games, drawing vector graphics, or just chatting online.<p>I can speak perfect Spanish, read and write perfect English and Python, and have programmed in C#, Java, JavaScript, Rust, some C and C++, and designed pages like this with plain HTML and CSS.<p>On the Internet, I'm often known as <i>Lonami</i>, although my real name is simply my nick name, put the other way round.<h2 id=projects>Project highlights</h2><ul><li><a href=https://github.com/LonamiWebs/Telethon/>Telethon</a>: Python implementation of the Telegram's API.<li><a href=klooni>1010! Klooni</a>: libGDX simple puzzle game based on the original <i>1010!</i>.<li><a href=https://github.com/LonamiWebs/Stringlate/>Stringlate</a>: Android application that makes it easy to translate other FOSS apps.</ul><p>These are only my <i>Top 3</i> projects, the ones I consider to be the most successful. If you're curious about what else I've done, feel free to check out my <a href=https://github.com/LonamiWebs/>GitHub</a>.<h2 id=more-links>More links</h2><dl><dt><a href=https://t.me/LonamiWebs><img src=/img/telegram.svg> My Telegram</a><dd>Come meet me at my group in Telegram and talk about anything!<dt><a href=/blog><img src=/img/blog.svg> My blog</a><dd>Sometimes I blog about things, whether it's games, techy stuff, or random life stuff.<dt><a href=/golb><img src=/img/blog.svg class=golb> My golb</a><dd>What? You don't know what a golb is? It's like a blog, but less conventional.<dt><a href=https://github.com/LonamiWebs><img src=/img/github.svg> My GitHub</a><dd>By far what I'm most proud of. I love releasing my projects as open source. There is no reason not to!<dt><a href=/utils><img src=/img/utils.svg> Several Utilities</a><dd>Random things I've put online because I keep forgetting about them.<dt><a href=/stopwatch.html><img src=/stopwatch.svg width=24 height=24> stopwatch</a><dd>An extremely simple JavaScript-based stopwatch.<dt><a href=donate><img src=/img/bitcoin.svg> Donate</a><dd>Some people like what I do and want to compensate me for it, but I'm fine with compliments if you can't afford a donation!<dt><a href=humans.txt><img src=/img/humans.svg> humans.txt</a><dd><a href=http://humanstxt.org/>We are humans, not robots.</a></dl><h2 id=contact>Contact</h2><p>If you use Telegram you can join <a href=https://t.me/LonamiWebs>@LonamiWebs</a> and just chat about any topics you like politely.<p>If you prefer, you can also send me a private email to <a href=mailto:totufals@hotmail.com>totufals[at]hotmail[dot]com</a> and I will try to reply as soon as I can. Please don't use the email if you need help with a specific project, this is better discussed in the group where everyone can benefit from it.</p><script> +<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="BiRabittoh's official website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> BiRabittoh's Site </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/ class=selected>birabittoh's site</a><li><a href=/blog>blog</a></ul><div class=right><a href=https://github.com/Bi-Rabittoh><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1>BiRabittoh's Site</h1><p>Welcome to my personal website and blog! I'll try to post here about whatever comes across my mind in a specific moment, so that people may read it when I become famous in 80+ years or so.<h2 id=about>About me</h2><p>Italian male <span id=age><noscript>born in 1998</noscript></span>. I have been programming <span id=programming><noscript>since 2016</noscript></span> and I love it.<p>I enjoy art in every form, including video-games, as well as every kind of humor. I usually hang around on Discord while programming stuff I'll probably never actually get finished.<p>While Italian is my main language, I can read and write perfect English. I'm a fast learner on any kind of programming language as long as it follows human logic.<p>BiRabittoh comes from the japanese pronunciation of "B-Rabbit", as said in <i><a href=https://en.wikipedia.org/wiki/Pandora_Hearts>Pandora Hearts</a></i>, which was the first anime I watched on the internet.<h2 id=projects>Project contributions</h2><ul><li><a href=https://github.com/Render96/Render96ex>Render96ex</a>: writing a translation for this <i>Super Mario 64</i> port.<li><a href=https://github.com/nxengine/nxengine-evo>nxengine-evo</a>: fixing the italian translation for <i>Cave Story</i>.<li><a href=#>URhythm</a>: ???</ul><p>If you're curious about my other projects, feel free to check out my <a href=https://github.com/Bi-Rabittoh>GitHub</a>.<h2 id=contact>Contact</h2><p>If you use Discord you can add me (<a href=https://discordapp.com/users/132123740724002816/>Bi-Rabittoh#5566</a>) and chat about anything.<p>You can also send me an email to <a href=mailto:andronacomarco@gmail.com>andronacomarco@gmail.com</a> and I will try to reply as soon as I can.</p><script> now = (new Date()).getFullYear(); document.getElementById("age").innerHTML = "aged " + (now - 1999); - document.getElementById("programming").innerHTML = "for " + (now - 2012) + " years"; -</script></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!+ document.getElementById("programming").innerHTML = "for " + (now - 2016) + " years"; +</script></main><footer><div><p>Please use email for business inquiries <a href=mailto:andronacomarco@gmail.com><img src=/img/mail.svg alt=mail></a></div></footer></article><p class=abyss>owo what's this
@@ -1,56 +0,0 @@
-================================================================== -https://keybase.io/lonami --------------------------------------------------------------------- - -I hereby claim: - - * I am an admin of https://lonami.dev - * I am lonami (https://keybase.io/lonami) on keybase. - * I have a public key ASCoYKcU4x-Wl1k9AbYqLywxX2OUiE5XxHIfGn7Z17QheAo - -To do so, I am signing this object: - -{ - "body": { - "key": { - "eldest_kid": "0120a860a714e31f9697593d01b62a2f2c315f6394884e57c4721f1a7ed9d7b421780a", - "host": "keybase.io", - "kid": "0120a860a714e31f9697593d01b62a2f2c315f6394884e57c4721f1a7ed9d7b421780a", - "uid": "15c9efa5730081a74aa068919df4fa19", - "username": "lonami" - }, - "merkle_root": { - "ctime": 1560621172, - "hash": "e2f543034ee1d2b87c9aa74b056b2ce2e22f31c09b69f9eabb5f82aba8b71b2ba81768eda18b3fc26313b612fd21068a4af9cecbd433ed4f6444aa0597e571a3", - "hash_meta": "60bb690f4585484269e45d86d61670dbb53261b7aad9172342b6f4d940241c34", - "seqno": 5576906 - }, - "service": { - "entropy": "4YjcZ530ruXweakqancRWRoE", - "hostname": "lonami.dev", - "protocol": "https:" - }, - "type": "web_service_binding", - "version": 2 - }, - "client": { - "name": "keybase.io go client", - "version": "4.1.0" - }, - "ctime": 1560621178, - "expire_in": 504576000, - "prev": "7932ef5dd721a6b02c2bea3ba68d0e910b8ee41e4b37ac915deddc7e4888e8c9", - "seqno": 19, - "tag": "signature" -} - -which yields the signature: - -hKRib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEgqGCnFOMflpdZPQG2Ki8sMV9jlIhOV8RyHxp+2de0IXgKp3BheWxvYWTESpcCE8QgeTLvXdchprAsK+o7po0OkQuO5B5LN6yRXe3cfkiI6MnEIG0Tokn/VknUDnw80CKpdH9TIrVT5rJzcq5JJiv7TZz1AgHCo3NpZ8RAdBlhtMIzOfWS8Yhop+sQAGzIAW1qYhHanGkjAbN+9LxEVhwd2knyd0J9dAaLE/xlydqtZRb1qV1sRmMMWXBRC6hzaWdfdHlwZSCkaGFzaIKkdHlwZQildmFsdWXEIHGReKp8ETAF8wxr4VSUBHsS5DJ8Pz3hFFL4iLt5U/WJo3RhZ80CAqd2ZXJzaW9uAQ== - -And finally, I am proving ownership of this host by posting or -appending to this document. - -View my publicly-auditable identity here: https://keybase.io/lonami - -==================================================================
@@ -1,121 +0,0 @@
-@font-face { - /* http://www.dafont.com/nilland.font */ - font-family: Nilland; - src: url('../font/Nilland.ttf'); -} - -@font-face { - /* http://www.dafont.com/kirvy.font */ - font-family: Kirvy; - src: url('../font/Kirvy.otf'); -} - -::-moz-selection { - background-color: #FFAA00; - color: #000000; -} - -::selection { - background-color: #FFAA00; - color: #000000; -} - -body { - margin: 0; -} - -img { - vertical-align: middle; -} - -img.emoji { - display: inline-block; - float: none !important; - margin: 0 !important; - width: 32px; - height: 32px; -} - -h2, p { - text-shadow: 2px 2px 5px #000; -} - -a { - text-decoration: none; - color: #FFAA00; - transition: opacity 300ms; -} - -a:hover { - opacity: 0.8; -} - -.band { - width: 100%; - height: 500px; - background-color: #fff; - position: relative; - text-align: center; -} - -.band.header { - background: url('../img/header.svg') top center no-repeat; -} - -.band div { - display: table; - width: 100%; - max-width: 1024px; - height: 500px; - color: #fff; - margin: 0 auto; -} - -.band h2 { - font-family: Kirvy; - font-size: 24pt; - margin-top: 140px; - font-variant: small-caps; -} - -.band p { - font-family: Nilland; - font-size: 16pt; - line-height: 200%; - margin-top: 40px; -} - -.band img { - margin: 20px; - margin-top: 110px; - border-radius: 10px; -} - -.band.left img { - float: left; -} - -.band.right img { - float: right; -} - - -/* For mobile devices, don't show the images in two columns */ -@media (max-width:1000px) { - .band div { - height: auto; - } - - .band.left img, .band.right img { - margin: 20px; - float: none; - } - - .band h2 { - margin-top: 0; - } - - .band p { - margin-top: 20px; - } -}
@@ -1,1 +0,0 @@
-<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -1036.3622)"><ellipse cx="8" cy="1044.5884" fill="#ffeb3b" rx="7.8580317" ry="7.6318169"/><path d="m6.2064503 1036.6502a2.2755178 5.9051214 82.545344 0 0-5.23961846 3.0944 2.2755178 5.9051214 82.545344 0 0 6.42577886 1.3343 2.2755178 5.9051214 82.545344 0 0 5.2393563-3.0944 2.2755178 5.9051214 82.545344 0 0-6.4255167-1.3343zm.2359892.8811a1.3704478 4.1657823 82.202353 0 1 4.4730255.7122 1.3704478 4.1657823 82.202353 0 1-3.7588406 1.9543 1.3704478 4.1657823 82.202353 0 1-4.4730272-.7122 1.3704478 4.1657823 82.202353 0 1 3.7588423-1.9543z" fill="#00bcd4"/><path d="m7.9999345 1036.9567c-2.0768723 0-4.0686561.8015-5.539988 2.2251-.1060328.301.5823603.8624 1.0106317.8047 7.0811068-.9548 7.7697708-.6058 7.4282128-2.4846-.9221367-.358-1.9057838-.543-2.8988565-.5452z" fill="#ffeb3b" fill-rule="evenodd"/><path d="m4.7258196 1042.832c-.7671575 0-1.3890555.5285-1.3890771 1.1804.0004322.1384-.00259.5214.085814.4053.3167804-.4161.7200091-.7761 1.3032626-.7769.5829234.0005.9866463.3597 1.3035434.7755.08817.1157.084983-.266.085533-.4039-.0000216-.6519-.6219196-1.1804-1.389077-1.1804z" fill="#795548"/><path d="m11.274179 1042.832c-.767158 0-1.3890554.5285-1.389077 1.1804.0004322.1384-.00259.5214.085814.4053.316781-.4161.720009-.7761 1.303263-.7769.582924.0005.986646.3597 1.303544.7755.08817.1157.08498-.266.08553-.4039-.000022-.6519-.621919-1.1804-1.389077-1.1804z" fill="#795548"/><path d="m7.9999993 1049.4565c1.5105528 0 2.7350867-.5982 2.7351287-1.336-.000864-.1566.0051-.5903-.168971-.4588-.6237478.4711-1.417717.8784-2.5661577.8793-1.1477909 0-1.9427326-.4071-2.5667108-.8777-.1736083-.131-.1673346.3012-.1684179.4572.0000432.7378 1.2245759 1.336 2.7351287 1.336z" fill="#795548"/></g></svg>
@@ -1,1 +0,0 @@
-<svg height="256" viewBox="0 0 256 256" width="256" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -796.36216)"><path d="m1.8439354 798.20612h252.31212v252.31213h-252.31212z" fill="#fff"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="4" y="1025.3622"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="29" y="1025.3622"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="54" y="1025.3622"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="79" y="1025.3622"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="129" y="1025.3622"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="154" y="1025.3622"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="179" y="1025.3622"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="204.00002" y="1025.3622"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="104.00001" y="1025.3622"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="229" y="1025.3622"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="4" y="1000.3621"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="29" y="1000.3621"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="54" y="1000.3621"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="79" y="1000.3621"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="129" y="1000.3621"/><rect fill="#f1bc3a" height="23" ry="3.7123106" width="23" x="154" y="1000.3621"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="179" y="1000.3621"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="204.00002" y="1000.3621"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="104.00001" y="1000.3621"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="229" y="1000.3621"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="4" y="975.36206"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="29" y="975.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="54" y="975.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="79" y="975.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="129" y="975.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="154" y="975.36206"/><rect fill="#f1bc3a" height="23" ry="3.7123106" width="23" x="179" y="975.36206"/><rect fill="#f1bc3a" height="23" ry="3.7123106" width="23" x="204.00002" y="975.36206"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="104.00001" y="975.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="229" y="975.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="4" y="950.362"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="29" y="950.362"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="54" y="950.362"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="79" y="950.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="129" y="950.362"/><rect fill="#f1bc3a" height="23" ry="3.7123106" width="23" x="154" y="950.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="179" y="950.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="204.00002" y="950.362"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="104.00001" y="950.362"/><rect fill="#f1bc3a" height="23" ry="3.7123106" width="23" x="229" y="950.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="4" y="925.36212"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="29" y="925.36212"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="54" y="925.36212"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="79" y="925.36212"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="129" y="925.36212"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="154" y="925.36212"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="179" y="925.36212"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="204.00002" y="925.36212"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="104.00001" y="925.36212"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="229" y="925.36212"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="4" y="900.36206"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="29" y="900.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="54" y="900.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="79" y="900.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="129" y="900.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="154" y="900.36206"/><rect fill="#e08d44" height="23" ry="3.7123106" width="23" x="179" y="900.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="204.00002" y="900.36206"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="104.00001" y="900.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="229" y="900.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="4" y="875.36206"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="29" y="875.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="54" y="875.36206"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="79" y="875.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="129" y="875.36206"/><rect fill="#e08d44" height="23" ry="3.7123106" width="23" x="154" y="875.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="179" y="875.36206"/><rect fill="#e08d44" height="23" ry="3.7123106" width="23" x="204.00002" y="875.36206"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="104.00001" y="875.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="229" y="875.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="4" y="850.362"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="29" y="850.362"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="54" y="850.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="79" y="850.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="129" y="850.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="154" y="850.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="179" y="850.362"/><rect fill="#e08d44" height="23" ry="3.7123106" width="23" x="204.00002" y="850.362"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="104.00001" y="850.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="229" y="850.362"/><rect fill="#53c17d" height="23" ry="3.7123106" width="23" x="4" y="825.36206"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="29" y="825.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="54" y="825.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="79" y="825.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="129" y="825.36206"/><rect fill="#e08d44" height="23" ry="3.7123106" width="23" x="154" y="825.36206"/><rect fill="#e08d44" height="23" ry="3.7123106" width="23" x="179" y="825.36206"/><rect fill="#e08d44" height="23" ry="3.7123106" width="23" x="204.00002" y="825.36206"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="104.00001" y="825.36206"/><rect fill="#e08d44" height="23" ry="3.7123106" width="23" x="229" y="825.36206"/><rect fill="#53c17d" height="23" ry="3.7123106" width="23" x="4" y="800.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="29" y="800.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="54" y="800.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="79" y="800.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="129" y="800.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="154" y="800.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="179" y="800.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="204.00002" y="800.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="104.00001" y="800.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="229" y="800.36206"/></g></svg>
@@ -1,1 +0,0 @@
-<svg height="256" viewBox="0 0 256 256" width="256" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -796.36216)"><path d="m1.8439354 798.20612h252.31212v252.31213h-252.31212z" fill="#fff"/><rect fill="#55b4d6" height="23" ry="3.7123106" width="23" x="4" y="1025.3622"/><rect fill="#55b4d6" height="23" ry="3.7123106" width="23" x="29" y="1025.3622"/><rect fill="#55b4d6" height="23" ry="3.7123106" width="23" x="54" y="1025.3622"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="79" y="1025.3622"/><rect fill="#cf6050" height="23" ry="3.7123106" width="23" x="129" y="1025.3622"/><rect fill="#cf6050" height="23" ry="3.7123106" width="23" x="154" y="1025.3622"/><rect fill="#cf6050" height="23" ry="3.7123106" width="23" x="179" y="1025.3622"/><rect fill="#cf6050" height="23" ry="3.7123106" width="23" x="204.00002" y="1025.3622"/><rect fill="#cf6050" height="23" ry="3.7123106" width="23" x="104.00001" y="1025.3622"/><rect fill="#cf6050" height="23" ry="3.7123106" width="23" x="229" y="1025.3622"/><rect fill="#55b4d6" height="23" ry="3.7123106" width="23" x="4" y="1000.3621"/><rect fill="#53c17d" height="23" ry="3.7123106" width="23" x="29" y="1000.3621"/><rect fill="#53c17d" height="23" ry="3.7123106" width="23" x="54" y="1000.3621"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="79" y="1000.3621"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="129" y="1000.3621"/><rect fill="#e08d44" height="23" ry="3.7123106" width="23" x="154" y="1000.3621"/><rect fill="#e08d44" height="23" ry="3.7123106" width="23" x="179" y="1000.3621"/><rect fill="#e08d44" height="23" ry="3.7123106" width="23" x="204.00002" y="1000.3621"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="104.00001" y="1000.3621"/><rect fill="#cf6050" height="23" ry="3.7123106" width="23" x="229" y="1000.3621"/><rect fill="#55b4d6" height="23" ry="3.7123106" width="23" x="4" y="975.36206"/><rect fill="#53c17d" height="23" ry="3.7123106" width="23" x="29" y="975.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="54" y="975.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="79" y="975.36206"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="129" y="975.36206"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="154" y="975.36206"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="179" y="975.36206"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="204.00002" y="975.36206"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="104.00001" y="975.36206"/><rect fill="#cf6050" height="23" ry="3.7123106" width="23" x="229" y="975.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="4" y="950.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="29" y="950.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="54" y="950.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="79" y="950.362"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="129" y="950.362"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="154" y="950.362"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="179" y="950.362"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="204.00002" y="950.362"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="104.00001" y="950.362"/><rect fill="#cf6050" height="23" ry="3.7123106" width="23" x="229" y="950.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="4" y="925.36212"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="29" y="925.36212"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="54" y="925.36212"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="79" y="925.36212"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="129" y="925.36212"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="154" y="925.36212"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="179" y="925.36212"/><rect fill="#48c9a5" height="23" ry="3.7123106" width="23" x="204.00002" y="925.36212"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="104.00001" y="925.36212"/><rect fill="#cf6050" height="23" ry="3.7123106" width="23" x="229" y="925.36212"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="4" y="900.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="29" y="900.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="54" y="900.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="79" y="900.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="129" y="900.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="154" y="900.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="179" y="900.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="204.00002" y="900.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="104.00001" y="900.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="229" y="900.36206"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="4" y="875.36206"/><rect fill="#55b4d6" height="23" ry="3.7123106" width="23" x="29" y="875.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="54" y="875.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="79" y="875.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="129" y="875.36206"/><rect fill="#53c17d" height="23" ry="3.7123106" width="23" x="154" y="875.36206"/><rect fill="#53c17d" height="23" ry="3.7123106" width="23" x="179" y="875.36206"/><rect fill="#55b4d6" height="23" ry="3.7123106" width="23" x="204.00002" y="875.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="104.00001" y="875.36206"/><rect fill="#f1bc3a" height="23" ry="3.7123106" width="23" x="229" y="875.36206"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="4" y="850.362"/><rect fill="#55b4d6" height="23" ry="3.7123106" width="23" x="29" y="850.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="54" y="850.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="79" y="850.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="129" y="850.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="154" y="850.362"/><rect fill="#53c17d" height="23" ry="3.7123106" width="23" x="179" y="850.362"/><rect fill="#55b4d6" height="23" ry="3.7123106" width="23" x="204.00002" y="850.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="104.00001" y="850.362"/><rect fill="#f1bc3a" height="23" ry="3.7123106" width="23" x="229" y="850.362"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="4" y="825.36206"/><rect fill="#53c17d" height="23" ry="3.7123106" width="23" x="29" y="825.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="54" y="825.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="79" y="825.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="129" y="825.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="154" y="825.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="179" y="825.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="204.00002" y="825.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="104.00001" y="825.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="229" y="825.36206"/><rect fill="#da657b" height="23" ry="3.7123106" width="23" x="4" y="800.36206"/><rect fill="#53c17d" height="23" ry="3.7123106" width="23" x="29" y="800.36206"/><rect fill="#53c17d" height="23" ry="3.7123106" width="23" x="54" y="800.36206"/><rect fill="#7381b5" height="23" ry="3.7123106" width="23" x="79" y="800.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="129" y="800.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="154" y="800.36206"/><rect fill="#e08d44" height="23" ry="3.7123106" width="23" x="179" y="800.36206"/><rect fill="#e08d44" height="23" ry="3.7123106" width="23" x="204.00002" y="800.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="104.00001" y="800.36206"/><rect fill="#e08d44" height="23" ry="3.7123106" width="23" x="229" y="800.36206"/><g transform="matrix(.97656252 0 0 .97656252 151.21587 -124.47349)"><rect fill="#f1bc3a" height="23.552" ry="3.8014059" width="23.552" x="-85.937706" y="1008.2969"/><rect fill="#f1bc3a" height="23.552" ry="3.8014059" width="23.552" x="-85.937706" y="982.69684"/><path d="m-63.396484 1019.4668c-.403493-.022-.715155.024-.923828.1445-3.338799 1.9277-3.338799 36.6251 0 38.5528 1.423092.8216 7.704486-1.8689 14.4375-5.5137l10.880859 18.8457c1.05299 1.8238 3.369526 2.4436 5.193359 1.3906l2.013672-1.164c1.823833-1.053 2.445569-3.3676 1.392578-5.1915l-10.914062-18.9042c5.784742-3.6696 10.382812-7.2347 10.382812-8.7403 0-3.6143-26.410502-19.0881-32.46289-19.4199z" fill="#4d4d4d"/></g></g></svg>
@@ -1,1 +0,0 @@
-<svg height="256" viewBox="0 0 256 256" width="256" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><filter id="a" color-interpolation-filters="sRGB" height="1.1421829" width="1.0254692" x="-.0127346" y="-.07109144"><feGaussianBlur stdDeviation=".872055"/></filter><g transform="translate(0 -796.36216)"><path d="m1.8439354 798.20612h252.31212v252.31213h-252.31212z" fill="#fff"/><rect fill="#cf6050" height="23" ry="3.7123106" width="23" x="4" y="1025.3622"/><rect fill="#cf6050" height="23" ry="3.7123106" width="23" x="29" y="1025.3622"/><rect fill="#500" height="23" ry="3.7123106" width="23" x="79" y="1025.3622"/><rect fill="#06f" height="23" ry="3.7123106" width="23" x="129" y="1025.3622"/><rect fill="#06f" height="23" ry="3.7123106" width="23" x="154" y="1025.3622"/><rect fill="#1a1a1a" height="23" ry="3.7123106" width="23" x="179" y="1025.3622"/><rect fill="#1a1a1a" height="23" ry="3.7123106" width="23" x="204.00002" y="1025.3622"/><rect fill="#06f" height="23" ry="3.7123106" width="23" x="104.00001" y="1025.3622"/><rect fill="#1a1a1a" height="23" ry="3.7123106" width="23" x="229" y="1025.3622"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="4" y="1000.3621"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="29" y="1000.3621"/><rect fill="#333" height="23" ry="3.7123106" width="23" x="79" y="1000.3621"/><rect fill="#87cdde" height="23" ry="3.7123106" width="23" x="129" y="1000.3621"/><rect fill="#3771c8" height="23" ry="3.7123106" width="23" x="154" y="1000.3621"/><rect fill="#1a1a1a" height="23" ry="3.7123106" width="23" x="179" y="1000.3621"/><rect fill="#ccc" height="23" ry="3.7123106" width="23" x="204.00002" y="1000.3621"/><rect fill="#87cdde" height="23" ry="3.7123106" width="23" x="104.00001" y="1000.3621"/><rect fill="#ccc" height="23" ry="3.7123106" width="23" x="229" y="1000.3621"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="4" y="975.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="29" y="975.36206"/><rect fill="#052" height="3.6222653" ry=".58465105" width="3.6222653" x="63.688866" y="985.0509"/><rect fill="#052" height="23" ry="3.7123106" width="23" x="79" y="975.36206"/><rect fill="#87cdde" height="23" ry="3.7123106" width="23" x="129" y="975.36206"/><rect fill="#3771c8" height="23" ry="3.7123106" width="23" x="154" y="975.36206"/><rect fill="#ccc" height="23" ry="3.7123106" width="23" x="179" y="975.36206"/><rect fill="#ccc" height="23" ry="3.7123106" width="23" x="204.00002" y="975.36206"/><rect fill="#87cdde" height="23" ry="3.7123106" width="23" x="104.00001" y="975.36206"/><rect fill="#ccc" height="23" ry="3.7123106" width="23" x="229" y="975.36206"/><rect fill="#7381b5" height="23" ry="3.7123106" width="23" x="4" y="950.362"/><rect fill="#7381b5" height="23" ry="3.7123106" width="23" x="29" y="950.362"/><rect fill="#052" height="9.158761" ry="1.478268" width="9.158761" x="60.92062" y="957.28259"/><rect fill="#052" height="23" ry="3.7123106" width="23" x="79" y="950.362"/><rect fill="#0fc" height="23" ry="3.7123106" width="23" x="129" y="950.362"/><rect fill="#0fc" height="23" ry="3.7123106" width="23" x="154" y="950.362"/><rect fill="#1a1a1a" height="23" ry="3.7123106" width="23" x="179" y="950.362"/><rect fill="#1a1a1a" height="23" ry="3.7123106" width="23" x="204.00002" y="950.362"/><rect fill="#0fc" height="23" ry="3.7123106" width="23" x="104.00001" y="950.362"/><rect fill="#1a1a1a" height="23" ry="3.7123106" width="23" x="229" y="950.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="4" y="925.36212"/><rect fill="#e08d44" height="23" ry="3.7123106" width="23" x="29" y="925.36212"/><rect fill="#333" height="18.386253" ry="2.9676297" width="18.386253" x="56.306873" y="927.66901"/><rect fill="#333" height="23" ry="3.7123106" width="23" x="79" y="925.36212"/><rect fill="#0fc" height="23" ry="3.7123106" width="23" x="129" y="925.36212"/><rect fill="#0fc" height="23" ry="3.7123106" width="23" x="154" y="925.36212"/><rect fill="#1a1a1a" height="23" ry="3.7123106" width="23" x="179" y="925.36212"/><rect fill="#ccc" height="23" ry="3.7123106" width="23" x="204.00002" y="925.36212"/><rect fill="#0fc" height="23" ry="3.7123106" width="23" x="104.00001" y="925.36212"/><rect fill="#1a1a1a" height="23" ry="3.7123106" width="23" x="229" y="925.36212"/><rect fill="#53c17d" height="23" ry="3.7123106" width="23" x="4" y="900.36206"/><rect fill="#e08d44" height="23" ry="3.7123106" width="23" x="29" y="900.36206"/><rect fill="#333" height="23" ry="3.7123106" width="23" x="54" y="900.36206"/><rect fill="#a80" height="23" ry="3.7123106" width="23" x="79" y="900.36206"/><rect fill="#0fc" height="23" ry="3.7123106" width="23" x="129" y="900.36206"/><rect fill="#0fc" height="23" ry="3.7123106" width="23" x="154" y="900.36206"/><rect fill="#1a1a1a" height="23" ry="3.7123106" width="23" x="179" y="900.36206"/><rect fill="#1a1a1a" height="23" ry="3.7123106" width="23" x="204.00002" y="900.36206"/><rect fill="#0fc" height="23" ry="3.7123106" width="23" x="104.00001" y="900.36206"/><rect fill="#1a1a1a" height="23" ry="3.7123106" width="23" x="229" y="900.36206"/><rect fill="#53c17d" height="23" ry="3.7123106" width="23" x="4" y="875.36206"/><rect fill="#53c17d" height="23" ry="3.7123106" width="23" x="29" y="875.36206"/><rect fill="#333" height="23" ry="3.7123106" width="23" x="54" y="875.36206"/><rect fill="#a80" height="23" ry="3.7123106" width="23" x="79" y="875.36206"/><rect fill="#87cdde" height="23" ry="3.7123106" width="23" x="129" y="875.36206"/><rect fill="#87cdde" height="23" ry="3.7123106" width="23" x="154" y="875.36206"/><rect fill="#ccc" height="23" ry="3.7123106" width="23" x="179" y="875.36206"/><rect fill="#ccc" height="23" ry="3.7123106" width="23" x="204.00002" y="875.36206"/><rect fill="#87cdde" height="23" ry="3.7123106" width="23" x="104.00001" y="875.36206"/><rect fill="#ccc" height="23" ry="3.7123106" width="23" x="229" y="875.36206"/><rect fill="#55b4d6" height="23" ry="3.7123106" width="23" x="4" y="850.362"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="29" y="850.362"/><rect fill="#333" height="23" ry="3.7123106" width="23" x="54" y="850.362"/><rect fill="#a80" height="23" ry="3.7123106" width="23" x="79" y="850.362"/><rect fill="#87cdde" height="23" ry="3.7123106" width="23" x="129" y="850.362"/><rect fill="#87cdde" height="23" ry="3.7123106" width="23" x="154" y="850.362"/><rect fill="#1a1a1a" height="23" ry="3.7123106" width="23" x="179" y="850.362"/><rect fill="#1a1a1a" height="23" ry="3.7123106" width="23" x="204.00002" y="850.362"/><rect fill="#87cdde" height="23" ry="3.7123106" width="23" x="104.00001" y="850.362"/><rect fill="#1a1a1a" height="23" ry="3.7123106" width="23" x="229" y="850.362"/><rect fill="#55b4d6" height="23" ry="3.7123106" width="23" x="4" y="825.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="29" y="825.36206"/><rect fill="#333" height="17.127659" ry="2.7644866" width="17.127659" x="56.936169" y="828.29822"/><rect fill="#a80" height="23" ry="3.7123106" width="23" x="79" y="825.36206"/><rect fill="#0055d4" height="23" ry="3.7123106" width="23" x="129" y="825.36206"/><rect fill="#0055d4" height="23" ry="3.7123106" width="23" x="154" y="825.36206"/><rect fill="#1a1a1a" height="23" ry="3.7123106" width="23" x="179" y="825.36206"/><rect fill="#ccc" height="23" ry="3.7123106" width="23" x="204.00002" y="825.36206"/><rect fill="#87cdde" height="23" ry="3.7123106" width="23" x="104.00001" y="825.36206"/><rect fill="#ccc" height="23" ry="3.7123106" width="23" x="229" y="825.36206"/><rect fill="#55b4d6" height="23" ry="3.7123106" width="23" x="4" y="800.36206"/><rect fill="#f2f2f2" height="23" ry="3.7123106" width="23" x="29" y="800.36206"/><rect fill="#333" height="13.212766" ry="2.1326039" width="13.212766" x="58.893616" y="805.25568"/><rect fill="#a80" height="23" ry="3.7123106" width="23" x="79" y="800.36206"/><rect fill="#0055d4" height="23" ry="3.7123106" width="23" x="129" y="800.36206"/><rect fill="#37abc8" height="23" ry="3.7123106" width="23" x="154" y="800.36206"/><rect fill="#ccc" height="23" ry="3.7123106" width="23" x="179" y="800.36206"/><rect fill="#ccc" height="23" ry="3.7123106" width="23" x="204.00002" y="800.36206"/><rect fill="#87cdde" height="23" ry="3.7123106" width="23" x="104.00001" y="800.36206"/><rect fill="#ccc" height="23" ry="3.7123106" width="23" x="229" y="800.36206"/><g transform="matrix(.83591472 -.48597892 .48597892 .83591472 -688.29402 299.05674)"><path d="m397.2578 948.4531c-2.83872 1.31018-21.24534-19.0488-24.31201-19.6574-3.06668-.6086-27.85289 11.17849-29.97615 8.88357-2.12327-2.29491 11.55131-26.09192 11.18247-29.19656-.36884-3.10465-19.23838-23.03533-17.71192-25.76384 1.52647-2.72851 28.38445 2.92311 31.22316 1.61293 2.83872-1.31018 15.96291-25.4151 19.02958-24.8065 3.06668.6086 5.99124 27.8985 8.1145 30.19341 2.12327 2.29492 29.10401 7.32793 29.47285 10.43258.36884 3.10465-24.68166 14.31911-26.20812 17.04762-1.52647 2.72851 2.02435 29.94401-.81436 31.25419z" fill="#ff2a2a"/><path d="m394.32624 944.56071c-2.28526 1.05473-16.36425-16.42565-18.87071-16.65485-2.50646-.22921-19.5224 14.40771-21.57846 12.95599-2.05605-1.45173 6.04291-22.38468 4.98818-24.66994-1.05474-2.28526-22.23865-9.70304-22.00945-12.2095.2292-2.50646 22.40715-5.95902 23.85888-8.01508 1.45172-2.05606-2.71625-24.11075-.43099-25.16549 2.28526-1.05473 16.36424 16.42565 18.8707 16.65485 2.50646.22921 19.5224-14.40771 21.57846-12.95599 2.05606 1.45173-6.04291 22.38468-4.98817 24.66994 1.05473 2.28526 22.23865 9.70303 22.00945 12.20949-.22921 2.50646-22.40716 5.95903-23.85888 8.01509-1.45173 2.05606 2.71625 24.11075.43099 25.16549z" fill="#ff7f2a" transform="matrix(.4060959 -.86054354 .86054354 .4060959 -554.98762 861.9104)"/><path d="m394.32624 944.56071c-2.1788 1.00559-14.95481-16.53387-17.35415-16.5732-2.39934-.0393-15.74337 17.07195-17.88804 15.99548-2.14466-1.07647 3.60252-22.00083 2.13731-23.90124-1.46521-1.9004-23.16322-1.66447-23.65878-4.01241-.49555-2.34793 19.44709-10.90073 20.01934-13.23116.57224-2.33043-13.1407-19.14752-11.61398-20.99887 1.52671-1.85136 20.64759 8.40785 22.82639 7.40225 2.17879-1.0056 6.77704-22.21209 9.17638-22.17276 2.39934.0393 6.30004 21.38515 8.44471 22.46162 2.14466 1.07647 21.59153-8.55051 23.05674-6.65011 1.46521 1.9004-12.79157 18.259-12.29601 20.60693.49556 2.34793 20.14715 11.54979 19.5749 13.88022-.57224 2.33043-22.25086 1.38344-23.77758 3.2348-1.52671 1.85135 3.53156 22.95285 1.35277 23.95845z" fill="#ffd42a" transform="matrix(-.07318693 -.59956127 .59956127 -.07318693 -137.92789 1196.4736)"/></g><g transform="matrix(.94054186 -.3396778 .3396778 .94054186 -541.22159 -72.93379)"><g fill-opacity=".25568183" filter="url(#a)"><path d="m232.27348 1164.9911h10v6.52h-16.56v-29.44h6.56z"/><path d="m264.5191 1148.6311h-12.48v4.92h12.48v6.52h-12.48v4.92h12.48v6.52h-14.96l0 0h-4.08v-29.44h6.56v.04h12.48z"/><path d="m284.66473 1142.1111h6.72l-6.96 29.4h-9.76l-6.92-29.4h6.72l5.08 21.6z"/><path d="m313.65973 1148.6311h-12.48v4.92h12.48v6.52h-12.48v4.92h12.48v6.52h-14.96l0 0h-4.08v-29.44h6.56v.04h12.48z"/><path d="m323.44535 1164.9911h10v6.52h-16.56v-29.44h6.56z"/><path d="m355.0766 1171.5111q-2.32 0-4.36-.88-2-.88-3.52-2.4-1.52-1.52-2.4-3.52-.88-2.04-.88-4.36v-18.24h6.56v18.24q0 .96.36 1.8.36.84 1 1.48.64.64 1.44 1 .84.36 1.8.36.96 0 1.8-.36.84-.36 1.48-1 .64-.64 1-1.48.36-.84.36-1.8v-18.24h6.52v18.24q0 2.32-.88 4.36-.88 2-2.4 3.52-1.52 1.52-3.56 2.4-2 .88-4.32.88z"/><path d="m379.26348 1142.1111q2.24 0 4.2.84 1.96.84 3.44 2.32 1.48 1.48 2.32 3.44.84 1.96.84 4.16 0 2.24-.84 4.2-.84 1.96-2.32 3.44-1.48 1.48-3.44 2.32-1.96.84-4.2.84h-3.24v7.84h-6.56v-29.4zm0 15.04q.88 0 1.64-.32.8-.36 1.36-.92.6-.6.92-1.36.36-.8.36-1.68 0-.88-.36-1.64-.32-.76-.92-1.32-.56-.6-1.36-.92-.76-.36-1.64-.36h-3.24v8.52z"/></g><g fill="#fff" transform="matrix(.9318622 0 0 .9318622 20.978842 78.821195)"><path d="m232.27348 1164.9911h10v6.52h-16.56v-29.44h6.56z" fill="#fff"/><path d="m264.5191 1148.6311h-12.48v4.92h12.48v6.52h-12.48v4.92h12.48v6.52h-14.96l0 0h-4.08v-29.44h6.56v.04h12.48z" fill="#fff"/><path d="m284.66473 1142.1111h6.72l-6.96 29.4h-9.76l-6.92-29.4h6.72l5.08 21.6z" fill="#fff"/><path d="m313.65973 1148.6311h-12.48v4.92h12.48v6.52h-12.48v4.92h12.48v6.52h-14.96l0 0h-4.08v-29.44h6.56v.04h12.48z" fill="#fff"/><path d="m323.44535 1164.9911h10v6.52h-16.56v-29.44h6.56z" fill="#fff"/><path d="m355.0766 1171.5111q-2.32 0-4.36-.88-2-.88-3.52-2.4-1.52-1.52-2.4-3.52-.88-2.04-.88-4.36v-18.24h6.56v18.24q0 .96.36 1.8.36.84 1 1.48.64.64 1.44 1 .84.36 1.8.36.96 0 1.8-.36.84-.36 1.48-1 .64-.64 1-1.48.36-.84.36-1.8v-18.24h6.52v18.24q0 2.32-.88 4.36-.88 2-2.4 3.52-1.52 1.52-3.56 2.4-2 .88-4.32.88z" fill="#ff0"/><path d="m379.26348 1142.1111q2.24 0 4.2.84 1.96.84 3.44 2.32 1.48 1.48 2.32 3.44.84 1.96.84 4.16 0 2.24-.84 4.2-.84 1.96-2.32 3.44-1.48 1.48-3.44 2.32-1.96.84-4.2.84h-3.24v7.84h-6.56v-29.4zm0 15.04q.88 0 1.64-.32.8-.36 1.36-.92.6-.6.92-1.36.36-.8.36-1.68 0-.88-.36-1.64-.32-.76-.92-1.32-.56-.6-1.36-.92-.76-.36-1.64-.36h-3.24v8.52z" fill="#ff0"/></g></g></g></svg>
@@ -1,1 +0,0 @@
-<svg height="500" viewBox="0 0 1366 500" width="1366" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="-440.1731564" x2="-440.1731564" y1="1002.751495" y2="1062.271995"><stop offset="0" stop-color="#005497" stop-opacity=".27272728"/><stop offset="1" stop-color="#005497" stop-opacity=".06818182"/></linearGradient><filter id="b" color-interpolation-filters="sRGB" height="1.1907477" width="1.0800771" x="-.04003854" y="-.09537383"><feGaussianBlur stdDeviation="3.6669538"/></filter><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="-440.1731564" x2="-440.1731564" y1="1002.751495" y2="1062.271995"><stop offset="0" stop-color="#ff6e07" stop-opacity=".27450982"/><stop offset="1" stop-color="#ff541c" stop-opacity=".06666667"/></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="-440.1731564" x2="-440.1731564" y1="1002.751495" y2="1062.271995"><stop offset="0" stop-color="#ff5497" stop-opacity=".27450982"/><stop offset="1" stop-color="#ff5497" stop-opacity=".06666667"/></linearGradient><g transform="translate(0 -552.36216)"><g transform="matrix(.96335893 0 0 .96335893 598.31073427 -80.34141858)"><path d="m-442.65137 1055.1932c-5.37558-.0004-10.75247-.4342-14.87109-1.3009l-84.63672-17.8104c-1.65176-.3475-2.95609-.7386-3.94531-1.154-3.71453-1.1985-6.62987-3.2201-7.13477-4.9803-.41372-.4331-.64391-.9429-.63281-1.5313l.69922-37.06c.054-2.86135 5.52976-6.35712 12.27734-7.8371l89.12695-19.54942c1.48558-.32573 2.89873-.51836 4.20899-.60182 3.99244-.574 8.30073-.65058 11.66992.11216l88.56445 20.04642c6.90343 1.56259 12.50464 5.19835 12.5586 8.15116l.69922 38.2432c.0315 1.7289-1.90581 3.6797-5.14063 5.0001-.19992.086-.41416.1725-.64258.2558-.016 0-.0327.022-.0488.022-.90767.3313-2.01326.6462-3.36329.9301l-84.51953 17.7707c-4.11948.8661-9.49355 1.2992-14.86914 1.2988z" fill="url(#a)" filter="url(#b)" transform="matrix(.84718319 0 0 1.0213689 -103.68658 -169.01715)"/><path d="m-397.75084 715.67331l-163.78868 1.15534c-6.94231.049-14.10792 13.10451-10.71655 19.16231l80.79533 144.31978c3.39136 6.05781 12.65442 10.6298 18.54535 6.9562l75.57536-47.12863c5.89092-3.67357 10.6705-12.21995 10.71655-19.16229l.59631-89.90907c.046-6.94233-4.78135-15.44261-11.72367-15.39364z" fill="#00aad4"/><rect fill="#0cf" height="111.41566" ry="12.143711" transform="matrix(.85586624 .51719723 .00684405 .99997658 0 0)" width="113.15113" x="-678.87305" y="1073.5972"/><rect fill="#5df" height="112.81577" ry="14.66605" transform="matrix(.86530322 .50124878 -.86512318 .50155946 0 0)" width="112.68221" x="388.7005" y="942.09778"/></g><g transform="matrix(.96335893 0 0 .96335893 1533.71194427 -84.38202958)"><path d="m-442.65137 1055.1932c-5.37558-.0004-10.75247-.4342-14.87109-1.3009l-84.63672-17.8104c-1.65176-.3475-2.95609-.7386-3.94531-1.154-3.71453-1.1985-6.62987-3.2201-7.13477-4.9803-.41372-.4331-.64391-.9429-.63281-1.5313l.69922-37.06c.054-2.86135 5.52976-6.35712 12.27734-7.8371l89.12695-19.54942c1.48558-.32573 2.89873-.51836 4.20899-.60182 3.99244-.574 8.30073-.65058 11.66992.11216l88.56445 20.04642c6.90343 1.56259 12.50464 5.19835 12.5586 8.15116l.69922 38.2432c.0315 1.7289-1.90581 3.6797-5.14063 5.0001-.19992.086-.41416.1725-.64258.2558-.016 0-.0327.022-.0488.022-.90767.3313-2.01326.6462-3.36329.9301l-84.51953 17.7707c-4.11948.8661-9.49355 1.2992-14.86914 1.2988z" fill="url(#c)" filter="url(#b)" transform="matrix(.84718319 0 0 1.0213689 -103.68658 -169.01715)"/><path d="m-397.75084 715.67331l-163.78868 1.15534c-6.94231.049-14.10792 13.10451-10.71655 19.16231l80.79533 144.31978c3.39136 6.05781 12.65442 10.6298 18.54535 6.9562l75.57536-47.12863c5.89092-3.67357 10.6705-12.21995 10.71655-19.16229l.59631-89.90907c.046-6.94233-4.78135-15.44261-11.72367-15.39364z" fill="#e67e22"/><rect fill="#ffa522" height="111.41566" ry="12.143711" transform="matrix(.85586624 .51719723 .00684405 .99997658 0 0)" width="113.15113" x="-678.87305" y="1073.5972"/><rect fill="#ffc647" height="112.81577" ry="14.66605" transform="matrix(.86530322 .50124878 -.86512318 .50155946 0 0)" width="112.68221" x="388.7005" y="942.09778"/></g><g transform="matrix(.96335893 0 0 .96335893 1685.23494427 -15.69165758)"><path d="m-442.65137 1055.1932c-5.37558-.0004-10.75247-.4342-14.87109-1.3009l-84.63672-17.8104c-1.65176-.3475-2.95609-.7386-3.94531-1.154-3.71453-1.1985-6.62987-3.2201-7.13477-4.9803-.41372-.4331-.64391-.9429-.63281-1.5313l.69922-37.06c.054-2.86135 5.52976-6.35712 12.27734-7.8371l89.12695-19.54942c1.48558-.32573 2.89873-.51836 4.20899-.60182 3.99244-.574 8.30073-.65058 11.66992.11216l88.56445 20.04642c6.90343 1.56259 12.50464 5.19835 12.5586 8.15116l.69922 38.2432c.0315 1.7289-1.90581 3.6797-5.14063 5.0001-.19992.086-.41416.1725-.64258.2558-.016 0-.0327.022-.0488.022-.90767.3313-2.01326.6462-3.36329.9301l-84.51953 17.7707c-4.11948.8661-9.49355 1.2992-14.86914 1.2988z" fill="url(#c)" filter="url(#b)" transform="matrix(.84718319 0 0 1.0213689 -103.68658 -169.01715)"/><path d="m-397.75084 715.67331l-163.78868 1.15534c-6.94231.049-14.10792 13.10451-10.71655 19.16231l80.79533 144.31978c3.39136 6.05781 12.65442 10.6298 18.54535 6.9562l75.57536-47.12863c5.89092-3.67357 10.6705-12.21995 10.71655-19.16229l.59631-89.90907c.046-6.94233-4.78135-15.44261-11.72367-15.39364z" fill="#e67e22"/><rect fill="#ffa522" height="111.41566" ry="12.143711" transform="matrix(.85586624 .51719723 .00684405 .99997658 0 0)" width="113.15113" x="-678.87305" y="1073.5972"/><rect fill="#ffc647" height="112.81577" ry="14.66605" transform="matrix(.86530322 .50124878 -.86512318 .50155946 0 0)" width="112.68221" x="388.7005" y="942.09778"/></g><g transform="matrix(.96335893 0 0 .96335893 1539.77294427 57.03932642)"><path d="m-442.65137 1055.1932c-5.37558-.0004-10.75247-.4342-14.87109-1.3009l-84.63672-17.8104c-1.65176-.3475-2.95609-.7386-3.94531-1.154-3.71453-1.1985-6.62987-3.2201-7.13477-4.9803-.41372-.4331-.64391-.9429-.63281-1.5313l.69922-37.06c.054-2.86135 5.52976-6.35712 12.27734-7.8371l89.12695-19.54942c1.48558-.32573 2.89873-.51836 4.20899-.60182 3.99244-.574 8.30073-.65058 11.66992.11216l88.56445 20.04642c6.90343 1.56259 12.50464 5.19835 12.5586 8.15116l.69922 38.2432c.0315 1.7289-1.90581 3.6797-5.14063 5.0001-.19992.086-.41416.1725-.64258.2558-.016 0-.0327.022-.0488.022-.90767.3313-2.01326.6462-3.36329.9301l-84.51953 17.7707c-4.11948.8661-9.49355 1.2992-14.86914 1.2988z" fill="url(#c)" filter="url(#b)" transform="matrix(.84718319 0 0 1.0213689 -103.68658 -169.01715)"/><path d="m-397.75084 715.67331l-163.78868 1.15534c-6.94231.049-14.10792 13.10451-10.71655 19.16231l80.79533 144.31978c3.39136 6.05781 12.65442 10.6298 18.54535 6.9562l75.57536-47.12863c5.89092-3.67357 10.6705-12.21995 10.71655-19.16229l.59631-89.90907c.046-6.94233-4.78135-15.44261-11.72367-15.39364z" fill="#e67e22"/><rect fill="#ffa522" height="111.41566" ry="12.143711" transform="matrix(.85586624 .51719723 .00684405 .99997658 0 0)" width="113.15113" x="-678.87305" y="1073.5972"/><rect fill="#ffc647" height="112.81577" ry="14.66605" transform="matrix(.86530322 .50124878 -.86512318 .50155946 0 0)" width="112.68221" x="388.7005" y="942.09778"/></g><g transform="matrix(.96335893 0 0 .96335893 594.27012427 46.93779242)"><path d="m-442.65137 1055.1932c-5.37558-.0004-10.75247-.4342-14.87109-1.3009l-84.63672-17.8104c-1.65176-.3475-2.95609-.7386-3.94531-1.154-3.71453-1.1985-6.62987-3.2201-7.13477-4.9803-.41372-.4331-.64391-.9429-.63281-1.5313l.69922-37.06c.054-2.86135 5.52976-6.35712 12.27734-7.8371l89.12695-19.54942c1.48558-.32573 2.89873-.51836 4.20899-.60182 3.99244-.574 8.30073-.65058 11.66992.11216l88.56445 20.04642c6.90343 1.56259 12.50464 5.19835 12.5586 8.15116l.69922 38.2432c.0315 1.7289-1.90581 3.6797-5.14063 5.0001-.19992.086-.41416.1725-.64258.2558-.016 0-.0327.022-.0488.022-.90767.3313-2.01326.6462-3.36329.9301l-84.51953 17.7707c-4.11948.8661-9.49355 1.2992-14.86914 1.2988z" fill="url(#a)" filter="url(#b)" transform="matrix(.84718319 0 0 1.0213689 -103.68658 -169.01715)"/><path d="m-397.75084 715.67331l-163.78868 1.15534c-6.94231.049-14.10792 13.10451-10.71655 19.16231l80.79533 144.31978c3.39136 6.05781 12.65442 10.6298 18.54535 6.9562l75.57536-47.12863c5.89092-3.67357 10.6705-12.21995 10.71655-19.16229l.59631-89.90907c.046-6.94233-4.78135-15.44261-11.72367-15.39364z" fill="#00aad4"/><rect fill="#0cf" height="111.41566" ry="12.143711" transform="matrix(.85586624 .51719723 .00684405 .99997658 0 0)" width="113.15113" x="-678.87305" y="1073.5972"/><rect fill="#5df" height="112.81577" ry="14.66605" transform="matrix(.86530322 .50124878 -.86512318 .50155946 0 0)" width="112.68221" x="388.7005" y="942.09778"/></g><g transform="matrix(.96335893 0 0 .96335893 757.22157427 119.81236042)"><path d="m-442.65137 1055.1932c-5.37558-.0004-10.75247-.4342-14.87109-1.3009l-84.63672-17.8104c-1.65176-.3475-2.95609-.7386-3.94531-1.154-3.71453-1.1985-6.62987-3.2201-7.13477-4.9803-.41372-.4331-.64391-.9429-.63281-1.5313l.69922-37.06c.054-2.86135 5.52976-6.35712 12.27734-7.8371l89.12695-19.54942c1.48558-.32573 2.89873-.51836 4.20899-.60182 3.99244-.574 8.30073-.65058 11.66992.11216l88.56445 20.04642c6.90343 1.56259 12.50464 5.19835 12.5586 8.15116l.69922 38.2432c.0315 1.7289-1.90581 3.6797-5.14063 5.0001-.19992.086-.41416.1725-.64258.2558-.016 0-.0327.022-.0488.022-.90767.3313-2.01326.6462-3.36329.9301l-84.51953 17.7707c-4.11948.8661-9.49355 1.2992-14.86914 1.2988z" fill="url(#a)" filter="url(#b)" transform="matrix(.84718319 0 0 1.0213689 -103.68658 -169.01715)"/><path d="m-397.75084 715.67331l-163.78868 1.15534c-6.94231.049-14.10792 13.10451-10.71655 19.16231l80.79533 144.31978c3.39136 6.05781 12.65442 10.6298 18.54535 6.9562l75.57536-47.12863c5.89092-3.67357 10.6705-12.21995 10.71655-19.16229l.59631-89.90907c.046-6.94233-4.78135-15.44261-11.72367-15.39364z" fill="#00aad4"/><rect fill="#0cf" height="111.41566" ry="12.143711" transform="matrix(.85586624 .51719723 .00684405 .99997658 0 0)" width="113.15113" x="-678.87305" y="1073.5972"/><rect fill="#5df" height="112.81577" ry="14.66605" transform="matrix(.86530322 .50124878 -.86512318 .50155946 0 0)" width="112.68221" x="388.7005" y="942.09778"/></g><g transform="matrix(.96335893 0 0 .96335893 594.27012427 160.07488042)"><path d="m-442.65137 1055.1932c-5.37558-.0004-10.75247-.4342-14.87109-1.3009l-84.63672-17.8104c-1.65176-.3475-2.95609-.7386-3.94531-1.154-3.71453-1.1985-6.62987-3.2201-7.13477-4.9803-.41372-.4331-.64391-.9429-.63281-1.5313l.69922-37.06c.054-2.86135 5.52976-6.35712 12.27734-7.8371l89.12695-19.54942c1.48558-.32573 2.89873-.51836 4.20899-.60182 3.99244-.574 8.30073-.65058 11.66992.11216l88.56445 20.04642c6.90343 1.56259 12.50464 5.19835 12.5586 8.15116l.69922 38.2432c.0315 1.7289-1.90581 3.6797-5.14063 5.0001-.19992.086-.41416.1725-.64258.2558-.016 0-.0327.022-.0488.022-.90767.3313-2.01326.6462-3.36329.9301l-84.51953 17.7707c-4.11948.8661-9.49355 1.2992-14.86914 1.2988z" fill="url(#a)" filter="url(#b)" transform="matrix(.84718319 0 0 1.0213689 -103.68658 -169.01715)"/><path d="m-397.75084 715.67331l-163.78868 1.15534c-6.94231.049-14.10792 13.10451-10.71655 19.16231l80.79533 144.31978c3.39136 6.05781 12.65442 10.6298 18.54535 6.9562l75.57536-47.12863c5.89092-3.67357 10.6705-12.21995 10.71655-19.16229l.59631-89.90907c.046-6.94233-4.78135-15.44261-11.72367-15.39364z" fill="#00aad4"/><rect fill="#0cf" height="111.41566" ry="12.143711" transform="matrix(.85586624 .51719723 .00684405 .99997658 0 0)" width="113.15113" x="-678.87305" y="1073.5972"/><rect fill="#5df" height="112.81577" ry="14.66605" transform="matrix(.86530322 .50124878 -.86512318 .50155946 0 0)" width="112.68221" x="388.7005" y="942.09778"/></g><g transform="matrix(.96335893 0 0 .96335893 1652.91004427 154.01396042)"><path d="m-442.65137 1055.1932c-5.37558-.0004-10.75247-.4342-14.87109-1.3009l-84.63672-17.8104c-1.65176-.3475-2.95609-.7386-3.94531-1.154-3.71453-1.1985-6.62987-3.2201-7.13477-4.9803-.41372-.4331-.64391-.9429-.63281-1.5313l.69922-37.06c.054-2.86135 5.52976-6.35712 12.27734-7.8371l89.12695-19.54942c1.48558-.32573 2.89873-.51836 4.20899-.60182 3.99244-.574 8.30073-.65058 11.66992.11216l88.56445 20.04642c6.90343 1.56259 12.50464 5.19835 12.5586 8.15116l.69922 38.2432c.0315 1.7289-1.90581 3.6797-5.14063 5.0001-.19992.086-.41416.1725-.64258.2558-.016 0-.0327.022-.0488.022-.90767.3313-2.01326.6462-3.36329.9301l-84.51953 17.7707c-4.11948.8661-9.49355 1.2992-14.86914 1.2988z" fill="url(#c)" filter="url(#b)" transform="matrix(.84718319 0 0 1.0213689 -103.68658 -169.01715)"/><path d="m-397.75084 715.67331l-163.78868 1.15534c-6.94231.049-14.10792 13.10451-10.71655 19.16231l80.79533 144.31978c3.39136 6.05781 12.65442 10.6298 18.54535 6.9562l75.57536-47.12863c5.89092-3.67357 10.6705-12.21995 10.71655-19.16229l.59631-89.90907c.046-6.94233-4.78135-15.44261-11.72367-15.39364z" fill="#e67e22"/><rect fill="#ffa522" height="111.41566" ry="12.143711" transform="matrix(.85586624 .51719723 .00684405 .99997658 0 0)" width="113.15113" x="-678.87305" y="1073.5972"/><rect fill="#ffc647" height="112.81577" ry="14.66605" transform="matrix(.86530322 .50124878 -.86512318 .50155946 0 0)" width="112.68221" x="388.7005" y="942.09778"/></g><g transform="matrix(.96335893 0 0 .96335893 948.31072427 -81.76998958)"><path d="m-442.65137 1055.1932c-5.37558-.0004-10.75247-.4342-14.87109-1.3009l-84.63672-17.8104c-1.65176-.3475-2.95609-.7386-3.94531-1.154-3.71453-1.1985-6.62987-3.2201-7.13477-4.9803-.41372-.4331-.64391-.9429-.63281-1.5313l.69922-37.06c.054-2.86135 5.52976-6.35712 12.27734-7.8371l89.12695-19.54942c1.48558-.32573 2.89873-.51836 4.20899-.60182 3.99244-.574 8.30073-.65058 11.66992.11216l88.56445 20.04642c6.90343 1.56259 12.50464 5.19835 12.5586 8.15116l.69922 38.2432c.0315 1.7289-1.90581 3.6797-5.14063 5.0001-.19992.086-.41416.1725-.64258.2558-.016 0-.0327.022-.0488.022-.90767.3313-2.01326.6462-3.36329.9301l-84.51953 17.7707c-4.11948.8661-9.49355 1.2992-14.86914 1.2988z" fill="url(#d)" filter="url(#b)" transform="matrix(.84718319 0 0 1.0213689 -103.68658 -169.01715)"/><path d="m-397.75084 715.67331l-163.78868 1.15534c-6.94231.049-14.10792 13.10451-10.71655 19.16231l80.79533 144.31978c3.39136 6.05781 12.65442 10.6298 18.54535 6.9562l75.57536-47.12863c5.89092-3.67357 10.6705-12.21995 10.71655-19.16229l.59631-89.90907c.046-6.94233-4.78135-15.44261-11.72367-15.39364z" fill="#d80073"/><rect fill="#e8338b" height="111.41566" ry="12.143711" transform="matrix(.85586624 .51719723 .00684405 .99997658 0 0)" width="113.15113" x="-678.87305" y="1073.5972"/><rect fill="#e85bbe" height="112.81577" ry="14.66605" transform="matrix(.86530322 .50124878 -.86512318 .50155946 0 0)" width="112.68221" x="388.7005" y="942.09778"/></g><g transform="matrix(.96335893 0 0 .96335893 1089.73929427 -30.34141858)"><path d="m-442.65137 1055.1932c-5.37558-.0004-10.75247-.4342-14.87109-1.3009l-84.63672-17.8104c-1.65176-.3475-2.95609-.7386-3.94531-1.154-3.71453-1.1985-6.62987-3.2201-7.13477-4.9803-.41372-.4331-.64391-.9429-.63281-1.5313l.69922-37.06c.054-2.86135 5.52976-6.35712 12.27734-7.8371l89.12695-19.54942c1.48558-.32573 2.89873-.51836 4.20899-.60182 3.99244-.574 8.30073-.65058 11.66992.11216l88.56445 20.04642c6.90343 1.56259 12.50464 5.19835 12.5586 8.15116l.69922 38.2432c.0315 1.7289-1.90581 3.6797-5.14063 5.0001-.19992.086-.41416.1725-.64258.2558-.016 0-.0327.022-.0488.022-.90767.3313-2.01326.6462-3.36329.9301l-84.51953 17.7707c-4.11948.8661-9.49355 1.2992-14.86914 1.2988z" fill="url(#d)" filter="url(#b)" transform="matrix(.84718319 0 0 1.0213689 -103.68658 -169.01715)"/><path d="m-397.75084 715.67331l-163.78868 1.15534c-6.94231.049-14.10792 13.10451-10.71655 19.16231l80.79533 144.31978c3.39136 6.05781 12.65442 10.6298 18.54535 6.9562l75.57536-47.12863c5.89092-3.67357 10.6705-12.21995 10.71655-19.16229l.59631-89.90907c.046-6.94233-4.78135-15.44261-11.72367-15.39364z" fill="#d80073"/><rect fill="#e8338b" height="111.41566" ry="12.143711" transform="matrix(.85586624 .51719723 .00684405 .99997658 0 0)" width="113.15113" x="-678.87305" y="1073.5972"/><rect fill="#e85bbe" height="112.81577" ry="14.66605" transform="matrix(.86530322 .50124878 -.86512318 .50155946 0 0)" width="112.68221" x="388.7005" y="942.09778"/></g><g><path d="m776.6785 662.47113v-63.56849h-12.01925l-2.40385 3.65029h10.77281v59.9182z"/><path d="m834.40499 630.1527q0-12.55344-5.25286-21.99078-6.41026-11.307-18.07339-11.307-11.66313 0-18.07339 11.307-5.25286 9.25927-5.25286 21.99078 0 12.64247 5.25286 21.90175 6.41026 11.307 18.07339 11.307 11.75216 0 18.16242-11.21797 5.16383-9.17025 5.16383-21.99078zm-3.82835 0q0 12.19731-4.89674 20.29918-5.43092 9.17024-14.60116 9.17024-9.8825 0-15.22439-10.32765-4.18448-8.1909-4.18448-19.14177 0-12.01925 4.89674-20.38821 5.51995-9.25928 14.51213-9.25928 10.06056 0 15.31342 10.32765 4.18448 8.1909 4.18448 19.31984z"/><path d="m850.40771 662.47113v-63.56849h-12.01925l-2.40385 3.65029h10.77281v59.9182z"/><path d="m908.1342 630.1527q0-12.55344-5.25286-21.99078-6.41027-11.307-18.07339-11.307-11.66313 0-18.0734 11.307-5.25286 9.25927-5.25286 21.99078 0 12.64247 5.25286 21.90175 6.41027 11.307 18.0734 11.307 11.75215 0 18.16242-11.21797 5.16383-9.17025 5.16383-21.99078zm-3.82836 0q0 12.19731-4.89673 20.29918-5.43092 9.17024-14.60116 9.17024-9.8825 0-15.22439-10.32765-4.18448-8.1909-4.18448-19.14177 0-12.01925 4.89673-20.38821 5.51995-9.25928 14.51214-9.25928 10.06055 0 15.31341 10.32765 4.18448 8.1909 4.18448 19.31984z"/><path d="m920.38645 649.29447v-50.39183h-3.56126v50.39183zm1.51354 10.32765q-.26709-1.51353-.97935-2.22578-.97934-.97935-2.22578-.97935-3.20514.53419-3.20514 3.20513 0 3.20514 3.20514 3.20514 3.20513-.53419 3.20513-3.20514z"/><path d="m792.5261 773.7605l-33.11971-33.20875 30.44877-30.35974h-5.16383l-28.40104 28.22298v-28.22298h-3.6503v63.56849h3.6503v-30.18168l.53418-.53419 30.62684 30.71587z"/><path d="m803.78303 773.7605v-65.88331h-3.56126v65.88331z"/><path d="m851.45104 755.42001q0-8.01283-5.51995-13.53279-5.51995-5.51995-13.44376-5.51995-8.01283 0-13.44375 5.60898-5.51995 5.51996-5.51995 13.44376 0 8.01283 5.51995 13.53279 5.43092 5.43092 13.44375 5.43092 8.01284 0 13.53279-5.51995 5.43092-5.43093 5.43092-13.44376zm-3.56126 0q0 6.41027-4.5406 10.95087-4.45158 4.45158-10.86185 4.45158-6.32123 0-10.86184-4.54061-4.5406-4.5406-4.5406-10.86184 0-6.2322 4.62963-10.86184 4.54061-4.62964 10.77281-4.62964 6.4993 0 10.95088 4.54061 4.45157 4.45157 4.45157 10.95087z"/><path d="m899.61847 755.42001q0-8.01283-5.51995-13.53279-5.51996-5.51995-13.44376-5.51995-8.01283 0-13.44375 5.60898-5.51996 5.51996-5.51996 13.44376 0 8.01283 5.51996 13.53279 5.43092 5.43092 13.44375 5.43092 8.01284 0 13.53279-5.51995 5.43092-5.43093 5.43092-13.44376zm-3.56126 0q0 6.41027-4.54061 10.95087-4.45157 4.45158-10.86184 4.45158-6.32123 0-10.86184-4.54061-4.54061-4.5406-4.54061-10.86184 0-6.2322 4.62964-10.86184 4.54061-4.62964 10.77281-4.62964 6.4993 0 10.95088 4.54061 4.45157 4.45157 4.45157 10.95087z"/><path d="m937.19393 773.7605v-22.34691q0-7.03349-3.65029-10.95087-3.65029-3.91739-10.50571-3.91739-8.10187 0-10.59475 4.89674v-4.45158h-3.65029v36.77001h3.65029v-22.34691q0-5.25286 2.75998-8.27993 2.67094-3.02707 7.83477-3.02707 10.50571 0 10.50571 11.66313v21.99078z"/><path d="m949.78423 727.46412q-.71225-2.93804-2.93804-2.93804-2.93803.53419-2.93803 2.93804.089 1.33547.89031 2.13676 1.06838.80128 2.04772.80128 2.93804 0 2.93804-2.93804zm-1.15741 46.29638v-36.77001h-3.65029v36.77001z"/></g></g></svg>
@@ -1,705 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - id="svg4534" - version="1.1" - viewBox="0 0 186.53125 91.016669" - height="344" - width="705"> - <defs - id="defs4528"> - <linearGradient - y2="1062.272" - y1="1002.7515" - x2="-440.17316" - x1="-440.17316" - gradientUnits="userSpaceOnUse" - id="a-3"> - <stop - id="stop2-6" - stop-opacity=".27272728" - stop-color="#005497" - offset="0" /> - <stop - id="stop4-7" - stop-opacity=".06818182" - stop-color="#005497" - offset="1" /> - </linearGradient> - <filter - y="-0.095373832" - x="-0.040038541" - width="1.0800771" - height="1.1907477" - id="b-5" - style="color-interpolation-filters:sRGB"> - <feGaussianBlur - id="feGaussianBlur7-3" - stdDeviation="3.6669538" /> - </filter> - <linearGradient - y2="1062.272" - x2="-440.17316" - y1="1002.7515" - x1="-440.17316" - gradientUnits="userSpaceOnUse" - id="linearGradient4780" - xlink:href="#d" /> - <linearGradient - y2="1062.272" - y1="1002.7515" - x2="-440.17316" - x1="-440.17316" - gradientUnits="userSpaceOnUse" - id="d"> - <stop - id="stop15" - stop-opacity=".27450982" - stop-color="#ff5497" - offset="0" /> - <stop - id="stop17" - stop-opacity=".06666667" - stop-color="#ff5497" - offset="1" /> - </linearGradient> - <filter - y="-0.095373832" - x="-0.040038541" - width="1.0800771" - height="1.1907477" - id="b" - style="color-interpolation-filters:sRGB"> - <feGaussianBlur - id="feGaussianBlur7" - stdDeviation="3.6669538" /> - </filter> - <filter - y="-0.095373832" - x="-0.040038541" - width="1.0800771" - height="1.1907477" - id="filter5144" - style="color-interpolation-filters:sRGB"> - <feGaussianBlur - id="feGaussianBlur5142" - stdDeviation="3.6669538" /> - </filter> - <linearGradient - y2="1062.272" - x2="-440.17316" - y1="1002.7515" - x1="-440.17316" - gradientUnits="userSpaceOnUse" - id="linearGradient4776" - xlink:href="#c" /> - <linearGradient - y2="1062.272" - y1="1002.7515" - x2="-440.17316" - x1="-440.17316" - gradientUnits="userSpaceOnUse" - id="c"> - <stop - id="stop10" - stop-opacity=".27450982" - stop-color="#ff6e07" - offset="0" /> - <stop - id="stop12" - stop-opacity=".06666667" - stop-color="#ff541c" - offset="1" /> - </linearGradient> - <filter - y="-0.095373832" - x="-0.040038541" - width="1.0800771" - height="1.1907477" - id="filter5152" - style="color-interpolation-filters:sRGB"> - <feGaussianBlur - id="feGaussianBlur5150" - stdDeviation="3.6669538" /> - </filter> - <linearGradient - y2="1062.272" - x2="-440.17316" - y1="1002.7515" - x1="-440.17316" - gradientUnits="userSpaceOnUse" - id="linearGradient4774" - xlink:href="#a-3" /> - <filter - y="-0.095373832" - x="-0.040038541" - width="1.0800771" - height="1.1907477" - id="filter5160" - style="color-interpolation-filters:sRGB"> - <feGaussianBlur - id="feGaussianBlur5158" - stdDeviation="3.6669538" /> - </filter> - <filter - y="-0.095373832" - x="-0.040038541" - width="1.0800771" - height="1.1907477" - id="filter5171" - style="color-interpolation-filters:sRGB"> - <feGaussianBlur - id="feGaussianBlur5169" - stdDeviation="3.6669538" /> - </filter> - <filter - y="-0.095373832" - x="-0.040038541" - width="1.0800771" - height="1.1907477" - id="filter5182" - style="color-interpolation-filters:sRGB"> - <feGaussianBlur - id="feGaussianBlur5180" - stdDeviation="3.6669538" /> - </filter> - <filter - y="-0.095373832" - x="-0.040038541" - width="1.0800771" - height="1.1907477" - id="filter5193" - style="color-interpolation-filters:sRGB"> - <feGaussianBlur - id="feGaussianBlur5191" - stdDeviation="3.6669538" /> - </filter> - <filter - y="-0.095373832" - x="-0.040038541" - width="1.0800771" - height="1.1907477" - id="filter5204" - style="color-interpolation-filters:sRGB"> - <feGaussianBlur - id="feGaussianBlur5202" - stdDeviation="3.6669538" /> - </filter> - <filter - y="-0.095373832" - x="-0.040038541" - width="1.0800771" - height="1.1907477" - id="filter5215" - style="color-interpolation-filters:sRGB"> - <feGaussianBlur - id="feGaussianBlur5213" - stdDeviation="3.6669538" /> - </filter> - <filter - y="-0.095373832" - x="-0.040038541" - width="1.0800771" - height="1.1907477" - id="filter5226" - style="color-interpolation-filters:sRGB"> - <feGaussianBlur - id="feGaussianBlur5224" - stdDeviation="3.6669538" /> - </filter> - <linearGradient - gradientUnits="userSpaceOnUse" - y2="-354.60028" - x2="189.20795" - y1="-485.40198" - x1="117.92211" - id="linearGradient4929" - xlink:href="#linearGradient4927" - gradientTransform="matrix(0.26458333,0,0,0.26458333,54.673499,367.28816)" /> - <linearGradient - id="linearGradient4927"> - <stop - id="stop4923" - offset="0" - style="stop-color:#e0ffff;stop-opacity:1" /> - <stop - id="stop4925" - offset="1" - style="stop-color:#ffccff;stop-opacity:1" /> - </linearGradient> - <linearGradient - y2="1062.272" - x2="-440.17316" - y1="1002.7515" - x1="-440.17316" - gradientUnits="userSpaceOnUse" - id="linearGradient5328" - xlink:href="#a-3" /> - <linearGradient - y2="1062.272" - x2="-440.17316" - y1="1002.7515" - x1="-440.17316" - gradientUnits="userSpaceOnUse" - id="linearGradient5330" - xlink:href="#c" /> - <linearGradient - y2="1062.272" - x2="-440.17316" - y1="1002.7515" - x1="-440.17316" - gradientUnits="userSpaceOnUse" - id="linearGradient5332" - xlink:href="#c" /> - <linearGradient - y2="1062.272" - x2="-440.17316" - y1="1002.7515" - x1="-440.17316" - gradientUnits="userSpaceOnUse" - id="linearGradient5334" - xlink:href="#c" /> - <linearGradient - y2="1062.272" - x2="-440.17316" - y1="1002.7515" - x1="-440.17316" - gradientUnits="userSpaceOnUse" - id="linearGradient5336" - xlink:href="#a-3" /> - <linearGradient - y2="1062.272" - x2="-440.17316" - y1="1002.7515" - x1="-440.17316" - gradientUnits="userSpaceOnUse" - id="linearGradient5338" - xlink:href="#a-3" /> - <linearGradient - y2="1062.272" - x2="-440.17316" - y1="1002.7515" - x1="-440.17316" - gradientUnits="userSpaceOnUse" - id="linearGradient5340" - xlink:href="#d" /> - <linearGradient - y2="1062.272" - x2="-440.17316" - y1="1002.7515" - x1="-440.17316" - gradientUnits="userSpaceOnUse" - id="linearGradient5342" - xlink:href="#a-3" /> - </defs> - <metadata - id="metadata4531"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - transform="translate(0,-205.98332)" - id="layer1"> - <rect - y="205.98332" - x="0" - height="91.01667" - width="186.53125" - id="rect4634" - style="fill:url(#linearGradient4929);fill-opacity:1;stroke:none;stroke-width:7.32099152;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <g - id="g4644" - transform="matrix(0.130994,0,0,0.130994,82.301731,125.30822)"> - <path - id="path4636" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - style="fill:url(#linearGradient5328);filter:url(#b)" /> - <path - id="path4638" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - style="fill:#00aad4" /> - <rect - id="rect4640" - y="1073.5972" - x="-678.87305" - width="113.15113" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" - ry="12.143711" - height="111.41566" - style="fill:#00ccff" /> - <rect - id="rect4642" - y="942.09778" - x="388.7005" - width="112.68221" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" - ry="14.66605" - height="112.81577" - style="fill:#55ddff" /> - </g> - <g - id="g4654" - transform="matrix(0.130994,0,0,0.130994,209.49413,124.75879)"> - <path - id="path4646" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - style="fill:url(#linearGradient5330);filter:url(#b)" /> - <path - id="path4648" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - style="fill:#e67e22" /> - <rect - id="rect4650" - y="1073.5972" - x="-678.87305" - width="113.15113" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" - ry="12.143711" - height="111.41566" - style="fill:#ffa522" /> - <rect - id="rect4652" - y="942.09778" - x="388.7005" - width="112.68221" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" - ry="14.66605" - height="112.81577" - style="fill:#ffc647" /> - </g> - <g - id="g4664" - transform="matrix(0.130994,0,0,0.130994,230.09767,134.09906)"> - <path - id="path4656" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - style="fill:url(#linearGradient5332);filter:url(#b)" /> - <path - id="path4658" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - style="fill:#e67e22" /> - <rect - id="rect4660" - y="1073.5972" - x="-678.87305" - width="113.15113" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" - ry="12.143711" - height="111.41566" - style="fill:#ffa522" /> - <rect - id="rect4662" - y="942.09778" - x="388.7005" - width="112.68221" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" - ry="14.66605" - height="112.81577" - style="fill:#ffc647" /> - </g> - <g - id="g4674" - transform="matrix(0.130994,0,0,0.130994,210.31829,143.98875)"> - <path - id="path4666" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - style="fill:url(#linearGradient5334);filter:url(#b)" /> - <path - id="path4668" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - style="fill:#e67e22" /> - <rect - id="rect4670" - y="1073.5972" - x="-678.87305" - width="113.15113" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" - ry="12.143711" - height="111.41566" - style="fill:#ffa522" /> - <rect - id="rect4672" - y="942.09778" - x="388.7005" - width="112.68221" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" - ry="14.66605" - height="112.81577" - style="fill:#ffc647" /> - </g> - <g - id="g4684" - transform="matrix(0.130994,0,0,0.130994,81.752302,142.61518)"> - <path - id="path4676" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - style="fill:url(#linearGradient5336);filter:url(#b)" /> - <path - id="path4678" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - style="fill:#00aad4" /> - <rect - id="rect4680" - y="1073.5972" - x="-678.87305" - width="113.15113" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" - ry="12.143711" - height="111.41566" - style="fill:#00ccff" /> - <rect - id="rect4682" - y="942.09778" - x="388.7005" - width="112.68221" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" - ry="14.66605" - height="112.81577" - style="fill:#55ddff" /> - </g> - <g - id="g4694" - transform="matrix(0.130994,0,0,0.130994,103.90984,152.52439)"> - <path - id="path4686" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - style="fill:url(#linearGradient5338);filter:url(#b)" /> - <path - id="path4688" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - style="fill:#00aad4" /> - <rect - id="rect4690" - y="1073.5972" - x="-678.87305" - width="113.15113" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" - ry="12.143711" - height="111.41566" - style="fill:#00ccff" /> - <rect - id="rect4692" - y="942.09778" - x="388.7005" - width="112.68221" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" - ry="14.66605" - height="112.81577" - style="fill:#55ddff" /> - </g> - <g - id="g4704" - transform="matrix(0.130994,0,0,0.130994,81.752302,157.99914)"> - <path - id="path4696" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - style="fill:url(#linearGradient4774);filter:url(#b)" /> - <path - id="path4698" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - style="fill:#00aad4" /> - <rect - id="rect4700" - y="1073.5972" - x="-678.87305" - width="113.15113" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" - ry="12.143711" - height="111.41566" - style="fill:#00ccff" /> - <rect - id="rect4702" - y="942.09778" - x="388.7005" - width="112.68221" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" - ry="14.66605" - height="112.81577" - style="fill:#55ddff" /> - </g> - <g - id="g4714" - transform="matrix(0.130994,0,0,0.130994,225.70225,157.175)"> - <path - id="path4706" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - style="fill:url(#linearGradient4776);filter:url(#b)" /> - <path - id="path4708" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - style="fill:#e67e22" /> - <rect - id="rect4710" - y="1073.5972" - x="-678.87305" - width="113.15113" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" - ry="12.143711" - height="111.41566" - style="fill:#ffa522" /> - <rect - id="rect4712" - y="942.09778" - x="388.7005" - width="112.68221" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" - ry="14.66605" - height="112.81577" - style="fill:#ffc647" /> - </g> - <g - id="g4724" - transform="matrix(0.130994,0,0,0.130994,129.89344,125.11397)"> - <path - id="path4716" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - style="fill:url(#linearGradient5340);filter:url(#b)" /> - <path - id="path4718" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - style="fill:#d80073" /> - <rect - id="rect4720" - y="1073.5972" - x="-678.87305" - width="113.15113" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" - ry="12.143711" - height="111.41566" - style="fill:#e8338b" /> - <rect - id="rect4722" - y="942.09778" - x="388.7005" - width="112.68221" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" - ry="14.66605" - height="112.81577" - style="fill:#e85bbe" /> - </g> - <g - id="g4734" - transform="matrix(0.130994,0,0,0.130994,149.12438,132.10704)"> - <path - id="path4726" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - style="fill:url(#linearGradient4780);filter:url(#b)" /> - <path - id="path4728" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - style="fill:#d80073" /> - <rect - id="rect4730" - y="1073.5972" - x="-678.87305" - width="113.15113" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" - ry="12.143711" - height="111.41566" - style="fill:#e8338b" /> - <rect - id="rect4732" - y="942.09778" - x="388.7005" - width="112.68221" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" - ry="14.66605" - height="112.81577" - style="fill:#e85bbe" /> - </g> - <g - transform="matrix(0.15276193,0,0,0.15276193,-59.785212,172.36969)" - id="g4758"> - <path - id="path4736" - d="m 776.6785,662.47113 v -63.56849 h -12.01925 l -2.40385,3.65029 h 10.77281 v 59.9182 z" /> - <path - id="path4738" - d="m 834.40499,630.1527 q 0,-12.55344 -5.25286,-21.99078 -6.41026,-11.307 -18.07339,-11.307 -11.66313,0 -18.07339,11.307 -5.25286,9.25927 -5.25286,21.99078 0,12.64247 5.25286,21.90175 6.41026,11.307 18.07339,11.307 11.75216,0 18.16242,-11.21797 5.16383,-9.17025 5.16383,-21.99078 z m -3.82835,0 q 0,12.19731 -4.89674,20.29918 -5.43092,9.17024 -14.60116,9.17024 -9.8825,0 -15.22439,-10.32765 -4.18448,-8.1909 -4.18448,-19.14177 0,-12.01925 4.89674,-20.38821 5.51995,-9.25928 14.51213,-9.25928 10.06056,0 15.31342,10.32765 4.18448,8.1909 4.18448,19.31984 z" /> - <path - id="path4740" - d="m 850.40771,662.47113 v -63.56849 h -12.01925 l -2.40385,3.65029 h 10.77281 v 59.9182 z" /> - <path - id="path4742" - d="m 908.1342,630.1527 q 0,-12.55344 -5.25286,-21.99078 -6.41027,-11.307 -18.07339,-11.307 -11.66313,0 -18.0734,11.307 -5.25286,9.25927 -5.25286,21.99078 0,12.64247 5.25286,21.90175 6.41027,11.307 18.0734,11.307 11.75215,0 18.16242,-11.21797 5.16383,-9.17025 5.16383,-21.99078 z m -3.82836,0 q 0,12.19731 -4.89673,20.29918 -5.43092,9.17024 -14.60116,9.17024 -9.8825,0 -15.22439,-10.32765 -4.18448,-8.1909 -4.18448,-19.14177 0,-12.01925 4.89673,-20.38821 5.51995,-9.25928 14.51214,-9.25928 10.06055,0 15.31341,10.32765 4.18448,8.1909 4.18448,19.31984 z" /> - <path - id="path4744" - d="m 920.38645,649.29447 v -50.39183 h -3.56126 v 50.39183 z m 1.51354,10.32765 q -0.26709,-1.51353 -0.97935,-2.22578 -0.97934,-0.97935 -2.22578,-0.97935 -3.20514,0.53419 -3.20514,3.20513 0,3.20514 3.20514,3.20514 3.20513,-0.53419 3.20513,-3.20514 z" /> - <path - id="path4746" - d="m 792.5261,773.7605 -33.11971,-33.20875 30.44877,-30.35974 h -5.16383 l -28.40104,28.22298 v -28.22298 h -3.6503 v 63.56849 h 3.6503 v -30.18168 l 0.53418,-0.53419 30.62684,30.71587 z" /> - <path - id="path4748" - d="m 803.78303,773.7605 v -65.88331 h -3.56126 v 65.88331 z" /> - <path - id="path4750" - d="m 851.45104,755.42001 q 0,-8.01283 -5.51995,-13.53279 -5.51995,-5.51995 -13.44376,-5.51995 -8.01283,0 -13.44375,5.60898 -5.51995,5.51996 -5.51995,13.44376 0,8.01283 5.51995,13.53279 5.43092,5.43092 13.44375,5.43092 8.01284,0 13.53279,-5.51995 5.43092,-5.43093 5.43092,-13.44376 z m -3.56126,0 q 0,6.41027 -4.5406,10.95087 -4.45158,4.45158 -10.86185,4.45158 -6.32123,0 -10.86184,-4.54061 -4.5406,-4.5406 -4.5406,-10.86184 0,-6.2322 4.62963,-10.86184 4.54061,-4.62964 10.77281,-4.62964 6.4993,0 10.95088,4.54061 4.45157,4.45157 4.45157,10.95087 z" /> - <path - id="path4752" - d="m 899.61847,755.42001 q 0,-8.01283 -5.51995,-13.53279 -5.51996,-5.51995 -13.44376,-5.51995 -8.01283,0 -13.44375,5.60898 -5.51996,5.51996 -5.51996,13.44376 0,8.01283 5.51996,13.53279 5.43092,5.43092 13.44375,5.43092 8.01284,0 13.53279,-5.51995 5.43092,-5.43093 5.43092,-13.44376 z m -3.56126,0 q 0,6.41027 -4.54061,10.95087 -4.45157,4.45158 -10.86184,4.45158 -6.32123,0 -10.86184,-4.54061 -4.54061,-4.5406 -4.54061,-10.86184 0,-6.2322 4.62964,-10.86184 4.54061,-4.62964 10.77281,-4.62964 6.4993,0 10.95088,4.54061 4.45157,4.45157 4.45157,10.95087 z" /> - <path - id="path4754" - d="m 937.19393,773.7605 v -22.34691 q 0,-7.03349 -3.65029,-10.95087 -3.65029,-3.91739 -10.50571,-3.91739 -8.10187,0 -10.59475,4.89674 v -4.45158 h -3.65029 v 36.77001 h 3.65029 v -22.34691 q 0,-5.25286 2.75998,-8.27993 2.67094,-3.02707 7.83477,-3.02707 10.50571,0 10.50571,11.66313 v 21.99078 z" /> - <path - id="path4756" - d="m 949.78423,727.46412 q -0.71225,-2.93804 -2.93804,-2.93804 -2.93803,0.53419 -2.93803,2.93804 0.089,1.33547 0.89031,2.13676 1.06838,0.80128 2.04772,0.80128 2.93804,0 2.93804,-2.93804 z m -1.15741,46.29638 v -36.77001 h -3.65029 v 36.77001 z" /> - </g> - <g - id="g22" - transform="matrix(0.14591547,0,0,0.14591547,181.1832,110.17491)"> - <g - id="g20" - transform="matrix(1.3547235,0,0,1.3547235,160.26025,20.43346)"> - <path - id="path12" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - style="fill:url(#linearGradient5342);filter:url(#b-5)" /> - <path - id="path14" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - style="fill:#00aad4" /> - <rect - id="rect16" - y="1073.5972" - x="-678.87305" - width="113.15113" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" - ry="12.143711" - height="111.41566" - style="fill:#00ccff" /> - <rect - id="rect18" - y="942.09778" - x="388.7005" - width="112.68221" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" - ry="14.66605" - height="112.81577" - style="fill:#55ddff" /> - </g> - </g> - </g> -</svg>
@@ -1,1 +0,0 @@
-<svg height="256" viewBox="0 0 256 256" width="256" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="-440.1731564" x2="-440.1731564" y1="1002.751495" y2="1062.271995"><stop offset="0" stop-color="#005497" stop-opacity=".27272728"/><stop offset="1" stop-color="#005497" stop-opacity=".06818182"/></linearGradient><filter id="b" color-interpolation-filters="sRGB" height="1.1907477" width="1.0800771" x="-.04003854" y="-.09537383"><feGaussianBlur stdDeviation="3.6669538"/></filter><g transform="matrix(.71111111 0 0 .71111111 476.26667 -644.75682)"><path d="m-669.75 906.68933h360v360h-360z" fill="#fff"/><g transform="matrix(1.3547235 0 0 1.3547235 160.26025 20.43346)"><path d="m-442.65137 1055.1932c-5.37558-.0004-10.75247-.4342-14.87109-1.3009l-84.63672-17.8104c-1.65176-.3475-2.95609-.7386-3.94531-1.154-3.71453-1.1985-6.62987-3.2201-7.13477-4.9803-.41372-.4331-.64391-.9429-.63281-1.5313l.69922-37.06c.054-2.86135 5.52976-6.35712 12.27734-7.8371l89.12695-19.54942c1.48558-.32573 2.89873-.51836 4.20899-.60182 3.99244-.574 8.30073-.65058 11.66992.11216l88.56445 20.04642c6.90343 1.56259 12.50464 5.19835 12.5586 8.15116l.69922 38.2432c.0315 1.7289-1.90581 3.6797-5.14063 5.0001-.19992.086-.41416.1725-.64258.2558-.016 0-.0327.022-.0488.022-.90767.3313-2.01326.6462-3.36329.9301l-84.51953 17.7707c-4.11948.8661-9.49355 1.2992-14.86914 1.2988z" fill="url(#a)" filter="url(#b)" transform="matrix(.84718319 0 0 1.0213689 -103.68658 -169.01715)"/><path d="m-397.75084 715.67331l-163.78868 1.15534c-6.94231.049-14.10792 13.10451-10.71655 19.16231l80.79533 144.31978c3.39136 6.05781 12.65442 10.6298 18.54535 6.9562l75.57536-47.12863c5.89092-3.67357 10.6705-12.21995 10.71655-19.16229l.59631-89.90907c.046-6.94233-4.78135-15.44261-11.72367-15.39364z" fill="#00aad4"/><rect fill="#0cf" height="111.41566" ry="12.143711" transform="matrix(.85586624 .51719723 .00684405 .99997658 0 0)" width="113.15113" x="-678.87305" y="1073.5972"/><rect fill="#5df" height="112.81577" ry="14.66605" transform="matrix(.86530322 .50124878 -.86512318 .50155946 0 0)" width="112.68221" x="388.7005" y="942.09778"/></g></g></svg>
@@ -1,94 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="16" - height="16" - viewBox="0 0 16 16" - id="svg2" - version="1.1" - inkscape:version="0.91 r13725" - sodipodi:docname="angel.svg"> - <defs - id="defs4" /> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="3.959798" - inkscape:cx="32.699251" - inkscape:cy="0.47610902" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - inkscape:window-width="1366" - inkscape:window-height="744" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" - units="px" /> - <metadata - id="metadata7"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-1036.3622)"> - <ellipse - style="opacity:1;fill:#ffeb3b;fill-opacity:1;stroke:#000000;stroke-width:0.28393582;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="path4147" - cx="8" - cy="1044.5884" - rx="7.8580317" - ry="7.6318169" /> - <path - style="opacity:1;fill:#00bcd4;fill-opacity:1;stroke:#000000;stroke-width:0.23731966;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - d="m 6.2064503,1036.6502 a 2.2755178,5.9051214 82.545344 0 0 -5.23961846,3.0944 2.2755178,5.9051214 82.545344 0 0 6.42577886,1.3343 2.2755178,5.9051214 82.545344 0 0 5.2393563,-3.0944 2.2755178,5.9051214 82.545344 0 0 -6.4255167,-1.3343 z m 0.2359892,0.8811 a 1.3704478,4.1657823 82.202353 0 1 4.4730255,0.7122 1.3704478,4.1657823 82.202353 0 1 -3.7588406,1.9543 1.3704478,4.1657823 82.202353 0 1 -4.4730272,-0.7122 1.3704478,4.1657823 82.202353 0 1 3.7588423,-1.9543 z" - id="path4168" - inkscape:connector-curvature="0" /> - <path - style="fill:#ffeb3b;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - d="m 7.9999345,1036.9567 c -2.0768723,0 -4.0686561,0.8015 -5.539988,2.2251 -0.1060328,0.301 0.5823603,0.8624 1.0106317,0.8047 7.0811068,-0.9548 7.7697708,-0.6058 7.4282128,-2.4846 -0.9221367,-0.358 -1.9057838,-0.543 -2.8988565,-0.5452 z" - id="path4175" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ccscc" /> - <path - style="opacity:1;fill:#795548;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - d="m 4.7258196,1042.832 c -0.7671575,0 -1.3890555,0.5285 -1.3890771,1.1804 4.322e-4,0.1384 -0.00259,0.5214 0.085814,0.4053 0.3167804,-0.4161 0.7200091,-0.7761 1.3032626,-0.7769 0.5829234,5e-4 0.9866463,0.3597 1.3035434,0.7755 0.08817,0.1157 0.084983,-0.266 0.085533,-0.4039 -2.16e-5,-0.6519 -0.6219196,-1.1804 -1.389077,-1.1804 z" - id="path4183" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ccscscc" /> - <path - sodipodi:nodetypes="ccscscc" - inkscape:connector-curvature="0" - id="path4188" - d="m 11.274179,1042.832 c -0.767158,0 -1.3890554,0.5285 -1.389077,1.1804 4.322e-4,0.1384 -0.00259,0.5214 0.085814,0.4053 0.316781,-0.4161 0.720009,-0.7761 1.303263,-0.7769 0.582924,5e-4 0.986646,0.3597 1.303544,0.7755 0.08817,0.1157 0.08498,-0.266 0.08553,-0.4039 -2.2e-5,-0.6519 -0.621919,-1.1804 -1.389077,-1.1804 z" - style="opacity:1;fill:#795548;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <path - style="opacity:1;fill:#795548;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - d="m 7.9999993,1049.4565 c 1.5105528,0 2.7350867,-0.5982 2.7351287,-1.336 -8.64e-4,-0.1566 0.0051,-0.5903 -0.168971,-0.4588 -0.6237478,0.4711 -1.417717,0.8784 -2.5661577,0.8793 -1.1477909,0 -1.9427326,-0.4071 -2.5667108,-0.8777 -0.1736083,-0.131 -0.1673346,0.3012 -0.1684179,0.4572 4.32e-5,0.7378 1.2245759,1.336 2.7351287,1.336 z" - id="path4190" - inkscape:connector-curvature="0" - sodipodi:nodetypes="ccscscc" /> - </g> -</svg>
@@ -1,866 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="256" - height="256" - viewBox="0 0 256 256" - id="svg2" - version="1.1" - inkscape:version="0.91 r13725" - sodipodi:docname="enjoy.svg"> - <defs - id="defs4" /> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="0.49851028" - inkscape:cx="268.97214" - inkscape:cy="191.42704" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - inkscape:window-width="1366" - inkscape:window-height="744" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" - units="px" /> - <metadata - id="metadata7"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title /> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-796.36216)"> - <rect - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3.68787074;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4238" - width="252.31212" - height="252.31213" - x="1.8439354" - y="798.20612" - ry="0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4160" - width="23" - height="23" - x="4" - y="1025.3622" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1025.3622" - x="29" - height="23" - width="23" - id="rect4199" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="1025.3622" - x="54" - height="23" - width="23" - id="rect4201" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4203" - width="23" - height="23" - x="79" - y="1025.3622" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1025.3622" - x="129" - height="23" - width="23" - id="rect4205" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4207" - width="23" - height="23" - x="154" - y="1025.3622" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4209" - width="23" - height="23" - x="179" - y="1025.3622" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1025.3622" - x="204.00002" - height="23" - width="23" - id="rect4211" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="1025.3622" - x="104.00001" - height="23" - width="23" - id="rect4213" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4215" - width="23" - height="23" - x="229" - y="1025.3622" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1000.3621" - x="4" - height="23" - width="23" - id="rect4237" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4239" - width="23" - height="23" - x="29" - y="1000.3621" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4241" - width="23" - height="23" - x="54" - y="1000.3621" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1000.3621" - x="79" - height="23" - width="23" - id="rect4243" - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4245" - width="23" - height="23" - x="129" - y="1000.3621" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1000.3621" - x="154" - height="23" - width="23" - id="rect4247" - style="opacity:1;fill:#f1bc3a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="1000.3621" - x="179" - height="23" - width="23" - id="rect4249" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4251" - width="23" - height="23" - x="204.00002" - y="1000.3621" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4253" - width="23" - height="23" - x="104.00001" - y="1000.3621" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1000.3621" - x="229" - height="23" - width="23" - id="rect4255" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="975.36206" - x="4" - height="23" - width="23" - id="rect4257" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4259" - width="23" - height="23" - x="29" - y="975.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4261" - width="23" - height="23" - x="54" - y="975.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="975.36206" - x="79" - height="23" - width="23" - id="rect4263" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4265" - width="23" - height="23" - x="129" - y="975.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="975.36206" - x="154" - height="23" - width="23" - id="rect4267" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="975.36206" - x="179" - height="23" - width="23" - id="rect4269" - style="opacity:1;fill:#f1bc3a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f1bc3a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4271" - width="23" - height="23" - x="204.00002" - y="975.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4273" - width="23" - height="23" - x="104.00001" - y="975.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="975.36206" - x="229" - height="23" - width="23" - id="rect4275" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4277" - width="23" - height="23" - x="4" - y="950.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="950.362" - x="29" - height="23" - width="23" - id="rect4279" - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="950.362" - x="54" - height="23" - width="23" - id="rect4281" - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4283" - width="23" - height="23" - x="79" - y="950.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="950.362" - x="129" - height="23" - width="23" - id="rect4285" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f1bc3a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4287" - width="23" - height="23" - x="154" - y="950.362" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4289" - width="23" - height="23" - x="179" - y="950.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="950.362" - x="204.00002" - height="23" - width="23" - id="rect4291" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="950.362" - x="104.00001" - height="23" - width="23" - id="rect4293" - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f1bc3a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4295" - width="23" - height="23" - x="229" - y="950.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="925.36212" - x="4" - height="23" - width="23" - id="rect4297" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4299" - width="23" - height="23" - x="29" - y="925.36212" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4301" - width="23" - height="23" - x="54" - y="925.36212" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="925.36212" - x="79" - height="23" - width="23" - id="rect4303" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4305" - width="23" - height="23" - x="129" - y="925.36212" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="925.36212" - x="154" - height="23" - width="23" - id="rect4307" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="925.36212" - x="179" - height="23" - width="23" - id="rect4309" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4311" - width="23" - height="23" - x="204.00002" - y="925.36212" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4313" - width="23" - height="23" - x="104.00001" - y="925.36212" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="925.36212" - x="229" - height="23" - width="23" - id="rect4315" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4317" - width="23" - height="23" - x="4" - y="900.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="900.36206" - x="29" - height="23" - width="23" - id="rect4319" - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="900.36206" - x="54" - height="23" - width="23" - id="rect4321" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4323" - width="23" - height="23" - x="79" - y="900.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="900.36206" - x="129" - height="23" - width="23" - id="rect4325" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4327" - width="23" - height="23" - x="154" - y="900.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#e08d44;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4329" - width="23" - height="23" - x="179" - y="900.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="900.36206" - x="204.00002" - height="23" - width="23" - id="rect4331" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="900.36206" - x="104.00001" - height="23" - width="23" - id="rect4333" - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4335" - width="23" - height="23" - x="229" - y="900.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4337" - width="23" - height="23" - x="4" - y="875.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="875.36206" - x="29" - height="23" - width="23" - id="rect4339" - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="875.36206" - x="54" - height="23" - width="23" - id="rect4341" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4343" - width="23" - height="23" - x="79" - y="875.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="875.36206" - x="129" - height="23" - width="23" - id="rect4345" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#e08d44;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4347" - width="23" - height="23" - x="154" - y="875.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4349" - width="23" - height="23" - x="179" - y="875.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="875.36206" - x="204.00002" - height="23" - width="23" - id="rect4351" - style="opacity:1;fill:#e08d44;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="875.36206" - x="104.00001" - height="23" - width="23" - id="rect4353" - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4355" - width="23" - height="23" - x="229" - y="875.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="850.362" - x="4" - height="23" - width="23" - id="rect4357" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4359" - width="23" - height="23" - x="29" - y="850.362" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4361" - width="23" - height="23" - x="54" - y="850.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="850.362" - x="79" - height="23" - width="23" - id="rect4363" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4365" - width="23" - height="23" - x="129" - y="850.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="850.362" - x="154" - height="23" - width="23" - id="rect4367" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="850.362" - x="179" - height="23" - width="23" - id="rect4369" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#e08d44;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4371" - width="23" - height="23" - x="204.00002" - y="850.362" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4373" - width="23" - height="23" - x="104.00001" - y="850.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="850.362" - x="229" - height="23" - width="23" - id="rect4375" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="825.36206" - x="4" - height="23" - width="23" - id="rect4377" - style="opacity:1;fill:#53c17d;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4379" - width="23" - height="23" - x="29" - y="825.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4381" - width="23" - height="23" - x="54" - y="825.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="825.36206" - x="79" - height="23" - width="23" - id="rect4383" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4385" - width="23" - height="23" - x="129" - y="825.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="825.36206" - x="154" - height="23" - width="23" - id="rect4387" - style="opacity:1;fill:#e08d44;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="825.36206" - x="179" - height="23" - width="23" - id="rect4389" - style="opacity:1;fill:#e08d44;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#e08d44;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4391" - width="23" - height="23" - x="204.00002" - y="825.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4393" - width="23" - height="23" - x="104.00001" - y="825.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="825.36206" - x="229" - height="23" - width="23" - id="rect4395" - style="opacity:1;fill:#e08d44;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#53c17d;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4397" - width="23" - height="23" - x="4" - y="800.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="800.36206" - x="29" - height="23" - width="23" - id="rect4399" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="800.36206" - x="54" - height="23" - width="23" - id="rect4401" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4403" - width="23" - height="23" - x="79" - y="800.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="800.36206" - x="129" - height="23" - width="23" - id="rect4405" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4407" - width="23" - height="23" - x="154" - y="800.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4409" - width="23" - height="23" - x="179" - y="800.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="800.36206" - x="204.00002" - height="23" - width="23" - id="rect4411" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="800.36206" - x="104.00001" - height="23" - width="23" - id="rect4413" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4415" - width="23" - height="23" - x="229" - y="800.36206" - ry="3.7123106" /> - </g> -</svg>
@@ -1,891 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="256" - height="256" - viewBox="0 0 256 256" - id="svg2" - version="1.1" - inkscape:version="0.91 r13725" - sodipodi:docname="gameplay.svg"> - <defs - id="defs4" /> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="0.705" - inkscape:cx="104.15194" - inkscape:cy="93.260828" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - inkscape:window-width="1366" - inkscape:window-height="744" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" - units="px" /> - <metadata - id="metadata7"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title /> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-796.36216)"> - <rect - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3.68787074;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4238" - width="252.31212" - height="252.31213" - x="1.8439354" - y="798.20612" - ry="0" /> - <rect - style="opacity:1;fill:#55b4d6;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4160" - width="23" - height="23" - x="4" - y="1025.3622" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1025.3622" - x="29" - height="23" - width="23" - id="rect4199" - style="opacity:1;fill:#55b4d6;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="1025.3622" - x="54" - height="23" - width="23" - id="rect4201" - style="opacity:1;fill:#55b4d6;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4203" - width="23" - height="23" - x="79" - y="1025.3622" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1025.3622" - x="129" - height="23" - width="23" - id="rect4205" - style="opacity:1;fill:#cf6050;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#cf6050;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4207" - width="23" - height="23" - x="154" - y="1025.3622" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#cf6050;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4209" - width="23" - height="23" - x="179" - y="1025.3622" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1025.3622" - x="204.00002" - height="23" - width="23" - id="rect4211" - style="opacity:1;fill:#cf6050;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="1025.3622" - x="104.00001" - height="23" - width="23" - id="rect4213" - style="opacity:1;fill:#cf6050;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#cf6050;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4215" - width="23" - height="23" - x="229" - y="1025.3622" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1000.3621" - x="4" - height="23" - width="23" - id="rect4237" - style="opacity:1;fill:#55b4d6;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#53c17d;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4239" - width="23" - height="23" - x="29" - y="1000.3621" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#53c17d;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4241" - width="23" - height="23" - x="54" - y="1000.3621" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1000.3621" - x="79" - height="23" - width="23" - id="rect4243" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4245" - width="23" - height="23" - x="129" - y="1000.3621" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1000.3621" - x="154" - height="23" - width="23" - id="rect4247" - style="opacity:1;fill:#e08d44;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="1000.3621" - x="179" - height="23" - width="23" - id="rect4249" - style="opacity:1;fill:#e08d44;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#e08d44;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4251" - width="23" - height="23" - x="204.00002" - y="1000.3621" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4253" - width="23" - height="23" - x="104.00001" - y="1000.3621" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1000.3621" - x="229" - height="23" - width="23" - id="rect4255" - style="opacity:1;fill:#cf6050;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="975.36206" - x="4" - height="23" - width="23" - id="rect4257" - style="opacity:1;fill:#55b4d6;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#53c17d;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4259" - width="23" - height="23" - x="29" - y="975.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4261" - width="23" - height="23" - x="54" - y="975.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="975.36206" - x="79" - height="23" - width="23" - id="rect4263" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4265" - width="23" - height="23" - x="129" - y="975.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="975.36206" - x="154" - height="23" - width="23" - id="rect4267" - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="975.36206" - x="179" - height="23" - width="23" - id="rect4269" - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4271" - width="23" - height="23" - x="204.00002" - y="975.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4273" - width="23" - height="23" - x="104.00001" - y="975.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="975.36206" - x="229" - height="23" - width="23" - id="rect4275" - style="opacity:1;fill:#cf6050;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4277" - width="23" - height="23" - x="4" - y="950.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="950.362" - x="29" - height="23" - width="23" - id="rect4279" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="950.362" - x="54" - height="23" - width="23" - id="rect4281" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4283" - width="23" - height="23" - x="79" - y="950.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="950.362" - x="129" - height="23" - width="23" - id="rect4285" - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4287" - width="23" - height="23" - x="154" - y="950.362" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4289" - width="23" - height="23" - x="179" - y="950.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="950.362" - x="204.00002" - height="23" - width="23" - id="rect4291" - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="950.362" - x="104.00001" - height="23" - width="23" - id="rect4293" - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#cf6050;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4295" - width="23" - height="23" - x="229" - y="950.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="925.36212" - x="4" - height="23" - width="23" - id="rect4297" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4299" - width="23" - height="23" - x="29" - y="925.36212" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4301" - width="23" - height="23" - x="54" - y="925.36212" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="925.36212" - x="79" - height="23" - width="23" - id="rect4303" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4305" - width="23" - height="23" - x="129" - y="925.36212" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="925.36212" - x="154" - height="23" - width="23" - id="rect4307" - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="925.36212" - x="179" - height="23" - width="23" - id="rect4309" - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#48c9a5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4311" - width="23" - height="23" - x="204.00002" - y="925.36212" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4313" - width="23" - height="23" - x="104.00001" - y="925.36212" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="925.36212" - x="229" - height="23" - width="23" - id="rect4315" - style="opacity:1;fill:#cf6050;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4317" - width="23" - height="23" - x="4" - y="900.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="900.36206" - x="29" - height="23" - width="23" - id="rect4319" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="900.36206" - x="54" - height="23" - width="23" - id="rect4321" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4323" - width="23" - height="23" - x="79" - y="900.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="900.36206" - x="129" - height="23" - width="23" - id="rect4325" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4327" - width="23" - height="23" - x="154" - y="900.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4329" - width="23" - height="23" - x="179" - y="900.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="900.36206" - x="204.00002" - height="23" - width="23" - id="rect4331" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="900.36206" - x="104.00001" - height="23" - width="23" - id="rect4333" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4335" - width="23" - height="23" - x="229" - y="900.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4337" - width="23" - height="23" - x="4" - y="875.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="875.36206" - x="29" - height="23" - width="23" - id="rect4339" - style="opacity:1;fill:#55b4d6;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="875.36206" - x="54" - height="23" - width="23" - id="rect4341" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4343" - width="23" - height="23" - x="79" - y="875.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="875.36206" - x="129" - height="23" - width="23" - id="rect4345" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#53c17d;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4347" - width="23" - height="23" - x="154" - y="875.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#53c17d;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4349" - width="23" - height="23" - x="179" - y="875.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="875.36206" - x="204.00002" - height="23" - width="23" - id="rect4351" - style="opacity:1;fill:#55b4d6;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="875.36206" - x="104.00001" - height="23" - width="23" - id="rect4353" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f1bc3a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4355" - width="23" - height="23" - x="229" - y="875.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="850.362" - x="4" - height="23" - width="23" - id="rect4357" - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#55b4d6;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4359" - width="23" - height="23" - x="29" - y="850.362" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4361" - width="23" - height="23" - x="54" - y="850.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="850.362" - x="79" - height="23" - width="23" - id="rect4363" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4365" - width="23" - height="23" - x="129" - y="850.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="850.362" - x="154" - height="23" - width="23" - id="rect4367" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="850.362" - x="179" - height="23" - width="23" - id="rect4369" - style="opacity:1;fill:#53c17d;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#55b4d6;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4371" - width="23" - height="23" - x="204.00002" - y="850.362" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4373" - width="23" - height="23" - x="104.00001" - y="850.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="850.362" - x="229" - height="23" - width="23" - id="rect4375" - style="opacity:1;fill:#f1bc3a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="825.36206" - x="4" - height="23" - width="23" - id="rect4377" - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#53c17d;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4379" - width="23" - height="23" - x="29" - y="825.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4381" - width="23" - height="23" - x="54" - y="825.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="825.36206" - x="79" - height="23" - width="23" - id="rect4383" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4385" - width="23" - height="23" - x="129" - y="825.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="825.36206" - x="154" - height="23" - width="23" - id="rect4387" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="825.36206" - x="179" - height="23" - width="23" - id="rect4389" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4391" - width="23" - height="23" - x="204.00002" - y="825.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4393" - width="23" - height="23" - x="104.00001" - y="825.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="825.36206" - x="229" - height="23" - width="23" - id="rect4395" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#da657b;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4397" - width="23" - height="23" - x="4" - y="800.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="800.36206" - x="29" - height="23" - width="23" - id="rect4399" - style="opacity:1;fill:#53c17d;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="800.36206" - x="54" - height="23" - width="23" - id="rect4401" - style="opacity:1;fill:#53c17d;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#7381b5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4403" - width="23" - height="23" - x="79" - y="800.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="800.36206" - x="129" - height="23" - width="23" - id="rect4405" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4407" - width="23" - height="23" - x="154" - y="800.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#e08d44;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4409" - width="23" - height="23" - x="179" - y="800.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="800.36206" - x="204.00002" - height="23" - width="23" - id="rect4411" - style="opacity:1;fill:#e08d44;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="800.36206" - x="104.00001" - height="23" - width="23" - id="rect4413" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#e08d44;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4415" - width="23" - height="23" - x="229" - y="800.36206" - ry="3.7123106" /> - <g - id="g4452" - transform="matrix(0.97656252,0,0,0.97656252,151.21587,-124.47349)"> - <rect - ry="3.8014059" - y="1008.2969" - x="-85.937706" - height="23.552" - width="23.552" - id="rect4442" - style="opacity:1;fill:#f1bc3a;fill-opacity:1;stroke:#000000;stroke-width:2.0480001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.8014059" - y="982.69684" - x="-85.937706" - height="23.552" - width="23.552" - id="rect4444" - style="opacity:1;fill:#f1bc3a;fill-opacity:1;stroke:#000000;stroke-width:2.0480001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <path - id="path4436" - d="m -63.396484,1019.4668 c -0.403493,-0.022 -0.715155,0.024 -0.923828,0.1445 -3.338799,1.9277 -3.338799,36.6251 0,38.5528 1.423092,0.8216 7.704486,-1.8689 14.4375,-5.5137 l 10.880859,18.8457 c 1.05299,1.8238 3.369526,2.4436 5.193359,1.3906 l 2.013672,-1.164 c 1.823833,-1.053 2.445569,-3.3676 1.392578,-5.1915 l -10.914062,-18.9042 c 5.784742,-3.6696 10.382812,-7.2347 10.382812,-8.7403 0,-3.6143 -26.410502,-19.0881 -32.46289,-19.4199 z" - style="opacity:1;fill:#4d4d4d;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - inkscape:connector-curvature="0" /> - </g> - </g> -</svg>
@@ -1,1004 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="256" - height="256" - viewBox="0 0 256 256" - id="svg2" - version="1.1" - inkscape:version="0.91 r13725" - sodipodi:docname="hack.svg"> - <defs - id="defs4"> - <filter - inkscape:collect="always" - style="color-interpolation-filters:sRGB" - id="filter4340" - x="-0.012734603" - width="1.0254692" - y="-0.07109144" - height="1.1421829"> - <feGaussianBlur - inkscape:collect="always" - stdDeviation="0.872055" - id="feGaussianBlur4342" /> - </filter> - </defs> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="0.99702056" - inkscape:cx="-86.616699" - inkscape:cy="106.1582" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - inkscape:window-width="1366" - inkscape:window-height="744" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" - units="px" /> - <metadata - id="metadata7"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title /> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-796.36216)"> - <rect - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3.68787074;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4238" - width="252.31212" - height="252.31213" - x="1.8439354" - y="798.20612" - ry="0" /> - <rect - style="opacity:1;fill:#cf6050;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4160" - width="23" - height="23" - x="4" - y="1025.3622" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1025.3622" - x="29" - height="23" - width="23" - id="rect4199" - style="opacity:1;fill:#cf6050;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#550000;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4203" - width="23" - height="23" - x="79" - y="1025.3622" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1025.3622" - x="129" - height="23" - width="23" - id="rect4205" - style="opacity:1;fill:#0066ff;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#0066ff;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4207" - width="23" - height="23" - x="154" - y="1025.3622" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4209" - width="23" - height="23" - x="179" - y="1025.3622" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1025.3622" - x="204.00002" - height="23" - width="23" - id="rect4211" - style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="1025.3622" - x="104.00001" - height="23" - width="23" - id="rect4213" - style="opacity:1;fill:#0066ff;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4215" - width="23" - height="23" - x="229" - y="1025.3622" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1000.3621" - x="4" - height="23" - width="23" - id="rect4237" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4239" - width="23" - height="23" - x="29" - y="1000.3621" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1000.3621" - x="79" - height="23" - width="23" - id="rect4243" - style="opacity:1;fill:#333333;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#87cdde;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4245" - width="23" - height="23" - x="129" - y="1000.3621" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1000.3621" - x="154" - height="23" - width="23" - id="rect4247" - style="opacity:1;fill:#3771c8;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="1000.3621" - x="179" - height="23" - width="23" - id="rect4249" - style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4251" - width="23" - height="23" - x="204.00002" - y="1000.3621" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#87cdde;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4253" - width="23" - height="23" - x="104.00001" - y="1000.3621" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="1000.3621" - x="229" - height="23" - width="23" - id="rect4255" - style="opacity:1;fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="975.36206" - x="4" - height="23" - width="23" - id="rect4257" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4259" - width="23" - height="23" - x="29" - y="975.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#005522;fill-opacity:1;stroke:#000000;stroke-width:0.31497964;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4261" - width="3.6222653" - height="3.6222653" - x="63.688866" - y="985.0509" - ry="0.58465105" /> - <rect - ry="3.7123106" - y="975.36206" - x="79" - height="23" - width="23" - id="rect4263" - style="opacity:1;fill:#005522;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#87cdde;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4265" - width="23" - height="23" - x="129" - y="975.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="975.36206" - x="154" - height="23" - width="23" - id="rect4267" - style="opacity:1;fill:#3771c8;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="975.36206" - x="179" - height="23" - width="23" - id="rect4269" - style="opacity:1;fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4271" - width="23" - height="23" - x="204.00002" - y="975.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#87cdde;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4273" - width="23" - height="23" - x="104.00001" - y="975.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="975.36206" - x="229" - height="23" - width="23" - id="rect4275" - style="opacity:1;fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#7381b5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4277" - width="23" - height="23" - x="4" - y="950.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="950.362" - x="29" - height="23" - width="23" - id="rect4279" - style="opacity:1;fill:#7381b5;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="1.478268" - y="957.28259" - x="60.92062" - height="9.158761" - width="9.158761" - id="rect4281" - style="opacity:1;fill:#005522;fill-opacity:1;stroke:#000000;stroke-width:0.79641408;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#005522;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4283" - width="23" - height="23" - x="79" - y="950.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="950.362" - x="129" - height="23" - width="23" - id="rect4285" - style="opacity:1;fill:#00ffcc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#00ffcc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4287" - width="23" - height="23" - x="154" - y="950.362" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4289" - width="23" - height="23" - x="179" - y="950.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="950.362" - x="204.00002" - height="23" - width="23" - id="rect4291" - style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="950.362" - x="104.00001" - height="23" - width="23" - id="rect4293" - style="opacity:1;fill:#00ffcc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4295" - width="23" - height="23" - x="229" - y="950.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="925.36212" - x="4" - height="23" - width="23" - id="rect4297" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#e08d44;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4299" - width="23" - height="23" - x="29" - y="925.36212" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#333333;fill-opacity:1;stroke:#000000;stroke-width:1.59880483;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4301" - width="18.386253" - height="18.386253" - x="56.306873" - y="927.66901" - ry="2.9676297" /> - <rect - ry="3.7123106" - y="925.36212" - x="79" - height="23" - width="23" - id="rect4303" - style="opacity:1;fill:#333333;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#00ffcc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4305" - width="23" - height="23" - x="129" - y="925.36212" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="925.36212" - x="154" - height="23" - width="23" - id="rect4307" - style="opacity:1;fill:#00ffcc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="925.36212" - x="179" - height="23" - width="23" - id="rect4309" - style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4311" - width="23" - height="23" - x="204.00002" - y="925.36212" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#00ffcc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4313" - width="23" - height="23" - x="104.00001" - y="925.36212" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="925.36212" - x="229" - height="23" - width="23" - id="rect4315" - style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#53c17d;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4317" - width="23" - height="23" - x="4" - y="900.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="900.36206" - x="29" - height="23" - width="23" - id="rect4319" - style="opacity:1;fill:#e08d44;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="900.36206" - x="54" - height="23" - width="23" - id="rect4321" - style="opacity:1;fill:#333333;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#aa8800;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4323" - width="23" - height="23" - x="79" - y="900.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="900.36206" - x="129" - height="23" - width="23" - id="rect4325" - style="opacity:1;fill:#00ffcc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#00ffcc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4327" - width="23" - height="23" - x="154" - y="900.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4329" - width="23" - height="23" - x="179" - y="900.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="900.36206" - x="204.00002" - height="23" - width="23" - id="rect4331" - style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="900.36206" - x="104.00001" - height="23" - width="23" - id="rect4333" - style="opacity:1;fill:#00ffcc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4335" - width="23" - height="23" - x="229" - y="900.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#53c17d;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4337" - width="23" - height="23" - x="4" - y="875.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="875.36206" - x="29" - height="23" - width="23" - id="rect4339" - style="opacity:1;fill:#53c17d;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="875.36206" - x="54" - height="23" - width="23" - id="rect4341" - style="opacity:1;fill:#333333;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#aa8800;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4343" - width="23" - height="23" - x="79" - y="875.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="875.36206" - x="129" - height="23" - width="23" - id="rect4345" - style="opacity:1;fill:#87cdde;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#87cdde;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4347" - width="23" - height="23" - x="154" - y="875.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4349" - width="23" - height="23" - x="179" - y="875.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="875.36206" - x="204.00002" - height="23" - width="23" - id="rect4351" - style="opacity:1;fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="875.36206" - x="104.00001" - height="23" - width="23" - id="rect4353" - style="opacity:1;fill:#87cdde;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4355" - width="23" - height="23" - x="229" - y="875.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="850.362" - x="4" - height="23" - width="23" - id="rect4357" - style="opacity:1;fill:#55b4d6;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4359" - width="23" - height="23" - x="29" - y="850.362" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#333333;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4361" - width="23" - height="23" - x="54" - y="850.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="850.362" - x="79" - height="23" - width="23" - id="rect4363" - style="opacity:1;fill:#aa8800;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#87cdde;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4365" - width="23" - height="23" - x="129" - y="850.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="850.362" - x="154" - height="23" - width="23" - id="rect4367" - style="opacity:1;fill:#87cdde;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="850.362" - x="179" - height="23" - width="23" - id="rect4369" - style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4371" - width="23" - height="23" - x="204.00002" - y="850.362" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#87cdde;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4373" - width="23" - height="23" - x="104.00001" - y="850.362" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="850.362" - x="229" - height="23" - width="23" - id="rect4375" - style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="825.36206" - x="4" - height="23" - width="23" - id="rect4377" - style="opacity:1;fill:#55b4d6;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4379" - width="23" - height="23" - x="29" - y="825.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#333333;fill-opacity:1;stroke:#000000;stroke-width:1.48936188;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4381" - width="17.127659" - height="17.127659" - x="56.936169" - y="828.29822" - ry="2.7644866" /> - <rect - ry="3.7123106" - y="825.36206" - x="79" - height="23" - width="23" - id="rect4383" - style="opacity:1;fill:#aa8800;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#0055d4;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4385" - width="23" - height="23" - x="129" - y="825.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="825.36206" - x="154" - height="23" - width="23" - id="rect4387" - style="opacity:1;fill:#0055d4;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="825.36206" - x="179" - height="23" - width="23" - id="rect4389" - style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4391" - width="23" - height="23" - x="204.00002" - y="825.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#87cdde;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4393" - width="23" - height="23" - x="104.00001" - y="825.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="825.36206" - x="229" - height="23" - width="23" - id="rect4395" - style="opacity:1;fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#55b4d6;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4397" - width="23" - height="23" - x="4" - y="800.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="800.36206" - x="29" - height="23" - width="23" - id="rect4399" - style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="2.1326039" - y="805.25568" - x="58.893616" - height="13.212766" - width="13.212766" - id="rect4401" - style="opacity:1;fill:#333333;fill-opacity:1;stroke:#000000;stroke-width:1.14893627;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#aa8800;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4403" - width="23" - height="23" - x="79" - y="800.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="800.36206" - x="129" - height="23" - width="23" - id="rect4405" - style="opacity:1;fill:#0055d4;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#37abc8;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4407" - width="23" - height="23" - x="154" - y="800.36206" - ry="3.7123106" /> - <rect - style="opacity:1;fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4409" - width="23" - height="23" - x="179" - y="800.36206" - ry="3.7123106" /> - <rect - ry="3.7123106" - y="800.36206" - x="204.00002" - height="23" - width="23" - id="rect4411" - style="opacity:1;fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - ry="3.7123106" - y="800.36206" - x="104.00001" - height="23" - width="23" - id="rect4413" - style="opacity:1;fill:#87cdde;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" /> - <rect - style="opacity:1;fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:2.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="rect4415" - width="23" - height="23" - x="229" - y="800.36206" - ry="3.7123106" /> - <g - id="g4240" - transform="matrix(0.83591472,-0.48597892,0.48597892,0.83591472,-688.29402,299.05674)"> - <path - inkscape:transform-center-y="1.998377" - inkscape:transform-center-x="8.4999859" - d="m 397.2578,948.4531 c -2.83872,1.31018 -21.24534,-19.0488 -24.31201,-19.6574 -3.06668,-0.6086 -27.85289,11.17849 -29.97615,8.88357 -2.12327,-2.29491 11.55131,-26.09192 11.18247,-29.19656 -0.36884,-3.10465 -19.23838,-23.03533 -17.71192,-25.76384 1.52647,-2.72851 28.38445,2.92311 31.22316,1.61293 2.83872,-1.31018 15.96291,-25.4151 19.02958,-24.8065 3.06668,0.6086 5.99124,27.8985 8.1145,30.19341 2.12327,2.29492 29.10401,7.32793 29.47285,10.43258 0.36884,3.10465 -24.68166,14.31911 -26.20812,17.04762 -1.52647,2.72851 2.02435,29.94401 -0.81436,31.25419 z" - inkscape:randomized="0" - inkscape:rounded="0.1" - inkscape:flatsided="false" - sodipodi:arg2="1.7667071" - sodipodi:arg1="1.1383886" - sodipodi:r2="23.540443" - sodipodi:r1="47.080887" - sodipodi:cy="905.70557" - sodipodi:cx="377.52817" - sodipodi:sides="5" - id="path4234" - style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - sodipodi:type="star" /> - <path - transform="matrix(0.4060959,-0.86054354,0.86054354,0.4060959,-554.98762,861.9104)" - sodipodi:type="star" - style="opacity:1;fill:#ff7f2a;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - id="path4236" - sodipodi:sides="6" - sodipodi:cx="377.30496" - sodipodi:cy="907.68127" - sodipodi:r1="40.617931" - sodipodi:r2="20.308966" - sodipodi:arg1="1.1383886" - sodipodi:arg2="1.6619874" - inkscape:flatsided="false" - inkscape:rounded="0.1" - inkscape:randomized="0" - d="m 394.32624,944.56071 c -2.28526,1.05473 -16.36425,-16.42565 -18.87071,-16.65485 -2.50646,-0.22921 -19.5224,14.40771 -21.57846,12.95599 -2.05605,-1.45173 6.04291,-22.38468 4.98818,-24.66994 -1.05474,-2.28526 -22.23865,-9.70304 -22.00945,-12.2095 0.2292,-2.50646 22.40715,-5.95902 23.85888,-8.01508 1.45172,-2.05606 -2.71625,-24.11075 -0.43099,-25.16549 2.28526,-1.05473 16.36424,16.42565 18.8707,16.65485 2.50646,0.22921 19.5224,-14.40771 21.57846,-12.95599 2.05606,1.45173 -6.04291,22.38468 -4.98817,24.66994 1.05473,2.28526 22.23865,9.70303 22.00945,12.20949 -0.22921,2.50646 -22.40716,5.95903 -23.85888,8.01509 -1.45173,2.05606 2.71625,24.11075 0.43099,25.16549 z" - inkscape:transform-center-x="3.8256176" - inkscape:transform-center-y="9.8877589" /> - <path - inkscape:transform-center-y="6.6045772" - inkscape:transform-center-x="-1.2922666" - d="m 394.32624,944.56071 c -2.1788,1.00559 -14.95481,-16.53387 -17.35415,-16.5732 -2.39934,-0.0393 -15.74337,17.07195 -17.88804,15.99548 -2.14466,-1.07647 3.60252,-22.00083 2.13731,-23.90124 -1.46521,-1.9004 -23.16322,-1.66447 -23.65878,-4.01241 -0.49555,-2.34793 19.44709,-10.90073 20.01934,-13.23116 0.57224,-2.33043 -13.1407,-19.14752 -11.61398,-20.99887 1.52671,-1.85136 20.64759,8.40785 22.82639,7.40225 2.17879,-1.0056 6.77704,-22.21209 9.17638,-22.17276 2.39934,0.0393 6.30004,21.38515 8.44471,22.46162 2.14466,1.07647 21.59153,-8.55051 23.05674,-6.65011 1.46521,1.9004 -12.79157,18.259 -12.29601,20.60693 0.49556,2.34793 20.14715,11.54979 19.5749,13.88022 -0.57224,2.33043 -22.25086,1.38344 -23.77758,3.2348 -1.52671,1.85135 3.53156,22.95285 1.35277,23.95845 z" - inkscape:randomized="0" - inkscape:rounded="0.1" - inkscape:flatsided="false" - sodipodi:arg2="1.5871876" - sodipodi:arg1="1.1383886" - sodipodi:r2="20.308966" - sodipodi:r1="40.617931" - sodipodi:cy="907.68127" - sodipodi:cx="377.30496" - sodipodi:sides="7" - id="path4238" - style="opacity:1;fill:#ffd42a;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" - sodipodi:type="star" - transform="matrix(-0.07318693,-0.59956127,0.59956127,-0.07318693,-137.92789,1196.4736)" /> - </g> - <g - id="g4344" - transform="matrix(0.94054186,-0.3396778,0.3396778,0.94054186,-541.22159,-72.93379)"> - <g - id="text4245" - style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:0.25568183;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter4340)"> - <path - id="path4254" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'The Next Font';-inkscape-font-specification:'The Next Font';fill:#000000;fill-opacity:0.25568183" - d="m 232.27348,1164.9911 10,0 0,6.52 -16.56,0 0,-29.44 6.56,0 0,22.92 0,0 z" - inkscape:connector-curvature="0" /> - <path - id="path4256" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'The Next Font';-inkscape-font-specification:'The Next Font';fill:#000000;fill-opacity:0.25568183" - d="m 264.5191,1148.6311 -12.48,0 0,4.92 12.48,0 0,6.52 -12.48,0 0,4.92 12.48,0 0,6.52 -14.96,0 0,0 -4.08,0 0,-29.44 6.56,0 0,0.04 12.48,0 0,6.52 0,0 z" - inkscape:connector-curvature="0" /> - <path - id="path4258" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'The Next Font';-inkscape-font-specification:'The Next Font';fill:#000000;fill-opacity:0.25568183" - d="m 284.66473,1142.1111 6.72,0 -6.96,29.4 -9.76,0 -6.92,-29.4 6.72,0 5.08,21.6 5.12,-21.6 0,0 z" - inkscape:connector-curvature="0" /> - <path - id="path4260" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'The Next Font';-inkscape-font-specification:'The Next Font';fill:#000000;fill-opacity:0.25568183" - d="m 313.65973,1148.6311 -12.48,0 0,4.92 12.48,0 0,6.52 -12.48,0 0,4.92 12.48,0 0,6.52 -14.96,0 0,0 -4.08,0 0,-29.44 6.56,0 0,0.04 12.48,0 0,6.52 0,0 z" - inkscape:connector-curvature="0" /> - <path - id="path4262" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'The Next Font';-inkscape-font-specification:'The Next Font';fill:#000000;fill-opacity:0.25568183" - d="m 323.44535,1164.9911 10,0 0,6.52 -16.56,0 0,-29.44 6.56,0 0,22.92 0,0 z" - inkscape:connector-curvature="0" /> - <path - id="path4264" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'The Next Font';-inkscape-font-specification:'The Next Font';fill:#000000;fill-opacity:0.25568183" - d="m 355.0766,1171.5111 q -2.32,0 -4.36,-0.88 -2,-0.88 -3.52,-2.4 -1.52,-1.52 -2.4,-3.52 -0.88,-2.04 -0.88,-4.36 l 0,-18.24 6.56,0 0,18.24 q 0,0.96 0.36,1.8 0.36,0.84 1,1.48 0.64,0.64 1.44,1 0.84,0.36 1.8,0.36 0.96,0 1.8,-0.36 0.84,-0.36 1.48,-1 0.64,-0.64 1,-1.48 0.36,-0.84 0.36,-1.8 l 0,-18.24 6.52,0 0,18.24 q 0,2.32 -0.88,4.36 -0.88,2 -2.4,3.52 -1.52,1.52 -3.56,2.4 -2,0.88 -4.32,0.88 z" - inkscape:connector-curvature="0" /> - <path - id="path4266" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'The Next Font';-inkscape-font-specification:'The Next Font';fill:#000000;fill-opacity:0.25568183" - d="m 379.26348,1142.1111 q 2.24,0 4.2,0.84 1.96,0.84 3.44,2.32 1.48,1.48 2.32,3.44 0.84,1.96 0.84,4.16 0,2.24 -0.84,4.2 -0.84,1.96 -2.32,3.44 -1.48,1.48 -3.44,2.32 -1.96,0.84 -4.2,0.84 l -3.24,0 0,7.84 -6.56,0 0,-29.4 9.8,0 z m 0,15.04 q 0.88,0 1.64,-0.32 0.8,-0.36 1.36,-0.92 0.6,-0.6 0.92,-1.36 0.36,-0.8 0.36,-1.68 0,-0.88 -0.36,-1.64 -0.32,-0.76 -0.92,-1.32 -0.56,-0.6 -1.36,-0.92 -0.76,-0.36 -1.64,-0.36 l -3.24,0 0,8.52 3.24,0 z" - inkscape:connector-curvature="0" /> - </g> - <g - transform="matrix(0.9318622,0,0,0.9318622,20.978842,78.821195)" - style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - id="g4268"> - <path - inkscape:connector-curvature="0" - d="m 232.27348,1164.9911 10,0 0,6.52 -16.56,0 0,-29.44 6.56,0 0,22.92 0,0 z" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'The Next Font';-inkscape-font-specification:'The Next Font';fill:#ffffff" - id="path4270" /> - <path - inkscape:connector-curvature="0" - d="m 264.5191,1148.6311 -12.48,0 0,4.92 12.48,0 0,6.52 -12.48,0 0,4.92 12.48,0 0,6.52 -14.96,0 0,0 -4.08,0 0,-29.44 6.56,0 0,0.04 12.48,0 0,6.52 0,0 z" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'The Next Font';-inkscape-font-specification:'The Next Font';fill:#ffffff" - id="path4272" /> - <path - inkscape:connector-curvature="0" - d="m 284.66473,1142.1111 6.72,0 -6.96,29.4 -9.76,0 -6.92,-29.4 6.72,0 5.08,21.6 5.12,-21.6 0,0 z" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'The Next Font';-inkscape-font-specification:'The Next Font';fill:#ffffff" - id="path4274" /> - <path - inkscape:connector-curvature="0" - d="m 313.65973,1148.6311 -12.48,0 0,4.92 12.48,0 0,6.52 -12.48,0 0,4.92 12.48,0 0,6.52 -14.96,0 0,0 -4.08,0 0,-29.44 6.56,0 0,0.04 12.48,0 0,6.52 0,0 z" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'The Next Font';-inkscape-font-specification:'The Next Font';fill:#ffffff" - id="path4276" /> - <path - inkscape:connector-curvature="0" - d="m 323.44535,1164.9911 10,0 0,6.52 -16.56,0 0,-29.44 6.56,0 0,22.92 0,0 z" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'The Next Font';-inkscape-font-specification:'The Next Font';fill:#ffffff" - id="path4278" /> - <path - inkscape:connector-curvature="0" - d="m 355.0766,1171.5111 q -2.32,0 -4.36,-0.88 -2,-0.88 -3.52,-2.4 -1.52,-1.52 -2.4,-3.52 -0.88,-2.04 -0.88,-4.36 l 0,-18.24 6.56,0 0,18.24 q 0,0.96 0.36,1.8 0.36,0.84 1,1.48 0.64,0.64 1.44,1 0.84,0.36 1.8,0.36 0.96,0 1.8,-0.36 0.84,-0.36 1.48,-1 0.64,-0.64 1,-1.48 0.36,-0.84 0.36,-1.8 l 0,-18.24 6.52,0 0,18.24 q 0,2.32 -0.88,4.36 -0.88,2 -2.4,3.52 -1.52,1.52 -3.56,2.4 -2,0.88 -4.32,0.88 z" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'The Next Font';-inkscape-font-specification:'The Next Font';fill:#ffff00;fill-opacity:1" - id="path4280" /> - <path - inkscape:connector-curvature="0" - d="m 379.26348,1142.1111 q 2.24,0 4.2,0.84 1.96,0.84 3.44,2.32 1.48,1.48 2.32,3.44 0.84,1.96 0.84,4.16 0,2.24 -0.84,4.2 -0.84,1.96 -2.32,3.44 -1.48,1.48 -3.44,2.32 -1.96,0.84 -4.2,0.84 l -3.24,0 0,7.84 -6.56,0 0,-29.4 9.8,0 z m 0,15.04 q 0.88,0 1.64,-0.32 0.8,-0.36 1.36,-0.92 0.6,-0.6 0.92,-1.36 0.36,-0.8 0.36,-1.68 0,-0.88 -0.36,-1.64 -0.32,-0.76 -0.92,-1.32 -0.56,-0.6 -1.36,-0.92 -0.76,-0.36 -1.64,-0.36 l -3.24,0 0,8.52 3.24,0 z" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'The Next Font';-inkscape-font-specification:'The Next Font';fill:#ffff00;fill-opacity:1" - id="path4282" /> - </g> - </g> - </g> -</svg>
@@ -1,685 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="1366" - height="500" - viewBox="0 0 1366 500" - id="svg2" - version="1.1" - inkscape:version="0.91 r13725" - sodipodi:docname="header.svg"> - <defs - id="defs4"> - <linearGradient - id="linearGradient4351" - inkscape:collect="always"> - <stop - id="stop4353" - offset="0" - style="stop-color:#ff5497;stop-opacity:0.27450982" /> - <stop - id="stop4355" - offset="1" - style="stop-color:#ff5497;stop-opacity:0.06666667" /> - </linearGradient> - <linearGradient - id="linearGradient4345" - inkscape:collect="always"> - <stop - id="stop4347" - offset="0" - style="stop-color:#ff5497;stop-opacity:0.27450982" /> - <stop - id="stop4349" - offset="1" - style="stop-color:#ff5497;stop-opacity:0.06666667" /> - </linearGradient> - <linearGradient - id="linearGradient4295" - inkscape:collect="always"> - <stop - id="stop4297" - offset="0" - style="stop-color:#ff6e07;stop-opacity:0.27450982" /> - <stop - id="stop4299" - offset="1" - style="stop-color:#ff541c;stop-opacity:0.06666667" /> - </linearGradient> - <linearGradient - id="linearGradient4289" - inkscape:collect="always"> - <stop - id="stop4291" - offset="0" - style="stop-color:#ff6e07;stop-opacity:0.27450982" /> - <stop - id="stop4293" - offset="1" - style="stop-color:#ff541c;stop-opacity:0.06666667" /> - </linearGradient> - <linearGradient - id="linearGradient4283" - inkscape:collect="always"> - <stop - id="stop4285" - offset="0" - style="stop-color:#ff6e07;stop-opacity:0.27450982" /> - <stop - id="stop4287" - offset="1" - style="stop-color:#ff541c;stop-opacity:0.06666667" /> - </linearGradient> - <linearGradient - id="linearGradient4277" - inkscape:collect="always"> - <stop - id="stop4279" - offset="0" - style="stop-color:#ff6e07;stop-opacity:0.27450982" /> - <stop - id="stop4281" - offset="1" - style="stop-color:#ff541c;stop-opacity:0.06666667" /> - </linearGradient> - <linearGradient - inkscape:collect="always" - xlink:href="#linearGradient4313" - id="linearGradient4319" - x1="-441.97687" - y1="1012.8427" - x2="-441.97687" - y2="1072.3632" - gradientUnits="userSpaceOnUse" - gradientTransform="translate(1.8037136,-10.091205)" /> - <linearGradient - inkscape:collect="always" - id="linearGradient4313"> - <stop - style="stop-color:#005497;stop-opacity:0.27272728" - offset="0" - id="stop4315" /> - <stop - style="stop-color:#005497;stop-opacity:0.06818182" - offset="1" - id="stop4317" /> - </linearGradient> - <filter - inkscape:collect="always" - style="color-interpolation-filters:sRGB" - id="filter4309" - x="-0.040038537" - width="1.0800771" - y="-0.095373832" - height="1.1907477"> - <feGaussianBlur - inkscape:collect="always" - stdDeviation="3.6669538" - id="feGaussianBlur4311" /> - </filter> - <linearGradient - inkscape:collect="always" - xlink:href="#linearGradient4313" - id="linearGradient4160" - gradientUnits="userSpaceOnUse" - gradientTransform="translate(1.8037136,-10.091205)" - x1="-441.97687" - y1="1012.8427" - x2="-441.97687" - y2="1072.3632" /> - <linearGradient - inkscape:collect="always" - xlink:href="#linearGradient4295" - id="linearGradient4188" - gradientUnits="userSpaceOnUse" - gradientTransform="translate(1.8037136,-10.091205)" - x1="-441.97687" - y1="1012.8427" - x2="-441.97687" - y2="1072.3632" /> - <linearGradient - inkscape:collect="always" - xlink:href="#linearGradient4289" - id="linearGradient4202" - gradientUnits="userSpaceOnUse" - gradientTransform="translate(1.8037136,-10.091205)" - x1="-441.97687" - y1="1012.8427" - x2="-441.97687" - y2="1072.3632" /> - <linearGradient - inkscape:collect="always" - xlink:href="#linearGradient4283" - id="linearGradient4216" - gradientUnits="userSpaceOnUse" - gradientTransform="translate(1.8037136,-10.091205)" - x1="-441.97687" - y1="1012.8427" - x2="-441.97687" - y2="1072.3632" /> - <linearGradient - inkscape:collect="always" - xlink:href="#linearGradient4313" - id="linearGradient4230" - gradientUnits="userSpaceOnUse" - gradientTransform="translate(1.8037136,-10.091205)" - x1="-441.97687" - y1="1012.8427" - x2="-441.97687" - y2="1072.3632" /> - <linearGradient - inkscape:collect="always" - xlink:href="#linearGradient4313" - id="linearGradient4245" - gradientUnits="userSpaceOnUse" - gradientTransform="translate(1.8037136,-10.091205)" - x1="-441.97687" - y1="1012.8427" - x2="-441.97687" - y2="1072.3632" /> - <linearGradient - inkscape:collect="always" - xlink:href="#linearGradient4277" - id="linearGradient4259" - gradientUnits="userSpaceOnUse" - gradientTransform="translate(1.8037136,-10.091205)" - x1="-441.97687" - y1="1012.8427" - x2="-441.97687" - y2="1072.3632" /> - <linearGradient - inkscape:collect="always" - xlink:href="#linearGradient4345" - id="linearGradient4275" - gradientUnits="userSpaceOnUse" - gradientTransform="translate(1.8037136,-10.091205)" - x1="-441.97687" - y1="1012.8427" - x2="-441.97687" - y2="1072.3632" /> - <linearGradient - inkscape:collect="always" - xlink:href="#linearGradient4351" - id="linearGradient4314" - gradientUnits="userSpaceOnUse" - gradientTransform="translate(1.8037136,-10.091205)" - x1="-441.97687" - y1="1012.8427" - x2="-441.97687" - y2="1072.3632" /> - </defs> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="0.35" - inkscape:cx="1363.1932" - inkscape:cy="104.84659" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - units="px" - inkscape:window-width="1366" - inkscape:window-height="744" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" /> - <metadata - id="metadata7"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-552.36216)"> - <g - id="g4261" - transform="matrix(0.71111111,0,0,0.71111111,484.34789,-94.871879)"> - <g - transform="matrix(1.3547235,0,0,1.3547235,160.26025,20.43346)" - id="g4240"> - <path - style="opacity:1;fill:url(#linearGradient4319);fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter4309)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - id="path4238" - inkscape:connector-curvature="0" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" /> - <path - style="opacity:1;fill:#00aad4;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - id="rect4234" - inkscape:connector-curvature="0" - sodipodi:nodetypes="sssssssss" /> - <rect - style="opacity:1;fill:#00ccff;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect4232" - width="113.15113" - height="111.41566" - x="-678.87305" - y="1073.5972" - ry="12.143711" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" /> - <rect - style="opacity:1;fill:#55ddff;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect4230" - width="112.68221" - height="112.81577" - x="388.7005" - y="942.09778" - ry="14.66605" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" /> - </g> - </g> - <g - id="g4176" - transform="matrix(0.71111111,0,0,0.71111111,1419.7491,-98.91249)"> - <g - transform="matrix(1.3547235,0,0,1.3547235,160.26025,20.43346)" - id="g4178"> - <path - style="opacity:1;fill:url(#linearGradient4188);fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter4309)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - id="path4180" - inkscape:connector-curvature="0" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" /> - <path - style="opacity:1;fill:#e67e22;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - id="path4182" - inkscape:connector-curvature="0" - sodipodi:nodetypes="sssssssss" /> - <rect - style="opacity:1;fill:#ffa522;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect4184" - width="113.15113" - height="111.41566" - x="-678.87305" - y="1073.5972" - ry="12.143711" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" /> - <rect - style="opacity:1;fill:#ffc647;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect4186" - width="112.68221" - height="112.81577" - x="388.7005" - y="942.09778" - ry="14.66605" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" /> - </g> - </g> - <g - transform="matrix(0.71111111,0,0,0.71111111,1571.2721,-30.222118)" - id="g4190"> - <g - id="g4192" - transform="matrix(1.3547235,0,0,1.3547235,160.26025,20.43346)"> - <path - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" - inkscape:connector-curvature="0" - id="path4194" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - style="opacity:1;fill:url(#linearGradient4202);fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter4309)" /> - <path - sodipodi:nodetypes="sssssssss" - inkscape:connector-curvature="0" - id="path4196" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - style="opacity:1;fill:#e67e22;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - <rect - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" - ry="12.143711" - y="1073.5972" - x="-678.87305" - height="111.41566" - width="113.15113" - id="rect4198" - style="opacity:1;fill:#ffa522;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - <rect - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" - ry="14.66605" - y="942.09778" - x="388.7005" - height="112.81577" - width="112.68221" - id="rect4200" - style="opacity:1;fill:#ffc647;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - </g> - </g> - <g - id="g4204" - transform="matrix(0.71111111,0,0,0.71111111,1425.8101,42.508866)"> - <g - transform="matrix(1.3547235,0,0,1.3547235,160.26025,20.43346)" - id="g4206"> - <path - style="opacity:1;fill:url(#linearGradient4216);fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter4309)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - id="path4208" - inkscape:connector-curvature="0" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" /> - <path - style="opacity:1;fill:#e67e22;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - id="path4210" - inkscape:connector-curvature="0" - sodipodi:nodetypes="sssssssss" /> - <rect - style="opacity:1;fill:#ffa522;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect4212" - width="113.15113" - height="111.41566" - x="-678.87305" - y="1073.5972" - ry="12.143711" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" /> - <rect - style="opacity:1;fill:#ffc647;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect4214" - width="112.68221" - height="112.81577" - x="388.7005" - y="942.09778" - ry="14.66605" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" /> - </g> - </g> - <g - transform="matrix(0.71111111,0,0,0.71111111,480.30728,32.407332)" - id="g4148"> - <g - id="g4150" - transform="matrix(1.3547235,0,0,1.3547235,160.26025,20.43346)"> - <path - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" - inkscape:connector-curvature="0" - id="path4152" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - style="opacity:1;fill:url(#linearGradient4160);fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter4309)" /> - <path - sodipodi:nodetypes="sssssssss" - inkscape:connector-curvature="0" - id="path4154" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - style="opacity:1;fill:#00aad4;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - <rect - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" - ry="12.143711" - y="1073.5972" - x="-678.87305" - height="111.41566" - width="113.15113" - id="rect4156" - style="opacity:1;fill:#00ccff;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - <rect - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" - ry="14.66605" - y="942.09778" - x="388.7005" - height="112.81577" - width="112.68221" - id="rect4158" - style="opacity:1;fill:#55ddff;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - </g> - </g> - <g - id="g4232" - transform="matrix(0.71111111,0,0,0.71111111,643.25873,105.2819)"> - <g - transform="matrix(1.3547235,0,0,1.3547235,160.26025,20.43346)" - id="g4234"> - <path - style="opacity:1;fill:url(#linearGradient4245);fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter4309)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - id="path4236" - inkscape:connector-curvature="0" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" /> - <path - style="opacity:1;fill:#00aad4;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - id="path4239" - inkscape:connector-curvature="0" - sodipodi:nodetypes="sssssssss" /> - <rect - style="opacity:1;fill:#00ccff;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect4241" - width="113.15113" - height="111.41566" - x="-678.87305" - y="1073.5972" - ry="12.143711" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" /> - <rect - style="opacity:1;fill:#55ddff;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect4243" - width="112.68221" - height="112.81577" - x="388.7005" - y="942.09778" - ry="14.66605" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" /> - </g> - </g> - <g - id="g4218" - transform="matrix(0.71111111,0,0,0.71111111,480.30728,145.54442)"> - <g - transform="matrix(1.3547235,0,0,1.3547235,160.26025,20.43346)" - id="g4220"> - <path - style="opacity:1;fill:url(#linearGradient4230);fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter4309)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - id="path4222" - inkscape:connector-curvature="0" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" /> - <path - style="opacity:1;fill:#00aad4;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - id="path4224" - inkscape:connector-curvature="0" - sodipodi:nodetypes="sssssssss" /> - <rect - style="opacity:1;fill:#00ccff;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect4226" - width="113.15113" - height="111.41566" - x="-678.87305" - y="1073.5972" - ry="12.143711" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" /> - <rect - style="opacity:1;fill:#55ddff;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect4228" - width="112.68221" - height="112.81577" - x="388.7005" - y="942.09778" - ry="14.66605" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" /> - </g> - </g> - <g - transform="matrix(0.71111111,0,0,0.71111111,1538.9472,139.4835)" - id="g4247"> - <g - id="g4249" - transform="matrix(1.3547235,0,0,1.3547235,160.26025,20.43346)"> - <path - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" - inkscape:connector-curvature="0" - id="path4251" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - style="opacity:1;fill:url(#linearGradient4259);fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter4309)" /> - <path - sodipodi:nodetypes="sssssssss" - inkscape:connector-curvature="0" - id="path4253" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - style="opacity:1;fill:#e67e22;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - <rect - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" - ry="12.143711" - y="1073.5972" - x="-678.87305" - height="111.41566" - width="113.15113" - id="rect4255" - style="opacity:1;fill:#ffa522;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - <rect - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" - ry="14.66605" - y="942.09778" - x="388.7005" - height="112.81577" - width="112.68221" - id="rect4257" - style="opacity:1;fill:#ffc647;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - </g> - </g> - <g - transform="matrix(0.71111111,0,0,0.71111111,834.34788,-96.30045)" - id="g4263"> - <g - id="g4265" - transform="matrix(1.3547235,0,0,1.3547235,160.26025,20.43346)"> - <path - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" - inkscape:connector-curvature="0" - id="path4267" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - style="opacity:1;fill:url(#linearGradient4275);fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter4309)" /> - <path - sodipodi:nodetypes="sssssssss" - inkscape:connector-curvature="0" - id="path4269" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - style="opacity:1;fill:#d80073;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - <rect - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" - ry="12.143711" - y="1073.5972" - x="-678.87305" - height="111.41566" - width="113.15113" - id="rect4271" - style="opacity:1;fill:#e8338b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - <rect - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" - ry="14.66605" - y="942.09778" - x="388.7005" - height="112.81577" - width="112.68221" - id="rect4273" - style="opacity:1;fill:#e85bbe;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - </g> - </g> - <g - id="g4301" - transform="matrix(0.71111111,0,0,0.71111111,975.77645,-44.871879)"> - <g - transform="matrix(1.3547235,0,0,1.3547235,160.26025,20.43346)" - id="g4303"> - <path - style="opacity:1;fill:url(#linearGradient4314);fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter4309)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - id="path4305" - inkscape:connector-curvature="0" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" /> - <path - style="opacity:1;fill:#d80073;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - id="path4307" - inkscape:connector-curvature="0" - sodipodi:nodetypes="sssssssss" /> - <rect - style="opacity:1;fill:#e8338b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect4309" - width="113.15113" - height="111.41566" - x="-678.87305" - y="1073.5972" - ry="12.143711" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" /> - <rect - style="opacity:1;fill:#e85bbe;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect4311" - width="112.68221" - height="112.81577" - x="388.7005" - y="942.09778" - ry="14.66605" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" /> - </g> - </g> - <g - style="font-style:normal;font-weight:normal;font-size:89.03149414px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - id="text4316"> - <path - d="m 776.6785,662.47113 0,-63.56849 -12.01925,0 -2.40385,3.65029 10.77281,0 0,59.9182 3.65029,0 z" - style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:GeosansLight;-inkscape-font-specification:'GeosansLight Medium'" - id="path4323" /> - <path - d="m 834.40499,630.1527 q 0,-12.55344 -5.25286,-21.99078 -6.41026,-11.307 -18.07339,-11.307 -11.66313,0 -18.07339,11.307 -5.25286,9.25927 -5.25286,21.99078 0,12.64247 5.25286,21.90175 6.41026,11.307 18.07339,11.307 11.75216,0 18.16242,-11.21797 5.16383,-9.17025 5.16383,-21.99078 z m -3.82835,0 q 0,12.19731 -4.89674,20.29918 -5.43092,9.17024 -14.60116,9.17024 -9.8825,0 -15.22439,-10.32765 -4.18448,-8.1909 -4.18448,-19.14177 0,-12.01925 4.89674,-20.38821 5.51995,-9.25928 14.51213,-9.25928 10.06056,0 15.31342,10.32765 4.18448,8.1909 4.18448,19.31984 z" - style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:GeosansLight;-inkscape-font-specification:'GeosansLight Medium'" - id="path4325" /> - <path - d="m 850.40771,662.47113 0,-63.56849 -12.01925,0 -2.40385,3.65029 10.77281,0 0,59.9182 3.65029,0 z" - style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:GeosansLight;-inkscape-font-specification:'GeosansLight Medium'" - id="path4327" /> - <path - d="m 908.1342,630.1527 q 0,-12.55344 -5.25286,-21.99078 -6.41027,-11.307 -18.07339,-11.307 -11.66313,0 -18.0734,11.307 -5.25286,9.25927 -5.25286,21.99078 0,12.64247 5.25286,21.90175 6.41027,11.307 18.0734,11.307 11.75215,0 18.16242,-11.21797 5.16383,-9.17025 5.16383,-21.99078 z m -3.82836,0 q 0,12.19731 -4.89673,20.29918 -5.43092,9.17024 -14.60116,9.17024 -9.8825,0 -15.22439,-10.32765 -4.18448,-8.1909 -4.18448,-19.14177 0,-12.01925 4.89673,-20.38821 5.51995,-9.25928 14.51214,-9.25928 10.06055,0 15.31341,10.32765 4.18448,8.1909 4.18448,19.31984 z" - style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:GeosansLight;-inkscape-font-specification:'GeosansLight Medium'" - id="path4329" /> - <path - d="m 920.38645,649.29447 0,-50.39183 -3.56126,0 0,50.39183 3.56126,0 z m 1.51354,10.32765 q -0.26709,-1.51353 -0.97935,-2.22578 -0.97934,-0.97935 -2.22578,-0.97935 -3.20514,0.53419 -3.20514,3.20513 0,3.20514 3.20514,3.20514 3.20513,-0.53419 3.20513,-3.20514 z" - style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:GeosansLight;-inkscape-font-specification:'GeosansLight Medium'" - id="path4331" /> - <path - d="m 792.5261,773.7605 -33.11971,-33.20875 30.44877,-30.35974 -5.16383,0 -28.40104,28.22298 0,-28.22298 -3.6503,0 0,63.56849 3.6503,0 0,-30.18168 0.53418,-0.53419 30.62684,30.71587 5.07479,0 z" - style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:GeosansLight;-inkscape-font-specification:'GeosansLight Medium'" - id="path4333" /> - <path - d="m 803.78303,773.7605 0,-65.88331 -3.56126,0 0,65.88331 3.56126,0 z" - style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:GeosansLight;-inkscape-font-specification:'GeosansLight Medium'" - id="path4335" /> - <path - d="m 851.45104,755.42001 q 0,-8.01283 -5.51995,-13.53279 -5.51995,-5.51995 -13.44376,-5.51995 -8.01283,0 -13.44375,5.60898 -5.51995,5.51996 -5.51995,13.44376 0,8.01283 5.51995,13.53279 5.43092,5.43092 13.44375,5.43092 8.01284,0 13.53279,-5.51995 5.43092,-5.43093 5.43092,-13.44376 z m -3.56126,0 q 0,6.41027 -4.5406,10.95087 -4.45158,4.45158 -10.86185,4.45158 -6.32123,0 -10.86184,-4.54061 -4.5406,-4.5406 -4.5406,-10.86184 0,-6.2322 4.62963,-10.86184 4.54061,-4.62964 10.77281,-4.62964 6.4993,0 10.95088,4.54061 4.45157,4.45157 4.45157,10.95087 z" - style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:GeosansLight;-inkscape-font-specification:'GeosansLight Medium'" - id="path4337" /> - <path - d="m 899.61847,755.42001 q 0,-8.01283 -5.51995,-13.53279 -5.51996,-5.51995 -13.44376,-5.51995 -8.01283,0 -13.44375,5.60898 -5.51996,5.51996 -5.51996,13.44376 0,8.01283 5.51996,13.53279 5.43092,5.43092 13.44375,5.43092 8.01284,0 13.53279,-5.51995 5.43092,-5.43093 5.43092,-13.44376 z m -3.56126,0 q 0,6.41027 -4.54061,10.95087 -4.45157,4.45158 -10.86184,4.45158 -6.32123,0 -10.86184,-4.54061 -4.54061,-4.5406 -4.54061,-10.86184 0,-6.2322 4.62964,-10.86184 4.54061,-4.62964 10.77281,-4.62964 6.4993,0 10.95088,4.54061 4.45157,4.45157 4.45157,10.95087 z" - style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:GeosansLight;-inkscape-font-specification:'GeosansLight Medium'" - id="path4339" /> - <path - d="m 937.19393,773.7605 0,-22.34691 q 0,-7.03349 -3.65029,-10.95087 -3.65029,-3.91739 -10.50571,-3.91739 -8.10187,0 -10.59475,4.89674 l 0,-4.45158 -3.65029,0 0,36.77001 3.65029,0 0,-22.34691 q 0,-5.25286 2.75998,-8.27993 2.67094,-3.02707 7.83477,-3.02707 10.50571,0 10.50571,11.66313 l 0,21.99078 3.65029,0 z" - style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:GeosansLight;-inkscape-font-specification:'GeosansLight Medium'" - id="path4341" /> - <path - d="m 949.78423,727.46412 q -0.71225,-2.93804 -2.93804,-2.93804 -2.93803,0.53419 -2.93803,2.93804 0.089,1.33547 0.89031,2.13676 1.06838,0.80128 2.04772,0.80128 2.93804,0 2.93804,-2.93804 z m -1.15741,46.29638 0,-36.77001 -3.65029,0 0,36.77001 3.65029,0 z" - style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:GeosansLight;-inkscape-font-specification:'GeosansLight Medium'" - id="path4343" /> - </g> - </g> -</svg>
@@ -1,141 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="256" - height="256" - viewBox="0 0 256 256" - id="svg2" - version="1.1" - inkscape:version="0.91 r13725" - sodipodi:docname="logo.svg"> - <defs - id="defs4"> - <linearGradient - inkscape:collect="always" - xlink:href="#linearGradient4313" - id="linearGradient4319" - x1="-441.97687" - y1="1012.8427" - x2="-441.97687" - y2="1072.3632" - gradientUnits="userSpaceOnUse" - gradientTransform="translate(1.8037136,-10.091205)" /> - <linearGradient - inkscape:collect="always" - id="linearGradient4313"> - <stop - style="stop-color:#005497;stop-opacity:0.27272728" - offset="0" - id="stop4315" /> - <stop - style="stop-color:#005497;stop-opacity:0.06818182" - offset="1" - id="stop4317" /> - </linearGradient> - <filter - inkscape:collect="always" - style="color-interpolation-filters:sRGB" - id="filter4309" - x="-0.040038537" - width="1.0800771" - y="-0.095373832" - height="1.1907477"> - <feGaussianBlur - inkscape:collect="always" - stdDeviation="3.6669538" - id="feGaussianBlur4311" /> - </filter> - </defs> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="0.49497475" - inkscape:cx="330.31084" - inkscape:cy="-82.873624" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - units="px" - inkscape:window-width="1366" - inkscape:window-height="744" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="1" /> - <metadata - id="metadata7"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-796.36216)"> - <g - id="g4261" - transform="matrix(0.71111111,0,0,0.71111111,476.26667,151.60534)"> - <rect - ry="0" - y="906.68933" - x="-669.75" - height="360" - width="360" - id="rect4228" - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - <g - transform="matrix(1.3547235,0,0,1.3547235,160.26025,20.43346)" - id="g4240"> - <path - style="opacity:1;fill:url(#linearGradient4319);fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter4309)" - d="m -442.65137,1055.1932 c -5.37558,-4e-4 -10.75247,-0.4342 -14.87109,-1.3009 l -84.63672,-17.8104 c -1.65176,-0.3475 -2.95609,-0.7386 -3.94531,-1.154 -3.71453,-1.1985 -6.62987,-3.2201 -7.13477,-4.9803 -0.41372,-0.4331 -0.64391,-0.9429 -0.63281,-1.5313 l 0.69922,-37.06 c 0.054,-2.86135 5.52976,-6.35712 12.27734,-7.8371 l 89.12695,-19.54942 c 1.48558,-0.32573 2.89873,-0.51836 4.20899,-0.60182 3.99244,-0.574 8.30073,-0.65058 11.66992,0.11216 l 88.56445,20.04642 c 6.90343,1.56259 12.50464,5.19835 12.5586,8.15116 l 0.69922,38.2432 c 0.0315,1.7289 -1.90581,3.6797 -5.14063,5.0001 -0.19992,0.086 -0.41416,0.1725 -0.64258,0.2558 -0.016,0 -0.0327,0.022 -0.0488,0.022 -0.90767,0.3313 -2.01326,0.6462 -3.36329,0.9301 l -84.51953,17.7707 c -4.11948,0.8661 -9.49355,1.2992 -14.86914,1.2988 z" - id="path4238" - inkscape:connector-curvature="0" - transform="matrix(0.84718319,0,0,1.0213689,-103.68658,-169.01715)" /> - <path - style="opacity:1;fill:#00aad4;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - d="m -397.75084,715.67331 -163.78868,1.15534 c -6.94231,0.049 -14.10792,13.10451 -10.71655,19.16231 l 80.79533,144.31978 c 3.39136,6.05781 12.65442,10.6298 18.54535,6.9562 l 75.57536,-47.12863 c 5.89092,-3.67357 10.6705,-12.21995 10.71655,-19.16229 l 0.59631,-89.90907 c 0.046,-6.94233 -4.78135,-15.44261 -11.72367,-15.39364 z" - id="rect4234" - inkscape:connector-curvature="0" - sodipodi:nodetypes="sssssssss" /> - <rect - style="opacity:1;fill:#00ccff;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect4232" - width="113.15113" - height="111.41566" - x="-678.87305" - y="1073.5972" - ry="12.143711" - transform="matrix(0.85586624,0.51719723,0.00684405,0.99997658,0,0)" /> - <rect - style="opacity:1;fill:#55ddff;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="rect4230" - width="112.68221" - height="112.81577" - x="388.7005" - y="942.09778" - ry="14.66605" - transform="matrix(0.86530322,0.50124878,-0.86512318,0.50155946,0,0)" /> - </g> - </g> - </g> -</svg>
@@ -1,6 +0,0 @@
-#!/bin/bash - -for f in *.svg -do - svgcleaner $f ../$f -done
@@ -1,49 +0,0 @@
-<!DOCTYPE html> -<html> -<head> - <title>1010! Klooni</title> - <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1"> - <meta name="description" content="1010! Klooni is the FLOSS version of the original minimalistic puzzle game by Gram Games."> - <link rel="stylesheet" href="css/main.css"> - <link rel="icon" href="favicon.png" /> -</head> -</head> -<body> - <div class="band header"> - <a href="https://f-droid.org/app/io.github.lonamiwebs.klooni"> - <img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="100px" style="margin-top:400px"> - </a> - </div> - <div class="band left" style="background-color:#2ecc71"> - <div> - <img src="img/logo.svg" alt="Klooni logo"> - <h2>Featuring… Klooni!</h2> - <p>1010! Klooni is a free, libre and open source <i>klooni</i> of the original minimalistic puzzle game by <b><a href="http://1010ga.me/" target="_blank">Gram Games</a></b>.</p> - </div> - </div> - <div class="band right" style="background-color:#3498db"> - <div> - <img src="img/gameplay.svg" alt="Gameplay"> - <h2>Fill Up the Board</h2> - <p>Drag the pieces on the board and try to clear as many strips as you can! Beware though, it is not <i>that</i> easy <img class="emoji" src="img/angel.svg" alt="o:)"/></p> - </div> - </div> - <div class="band left" style="background-color:#e74c3c"> - <div> - <img src="img/hack.svg" alt="Custom themes"> - <h2>Hack It</h2> - <p>Make the game yours! <a href="https://github.com/LonamiWebs/Klooni1010">Collaborate</a>! Add new themes, pieces, sounds or even explosions!</p> - </div> - </div> - <div class="band right" style="background-color:#9b59b6"> - <div> - <img src="img/enjoy.svg" alt="Enjoy!"> - <h2>Have Fun :)</h2> - <p>Overall, remember to have fun while playing the game. Also share the love with the original developers if you like it!</p> - </div> - </div> - <!-- https://github.com/blog/273-github-ribbons --> - <a href="https://github.com/LonamiWebs/Klooni1010"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/652c5b9acfaddf3a9c326fa6bde407b87f7be0f4/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6f72616e67655f6666373630302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png"></a> -</body> -</html>
@@ -1,30 +0,0 @@
-KLOONI PRIVACY POLICY -===================== - -NOTICE ------- - -Q: What personal information is being collected? -A: No information is collected by the application, other than your highscores, in-game money, and last running game. In addition, the application lacks internet access. - -CHOICE ------- - -Q: What options do I have as a user of the application about how/whether personal data is collected and used? -A: If you are not comfortable with the game remembering highscores, in-game money or last-saved games offline, you may clear application data and/or uninstall the application. - -ACCESS ------- - -Q: How can I as a user of the application see what data has been collected and change/correct it if necessary? -A: You may open the application and play the game to see what information it had saved previously, and you may try to achieve better highscores or earn/spend more money to change its in-game amount. - -SECURITY --------- -Q: How any data that is collected is stored/protected? -A: No sensitive data is stored, and it is accessible if your phone is rooted. - -REDRESS -------- -Q: What can I as a user of the application do if the privacy policy is not met? -A: Please contact the developer and let them know that they are not a lawyer and how to fix or improve the privacy policy.
@@ -1,3 +1,3 @@
User-agent: * Allow: / -Sitemap: https://lonami.dev/sitemap.xml +Sitemap: https://birabittoh.dev/sitemap.xml
@@ -1,330 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> - <loc>https://lonami.dev/</loc> - </url> - <url> - <loc>https://lonami.dev/blog/</loc> - </url> - <url> - <loc>https://lonami.dev/blog/asyncio/</loc> - <lastmod>2020-10-03</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/breaking-ror/</loc> - <lastmod>2019-01-12</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ctypes-and-windows/</loc> - <lastmod>2019-06-19</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/graphs/</loc> - <lastmod>2017-06-02</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/installing-nixos-2/</loc> - <lastmod>2019-02-16</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/installing-nixos/</loc> - <lastmod>2019-02-16</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/mdad/</loc> - </url> - <url> - <loc>https://lonami.dev/blog/mdad/a-practical-example-with-hadoop/</loc> - <lastmod>2020-04-18T13:25:43+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/mdad/big-data/</loc> - <lastmod>2020-03-18T09:51:17+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/mdad/cassandra-introduccion/</loc> - <lastmod>2020-03-30T09:28:07+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/mdad/cassandra-operaciones-basicas-y-arquitectura/</loc> - <lastmod>2020-03-20T11:36:18+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/mdad/data-warehousing-and-olap/</loc> - <lastmod>2020-04-01T09:45:41+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/mdad/developing-a-python-application-for-cassandra/</loc> - <lastmod>2020-04-16T07:52:26+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/mdad/final-nosql-evaluation/</loc> - <lastmod>2020-05-14T08:31:06+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/mdad/introduction-to-hadoop-and-its-mapreduce/</loc> - <lastmod>2020-04-01T11:01:46+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/mdad/introduction-to-nosql/</loc> - <lastmod>2020-03-18T09:51:33+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/mdad/mining-of-massive-datasets/</loc> - <lastmod>2020-03-28T19:09:44+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/mdad/mongodb-introduction/</loc> - <lastmod>2020-03-20T10:31:10+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/mdad/mongodb-operaciones-basicas-y-arquitectura/</loc> - <lastmod>2020-03-20T11:42:15+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/mdad/nosql-evaluation/</loc> - <lastmod>2020-03-28T19:22:31+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/mdad/visualizing-caceres-opendata/</loc> - <lastmod>2020-03-19T14:38:41+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/mdad/what-is-an-algorithm/</loc> - <lastmod>2020-03-18T09:51:02+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/new-computer/</loc> - <lastmod>2020-07-03</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/pixel-dungeon/</loc> - <lastmod>2019-06-03</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/posts/</loc> - <lastmod>2021-02-19</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/</loc> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/a-practical-example-with-hadoop/</loc> - <lastmod>2020-04-03T08:43:41+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/about-boolean-retrieval/</loc> - <lastmod>2020-03-18T09:38:02+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/build-your-own-pc/</loc> - <lastmod>2020-03-18T09:38:46+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/cassandra-an-introduction/</loc> - <lastmod>2020-03-18T09:47:05+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/developing-a-python-application-for-mongodb/</loc> - <lastmod>2020-04-16T08:01:23+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/final-nosql-evaluation/</loc> - <lastmod>2020-05-14T07:30:08+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/googles-bigtable/</loc> - <lastmod>2020-04-03T09:30:05+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/how-does-googles-search-engine-work/</loc> - <lastmod>2020-03-28T10:17:09+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/integrating-apache-tika-into-our-crawler/</loc> - <lastmod>2020-03-25T17:38:07+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/introduction-to-hadoop-and-its-mapreduce/</loc> - <lastmod>2020-04-03T08:43:44+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/introduction-to-nosql/</loc> - <lastmod>2020-03-18T09:38:23+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/mongodb-an-introduction/</loc> - <lastmod>2020-04-08T17:38:22+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/mongodb-basic-operations-and-architecture/</loc> - <lastmod>2020-04-08T17:36:25+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/nosql-databases-basic-operations-and-architecture/</loc> - <lastmod>2020-03-24T17:57:05+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/nosql-evaluation/</loc> - <lastmod>2020-03-27T11:22:45+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/pc-crawler-evaluation-2/</loc> - <lastmod>2020-03-28T10:29:49+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/pc-crawler-evaluation/</loc> - <lastmod>2020-03-18T09:39:27+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/upgrading-our-baby-crawler/</loc> - <lastmod>2020-03-18T09:49:33+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/ribw/what-is-elasticsearch-and-why-should-you-care/</loc> - <lastmod>2020-03-27T11:04:45+00:00</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/tips-outpost/</loc> - <lastmod>2020-05-22</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/university/</loc> - <lastmod>2020-07-03</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/woce-1/</loc> - <lastmod>2021-02-19</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/woce-2/</loc> - <lastmod>2021-02-19</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/woce-3/</loc> - <lastmod>2021-02-19</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/woce-4/</loc> - <lastmod>2021-02-28</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/woce-5/</loc> - <lastmod>2021-03-06</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/woce-6/</loc> - <lastmod>2021-03-13</lastmod> - </url> - <url> - <loc>https://lonami.dev/blog/world-edit/</loc> - <lastmod>2018-07-11</lastmod> - </url> - <url> - <loc>https://lonami.dev/category/</loc> - </url> - <url> - <loc>https://lonami.dev/category/algos/</loc> - </url> - <url> - <loc>https://lonami.dev/category/games/</loc> - </url> - <url> - <loc>https://lonami.dev/category/hw/</loc> - </url> - <url> - <loc>https://lonami.dev/category/sw/</loc> - </url> - <url> - <loc>https://lonami.dev/golb/</loc> - </url> - <url> - <loc>https://lonami.dev/golb/filosofia/</loc> - <lastmod>2016-06-21</lastmod> - </url> - <url> - <loc>https://lonami.dev/golb/inteligencia-artificial/</loc> - <lastmod>2016-03-05</lastmod> - </url> - <url> - <loc>https://lonami.dev/golb/making-a-difference/</loc> - <lastmod>2020-08-24</lastmod> - </url> - <url> - <loc>https://lonami.dev/golb/reflexion-ia/</loc> - <lastmod>2016-06-13</lastmod> - </url> - <url> - <loc>https://lonami.dev/golb/sentences/</loc> - <lastmod>2018-01-31</lastmod> - </url> - <url> - <loc>https://lonami.dev/tags/</loc> - </url> - <url> - <loc>https://lonami.dev/tags/algorithms/</loc> - </url> - <url> - <loc>https://lonami.dev/tags/asyncio/</loc> - </url> - <url> - <loc>https://lonami.dev/tags/bigdata/</loc> - </url> - <url> - <loc>https://lonami.dev/tags/culture/</loc> - </url> - <url> - <loc>https://lonami.dev/tags/databases/</loc> - </url> - <url> - <loc>https://lonami.dev/tags/debate/</loc> - </url> - <url> - <loc>https://lonami.dev/tags/ffi/</loc> - </url> - <url> - <loc>https://lonami.dev/tags/foodforthought/</loc> - </url> - <url> - <loc>https://lonami.dev/tags/graphics/</loc> - </url> - <url> - <loc>https://lonami.dev/tags/graphs/</loc> - </url> - <url> - <loc>https://lonami.dev/tags/hacking/</loc> - </url> - <url> - <loc>https://lonami.dev/tags/minecraft/</loc> - </url> - <url> - <loc>https://lonami.dev/tags/nixos/</loc> - </url> - <url> - <loc>https://lonami.dev/tags/optimization/</loc> - </url> - <url> - <loc>https://lonami.dev/tags/os/</loc> + <loc>https://birabittoh.dev/</loc> </url> <url> - <loc>https://lonami.dev/tags/python/</loc> + <loc>https://birabittoh.dev/blog/</loc> </url> <url> - <loc>https://lonami.dev/tags/rust/</loc> + <loc>https://birabittoh.dev/blog/modern-web-bloat/</loc> + <lastmod>2021-04-09</lastmod> </url> <url> - <loc>https://lonami.dev/tags/series/</loc> + <loc>https://birabittoh.dev/category/</loc> </url> <url> - <loc>https://lonami.dev/tags/showoff/</loc> + <loc>https://birabittoh.dev/category/tech/</loc> </url> <url> - <loc>https://lonami.dev/tags/tips/</loc> + <loc>https://birabittoh.dev/tags/</loc> </url> <url> - <loc>https://lonami.dev/tags/windows/</loc> + <loc>https://birabittoh.dev/tags/tips/</loc> </url> <url> - <loc>https://lonami.dev/tags/worldedit/</loc> + <loc>https://birabittoh.dev/tags/vent/</loc> </url> </urlset>
@@ -1,126 +0,0 @@
-<!DOCTYPE html> -<html> -<head> - <meta charset="utf-8"> - <title>Stopwatch</title> - <link rel="shortcut icon" type="image/svg+xml" href="stopwatch.svg"/> -<style> -body { - background-color: #000; - color: #eee; -} -.big { - font-size: 8em; - text-align: center; - margin-bottom: 0; -} -.smaller { - font-size: 0.2em; - margin-left: -1.5em; -} -.center { - margin: 1em auto; - width: 60%; -} -button { - border-radius: 0; - border: 0; - width: 46%; - height: 4em; -} -button { - font-size: 2em; - color: #fff; - transition: background-color 300ms; -} -button.green { - background-color: #373; -} -button.green:hover { - background-color: #595; -} -button.red { - background-color: #733; - float: right; -} -button.red:hover { - background-color: #955; -} -</style> -</head> -<body> - <p class="big"> - <span id="counter">00:00:00</span> - <span id="counterms" class="smaller">.000</span> - </p> - <div class="center"> - <button id="toggle" class="green" type="button" - onclick="toggleCounter()">Start</button> - <button type="button" class="red" - onclick="resetCounter()">Reset</button> - </div> - - <script> -const counter = document.getElementById("counter"); -const counterms = document.getElementById("counterms"); -const toggle = document.getElementById("toggle"); - -let start = null; -let paused = null; - -function renderCounter() { - if (start == null) { - counterms.innerHTML = ".000"; - counter.innerHTML = "00:00:00"; - return; - } - - var t = (paused == null ? Date.now() : paused) - start; - var h = Math.floor(t / 3600000).toString().padStart(2, "0"); - t %= 3600000; - var m = Math.floor(t / 60000).toString().padStart(2, "0"); - t %= 60000; - var s = Math.floor(t / 1000).toString().padStart(2, "0"); - t %= 1000; - var z = Math.floor(t).toString().padStart(3, "0"); - - counterms.innerHTML = `.${z}`; - counter.innerHTML = `${h}:${m}:${s}` -} - -function updateCounter(timestamp) { - if (start != null && paused == null) { - renderCounter(); - window.requestAnimationFrame(updateCounter); - } -} - -function toggleCounter() { - const now = Date.now(); - if (start != null && paused == null) { - paused = now; - toggle.innerHTML = "Start"; - renderCounter(); - } else { - if (start == null) { - start = now; - } else if (paused != null) { - start += now - paused; - paused = null; - } - toggle.innerHTML = "Stop"; - updateCounter(0); - } -} - -function resetCounter() { - start = null; - paused = null; - toggle.innerHTML = "Start"; - renderCounter(); -} - -renderCounter(); - </script> -</body> -</html>
@@ -1,1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25.4 25.4"><g transform="translate(0 -271.6)"><circle cx="12.7" cy="284.3" r="12.7"/><path d="M22.21 273.29l1.5 1.5-8.98 8.98-1.5-1.5z"/><path d="M22.66 271.72l2.62 2.62-1.5 1.5-2.62-2.62z"/><circle cx="12.7" cy="284.3" r="9.52" fill="#fff"/><path d="M11.64 279.1h2.12v7.41h-2.12z"/><path d="M8.52 284.26l1.5-1.5 3.74 3.75-1.5 1.5zM12.17 274.25h1.06v2.12h-1.06zM12.17 292.24h1.06v2.12h-1.06zM22.75 283.77v1.06h-2.12v-1.06zM4.76 283.77v1.06H2.64v-1.06z"/></g></svg>
@@ -1,187 +0,0 @@
-<!DOCTYPE html> -<html> - <head> - <title>Stringlate</title> - <link rel="stylesheet" type="text/css" href="style.css"> - <link href="https://fonts.googleapis.com/css?family=Crimson+Text|Droid+Serif" rel="stylesheet"> - <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"> - <link rel="shortcut icon" href="favicon.ico" /> - </head> - <body> - <main> - <h1>Stringlate</h1> - <p><em>Help translating FOSS applications.</em></p> - - <h2>Deprecation Notice</h2> - <p>I, Lonami, have lost most interest in maintaining Stringlate.</p> - - <p><strong>There are a few big issues with a big impact and I don't want - them to cause more harm than good</strong>, so I'm making it clear that - I am aware of the issues and that you should know about them too if you - still decide to make use the application.</p> - - <p>Stringlate started as an idea to suit my personal needs. My mum wanted - an application but it was only available in English, so I decided to - translate it myself. However, doing so proved very inconvenient, and I - decided to make my own Android application to help me in the process. In - the end though, I made the translation by hand far sooner, before the - application was ready :)</p> - - <p>It was a fun project, and I learnt a lot, but any issues in the - application have serious implications, since they impact a lot of open - source projects. There are several big issues:</p> - - <ul> - <li>Making a pull request may use old state of the repository</li> - <li>Some tags like bool and string arrays aren't handled well</li> - <li>Some projects only accept changes through other platforms</li> - <li>Integration with other services could really be improved</li> - </ul> - - <p><strong>In short</strong>, please consider other ways to help - translating the project you like instead of using Stringlate.</p> - - <p>If you're interested in <strong>maintaining Stringlate</strong>, - please <a href="https://lonami.dev/contact">contact me</a> and - we can discuss it further. Thank you for your interest, and thanks to - everyone who has contributed translations. It's been a nice journey!</p> - - <p><em>(The rest of the page remains the same for historical - purposes.)</em></p> - - <h1>About Stringlate</h1> - - <p> - This application is meant to help the FOSS community translate their - Android applications in an easy way. Any free moment you have could - be invested into translating that application you love, but is not - available in your language, or has some strings wrong. - </p> - - <p> - The application works by fetching a <code>git</code> repository (yes, any!) - to retrieve all the available strings, for you to translate whenever - you want, offline. Once you're done, submit your work! - </p> - - <p> - This application is of course open source, and is available on both - <a href="https://github.com/LonamiWebs/Stringlate">GitHub</a> and - <a href="https://f-droid.org/app/io.github.lonamiwebs.stringlate">F-Droid</a>. - </p> - - <h1>Integrating with your own app</h1> - <p> - Are you a developer? Are you interested on people knowing that they - can use Stringlate to translate your application? If both answers are - yes, that's awesome! - </p> - - <p> - There are several ways to do this, one is from in-app, for which - you can check and use this plain simple - <a href="https://github.com/LonamiWebs/Stringlate/blob/master/src/app/src/main/java/io/github/lonamiwebs/stringlate/utilities/StringlateApi.java">API</a> - <sup>(<a href="https://github.com/LonamiWebs/Stringlate/issues/72">original issue</a>)</sup>. - </p> - - <p> - Another way is by creating a <a href="http://shields.io/">badge</a> - <sup>(<a href="https://github.com/gsantner/memetastic">original idea</a>)</sup> - for Stringlate: - </p> - - <img - src="https://img.shields.io/badge/translate%20with-stringlate-green.svg" - alt="translate with stringlate badge" /> - <pre><a href="https://lonami.dev/stringlate/translate?git={git url}"> - <img - src="https://img.shields.io/badge/translate%20with-stringlate-green.svg" - alt="Translate - with Stringlate" /> -</a> - -<!-- or using markdown --> -[![Translate - with Stringlate](https://img.shields.io/badge/translate%20with-stringlate-green.svg)](https://lonami.dev/stringlate/translate?git={git url})</pre> - - <p> - You should replace <code>{git url}</code> to wherever your project's - <code>git</code> lives. It should be encoded to be a valid parameter - though. - </p> - - <noscript><p> - You can also enable JavaScript to simply generate the badge link - right from this page! - </p></noscript> - - <div id="generateUrlDiv" style="display:none"> - <p>You can also enter said URL here and get the code straight away:</p> - - <label for="gitUrl">Project's <code>git</code> URL:</label> - <input type="text" id="gitUrl" oninput="updateBadgeCode()"> - <select id="genKind" onchange="updateBadgeCode()"> - <option value="html">HTML</option> - <option value="md">Markdown</option> - </select> - - <p>Generated code (copy and paste this on your <code>README</code>):</p> - <textarea id="generatedCode" readonly></textarea><br> - <button id="copyButton" onclick="copyGenerated()">Copy text</button> - </div> - <noscript>If you want <p> - If you had JavaScript enabled, I could tell you which repository you - were trying to translate. But it's fine! - </p></noscript> - <p id="whichAppP" style="display:none"> - You were probably trying to translate <a id="whichAppA"></a>. - </p> - </main> - - <script> - var generateUrlDiv = document.getElementById('generateUrlDiv'); - generateUrlDiv.style.display = ''; - - var gitUrl = document.getElementById('gitUrl'); - var genKind = document.getElementById('genKind'); - var generatedCode = document.getElementById('generatedCode'); - var copyButton = document.getElementById('copyButton'); - - function updateBadgeCode() { - var translateUrl = - 'https://lonami.dev/stringlate/translate?git='+ - encodeURIComponent(gitUrl.value); - - var badgeUrl = - 'https://img.shields.io/badge/translate%20with-stringlate-green.svg'; - - switch (genKind.value) { - case 'html': - generatedCode.rows = 5; - generatedCode.value = - '<a href="'+translateUrl+ - '>\n <img\n src="'+badgeUrl+ - '"\n alt="Translate - with Stringlate" />\n</a>'; - break; - case 'md': - generatedCode.rows = 2; - generatedCode.value = - '[![Translate - with Stringlate]('+badgeUrl+ - ')]('+translateUrl+')'; - break; - } - } - - function copyGenerated() { - generatedCode.select(); - try { - var ok = document.execCommand('copy'); - if (ok) copyButton.innerHTML = 'Copied!'; - else copyButton.innerHTML = 'Unable to copy!'; - } catch (err) { - answer.innerHTML = 'Unsupported browser!'; - } - - setTimeout(function(){ copyButton.innerHTML = 'Copy text'; }, 2000); - } - </script> - </body> -</html>
@@ -1,75 +0,0 @@
-body { - background: #007b6b; - background: linear-gradient(#009789, #007b6b, #009789); -} - -main { - max-width: 640px; - margin: 80px auto; - background-color: #d5ffba; - padding: 20px; - border-radius: 4px; - border: 4px solid #ffc107; - box-shadow: 8px 8px 10px rgba(60, 20, 0, 0.3); -} - -h1 { - font-family: 'Droid Serif', serif; -} - -p, label { - font-family: 'Crimson Text', serif; - font-size: 18px; -} - -a { - color: #009789; - padding: 2px; - border-radius: 2px; -} - -code { - color: #007b6b; - font-size: 16px; -} - -pre, textarea { - color: #b1ddd9; - background-color: #212121; - font-family: monospace; - padding: 8px 12px; - overflow-x: scroll; -} - -textarea { - white-space: pre; - overflow-wrap: normal; -} - -/* Flaired edges, by Tomas Theunissen - Reference: https://www.css-tricks.com/examples/hrs/ -*/ -hr { - height: 30px; - border-style: solid; - border-color: #ffc107; - border-width: 1px 0 0 0; - border-radius: 20px; -} -hr:before { - display: block; - content: ""; - height: 30px; - margin-top: -31px; - border-style: solid; - border-color: #ffc107; - border-width: 0 0 1px 0; - border-radius: 20px; -} - -/* Less padding on mobile */ -@media (max-width:1000px) { - main { - margin: auto auto; - } -}
@@ -1,67 +0,0 @@
-<!DOCTYPE html> -<html> - <head> - <title>Translate with Stringlate</title> - <link rel="stylesheet" type="text/css" href="style.css"> - <link href="https://fonts.googleapis.com/css?family=Crimson+Text|Droid+Serif" rel="stylesheet"> - <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"> - <link rel="shortcut icon" href="favicon.ico" /> - </head> - <body> - <main> - <h1>Translate with Stringlate</h1> - <p> - This is a special link that you can open from your phone to translate - someone's application via Stringlate. You should have clicked - "Open with Stringlate" instead! - </p> - <noscript><p> - If you had JavaScript enabled, I could tell you which repository you - were trying to translate. But it's fine! - </p></noscript> - <p id="whichAppP" style="display:none"> - You were probably trying to translate <a id="whichAppA"></a>. - </p> - <hr /> - <p> - Don't have Stringlate installed? - <a href="https://f-droid.org/app/io.github.lonamiwebs.stringlate">Get - it from F-Droid!</a> or - <a href="https://github.com/LonamiWebs/Stringlate/releases">grab the - latest release</a>! - </p> - <p> - Want to integrate Stringlate with your application too? Check out the - <a href="https://lonami.dev/stringlate/index">main page</a>! - </p> - </main> - - <script> - // https://stackoverflow.com/a/1404100/4759433 - function getURLParameter(name) { - var r = RegExp(name+'='+'(.+?)(&|$)').exec(location.search)||[,null]; - if (r[1]) return decodeURIComponent(r[1]); - } - - var git = getURLParameter('git'); - if (git) { - var whichAppP = document.getElementById('whichAppP'); - var whichAppA = document.getElementById('whichAppA'); - - whichAppP.style.display = ''; - whichAppA.href = git; - - var slash = git.lastIndexOf('/'); - if (slash == -1) { - whichAppA.innerHTML = git; - } else { - var name = git.substring(slash + 1); - if (/.git$/.test(name)) { - name = name.substring(0, name.length - 4); - } - whichAppA.innerHTML = name; - } - } - </script> - </body> -</html>
@@ -2,10 +2,11 @@ /* main */
body { background-color: #000; margin: 0; + color: #bcb6ae; } article { - background-color: #fffff0; + background-color: #0b0b0f; } nav, main, footer div {@@ -16,8 +17,8 @@
main { padding: 10px 20px; border-radius: 4px; - background-color: #fffff7; - box-shadow: 0 0 10px #eee; + background-color: #171820; + box-shadow: 0 0 10px #1c1c1c; } /* navigation */@@ -44,12 +45,12 @@
nav.sections .left a { color: #666; border-bottom: solid 2px #A8A8A8; - padding: 0 16px; + padding: 0 16px 0 0; } nav.sections .left a.selected, nav.sections .left a:hover { - color: #000000; - border-bottom: solid 2px #444444; + color: #df861d; + border-bottom: solid 2px #df861d ; } nav.sections img {@@ -68,7 +69,6 @@ /* footer */
footer { margin-top: 32px; min-height: 64px; - background: linear-gradient(#fffff0, #ccccc0); } footer p {@@ -84,6 +84,7 @@ }
.abyss { text-align: center; + color: #000; } /* headers */@@ -93,7 +94,7 @@ font-size: 200%;
font-weight: lighter; text-transform: capitalize; padding-bottom: 10px; - border-bottom: 1px solid #000; + border-bottom: 1px solid #bcb6ae; text-align: center; }@@ -143,26 +144,24 @@ }
a { text-decoration: none; - color: #c74727; + color: #df861d; border-bottom: 0 dashed rgba(0, 0, 0, 0); transition: color 300ms, border-bottom 300ms; } a:hover { - color: #b73717; + color: #ce7a1b; border-bottom: 1px solid #b73717; } blockquote { - border-left: 4px solid #000; + border-left: 4px solid #777; padding-left: 8px; font-style: italic; - color: #444; + color: #bcb6ae; } -.footnote-definition:target { - background-color: rgba(255, 255, 0, 0.2); -} + dl img { margin-bottom: -0.4em;@@ -197,7 +196,7 @@ /* code */
code { font-weight: bold; font-size: large; - background-color: #f7f7f0; + background-color: #0b0b0f; white-space: pre; }@@ -208,7 +207,7 @@ background: none;
} pre { - background-color: #eee; + background-color: #0b0b0f; padding: 4px; overflow: auto; max-height: 480px;@@ -217,11 +216,11 @@
kbd { padding: 3px 5px; font: 0.9em monospace; - color: #333; - background-color: #fafafa; - border: 1px solid #ddd; + color: #fafafa; + background-color: #333; + border: 1px solid #222; border-radius: 4px; - box-shadow: 0 2px 0 #ddd; + box-shadow: 0 2px 0 #222; } /* tables */@@ -240,31 +239,6 @@ }
tr:nth-child(even) { background-color: #f7f7e8; -} - -/* graphs post */ -div.matrix table { - border-width: 0 2pt 0 2pt; - border-style: solid; - border-color: #000; -} - -/* poor man's matrix */ -div.matrix tr:first-child td:first-child, div.matrix tr:first-child td:last-child { - border-top: 2pt solid #000; -} - -div.matrix tr:last-child td:first-child, div.matrix tr:last-child td:last-child { - border-bottom: 2pt solid #000; -} - -div.matrix td { - border: none; -} - -em.math { - font-family: 'Times New Roman', Times, serif; - font-size: larger; } /* special */
@@ -1,8 +0,0 @@
-div.card { - margin: 20px 20px; - padding: 10px 20px; -} - -pre { - font-size: 16px; -}
@@ -1,106 +0,0 @@
-body { - background-color: #ddd; - font-family: 'Raleway', sans-serif; -} - -div.card { - background-color: #fff; - margin: 20px 20%; - padding: 20px 40px; - border-radius: 4px; - box-shadow: 8px 8px 8px #ccc; -} - -.fadable { - opacity: 0; - transition: opacity 200ms; -} - -.fadein { - opacity: 1; -} - -ul.horizontal { - list-style-type: none; - margin: 0; - padding: 0; - overflow: hidden; -} - -ul.horizontal li { - float: left; - display: block; - background-color: #0AF; - margin: 4px; - padding: 4px; - border-radius: 4px; - box-shadow: 2px 2px 2px #ccc; -} - -#filters li { - cursor: pointer; - background-color: #48F; - transition: background-color 200ms; -} - -#filters li:hover { - background-color: #F44; -} - -a { - text-decoration: none; - color: #09F; - transition: color 200ms; -} - -a:hover { - color: #0BF; -} - -pre, code { - color: #235; - font-family: 'Inconsolata', monospace; -} - -pre { - background-color: #222; - color: #fff; - padding: 15px 30px; - overflow: auto; - word-wrap: normal; - white-space: pre; -} - -kbd { - background-color: #fbfbfb; - border-bottom-color: #888; - padding: 3px; - border: 1px solid #ccc; - border-bottom: 2px; - border-radius: 3px; - box-shadow: #aaa 0px -2px 0px 0px inset; - font-family: 'Inconsolata', monospace; - font-size: 14px; -} - -br { - line-height: 1.5; -} - -button { - background-color: #fa0; - color: #fff; - border-radius: 4px; - border: none; - padding: 8px; - display: inline-block; - font-size: 16px; - margin: 4px 2px; - cursor: pointer; - transition: box-shadow 150ms; -} - -button:hover { - box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2), 0 6px 20px 0 rgba(0,0,0,0.19); -} -
@@ -1,539 +0,0 @@
-<!DOCTYPE html> -<html> -<head> - <meta charset="utf-8"> - <title>Utilities</title> - <link href="https://fonts.googleapis.com/css?family=Inconsolata|Raleway" rel="stylesheet"> - <link rel="stylesheet" type="text/css" href="css/theme.css"> - <meta name="description" content="Several utilities for almost every purpose"> - <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"> - <link rel="stylesheet" type="text/css" href="css/mobile.css" - media="only screen and (max-width: 740px)" /> -</head> -<body> - <div class="card"> - <h1>Multipurpose utilities - miscellaneous things</h1> - <p>Welcome! On this page you will find several utilities for various - distinct purposes. You can use the box below to add hashtags if you - want to filter out some cards, or directly search in the content</p> - <input id="hashtagInput"> - <button onclick="addHashtag()">Add hashtag filter</button> - <button onclick="clearHashtags()">Clear hashtags</button> - <ul id="filters" class="horizontal"></ul> - </div> -<div id="allcards"> - <div class="card"> - <table> - <tr> - <td><h1>Boot Repair Disk</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>boot</li><li>repair</li><li>gpt</li><li>recovery</li> - </ul></td> - </tr> - </table> - <p>Small utility to fix broken laptops. It won't boot? Grab a copy of - <a href="https://sourceforge.net/projects/boot-repair-cd/" target="_blank">Boot Repair</a> - (<a href="https://help.ubuntu.com/community/Boot-Repair" target="_blank">wiki</a>) - and fix it!</p> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Installing <code>.rpm</code> files</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>opensuse</li><li>install</li><li>rpm</li> - </ul></td> - </tr> - </table> - <p>You can install <code>.rpm</code> files under openSUSE with the following command:</p> - <pre>sudo zypper install <rpm file name>.rpm</pre> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Viewing your local IP address</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>ipconfig</li><li>ip</li> - </ul></td> - </tr> - </table> - <p>On Windows the command is called <code>ipconfig</code>. It's very - similar under Linux, yet not the same: - <pre>ifconfig</pre> - </div> - <div class="card"> - <table> - <tr> - <td><h1>List PCI devices and system information</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>list</li><li>pci</li><li>graphics</li><li>card</li> - <li>system</li><li>version</li><li>ram</li><li>gpu</li><li>resolution</li> - </ul></td> - </tr> - </table> - <p>Lists all the available PCI devices (USB, audio, SATA controller…). - For example, if you wanted to check your graphic card you could do:</p> - <pre>lspci | grep VGA</pre> - <p>It might also be useful to run <code>screenfetch</code> (or - <code>neofetch</code>) to display more information, in a fancy way.</p> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Evaluate expressions</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>inline</li><li>commnad</li><li>evaluate</li><li>expression</li> - </ul></td> - </tr> - </table> - <p>You can inline commands by surrounding them between <code>`</code> - (back quotes), or by putting them inside parenthesis with a - <code>$</code> (dollar symbol) as prefix:</p> - <pre>echo The date is `date +"%d-%m-%Y"`. - -echo Or differently $(date +"%d-%m-%Y").</pre> - - <p>In a similar way, to use a variable, you can use <code>$VARIABLE</code> - or even <code>${VARIABLE}</code> if it interfers with more text.</p> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Force a terminal - <a href="http://stackoverflow.com/a/26744968" target="_blank">*</a> - </h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>xfce</li><li>force</li><li>terminal</li><li>bash</li><li>shell</li><li>script</li> - </ul></td> - </tr> - </table> - <p>You can force a shell script to launch a terminal if it wasn't opened - by one (e.g., if it's an executable script and you opened it via double - click) by prepending the following lines to it:</p> - <pre>if [ ! -t 0 ]; then - x-terminal-emulator -e "$0" - exit 0 -fi</pre> - </div> - <div class="card"> - <table> - <tr> - <td><h1>ISO burning and disk formatting</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>burn</li><li>iso</li><li>usb</li><li>disk</li> - <li>format</li><li>list</li><li>partition</li><li>fat32</li> - </ul></td> - </tr> - </table> - <p>To <b>l</b>i<b>s</b>t all the plugged <b>bl</b>oc<b>k</b> devices:</p> - <pre>lsblk</pre> - - <p>For example, if you identify your USB drive and you want to burn an - ISO on it or to wipe it completely with zeroes (respectively), after - you've made sure that <b>it is not mounted</b>:</p> - <pre>dd status=progress if=my.iso of=/dev/sdX - -dd status=progress if=/dev/zero of=/dev/sdX</pre> - - <p>To create a new partition, you can run <code>fdisk</code> and follow - its instructions.<br /> - To formt a partition, as FAT32 for instance, you can run: - <pre>mkfs -t vfat /dev/sdXY</pre> - - <p>A great tool with GUI however, it's <code>gparted</code>.</p> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Disabling the touchpad</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>disable</li><li>touchpad</li><li>trackpad</li> - </ul></td> - </tr> - </table> - <p>If you're like me, you probably hate the touchpad. No problem: - <pre>sudo add-apt-repository ppa:atareao/atareao -sudo apt-get update -sudo apt-get install touchpad-indicator</pre> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Find in files</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>grep</li><li>find</li><li>search</li><li>regex</li><li>files</li> - </ul></td> - </tr> - </table> - <p><code>grep</code> is a little great tool which can also search in files - <b>r</b>ecursively and show the line <b>n</b>umber for the matches of - an arbitrary <a href="http://regexr.com" target="_blank">regex</a> - <b>e</b>xpression:</p> - <pre>grep -rn '/path/to/somewhere/' -e "pattern"</pre> - <p>You don't know regex or need a quick refresher? Check out - <a href="https://www.regexone.com" target="_blank">RegexOne</a>.</p> - <p>You can also show the surrounding lines to those you matched by using - the <b>b</b>efore and <b>a</b>fter options, as follows: - <pre>grep -rn '/path/to/somewhere/' -B1 -A2 -e "pattern"</pre> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Environment variables</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>environment</li><li>variable</li> - </ul></td> - </tr> - </table> - <p>You can define environment variables under - <code>/etc/environment</code>. You probably need to have superuser - access before you can modify that file.</p> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Octave fix</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>octave</li><li>matlab</li> - </ul></td> - </tr> - </table> - <p>If Octave closes itself after plotting a function, you might want - to check what it's using to render them with the - <code>graphics_toolkit</code> command. If the output is not - <code>gnuplot</code>, install it via <code>apt</code>, restart Octave - and run <code>graphics_toolkit('gnuplot')</code>.<br /> - - <p>To run this command on startup, simply add it to the - <code>~/.octaverc</code> file.</p> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Open menu with the super key</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>xfce</li><li>key</li><li>shortcut</li><li>hotkey</li> - </ul></td> - </tr> - </table> - <p>You can bind the <code>xfce4-popup-whiskermenu</code> command to - the super key to open the Whiskers menu.</p> - </div> - <div class="card"> - <table> - <tr> - <td><h1>IO scheduler</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>io</li><li>kernel</li><li>scheduler</li> - </ul></td> - </tr> - </table> - <p>You can check the currently selected IO scheduler by issuing:</p> - <pre>cat /sys/block/sda/queue/scheduler - noop [deadline] cfq</pre> - - <p>This means that the selected scheduler is <code>deadline</code>. - On my experience, <code>cfq</code> performs better (e.g., when running - a server). You can change it without the need for a reboot by - <code>echo</code>ing the desired scheduler to it (<code>su</code> first):</p> - <pre>su -echo cfq > /sys/block/sda/queue/scheduler -exit</pre> - - <p>For this change to persist, you need to tell grub - <a href="http://askubuntu.com/a/82452" target="_blank">*</a>:</p> - <pre>sudo nano /etc/default/grub - -# Find and replace: -GRUB_CMDLINE_LINUX_DEFAULT="quiet splash" -# With: -GRUB_CMDLINE_LINUX_DEFAULT="quiet splash elevator=<OPTION>" -# Where option is one of the shown above (noop, deadline or cfq) -# Press Ctrl+X and to save and exit - -sudo update-grub -</pre> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Libraries</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>library</li><li>libraries</li><li>dependency</li><li>dependencies</li> - </ul></td> - </tr> - </table> - <p>If sometimes you get the error <code>cannot find -lLibrary</code>, - look for <code>libLibrary</code> (note the <code>lib</code> prefix) on - the Synaptics package manager, or directly search the missing library - with <code>apt-cache</code>:</p> - <pre>apt-cache search libLibrary*</pre> - - <p>You might want to install the <code>-dev</code> version too if you're - having trouble when building an application. You can list the libraries - a program uses by running <code>ldd <program></code>.</p> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Terminal shortcuts</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>terminal</li><li>shortcut</li><li>hotkey</li> - </ul></td> - </tr> - </table> - <ul> - <li><code>Ctrl+C</code> cancels the current program.</li> - <li><code>Ctrl+D</code> sends <code>EOF</code> (useful for - <code>cat</code> or to exit the terminal quickly rather than typing - <code>exit</code>.</li> - <li><code>Ctrl+W</code> deletes the previous word.</li> - <li><code>Ctrl+U</code> deletes the whole entered command.</li> - <li><code>Ctrl+Y</code> pastes back the cleared text.</li> - <li><code>Ctrl+Shift+C</code> copies the selected text.</li> - <li><code>Ctrl+Shift+V</code> pastes the clipboard text.</li> - </ul> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Desktop files</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>desktop</li><li>launcher</li><li>menu</li><li>startmenu</li> - </ul></td> - </tr> - </table> - <p><code>~/.local/share/applications</code> contains the - <code>.desktop</code> files used to launch your applications. - Their format, although not standarized, is usually as follows:</p> - <pre>[Desktop Entry] -Encoding=UTF-8 -Version=1.0 -Name=My Fancy App -Comment=This is my fancy application -Exec=/home/imthebest/My-Apps/launch -Icon=/home/imthebest/My-Apps/app.png -Terminal=false -Type=Application -Categories=Network;</pre> - - <p><code>/usr/share/applications</code> for all the users.</p> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Stream YouTube music</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>stream</li><li>play</li><li>youtube</li><li>music</li><li>audio</li> - </ul></td> - </tr> - </table> - <p>You can play songs from YouTube (or even <code>--shuffle</code> a - playlist) by using <code>mpv</code>:</p> - <pre>mpv --no-video https://www.youtube.com/playlist?list=blahblah</pre> - - <p>For this to work, you need to have a few things installed:</p> - <pre>sudo apt-get install python3-pip mpv -sudo -H pip3 install youtube-dl</pre> - - <p>Remember to <code>reset</code> the terminal afterwards.</p> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Screen capture - <a href="https://trac.ffmpeg.org/wiki/Capture/Desktop" target="_blank">*</a> - </h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>screen</li><li>grab</li><li>capture</li><li>record</li> - </ul></td> - </tr> - </table> - <p>You can use <code>ffmpeg</code> not only to convert files, but also - to capture your screen and send it to your friends as a <code>.gif</code>: - <pre>ffmpeg -video_size {WIDTH}x{HEIGHT} -framerate 25 -f x11grab -i :0.0+{X},{Y} -t {DURATION} {OUTPUT}.mp4</pre> - <p>This can prove very useful when combined with - <a href="https://github.com/naelstrof/slop" target="_blank">slop</a>: - <pre>DURATION=10 -OUTPUT=screenrecord -read -r X Y W H < <(slop -f "%x %y %w %h") -ffmpeg -video_size ${W}x$H -framerate 25 -f x11grab -i :0.0+$X,$Y -t $DURATION $OUTPUT.mp4</pre> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Running commands in parallel</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>execute</li><li>run</li><li>command</li> - <li>parallel</li><li>multithread</li><li>batch</li> - </ul></td> - </tr> - </table> - <p>The <code>parallel</code> command effectively allows you to run a - command in parallel, so you can get the most out of your CPU. For example, - let's assume you have a file called <code>urllist.txt</code> and you want - to <code>wget</code> them all as fast as possible: - <pre>cat urllist.txt | parallel --gnu "wget {}" - -# Or -parallel --gnu -a urllist.txt wget - -# Or -parallel --gnu wget < urllist.txt -</pre> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Move folders to RAM (RAM disk)</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>ubuntu</li><li>move</li><li>folder</li><li>ram</li><li>ramdisk</li> - </ul></td> - </tr> - </table> - <p>The <code>/dev/shm</code> folder, available on some Linux distrubitions, - serves as a faster alternative than <code>/tmp</code>. <b>Note that - this is not reliable</b> and should be only used as a faster alternative - for temporary storage. You can "move a folder to RAM" as follows:</p> - <pre>$FOLDER_NAME="your_folder" -$IN_RAM="/dev/shm/$folder_name" -$FOLDER="$(pwd)/$FOLDER_NAME" - -mv "$FOLDER" "$FOLDER.packed" -cp -r "$FOLDER.packed" "$IN_RAM" -ln -s "$IN_RAM" "$FOLDER"</pre> - <p>This will rename the original folder by adding the suffix - <code>.packed</code> to it, copy its contents to the RAM, and - create a <code>symlink</code> where the previous folder was before - to the folder on the RAM.<br /> - To undo this operation, simply run: - <pre>FOLDER_NAME="your_folder" -IN_RAM="/dev/shm/$folder_name" -FOLDER="$(pwd)/$folder_name" - -rm $FOLDER -cp -r $IN_RAM $FOLDER -rm -rf $IN_RAM</pre> - <p>This will preserve the <code>.packed</code> version since the RAM - may be unstable.</p> - </div> - <div class="card"> - <table> - <tr> - <td><h1><code>MakeFile</code> refresher - <a href="http://mrbook.org/blog/tutorials/make/" target="_blank">*</a> - </h1></td> - <td><ul class="filterlist horizontal"> - <li>make</li><li>makefile</li><li>build</li> - </ul></td> - </tr> - </table> - <p><code>MakeFile</code>s are an easy and powerful tool to build your - projects. However, if you're like me, you probably tend to forget how - they work too. Here's a quick refresher. Note that you <b>must use - the TAB character</b>. Their basic syntax is: - <pre>target_name:( target_dependencies) -	system command -</pre> - <p>A full example would be:</p> - <pre>all: hello - -hello: main.o hello.o -	g++ main.o hello.o -o hello - -main.o: main.cpp -	g++ -c main.cpp - -hello.o: hello.cpp -	g++ -c hello.cpp - -clean: -	rm *.o hello</pre> - - <p>A more powerful example, introducing the use of variables:</p> - <pre># Compiler used -CC=g++ - -# Compiler flags -CFLAGS=-c -Wall - -all: hello - -hello: main.o hello.o -	$(CC) main.o hello.o -o hello - -main.o: main.cpp -	$(CC) $(CFLAGS) main.cpp - -hello.o: hello.cpp -	$(CC) $(CFLAGS) hello.cpp - -clean: -	rm *.o hello</pre> - - <p>You can execute the default one by running <code>make</code>, - or a specific one with <code>make -f MyMakefile</code>. - </div> - <div class="card"> - <table> - <tr> - <td><h1><code>.gif</code> tricks with FFmpeg</h1></td> - <td><ul class="filterlist horizontal"> - <li>ffmpeg</li><li>convert</li><li>video</li><li>avi</li><li>mp4</li><li>gif</li> - </ul></td> - </tr> - </table> - <p><a href="http://ffmpeg.org/" target="_blank">FFmpeg</a> is not only - to convert between video and audio formats. It can also convert images - (like <code>.webp</code> to <code>.png</code>), and even <code>.gif</code>s:</p> - <pre>ffmpeg -i in.mp4 -framerate 10 out.gif</pre> - <p>Perhaps the generated file is too large. You can scale to a given size, - by either constraining the width, height (the other option will be - <code>-1</code>), or both:</p> - <pre>ffmpeg -i in.mp4 -framerate 10 -vf "scale=150:-1" out.gif</pre> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Multiple images to video with FFmpeg - <a href="http://ffmpeg.org/ffmpeg.html#Video-and-Audio-file-format-conversion" target="_blank">*</a> - </h1></td> - <td><ul class="filterlist horizontal"> - <li>ffmpeg</li><li>convert</li><li>multiple</li><li>many</li> - <li>images</li><li>video</li><li>avi</li><li>mp4</li><li>gif</li> - </ul></td> - </tr> - </table> - <p>Extracting images from a video:</p> - <pre>ffmpeg -i in.mp4 -r 1 -s WxH -f image2 out-%03d.jpg</pre> - <p>This will extract one video frame per second from <code>in.mp4</code> - with the <b>s</b>ize <code>WxH</code> and to <code>out-nnn.jpg</code>. - The format <code>%03d</code> on the name specifies 3 digits, 0 padded. - <br /> - If on the other hand you wanted to extract all the images, you can - drop the <code>-r 1</code> option, and even the <code>-s</code> option, - exactly leaving alone:</p> - <pre>ffmpeg -i in.mp4 out%d.jpg</pre> - <p>The reverse process, converting many images to a video:</p> - <pre>ffmpeg -f image2 -framerate 12 -i in-%03d.jpg -s WxH out.mp4</pre> - </div> - <div class="card"> - <table> - <tr> - <td><h1>Background processes</h1></td> - <td><ul class="filterlist horizontal"> - <li>linux</li><li>background</li><li>process</li><li>processes</li> - <li>terminal</li><li>bash</li><li>shell</li> - </ul></td> - </tr> - </table> - <p>You can press <kbd>Ctrl+Z</kbd> to send a running process to the - background, use <code>jobs</code> to see which have you sent, and - <code>fg n</code> to bring them back to foreground.</p> - </div> -</div> <!-- div id="allcards" --> - -<script src="js/utils.js"></script> -<script src="js/hashtags.js"></script> -</body> -</html>
@@ -1,69 +0,0 @@
-// Variables -hashtags = []; - -// Adding and removing -function addHashtag() { - var input = document.getElementById('hashtagInput'); - var value = input.value.trim().split(' ')[0].toLowerCase(); - if (!value || hashtags.contains(value)) - return; - - // Clear value and push new hashtag - input.value = ''; - var id = 'hashtag_'+hashtags.length; - hashtags.push(value); - - // Show new hashtag - var newElem = '<li id="'+id+'" class="fadable" onClick="'; - newElem += "clearHashtag('"+id+"');"; - newElem += '">'+value+'</li>'; - - document.getElementById('filters').innerHTML += newElem; - fadeIn(id); - filterByHashtag(); -} - -function clearHashtag(tag) { - document.getElementById(tag).outerHTML = ''; - hashtags.pop(tag); - filterByHashtag(); -} - -function clearHashtags() { - hashtags.length = 0; - document.getElementById('filters').innerHTML = ''; - filterByHashtag(); -} - -// Filtering -function filterByHashtag() { - var cards = document.getElementById('allcards').children; - for (var i = 0; i < cards.length; ++i) { - var card = cards[i]; - var filters = card.getElementsByClassName('filterlist')[0].children; - - // Determine if this card has the correct filters (hashtags) - var found = 0; - for (var j = 0; j < filters.length; ++j) { - filter = filters[j].innerHTML; - if (hashtags.contains(filter)) - ++found; - } - - // Set its visibility based on whether all hashtags were found - var ok = found == hashtags.length; - card.style.display = ok ? '' : 'none'; - } -} - -// Events -document.getElementById('hashtagInput').onkeypress = function(e) { - if (e.which == 13) { - addHashtag(); - return false; - } - - return (e.which >= 65 && e.which < 90) || - (e.which >= 97 && e.which < 122); -} -
@@ -1,10 +0,0 @@
-Array.prototype.contains = function(value) { - return this.indexOf(value) > -1; -} - -function fadeIn(elementId) { - var element = document.getElementById(elementId); - // Avoid single JavaScript rounds (http://stackoverflow.com/q/24148403) - element.offsetWidth = element.offsetWidth; - element.classList.add("fadein"); -}