From 3f695517195aa856b9904dcc93e040dfb465092d Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Sun, 13 Sep 2026 23:48:15 -0500 Subject: [PATCH] Some bug fixes in recent work. --- CHANGELOG | 70 +- CMakeLists.txt | 18 +- LICENSES | 6 + assets/Backdrop.singe | 7 +- assets/Forge/Author.singe | 49 +- assets/Forge/AuthorCompile.singe | 32 +- assets/Forge/Forge.singe | 1119 +++++++++- assets/Forge/games.dat | 1 - assets/Menu.singe | 54 +- assets/MenuDocument.singe | 6 +- assets/MenuOverlay.singe | 4 +- assets/Net.singe | 87 +- assets/Tools.singe | 2 +- assets/cacerts.pem | 3004 +++++++++++++++++++++++++++ build-docs.sh | 20 +- cmake/Superbuild.cmake | 15 +- docs/Forge.adoc | 253 +++ docs/Manual.adoc | 192 +- src/decode.c | 49 +- src/embedded.h | 1 + src/guiRender.cpp | 27 +- src/main.c | 287 +-- src/midi.c | 20 +- src/midiIo.c | 16 +- src/persist.c | 7 +- src/render.c | 16 +- src/renderGles.c | 107 +- src/scene.c | 24 +- src/scheduler.c | 74 +- src/singe.c | 910 ++++---- src/stats.c | 5 +- src/vfs.c | 19 +- src/videoPlayer.c | 740 +++---- testScripts/author/platformer.game | 2 +- testScripts/packedIo/packedIo.singe | 20 + testScripts/scene52.singe | 6 +- testScripts/scene55.singe | 4 +- testScripts/scene57.singe | 236 +++ 38 files changed, 6017 insertions(+), 1492 deletions(-) create mode 100644 assets/cacerts.pem create mode 100644 docs/Forge.adoc create mode 100644 testScripts/scene57.singe diff --git a/CHANGELOG b/CHANGELOG index 21d979d9f..8e5d20a93 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,8 +26,8 @@ API Changes the command line keep their values. Every game in a library therefore behaves the same whether it was started from the menu or from a shell. -- A game can be described rather than written. Singe/Author.singe and - Singe/AuthorCompile.singe take a table of layers, entities, behaviours +- A game can be described rather than written. Forge/Author.singe and + Forge/AuthorCompile.singe take a table of layers, entities, behaviours and rules and compile it into an ordinary Singe game: the rules become real Lua, so nothing walks a table every frame and the result can be read and edited by hand. There is no notion of genre in it -- a game @@ -38,6 +38,19 @@ API Changes releases, and the "lua" action is the deliberate way out when a rule needs something the vocabulary cannot say. See the manual. +- Forge runs as a game rather than only as a library: started from the + menu it opens on a chooser (its own descriptions, a new game from a + starter, a copy of anything dropped into its directory), the keys, the + mouse and the pad reach the editor, P saves, builds and plays what is on + screen and comes back to it, and ESC closes or leaves. Entities can be + added, duplicated, deleted and typed field by field -- position, look, + behaviours and their parameters, all from the manifest -- and renaming + one renames it in every rule. Conditions and actions are picked from + the vocabulary with their help beside them, rules reorder, a rule's + note is editable, and U undoes forty steps. The compiler loads the + runtime from Forge/, where it lives, so it no longer fails looking for + a Singe/Author.singe that never shipped. + - An editor for those descriptions, Forge, which is itself a Singe game: the canvas is the same overlay at the same coordinates the game will be played in, so what is placed is what is seen. Entity list @@ -1461,6 +1474,59 @@ Fixes materialNew, sceneGetSize and sceneGetStats no longer reject extra arguments. +- A review of the week's work (the io patch, the scheduler, Forge, the + menu, the GLES backend, saves and the GUI) fixed what it found: + + Lua's io on a packed asset: f:lines and io.lines with more than one + format handed back only the last value read a step; a later format + that comes up empty now returns what came before it with nil in its + place, as Lua does, and only an empty first read ends the loop. + read("a") reads in blocks rather than a byte at a time, read(0) is the + end-of-file test it is in Lua, and a NUL byte is no longer taken for + part of a number. testScripts/packedIo covers all of it. + + Timer and tween handles carry a generation: a handle kept past its + timer's end no longer cancels, or answers for, whatever timer was + given the same slot afterwards. + + Forge: a rule's keyHeld condition never fired from a real keyboard + (it compared a key name against the scancodes the engine delivers); + an entity whose look kind is unknown is drawn as a box with a message + instead of ending the game; and the compiler loads the runtime from + Forge/, where it lives, rather than a Singe/Author.singe that was never + shipped. The games.dat Forge writes no longer carries a DATA key the + engine does not read, and the manual's example loses it too. + + The menu: a games.dat entry with no ATTRACT clip no longer stops the + engine on either renderer, quitting with no games listed no longer + fails in onShutdown, and a directory entry lfs cannot describe is + skipped rather than indexed. + + The GLES backend freed nothing when its framebuffer cache filled and + was reset; the framebuffer objects go now. A save is written with a + single atomic rename everywhere but Windows, so the old save stays + whole until the new one is in place. A GUI texture decoded with a + padded pitch is packed row by row instead of uploaded with its padding + read as pixels. The menu's backdrop stops rewriting its grid colours + every frame once the grid is lit. + + Net.singe verified a connection against Linux's CA list by path, so + without a pin every HTTPS request failed on Windows, macOS and the + handhelds. The engine now extracts Mozilla's CA list as + Singe/cacerts.pem with the other support files and Net.singe uses it + wherever the platform's own list is missing. A chunked response -- + what an HTTP/1.1 server sends when it does not know the length -- is + unwrapped as it arrives rather than handed to the caller with the chunk + sizes still in it. And a mistyped value in a table given to scriptPush + or scriptExecute is reported like any other bad API argument -- the + script line and the call named -- rather than as a bare exit message. + +- Forge has its own manual, docs/Forge.adoc, built to Forge.html and + Forge.pdf beside the Singe manual; the engine's manual keeps a pointer. + The build packs Forge.game itself, with the manual inside, and Forge's + first run copies Forge.pdf out to its data directory and says where. + R redoes what U undid, forty steps deep. + SINGE 2.10 diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ecb6114a..2a6e8285e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -209,6 +209,7 @@ singeEmbed(${CMAKE_SOURCE_DIR}/assets/Tools.singe ${GENERATED_DIR}/Tools_singe.h # its own, not with the engine. Nothing of it -- not the editor, not the compiler, not the runtime # a game it builds loads -- belongs inside Singe. singeEmbed(${CMAKE_SOURCE_DIR}/assets/Net.singe ${GENERATED_DIR}/Net_singe.h "") +singeEmbed(${CMAKE_SOURCE_DIR}/assets/cacerts.pem ${GENERATED_DIR}/cacerts_pem.h "") singeEmbed(${CMAKE_SOURCE_DIR}/assets/Master.singe ${GENERATED_DIR}/Master_singe.h "") singeEmbed(${CMAKE_SOURCE_DIR}/assets/Backdrop.singe ${GENERATED_DIR}/Backdrop_singe.h "") singeEmbed(${CMAKE_SOURCE_DIR}/assets/MenuDocument.singe ${GENERATED_DIR}/MenuDocument_singe.h "") @@ -295,6 +296,18 @@ singeEmbedLua(thirdparty/lume/lume.lua "") singeEmbedLua(thirdparty/inspect.lua/inspect.lua "") singeEmbedLua(thirdparty/bump.lua/bump.lua "") +# Forge's manual, rendered the same way. It is not embedded: the superbuild packs it into +# Forge.game (cmake/Superbuild.cmake), which is the one thing that ships Forge. +add_custom_command( + OUTPUT ${MANUAL_DIR}/Forge.pdf + COMMAND ${CMAKE_COMMAND} -E make_directory ${MANUAL_DIR} + COMMAND ${ASCIIDOCTOR_PDF} -a revnumber=${PROJECT_VERSION} ${CMAKE_SOURCE_DIR}/docs/Forge.adoc -o ${MANUAL_DIR}/Forge.pdf + DEPENDS docs/Forge.adoc + COMMENT "Rendering Forge.pdf" + VERBATIM +) +add_custom_target(forgeManual ALL DEPENDS ${MANUAL_DIR}/Forge.pdf) + # Optional HTML manual for browsing: cmake --build . --target docs if(ASCIIDOCTOR) add_custom_target(docs @@ -819,7 +832,6 @@ target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_BINARY_DIR}/gen # Rockchip's hardware decoders are reached by name (videoPlayer.c), and only exist when FFmpeg was # built against the Rockchip MPP library. Asking FFmpeg itself is what decides it. -execute_process(COMMAND ${CMAKE_COMMAND} -E echo "" OUTPUT_QUIET) if(EXISTS ${BUILD_DIR}/lib/pkgconfig/libavcodec.pc) file(READ ${BUILD_DIR}/lib/pkgconfig/libavcodec.pc avcodecPc) if(avcodecPc MATCHES "rockchip_mpp") @@ -857,9 +869,9 @@ endif() # System libraries follow the static ones so their symbols resolve for everything above. # Apple's linker has no --start-group, so the list is repeated there instead. if(KANGAROO_OS STREQUAL "macos") - target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ${STATIC_LIBS} ${STATIC_LIBS} ${STATIC_LIBS} Jolt::Jolt RecastNavigation::DetourCrowd RecastNavigation::Detour RecastNavigation::Recast RmlUi::Lua RmlUi::Debugger RmlUi::Core ${SYSTEM_LIBS} -pthread -lm) + target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ${STATIC_LIBS} ${STATIC_LIBS} ${STATIC_LIBS} Jolt::Jolt RecastNavigation::DetourCrowd RecastNavigation::Detour RecastNavigation::Recast RmlUi::Lua RmlUi::Core ${SYSTEM_LIBS} -pthread -lm) else() - target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE -Wl,--start-group ${STATIC_LIBS} -Wl,--end-group Jolt::Jolt RecastNavigation::DetourCrowd RecastNavigation::Detour RecastNavigation::Recast RmlUi::Lua RmlUi::Debugger RmlUi::Core ${SYSTEM_LIBS} -pthread -lm) + target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE -Wl,--start-group ${STATIC_LIBS} -Wl,--end-group Jolt::Jolt RecastNavigation::DetourCrowd RecastNavigation::Detour RecastNavigation::Recast RmlUi::Lua RmlUi::Core ${SYSTEM_LIBS} -pthread -lm) endif() diff --git a/LICENSES b/LICENSES index d61ce50b2..edea0cbeb 100644 --- a/LICENSES +++ b/LICENSES @@ -67,3 +67,9 @@ Fonts ----- FreeSansBold GPL-3.0 with font exception https://www.gnu.org/software/freefont + + +Data +---- + +Mozilla CA list 20260601 (Debian ca-certificates) MPL-2.0 https://wiki.mozilla.org/CA (assets/cacerts.pem) diff --git a/assets/Backdrop.singe b/assets/Backdrop.singe index 045fb9eae..7c29b552e 100644 --- a/assets/Backdrop.singe +++ b/assets/Backdrop.singe @@ -134,6 +134,7 @@ local backdropStarted = 0 -- singeGetTicks() when the backdrop's clock began local backdropTagX = 0 local backdropTagY = 0 local blown = false -- Whether the charge has gone off. +local gridLit = false -- Whether the grid has finished coming up and its colours are final. local backdropFont = nil -- ===== The backdrop ========================================================================== @@ -473,7 +474,6 @@ function backdropFrame() emitterBurst(core, 110) emitterBurst(flash, 5) emitterBurst(sparks, 130) - lightSetIntensity(fireLight, 400) end -- The fire's light dies over the first second and a half. @@ -483,13 +483,15 @@ function backdropFrame() -- there is no moment where one thing has ended and the next has not begun. local lit = span(t, GRID_UP, GRID_LIT) - if (lit > 0) and (lit < 1.0001) then + if (lit > 0) and not gridLit then materialSetColor(gridMat, 255 * lit, 45 * lit, 190 * lit) materialSetColor(horizMat, 255 * lit, 150 * lit, 235 * lit) materialSetColor(sunMat2, 255 * lit, 130 * lit, 55 * lit) -- The sky stops being black at the same time, or the grid would rise into a void. sceneSetBackground(FOG_R * lit, FOG_G * lit, FOG_B * lit, 255) sceneSetFog(FOG_R, FOG_G, FOG_B, FOG_NEAR, FOG_FAR) + -- Once fully lit the colours are final, so they are not written again every frame. + gridLit = (lit >= 1) end if lit > 0 then -- Scrolling from the moment it is visible, so it is already alive when it arrives. @@ -579,6 +581,7 @@ function backdropBegin(showIntro) backdropStarted = singeGetTicks() - (showIntro and 0 or (LIFT_TO * 1000)) blown = not showIntro + gridLit = false if not showIntro then nodeSetVisible(logo, false) end diff --git a/assets/Forge/Author.singe b/assets/Forge/Author.singe index 24555cafc..664c4c84d 100644 --- a/assets/Forge/Author.singe +++ b/assets/Forge/Author.singe @@ -25,7 +25,7 @@ -- The runtime an authored game is compiled against (PLAN section 58). -- --- A game made with the authoring tools is not interpreted: Singe/AuthorCompile.singe turns its +-- A game made with the authoring tools is not interpreted: Forge/AuthorCompile.singe turns its -- description into ordinary Lua, and this file is the library that Lua calls. Rules become real -- `if` statements, so nothing walks a table every frame and the result can be opened in ZeroBrane -- and edited by hand like any other game. @@ -163,7 +163,9 @@ AUTHOR.behaviours.solid = { help = "Immovable ground or a wall.", params = {}, attach = function(entity) - bodyNew(entity.node, BODY_STATIC, SHAPE_BOX, entity.look.w / 2, entity.look.h / 2, entity.look.w / 2) + local w, h = authorSize(entity) + + bodyNew(entity.node, BODY_STATIC, SHAPE_BOX, w / 2, h / 2, w / 2) end } @@ -171,9 +173,11 @@ AUTHOR.behaviours.platformer = { help = "Runs, falls and jumps: the engine's character controller in 2D.", params = { speed = "number", jump = "number" }, attach = function(entity, params) + local w, h = authorSize(entity) + entity.speed = params.speed or 200 entity.jump = params.jump or 500 - playerNew(entity.node, SHAPE_CAPSULE, entity.look.w * PLAYER_RADIUS, entity.look.h) + playerNew(entity.node, SHAPE_CAPSULE, w * PLAYER_RADIUS, h) end, step = function(entity) -- The rules say which way; this clears it each frame so releasing a key stops the run. @@ -205,7 +209,7 @@ AUTHOR.behaviours.drift = { AUTHOR.conditions.keyHeld = { help = "A key is down.", params = { key = "scancode" }, - emit = function(p) return string.format("authorKeyHeld(SCANCODE.%s)", p.key) end + emit = function(p) return string.format("authorKeyHeld(SCANCODE.%s.value)", p.key) end } AUTHOR.conditions.switchHeld = { @@ -364,13 +368,27 @@ end function authorTouching(idA, idB) - local a = AUTHOR_WORLD[idA] - local b = AUTHOR_WORLD[idB] + local a = AUTHOR_WORLD[idA] + local b = AUTHOR_WORLD[idB] local ax, ay = authorPosition(a) local bx, by = authorPosition(b) + local aw, ah = authorSize(a) + local bw, bh = authorSize(b) - return collideRects(ax - a.look.w / 2, ay - a.look.h / 2, a.look.w, a.look.h, - bx - b.look.w / 2, by - b.look.h / 2, b.look.w, b.look.h) + return collideRects(ax - aw / 2, ay - ah / 2, aw, ah, bx - bw / 2, by - bh / 2, bw, bh) +end + + +-- How big an entity is, for touching and for the body a behaviour gives it. A box says; a sprite +-- is its image; a text look has no size of its own and counts as a point unless it was given one. +function authorSize(entity) + local look = entity.look + + if entity.sprite ~= nil then + return spriteGetWidth(entity.sprite), spriteGetHeight(entity.sprite) + end + + return look.w or 0, look.h or 0 end @@ -431,9 +449,17 @@ function authorMake(description) node = nodeNew() } local look = AUTHOR.looks[entity.look.kind] - local b nodeSetPosition(entity.node, description.x or 0, description.y or 0, 0) + if look == nil then + -- A look that does not exist would take the game down when it came to be drawn; a box + -- is what it wears instead, and the console says which entity it was. + debugPrint("Author: no look called '" .. tostring(entity.look.kind) .. "' on " .. tostring(entity.id) .. "; drawing a box") + entity.look.kind = "box" + entity.look.w = entity.look.w or 20 + entity.look.h = entity.look.h or 20 + look = AUTHOR.looks.box + end if look.load then look.load(entity) end @@ -458,8 +484,6 @@ end -- Starts the layers the game declared. Anything a layer needs of the engine is asked for here and -- nowhere else, which is what keeps the rest of this file free of genre. function authorBegin(layers) - local layer - -- A font, because the text look draws with fontPrint and fontPrint ends the game when none is -- selected. Every test had a scene select one first; a released game has nobody to do that, -- and died on its first text entity. A game that wants its own calls fontSelect afterwards. @@ -489,12 +513,9 @@ end function authorFrame(rules) local now = authorTime() local dt = now - lastTime - local entity lastTime = now for _, entity in ipairs(AUTHOR_ORDER) do - local b - for _, b in ipairs(entity.behaviours) do local kind = AUTHOR.behaviours[b.kind] diff --git a/assets/Forge/AuthorCompile.singe b/assets/Forge/AuthorCompile.singe index 7ba0b481c..1801d658a 100644 --- a/assets/Forge/AuthorCompile.singe +++ b/assets/Forge/AuthorCompile.singe @@ -33,10 +33,14 @@ -- This is written in Lua rather than in util/ as a Python script because the editor is itself a -- Singe game: it has to be able to compile what it is editing without leaving the engine. It -- lives with Forge rather than in Singe/ because only building needs it -- a finished game is --- already Lua. Singe/Author.singe, the runtime that game loads, has to stay an engine support --- file: a game handed to someone who has never installed Forge still has to run. +-- already Lua. Author.singe, the runtime that game loads, travels with the game: a copy is +-- written beside it when it is built, so a game handed to someone who has never installed Forge +-- still runs. The compiler loads it too, for the vocabulary it emits against. -dofile("Singe/Author.singe") +-- Where the runtime is taken from when a game is built. Forge carries it; a game gets a copy. +AUTHOR_RUNTIME = "Forge/Author.singe" + +dofile(AUTHOR_RUNTIME) local INDENT = "\t" @@ -58,7 +62,6 @@ end local function tableSource(t) local parts = {} local keys = {} - local key for key in pairs(t) do keys[#keys + 1] = key @@ -88,7 +91,6 @@ end local function emitConditions(rule) local tests = {} - local item for _, item in ipairs(rule.when or {}) do local test = emitOne(AUTHOR.conditions, item, "condition") @@ -108,8 +110,6 @@ end local function emitRule(out, rule, index) - local item - out[#out + 1] = string.format("%s-- %s", INDENT, rule.note or ("rule " .. index)) out[#out + 1] = string.format("%sif %s then", INDENT, emitConditions(rule)) for _, item in ipairs(rule.act or {}) do @@ -131,11 +131,8 @@ end -- the engine's, as it is for every game ever written for Singe. function authorCompile(game) local out = {} - local entity - local layer - local rule - out[#out + 1] = "-- Generated by Singe/AuthorCompile.singe from " .. (game.source or "a game description") + out[#out + 1] = "-- Generated by Forge/AuthorCompile.singe from " .. (game.source or "a game description") out[#out + 1] = "-- " .. (game.title or "Untitled") out[#out + 1] = "--" out[#out + 1] = "-- This is an ordinary Singe game and can be edited by hand. Doing so and then" @@ -160,7 +157,6 @@ function authorCompile(game) for _, entity in ipairs(game.entities or {}) do local parts = {} - local b parts[#parts + 1] = string.format("id = %q", entity.id) parts[#parts + 1] = string.format("x = %s, y = %s", entity.x or 0, entity.y or 0) @@ -226,10 +222,6 @@ function authorCopy(fromPath, toPath) end --- Where the runtime is taken from when a game is built. Forge carries it; a game gets a copy. -AUTHOR_RUNTIME = "Forge/Author.singe" - - -- The directory part of a path, with its separator, or "" for a bare name. local function directoryOf(path) return (string.match(path, "^(.*[/\\])") or "") @@ -267,9 +259,6 @@ end -- Whether a table holds only leaves, in which case it is written on one line. A rule reads far -- better as { "keyHeld", key = "LEFT" } than as six lines, and it is what an author typed. local function isLeaf(t) - local key - local value - for key, value in pairs(t) do if type(value) == "table" then return false @@ -288,8 +277,6 @@ local function valueSource(value, depth) local parts = {} local keys = {} local count = 0 - local key - local item local sep local open local close @@ -340,7 +327,6 @@ end function authorSave(game, path) local file = assert(io.open(path, "w")) local copy = {} - local key -- source says where a description came from and is set by the loader, so writing it back out -- would make a description that had been through the editor differ from one that had not. @@ -349,7 +335,7 @@ function authorSave(game, path) copy[key] = game[key] end end - file:write("-- Written by Singe/AuthorCompile.singe. Edit by hand or in the editor; either is fine.\n") + file:write("-- Written by Forge/AuthorCompile.singe. Edit by hand or in the editor; either is fine.\n") file:write("return " .. valueSource(copy, 0) .. "\n") file:close() diff --git a/assets/Forge/Forge.singe b/assets/Forge/Forge.singe index bbd13c506..34e07d60f 100644 --- a/assets/Forge/Forge.singe +++ b/assets/Forge/Forge.singe @@ -23,6 +23,7 @@ + -- Forge: the authoring tool (PLAN section 58). -- -- It is a Singe game. Nothing here is a preview: the canvas is the same overlay, at the same @@ -33,6 +34,11 @@ -- collidePointRect, which is what Singe has always done with a pointer and a set of rectangles. -- They compose because the engine offers a button to the GUI first and passes on what it did not -- use, while pointer motion is never consumed at all. +-- +-- The file is two things. Everything up to the last section is a library: a scene that drives +-- the editor sets FORGE_LIBRARY before loading it and supplies its own callbacks. The last section +-- is Forge as a game -- the file chooser, the engine callbacks and the play button -- and runs only +-- when nothing has said otherwise. -- Framework is wanted for SCANCODE, which the key handling below names; AuthorCompile brings in -- the runtime and the manifest. A generated game loads Framework for itself, so the editor only @@ -44,28 +50,38 @@ local lfs = require("lfs") FORGE = { - game = nil, -- The description being edited. - path = nil, - selected = nil, -- Index into game.entities. - dirty = false, - gui = nil, - document = nil, - held = nil, -- The entity being dragged. - grabX = 0, - grabY = 0, - panelX = 0, -- The chrome slides sideways so nothing is permanently under it. - panelDrag = nil, -- Offset from the panel's left edge while it is being dragged. - mode = "entities", -- or "rules". The panel is narrow; one list at a time reads better. - rule = 1, -- Selected rule. - part = 0, -- Selected condition or action within it; 0 is the rule itself. - message = "loaded" + game = nil, -- The description being edited. + path = nil, + selected = nil, -- Index into game.entities. + dirty = false, + gui = nil, + document = nil, + held = nil, -- The entity being dragged. + grabX = 0, + grabY = 0, + panelX = 0, -- The chrome slides sideways so nothing is permanently under it. + panelDrag = nil, -- Offset from the panel's left edge while it is being dragged. + mode = "entities", -- or "rules". The panel is narrow; one list at a time reads better. + rule = 1, -- Selected rule. + part = 0, -- Selected condition or action within it; 0 is the rule itself. + lastField = nil, -- The parameter ENTER edited last, so the next ENTER moves on from it. + editing = nil, -- { key, text, was, set }: see "Typing a value". + picker = nil, -- A list being chosen from: see "Pickers". + undo = {}, -- Copies of the description, newest last. + redo = {}, -- What undo took away, newest last; any other change empties it. + sprites = {}, -- Sprite looks loaded for drawing, by file name; false when missing. + closing = false, -- ESC has been pressed once on an unsaved description. + message = "loaded" } -local PANEL_W = 190 -- Matches AuthorEdit.rml; the canvas starts to the right of it. -local HANDLE = 6 -- Half the size of the square drawn at an entity's own position. -local TAB_W = 16 -- The panel's drag tab, drawn on the canvas just outside its edge. -local TAB_H = 54 -local TAB_Y = 10 +local PANEL_W = 190 -- Matches Forge.rml; the canvas starts to the right of it. +local HANDLE = 6 -- Half the size of the square drawn at an entity's own position. +local TAB_W = 16 -- The panel's drag tab, drawn on the canvas just outside its edge. +local TAB_H = 54 +local TAB_Y = 10 +local UNDO_MAX = 40 -- Steps kept. A description is small; forty copies are nothing. +local NEW_W = 40 -- The box a new entity wears until it is given a look. +local NEW_H = 40 local function element(id) @@ -73,26 +89,51 @@ local function element(id) end +-- A sprite look's image, loaded once and kept, so the canvas can show it. A file that is not +-- there answers nil rather than ending the editor the way spriteLoad would: the entity is drawn +-- as a box until the name is right. +local function spriteFor(file) + if FORGE.sprites[file] == nil then + if (file ~= "") and (lfs.attributes(file, "mode") == "file") then + FORGE.sprites[file] = spriteLoad(file) + else + FORGE.sprites[file] = false + end + end + + return FORGE.sprites[file] or nil +end + + -- An entity's box in overlay coordinates. A look without a size still gets one, so that a text --- entity can be picked up and moved like anything else. +-- entity can be picked up and moved like anything else; a sprite is as big as its image. function forgeBounds(entity) - local w = (entity.look and entity.look.w) or 90 - local h = (entity.look and entity.look.h) or 20 + local look = entity.look or {} + local w = look.w or 90 + local h = look.h or 20 + + if look.kind == "sprite" then + local sprite = spriteFor(look.file or "") + + if sprite ~= nil then + w = spriteGetWidth(sprite) + h = spriteGetHeight(sprite) + end + end return entity.x - w / 2, entity.y - h / 2, w, h end function forgeSelect(index) - FORGE.selected = index + FORGE.selected = index + FORGE.lastField = nil forgeRefresh() end -- The topmost entity under a point, so overlapping things pick the one drawn last. function forgePick(x, y) - local index - for index = #FORGE.game.entities, 1, -1 do local bx, by, bw, bh = forgeBounds(FORGE.game.entities[index]) @@ -118,25 +159,41 @@ local function escape(text) end -local function refreshEntities() +-- The fields of whatever is selected, one row each, with the one ENTER is on marked. This is the +-- detail panel for an entity and for a condition or action alike: the same rows, the same keys. +local function fieldRows(fields) local rows = {} - local index + + for _, name in ipairs(fields.names) do + local class = (name == FORGE.lastField) and "part here" or "part" + + rows[#rows + 1] = string.format('
%s %s
', class, escape(name), escape(tostring(fields.get(name)))) + end + + return table.concat(rows) +end + + +local function refreshEntities() + local rows = {} + local fields = forgeFields() for index, entity in ipairs(FORGE.game.entities) do local class = (index == FORGE.selected) and "row selected" or "row" rows[#rows + 1] = string.format('
%s
', index, class, escape(entity.id)) end + if #rows == 0 then + rows[1] = '
no entities yet: A adds one
' + end element("list").inner_rml = table.concat(rows) for index = 1, #FORGE.game.entities do guiSetHandler(FORGE.gui, FORGE.document, "e" .. index, "click", function() forgeSelect(index) end) end - if FORGE.selected ~= nil then - local entity = FORGE.game.entities[FORGE.selected] - - element("detail").inner_rml = string.format("%s
x %d y %d
%s", escape(entity.id), entity.x or 0, entity.y or 0, escape(FORGE.message)) + if fields ~= nil then + element("detail").inner_rml = fieldRows(fields) .. '
' .. escape(FORGE.message) .. "
" else element("detail").inner_rml = "nothing selected
" .. escape(FORGE.message) end @@ -146,9 +203,8 @@ end -- The event sheet. The selected rule is opened in place and its conditions and actions listed -- under it, because a rule only means anything as a whole: seeing "when" without "then" is no use. local function refreshRules() - local rows = {} - local index - local rule + local rows = {} + local fields = forgeFields() for index, rule in ipairs(FORGE.game.rules or {}) do local class = (index == FORGE.rule) and "row selected" or "row" @@ -156,7 +212,6 @@ local function refreshRules() rows[#rows + 1] = string.format('
%d. %s
', class, index, escape(rule.note or "rule")) if index == FORGE.rule then local parts = forgeParts(rule) - local at for at, part in ipairs(parts) do local mark = (at == FORGE.part) and "part here" or "part" @@ -164,15 +219,30 @@ local function refreshRules() rows[#rows + 1] = string.format('
%s %s
', mark, (part.kind == "when") and "when" or "then", escape(forgePartText(part.item))) end if #parts == 0 then - rows[#rows + 1] = '
(empty)
' + rows[#rows + 1] = '
(empty: C adds a condition, T an action)
' end end end if #rows == 0 then - rows[1] = '
no rules yet
' + rows[1] = '
no rules yet: N adds one
' end element("list").inner_rml = table.concat(rows) - element("detail").inner_rml = string.format("rule %d of %d
%s", FORGE.rule, #(FORGE.game.rules or {}), escape(FORGE.message)) + element("detail").inner_rml = string.format("rule %d of %d
%s%s", FORGE.rule, #(FORGE.game.rules or {}), (fields ~= nil) and fieldRows(fields) or "", '
' .. escape(FORGE.message) .. "
") +end + + +-- A picker takes the list over while it is up: the vocabulary is what is being chosen from, and +-- the panel is too narrow to show it beside anything else. +local function refreshPicker() + local rows = {} + + for at, item in ipairs(FORGE.picker.items) do + local class = (at == FORGE.picker.at) and "row selected" or "row" + + rows[#rows + 1] = string.format('
%s
', class, escape(item.name)) + end + element("list").inner_rml = table.concat(rows) + element("detail").inner_rml = escape(FORGE.picker.title) .. "
" .. escape(FORGE.picker.items[FORGE.picker.at].help or "") .. '
UP / DOWN choose, ENTER takes it, ESC leaves it
' end @@ -182,7 +252,9 @@ function forgeRefresh() end element("title").inner_rml = escape(FORGE.game.title or "Untitled") .. (FORGE.dirty and " *" or "") element("grip").inner_rml = (FORGE.mode == "rules") and "≡ Rules" or "≡ Entities" - if FORGE.mode == "rules" then + if FORGE.picker ~= nil then + refreshPicker() + elseif FORGE.mode == "rules" then refreshRules() else refreshEntities() @@ -196,8 +268,22 @@ function forgeBegin(path) if FORGE.game == nil then return false end - FORGE.gui = guiNew(overlayGetWidth(), overlayGetHeight()) - FORGE.document = guiLoad(FORGE.gui, "Forge/Forge.rml") + FORGE.game.entities = FORGE.game.entities or {} + FORGE.game.rules = FORGE.game.rules or {} + FORGE.game.layers = FORGE.game.layers or {} + FORGE.selected = nil + FORGE.rule = 1 + FORGE.part = 0 + FORGE.lastField = nil + FORGE.editing = nil + FORGE.picker = nil + FORGE.undo = {} + FORGE.redo = {} + FORGE.dirty = false + FORGE.closing = false + FORGE.message = "loaded" + FORGE.gui = guiNew(overlayGetWidth(), overlayGetHeight()) + FORGE.document = guiLoad(FORGE.gui, "Forge/Forge.rml") guiSetInput(FORGE.gui, true) guiSetHandler(FORGE.gui, FORGE.document, "grip", "mousedown", function() local x = mouseGetPosition(0) @@ -216,6 +302,29 @@ function forgeBegin(path) end +-- Puts the editor away. The description is dropped, saved or not: the caller decides that first. +function forgeClose() + if FORGE.gui ~= nil then + guiDelete(FORGE.gui) + end + for _, sprite in pairs(FORGE.sprites) do + if sprite then + spriteUnload(sprite) + end + end + FORGE.gui = nil + FORGE.document = nil + FORGE.game = nil + FORGE.path = nil + FORGE.sprites = {} + FORGE.editing = nil + FORGE.picker = nil + FORGE.held = nil + FORGE.undo = {} + FORGE.redo = {} +end + + -- Moves the selected entity. The description is the truth; nothing is cached anywhere else, so -- saving is only writing it back out. function forgeMove(index, x, y) @@ -231,6 +340,7 @@ end function forgeSave() authorSave(FORGE.game, FORGE.path) FORGE.dirty = false + FORGE.closing = false FORGE.message = "saved" forgeRefresh() @@ -285,11 +395,13 @@ function forgeExport(folder, name) -- The description travels with the game, so the release can be opened and edited again. authorSave(FORGE.game, folder .. "/" .. script .. ".game") - -- A games.dat so the menu lists it, written from what the description already knows. + -- A games.dat so the menu lists it, written from what the description already knows. A game + -- over a disc names its video in the description; anything else plays over the menu's, since + -- the engine wants one and the game draws over all of it anyway. dat = io.open(folder .. "/games.dat", "w") if dat ~= nil then - dat:write(string.format("-- Written by Forge.\nGAMES = {\n\t{\n\t\tTITLE = %q,\n\t\tSCRIPT = %q,\n\t\tDATA = %q,\n\t\tVIDEO = \"Singe/menuBackground.mkv\",\n\t\tRESOLUTION_X = %d,\n\t\tRESOLUTION_Y = %d\n\t}\n}\n", - title, folder .. "/" .. script .. ".singe", folder, overlayGetWidth(), overlayGetHeight())) + dat:write(string.format("-- Written by Forge.\nGAMES = {\n\t{\n\t\tTITLE = %q,\n\t\tSCRIPT = %q,\n\t\tVIDEO = %q,\n\t\tRESOLUTION_X = %d,\n\t\tRESOLUTION_Y = %d\n\t}\n}\n", + title, folder .. "/" .. script .. ".singe", forgeVideo(), overlayGetWidth(), overlayGetHeight())) dat:close() end @@ -300,13 +412,21 @@ function forgeExport(folder, name) end --- Compiles what is on screen and hands back the path, so the caller can dofile it and play. +-- The video a description plays over. Its own when it names one, the menu's otherwise. +function forgeVideo() + return FORGE.game.video or "Singe/menuBackground.mkv" +end + + +-- Compiles what is on screen and hands back the path, so the caller can dofile it and play. The +-- runtime goes beside it, because that is where the compiled game looks for it. function forgeBuild(outputFile) FORGE.game.source = FORGE.path local file = assert(io.open(outputFile, "w")) file:write(authorCompile(FORGE.game)) file:close() + authorCopy(AUTHOR_RUNTIME, (string.match(outputFile, "^(.*[/\\])") or "") .. "Author.singe") FORGE.message = "built" forgeRefresh() @@ -332,8 +452,13 @@ function forgePress(x, y) if FORGE.held ~= nil then local entity = FORGE.game.entities[FORGE.held] + -- One undo step for the whole drag, taken before it starts. + forgeRemember() FORGE.grabX = x - entity.x FORGE.grabY = y - entity.y + if FORGE.mode ~= "entities" then + FORGE.mode = "entities" + end forgeSelect(FORGE.held) end end @@ -391,14 +516,12 @@ end -- Draws the description. Note that this draws the *description*, not a running game: there are -- no nodes here and no physics, which is why an entity can be dragged through a wall. function forgeDraw(pointerX, pointerY) - local index - colorBackground(18, 18, 26, 255) overlayClear() for index, entity in ipairs(FORGE.game.entities) do local bx, by, bw, bh = forgeBounds(entity) local look = entity.look or {} - local row + local sprite = (look.kind == "sprite") and spriteFor(look.file or "") or nil -- Everything is drawn, including whatever is under the chrome. The panel is opaque and -- slides, so it covers rather than hides: drag it aside and what was beneath it is there, @@ -410,6 +533,8 @@ function forgeDraw(pointerX, pointerY) fontPrint(entity.x, entity.y, look.text) end overlayBox(bx, by, bx + bw, by + bh) + elseif sprite ~= nil then + spriteDraw(sprite, bx, by) else for row = math.floor(by), math.floor(by + bh) do overlayLine(bx, row, bx + bw, row) @@ -423,7 +548,6 @@ function forgeDraw(pointerX, pointerY) end -- The panel's drag tab. local tx, ty, tw, th = forgeTab() - local trow colorForeground(70, 70, 96, 255) for trow = ty, ty + th do @@ -443,6 +567,334 @@ function forgeDraw(pointerX, pointerY) end +-- ===== Undo and redo ========================================================================== +-- +-- The description is small and plain data, so undo is a copy of the whole thing taken before +-- each change, and undoing is putting the last copy back. Nothing has to know how to reverse +-- itself, which is what keeps every later feature from having to. Redo is the same the other +-- way: what undo took out is kept until something else changes. + +local function copyOf(value) + local out + + if type(value) ~= "table" then + return value + end + out = {} + for key, item in pairs(value) do + out[key] = copyOf(item) + end + + return out +end + + +-- Keeps a copy on one of the two stacks, oldest dropped once it is full. +local function keep(stack) + stack[#stack + 1] = copyOf(FORGE.game) + if #stack > UNDO_MAX then + table.remove(stack, 1) + end +end + + +-- Call before changing the description. A drag is one step, a typed value is one step. A change +-- made by hand is the end of the redo history: what was undone before it no longer applies. +function forgeRemember() + keep(FORGE.undo) + FORGE.redo = {} +end + + +-- Puts a copy back in place of the description, keeping the current one on the other stack. +local function swapTo(copy, other, message) + keep(other) + FORGE.game = copy + FORGE.editing = nil -- Whatever was being typed referred to the description just replaced. + FORGE.held = nil + FORGE.selected = FORGE.selected and math.min(FORGE.selected, #FORGE.game.entities) or nil + if FORGE.selected == 0 then + FORGE.selected = nil + end + FORGE.rule = math.max(1, math.min(FORGE.rule, #FORGE.game.rules)) + FORGE.part = math.min(FORGE.part, #forgeParts(FORGE.game.rules[FORGE.rule] or {})) + FORGE.dirty = true + FORGE.message = message + forgeRefresh() +end + + +function forgeUndo() + local was = table.remove(FORGE.undo) + + if was == nil then + FORGE.message = "nothing to undo" + forgeRefresh() + return false + end + swapTo(was, FORGE.redo, "undone") + + return true +end + + +function forgeRedo() + local again = table.remove(FORGE.redo) + + if again == nil then + FORGE.message = "nothing to redo" + forgeRefresh() + return false + end + swapTo(again, FORGE.undo, "redone") + + return true +end + + +-- ===== Entities =============================================================================== +-- +-- An entity is a position, a look and a list of behaviours. All of it is edited the same way a +-- rule's parameters are: ENTER walks the fields, and each is typed. The fields come from the +-- manifest -- a look's parameters, a behaviour's parameters -- so a new look or behaviour is +-- editable the moment it is declared. + +-- A name no other entity has, from a stem. +local function uniqueId(stem) + local taken = {} + local n = 1 + + for _, entity in ipairs(FORGE.game.entities) do + taken[entity.id] = true + end + if not taken[stem] then + return stem + end + while taken[stem .. n] do + n = n + 1 + end + + return stem .. n +end + + +-- A new box in the middle of the canvas, clear of the panel, selected and ready to be moved. +function forgeEntityAdd(x, y) + local entity = { + id = uniqueId("thing"), + x = math.floor(x or ((FORGE.panelX + PANEL_W + overlayGetWidth()) / 2)), + y = math.floor(y or (overlayGetHeight() / 2)), + look = { kind = "box", w = NEW_W, h = NEW_H, r = 180, g = 180, b = 190 }, + behaviours = {} + } + + forgeRemember() + FORGE.game.entities[#FORGE.game.entities + 1] = entity + FORGE.dirty = true + FORGE.message = "added " .. entity.id + forgeSelect(#FORGE.game.entities) + + return #FORGE.game.entities +end + + +-- A copy of the selected entity, a little to one side so the two can be told apart. +function forgeEntityDuplicate() + local entity = FORGE.game.entities[FORGE.selected] + local twin + + if entity == nil then + return nil + end + forgeRemember() + twin = copyOf(entity) + twin.id = uniqueId(entity.id) + twin.x = entity.x + 20 + twin.y = entity.y + 20 + FORGE.game.entities[#FORGE.game.entities + 1] = twin + FORGE.dirty = true + FORGE.message = "copied " .. entity.id .. " as " .. twin.id + forgeSelect(#FORGE.game.entities) + + return #FORGE.game.entities +end + + +function forgeEntityDelete() + local entity = FORGE.game.entities[FORGE.selected] + + if entity == nil then + return false + end + forgeRemember() + table.remove(FORGE.game.entities, FORGE.selected) + FORGE.dirty = true + FORGE.message = "deleted " .. entity.id + if #FORGE.game.entities == 0 then + forgeSelect(nil) + else + forgeSelect(math.min(FORGE.selected, #FORGE.game.entities)) + end + + return true +end + + +-- The sorted keys of a parameter table, so fields always read in the same order. +local function sortedKeys(t) + local keys = {} + + for key in pairs(t or {}) do + keys[#keys + 1] = key + end + table.sort(keys, function(a, b) return tostring(a) < tostring(b) end) + + return keys +end + + +-- The behaviour of a kind an entity carries, if any. +local function behaviourOf(entity, kind) + for _, b in ipairs(entity.behaviours or {}) do + if b.kind == kind then + return b + end + end + + return nil +end + + +-- An entity's fields, in the order the panel shows them: what every entity has, then what its +-- look takes, then its behaviours and what each of those takes. A behaviour's parameter is +-- named kind.parameter, which is how it is typed as well. +function forgeEntityFieldNames(entity) + local names = { "id", "x", "y", "kind" } + local look = AUTHOR.looks[(entity.look or {}).kind] + + for _, key in ipairs(sortedKeys(look and look.params or entity.look)) do + if key ~= "kind" then + names[#names + 1] = key + end + end + names[#names + 1] = "behaviours" + for _, b in ipairs(entity.behaviours or {}) do + local kind = AUTHOR.behaviours[b.kind] + + for _, key in ipairs(sortedKeys(kind and kind.params or b)) do + if key ~= "kind" then + names[#names + 1] = b.kind .. "." .. key + end + end + end + + return names +end + + +function forgeEntityGet(entity, name) + local kind, key = string.match(name, "^([^.]+)%.(.+)$") + + if (name == "id") or (name == "x") or (name == "y") then + return entity[name] + elseif name == "kind" then + return (entity.look or {}).kind + elseif name == "behaviours" then + local kinds = {} + + for _, b in ipairs(entity.behaviours or {}) do + kinds[#kinds + 1] = b.kind + end + return table.concat(kinds, ", ") + elseif kind ~= nil then + local b = behaviourOf(entity, kind) + + return b and b[key] or nil + end + + return (entity.look or {})[name] +end + + +-- Renaming an entity renames it in every rule that talks about it, which is what the author meant. +local function renameEverywhere(from, to) + for _, rule in ipairs(FORGE.game.rules) do + for _, list in ipairs({ rule.when or {}, rule.act or {} }) do + for _, item in ipairs(list) do + local entry = AUTHOR.conditions[item[1]] or AUTHOR.actions[item[1]] + + for key, kind in pairs(entry and entry.params or {}) do + if (kind == "entity") and (item[key] == from) then + item[key] = to + end + end + end + end + end +end + + +-- Fills in whatever a look or a behaviour declares and the table does not yet have, so a kind +-- that was just typed in has a value for every parameter and compiles at once. +local function fillDefaults(t, params) + for key, kind in pairs(params or {}) do + if t[key] == nil then + t[key] = FORGE_DEFAULTS[kind] + end + end +end + + +function forgeEntitySet(entity, name, value) + local kind, key = string.match(name, "^([^.]+)%.(.+)$") + + entity.look = entity.look or {} + if name == "id" then + if (value ~= "") and (value ~= entity.id) then + renameEverywhere(entity.id, value) + entity.id = value + end + elseif (name == "x") or (name == "y") then + if type(value) == "number" then + entity[name] = math.floor(value) + end + elseif name == "kind" then + if AUTHOR.looks[value] ~= nil then + entity.look.kind = value + fillDefaults(entity.look, AUTHOR.looks[value].params) + else + FORGE.message = "no look called " .. tostring(value) + end + elseif name == "behaviours" then + -- A comma separated list of kinds. One already carried keeps its values; a new one + -- starts with defaults; one left out goes. + local list = {} + + for want in string.gmatch(tostring(value), "[^,%s]+") do + local b = behaviourOf(entity, want) or { kind = want } + + if AUTHOR.behaviours[want] ~= nil then + fillDefaults(b, AUTHOR.behaviours[want].params) + list[#list + 1] = b + else + FORGE.message = "no behaviour called " .. want + end + end + entity.behaviours = list + elseif kind ~= nil then + local b = behaviourOf(entity, kind) + + if b ~= nil then + b[key] = value + end + else + entity.look[name] = value + end + FORGE.dirty = true + forgeRefresh() +end + + -- ===== The event sheet ======================================================================== -- -- Rules are where someone with little programming skill actually spends their time, so this is the @@ -454,7 +906,7 @@ end -- The vocabulary comes from AUTHOR, so a rule editor never needs changing when a condition or an -- action is added: it lists whatever the manifest declares. -local DEFAULTS = { +FORGE_DEFAULTS = { number = 0, string = "", boolean = true, @@ -470,7 +922,6 @@ local DEFAULTS = { -- Every condition and action of a rule, flattened, so one index walks the whole thing. function forgeParts(rule) local parts = {} - local item for _, item in ipairs(rule.when or {}) do parts[#parts + 1] = { kind = "when", item = item } @@ -488,17 +939,15 @@ end function forgeMakePart(set, name) local entry = set[name] local item = { name } - local key - local kind if entry == nil then return nil end for key, kind in pairs(entry.params or {}) do if kind == "entity" and #FORGE.game.entities > 0 then - item[key] = FORGE.game.entities[1].id + item[key] = FORGE.game.entities[FORGE.selected or 1].id else - item[key] = DEFAULTS[kind] + item[key] = FORGE_DEFAULTS[kind] end end @@ -516,11 +965,12 @@ function forgeRuleAdd(kindName, name) return false end local parts - local at + forgeRemember() rule[kindName] = rule[kindName] or {} rule[kindName][#rule[kindName] + 1] = item FORGE.dirty = true + FORGE.lastField = nil FORGE.message = "added " .. name -- Select what was just added, so its parameters can be set straight away. Conditions come @@ -543,11 +993,11 @@ function forgePartDelete() local parts = forgeParts(rule) local part = parts[FORGE.part] local list - local i if part == nil then return false end + forgeRemember() list = rule[part.kind] for i = 1, #list do if list[i] == part.item then @@ -583,9 +1033,11 @@ end function forgeRuleNew(note) + forgeRemember() FORGE.game.rules[#FORGE.game.rules + 1] = { note = note or "new rule", when = {}, act = {} } FORGE.rule = #FORGE.game.rules FORGE.part = 0 + FORGE.lastField = nil FORGE.dirty = true FORGE.message = "rule added" forgeRefresh() @@ -598,6 +1050,7 @@ function forgeRuleDelete() if FORGE.game.rules[FORGE.rule] == nil then return false end + forgeRemember() table.remove(FORGE.game.rules, FORGE.rule) FORGE.rule = math.max(1, math.min(FORGE.rule, #FORGE.game.rules)) FORGE.part = 0 @@ -609,20 +1062,32 @@ function forgeRuleDelete() end +-- Moves the selected rule up or down the sheet. Rules run in order, and "the readout, every +-- frame" wants to be last, so the order is part of the game. +function forgeRuleMove(by) + local rules = FORGE.game.rules + local to = FORGE.rule + by + + if (rules[FORGE.rule] == nil) or (rules[to] == nil) then + return false + end + forgeRemember() + rules[FORGE.rule], rules[to] = rules[to], rules[FORGE.rule] + FORGE.rule = to + FORGE.dirty = true + FORGE.message = "rule moved" + forgeRefresh() + + return true +end + + -- One condition or action as a line: its name, then its parameters in a fixed order so the same -- rule always reads the same way. function forgePartText(item) - local keys = {} - local out = {} - local key + local out = {} - for key in pairs(item) do - if key ~= 1 then - keys[#keys + 1] = key - end - end - table.sort(keys, function(a, b) return tostring(a) < tostring(b) end) - for _, key in ipairs(keys) do + for _, key in ipairs(forgeFieldNames(item)) do out[#out + 1] = key .. " " .. tostring(item[key]) end @@ -631,27 +1096,89 @@ end function forgeMode(mode) - FORGE.mode = mode + FORGE.mode = mode + FORGE.lastField = nil + FORGE.picker = nil forgeRefresh() end --- Keys. Point the engine's callback at this and the editor is usable without a pointer at all, --- which is how the bundled menu has always worked and what a cabinet needs. +-- ===== Pickers ================================================================================ +-- +-- Choosing a condition, an action, a look or a behaviour is choosing from the manifest, and the +-- manifest carries a line of help for each. The list takes the panel over while it is up. + +-- Opens a list. set is a manifest table (AUTHOR.conditions and its kind); choose is called with +-- the name picked. +function forgePickBegin(title, set, choose) + local items = {} + + for _, name in ipairs(sortedKeys(set)) do + items[#items + 1] = { name = name, help = set[name].help } + end + if #items == 0 then + FORGE.message = "nothing to choose from" + forgeRefresh() + return false + end + FORGE.picker = { title = title, items = items, at = 1, choose = choose } + forgeRefresh() + + return true +end + + +-- A key while a list is up. Answers whether it was taken. +function forgePickKey(scancode) + local picker = FORGE.picker + + if picker == nil then + return false + end + if scancode == SCANCODE.DOWN.value then + picker.at = math.min(picker.at + 1, #picker.items) + elseif scancode == SCANCODE.UP.value then + picker.at = math.max(picker.at - 1, 1) + elseif scancode == SCANCODE.RETURN.value then + FORGE.picker = nil + picker.choose(picker.items[picker.at].name) + elseif scancode == SCANCODE.ESCAPE.value then + FORGE.picker = nil + FORGE.message = "left it" + else + return true + end + forgeRefresh() + + return true +end + + +-- ===== Keys =================================================================================== +-- +-- Point the engine's callback at this and the editor is usable without a pointer at all, which is +-- how the bundled menu has always worked and what a cabinet needs. -- -- TAB entities or rules -- UP / DOWN move within the list -- LEFT/RIGHT move the panel out of the way --- ENTER type a value for the selected condition or action; again for its next value --- ESC put the value back --- N new rule DELETE delete the selected rule or part --- S save B build +-- ENTER type a value for what is selected; again for its next value +-- ESC put the value back; otherwise close the description +-- A add an entity D duplicate it +-- N new rule C / T add a condition / an action from the list +-- [ / ] move the rule up / down the sheet +-- DELETE delete the selected entity, rule, condition or action +-- U / R undo / redo S save +-- B build P play (Forge as a game only) function forgeKey(keysym, scancode) - local rules = FORGE.game.rules or {} + local rules = FORGE.game.rules -- SCANCODE entries are tables of { name, value } and the engine hands onKeyPressed two plain -- integers, so the comparison is against .value. Comparing against the table itself is always -- false with a real keyboard; it only appeared to work because a test passed the table. + if forgePickKey(scancode) then + return + end if FORGE.editing ~= nil then if scancode == SCANCODE.RETURN.value then forgeEditNext() @@ -668,6 +1195,9 @@ function forgeKey(keysym, scancode) forgeEditNext() return end + if scancode ~= SCANCODE.ESCAPE.value then + FORGE.closing = false + end if scancode == SCANCODE.TAB.value then forgeMode((FORGE.mode == "rules") and "entities" or "rules") @@ -682,6 +1212,12 @@ function forgeKey(keysym, scancode) forgeSelect(math.min((FORGE.selected or 0) + 1, #FORGE.game.entities)) elseif scancode == SCANCODE.UP.value then forgeSelect(math.max((FORGE.selected or 2) - 1, 1)) + elseif scancode == SCANCODE.A.value then + forgeEntityAdd() + elseif scancode == SCANCODE.D.value then + forgeEntityDuplicate() + elseif scancode == SCANCODE.DELETE.value then + forgeEntityDelete() end else @@ -709,6 +1245,18 @@ function forgeKey(keysym, scancode) forgeRefresh() elseif scancode == SCANCODE.N.value then forgeRuleNew() + elseif scancode == SCANCODE.C.value then + if rules[FORGE.rule] ~= nil then + forgePickBegin("a condition", AUTHOR.conditions, function(name) forgeRuleAdd("when", name) end) + end + elseif scancode == SCANCODE.T.value then + if rules[FORGE.rule] ~= nil then + forgePickBegin("an action", AUTHOR.actions, function(name) forgeRuleAdd("act", name) end) + end + elseif scancode == SCANCODE.LEFTBRACKET.value then + forgeRuleMove(-1) + elseif scancode == SCANCODE.RIGHTBRACKET.value then + forgeRuleMove(1) elseif scancode == SCANCODE.DELETE.value then if FORGE.part > 0 then forgePartDelete() @@ -722,6 +1270,10 @@ function forgeKey(keysym, scancode) forgeSave() elseif scancode == SCANCODE.B.value then forgeBuild(singeGetDataPath() .. "preview.singe") + elseif scancode == SCANCODE.U.value then + forgeUndo() + elseif scancode == SCANCODE.R.value then + forgeRedo() end end @@ -732,16 +1284,14 @@ end -- useless, which is the half of the job that matters for someone with little programming skill. -- -- Keysyms arrive as characters the way Tools.singe takes them, so this needs nothing of the GUI: --- ENTER starts editing the selected condition or action, each press moves to its next parameter, --- and ESCAPE puts back what was there. +-- ENTER starts editing whatever is selected, each press moves to its next field, and ESCAPE puts +-- back what was there. An entity and a condition are typed the same way; forgeFields says what +-- the fields are and how to reach them. -FORGE.editing = nil -- { key = name, text = typed so far, was = the value it started as } - - --- The parameters of a part, in the order the panel shows them, so typing walks them the same way. +-- The parameters of a condition or action, in the order the panel shows them, so typing walks them +-- the same way. function forgeFieldNames(item) local keys = {} - local key for key in pairs(item) do if key ~= 1 then @@ -754,6 +1304,51 @@ function forgeFieldNames(item) end +-- What ENTER edits: the fields of whatever is selected, with a way to read and write each. nil +-- when nothing is. +function forgeFields() + if FORGE.mode == "rules" then + local rule = FORGE.game.rules[FORGE.rule] + local part + + if rule == nil then + return nil + end + if FORGE.part == 0 then + return { + names = { "note" }, + get = function(key) return rule[key] end, + set = function(key, value) + rule[key] = value + FORGE.dirty = true + forgeRefresh() + end + } + end + part = forgeParts(rule)[FORGE.part] + if part == nil then + return nil + end + return { + names = forgeFieldNames(part.item), + get = function(key) return part.item[key] end, + set = forgePartSet + } + end + local entity = FORGE.game.entities[FORGE.selected] + + if entity == nil then + return nil + end + + return { + names = forgeEntityFieldNames(entity), + get = function(key) return forgeEntityGet(entity, key) end, + set = function(key, value) forgeEntitySet(entity, key, value) end + } +end + + -- A typed value as the description should hold it. A number has to come back a number: "220" and -- 220 compile to different source, and a description that changed shape because a value was -- retyped would stop round-tripping. @@ -781,7 +1376,7 @@ function forgeEditCommit() if FORGE.editing == nil then return false end - forgePartSet(FORGE.editing.key, typedValue(FORGE.editing.text, FORGE.editing.was)) + FORGE.editing.set(FORGE.editing.key, typedValue(FORGE.editing.text, FORGE.editing.was)) FORGE.editing = nil return true @@ -792,46 +1387,45 @@ function forgeEditCancel() if FORGE.editing == nil then return false end - forgePartSet(FORGE.editing.key, FORGE.editing.was) - FORGE.editing = nil - FORGE.message = "unchanged" + FORGE.editing.set(FORGE.editing.key, FORGE.editing.was) + FORGE.editing = nil + FORGE.lastField = nil -- The next ENTER starts the form over rather than moving on. + FORGE.message = "unchanged" forgeRefresh() return true end --- Starts editing, or moves on to the next parameter of the same part. Committing as it moves is +-- Starts editing, or moves on to the next field of the same thing. Committing as it moves is -- what makes ENTER, ENTER, ENTER feel like filling in a form. function forgeEditNext() - local rule = FORGE.game.rules[FORGE.rule] - local parts = forgeParts(rule or {}) - local part = parts[FORGE.part] - local names - local at = 0 - local i + local fields = forgeFields() + local at = 0 - if part == nil then + if fields == nil then return false end - names = forgeFieldNames(part.item) - if #names == 0 then - FORGE.message = part.item[1] .. " takes no values" + if #fields.names == 0 then + FORGE.message = "nothing to type here" forgeRefresh() return false end if FORGE.editing ~= nil then forgeEditCommit() + else + -- One undo step for the whole form, taken when typing starts. + forgeRemember() end - for i = 1, #names do - if names[i] == FORGE.lastField then + for i = 1, #fields.names do + if fields.names[i] == FORGE.lastField then at = i end end - at = (at % #names) + 1 - FORGE.lastField = names[at] - FORGE.editing = { key = names[at], text = "", was = part.item[names[at]] } - FORGE.message = "type a value for " .. names[at] .. ", ENTER for the next, ESC to put it back" + at = (at % #fields.names) + 1 + FORGE.lastField = fields.names[at] + FORGE.editing = { key = fields.names[at], text = "", was = fields.get(fields.names[at]), set = fields.set } + FORGE.message = "type a value for " .. fields.names[at] .. ", ENTER for the next, ESC to put it back" forgeRefresh() return true @@ -851,9 +1445,324 @@ function forgeTyped(keysym) return false end -- Shown as it is typed, so the value in the list is the value being entered. - forgePartSet(FORGE.editing.key, (FORGE.editing.text == "") and FORGE.editing.was or typedValue(FORGE.editing.text, FORGE.editing.was)) + FORGE.editing.set(FORGE.editing.key, (FORGE.editing.text == "") and FORGE.editing.was or typedValue(FORGE.editing.text, FORGE.editing.was)) FORGE.message = FORGE.editing.key .. " = " .. FORGE.editing.text .. "_" forgeRefresh() return true end + + +-- ===== Files ================================================================================== +-- +-- Descriptions live in Forge's data directory, which is the one place a packed Forge can write. +-- Ones dropped into Forge's own directory are offered too, and opened as a copy in the data +-- directory, since inside a .game they are read only. + +-- A game to start from: ground, a hero that runs and jumps, and a readout. Enough to press play +-- on, which is the point; everything in it can be changed or deleted. +function forgeTemplate() + return { + title = "New game", + layers = { { kind = "world2d", gravity = 1500 } }, + entities = { + { id = "ground", x = 360, y = 440, look = { kind = "box", w = 720, h = 40, r = 60, g = 70, b = 90 }, + behaviours = { { kind = "solid" } } }, + { id = "hero", x = 120, y = 380, look = { kind = "box", w = 24, h = 44, r = 230, g = 90, b = 170 }, + behaviours = { { kind = "platformer", speed = 210, jump = 620 } } }, + { id = "readout", x = 12, y = 12, look = { kind = "text", text = "score 0", r = 235, g = 235, b = 245 } } + }, + rules = { + { note = "Run left", when = { { "keyHeld", key = "LEFT" } }, act = { { "run", entity = "hero", direction = -1 } } }, + { note = "Run right", when = { { "keyHeld", key = "RIGHT" } }, act = { { "run", entity = "hero", direction = 1 } } }, + { note = "Jump, with ground underfoot", when = { { "keyHeld", key = "SPACE" }, { "onGround", entity = "hero" } }, act = { { "jump", entity = "hero" } } }, + { note = "The readout, every frame", when = {}, act = { { "setText", entity = "readout", text = '"score " .. AUTHOR_SCORE' } } } + } + } +end + + +-- Every .game description in a directory, sorted. A directory that is not there lists nothing. +local function descriptionsIn(directory) + local found = {} + + if lfs.attributes(directory, "mode") ~= "directory" then + return found + end + for name in lfs.dir(directory) do + if string.sub(name, -5) == ".game" then + found[#found + 1] = { name = string.sub(name, 1, -6), path = directory .. name } + end + end + table.sort(found, function(a, b) return a.name < b.name end) + + return found +end + + +-- What the chooser offers: the data directory's descriptions, then Forge's own, then a new game. +function forgeFiles() + local files = descriptionsIn(singeGetDataPath()) + + for _, sample in ipairs(descriptionsIn("Forge/")) do + sample.sample = true + files[#files + 1] = sample + end + files[#files + 1] = { name = "New game", fresh = true } + + return files +end + + +-- A path in the data directory no description has yet. +local function freshPath(stem) + local base = singeGetDataPath() .. stem + local n = 1 + + if lfs.attributes(base .. ".game") == nil then + return base .. ".game" + end + while lfs.attributes(base .. n .. ".game") ~= nil do + n = n + 1 + end + + return base .. n .. ".game" +end + + +-- Opens an entry from forgeFiles. A sample is copied first; a new game is written first. +function forgeOpen(entry) + local path = entry.path + + if entry.fresh then + path = freshPath("NewGame") + authorSave(forgeTemplate(), path) + elseif entry.sample then + path = freshPath(entry.name) + if not authorCopy(entry.path, path) then + return false + end + end + if not forgeBegin(path) then + return false + end + if entry.sample then + FORGE.message = "opened a copy: " .. path + elseif entry.fresh then + FORGE.message = "new game at " .. path + end + forgeRefresh() + + return true +end + + +-- ===== Forge as a game ======================================================================== +-- +-- Everything above is a library; this is what runs when Forge is started from the menu. A scene +-- that drives the editor sets FORGE_LIBRARY and supplies its own callbacks instead. +-- +-- Playing is a scriptPush of the built game. The engine runs this script again from the top when +-- the game ends, so the description being edited is noted in a file first and reopened on the way +-- back, which is what makes P feel like a button rather than a restart. + +if not FORGE_LIBRARY then + + local LAST_FILE = singeGetDataPath() .. "last.txt" -- What was open when play was pressed. + local MANUAL = singeGetDataPath() .. "Forge.pdf" -- The manual, put where a person can open it. + local MANUAL_SHOWN = 64 -- Characters of its path the chooser has room for. + local ROW_H = 22 + local LIST_X = 60 + local LIST_Y = 80 + + local chooser = { files = {}, at = 1 } + local font = fontLoad("Singe/FreeSansBold.ttf", 15) + + fontSelect(font) + fontQuality(FONT_QUALITY_BLENDED) + overlaySetResolution(720, 480) + + + local function chooserScan() + chooser.files = forgeFiles() + chooser.at = math.max(1, math.min(chooser.at, #chooser.files)) + end + + + -- The manual travels inside Forge.game, where nothing but Singe can read it. The first run + -- copies it out beside the descriptions, and the chooser says where. + local function extractManual() + if (lfs.attributes(MANUAL, "mode") ~= "file") and (lfs.attributes("Forge/Forge.pdf", "mode") == "file") then + authorCopy("Forge/Forge.pdf", MANUAL) + end + + return (lfs.attributes(MANUAL, "mode") == "file") and MANUAL or nil + end + + + local function chooserDraw() + local y = LIST_Y + + colorBackground(18, 18, 26, 255) + overlayClear() + colorForeground(255, 207, 74, 255) + fontPrint(LIST_X, 30, "Forge") + colorForeground(154, 160, 180, 255) + fontPrint(LIST_X + 80, 30, "UP / DOWN choose ENTER open ESC leave") + if chooser.manual ~= nil then + local shown = chooser.manual + + -- A data directory given on the command line can be anywhere; the end of the path is + -- the part that says where the file is. + if #shown > MANUAL_SHOWN then + shown = "..." .. string.sub(shown, -MANUAL_SHOWN) + end + fontPrint(LIST_X, overlayGetHeight() - 40, "Manual: " .. shown) + end + for at, entry in ipairs(chooser.files) do + local label = entry.name + + if entry.sample then + label = label .. " (a copy will be made)" + end + if at == chooser.at then + colorForeground(192, 60, 150, 255) + for row = y - 3, y + ROW_H - 6 do + overlayLine(LIST_X - 10, row, LIST_X + 400, row) + end + colorForeground(255, 255, 255, 255) + else + colorForeground(230, 230, 238, 255) + end + fontPrint(LIST_X, y, label) + y = y + ROW_H + end + end + + + -- Saves, builds, and hands the game to the engine. Back here afterwards, on the same file. + function forgePlay() + local built = forgeBuild(singeGetDataPath() .. "preview.singe") + local note = io.open(LAST_FILE, "w") + + forgeSave() + if note ~= nil then + note:write(FORGE.path) + note:close() + end + scriptPush({ SCRIPT = built, VIDEO = forgeVideo() }) + end + + + -- Where play left off, if it did. The note is read once and removed, so a fresh start shows + -- the chooser. + local function reopen() + local note = io.open(LAST_FILE, "r") + local path + + if note == nil then + return false + end + path = note:read("l") + note:close() + os.remove(LAST_FILE) + if (path == nil) or not forgeBegin(path) then + return false + end + FORGE.message = "back from playing" + forgeRefresh() + + return true + end + + + local function chooserKey(scancode) + if scancode == SCANCODE.DOWN.value then + chooser.at = math.min(chooser.at + 1, #chooser.files) + elseif scancode == SCANCODE.UP.value then + chooser.at = math.max(chooser.at - 1, 1) + elseif scancode == SCANCODE.RETURN.value then + forgeOpen(chooser.files[chooser.at]) + elseif scancode == SCANCODE.ESCAPE.value then + singeQuit() + end + end + + + -- ESC in the editor closes the description; twice, when it has unsaved changes. + local function editorEscape() + if FORGE.dirty and not FORGE.closing then + FORGE.closing = true + FORGE.message = "unsaved: S saves, ESC again leaves without saving" + forgeRefresh() + return + end + forgeClose() + chooserScan() + end + + + function onOverlayUpdate() + if FORGE.game == nil then + chooserDraw() + else + local x, y = mouseGetPosition(0) + + forgeDraw(x, y) + end + + return OVERLAY_UPDATED + end + + + function onKeyPressed(keysym, scancode) + if FORGE.game == nil then + chooserKey(scancode) + elseif (scancode == SCANCODE.ESCAPE.value) and (FORGE.editing == nil) and (FORGE.picker == nil) then + editorEscape() + elseif (scancode == SCANCODE.P.value) and (FORGE.editing == nil) and (FORGE.picker == nil) then + forgePlay() + else + forgeKey(keysym, scancode) + end + end + + + -- The pad walks the chooser; in the editor the left mouse button is the pointer's press. + function onInputPressed(what) + if FORGE.game == nil then + if what == SWITCH_DOWN then + chooserKey(SCANCODE.DOWN.value) + elseif what == SWITCH_UP then + chooserKey(SCANCODE.UP.value) + elseif (what == SWITCH_START1) or (what == SWITCH_BUTTON1) then + chooserKey(SCANCODE.RETURN.value) + end + elseif what == SWITCH_BUTTON3 then + local x, y = mouseGetPosition(0) + + forgePress(x, y) + end + end + + + function onInputReleased(what) + if (FORGE.game ~= nil) and (what == SWITCH_BUTTON3) then + forgeRelease() + end + end + + + function onMouseMoved(x, y) + if FORGE.game ~= nil then + forgeDrag(x, y) + end + end + + + chooser.manual = extractManual() + if not reopen() then + chooserScan() + end + +end diff --git a/assets/Forge/games.dat b/assets/Forge/games.dat index eb3c12136..0a701a794 100644 --- a/assets/Forge/games.dat +++ b/assets/Forge/games.dat @@ -7,7 +7,6 @@ GAMES = { SCRIPT = "Forge/Forge.singe", LEGACY_SPRITE_ARGS = false, VIDEO = "Singe/menuBackground.mkv", - DATA = "Forge", STRETCH = false, NO_MOUSE = false, RESOLUTION_X = 720, diff --git a/assets/Menu.singe b/assets/Menu.singe index e7184a8f1..11b2c8c4a 100644 --- a/assets/Menu.singe +++ b/assets/Menu.singe @@ -81,12 +81,18 @@ end function loadGameAssets() local game = GAME_LIST[GAME_SELECTED] - VIDEO_ATTRACT = videoLoad(game.ATTRACT) - if game.AUDIO_TRACK then - videoSetAudioTrack(VIDEO_ATTRACT, game.AUDIO_TRACK) + -- An entry without an attract clip, or naming one that is not there, shows its art and no + -- video rather than taking the whole menu down with a videoLoad that cannot open it. + if menuFileExists(game.ATTRACT) then + VIDEO_ATTRACT = videoLoad(game.ATTRACT) + if game.AUDIO_TRACK then + videoSetAudioTrack(VIDEO_ATTRACT, game.AUDIO_TRACK) + end + videoPlay(VIDEO_ATTRACT) + videoSeek(VIDEO_ATTRACT, game.ATTRACT_START or 0) + else + debugPrint("Menu: " .. tostring(game.TITLE) .. " has no attract clip to play.") end - videoPlay(VIDEO_ATTRACT) - videoSeek(VIDEO_ATTRACT, game.ATTRACT_START) MENU_RENDER.showGame(game) end @@ -96,18 +102,26 @@ end MENU_MISSING_ART = "Singe/missing.png" --- A readable art path: the game's own when it can be opened, the missing-art picture when not. -function menuArtPath(file) +-- Whether a name is a file the engine can open. Asked of the index rather than of the file: +-- io.open, which this used to use, has to hand Lua a real file it can read with the C library, so +-- every picture it was asked about was unpacked out of the .game into data//cache first -- +-- and the menu asks about two of them for every game in the library, none of which it then opens +-- that way: the sprite loader and the document's texture loader both read straight out of the +-- database. lfs.attributes goes through the same virtual filesystem and answers from the index, +-- opening nothing. +function menuFileExists(file) local path = tostring(file or "") local found = (path ~= "") and lfs.attributes(path) or nil - -- Asked of the index rather than of the file. io.open, which this used to use, has to hand - -- Lua a real file it can read with the C library, so every picture it was asked about was - -- unpacked out of the .game into data//cache first -- and the menu asks about two of - -- them for every game in the library, none of which it then opens that way: the sprite loader - -- and the document's texture loader both read straight out of the database. lfs.attributes - -- goes through the same virtual filesystem and answers from the index, opening nothing. - if (found == nil) or (found.mode == "directory") then + return (found ~= nil) and (found.mode ~= "directory") +end + + +-- A readable art path: the game's own when it can be opened, the missing-art picture when not. +function menuArtPath(file) + local path = tostring(file or "") + + if not menuFileExists(path) then if path ~= "" then debugPrint("Menu: cannot read " .. path .. "; showing the missing-art picture.") end @@ -209,8 +223,10 @@ function onOverlayUpdate() end -- The attract clip runs between the two frames the game's entry names. - if videoGetFrame(VIDEO_ATTRACT) > GAME_LIST[GAME_SELECTED].ATTRACT_END then - videoSeek(VIDEO_ATTRACT, GAME_LIST[GAME_SELECTED].ATTRACT_START) + if VIDEO_ATTRACT and GAME_LIST[GAME_SELECTED].ATTRACT_END then + if videoGetFrame(VIDEO_ATTRACT) > GAME_LIST[GAME_SELECTED].ATTRACT_END then + videoSeek(VIDEO_ATTRACT, GAME_LIST[GAME_SELECTED].ATTRACT_START or 0) + end end MENU_RENDER.frame() end @@ -221,6 +237,10 @@ end function onShutdown() + -- With no games found there is no renderer and nothing was loaded; quitting is all there is. + if MENU_RENDER == nil then + return + end unloadGameAssets() saveConfig(not SHUTDOWN_FROM_PUSH) MENU_RENDER.finish() @@ -306,7 +326,7 @@ for dir in lfs.dir(".") do local dirattr = lfs.attributes(dir) if dir:sub(-5):lower() == ".game" then loadGamesDat(dir .. "/games.dat", dir) - elseif dirattr.mode == "directory" then + elseif dirattr and (dirattr.mode == "directory") then for file in lfs.dir(dir .. "/.") do if file == "games.dat" then loadGamesDat(dir .. "/games.dat", nil) diff --git a/assets/MenuDocument.singe b/assets/MenuDocument.singe index ba4379d32..0b5fc1508 100644 --- a/assets/MenuDocument.singe +++ b/assets/MenuDocument.singe @@ -69,8 +69,6 @@ local detailClock = 0 local SCROLL_HOLD = 3.0 -- Seconds it rests at each end before moving, as in the overlay local SCROLL_STEP = 1.1 -- renderer, and seconds a step while it is moving. local SCROLL_LINE = 16 -- How far a step goes: a line of the description. -local backdropRoot = nil -local backdropGrid = nil function menuElement(id) @@ -358,7 +356,9 @@ end MENU_RENDER.frame = function() -- Attract Mode Video, under the hole the document leaves for it - videoDraw(VIDEO_ATTRACT, videoX, videoY, videoX + videoW, videoY + videoH) + if VIDEO_ATTRACT then + videoDraw(VIDEO_ATTRACT, videoX, videoY, videoX + videoW, videoY + videoH) + end if scrollPending then menuScrollToSelection() end diff --git a/assets/MenuOverlay.singe b/assets/MenuOverlay.singe index ff0d1288e..f31782ccb 100644 --- a/assets/MenuOverlay.singe +++ b/assets/MenuOverlay.singe @@ -430,7 +430,9 @@ MENU_RENDER.frame = function() spriteDraw(spriteMarquee, x, y) -- Attract mode video - videoDraw(VIDEO_ATTRACT, videoX, videoY, videoX + videoW, videoY + videoH) + if VIDEO_ATTRACT then + videoDraw(VIDEO_ATTRACT, videoX, videoY, videoX + videoW, videoY + videoH) + end -- Which game this is, since there is no list to look at. It heads the details rather than -- sitting over the cabinet artwork, where it was unreadable against whatever the picture was. diff --git a/assets/Net.singe b/assets/Net.singe index a6aca2fb0..39f835144 100644 --- a/assets/Net.singe +++ b/assets/Net.singe @@ -39,6 +39,11 @@ -- -- The pin is of the *public key*, not the certificate, so an ordinary renewal that keeps the key -- does not lock out every cabinet; two pins are carried so a key can actually be rotated. +-- +-- Without a pin the chain is verified against a CA list. Linux has one; Windows, macOS and the +-- handhelds have nothing LuaSec can read, so the engine extracts Mozilla's list as +-- Singe/cacerts.pem beside the other support files and that is used wherever the platform's own +-- is missing. local socket = require("socket") @@ -53,6 +58,9 @@ NET_REDIRECT_MAX = 5 -- host's own trust store is used instead, which is what a self-hosted server without a pin gets. NET_PINS = {} +NET_CA_SYSTEM = "/etc/ssl/certs/ca-certificates.crt" -- Linux's list, when it is there, +NET_CA_SHIPPED = "Singe/cacerts.pem" -- and the one Singe carries otherwise. + local active = {} local nextId = 1 @@ -149,17 +157,28 @@ local function buildRequest(request) end +-- The CA list a connection without a pin is verified against. +local function caFile() + local lfs = require("lfs") + + if lfs.attributes(NET_CA_SYSTEM, "mode") == "file" then + return NET_CA_SYSTEM + end + + return NET_CA_SHIPPED +end + + local function startTls(request) local params = { mode = "client", protocol = "any", options = { "all", "no_sslv2", "no_sslv3", "no_tlsv1", "no_tlsv1_1" }, -- With a pin, the chain is checked against the key rather than a CA list, and the pin is - -- what decides. Without one, the host's own store is asked -- and if it has nothing to - -- say, the request fails rather than proceeding unverified. + -- what decides. Without one, a CA list is asked -- and if it has nothing to say, the + -- request fails rather than proceeding unverified. verify = (#NET_PINS > 0) and "none" or "peer", - capath = "/etc/ssl/certs", - cafile = "/etc/ssl/certs/ca-certificates.crt", + cafile = caFile(), } local wrapped, err = ssl.wrap(request.socket, params) @@ -238,6 +257,12 @@ local function splitHead(request) end request.length = tonumber(request.responseHeaders["content-length"]) request.got = 0 + -- HTTP/1.1 may send the body in sized pieces instead of announcing a length; those are + -- unwrapped as they arrive and the last, empty, piece is the end of the body. + request.chunked = (request.responseHeaders["transfer-encoding"] or ""):lower():find("chunked", 1, true) ~= nil + request.chunkBuffer = "" + request.chunkLeft = nil + request.chunkDone = false if request.toFile then local file, err = io.open(request.toFile, "wb") if not file then @@ -263,6 +288,46 @@ local function consume(request, data) end +-- Body bytes arriving under chunked transfer encoding: a hexadecimal size on a line, that many +-- bytes, a blank line, again until a size of zero. Whatever is left over waits for the next read. +local function consumeChunked(request, data) + request.chunkBuffer = request.chunkBuffer .. data + while not request.chunkDone do + if request.chunkLeft == nil then + local at = request.chunkBuffer:find("\r\n", 1, true) + if not at then + return + end + local size = tonumber(request.chunkBuffer:sub(1, at - 1):match("^%x+"), 16) + request.chunkBuffer = request.chunkBuffer:sub(at + 2) + if size == nil then + return fail(request, "the server sent a chunk with no size") + end + if size == 0 then + request.chunkDone = true + return + end + request.chunkLeft = size + elseif request.chunkLeft > 0 then + local take = request.chunkBuffer:sub(1, request.chunkLeft) + if #take == 0 then + return + end + request.chunkBuffer = request.chunkBuffer:sub(#take + 1) + request.chunkLeft = request.chunkLeft - #take + consume(request, take) + else + -- The blank line after a chunk's bytes. + if #request.chunkBuffer < 2 then + return + end + request.chunkBuffer = request.chunkBuffer:sub(3) + request.chunkLeft = nil + end + end +end + + local function complete(request) local body = request.file and "" or table.concat(request.parts) @@ -277,6 +342,7 @@ local function complete(request) local _, _, port, path = parseUrl(location) request.port, request.path = port, path request.buffer, request.parts, request.outgoing, request.sent = "", {}, nil, 0 + request.chunked, request.chunkBuffer, request.chunkLeft, request.chunkDone = false, "", nil, false if request.socket then pcall(function() request.socket:close() end) end request.socket = nil return beginConnect(request) @@ -298,13 +364,22 @@ local function pumpReading(request) if splitHead(request) and #request.buffer > 0 then local rest = request.buffer request.buffer = "" - consume(request, rest) + if request.chunked then + consumeChunked(request, rest) + else + consume(request, rest) + end end + elseif request.chunked then + consumeChunked(request, got) else consume(request, got) end end - if request.responseHeaders and request.length and request.got >= request.length then + if not active[request.id] then + return -- A bad chunk failed the request from inside consumeChunked. + end + if request.responseHeaders and ((request.length and request.got >= request.length) or request.chunkDone) then return complete(request) end if err == "closed" then diff --git a/assets/Tools.singe b/assets/Tools.singe index dd846b0d7..85734520e 100644 --- a/assets/Tools.singe +++ b/assets/Tools.singe @@ -1214,7 +1214,7 @@ end -- A .game installed here that the catalogue no longer lists. Without this it would vanish from -- the page the moment it was withdrawn, leaving a player with a game they can play, cannot update, -- and cannot remove from anywhere but a file manager. -function shopAddWithdrawn() +local function shopAddWithdrawn() local listed = {} for _, game in ipairs(shopGames) do diff --git a/assets/cacerts.pem b/assets/cacerts.pem new file mode 100644 index 000000000..4cde36ec9 --- /dev/null +++ b/assets/cacerts.pem @@ -0,0 +1,3004 @@ +# Root certificates the master service is verified against on platforms with no trust store +# LuaSec can read (Windows, macOS, the handhelds). Mozilla's CA list, as packaged by Debian +# ca-certificates 20260601, taken 2026-09-13. Distributed under the Mozilla Public License 2.0. +# Refresh from /etc/ssl/certs/ca-certificates.crt on a current Linux; see Net.singe. +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx +CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ +WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ +BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG +Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ +yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf +BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz +WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF +tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z +374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC +IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL +mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 +wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS +MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 +ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet +UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H +YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 +LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 +RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM +LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf +77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N +JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm +fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp +6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp +1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B +9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok +RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv +uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw +CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw +FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S +Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5 +MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL +DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS +QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH +sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK +Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu +SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC +MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy +v+c= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV +BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk +YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV +BAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN +MzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF +UzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD +VQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v +dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj +cqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q +yGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH +2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX +H1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL +zc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR +p1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz +W7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/ +SiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn +LNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3 +n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B +u8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj +o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L +9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej +rw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK +pFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0 +vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq +OknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ +/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9 +2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI ++PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2 +MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo +tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w +LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w +CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0 +MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBF +Q0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMHYwEAYHKoZI +zj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6KDP/X +tXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4 +AjJn8ZQSb+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2 +KCXWfeBmmnoJsmo7jjPXNtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMD +aAAwZQIwW5kp85wxtolrbNa9d+F851F+uDrNozZffPc8dz7kUK2o59JZDCaOMDtu +CCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo +9H1/IISpQuQo +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM +MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx +MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00 +MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBD +QSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BBl01Z +4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYv +Ye+W/CBGvevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZ +kmGbzSoXfduP9LVq6hdKZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDs +GY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt0xU6kGpn8bRrZtkh68rZYnxGEFzedUln +nkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVKPNe0OwANwI8f4UDErmwh +3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMYsluMWuPD +0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzy +geBYBr3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8 +ANSbhqRAvNncTFd+rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezB +c6eUWsuSZIKmAMFwoW4sKeFYV+xafJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lI +pw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +dEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +DAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPs +o0UvFJ/1TCplQ3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJ +qM7F78PRreBrAwA0JrRUITWXAdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuyw +xfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9GslA9hGCZcbUztVdF5kJHdWoOsAgM +rr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2VktafcxBPTy+av5EzH4 +AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9qTFsR +0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuY +o7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5 +dDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE +oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE +BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h +cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1 +MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg +Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 +thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM +cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG +L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i +NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h +X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b +m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy +Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja +EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T +KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF +6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh +OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc +tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd +IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC +AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw +ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m +iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF +Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ +hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P +Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE +EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV +1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t +CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR +5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw +f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9 +ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK +GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBU +MQswCQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRI +T1JJVFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAz +MTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJF +SUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2Jh +bCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFmCL3Z +xRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZ +spDyRhySsTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O5 +58dnJCNPYwpj9mZ9S1WnP3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgR +at7GGPZHOiJBhyL8xIkoVNiMpTAK+BcWyqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll +5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRjeulumijWML3mG90Vr4Tq +nMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNnMoH1V6XK +V0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/ +pj+bOT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZO +z2nxbkRs1CTqjSShGL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXn +jSXWgXSHRtQpdaJCbPdzied9v3pKH9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+ +WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMBAAGjQjBAMB0GA1UdDgQWBBTF +7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4 +YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3Kli +awLwQ8hOnThJdMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u ++2D2/VnGKhs/I0qUJDAnyIm860Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88 +X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuhTaRjAv04l5U/BXCga99igUOLtFkN +SoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW4AB+dAb/OMRyHdOo +P2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmpGQrI ++pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRz +znfSxqxx4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9 +eVzYH6Eze9mCUAyTF6ps3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2 +YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4SSPfSKcOYKMryMguTjClPPGAyzQWWYezy +r/6zcCwupvI= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQsw +CQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJ +VFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgy +MVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJ +TkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2JhbCBS +b290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jlSR9B +IgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK+ ++kpRuDCK/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJK +sVF/BvDRgh9Obl+rg/xI1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA +94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8gUXOQwKhbYdDFUDn9hf7B +43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr +6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV +L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 +1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx +MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ +QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB +arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr +Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi +FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS +P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN +9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz +uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h +9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t +OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo ++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 +KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 +DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us +H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ +I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 +5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h +3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz +Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y +ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E +N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 +tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX +0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c +/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X +KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY +zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS +O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D +34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP +K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv +Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj +QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS +IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 +HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa +O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv +033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u +dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE +kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 +3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD +u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq +4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy +MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe +NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH +PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I +x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe +QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR +yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO +QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 +H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ +QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD +i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs +nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 +rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI +hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf +GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb +lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka ++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal +TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i +nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 +gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr +G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os +zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x +L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD +TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y +aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx +MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j +aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP +T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 +sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL +TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 +/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp +7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz +EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt +hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP +a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot +aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg +TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV +PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv +cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL +tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT +ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL +jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS +ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy +P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 +xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d +Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN +5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe +/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z +AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ +5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw +MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR +FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J +cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW +BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm +fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv +GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw +CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu +bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ +BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s +eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK ++IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2 +QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4 +hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm +ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG +BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw +PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy +dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9 +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0 +YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2 +1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT +vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed +aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0 +1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5 +r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5 +cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ +wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ +6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA +2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH +Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR +eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB +/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u +d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr +PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi +1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd +rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di +taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7 +lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj +yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn +Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy +yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n +wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6 +OV+KmalBWQewLK8= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw +WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw +MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x +MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD +VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX +BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO +ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M +CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu +I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm +TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh +C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf +ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz +IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT +Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k +JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5 +hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB +GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov +L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo +dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr +aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq +hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L +6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG +HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6 +0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB +lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi +o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1 +gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v +faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 +Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh +jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw +3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw +CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw +JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT +EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0 +WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT +LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX +BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE +KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm +Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8 +EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J +UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn +nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM +MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D +ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU +cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 +WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg +Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw +IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH +UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM +TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU +BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM +kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x +AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y +sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL +I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 +J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY +VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6 +MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu +MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV +BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw +MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg +U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ +n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q +p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq +NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF +8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3 +HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa +mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi +7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF +ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P +qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ +v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6 +Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD +ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4 +WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo +zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR +5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ +GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf +5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq +0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D +P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM +qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP +0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf +E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5 +NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS +zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0 +QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/ +VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW +wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV +dWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5 +NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC +/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD +wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3 +OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA +y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb +gfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha +ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM +HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 +UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 +tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R +ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM +lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp +/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G +A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy +MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl +cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js +L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL +BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni +acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K +zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 +PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y +Johw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp +Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2 +MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ +bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS +7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp +0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS +B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49 +BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ +LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4 +DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT +HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN +NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs +IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+ +ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0 +2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp +wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM +pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD +nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po +sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx +Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd +Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX +KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe +XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL +tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv +TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN +AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H +PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF +O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ +REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik +AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv +/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+ +p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw +MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF +qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK +ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 +Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW +KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw +NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw +NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy +ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV +BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo +Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 +4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 +KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI +rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi +94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB +sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi +gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo +kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE +vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t +O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua +AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP +9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ +eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m +0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE +BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0 +MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV +BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w +HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj +Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj +TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u +KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj +qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm +MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12 +ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP +zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk +L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC +jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA +HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC +AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm +DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5 +COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry +L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf +JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg +IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io +2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV +09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ +XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq +T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe +MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo +27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w +Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw +TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl +qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH +szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8 +Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk +MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92 +wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p +aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN +VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb +C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy +h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4 +7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J +ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef +MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/ +Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT +6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ +0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm +2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb +bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G +jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2 +4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7 +VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm +ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi +QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR +HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D +9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8 +p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD +VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw +MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g +UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT +BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx +uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV +HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/ ++wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147 +bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg +MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx +MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET +MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI +xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k +ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD +aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw +LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw +1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX +k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 +SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h +bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n +WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY +rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce +MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu +bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt +Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 +55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj +vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf +cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz +oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp +nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs +pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v +JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R +8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 +5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx +CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD +ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw +MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex +HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq +R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd +yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ +7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8 ++RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA +MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD +VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy +MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt +c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ +OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG +vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud +316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo +0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE +y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF +zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE ++cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN +I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs +x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa +ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC +4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4 +7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti +2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk +pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF +FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt +rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk +ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5 +u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP +4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6 +N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3 +vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw +CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh +cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v +dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG +A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7 +KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y +STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD +AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw +SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN +nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs +MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg +Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL +MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l +mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE +4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv +a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M +pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw +Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b +LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY +AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB +AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq +E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr +W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ +CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU +X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3 +f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja +H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP +JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P +zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt +jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0 +/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT +BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79 +aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW +xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU +63ZTGI0RmLo= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN +BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl +bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv +b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ +BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj +YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 +MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 +dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg +QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa +jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi +C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep +lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof +TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix +DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k +IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT +N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v +dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG +A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh +ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx +QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA +4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 +AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 +4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C +ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV +9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD +gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 +Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq +NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko +LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd +ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I +XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI +M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot +9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V +Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea +j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh +X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ +l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf +bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 +pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK +e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 +vm9qp/UsQu0yrbYhnr68 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa +Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3 +YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw +qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv +Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6 +lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz +Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ +KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK +FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj +HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr +y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ +/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM +a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6 +fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG +SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc +SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza +ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc +XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg +iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho +L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF +Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr +kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+ +vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU +YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL +BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ +SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n +a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5 +NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT +CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u +Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO +dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI +VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV +9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY +2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY +vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt +bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb +x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+ +l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK +TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj +Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw +DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG +7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk +MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr +gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk +GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS +3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm +Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+ +l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c +JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP +L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa +LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG +mpv0 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw +CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg +R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 +MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT +ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW ++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 +ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI +zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW +tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 +/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 +MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 +ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD +VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j +b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq +scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO +xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H +LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX +uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD +yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+ +JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q +rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN +BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L +hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB +QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+ +HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu +Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg +QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB +BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA +A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb +laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56 +awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo +JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw +LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT +VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk +LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb +UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/ +QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ +naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls +QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G +CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y +OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx +FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp +Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP +kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc +cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U +fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 +N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC +xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 ++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM +Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG +SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h +mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk +ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c +2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t +HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD +VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw +MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy +b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR +ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb +hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3 +FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV +L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB +iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N +aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ +Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0 +ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1 +HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm +gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ +jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc +aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG +YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6 +W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K +UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH ++FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q +W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC +LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC +gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6 +tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh +SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2 +TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3 +pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR +xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp +GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 +dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN +AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB +RA+GsCyRxj3qrg+E +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM +BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG +T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx +CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD +b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA +iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH +38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE +HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz +kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP +szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq +vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf +nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG +YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo +0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a +CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K +AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I +36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN +qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj +cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm ++LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL +hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe +lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7 +p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8 +piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR +LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX +5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO +dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul +9XXeifdy +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG +EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 +MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl +cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR +dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB +pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM +b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm +aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz +IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT +lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz +AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 +VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG +ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 +BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG +AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M +U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh +bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C ++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F +uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 +XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw +CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 +bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg +Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ +BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu +ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS +b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni +eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W +p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T +rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV +57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg +Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx +NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv +bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA +VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku +WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX +5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ +ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg +h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV +BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE +CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy +MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G +A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD +DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq +M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf +OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa +4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 +HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR +aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA +b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ +Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV +PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO +pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu +UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY +MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 +9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW +s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 +Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg +cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM +79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz +/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt +ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm +Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK +QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ +w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi +S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 +mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz +WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 +b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS +b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI +7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg +CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD +VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T +kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ +gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE +BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK +DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz +OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R +xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX +qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC +C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 +6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh +/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF +YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E +JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc +US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 +ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm ++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi +M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV +cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc +Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs +PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ +q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 +cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr +a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I +H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y +K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu +nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf +oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY +Ic2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT +U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2 +MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh +dGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3QgQ0EgMjAyMjB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWyJGYm +acCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFN +SeR7T5v15wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME +GDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW +uCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp +15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN +b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO +MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD +DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX +DTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw +b3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJvb3QgQ0EgMjAyMjCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u9nTP +L3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OY +t6/wNr/y7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0ins +S657Lb85/bRi3pZ7QcacoOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3 +PnxEX4MN8/HdIGkWCVDi1FW24IBydm5MR7d1VVm0U3TZlMZBrViKMWYPHqIbKUBO +L9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDGD6C1vBdOSHtRwvzpXGk3 +R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEWTO6Af77w +dr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS ++YCk8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYS +d66UNHsef8JmAOSqg+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoG +AtUjHBPW6dvbxrB6y3snm/vg1UYk7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2f +gTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j +BBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsuN+7jhHonLs0Z +NbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsM +QtfhWsSWTVTNj8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvf +R4iyrT7gJ4eLSYwfqUdYe5byiB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJ +DPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjUo3KUQyxi4U5cMj29TH0ZR6LDSeeW +P4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqoENjwuSfr98t67wVy +lrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7EgkaibMOlq +bLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2w +AgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q +r5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji +Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU +98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw +CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN +MjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYG +A1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccC +WvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+ +6xnOQ6OjQjBAMB0GA1UdDgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8B +Af8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa +qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q +4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD +Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw +HhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY +MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp +YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa +ef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz +SDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf +iOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X +ME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3 +IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS +VYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE +SJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu ++Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt +8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L +HaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt +zwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P +AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ +YKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52 +gDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA +Fv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB +JYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX +DhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui +TdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5 +dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65 +LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp +0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY +QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT +AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD +VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx +NjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT +HFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5 +IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl +dB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK +ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu +9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O +be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX +DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy +dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj +YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV +OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr +zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM +VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ +hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO +ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw +awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs +OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF +coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc +okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 +t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy +1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ +SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs +ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD +VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy +ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy +dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p +OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 +8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K +Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe +hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk +6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q +AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI +bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB +ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z +qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn +0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN +sSi6 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN +8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ +RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 +hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 +ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM +EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 +A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy +WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ +1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 +6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT +91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p +TpPDpFQUWw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx +GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp +bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w +KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 +BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy +dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG +EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll +IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU +QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT +TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg +LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 +a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr +LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr +N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X +YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ +iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f +AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH +V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf +IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 +lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c +8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf +lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx +EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT +VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 +NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT +B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF +10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz +0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh +MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH +zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc +46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 +yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi +laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP +oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA +BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE +qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm +4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL +1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF +H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo +RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ +nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh +15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW +6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW +nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j +wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz +aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy +KwbQBM0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES +MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU +V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz +WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO +LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE +AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH +K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX +RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z +rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx +3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq +hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC +MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls +XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D +lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn +aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ +YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx +CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE +AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1 +NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ +MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq +AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9 +vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9 +lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD +n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT +7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o +6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC +TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6 +WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R +DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI +pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj +YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy +rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi +0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM +A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS +SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K +TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF +6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er +3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt +Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT +VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW +ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA +rBPuUBQemMc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEM +BQAwWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dp +ZXMsIEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAe +Fw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEwMTlaMFoxCzAJBgNVBAYTAkNOMSUw +IwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtU +cnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNS +T1QY4SxzlZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqK +AtCWHwDNBSHvBm3dIZwZQ0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1 +nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/VP68czH5GX6zfZBCK70bwkPAPLfSIC7Ep +qq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1AgdB4SQXMeJNnKziyhWTXA +yB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm9WAPzJMs +hH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gX +zhqcD0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAv +kV34PmVACxmZySYgWmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msT +f9FkPz2ccEblooV7WIQn3MSAPmeamseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jA +uPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCFTIcQcf+eQxuulXUtgQIDAQAB +o2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj7zjKsK5Xf/Ih +MBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4 +wM8zAQLpw6o1D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2 +XFNFV1pF1AWZLy4jVe5jaN/TG3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1 +JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNjduMNhXJEIlU/HHzp/LgV6FL6qj6j +ITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstlcHboCoWASzY9M/eV +VHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys+TIx +xHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1on +AX1daBli2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d +7XB4tmBZrOFdRWOPyN9yaFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2Ntjj +gKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsASZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV ++Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFRJQJ6+N1rZdVtTTDIZbpo +FGWsJwt0ivKH +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMw +WjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs +IEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0y +MTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJaMFoxCzAJBgNVBAYTAkNOMSUwIwYD +VQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtUcnVz +dEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATx +s8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbw +LxYI+hW8m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJij +YzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mD +pm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/pDHel4NZg6ZvccveMA4GA1UdDwEB/wQE +AwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AAbbd+NvBNEU/zy4k6LHiR +UKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xkdUfFVZDj +/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL +BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg +Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv +b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG +EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u +IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ +n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd +2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF +VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ +GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF +li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU +r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2 +eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb +MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg +jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB +7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW +5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE +ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 +90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z +xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu +QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4 +FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH +22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP +xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn +dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5 +Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b +nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ +CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH +u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj +d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF +eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx +MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV +BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog +D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS +sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop +O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk +sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi +c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj +VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz +KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/ +TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G +sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs +1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD +fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN +l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ +VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5 +c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp +4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s +t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj +2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO +vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C +xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx +cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM +fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9 +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH +bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x +CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds +b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr +b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9 +kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm +VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R +VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc +C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj +tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY +D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv +j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl +NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6 +iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP +O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV +ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj +L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl +1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU +b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV +PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj +y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb +EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg +DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI ++Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy +YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX +UB+K+wb1whnw0A== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV +BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g +Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ +BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ +R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF +dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw +vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ +uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp +n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs +cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW +xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P +rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF +DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx +DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy +LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C +eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ +d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq +kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl +qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0 +OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c +NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk +ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO +pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj +03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk +PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE +1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX +QRBdJ3NghVdJIgc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV +BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk +LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv +b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ +BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg +THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v +IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv +xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H +Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB +eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo +jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ ++efcMQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw +IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL +SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH +SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh +ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X +DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 +TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ +fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA +sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU +WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS +nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH +dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip +NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC +AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF +MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB +uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl +PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP +JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ +gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 +j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 +5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB +o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS +/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z +Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE +W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D +hNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG +EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx +IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND +IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci +MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti +sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O +BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB +Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c +3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J +0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG +EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo +bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ +TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s +b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 +WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS +fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB +zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB +CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD ++JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG +A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg +SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v +dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ +BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ +HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH +3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH +GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c +xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1 +aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq +TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87 +/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4 +kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG +YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT ++xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo +WXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD +VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU +ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH +MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO +MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv +Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz +f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO +8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq +d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM +tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt +Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB +o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x +PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM +wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d +GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH +6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby +RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMw +RzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAY +BgNVBAMTEXZUcnVzIEVDQyBSb290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDcz +MTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28u +LEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+cToL0 +v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUd +e4BdS49nTPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIw +V53dVvHH4+m4SVBrm2nDb+zDfSXkV5UTQJtS0zvzQBm8JsctBp61ezaf9SXUY2sA +AjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQLYgmRWAD5Tfs0aNoJrSEG +GJTO +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQEL +BQAwQzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4x +FjAUBgNVBAMTDXZUcnVzIFJvb3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMx +MDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoGA1UEChMTaVRydXNDaGluYSBDby4s +THRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZotsSKYc +IrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykU +AyyNJJrIZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+ +GrPSbcKvdmaVayqwlHeFXgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z9 +8Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KAYPxMvDVTAWqXcoKv8R1w6Jz1717CbMdH +flqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70kLJrxLT5ZOrpGgrIDajt +J8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2AXPKBlim +0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZN +pGvu/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQ +UqqzApVg+QxMaPnu1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHW +OXSuTEGC2/KmSNGzm/MzqvOmwMVO9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMB +AAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYgscasGrz2iTAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAKbqSSaet +8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd +nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1j +bhd47F18iMjrjld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvM +Kar5CKXiNxTKsbhm7xqC5PD48acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIiv +TDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJnxDHO2zTlJQNgJXtxmOTAGytfdELS +S8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554WgicEFOwE30z9J4nfr +I8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4sEb9 +b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNB +UvupLnKWnyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1P +Ti07NEPhmg4NpGaXutIcSkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929ven +sBxXVsFy6K2ir40zSbofitzmdHxghm+Hl3s= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBI +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE +LVRSVVNUIEJSIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUw +OTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi +MCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCTcfKr +i3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNE +gXtRr90zsWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8 +k12b9py0i4a6Ibn08OhZWiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCT +Rphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl +2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LULQyReS2tNZ9/WtT5PeB+U +cSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIvx9gvdhFP +/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bS +uREVMweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+ +0bpwHJwh5Q8xaRfX/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4N +DfTisl01gLmB1IRpkQLLddCNxbU9CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+ +XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUZ5Dw1t61 +GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG +OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tI +FoE9c+CeJyrrd6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67n +riv6uvw8l5VAk1/DLQOj7aRvU9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTR +VFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4nj8+AybmTNudX0KEPUUDAxxZiMrc +LmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdijYQ6qgYF/6FKC0ULn +4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff/vtD +hQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsG +koHU6XCPpz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46 +ls/pdu4D58JDUjxqgejBWoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aS +Ecr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/5usWDiJFAbzdNpQ0qTUmiteXue4Icr80 +knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jtn/mtd+ArY0+ew+43u3gJ +hJ65bvspmZDogNOfJA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBI +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE +LVRSVVNUIEVWIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUw +OTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi +MCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1sJkK +F8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE +7CUXFId/MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFe +EMbsh2aJgWi6zCudR3Mfvc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6 +lHPTGGkKSv/BAQP/eX+1SH977ugpbzZMlWGG2Pmic4ruri+W7mjNPU0oQvlFKzIb +RlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3YG14C8qKXO0elg6DpkiV +jTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq9107PncjLgc +jmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZx +TnXonMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ +ARZZaBhDM7DS3LAaQzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nk +hbDhezGdpn9yo7nELC7MmVcOIQxFAZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knF +NXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUqvyREBuH +kV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG +OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14 +QvBukEdHjqOSMo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4 +pZt+UPJ26oUFKidBK7GB0aL2QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q +3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xDUmPBEcrCRbH0O1P1aa4846XerOhU +t7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V4U/M5d40VxDJI3IX +cI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuodNv8 +ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT +2vFp4LJiTZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs +7dpn1mKmS00PaaLJvOwiS5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNP +gofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAst +Nl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L+KIkBI3Y4WNeApI02phh +XBxvWHZks/wCuPWdCg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICzzCCAjGgAwIBAgINAOhvGHvWOWuYSkmYCjAKBggqhkjOPQQDBDB1MQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xFzAVBgNVBGEMDlZBVEhVLTIzNTg0NDk3MSIwIAYDVQQDDBllLVN6aWdubyBU +TFMgUm9vdCBDQSAyMDIzMB4XDTIzMDcxNzE0MDAwMFoXDTM4MDcxNzE0MDAwMFow +dTELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRYwFAYDVQQKDA1NaWNy +b3NlYyBMdGQuMRcwFQYDVQRhDA5WQVRIVS0yMzU4NDQ5NzEiMCAGA1UEAwwZZS1T +emlnbm8gVExTIFJvb3QgQ0EgMjAyMzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAE +AGgP36J8PKp0iGEKjcJMpQEiFNT3YHdCnAo4YKGMZz6zY+n6kbCLS+Y53wLCMAFS +AL/fjO1ZrTJlqwlZULUZwmgcAOAFX9pQJhzDrAQixTpN7+lXWDajwRlTEArRzT/v +SzUaQ49CE0y5LBqcvjC2xN7cS53kpDzLLtmt3999Cd8ukv+ho2MwYTAPBgNVHRMB +Af8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUWYQCYlpGePVd3I8K +ECgj3NXW+0UwHwYDVR0jBBgwFoAUWYQCYlpGePVd3I8KECgj3NXW+0UwCgYIKoZI +zj0EAwQDgYsAMIGHAkIBLdqu9S54tma4n7Zwf2Z0z+yOfP7AAXmazlIC58PRDHpt +y7Ve7hekm9sEdu4pKeiv+62sUvTXK9Z3hBC9xdIoaDQCQTV2WnXzkoYI9bIeCvZl +C9p2x1L/Cx6AcCIwwzPbGO2E14vs7dOoY4G1VnxHx1YwlGhza9IuqbnZLBwpvQy6 +uWWL +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICNTCCAbqgAwIBAgIQI/nD1jWvjyhLH/BU6n6XnTAKBggqhkjOPQQDAzBLMQsw +CQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwY +T0lTVEUgU2VydmVyIFJvb3QgRUNDIEcxMB4XDTIzMDUzMTE0NDIyOFoXDTQ4MDUy +NDE0NDIyN1owSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRp +b24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IEVDQyBHMTB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABBcv+hK8rBjzCvRE1nZCnrPoH7d5qVi2+GXROiFPqOujvqQy +cvO2Ackr/XeFblPdreqqLiWStukhEaivtUwL85Zgmjvn6hp4LrQ95SjeHIC6XG4N +2xml4z+cKrhAS93mT6NjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQ3 +TYhlz/w9itWj8UnATgwQb0K0nDAdBgNVHQ4EFgQUN02IZc/8PYrVo/FJwE4MEG9C +tJwwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2kAMGYCMQCpKjAd0MKfkFFR +QD6VVCHNFmb3U2wIFjnQEnx/Yxvf4zgAOdktUyBFCxxgZzFDJe0CMQCSia7pXGKD +YmH5LVerVrkR3SW+ak5KGoJr3M/TvEqzPNcum9v4KGm8ay3sMaE641c= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIQVaXZZ5Qoxu0M+ifdWwFNGDANBgkqhkiG9w0BAQwFADBL +MQswCQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UE +AwwYT0lTVEUgU2VydmVyIFJvb3QgUlNBIEcxMB4XDTIzMDUzMTE0MzcxNloXDTQ4 +MDUyNDE0MzcxNVowSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5k +YXRpb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IFJTQSBHMTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAKqu9KuCz/vlNwvn1ZatkOhLKdxVYOPM +vLO8LZK55KN68YG0nnJyQ98/qwsmtO57Gmn7KNByXEptaZnwYx4M0rH/1ow00O7b +rEi56rAUjtgHqSSY3ekJvqgiG1k50SeH3BzN+Puz6+mTeO0Pzjd8JnduodgsIUzk +ik/HEzxux9UTl7Ko2yRpg1bTacuCErudG/L4NPKYKyqOBGf244ehHa1uzjZ0Dl4z +O8vbUZeUapU8zhhabkvG/AePLhq5SvdkNCncpo1Q4Y2LS+VIG24ugBA/5J8bZT8R +tOpXaZ+0AOuFJJkk9SGdl6r7NH8CaxWQrbueWhl/pIzY+m0o/DjH40ytas7ZTpOS +jswMZ78LS5bOZmdTaMsXEY5Z96ycG7mOaES3GK/m5Q9l3JUJsJMStR8+lKXHiHUh +sd4JJCpM4rzsTGdHwimIuQq6+cF0zowYJmXa92/GjHtoXAvuY8BeS/FOzJ8vD+Ho +mnqT8eDI278n5mUpezbgMxVz8p1rhAhoKzYHKyfMeNhqhw5HdPSqoBNdZH702xSu ++zrkL8Fl47l6QGzwBrd7KJvX4V84c5Ss2XCTLdyEr0YconosP4EmQufU2MVshGYR +i3drVByjtdgQ8K4p92cIiBdcuJd5z+orKu5YM+Vt6SmqZQENghPsJQtdLEByFSnT +kCz3GkPVavBpAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU +8snBDw1jALvsRQ5KH7WxszbNDo0wHQYDVR0OBBYEFPLJwQ8NYwC77EUOSh+1sbM2 +zQ6NMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEANGd5sjrG5T33 +I3K5Ce+SrScfoE4KsvXaFwyihdJ+klH9FWXXXGtkFu6KRcoMQzZENdl//nk6HOjG +5D1rd9QhEOP28yBOqb6J8xycqd+8MDoX0TJD0KqKchxRKEzdNsjkLWd9kYccnbz8 +qyiWXmFcuCIzGEgWUOrKL+mlSdx/PKQZvDatkuK59EvV6wit53j+F8Bdh3foZ3dP +AGav9LEDOr4SfEE15fSmG0eLy3n31r8Xbk5l8PjaV8GUgeV6Vg27Rn9vkf195hfk +gSe7BYhW3SCl95gtkRlpMV+bMPKZrXJAlszYd2abtNUOshD+FKrDgHGdPY3ofRRs +YWSGRqbXVMW215AWRqWFyp464+YTFrYVI8ypKVL9AMb2kI5Wj4kI3Zaq5tNqqYY1 +9tVFeEJKRvwDyF7YZvZFZSS0vod7VSCd9521Kvy5YhnLbDuv0204bKt7ph6N/Ome +/msVuduCmsuY33OhkKCgxeDoAaijFJzIwZqsFVAzje18KotzlUBDJvyBpCpfOZC3 +J8tRd/iWkx7P8nd9H0aTolkelUTFLXVksNb54Dxp6gS1HAviRkRNQzuXSXERvSS2 +wq1yVAb+axj5d9spLFKebXd7Yv0PTY6YMjAwcRLWJTXjn/hvnLXrahut6hDTlhZy +BiElxky8j3C7DOReIoMt0r7+hVu05L0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u +LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgw +NTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD +eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS +b290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3emhF +KxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mt +p7JIKwccJ/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zd +J1M3s6oYwlkm7Fsf0uZlfO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gur +FzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBFEaCeVESE99g2zvVQR9wsMJvuwPWW0v4J +hscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1UefNzFJM3IFTQy2VYzxV4+K +h9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsF +AAOCAQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6Ld +mmQOmFxv3Y67ilQiLUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJ +mBClnW8Zt7vPemVV2zfrPIpyMpcemik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA +8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPSvWKErI4cqc1avTc7bgoitPQV +55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhgaaaI5gdka9at/ +yOPiZwud9AzqVN/Ssq+xIvEg37xEHA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEM +BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u +LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgw +NzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD +eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS +b290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh1oq/ +FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOg +vlIfX8xnbacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy +6pJxaeQp8E+BgQQ8sqVb1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo +/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9J +kdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOEkJTRX45zGRBdAuVwpcAQ +0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSxjVIHvXib +y8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac +18izju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs +0Wq2XSqypWa9a4X0dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIAB +SMbHdPTGrMNASRZhdCyvjG817XsYAFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVL +ApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeqYR3r6/wtbyPk +86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E +rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ib +ed87hwriZLoAymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopT +zfFP7ELyk+OZpDc8h7hi2/DsHzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHS +DCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPGFrojutzdfhrGe0K22VoF3Jpf1d+4 +2kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6qnsb58Nn4DSEC5MUo +FlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/OfVy +K4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6 +dB7h7sxaOgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtl +Lor6CZpO2oYofaphNdgOpygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB +365jJ6UeTo3cKXhZ+PmhIIynJkBugnLNeLLIjzwec+fBH7/PzqUqm9tEZDKgu39c +JRNItX+S +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMw +UTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBM +dGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMy +NTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJl +cnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBSb290 +IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5GdCx4 +wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSR +ZHX+AezB2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT +9DAKBggqhkjOPQQDAwNoADBlAjEA2S6Jfl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp +4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJSwdLZrWeqrqgHkHZAXQ6 +bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UE +AxMiU3dpc3NTaWduIFJTQSBUTFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgx +MTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxT +d2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0Eg +MjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmjiC8NX +vDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7 +LCTLf5ImgKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX +5XH8irCRIFucdFJtrhUnWXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyE +EPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlfGUEGjw5NBuBwQCMBauTLE5tzrE0USJIt +/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36qOTw7D59Ke4LKa2/KIj4x +0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLOEGrOyvi5 +KaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM +0ZPlEuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shd +OxtYk8EXlFXIC+OCeYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrta +clXvyFu1cvh43zcgTFeRc5JzrBh3Q4IgaezprClG5QtO+DdziZaKHG29777YtvTK +wP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQABo2MwYTAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow4UD2p8P98Q+4 +DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL +BQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO3 +10aewCoSPY6WlkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgz +Hqp41eZUBDqyggmNzhYzWUUo8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQ +iJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zpy1FVCypM9fJkT6lc/2cyjlUtMoIc +gC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3CjlvrzG4ngRhZi0Rjn9UM +ZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6MOuhF +LhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJp +zv1/THfQwUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/Td +Ao9QAwKxuDdollDruF/UKIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0 +rk4N3hY9A4GzJl5LuEsAz/+MF7psYC0nhzck5npgL7XTgwSqT0N1osGDsieYK7EO +gLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rwtnu64ZzZ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw +CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH +bWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw +MB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIzNTk1OVowYzELMAkGA1UEBhMCREUx +JzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkGA1UE +AwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/O +tdKPD/M12kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDP +f8iAC8GXs7s1J8nCG6NCMEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6f +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA +MGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di +z6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn +27iQ7t0l +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj +MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 +eSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy +MDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMyNzIzNTk1OVowYzELMAkGA1UEBhMC +REUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkG +A1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9 +cUD/h3VCKSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHV +cp6R+SPWcHu79ZvB7JPPGeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMA +U6DksquDOFczJZSfvkgdmOGjup5czQRxUX11eKvzWarE4GC+j4NSuHUaQTXtvPM6 +Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWol8hHD/BeEIvnHRz+sTug +BTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9FIS3R/qy +8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73J +co4vzLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg +8qKrBC7m8kwOFjQgrIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8 +rFEz0ciD0cmfHdRHNCk+y7AO+oMLKFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12 +mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7SWWO/gLCMk3PLNaaZlSJhZQNg ++y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtqeX +gj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 +p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQ +pGv7qHBFfLp+sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm +9S3ul0A8Yute1hTWjOKWi0FpkzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErw +M807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy/SKE8YXJN3nptT+/XOR0so8RYgDd +GGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4mZqTuXNnQkYRIer+ +CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtzaL1t +xKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+ +w6jv/naaoqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aK +L4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj +X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q +ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm +dTdmQRCsu/WU48IxK63nI1bMNSWSs1A= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMw +WDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs +IEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQw +NTE1MDU0MTU2WhcNNDQwNTE1MDU0MTU1WjBYMQswCQYDVQQGEwJDTjElMCMGA1UE +ChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1c3RB +c2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLh/pVs/ +AT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPoXlfXTr4sP/MSpwDp +guMqWzJ8S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kzsaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAw +DgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01 +L18N9mdsd0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15KeAIxAKORh/IRM4PDwYqR +OkwrULG9IpRdNYlzg8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEM +BQAwWDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dp +ZXMsIEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcN +MjQwNTE1MDU0MTU3WhcNNDQwNTE1MDU0MTU2WjBYMQswCQYDVQQGEwJDTjElMCMG +A1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1 +c3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC +AgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwhaGnrhB3YmH49pVr7+ +NmDQDIPNlOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMmSoPGlbYJ +Q1DNDX3eRA5gEk9bNb2/mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561 +HmHVb70AcnKtEj+qpklz8oYVlQwQX1Fkzv93uMltrOXVmPGZLmzjyUT5tUMnCE32 +ft5EebuyjBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYozza/+lcK7Fs/6TAWe8Tb +xNRkoDD75f0dcZLdKY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyrz2I8sMeX +i9bQn9P+PN7F4/w6g3CEIR0JwqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQ +UNoyIBnkiz/r1RYmNzz7dZ6wB3C4FGB33PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+j +TnhMmCWr8n4uIF6CFabW2I+s5c0yhsj55NqJ4js+k8UTav/H9xj8Z7XvGCxUq0DT +bE3txci3OE9kxJRMT6DNrqXGJyV1J23G2pyOsAWZ1SgRxSHUuPzHlqtKZFlhaxP8 +S8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnTq1mt1tve1CuBAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZylomkadFK/hT +MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3 +Rz/NyjuujsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4 +iqME3mmL5Dw8veWv0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt +7DlK9RME7I10nYEKqG/odv6LTytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp +2xIQaOHEibgGIOcberyxk2GaGUARtWqFVwHxtlotJnMnlvm5P1vQiJ3koP26TpUJ +g3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soaB82G39tp27RIGAAtvKLEiUUj +pQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4GS/+X/jbh87qqA8M +pugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjEWn9hongP +XvPKnbwbPKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIwe +SsCI3zWQzj8C9GRh3sfIB5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0 +ly4wBOeY99sLAZDBHwo/+ML+TvrbmnNzFrwFuHnYWa8G5z9nODmxfKuU4CkUpijy +323imttUQ/hHWKNddBWcwauwxzQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ +MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290 +IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5 +WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FO +LUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1sTs6P +40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxF +avcokPFhV8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/ +34bKS1PE2Y2yHer43CdTo0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684i +JkXXYJndzk834H/nY62wuFm40AZoNWDTNq5xQwTxaWV4fPMf88oon1oglWa0zbfu +j3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK/c/WMw+f+5eesRycnupf +Xtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkHIuNZW0CP +2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDA +S9TMfAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDA +oS/xUgXJP+92ZuJF2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzC +kHDXShi8fgGwsOsVHkQGzaRP6AzRwyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW +5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83QOGt4A1WNzAd +BgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB +AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0t +tGlTITVX1olNc79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn +68xDiBaiA9a5F/gZbG0jAn/xX9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNn +TKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDRIG4kqIQnoVesqlVYL9zZyvpoBJ7t +RCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq/p1hvIbZv97Tujqx +f36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0RFxbI +Qh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz +8ppy6rBePm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4 +NxKfKjLji7gh7MMrZQzvIt6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzX +xeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6 +t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X +-----END CERTIFICATE----- diff --git a/build-docs.sh b/build-docs.sh index 2e72ca9ab..63eee70fa 100755 --- a/build-docs.sh +++ b/build-docs.sh @@ -1,15 +1,19 @@ #!/usr/bin/env bash -# Build the Singe manual from docs/Manual.adoc. -# Produces .builddir/Manual.html and .builddir/Manual.pdf. The version comes from CMakeLists.txt. +# Build the Singe manual from docs/Manual.adoc and the Forge manual from docs/Forge.adoc. +# Produces .builddir/Manual.html, Manual.pdf, Forge.html and Forge.pdf. The version comes from +# CMakeLists.txt. set -euo pipefail here=$(cd "$(dirname "$0")" && pwd) src=$here/docs/Manual.adoc +forge=$here/docs/Forge.adoc -if [[ ! -f $src ]]; then - echo "error: $src not found" >&2 - exit 1 -fi +for doc in "$src" "$forge"; do + if [[ ! -f $doc ]]; then + echo "error: $doc not found" >&2 + exit 1 + fi +done for tool in asciidoctor asciidoctor-pdf; do if ! command -v "$tool" > /dev/null; then echo "error: $tool not found (gem install asciidoctor asciidoctor-pdf rouge)" >&2 @@ -23,6 +27,8 @@ out=$here/.builddir mkdir -p "$out" asciidoctor -a revnumber="$version" "$src" -o "$out/Manual.html" asciidoctor-pdf -a revnumber="$version" "$src" -o "$out/Manual.pdf" +asciidoctor -a revnumber="$version" "$forge" -o "$out/Forge.html" +asciidoctor-pdf -a revnumber="$version" "$forge" -o "$out/Forge.pdf" echo "built:" -ls -la "$out/Manual.html" "$out/Manual.pdf" +ls -la "$out/Manual.html" "$out/Manual.pdf" "$out/Forge.html" "$out/Forge.pdf" diff --git a/cmake/Superbuild.cmake b/cmake/Superbuild.cmake index 8028ad551..613c05c42 100644 --- a/cmake/Superbuild.cmake +++ b/cmake/Superbuild.cmake @@ -457,18 +457,23 @@ ExternalProject_Add(singe # Forge, packed into .builddir beside the binary so it can be copied out and tested. It is NOT # part of the engine and nothing of it is embedded; this only puts the distributable where the # binaries already are. Only a build that produces a runnable binary can pack it, since packing is -# the engine's own --pack, so cross builds skip it and the host build does the work. +# the engine's own --pack, so cross builds skip it and the host build does the work. The manual, +# rendered by the engine's own build into .builddir, goes in beside the scripts: Forge copies it +# out to its data directory on its first run. if((KANGAROO_OS STREQUAL "linux") AND (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")) file(GLOB forgeSources ${CMAKE_SOURCE_DIR}/assets/Forge/*) set(forgePack ${CMAKE_SOURCE_DIR}/.builddir/Forge.game) + set(forgeManual ${CMAKE_SOURCE_DIR}/.builddir/Forge.pdf) + set(forgeStage ${SB_PREFIX}/forgepack) add_custom_command( OUTPUT ${forgePack} # Packing refuses to overwrite, and a stale pack is worse than none. - COMMAND ${CMAKE_COMMAND} -E rm -f ${forgePack} - COMMAND ${CMAKE_COMMAND} -E make_directory ${SB_PREFIX}/forgepack + COMMAND ${CMAKE_COMMAND} -E rm -rf ${forgePack} ${forgeStage} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets/Forge ${forgeStage}/Forge + COMMAND ${CMAKE_COMMAND} -E copy ${forgeManual} ${forgeStage}/Forge/Forge.pdf # Run it somewhere harmless: the engine writes Singe/ and data/ into the working directory. - COMMAND ${CMAKE_COMMAND} -E chdir ${SB_PREFIX}/forgepack ${CMAKE_SOURCE_DIR}/.builddir/${singeBinaryName} --pack ${CMAKE_SOURCE_DIR}/assets/Forge ${forgePack} - DEPENDS singe ${forgeSources} + COMMAND ${CMAKE_COMMAND} -E chdir ${forgeStage} ${CMAKE_SOURCE_DIR}/.builddir/${singeBinaryName} --pack ${forgeStage}/Forge ${forgePack} + DEPENDS singe ${forgeSources} ${forgeManual} COMMENT "Packing Forge into .builddir/Forge.game" VERBATIM ) diff --git a/docs/Forge.adoc b/docs/Forge.adoc new file mode 100644 index 000000000..034e7a000 --- /dev/null +++ b/docs/Forge.adoc @@ -0,0 +1,253 @@ += Forge +Scott Duensing +:revnumber: 3.00 +:revdate: 2026 +:doctype: book +:toc: left +:toclevels: 3 +:sectnums: +:sectnumlevels: 3 +:source-highlighter: rouge +:icons: font +:experimental: + +[preface] +== About Forge + +Forge is Singe's authoring tool: a way to make a game by describing it -- +placing things, attaching behaviours, writing rules -- and having ordinary +Singe Lua written for you. It is itself a Singe game and is distributed on +its own, beside the engine rather than inside it. This document covers the +description format, the vocabulary, the compiler and the editor. The engine +calls the generated code makes (`playerNew`, `collidePointRect`, +`onKeyPressed`, `scriptPush` and the rest) are documented in the Singe Manual. + +== Describing a Game Instead of Writing One + +A game can be written as a *description* -- a table of layers, entities and +rules -- and compiled into an ordinary Singe game. +`Forge/AuthorCompile.singe` does the compiling and `Author.singe` is the runtime +the result calls. **No part of Forge ships with Singe** -- not the editor, not +the compiler, not the runtime -- so a game built with it carries its own copy of +that runtime and stands entirely on its own. Nothing is interpreted at run time: the rules +become real Lua `if` statements, so a description costs nothing per frame on a +Raspberry Pi, and the game it produces can be opened, read and edited by hand +like any other. + +There is no notion of genre anywhere in it. A game declares which of the +engine's own layers it uses, and that is the only difference between a light +gun game, a platformer and a quick-time event over video. + +=== The three nouns + +*Layers* are what the game draws through: `world2d` (physics in the XY plane, +drawn into the overlay), `overlay` (flat drawing over everything) and `disc` +(the video the game is played over). A game lists the ones it wants. + +*Entities* are things on a layer. Each has a position, a `look` (`box`, +`sprite` or `text`) and any number of behaviours. Every entity is a node, +whether or not the 3D scene is drawing, which is what lets a 2D game built this +way run on a machine with no GPU. + +*Behaviours* are bundles over engine calls that already exist: `platformer` is +the character controller (`playerNew` and friends), `solid` is a +static body, `drift` moves at a constant velocity. Attaching one is a line in +the description rather than code. + +*Rules* are conditions and actions. Every rule is tested every frame, in the +order written, and all of a rule's conditions must hold for its actions to run. + +.A description, in full +[source,lua] +---- +return { + title = "One rule", + layers = { { kind = "world2d", gravity = 1500 } }, + entities = { + { id = "ground", x = 360, y = 440, + look = { kind = "box", w = 720, h = 40, r = 60, g = 70, b = 90 }, + behaviours = { { kind = "solid" } } }, + { id = "hero", x = 120, y = 380, + look = { kind = "box", w = 24, h = 44, r = 230, g = 90, b = 170 }, + behaviours = { { kind = "platformer", speed = 210, jump = 620 } } } + }, + rules = { + { note = "Run right", + when = { { "keyHeld", key = "RIGHT" } }, + act = { { "run", entity = "hero", direction = 1 } } } + } +} +---- + +Compile it and run what comes out: + +[source,lua] +---- +dofile("Forge/AuthorCompile.singe") +dofile(authorBuild("mygame.game", singeGetDataPath() .. "mygame.singe")) +---- + +`testScripts/author/platformer.game` and `testScripts/author/qte.game` are +worked examples, and `testScripts/scene52.singe` and `scene53.singe` compile +and play them. + +=== The vocabulary, and adding to it + +Conditions and actions are not built into the compiler. Each is an entry in +the `AUTHOR` table declaring its parameters and the Lua it emits, so a new kind +of game is a set of entries rather than a new release. The conditions today +are `keyHeld`, `switchHeld`, `timeBetween`, `discBetween`, `onGround`, +`touching`, `below`, `flagSet` and `once`; the actions are `run`, `jump`, +`moveTo`, `setText`, `show`, `addScore`, `setFlag`, `discTo` and `lua`. + +`once` deserves a word. Rules run every frame, so anything that should happen +a single time -- a door opening, a score awarded -- needs it: + +[source,lua] +---- +{ when = { { "touching", entity = "hero", other = "prize" }, + { "once", tag = "prize" } }, + act = { { "addScore", amount = 100 }, + { "show", entity = "prize", visible = false } } } +---- + +=== The way out + +The `lua` action takes a line of Lua and emits it as it stands. It is there on +purpose: when a rule needs something the vocabulary cannot say, that rule drops +to Lua and the rest of the game is unaffected. A description is a convenience, +not a cage, and the compiled output is a normal game you can stop describing +and start editing whenever it suits you. + +=== The editor + +`Forge/Forge.singe` edits a description, and it is itself a Singe game. It has +its own directory beside the games, appears in the menu like one, and packs to +`Forge.game` with `--pack`; the build does that itself (the `forge` target), +with this manual inside. +Nothing in it is a preview: the canvas is the same overlay at the same +coordinates the game will be played in, so what is placed is what is seen. + +Started from the menu it opens on a chooser: the descriptions in its data +directory, any dropped into the `Forge` directory itself (those are opened as a +copy, since inside a `.game` they are read only), and *New game*, which writes +a starter -- ground, a hero that runs and jumps, a score readout -- and opens +it. The first run copies this manual, `Forge.pdf`, out of `Forge.game` into +that data directory, and the chooser says where it is. `Esc` in the editor closes the description (twice, when it has unsaved +changes) and `Esc` on the chooser leaves Forge. `P` plays: the description is +saved, compiled beside a copy of the runtime, and handed to the engine with +`scriptPush`; when the game ends Forge comes back on the same file. + +A script can drive the editor instead, which is how the test scenes do it: + +[source,lua] +---- +FORGE_LIBRARY = true +dofile("Forge/Forge.singe") +forgeBegin("mygame.game") + +function onOverlayUpdate() + local x, y = mouseGetPosition(0) + + forgeDraw(x, y) + + return OVERLAY_UPDATED +end +---- + +The entity list and the details are an RmlUi document; the canvas beside them is +drawn into the overlay and picked with `collidePointRect`. +The two compose because the engine offers a button to the GUI first and passes +on what it did not use, while pointer motion is never consumed at all -- so +point `forgePress`, `forgeDrag` and `forgeRelease` at the mouse +callbacks and clicks on the panels will not reach the canvas. + +`forgeSave()` writes the description back, `forgeBuild(path)` +compiles what is on screen (and puts the runtime beside it, so the result +plays), and `forgeMove(index, x, y)` moves an entity without a pointer, which +is how `testScripts/scene54.singe` drives it. + +=== Editing the entities + +`A` adds a box in the middle of the canvas, `D` duplicates the selected entity +a little to one side, and `Delete` removes it. `Enter` walks the entity's +fields the same way it walks a rule's parameters: `id`, `x`, `y`, the look's +`kind`, then whatever that look takes, then `behaviours` -- typed as a list of +kinds, `platformer, solid` -- and then each behaviour's own parameters as +`platformer.speed` and so on. The fields come from the `AUTHOR` manifest, so a +new look or behaviour is editable the moment it is declared. Renaming an +entity renames it in every rule that talks about it. A `sprite` look is drawn +with its image once the file name is right, and as a box until then. + +From a script, `forgeEntityAdd(x, y)`, `forgeEntityDuplicate()`, +`forgeEntityDelete()` and `forgeEntitySet(entity, field, value)` do the same. + +=== Editing the rules + +The panel shows either the entities or the event sheet; `Tab` swaps them. In +the rules, the selected rule opens in place and its conditions and actions are +listed under it, because a rule only means anything whole -- a `when` without a +`then` tells you nothing. + +Point `onKeyPressed` at `forgeKey` and the whole editor +works without a pointer, which is how the bundled menu has always been driven +and what a cabinet wants: + +[cols="1,4"] +|=== +| `Tab` | entities or rules +| Up, Down | move through the list, and through the parts of the open rule +| Left, Right | slide the panel +| `Enter` | type a value for whatever is selected; again for its next value +| `Esc` | put the value back +| `A`, `D`, `Delete` | add, duplicate, delete the selected entity +| `N` | a new rule +| `C`, `T` | add a condition, an action -- picked from the vocabulary, with its help beside it +| `[`, `]` | move the rule up or down the sheet +| `Delete` | delete the selected rule, or the selected condition or action +| `U`, `R` | undo and redo, forty steps deep; a drag or a typed form is one step, and a change by hand ends the redo history +| `S`, `B`, `P` | save, build, play +|=== + +With the rule itself selected rather than one of its parts, `Enter` edits its +note. + +A typed number comes back a number rather than a string, because `100` and +`"100"` compile to different source and a description that changed shape when a +value was retyped would stop round-tripping. + +From a script, `forgeRuleNew(note)`, `forgeRuleAdd("when"|"act", +name)`, `forgePartSet(key, value)`, `forgePartDelete()`, `forgeRuleMove(by)`, +`forgeUndo()` and `forgeRedo()` do the same work. Adding a condition selects it, so its +parameters can be set at once. + +The vocabulary comes from the `AUTHOR` manifest, so the rule editor never needs +changing when a condition or an action is added: it offers whatever is +declared. A new parameter gets a sensible starting value for its type, so a +rule compiles the moment it is made rather than only once every field is +filled in. + +A description survives the round trip: loading one, saving it and loading it +again compiles to the same game, byte for byte. The editor depends on that and +the test asserts it. + +=== Releasing a game + +`forgeExport(folder, name)` writes everything a finished game needs into a +directory of its own: the compiled script, a `games.dat` so the menu lists it, +the description it was built from so it can be opened again, and **a copy of +the runtime**, taken out of Forge. `--pack` turns that directory into a `.game` +like any other, and it runs on a machine that has never had Forge on it. + +Every built game finds its own directory to load that runtime from, using +`debug.getinfo` rather than `DIR`: `DIR` is the directory of the script the +engine was *launched* with, so a game reached by `dofile` -- a test, a +launcher, a preview -- would otherwise look beside the caller. + +The panel slides. Drag the tab on its outer edge and it moves across the +window, so an entity that lives underneath it -- a score readout at `12, 12` +does -- is never permanently out of reach. `forgePanelTo(x)` moves it from +a script, and `forgeOverPanel(x)` says whether a point is currently +covered. + +[#migrating] diff --git a/docs/Manual.adoc b/docs/Manual.adoc index 2eae26bd0..29104e5b2 100644 --- a/docs/Manual.adoc +++ b/docs/Manual.adoc @@ -1421,7 +1421,6 @@ GAMES = { TITLE = ".38 Ambush Alley", SCRIPT = "ActionMax/38AmbushAlley.singe", VIDEO = "ActionMax/frame_38AmbushAlley.txt", - DATA = "ActionMax", STRETCH = false, NO_MOUSE = false, RESOLUTION_X = 720, @@ -3192,195 +3191,12 @@ die. === Describing a Game Instead of Writing One A game can be written as a *description* -- a table of layers, entities and -rules -- and compiled into an ordinary Singe game. -`Forge/AuthorCompile.singe` does the compiling and `Author.singe` is the runtime -the result calls. **No part of Forge ships with Singe** -- not the editor, not -the compiler, not the runtime -- so a game built with it carries its own copy of -that runtime and stands entirely on its own. Nothing is interpreted at run time: the rules -become real Lua `if` statements, so a description costs nothing per frame on a -Raspberry Pi, and the game it produces can be opened, read and edited by hand -like any other. +rules -- and compiled into an ordinary Singe game by Forge, the authoring +tool. Forge is distributed on its own, beside the engine, and has its own +manual: `docs/Forge.adoc` in the source tree, built to `Forge.html` and +`Forge.pdf` beside this one. -There is no notion of genre anywhere in it. A game declares which of the -engine's own layers it uses, and that is the only difference between a light -gun game, a platformer and a quick-time event over video. -==== The three nouns - -*Layers* are what the game draws through: `world2d` (physics in the XY plane, -drawn into the overlay), `overlay` (flat drawing over everything) and `disc` -(the video the game is played over). A game lists the ones it wants. - -*Entities* are things on a layer. Each has a position, a `look` (`box`, -`sprite` or `text`) and any number of behaviours. Every entity is a node, -whether or not the 3D scene is drawing, which is what lets a 2D game built this -way run on a machine with no GPU. - -*Behaviours* are bundles over engine calls that already exist: `platformer` is -the character controller (<> and friends), `solid` is a -static body, `drift` moves at a constant velocity. Attaching one is a line in -the description rather than code. - -*Rules* are conditions and actions. Every rule is tested every frame, in the -order written, and all of a rule's conditions must hold for its actions to run. - -.A description, in full -[source,lua] ----- -return { - title = "One rule", - layers = { { kind = "world2d", gravity = 1500 } }, - entities = { - { id = "ground", x = 360, y = 440, - look = { kind = "box", w = 720, h = 40, r = 60, g = 70, b = 90 }, - behaviours = { { kind = "solid" } } }, - { id = "hero", x = 120, y = 380, - look = { kind = "box", w = 24, h = 44, r = 230, g = 90, b = 170 }, - behaviours = { { kind = "platformer", speed = 210, jump = 620 } } } - }, - rules = { - { note = "Run right", - when = { { "keyHeld", key = "RIGHT" } }, - act = { { "run", entity = "hero", direction = 1 } } } - } -} ----- - -Compile it and run what comes out: - -[source,lua] ----- -dofile("Forge/AuthorCompile.singe") -dofile(authorBuild("mygame.game", singeGetDataPath() .. "mygame.singe")) ----- - -`testScripts/author/platformer.game` and `testScripts/author/qte.game` are -worked examples, and `testScripts/scene52.singe` and `scene53.singe` compile -and play them. - -==== The vocabulary, and adding to it - -Conditions and actions are not built into the compiler. Each is an entry in -the `AUTHOR` table declaring its parameters and the Lua it emits, so a new kind -of game is a set of entries rather than a new release. The conditions today -are `keyHeld`, `switchHeld`, `timeBetween`, `discBetween`, `onGround`, -`touching`, `below`, `flagSet` and `once`; the actions are `run`, `jump`, -`moveTo`, `setText`, `show`, `addScore`, `setFlag`, `discTo` and `lua`. - -`once` deserves a word. Rules run every frame, so anything that should happen -a single time -- a door opening, a score awarded -- needs it: - -[source,lua] ----- -{ when = { { "touching", entity = "hero", other = "prize" }, - { "once", tag = "prize" } }, - act = { { "addScore", amount = 100 }, - { "show", entity = "prize", visible = false } } } ----- - -==== The way out - -The `lua` action takes a line of Lua and emits it as it stands. It is there on -purpose: when a rule needs something the vocabulary cannot say, that rule drops -to Lua and the rest of the game is unaffected. A description is a convenience, -not a cage, and the compiled output is a normal game you can stop describing -and start editing whenever it suits you. - -==== The editor - -`Forge/Forge.singe` edits a description, and it is itself a Singe game. It has -its own directory beside the games, appears in the menu like one, and packs to -`Forge.game` with `--pack`. -Nothing in it is a preview: the canvas is the same overlay at the same -coordinates the game will be played in, so what is placed is what is seen. - -[source,lua] ----- -FORGE_LIBRARY = true -dofile("Forge/Forge.singe") -forgeBegin("mygame.game") - -function onOverlayUpdate() - local x, y = mouseGetPosition(0) - - forgeDraw(x, y) - - return OVERLAY_UPDATED -end ----- - -The entity list and the details are an RmlUi document; the canvas beside them is -drawn into the overlay and picked with <>. -The two compose because the engine offers a button to the GUI first and passes -on what it did not use, while pointer motion is never consumed at all -- so -point `forgePress`, `forgeDrag` and `forgeRelease` at the mouse -callbacks and clicks on the panels will not reach the canvas. - -`forgeSave()` writes the description back, `forgeBuild(path)` -compiles what is on screen, and `forgeMove(index, x, y)` moves an entity -without a pointer, which is how `testScripts/scene54.singe` drives it. - -==== Editing the rules - -The panel shows either the entities or the event sheet; `Tab` swaps them. In -the rules, the selected rule opens in place and its conditions and actions are -listed under it, because a rule only means anything whole -- a `when` without a -`then` tells you nothing. - -Point <> at `forgeKey` and the whole editor -works without a pointer, which is how the bundled menu has always been driven -and what a cabinet wants: - -[cols="1,4"] -|=== -| `Tab` | entities or rules -| Up, Down | move through the list, and through the parts of the open rule -| Left, Right | slide the panel -| `Enter` | type a value for the selected condition or action; again for its next value -| `Esc` | put the value back -| `N` | a new rule -| `Delete` | delete the selected rule, or the selected condition or action -| `S`, `B` | save, build -|=== - -A typed number comes back a number rather than a string, because `100` and -`"100"` compile to different source and a description that changed shape when a -value was retyped would stop round-tripping. - -From a script, `forgeRuleNew(note)`, `forgeRuleAdd("when"|"act", -name)`, `forgePartSet(key, value)` and `forgePartDelete()` do the -same work. Adding a condition selects it, so its parameters can be set at once. - -The vocabulary comes from the `AUTHOR` manifest, so the rule editor never needs -changing when a condition or an action is added: it offers whatever is -declared. A new parameter gets a sensible starting value for its type, so a -rule compiles the moment it is made rather than only once every field is -filled in. - -A description survives the round trip: loading one, saving it and loading it -again compiles to the same game, byte for byte. The editor depends on that and -the test asserts it. - -==== Releasing a game - -`forgeExport(folder, name)` writes everything a finished game needs into a -directory of its own: the compiled script, a `games.dat` so the menu lists it, -the description it was built from so it can be opened again, and **a copy of -the runtime**, taken out of Forge. `--pack` turns that directory into a `.game` -like any other, and it runs on a machine that has never had Forge on it. - -Every built game finds its own directory to load that runtime from, using -`debug.getinfo` rather than `DIR`: `DIR` is the directory of the script the -engine was *launched* with, so a game reached by `dofile` -- a test, a -launcher, a preview -- would otherwise look beside the caller. - -The panel slides. Drag the tab on its outer edge and it moves across the -window, so an entity that lives underneath it -- a score readout at `12, 12` -does -- is never permanently out of reach. `forgePanelTo(x)` moves it from -a script, and `forgeOverPanel(x)` says whether a point is currently -covered. - -[#migrating] === Migrating from Singe 2.10 Singe 3.00 moved the sprite handle to the first argument of `spriteDraw`, diff --git a/src/decode.c b/src/decode.c index 9a2810f0a..997d28d92 100644 --- a/src/decode.c +++ b/src/decode.c @@ -477,30 +477,6 @@ SDL_Surface *decodeImage(const void *bytes, size_t size) { } -SDL_Surface *decodeImageIO(SDL_IOStream *io, bool closeio) { - SDL_Surface *surface = IMG_Load_IO(io, false); - void *bytes = NULL; - size_t size = 0; - - if (surface == NULL) { - // SDL_image could not read it. It may still be a TIFF, an OpenEXR, a JPEG 2000, an AVIF or - // any of the other pictures the bundled FFmpeg decodes for video. - if (SDL_SeekIO(io, 0, SDL_IO_SEEK_SET) >= 0) { - bytes = SDL_LoadFile_IO(io, &size, false); - if (bytes != NULL) { - surface = decodeImage(bytes, size); - SDL_free(bytes); - } - } - } - if (closeio) { - SDL_CloseIO(io); - } - - return surface; -} - - float *decodeImageFloat(const void *bytes, size_t size, int32_t *width, int32_t *height) { const AVPixFmtDescriptor *description = NULL; SourceT source; @@ -576,3 +552,28 @@ float *decodeImageFloat(const void *bytes, size_t size, int32_t *width, int32_t return out; } + + +SDL_Surface *decodeImageIO(SDL_IOStream *io, bool closeio) { + SDL_Surface *surface = IMG_Load_IO(io, false); + void *bytes = NULL; + size_t size = 0; + + if (surface == NULL) { + // SDL_image could not read it. It may still be a TIFF, an OpenEXR, a JPEG 2000, an AVIF or + // any of the other pictures the bundled FFmpeg decodes for video. + if (SDL_SeekIO(io, 0, SDL_IO_SEEK_SET) >= 0) { + bytes = SDL_LoadFile_IO(io, &size, false); + if (bytes != NULL) { + surface = decodeImage(bytes, size); + SDL_free(bytes); + } + } + } + if (closeio) { + SDL_CloseIO(io); + } + + return surface; +} + diff --git a/src/embedded.h b/src/embedded.h index aaa69a30f..7318e4e3a 100644 --- a/src/embedded.h +++ b/src/embedded.h @@ -36,6 +36,7 @@ #include "generated/Menu_singe.h" #include "generated/Tools_singe.h" #include "generated/Net_singe.h" +#include "generated/cacerts_pem.h" #include "generated/Master_singe.h" #include "generated/Backdrop_singe.h" #include "generated/MenuDocument_singe.h" diff --git a/src/guiRender.cpp b/src/guiRender.cpp index fb3438245..c2cb5f86b 100644 --- a/src/guiRender.cpp +++ b/src/guiRender.cpp @@ -819,9 +819,11 @@ Rml::TextureHandle GuiRenderT::LoadTexture(Rml::Vector2i &textureDimensions, con SDL_Surface *surface = nullptr; SDL_Surface *converted = nullptr; Rml::byte *pixels = nullptr; + Rml::byte *row = nullptr; size_t fileSize = 0; - size_t pixelSize = 0; - size_t i = 0; + size_t rowSize = 0; + size_t x = 0; + size_t y = 0; size_t j = 0; size_t dot = source.rfind('.'); Rml::String extension = (dot == Rml::String::npos) ? Rml::String() : source.substr(dot + 1); @@ -865,16 +867,23 @@ Rml::TextureHandle GuiRenderT::LoadTexture(Rml::Vector2i &textureDimensions, con } surface = converted; } - // Premultiplied alpha, which compositing needs. - pixelSize = (size_t)surface->w * (size_t)surface->h * BYTES_PER_PIXEL; - pixels = static_cast(surface->pixels); - for (i = 0; i < pixelSize; i += BYTES_PER_PIXEL) { - for (j = 0; j < COLOUR_CHANNELS; j++) { - pixels[i + j] = (Rml::byte)((int)pixels[i + j] * (int)pixels[i + COLOUR_CHANNELS] / COLOUR_MAX); + // Premultiplied alpha, which compositing needs. Rows are packed as they go, since the surface + // may pad its pitch and GenerateTexture wants the rows back to back. + rowSize = (size_t)surface->w * BYTES_PER_PIXEL; + pixels = static_cast(surface->pixels); + for (y = 0; y < (size_t)surface->h; y++) { + row = pixels + (y * (size_t)surface->pitch); + for (x = 0; x < rowSize; x += BYTES_PER_PIXEL) { + for (j = 0; j < COLOUR_CHANNELS; j++) { + row[x + j] = (Rml::byte)((int)row[x + j] * (int)row[x + COLOUR_CHANNELS] / COLOUR_MAX); + } + } + if (row != pixels + (y * rowSize)) { + memmove(pixels + (y * rowSize), row, rowSize); } } textureDimensions = { surface->w, surface->h }; - handle = GenerateTexture(Rml::Span(pixels, (size_t)surface->pitch * (size_t)surface->h), textureDimensions); + handle = GenerateTexture(Rml::Span(pixels, rowSize * (size_t)surface->h), textureDimensions); SDL_DestroySurface(surface); return handle; } diff --git a/src/main.c b/src/main.c index 3425a9706..198d01ddf 100644 --- a/src/main.c +++ b/src/main.c @@ -287,8 +287,8 @@ static int32_t _optionIndex(int32_t code); static int32_t _optionNamed(const char *name); static ConfigT *_parseArguments(const char *exeName, int32_t argc, char *argv[]); static bool _parseBoolean(const char *text, bool *value); -static bool _parseGamepadOrder(const char *text); static bool _parseFloat(const char *text, double *value); +static bool _parseGamepadOrder(const char *text); static bool _parseInteger(const char *text, int32_t *value); static void _requireRange(const char *exeName, const char *source, int32_t value, int32_t min, int32_t max, const char *what, const char *unit); static void _requireRangeFloat(const char *exeName, const char *source, double value, double min, double max, const char *what); @@ -950,98 +950,6 @@ static void _crashHandler(int signalNumber) { #endif -// Which audio formats this build can decode, for a bug report and so a user whose music is silent -// can see at a glance whether its format was ever compiled in. The mixer keeps the list. -char *mainDescribeAudioDecoders(void) { - char *list = strdup(""); - char *grown = NULL; - int32_t count = MIX_GetNumAudioDecoders(); - int32_t x = 0; - - for (x = 0; x < count; x++) { - grown = utilCreateString("%s%s%s", list, (x > 0) ? ", " : "", MIX_GetAudioDecoder(x)); - free(list); - list = grown; - } - - return list; -} - - -// The processor, for a bug report. Every platform keeps the name somewhere different, and none of -// them is worth failing over: the core count is always there as a fallback. -char *mainDescribeCpu(void) { -#ifdef _WIN32 - char name[128]; - DWORD bytes = sizeof(name); - - if (RegGetValueA(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", "ProcessorNameString", RRF_RT_REG_SZ, NULL, name, &bytes) == ERROR_SUCCESS) { - return utilCreateString("%s, %d cores, %d MB", name, SDL_GetNumLogicalCPUCores(), SDL_GetSystemRAM()); - } -#elif defined(__APPLE__) - char name[128]; - size_t bytes = sizeof(name); - - if (sysctlbyname("machdep.cpu.brand_string", name, &bytes, NULL, 0) == 0) { - return utilCreateString("%s, %d cores, %d MB", name, SDL_GetNumLogicalCPUCores(), SDL_GetSystemRAM()); - } -#else - char line[256]; - FILE *info = fopen("/proc/cpuinfo", "r"); - char *colon = NULL; - char *end = NULL; - char *found = NULL; - - // /proc/cpuinfo calls it "model name" on x86 and "Model" on a Raspberry Pi. The kernel - // reports its size as zero, so it is read a line at a time rather than in one piece. - while ((info != NULL) && (found == NULL) && (fgets(line, sizeof(line), info) != NULL)) { - if (utilStartsWith(line, "model name") || utilStartsWith(line, "Model")) { - colon = strchr(line, ':'); - if (colon != NULL) { - colon++; - while (*colon == ' ') { - colon++; - } - for (end = colon + strlen(colon); (end > colon) && ((uint8_t)end[-1] <= ' '); end--) { - end[-1] = 0; - } - found = utilCreateString("%s, %d cores, %d MB", colon, SDL_GetNumLogicalCPUCores(), SDL_GetSystemRAM()); - } - } - } - if (info != NULL) { - fclose(info); - } - if (found != NULL) { - return found; - } -#endif - - return utilCreateString("%s, %d cores, %d MB", SDL_GetPlatform(), SDL_GetNumLogicalCPUCores(), SDL_GetSystemRAM()); -} - - -// The operating system and its version, for a bug report. -char *mainDescribeOs(void) { -#ifdef _WIN32 - char release[64]; - DWORD bytes = sizeof(release); - - if (RegGetValueA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "CurrentBuild", RRF_RT_REG_SZ, NULL, release, &bytes) == ERROR_SUCCESS) { - return utilCreateString("%s build %s", SDL_GetPlatform(), release); - } -#else - struct utsname system; - - if (uname(&system) == 0) { - return utilCreateString("%s %s (%s)", system.sysname, system.release, system.machine); - } -#endif - - return strdup(SDL_GetPlatform()); -} - - // Writes an embedded support file, or rewrites it when the installed copy differs from this build's. static bool _extractFile(const char *filename, const uint8_t *data, size_t length) { FILE *out = NULL; @@ -1428,33 +1336,6 @@ static ConfigT *_parseArguments(const char *exeName, int32_t argc, char *argv[]) } -// --gamepad_reorder is a list of enumeration positions, one per gamepad slot, written as Hypseus -// writes it: bare digits (3210) or separated by commas or spaces (3, 2, 1, 0). A repeated position -// is the user contradicting themselves and is refused; anything but a digit or a separator is too. -static bool _parseGamepadOrder(const char *text) { - bool seen[GAMEPAD_ORDER_DIGITS]; - int32_t digits = 0; - int32_t x = 0; - - memset(seen, 0, sizeof(seen)); - for (x = 0; text[x] != '\0'; x++) { - if ((text[x] == ',') || (text[x] == ' ')) { - continue; - } - if ((text[x] < '0') || (text[x] > '9')) { - return false; - } - if (seen[text[x] - '0']) { - return false; - } - seen[text[x] - '0'] = true; - digits++; - } - - return (digits > 0); -} - - // The words a settings file (and --linearscale) may use for a switch. static bool _parseBoolean(const char *text, bool *value) { static const char *yes[] = { "true", "yes", "on", "1" }; @@ -1494,6 +1375,33 @@ static bool _parseFloat(const char *text, double *value) { } +// --gamepad_reorder is a list of enumeration positions, one per gamepad slot, written as Hypseus +// writes it: bare digits (3210) or separated by commas or spaces (3, 2, 1, 0). A repeated position +// is the user contradicting themselves and is refused; anything but a digit or a separator is too. +static bool _parseGamepadOrder(const char *text) { + bool seen[GAMEPAD_ORDER_DIGITS]; + int32_t digits = 0; + int32_t x = 0; + + memset(seen, 0, sizeof(seen)); + for (x = 0; text[x] != '\0'; x++) { + if ((text[x] == ',') || (text[x] == ' ')) { + continue; + } + if ((text[x] < '0') || (text[x] > '9')) { + return false; + } + if (seen[text[x] - '0']) { + return false; + } + seen[text[x] - '0'] = true; + digits++; + } + + return (digits > 0); +} + + static bool _parseInteger(const char *text, int32_t *value) { char *end = NULL; long parsed = 0; @@ -1764,6 +1672,29 @@ static void _stopSDL(void) { } +// Reduces every component that names a packed game to its stem, in place. A game writes to one +// data directory whether it was reached as the container the engine opened ("ActionMax.game", which +// createDataDirFor already reduces) or by a name that passes through it ("ActionMax.game/video.mkv", +// which is how a script names a file inside one). Without this the two disagreed: a packed game's +// video index went to data/ActionMax.game while everything else it wrote went to data/ActionMax, +// and the library appeared to have two of every game. +static void _stripDatabaseNames(char *path) { + const size_t extension = strlen(VFS_DATABASE_EXTENSION); + size_t i = 0; + char next = 0; + + for (i = 0; path[i] != 0; i++) { + if (strncasecmp(path + i, VFS_DATABASE_EXTENSION, extension) != 0) { + continue; + } + next = path[i + extension]; + if ((next == 0) || (next == '/') || (next == '\\')) { + memmove(path + i, path + i + extension, strlen(path + i + extension) + 1); + } + } +} + + // What a bug report needs, at the top of trace.txt and nowhere else: the build, the command that // started it, the machine, and what the engine picked to run on. main.c's own trace lines wait // behind it so the block is always first and can be pasted whole. @@ -1810,6 +1741,7 @@ static void _unpackData(const char *exePath, bool absolute) { { "Menu.singe", Menu_singe, Menu_singe_len }, { "Tools.singe", Tools_singe, Tools_singe_len }, { "Net.singe", Net_singe, Net_singe_len }, + { "cacerts.pem", cacerts_pem, cacerts_pem_len }, { "Master.singe", Master_singe, Master_singe_len }, { "Backdrop.singe", Backdrop_singe, Backdrop_singe_len }, { "MenuDocument.singe", MenuDocument_singe, MenuDocument_singe_len }, @@ -1909,29 +1841,6 @@ ConfigT *cloneConf(const ConfigT *conf) { } -// Reduces every component that names a packed game to its stem, in place. A game writes to one -// data directory whether it was reached as the container the engine opened ("ActionMax.game", which -// createDataDirFor already reduces) or by a name that passes through it ("ActionMax.game/video.mkv", -// which is how a script names a file inside one). Without this the two disagreed: a packed game's -// video index went to data/ActionMax.game while everything else it wrote went to data/ActionMax, -// and the library appeared to have two of every game. -static void _stripDatabaseNames(char *path) { - const size_t extension = strlen(VFS_DATABASE_EXTENSION); - size_t i = 0; - char next = 0; - - for (i = 0; path[i] != 0; i++) { - if (strncasecmp(path + i, VFS_DATABASE_EXTENSION, extension) != 0) { - continue; - } - next = path[i + extension]; - if ((next == 0) || (next == '/') || (next == '\\')) { - memmove(path + i, path + i + extension, strlen(path + i + extension) + 1); - } - } -} - - // Builds and creates dataDirBase + directory of filename. Returns a new string or NULL on failure. char *createDataDir(const char *dataDirBase, const char *filename) { const char separator = utilGetPathSeparator(); @@ -2020,6 +1929,98 @@ bool isFrameFileName(const char *filename) { } +// Which audio formats this build can decode, for a bug report and so a user whose music is silent +// can see at a glance whether its format was ever compiled in. The mixer keeps the list. +char *mainDescribeAudioDecoders(void) { + char *list = strdup(""); + char *grown = NULL; + int32_t count = MIX_GetNumAudioDecoders(); + int32_t x = 0; + + for (x = 0; x < count; x++) { + grown = utilCreateString("%s%s%s", list, (x > 0) ? ", " : "", MIX_GetAudioDecoder(x)); + free(list); + list = grown; + } + + return list; +} + + +// The processor, for a bug report. Every platform keeps the name somewhere different, and none of +// them is worth failing over: the core count is always there as a fallback. +char *mainDescribeCpu(void) { +#ifdef _WIN32 + char name[128]; + DWORD bytes = sizeof(name); + + if (RegGetValueA(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", "ProcessorNameString", RRF_RT_REG_SZ, NULL, name, &bytes) == ERROR_SUCCESS) { + return utilCreateString("%s, %d cores, %d MB", name, SDL_GetNumLogicalCPUCores(), SDL_GetSystemRAM()); + } +#elif defined(__APPLE__) + char name[128]; + size_t bytes = sizeof(name); + + if (sysctlbyname("machdep.cpu.brand_string", name, &bytes, NULL, 0) == 0) { + return utilCreateString("%s, %d cores, %d MB", name, SDL_GetNumLogicalCPUCores(), SDL_GetSystemRAM()); + } +#else + char line[256]; + FILE *info = fopen("/proc/cpuinfo", "r"); + char *colon = NULL; + char *end = NULL; + char *found = NULL; + + // /proc/cpuinfo calls it "model name" on x86 and "Model" on a Raspberry Pi. The kernel + // reports its size as zero, so it is read a line at a time rather than in one piece. + while ((info != NULL) && (found == NULL) && (fgets(line, sizeof(line), info) != NULL)) { + if (utilStartsWith(line, "model name") || utilStartsWith(line, "Model")) { + colon = strchr(line, ':'); + if (colon != NULL) { + colon++; + while (*colon == ' ') { + colon++; + } + for (end = colon + strlen(colon); (end > colon) && ((uint8_t)end[-1] <= ' '); end--) { + end[-1] = 0; + } + found = utilCreateString("%s, %d cores, %d MB", colon, SDL_GetNumLogicalCPUCores(), SDL_GetSystemRAM()); + } + } + } + if (info != NULL) { + fclose(info); + } + if (found != NULL) { + return found; + } +#endif + + return utilCreateString("%s, %d cores, %d MB", SDL_GetPlatform(), SDL_GetNumLogicalCPUCores(), SDL_GetSystemRAM()); +} + + +// The operating system and its version, for a bug report. +char *mainDescribeOs(void) { +#ifdef _WIN32 + char release[64]; + DWORD bytes = sizeof(release); + + if (RegGetValueA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "CurrentBuild", RRF_RT_REG_SZ, NULL, release, &bytes) == ERROR_SUCCESS) { + return utilCreateString("%s build %s", SDL_GetPlatform(), release); + } +#else + struct utsname system; + + if (uname(&system) == 0) { + return utilCreateString("%s %s (%s)", system.sysname, system.release, system.machine); + } +#endif + + return strdup(SDL_GetPlatform()); +} + + // Ok, this thing can have a mess of different arguments: // WW - Just the width of the white border // WW WB - Width of white border and then black border diff --git a/src/midi.c b/src/midi.c index 39a03e690..516f357dc 100644 --- a/src/midi.c +++ b/src/midi.c @@ -152,6 +152,16 @@ bool midiIs(const void *bytes, size_t size) { } +void midiQuit(void) { + if (_soundfont != NULL) { + tsf_close(_soundfont); + _soundfont = NULL; + } + free(_found); + _found = NULL; +} + + bool midiRender(const void *bytes, size_t size, DecodedAudioT *out) { tml_message *messages = NULL; tml_message *message = NULL; @@ -212,16 +222,6 @@ bool midiRender(const void *bytes, size_t size, DecodedAudioT *out) { } -void midiQuit(void) { - if (_soundfont != NULL) { - tsf_close(_soundfont); - _soundfont = NULL; - } - free(_found); - _found = NULL; -} - - const char *midiSoundfont(void) { return (_found != NULL) ? _found : "none found (MIDI will not play; name one with --soundfont)"; } diff --git a/src/midiIo.c b/src/midiIo.c index b3e79ab13..c09e33dfc 100644 --- a/src/midiIo.c +++ b/src/midiIo.c @@ -97,14 +97,14 @@ static void _receive(void); #define ALSA_PORT "Singe MIDI" #define ALSA_ADDRESS "%d:%d" -static void *_alsa = NULL; -static snd_seq_t *_seq = NULL; -static snd_midi_event_t *_codec = NULL; -static int32_t _seqPort = -1; -static int32_t _inClient = -1; -static int32_t _inPort = -1; -static int32_t _outClient = -1; -static int32_t _outPort = -1; +static void *_alsa = NULL; +static snd_seq_t *_seq = NULL; +static snd_midi_event_t *_codec = NULL; +static int32_t _seqPort = -1; +static int32_t _inClient = -1; +static int32_t _inPort = -1; +static int32_t _outClient = -1; +static int32_t _outPort = -1; static int (*_seqOpen)(snd_seq_t **, const char *, int, int); static int (*_seqClose)(snd_seq_t *); diff --git a/src/persist.c b/src/persist.c index 9e9142f96..d21a87e75 100644 --- a/src/persist.c +++ b/src/persist.c @@ -116,9 +116,12 @@ bool persistWrite(const char *text, size_t length) { return false; } fclose(file); - // Windows will not rename onto an existing file, so the old one goes first. The window between - // the two is the one risk left, and it is smaller than writing in place. + // Windows will not rename onto an existing file, so the old one goes first there. The window + // between the two is the one risk left, and it is smaller than writing in place. Everywhere + // else the rename is atomic and the old save stays whole until the new one is in place. +#ifdef _WIN32 remove(_path); +#endif if (rename(_temporary, _path) != 0) { remove(_temporary); return false; diff --git a/src/render.c b/src/render.c index b7de22a2b..d614aaf60 100644 --- a/src/render.c +++ b/src/render.c @@ -68,6 +68,14 @@ void renderEnd(void) { } +void renderSelect(RenderApiT api, const RenderBackendT *backend, SDL_Renderer *renderer) { + _api = backend != NULL ? api : RENDER_NONE; + _backend = backend; + _renderer = renderer; + utilTrace("Render: %s", renderApiName()); +} + + SDL_GPUTexture *renderTextureFor(SDL_Texture *texture) { if (texture == NULL) { return NULL; @@ -112,14 +120,6 @@ SDL_Texture *renderWrapTexture(SDL_Renderer *renderer, SDL_GPUTexture *texture, } -void renderSelect(RenderApiT api, const RenderBackendT *backend, SDL_Renderer *renderer) { - _api = backend != NULL ? api : RENDER_NONE; - _backend = backend; - _renderer = renderer; - utilTrace("Render: %s", renderApiName()); -} - - // Every run of rgpu* drawing sits between acquiring a command buffer and submitting it, so that is // where the context is taken and given back. Bracketing here rather than at the callers means diff --git a/src/renderGles.c b/src/renderGles.c index 7d3e26a74..3a33e2791 100644 --- a/src/renderGles.c +++ b/src/renderGles.c @@ -159,9 +159,9 @@ typedef struct GlesDeviceS { static GlesDeviceT _device; +static GLenum _addressMode(SDL_GPUSamplerAddressMode mode); static void _applyVertexLayout(GlesRenderPassT *pass); static void _bindTextureUnit(uint32_t unit, GlesTextureT *texture, SDL_GPUSampler *sampler); -static GLenum _addressMode(SDL_GPUSamplerAddressMode mode); static GLenum _blendFactor(SDL_GPUBlendFactor factor); static GLenum _blendOp(SDL_GPUBlendOp op); static GLenum _compare(SDL_GPUCompareOp op); @@ -214,8 +214,8 @@ static void _glesUnmapTransferBuffer(SDL_GPUDevice *device, static void _glesUploadToBuffer(SDL_GPUCopyPass *copyPass, const SDL_GPUTransferBufferLocation *source, const SDL_GPUBufferRegion *destination, bool cycle); static void _glesUploadToTexture(SDL_GPUCopyPass *copyPass, const SDL_GPUTextureTransferInfo *source, const SDL_GPUTextureRegion *destination, bool cycle); static GLenum _primitive(SDL_GPUPrimitiveType type); -static GLuint _scratchFramebuffer(GLenum binding, GlesTextureT *texture, uint32_t level); static void _remapFragmentBlocks(GLuint program, const char *fragmentSource); +static GLuint _scratchFramebuffer(GLenum binding, GlesTextureT *texture, uint32_t level); static void _setPipelineState(GlesPipelineT *pipeline); static GLenum _stencilOp(SDL_GPUStencilOp op); static void _vertexFormat(SDL_GPUVertexElementFormat format, GLint *size, GLenum *type, GLboolean *normalized); @@ -224,6 +224,56 @@ void renderGlesRestore(void); bool renderGlesStart(void); +static GLenum _addressMode(SDL_GPUSamplerAddressMode mode) { + switch (mode) { + case SDL_GPU_SAMPLERADDRESSMODE_REPEAT: return GL_REPEAT; + case SDL_GPU_SAMPLERADDRESSMODE_MIRRORED_REPEAT: return GL_MIRRORED_REPEAT; + default: return GL_CLAMP_TO_EDGE; + } +} + + +static void _applyVertexLayout(GlesRenderPassT *pass) { + GlesPipelineT *pipeline = pass->pipeline; + uint32_t i; + uint32_t a; + + if ((pipeline == NULL) || !pass->vertexDirty) { + return; + } + pass->vertexDirty = false; + for (i = 0; i < pass->vertexCount; i++) { + GlesBufferT *buffer = pass->vertexBuffer[i]; + uint32_t slot = i; + + if (buffer == NULL) { + continue; + } + glBindBuffer(GL_ARRAY_BUFFER, buffer->name); + for (a = 0; a < pipeline->attributeCount; a++) { + const SDL_GPUVertexAttribute *attribute = &pipeline->attributes[a]; + GLboolean normalized; + GLenum type; + GLint size; + + if (attribute->buffer_slot != slot) { + continue; + } + _vertexFormat(attribute->format, &size, &type, &normalized); + glEnableVertexAttribArray(attribute->location); + // An integer attribute must be fed as an integer: the scene's joint indices are uvec4 in + // the shader, and handing them over as floats is a link-time type mismatch, not a value + // one, so the draw is rejected rather than merely wrong. + if (((type == GL_UNSIGNED_BYTE) && (normalized == GL_FALSE)) || (type == GL_INT) || (type == GL_UNSIGNED_INT)) { + glVertexAttribIPointer(attribute->location, size, type, (GLsizei)pipeline->strides[slot], (const void *)(uintptr_t)(pass->vertexOffset[slot] + attribute->offset)); + } else { + glVertexAttribPointer(attribute->location, size, type, normalized, (GLsizei)pipeline->strides[slot], (const void *)(uintptr_t)(pass->vertexOffset[slot] + attribute->offset)); + } + } + } +} + + static void _bindTextureUnit(uint32_t unit, GlesTextureT *texture, SDL_GPUSampler *sampler) { GLuint name = (GLuint)(uintptr_t)sampler; @@ -237,15 +287,6 @@ static void _bindTextureUnit(uint32_t unit, GlesTextureT *texture, SDL_GPUSample } -static GLenum _addressMode(SDL_GPUSamplerAddressMode mode) { - switch (mode) { - case SDL_GPU_SAMPLERADDRESSMODE_REPEAT: return GL_REPEAT; - case SDL_GPU_SAMPLERADDRESSMODE_MIRRORED_REPEAT: return GL_MIRRORED_REPEAT; - default: return GL_CLAMP_TO_EDGE; - } -} - - static GLenum _blendFactor(SDL_GPUBlendFactor factor) { switch (factor) { case SDL_GPU_BLENDFACTOR_ZERO: return GL_ZERO; @@ -398,6 +439,9 @@ static GLuint _framebufferFor(const SDL_GPUColorTargetInfo *colour, uint32_t col // The cache is a fixed size on purpose; overflowing it means a target set is being rebuilt // every frame, which is a bug worth hearing about rather than hiding behind a bigger array. utilTrace("Gles: framebuffer cache full"); + for (index = 0; index < _device.framebufferCount; index++) { + glDeleteFramebuffers(1, &_device.framebuffers[index].name); + } _device.framebufferCount = 0; } slot = &_device.framebuffers[_device.framebufferCount]; @@ -586,47 +630,6 @@ static void _glesBindVertexBuffers(SDL_GPURenderPass *renderPass, Uint32 firstSl } -static void _applyVertexLayout(GlesRenderPassT *pass) { - GlesPipelineT *pipeline = pass->pipeline; - uint32_t i; - uint32_t a; - - if ((pipeline == NULL) || !pass->vertexDirty) { - return; - } - pass->vertexDirty = false; - for (i = 0; i < pass->vertexCount; i++) { - GlesBufferT *buffer = pass->vertexBuffer[i]; - uint32_t slot = i; - - if (buffer == NULL) { - continue; - } - glBindBuffer(GL_ARRAY_BUFFER, buffer->name); - for (a = 0; a < pipeline->attributeCount; a++) { - const SDL_GPUVertexAttribute *attribute = &pipeline->attributes[a]; - GLboolean normalized; - GLenum type; - GLint size; - - if (attribute->buffer_slot != slot) { - continue; - } - _vertexFormat(attribute->format, &size, &type, &normalized); - glEnableVertexAttribArray(attribute->location); - // An integer attribute must be fed as an integer: the scene's joint indices are uvec4 in - // the shader, and handing them over as floats is a link-time type mismatch, not a value - // one, so the draw is rejected rather than merely wrong. - if (((type == GL_UNSIGNED_BYTE) && (normalized == GL_FALSE)) || (type == GL_INT) || (type == GL_UNSIGNED_INT)) { - glVertexAttribIPointer(attribute->location, size, type, (GLsizei)pipeline->strides[slot], (const void *)(uintptr_t)(pass->vertexOffset[slot] + attribute->offset)); - } else { - glVertexAttribPointer(attribute->location, size, type, normalized, (GLsizei)pipeline->strides[slot], (const void *)(uintptr_t)(pass->vertexOffset[slot] + attribute->offset)); - } - } - } -} - - static void _glesBindVertexStorageBuffers(SDL_GPURenderPass *renderPass, Uint32 firstSlot, SDL_GPUBuffer *const *storageBuffers, Uint32 numBindings) { uint32_t i; diff --git a/src/scene.c b/src/scene.c index 990f9f653..22b49727b 100644 --- a/src/scene.c +++ b/src/scene.c @@ -3978,18 +3978,6 @@ bool materialSetBlend(int32_t material, bool blend) { } -// glTF's alpha masking: a texel whose base colour alpha falls below the cutoff is discarded, in -// the lit pass and in the shadow pass alike. Zero turns masking off, which is the default and what -// every material that never asked for it keeps. -bool materialSetCutoff(int32_t material, float cutoff) { - if (!materialValid(material)) { - return false; - } - _scene.materials[material].cutoff = SDL_clamp(cutoff, 0.0f, 1.0f); - return true; -} - - bool materialSetColor(int32_t material, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { if (!materialValid(material)) { return false; @@ -4015,6 +4003,18 @@ bool materialSetColorLinear(int32_t material, float r, float g, float b, float a } +// glTF's alpha masking: a texel whose base colour alpha falls below the cutoff is discarded, in +// the lit pass and in the shadow pass alike. Zero turns masking off, which is the default and what +// every material that never asked for it keeps. +bool materialSetCutoff(int32_t material, float cutoff) { + if (!materialValid(material)) { + return false; + } + _scene.materials[material].cutoff = SDL_clamp(cutoff, 0.0f, 1.0f); + return true; +} + + bool materialSetDoubleSided(int32_t material, bool doubleSided) { if (!materialValid(material)) { return false; diff --git a/src/scheduler.c b/src/scheduler.c index a98ca2848..c9292fcc3 100644 --- a/src/scheduler.c +++ b/src/scheduler.c @@ -31,9 +31,11 @@ #include "util.h" #define SLOTS_FIRST 16 // Grown by doubling from here +#define SLOT_BITS 16 // A handle is the slot in its low bits and a generation above them: +#define SLOT_MASK 0xFFFF // a slot that is freed and given out again gets a new handle, so a +#define GENERATION_MASK 0x7FFF // stale handle a script kept cannot cancel or ask after the newcomer. #define BACK_OVERSHOOT 1.70158 // How far past the target the "back" easings go, the usual constant #define ELASTIC_PERIOD 0.3 -#define ELASTIC_AMPLITUDE 0.1 #define BOUNCE_SCALE 7.5625 // The bounce curve's four arcs, as everyone writes them #define BOUNCE_SPLIT 2.75 #define HALF 0.5 @@ -47,6 +49,7 @@ typedef struct { double from; double to; SchedulerEaseE easing; + int32_t generation; // Stepped every time the slot is given out bool repeating; bool used; } SlotT; @@ -60,24 +63,33 @@ static int32_t _capacity = 0; static int32_t _allocate(void); static double _ease(SchedulerEaseE easing, double t); static double _easeBounceOut(double t); +static int32_t _handleOf(int32_t slot); +static SlotT *_slotOf(int32_t handle); // ===== Internal helpers ===== -// The first free slot, growing the table when there is none. Handles are indexes, so a slot never -// moves while anything might still name it. +// The first free slot, growing the table when there is none. A slot never moves while anything +// might still name it, and each time one is handed out again its generation steps, so the handle +// a script was given for the old occupant names nothing once that occupant is gone. static int32_t _allocate(void) { - SlotT *grown = NULL; - int32_t want = 0; - int32_t x = 0; + SlotT *grown = NULL; + int32_t want = 0; + int32_t x = 0; + int32_t generation = 0; for (x = 0; x < _count; x++) { if (!_slots[x].used) { + generation = (_slots[x].generation + 1) & GENERATION_MASK; memset(&_slots[x], 0, sizeof(_slots[x])); - _slots[x].used = true; + _slots[x].generation = generation; + _slots[x].used = true; return x; } } + if (_count > SLOT_MASK) { + utilDie("Too many timers and tweens are alive at once."); + } if (_count == _capacity) { want = (_capacity == 0) ? SLOTS_FIRST : (_capacity * 2); grown = (SlotT *)realloc(_slots, sizeof(SlotT) * (size_t)want); @@ -155,13 +167,33 @@ static double _easeBounceOut(double t) { } +// The handle a script is given for a slot: the slot's index under its generation. +static int32_t _handleOf(int32_t slot) { + return (_slots[slot].generation << SLOT_BITS) | slot; +} + + +// The slot a handle names, or NULL when it names nothing: out of range, free, or an earlier +// occupant of a slot that has since been given to something else. +static SlotT *_slotOf(int32_t handle) { + int32_t slot = handle & SLOT_MASK; + + if ((handle < 0) || (slot >= _count) || !_slots[slot].used || (_handleOf(slot) != handle)) { + return NULL; + } + + return &_slots[slot]; +} + + // ===== Public ===== void schedulerCancel(int32_t handle) { - if ((handle < 0) || (handle >= _count)) { - return; + SlotT *slot = _slotOf(handle); + + if (slot != NULL) { + slot->used = false; } - _slots[handle].used = false; } @@ -180,11 +212,7 @@ int32_t schedulerCount(void) { bool schedulerIsActive(int32_t handle) { - if ((handle < 0) || (handle >= _count)) { - return false; - } - - return _slots[handle].used; + return _slotOf(handle) != NULL; } @@ -197,8 +225,8 @@ void schedulerReset(void) { int32_t schedulerTimer(int64_t milliseconds, bool repeating, uint64_t now) { - int32_t handle = _allocate(); - SlotT *slot = &_slots[handle]; + int32_t index = _allocate(); + SlotT *slot = &_slots[index]; if (milliseconds < 0) { milliseconds = 0; @@ -208,13 +236,13 @@ int32_t schedulerTimer(int64_t milliseconds, bool repeating, uint64_t now) { slot->due = now + (uint64_t)milliseconds; slot->repeating = repeating; - return handle; + return _handleOf(index); } int32_t schedulerTween(double from, double to, int64_t milliseconds, SchedulerEaseE easing, uint64_t now) { - int32_t handle = _allocate(); - SlotT *slot = &_slots[handle]; + int32_t index = _allocate(); + SlotT *slot = &_slots[index]; if (milliseconds < 0) { milliseconds = 0; @@ -230,7 +258,7 @@ int32_t schedulerTween(double from, double to, int64_t milliseconds, SchedulerEa slot->to = to; slot->easing = easing; - return handle; + return _handleOf(index); } @@ -260,7 +288,7 @@ void schedulerUpdate(uint64_t now, SchedulerFireT fire, void *context) { slot->used = false; } if (fire != NULL) { - fire(context, x, SCHEDULER_TIMER, 0, 0, !slot->repeating); + fire(context, _handleOf(x), SCHEDULER_TIMER, 0, 0, !slot->repeating); } continue; } @@ -274,7 +302,7 @@ void schedulerUpdate(uint64_t now, SchedulerFireT fire, void *context) { slot->used = false; } if (fire != NULL) { - fire(context, x, SCHEDULER_TWEEN, value, progress, finished); + fire(context, _handleOf(x), SCHEDULER_TWEEN, value, progress, finished); } } } diff --git a/src/singe.c b/src/singe.c index 1db5f0eee..dd14d6ded 100644 --- a/src/singe.c +++ b/src/singe.c @@ -760,12 +760,14 @@ static int32_t _axisIndexForCode(int32_t code); static void _bezelDraw(void); static void _bezelLoad(void); static bool _bezelReadCutout(const char *path, int32_t width, int32_t height, SDL_FRect *cutout); -static ConfigT *_buildConfFromTable(lua_State *L, const ConfigT *base); +static ConfigT *_buildConfFromTable(lua_State *L, const ConfigT *base, const char *method); static void _callLua(const char *func, const char *sig, ...); static bool _clipLine(int32_t *x1, int32_t *y1, int32_t *x2, int32_t *y2); static void _computeVideoRect(void); +static void _confFail(lua_State *L, const char *method, const char *fmt, ...) __attribute__((format(printf, 3, 4))) __attribute__((noreturn)); static void _configPlaces(const char *file, const char *dataDir, const char *scriptFile, char **places); static int32_t _controllerSlot(SDL_JoystickID which); +static void _createScriptContext(void); static int32_t _defaultVolume(int32_t maximum); static bool _delayAndPump(uint32_t ms); static void _deliverKey(bool down, int32_t keysym, int32_t scancode); @@ -844,6 +846,7 @@ static bool _luaFileReadNumber(lua_State *L, LuaFileT *file); static void _luaFileRegister(lua_State *L); static int32_t _luaFileSearcher(lua_State *L); static int32_t _luaFileSeek(lua_State *L); +static void _luaFileShut(LuaFileT *file); static LuaFileT *_luaFileTest(lua_State *L, int32_t index); static int32_t _luaFileToString(lua_State *L); static void _luaFileUngetc(LuaFileT *file); @@ -851,8 +854,8 @@ static int32_t _luaFileWrite(lua_State *L); static char *_luaFormat(lua_State *L, const char *method, const char *fmt, va_list args); static int32_t _luaIoClose(lua_State *L); static void _luaIoForgetInput(lua_State *L); -static LuaFileT *_luaIoInput(lua_State *L); static int32_t _luaIoHook(lua_State *L); +static LuaFileT *_luaIoInput(lua_State *L); static int32_t _luaIoRead(lua_State *L); static int32_t _luaIoSetInput(lua_State *L); static int32_t _luaIoType(lua_State *L); @@ -868,29 +871,18 @@ static void _mapPointer(float px, float py, int32_t *x, int32_t *y); static void _mapPointerDelta(float dx, float dy, int32_t *xr, int32_t *yr); static void _mapUnknownJoysticks(void); static int32_t _materialSetMap(lua_State *L, const char *method, MaterialMapE map, bool hasStrength); -static float _mixerGain(int32_t volume, int32_t maximum); -static int32_t _mouseCode(int32_t device, int32_t button); -static void _musicDestroy(MusicT *music); -static bool _saveCjson(lua_State *L, const char *name); -static void _saveMachineSettings(int32_t milliseconds); -static void _saveCopy(lua_State *L, int32_t index, const char *method, int32_t depth); -static void _saveNormalise(lua_State *L, int32_t depth); -static void _saveCheckValue(lua_State *L, const char *method, int32_t index); -static void _saveTable(lua_State *L); -static void _saveWrite(void); -static void _scheduleFired(void *context, int32_t handle, SchedulerKindE kind, double value, double progress, bool finished); -static void _scheduleRemember(lua_State *L, int32_t handle, int32_t updateIndex, int32_t doneIndex); -static void _scheduleReset(lua_State *L); static uint8_t _midiChannel(lua_State *L, const char *method, int32_t index); static uint8_t _midiData(lua_State *L, const char *method, int32_t index); static void _midiReceived(void *context, const uint8_t *bytes, size_t size); +static float _mixerGain(int32_t volume, int32_t maximum); +static int32_t _mouseCode(int32_t device, int32_t button); +static void _musicDestroy(MusicT *music); static void _navCallbacks(void); -static void _statsDraw(void); static void _noteInput(void); static void _overlayApplyOpacity(void); +static void _overlayApplyScaleMode(void); static uint32_t _overlayColor(const SDL_Color *color); static SDL_Surface *_overlayGrey(void); -static void _overlayApplyScaleMode(void); static void _overlayResize(int32_t width, int32_t height); static void _overlayTouched(void); static void _particleTexturesDestroy(ParticleTexturesT *cache); @@ -916,7 +908,17 @@ static void _resetScriptState(void); static void _rmlEscape(char *out, size_t size, const char *text); static void _runScript(bool fatal); static bool _sameLoosePath(const char *a, const char *b); +static void _saveCheckValue(lua_State *L, const char *method, int32_t index); +static bool _saveCjson(lua_State *L, const char *name); +static void _saveCopy(lua_State *L, int32_t index, const char *method, int32_t depth); +static void _saveMachineSettings(int32_t milliseconds); +static void _saveNormalise(lua_State *L, int32_t depth); +static void _saveTable(lua_State *L); +static void _saveWrite(void); static SDL_Texture *_sceneVideoSource(int32_t player); +static void _scheduleFired(void *context, int32_t handle, SchedulerKindE kind, double value, double progress, bool finished); +static void _scheduleRemember(lua_State *L, int32_t handle, int32_t updateIndex, int32_t doneIndex); +static void _scheduleReset(lua_State *L); static void _scorePanelDigits(char *text, size_t size, int32_t value, int32_t digits, bool blank); static bool _scorePanelEnable(bool enabled); static int32_t _scorePanelPlayer(int32_t player); @@ -947,6 +949,7 @@ static void _spriteRegister(SpriteT *sprite); static const char *_startAudioSuffix(void); static void _startControllers(void); static void _startLuaContext(lua_State *L); +static void _statsDraw(void); static void _stopControllers(void); static void _subsystemsInit(void); static void _subsystemsQuit(void); @@ -2114,7 +2117,7 @@ static bool _bezelReadCutout(const char *path, int32_t width, int32_t height, SD // Builds a config for scriptExecute/scriptPush from the games.dat style table at stack index 1. -static ConfigT *_buildConfFromTable(lua_State *L, const ConfigT *base) { +static ConfigT *_buildConfFromTable(lua_State *L, const ConfigT *base, const char *method) { const char *confKey = NULL; const char *valueString = NULL; bool valueBoolean = false; @@ -2166,7 +2169,7 @@ static ConfigT *_buildConfFromTable(lua_State *L, const ConfigT *base) { // Update config with new data if (strcmp(confKey, "SCRIPT") == 0) { if (valueString == NULL) { - utilDie("SCRIPT must be a string."); + _confFail(L, method, "SCRIPT must be a string."); } free(c->scriptFile); c->scriptFile = strdup(valueString); @@ -2175,20 +2178,20 @@ static ConfigT *_buildConfFromTable(lua_State *L, const ConfigT *base) { // The master service's id for this game. Nothing in the engine uses it; it is carried // so a script can ask singeGetGameId() which board its scores belong on. if (valueString == NULL) { - utilDie("GAME_ID must be a string."); + _confFail(L, method, "GAME_ID must be a string."); } free(c->gameId); c->gameId = strdup(valueString); } else if (strcmp(confKey, "CONTAINER") == 0) { if (valueString == NULL) { - utilDie("CONTAINER must be a string."); + _confFail(L, method, "CONTAINER must be a string."); } free(c->container); c->container = strdup(valueString); utilFixPathSeparators(&c->container, false); } else if (strcmp(confKey, "VIDEO") == 0) { if (valueString == NULL) { - utilDie("VIDEO must be a string."); + _confFail(L, method, "VIDEO must be a string."); } free(c->videoFile); c->videoFile = strdup(valueString); @@ -2214,7 +2217,7 @@ static ConfigT *_buildConfFromTable(lua_State *L, const ConfigT *base) { c->audioDelayMs = (int32_t)valueNumber; } else if (strcmp(confKey, "AUDIO_SUFFIX") == 0) { if (valueString == NULL) { - utilDie("AUDIO_SUFFIX must be a string."); + _confFail(L, method, "AUDIO_SUFFIX must be a string."); } free(c->audioSuffix); c->audioSuffix = strdup(valueString); @@ -2237,7 +2240,7 @@ static ConfigT *_buildConfFromTable(lua_State *L, const ConfigT *base) { } c->disc = (c->videoFile != NULL); if ((c->canvasWidth <= 0) || (c->canvasHeight <= 0)) { - utilDie("%s: CANVAS_X and CANVAS_Y must be positive.", c->scriptFile); + _confFail(L, method, "%s: CANVAS_X and CANVAS_Y must be positive.", c->scriptFile); } return c; @@ -2483,6 +2486,22 @@ static void _computeVideoRect(void) { } +// A bad games.dat value. From a script it is reported as an API argument error, with the line and +// the call named; at startup, before there is a script, it ends the run with the message alone. +static void _confFail(lua_State *L, const char *method, const char *fmt, ...) { + char message[256]; + va_list args; + + va_start(args, fmt); + vsnprintf(message, sizeof(message), fmt, args); + va_end(args); + if (method != NULL) { + _luaDie(L, method, "%s", message); + } + utilDie("%s", message); +} + + // Maps an SDL joystick instance ID to our controller slot, or -1. // The four places a user configuration file is read from, in the order they are applied: the // working directory, above the data directory, inside it, and beside the game's script. A place @@ -4170,13 +4189,7 @@ static int32_t _luaDofile(lua_State *L) { static int32_t _luaFileClose(lua_State *L) { - LuaFileT *file = (LuaFileT *)luaL_checkudata(L, 1, LUA_VFS_FILE); - - if (!file->closed) { - vfsStreamClose(file->stream); - file->stream = NULL; - file->closed = true; - } + _luaFileShut((LuaFileT *)luaL_checkudata(L, 1, LUA_VFS_FILE)); lua_pushboolean(L, 1); return 1; @@ -4184,13 +4197,7 @@ static int32_t _luaFileClose(lua_State *L) { static int32_t _luaFileGc(lua_State *L) { - LuaFileT *file = (LuaFileT *)luaL_checkudata(L, 1, LUA_VFS_FILE); - - if (!file->closed) { - vfsStreamClose(file->stream); - file->stream = NULL; - file->closed = true; - } + _luaFileShut((LuaFileT *)luaL_checkudata(L, 1, LUA_VFS_FILE)); return 0; } @@ -4224,28 +4231,35 @@ static int32_t _luaFileIterate(lua_State *L) { if (file->closed) { return luaL_error(L, "file is already closed"); } + // Whatever the generic for passed in is not wanted; the results build up from an empty stack. + lua_settop(L, 0); if (formats == 0) { if (_luaFileReadLine(L, file, false)) { return 1; } } else { + // Each format is pushed, read from where it sits, and then taken out from under its value, + // so the values are left in order and nothing read earlier is lost to a later format. for (i = 0; i < formats; i++) { lua_pushvalue(L, lua_upvalueindex(4 + i)); - lua_replace(L, 1); - lua_settop(L, 1); - if (!_luaFileReadFormat(L, file, 1)) { - lua_settop(L, 0); + if (!_luaFileReadFormat(L, file, lua_gettop(L))) { + lua_pop(L, 1); break; } + lua_remove(L, -2); } + // As Lua does: only the first format coming up empty ends the loop. A later one hands + // back what was read before it, with nil in its place. if (i == formats) { return formats; } + if (i > 0) { + lua_pushnil(L); + return i + 1; + } } if (closing) { - vfsStreamClose(file->stream); - file->stream = NULL; - file->closed = true; + _luaFileShut(file); } lua_pushnil(L); @@ -4342,15 +4356,22 @@ static int32_t _luaFileRead(lua_State *L) { // Reads until the end and pushes what there was. Always a string, empty at the end of the file, -// which is what Lua's "a" does. +// which is what Lua's "a" does. What the buffer already holds goes first, and the rest is pulled +// in blocks: a level file or a save is read whole, and a byte at a time is how that gets slow. static void _luaFileReadAll(lua_State *L, LuaFileT *file) { luaL_Buffer out; - int32_t c = 0; + int64_t got = 0; luaL_buffinit(L, &out); - while ((c = _luaFileGetc(file)) >= 0) { - luaL_addchar(&out, (char)c); + luaL_addlstring(&out, (const char *)file->buffer + file->at, (size_t)(file->have - file->at)); + file->pos += file->have - file->at; + file->at = file->have; + while ((got = vfsStreamRead(file->stream, file->buffer, LUA_VFS_BUFFER)) > 0) { + luaL_addlstring(&out, (const char *)file->buffer, (size_t)got); + file->pos += got; } + file->have = 0; + file->at = 0; luaL_pushresult(&out); } @@ -4362,13 +4383,22 @@ static bool _luaFileReadCount(lua_State *L, LuaFileT *file, int64_t count) { int64_t read = 0; int32_t c = 0; + // read(0) is Lua's test for the end: an empty string while there is more, nil at the end. + if (count == 0) { + if (_luaFileGetc(file) < 0) { + return false; + } + _luaFileUngetc(file); + lua_pushliteral(L, ""); + return true; + } luaL_buffinit(L, &out); while ((read < count) && ((c = _luaFileGetc(file)) >= 0)) { luaL_addchar(&out, (char)c); read++; } luaL_pushresult(&out); - if ((read == 0) && (count > 0)) { + if (read == 0) { lua_pop(L, 1); return false; } @@ -4442,7 +4472,7 @@ static bool _luaFileReadNumber(lua_State *L, LuaFileT *file) { do { c = _luaFileGetc(file); } while ((c >= 0) && isspace(c)); - while ((c >= 0) && (length < sizeof(text) - 1) && (isalnum(c) || (strchr("+-.", c) != NULL))) { + while ((c > 0) && (length < sizeof(text) - 1) && (isalnum(c) || (strchr("+-.", c) != NULL))) { text[length++] = (char)c; c = _luaFileGetc(file); } @@ -4572,6 +4602,16 @@ static int32_t _luaFileSeek(lua_State *L) { } +// Lets the stream go. Safe to call twice: close, collection and the end of io.lines all reach it. +static void _luaFileShut(LuaFileT *file) { + if (!file->closed) { + vfsStreamClose(file->stream); + file->stream = NULL; + file->closed = true; + } +} + + // The handle, if that is what is at index; NULL for anything else. static LuaFileT *_luaFileTest(lua_State *L, int32_t index) { return (LuaFileT *)luaL_testudata(L, index, LUA_VFS_FILE); @@ -4651,55 +4691,6 @@ static void _luaIoForgetInput(lua_State *L) { } -static LuaFileT *_luaIoInput(lua_State *L) { - LuaFileT *file = NULL; - - if (lua_getfield(L, LUA_REGISTRYINDEX, LUA_VFS_INPUT) != LUA_TNIL) { - file = _luaFileTest(L, -1); - } - lua_pop(L, 1); - - return file; -} - - -// io.read(...) -- from ours when io.input was given one, otherwise from Lua's own default input. -static int32_t _luaIoRead(lua_State *L) { - if (_luaIoInput(L) == NULL) { - return _luaCallOriginal(L); - } - lua_getfield(L, LUA_REGISTRYINDEX, LUA_VFS_INPUT); - lua_insert(L, 1); - - return _luaFileRead(L); -} - - -// The handle at 1 becomes the default input, and answers as io.input's result. Lua keeps its own -// default in the registry and its own functions would take ours for a FILE *, so ours is kept -// beside it and the three functions that read the default look here first. -static int32_t _luaIoSetInput(lua_State *L) { - lua_settop(L, 1); - lua_pushvalue(L, 1); - lua_setfield(L, LUA_REGISTRYINDEX, LUA_VFS_INPUT); - - return 1; -} - - -// io.type(x) -- ours answer as files, because to a script that is what they are. -static int32_t _luaIoType(lua_State *L) { - LuaFileT *file = _luaFileTest(L, 1); - - if (file == NULL) { - return _luaCallOriginal(L); - } - lua_pushstring(L, file->closed ? "closed file" : "file"); - - return 1; -} - - // io.open and friends with a name resolved through the vfs. Upvalues: the original, and how the name is used. static int32_t _luaIoHook(lua_State *L) { IoHookModeE mode = (IoHookModeE)lua_tointeger(L, lua_upvalueindex(2)); @@ -4772,6 +4763,55 @@ static int32_t _luaIoHook(lua_State *L) { } +static LuaFileT *_luaIoInput(lua_State *L) { + LuaFileT *file = NULL; + + if (lua_getfield(L, LUA_REGISTRYINDEX, LUA_VFS_INPUT) != LUA_TNIL) { + file = _luaFileTest(L, -1); + } + lua_pop(L, 1); + + return file; +} + + +// io.read(...) -- from ours when io.input was given one, otherwise from Lua's own default input. +static int32_t _luaIoRead(lua_State *L) { + if (_luaIoInput(L) == NULL) { + return _luaCallOriginal(L); + } + lua_getfield(L, LUA_REGISTRYINDEX, LUA_VFS_INPUT); + lua_insert(L, 1); + + return _luaFileRead(L); +} + + +// The handle at 1 becomes the default input, and answers as io.input's result. Lua keeps its own +// default in the registry and its own functions would take ours for a FILE *, so ours is kept +// beside it and the three functions that read the default look here first. +static int32_t _luaIoSetInput(lua_State *L) { + lua_settop(L, 1); + lua_pushvalue(L, 1); + lua_setfield(L, LUA_REGISTRYINDEX, LUA_VFS_INPUT); + + return 1; +} + + +// io.type(x) -- ours answer as files, because to a script that is what they are. +static int32_t _luaIoType(lua_State *L) { + LuaFileT *file = _luaFileTest(L, 1); + + if (file == NULL) { + return _luaCallOriginal(L); + } + lua_pushstring(L, file->closed ? "closed file" : "file"); + + return 1; +} + + // loadfile(name, mode, env) through the vfs; without a name the original reads stdin. static int32_t _luaLoadfile(lua_State *L) { const char *name = luaL_optstring(L, 1, NULL); @@ -5006,6 +5046,39 @@ static int32_t _materialSetMap(lua_State *L, const char *method, MaterialMapE ma } +// A MIDI channel argument, which a person counts from 1 and the wire counts from 0. +static uint8_t _midiChannel(lua_State *L, const char *method, int32_t index) { + int32_t channel = _argInteger(L, method, index) - 1; + + if ((channel < 0) || (channel > MIDI_CHANNEL_MAX)) { + _luaDie(L, method, "A MIDI channel is 1 to %d, not %d.", MIDI_CHANNEL_MAX + 1, channel + 1); + } + + return (uint8_t)channel; +} + + +// A seven bit MIDI data argument: a key, a velocity, a controller or its value. +static uint8_t _midiData(lua_State *L, const char *method, int32_t index) { + int32_t value = _argInteger(L, method, index); + + if ((value < 0) || (value > MIDI_DATA_MAX)) { + _luaDie(L, method, "A MIDI value is 0 to %d, not %d.", MIDI_DATA_MAX, value); + } + + return (uint8_t)value; +} + + +// onMidiMessage(status, data1, data2, bytes) for one message that arrived on the input port. The +// three numbers are the usual message; the string is the whole thing, which is what a system +// exclusive message needs. +static void _midiReceived(void *context, const uint8_t *bytes, size_t size) { + (void)context; + _callLua("onMidiMessage", "iiiS", (int32_t)bytes[0], (size > 1) ? (int32_t)bytes[1] : 0, (size > 2) ? (int32_t)bytes[2] : 0, (const char *)bytes, size); +} + + // Converts a script visible volume to the mixer's 0 to 1 gain. The two families keep different // scales: sound effects run to AUDIO_MAX_VOLUME and music to MUSIC_MAX_VOLUME, which is Hypseus's. static float _mixerGain(int32_t volume, int32_t maximum) { @@ -5019,38 +5092,14 @@ static int32_t _mouseCode(int32_t device, int32_t button) { } -// Everything the developer's overlay shows, gathered from wherever it lives. Drawn after the -// screenshot is taken, so a reference shot never has the overlay in it. -static void _statsDraw(void) { - StatsT stats; - int64_t textureBytes = 0; - SpriteT *sprite = NULL; - SpriteT *spriteNext = NULL; - SoundT *sound = NULL; - SoundT *soundNext = NULL; - - if (!statsIsEnabled()) { - return; - } - memset(&stats, 0, sizeof(stats)); - stats.frameMilliseconds = _global.statsFrameMs; - stats.worstMilliseconds = statsWorstMilliseconds(); - stats.framesPerSecond = statsFramesPerSecond(); - stats.luaKilobytes = (_global.luaContext != NULL) ? (double)lua_gc(_global.luaContext, LUA_GCCOUNT) : 0; - stats.timers = schedulerCount(); - HASH_ITER(hh, _global.spriteList, sprite, spriteNext) { - stats.sprites++; - } - HASH_ITER(hh, _global.soundList, sound, soundNext) { - stats.sounds++; - } - sceneGetStats(&stats.nodesTotal, &stats.nodesDrawn, &stats.batches, &textureBytes); - stats.textureKilobytes = textureBytes / BYTES_PER_KIB; - if (_global.videoHandle >= 0) { - stats.discFrame = videoGetFrame(_global.videoHandle); - stats.discState = _global.discStopped ? "stopped" : (videoIsPlaying(_global.videoHandle) ? "playing" : "paused"); - } - statsDraw(_global.renderer, &stats); +// Stops a music track, frees it and forgets it. +static void _musicDestroy(MusicT *music) { + HASH_DEL(_global.musicList, music); + MIX_StopTrack(music->track, 0); + MIX_SetTrackAudio(music->track, NULL); + MIX_DestroyTrack(music->track); + MIX_DestroyAudio(music->audio); + free(music); } @@ -5076,23 +5125,18 @@ static void _noteInput(void) { } -// Stops a music track, frees it and forgets it. -static void _musicDestroy(MusicT *music) { - HASH_DEL(_global.musicList, music); - MIX_StopTrack(music->track, 0); - MIX_SetTrackAudio(music->track, NULL); - MIX_DestroyTrack(music->track); - MIX_DestroyAudio(music->audio); - free(music); -} - - // Applies setOverlayOpacity's level to the overlay texture. Called again whenever the texture is made. static void _overlayApplyOpacity(void) { SDL_SetTextureAlphaMod(_global.overlayTexture, _global.overlayOpacity); } +// Applies setOverlayLinearScale's choice to the overlay texture. Called again whenever the texture is made. +static void _overlayApplyScaleMode(void) { + SDL_SetTextureScaleMode(_global.overlayTexture, _global.overlayLinearScale ? SDL_SCALEMODE_LINEAR : SDL_SCALEMODE_NEAREST); +} + + // A colour as the overlay surface stores it. static uint32_t _overlayColor(const SDL_Color *color) { return SDL_MapRGBA(SDL_GetPixelFormatDetails(_global.overlay->format), NULL, color->r, color->g, color->b, color->a); @@ -5132,12 +5176,6 @@ static SDL_Surface *_overlayGrey(void) { } -// Applies setOverlayLinearScale's choice to the overlay texture. Called again whenever the texture is made. -static void _overlayApplyScaleMode(void) { - SDL_SetTextureScaleMode(_global.overlayTexture, _global.overlayLinearScale ? SDL_SCALEMODE_LINEAR : SDL_SCALEMODE_NEAREST); -} - - // Replaces the overlay surface and texture (and the scene's targets) at a new size; its contents are lost. static void _overlayResize(int32_t width, int32_t height) { SDL_DestroySurface(_global.overlayGrey); @@ -6468,6 +6506,67 @@ static bool _sameLoosePath(const char *a, const char *b) { } +// A value a script wants kept. Tables are allowed because a save is usually a table of them; a +// function or a handle to something the engine owns is not, because neither means anything on the +// next run. +static void _saveCheckValue(lua_State *L, const char *method, int32_t index) { + int32_t type = lua_type(L, index); + + if ((type != LUA_TNUMBER) && (type != LUA_TSTRING) && (type != LUA_TBOOLEAN) && (type != LUA_TTABLE)) { + _luaDie(L, method, "A saved value is a number, a string, a boolean or a table of them, not a %s.", lua_typename(L, type)); + } +} + + +// Pushes one of cjson's functions. The save file is JSON because a person should be able to read +// and fix one, and cjson is already bundled and already knows every corner of the format. +static bool _saveCjson(lua_State *L, const char *name) { + lua_getglobal(L, "require"); + lua_pushstring(L, "cjson"); + if (lua_pcall(L, 1, 1, 0) != LUA_OK) { + lua_pop(L, 1); + return false; + } + lua_getfield(L, -1, name); + lua_remove(L, -2); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 1); + return false; + } + + return true; +} + + +// A deep copy of the value at "index", pushed on the stack. Everything going into the save and +// everything coming out of it is copied, so the table the engine keeps is its own: what a script +// sets is what is saved, and what it gets back is its own to change. Sharing the table instead +// made an edit through saveGet or saveGetAll persist or not depending on whether something unrelated +// saved later in the same run, which is not a rule anyone could hold in their head. +static void _saveCopy(lua_State *L, int32_t index, const char *method, int32_t depth) { + int32_t source = lua_absindex(L, index); + + if (!lua_istable(L, source)) { + lua_pushvalue(L, source); + return; + } + if (depth >= SAVE_DEPTH_MAX) { + _luaDie(L, method, "A saved table is nested more than %d deep, or refers to itself.", SAVE_DEPTH_MAX); + } + lua_newtable(L); + lua_pushnil(L); + while (lua_next(L, source) != 0) { + // The table, its key and its value are on the stack; the copy of the value goes beside + // them, then the key is put back on top so the pair can be written into the new table. + _saveCopy(L, -1, method, depth + 1); + lua_pushvalue(L, -3); + lua_insert(L, -2); + lua_rawset(L, -5); + lua_pop(L, 1); + } +} + + static void _saveMachineSettings(int32_t milliseconds) { char *path = _machineFilePath(); FILE *out = fopen(path, "w"); @@ -6488,6 +6587,99 @@ static void _saveMachineSettings(int32_t milliseconds) { } +// JSON has one kind of number and Lua 5.4 has two, so everything comes back from a decode as a +// float: a score saved as 100 would return 100.0 and print with a decimal point. Whole numbers are +// put back to integers as they are read, which is what the game that saved them had. Recursive, +// because a save is usually a table of tables, with a depth limit for a file somebody hand edited +// into a spiral. +static void _saveNormalise(lua_State *L, int32_t depth) { + lua_Number number = 0; + lua_Integer whole = 0; + + if (depth > SAVE_DEPTH_MAX) { + return; + } + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + if (lua_istable(L, -1)) { + _saveNormalise(L, depth + 1); + } else if (lua_isnumber(L, -1) && !lua_isinteger(L, -1)) { + number = lua_tonumber(L, -1); + whole = (lua_Integer)number; + if ((lua_Number)whole == number) { + // The key is still below the value, so the value is replaced in place rather than + // set again, which would disturb the traversal. + lua_pop(L, 1); + lua_pushinteger(L, whole); + lua_pushvalue(L, -2); + lua_pushvalue(L, -2); + lua_rawset(L, -5); + } + } + lua_pop(L, 1); + } +} + + +// The save table, read from the file the first time anything asks for it. Always leaves a table on +// the stack: a save that will not parse is reported and then treated as an empty one, because a +// game that cannot start is worse than a game that lost its high scores. +static void _saveTable(lua_State *L) { + char *text = NULL; + + lua_getfield(L, LUA_REGISTRYINDEX, SAVE_TABLE); + if (lua_istable(L, -1)) { + return; + } + lua_pop(L, 1); + lua_newtable(L); + text = persistRead(); + if (text != NULL) { + if (_saveCjson(L, "decode")) { + lua_pushstring(L, text); + if ((lua_pcall(L, 1, 1, 0) == LUA_OK) && lua_istable(L, -1)) { + lua_remove(L, -2); + _saveNormalise(L, 0); + } else { + utilSay("Warning: %s could not be read; starting with an empty save.", persistPath()); + lua_pop(L, 1); + } + } + free(text); + } + lua_pushvalue(L, -1); + lua_setfield(L, LUA_REGISTRYINDEX, SAVE_TABLE); +} + + +// Writes the save table out. Called at the end of a frame that changed it, and at shutdown. +static void _saveWrite(void) { + lua_State *L = _global.luaContext; + const char *text = NULL; + size_t size = 0; + + if ((L == NULL) || !_global.saveDirty) { + return; + } + _global.saveDirty = false; + if (!_saveCjson(L, "encode")) { + utilSay("Warning: the save file needs the cjson module, which did not load."); + return; + } + _saveTable(L); + if (lua_pcall(L, 1, 1, 0) != LUA_OK) { + utilSay("Warning: the save could not be turned into JSON: %s", lua_tostring(L, -1)); + lua_pop(L, 1); + return; + } + text = lua_tolstring(L, -1, &size); + if ((text != NULL) && !persistWrite(text, size)) { + utilSay("Warning: %s could not be written.", persistPath()); + } + lua_pop(L, 1); +} + + // A player's current frame for the 3D scene's video materials: the disc's texture is already // updated for this frame; a loaded video is advanced here (drawing it on the overlay too is harmless). static SDL_Texture *_sceneVideoSource(int32_t player) { @@ -6506,6 +6698,102 @@ static SDL_Texture *_sceneVideoSource(int32_t player) { } +// A timer or a tween has fired. Called from the frame loop by way of the scheduler, never from +// another thread, so this is an ordinary Lua call. +static void _scheduleFired(void *context, int32_t handle, SchedulerKindE kind, double value, double progress, bool finished) { + lua_State *L = _global.luaContext; + int32_t handler; + int32_t slot = (finished && (kind == SCHEDULER_TWEEN)) ? SCHEDULE_DONE : SCHEDULE_UPDATE; + + (void)context; + if (L == NULL) { + return; + } + if (!luaL_getsubtable(L, LUA_REGISTRYINDEX, SAVE_CALLBACKS)) { + lua_pop(L, 1); + return; + } + lua_rawgeti(L, -1, handle); + lua_remove(L, -2); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + return; + } + // A finished tween calls its update one last time at the end value, and then the optional + // second function; a game that only wants the end result gives just that one. + if ((slot == SCHEDULE_DONE) && (kind == SCHEDULER_TWEEN)) { + lua_rawgeti(L, -1, SCHEDULE_UPDATE); + if (lua_isfunction(L, -1)) { + lua_pushcfunction(L, _luaTraceback); + lua_insert(L, -2); + handler = lua_gettop(L) - 1; + lua_pushnumber(L, value); + lua_pushnumber(L, progress); + if (lua_pcall(L, 2, 0, handler) != LUA_OK) { + utilDie("Error in a tween: %s", lua_tostring(L, -1)); + } + lua_remove(L, handler); + } else { + lua_pop(L, 1); + } + } + lua_rawgeti(L, -1, slot); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 2); + return; + } + lua_pushcfunction(L, _luaTraceback); + lua_insert(L, -2); + handler = lua_gettop(L) - 1; + if (kind == SCHEDULER_TWEEN) { + lua_pushnumber(L, value); + lua_pushnumber(L, progress); + if (lua_pcall(L, 2, 0, handler) != LUA_OK) { + utilDie("Error in a tween: %s", lua_tostring(L, -1)); + } + } else { + lua_pushinteger(L, handle); + if (lua_pcall(L, 1, 0, handler) != LUA_OK) { + utilDie("Error in a timer: %s", lua_tostring(L, -1)); + } + } + lua_remove(L, handler); + lua_pop(L, 1); + if (finished) { + luaL_getsubtable(L, LUA_REGISTRYINDEX, SAVE_CALLBACKS); + lua_pushnil(L); + lua_rawseti(L, -2, handle); + lua_pop(L, 1); + } +} + + +// Remembers the functions a timer or tween will call, under its handle. +static void _scheduleRemember(lua_State *L, int32_t handle, int32_t updateIndex, int32_t doneIndex) { + luaL_getsubtable(L, LUA_REGISTRYINDEX, SAVE_CALLBACKS); + lua_newtable(L); + lua_pushvalue(L, updateIndex); + lua_rawseti(L, -2, SCHEDULE_UPDATE); + if ((doneIndex > 0) && lua_isfunction(L, doneIndex)) { + lua_pushvalue(L, doneIndex); + lua_rawseti(L, -2, SCHEDULE_DONE); + } + lua_rawseti(L, -2, handle); + lua_pop(L, 1); +} + + +// Everything a timer or tween left behind, for a script reload. +static void _scheduleReset(lua_State *L) { + schedulerReset(); + if (L == NULL) { + return; + } + lua_newtable(L); + lua_setfield(L, LUA_REGISTRYINDEX, SAVE_CALLBACKS); +} + + // One readout's digits: the value right justified in the places that readout has, with leading // zeros the way an arcade board shows them, the symbol Hypseus puts on every digit for each of its // negative codes, or nothing at all while the panel is blanked. @@ -6719,7 +7007,7 @@ static ConfigT *_scriptConfFromTable(lua_State *L, const char *method) { if (!lua_istable(L, 1)) { _luaDie(L, method, "Argument 1 must be a table."); } - conf = _buildConfFromTable(L, _global.conf); + conf = _buildConfFromTable(L, _global.conf, method); conf->dataDir = resolveDataDir(conf); if (conf->dataDir == NULL) { _luaDie(L, method, "Unable to create the data directory for %s.", conf->scriptFile); @@ -7021,289 +7309,6 @@ static SpriteT *_spriteLoadFromIO(lua_State *L, const char *method, SDL_IOStream } -// A MIDI channel argument, which a person counts from 1 and the wire counts from 0. -static uint8_t _midiChannel(lua_State *L, const char *method, int32_t index) { - int32_t channel = _argInteger(L, method, index) - 1; - - if ((channel < 0) || (channel > MIDI_CHANNEL_MAX)) { - _luaDie(L, method, "A MIDI channel is 1 to %d, not %d.", MIDI_CHANNEL_MAX + 1, channel + 1); - } - - return (uint8_t)channel; -} - - -// A seven bit MIDI data argument: a key, a velocity, a controller or its value. -static uint8_t _midiData(lua_State *L, const char *method, int32_t index) { - int32_t value = _argInteger(L, method, index); - - if ((value < 0) || (value > MIDI_DATA_MAX)) { - _luaDie(L, method, "A MIDI value is 0 to %d, not %d.", MIDI_DATA_MAX, value); - } - - return (uint8_t)value; -} - - -// onMidiMessage(status, data1, data2, bytes) for one message that arrived on the input port. The -// three numbers are the usual message; the string is the whole thing, which is what a system -// exclusive message needs. -static void _midiReceived(void *context, const uint8_t *bytes, size_t size) { - (void)context; - _callLua("onMidiMessage", "iiiS", (int32_t)bytes[0], (size > 1) ? (int32_t)bytes[1] : 0, (size > 2) ? (int32_t)bytes[2] : 0, (const char *)bytes, size); -} - - -// Pushes one of cjson's functions. The save file is JSON because a person should be able to read -// and fix one, and cjson is already bundled and already knows every corner of the format. -static bool _saveCjson(lua_State *L, const char *name) { - lua_getglobal(L, "require"); - lua_pushstring(L, "cjson"); - if (lua_pcall(L, 1, 1, 0) != LUA_OK) { - lua_pop(L, 1); - return false; - } - lua_getfield(L, -1, name); - lua_remove(L, -2); - if (!lua_isfunction(L, -1)) { - lua_pop(L, 1); - return false; - } - - return true; -} - - -// A deep copy of the value at "index", pushed on the stack. Everything going into the save and -// everything coming out of it is copied, so the table the engine keeps is its own: what a script -// sets is what is saved, and what it gets back is its own to change. Sharing the table instead -// made an edit through saveGet or saveGetAll persist or not depending on whether something unrelated -// saved later in the same run, which is not a rule anyone could hold in their head. -static void _saveCopy(lua_State *L, int32_t index, const char *method, int32_t depth) { - int32_t source = lua_absindex(L, index); - - if (!lua_istable(L, source)) { - lua_pushvalue(L, source); - return; - } - if (depth >= SAVE_DEPTH_MAX) { - _luaDie(L, method, "A saved table is nested more than %d deep, or refers to itself.", SAVE_DEPTH_MAX); - } - lua_newtable(L); - lua_pushnil(L); - while (lua_next(L, source) != 0) { - // The table, its key and its value are on the stack; the copy of the value goes beside - // them, then the key is put back on top so the pair can be written into the new table. - _saveCopy(L, -1, method, depth + 1); - lua_pushvalue(L, -3); - lua_insert(L, -2); - lua_rawset(L, -5); - lua_pop(L, 1); - } -} - - -// JSON has one kind of number and Lua 5.4 has two, so everything comes back from a decode as a -// float: a score saved as 100 would return 100.0 and print with a decimal point. Whole numbers are -// put back to integers as they are read, which is what the game that saved them had. Recursive, -// because a save is usually a table of tables, with a depth limit for a file somebody hand edited -// into a spiral. -static void _saveNormalise(lua_State *L, int32_t depth) { - lua_Number number = 0; - lua_Integer whole = 0; - - if (depth > SAVE_DEPTH_MAX) { - return; - } - lua_pushnil(L); - while (lua_next(L, -2) != 0) { - if (lua_istable(L, -1)) { - _saveNormalise(L, depth + 1); - } else if (lua_isnumber(L, -1) && !lua_isinteger(L, -1)) { - number = lua_tonumber(L, -1); - whole = (lua_Integer)number; - if ((lua_Number)whole == number) { - // The key is still below the value, so the value is replaced in place rather than - // set again, which would disturb the traversal. - lua_pop(L, 1); - lua_pushinteger(L, whole); - lua_pushvalue(L, -2); - lua_pushvalue(L, -2); - lua_rawset(L, -5); - } - } - lua_pop(L, 1); - } -} - - -// The save table, read from the file the first time anything asks for it. Always leaves a table on -// the stack: a save that will not parse is reported and then treated as an empty one, because a -// game that cannot start is worse than a game that lost its high scores. -static void _saveTable(lua_State *L) { - char *text = NULL; - - lua_getfield(L, LUA_REGISTRYINDEX, SAVE_TABLE); - if (lua_istable(L, -1)) { - return; - } - lua_pop(L, 1); - lua_newtable(L); - text = persistRead(); - if (text != NULL) { - if (_saveCjson(L, "decode")) { - lua_pushstring(L, text); - if ((lua_pcall(L, 1, 1, 0) == LUA_OK) && lua_istable(L, -1)) { - lua_remove(L, -2); - _saveNormalise(L, 0); - } else { - utilSay("Warning: %s could not be read; starting with an empty save.", persistPath()); - lua_pop(L, 1); - } - } - free(text); - } - lua_pushvalue(L, -1); - lua_setfield(L, LUA_REGISTRYINDEX, SAVE_TABLE); -} - - -// Writes the save table out. Called at the end of a frame that changed it, and at shutdown. -static void _saveWrite(void) { - lua_State *L = _global.luaContext; - const char *text = NULL; - size_t size = 0; - - if ((L == NULL) || !_global.saveDirty) { - return; - } - _global.saveDirty = false; - if (!_saveCjson(L, "encode")) { - utilSay("Warning: the save file needs the cjson module, which did not load."); - return; - } - _saveTable(L); - if (lua_pcall(L, 1, 1, 0) != LUA_OK) { - utilSay("Warning: the save could not be turned into JSON: %s", lua_tostring(L, -1)); - lua_pop(L, 1); - return; - } - text = lua_tolstring(L, -1, &size); - if ((text != NULL) && !persistWrite(text, size)) { - utilSay("Warning: %s could not be written.", persistPath()); - } - lua_pop(L, 1); -} - - -// A value a script wants kept. Tables are allowed because a save is usually a table of them; a -// function or a handle to something the engine owns is not, because neither means anything on the -// next run. -static void _saveCheckValue(lua_State *L, const char *method, int32_t index) { - int32_t type = lua_type(L, index); - - if ((type != LUA_TNUMBER) && (type != LUA_TSTRING) && (type != LUA_TBOOLEAN) && (type != LUA_TTABLE)) { - _luaDie(L, method, "A saved value is a number, a string, a boolean or a table of them, not a %s.", lua_typename(L, type)); - } -} - - -// A timer or a tween has fired. Called from the frame loop by way of the scheduler, never from -// another thread, so this is an ordinary Lua call. -static void _scheduleFired(void *context, int32_t handle, SchedulerKindE kind, double value, double progress, bool finished) { - lua_State *L = _global.luaContext; - int32_t handler; - int32_t slot = (finished && (kind == SCHEDULER_TWEEN)) ? SCHEDULE_DONE : SCHEDULE_UPDATE; - - (void)context; - if (L == NULL) { - return; - } - if (!luaL_getsubtable(L, LUA_REGISTRYINDEX, SAVE_CALLBACKS)) { - lua_pop(L, 1); - return; - } - lua_rawgeti(L, -1, handle); - lua_remove(L, -2); - if (!lua_istable(L, -1)) { - lua_pop(L, 1); - return; - } - // A finished tween calls its update one last time at the end value, and then the optional - // second function; a game that only wants the end result gives just that one. - if ((slot == SCHEDULE_DONE) && (kind == SCHEDULER_TWEEN)) { - lua_rawgeti(L, -1, SCHEDULE_UPDATE); - if (lua_isfunction(L, -1)) { - lua_pushcfunction(L, _luaTraceback); - lua_insert(L, -2); - handler = lua_gettop(L) - 1; - lua_pushnumber(L, value); - lua_pushnumber(L, progress); - if (lua_pcall(L, 2, 0, handler) != LUA_OK) { - utilDie("Error in a tween: %s", lua_tostring(L, -1)); - } - lua_remove(L, handler); - } else { - lua_pop(L, 1); - } - } - lua_rawgeti(L, -1, slot); - if (!lua_isfunction(L, -1)) { - lua_pop(L, 2); - return; - } - lua_pushcfunction(L, _luaTraceback); - lua_insert(L, -2); - handler = lua_gettop(L) - 1; - if (kind == SCHEDULER_TWEEN) { - lua_pushnumber(L, value); - lua_pushnumber(L, progress); - if (lua_pcall(L, 2, 0, handler) != LUA_OK) { - utilDie("Error in a tween: %s", lua_tostring(L, -1)); - } - } else { - lua_pushinteger(L, handle); - if (lua_pcall(L, 1, 0, handler) != LUA_OK) { - utilDie("Error in a timer: %s", lua_tostring(L, -1)); - } - } - lua_remove(L, handler); - lua_pop(L, 1); - if (finished) { - luaL_getsubtable(L, LUA_REGISTRYINDEX, SAVE_CALLBACKS); - lua_pushnil(L); - lua_rawseti(L, -2, handle); - lua_pop(L, 1); - } -} - - -// Remembers the functions a timer or tween will call, under its handle. -static void _scheduleRemember(lua_State *L, int32_t handle, int32_t updateIndex, int32_t doneIndex) { - luaL_getsubtable(L, LUA_REGISTRYINDEX, SAVE_CALLBACKS); - lua_newtable(L); - lua_pushvalue(L, updateIndex); - lua_rawseti(L, -2, SCHEDULE_UPDATE); - if ((doneIndex > 0) && lua_isfunction(L, doneIndex)) { - lua_pushvalue(L, doneIndex); - lua_rawseti(L, -2, SCHEDULE_DONE); - } - lua_rawseti(L, -2, handle); - lua_pop(L, 1); -} - - -// Everything a timer or tween left behind, for a script reload. -static void _scheduleReset(lua_State *L) { - schedulerReset(); - if (L == NULL) { - return; - } - lua_newtable(L); - lua_setfield(L, LUA_REGISTRYINDEX, SAVE_CALLBACKS); -} - - // An empty sprite record for a loader to fill. static SpriteT *_spriteNew(lua_State *L, const char *method) { SpriteT *sprite = (SpriteT *)calloc(1, sizeof(SpriteT)); @@ -7446,6 +7451,41 @@ static void _startLuaContext(lua_State *L) { } +// Everything the developer's overlay shows, gathered from wherever it lives. Drawn after the +// screenshot is taken, so a reference shot never has the overlay in it. +static void _statsDraw(void) { + StatsT stats; + int64_t textureBytes = 0; + SpriteT *sprite = NULL; + SpriteT *spriteNext = NULL; + SoundT *sound = NULL; + SoundT *soundNext = NULL; + + if (!statsIsEnabled()) { + return; + } + memset(&stats, 0, sizeof(stats)); + stats.frameMilliseconds = _global.statsFrameMs; + stats.worstMilliseconds = statsWorstMilliseconds(); + stats.framesPerSecond = statsFramesPerSecond(); + stats.luaKilobytes = (_global.luaContext != NULL) ? (double)lua_gc(_global.luaContext, LUA_GCCOUNT) : 0; + stats.timers = schedulerCount(); + HASH_ITER(hh, _global.spriteList, sprite, spriteNext) { + stats.sprites++; + } + HASH_ITER(hh, _global.soundList, sound, soundNext) { + stats.sounds++; + } + sceneGetStats(&stats.nodesTotal, &stats.nodesDrawn, &stats.batches, &textureBytes); + stats.textureKilobytes = textureBytes / BYTES_PER_KIB; + if (_global.videoHandle >= 0) { + stats.discFrame = videoGetFrame(_global.videoHandle); + stats.discState = _global.discStopped ? "stopped" : (videoIsPlaying(_global.videoHandle) ? "playing" : "paused"); + } + statsDraw(_global.renderer, &stats); +} + + static void _stopControllers(void) { int32_t x = 0; @@ -15734,7 +15774,7 @@ ConfigT *confFromDatabase(const ConfigT *conf) { } lua_replace(L, 1); lua_settop(L, 1); - result = _buildConfFromTable(L, base); + result = _buildConfFromTable(L, base, NULL); if (result->scriptFile == NULL) { utilDie("%s: games.dat entry %d has no SCRIPT.", base->container, conf->entry); } @@ -15831,7 +15871,7 @@ ConfigT *confFromGamesDat(const ConfigT *conf) { if (found) { lua_replace(L, 1); lua_settop(L, 1); - result = _buildConfFromTable(L, conf); + result = _buildConfFromTable(L, conf, NULL); result->entry = entry; } else { lua_pop(L, 1); diff --git a/src/stats.c b/src/stats.c index f0b1467e6..2391dd568 100644 --- a/src/stats.c +++ b/src/stats.c @@ -29,6 +29,7 @@ #define HISTORY 120 // Frames kept, about two seconds at sixty #define MARGIN 8 +#define PADDING 4 // Between the backdrop edge and the text #define LINE_HEIGHT (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE + 2) #define PANEL_WIDTH 300 // Wide enough for the longest line, which is the frame times #define LINES_MAX 6 // What statsDraw writes; the backdrop is sized from it @@ -50,7 +51,7 @@ static void _line(SDL_Renderer *renderer, int32_t index, const char *text); static void _line(SDL_Renderer *renderer, int32_t index, const char *text) { SDL_SetRenderDrawColor(renderer, 255, 255, 255, SDL_ALPHA_OPAQUE); - SDL_RenderDebugText(renderer, (float)(MARGIN + 4), (float)(MARGIN + 4 + (index * LINE_HEIGHT)), text); + SDL_RenderDebugText(renderer, (float)(MARGIN + PADDING), (float)(MARGIN + PADDING + (index * LINE_HEIGHT)), text); } @@ -67,7 +68,7 @@ void statsDraw(SDL_Renderer *renderer, const StatsT *stats) { backdrop.x = MARGIN; backdrop.y = MARGIN; backdrop.w = PANEL_WIDTH; - backdrop.h = (float)((LINES_MAX * LINE_HEIGHT) + 8); + backdrop.h = (float)((LINES_MAX * LINE_HEIGHT) + (PADDING * 2)); SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(renderer, 0, 0, 0, BACKDROP_ALPHA); SDL_RenderFillRect(renderer, &backdrop); diff --git a/src/vfs.c b/src/vfs.c index fc4a62910..495302879 100644 --- a/src/vfs.c +++ b/src/vfs.c @@ -127,7 +127,7 @@ static int _listCompare(const void *a, const void *b); // qsort callba static void _listDirectory(const char *path, ListT *list); static char **_listFinish(ListT *list, int32_t *count); static char *_normalise(const char *name); -static char *_overlayFor(const char *dataDirBase, const char *dataDir, const char *databasePath, bool isContainer, const char *directory); +static char *_overlayFor(const char *dataDirBase, const char *dataDir, const char *databasePath, bool isContainer); static uint8_t *_readAsset(DatabaseT *db, const char *key, size_t *bytes, bool sdlMemory); static bool _readChunk(DatabaseT *db, const char *key, int64_t index, uint8_t *buffer, int64_t capacity, int64_t *length); static bool _resolve(const char *name, TargetT *target); @@ -540,15 +540,15 @@ static char *_normalise(const char *name) { // A game's own directory under the data directory, holding its overlay. -static char *_overlayFor(const char *dataDirBase, const char *dataDir, const char *databasePath, bool isContainer, const char *directory) { +static char *_overlayFor(const char *dataDirBase, const char *dataDir, const char *databasePath, bool isContainer) { char *stem = NULL; char *result = NULL; if (isContainer && (dataDir != NULL)) { - return utilCreateString("%s%s%c", dataDir, directory, utilGetPathSeparator()); + return utilCreateString("%s%s%c", dataDir, OVERLAY_DIRECTORY, utilGetPathSeparator()); } stem = vfsDatabaseStem(utilGetLastPathComponent(databasePath)); - result = utilCreateString("%s%s%c%s%c", dataDirBase ? dataDirBase : "", stem, utilGetPathSeparator(), directory, utilGetPathSeparator()); + result = utilCreateString("%s%s%c%s%c", dataDirBase ? dataDirBase : "", stem, utilGetPathSeparator(), OVERLAY_DIRECTORY, utilGetPathSeparator()); free(stem); return result; @@ -655,7 +655,7 @@ static bool _resolve(const char *name, TargetT *target) { if (target->db != NULL) { inner = (norm[i] == 0) ? norm + i : norm + i + 1; if (target->db->overlay == NULL) { - target->db->overlay = _overlayFor(_dataDirBase, _dataDir, prefix, false, OVERLAY_DIRECTORY); + target->db->overlay = _overlayFor(_dataDirBase, _dataDir, prefix, false); } free(prefix); break; @@ -824,7 +824,7 @@ void vfsInit(const char *container, const char *dataDirBase, const char *dataDir utilDie("%s is not a Singe game database.", container); } free(_container->overlay); - _container->overlay = _overlayFor(_dataDirBase, _dataDir, container, true, OVERLAY_DIRECTORY); + _container->overlay = _overlayFor(_dataDirBase, _dataDir, container, true); } } @@ -872,14 +872,11 @@ bool vfsIsFilesystem(const char *name) { } -// The entries directly under a directory name, from the loose directory, the overlay and the -// database together, each name once, sorted. Free with vfsListFree. // Whether a name lives only inside a database: no loose file and no overlay copy shadows it. That // is the case that has no filesystem path at all: vfsFilePath answers with a name that is not // there, and a stream is the only way to read it. bool vfsIsPacked(const char *name) { TargetT target; - int64_t size = 0; bool packed = false; if (!_resolve(name, &target)) { @@ -887,13 +884,15 @@ bool vfsIsPacked(const char *name) { } packed = (target.db != NULL) && (target.key[0] != 0) && !utilFileExists(target.path) && !utilFileExists(target.overlay) && - _assetSize(target.db, target.key, &size); + _assetExists(target.db, target.key); _targetFree(&target); return packed; } +// The entries directly under a directory name, from the loose directory, the overlay and the +// database together, each name once, sorted. Free with vfsListFree. char **vfsList(const char *name, int32_t *count) { TargetT target; ListT list; diff --git a/src/videoPlayer.c b/src/videoPlayer.c index caec5c70e..304a96538 100644 --- a/src/videoPlayer.c +++ b/src/videoPlayer.c @@ -287,14 +287,11 @@ static int64_t _avioSeek(void *opaque, int64_t offset, int whence); static void _buildFrameTable(VideoPlayerT *v, const char *filename, const char *indexPath); static int _compareFrames(const void *a, const void *b); // qsort callback. Not changing int. static void _convertFrame(VideoPlayerT *v, FrameBufferT *buffer, const AVFrame *frame); -static const AVFrame *_deinterlace(VideoPlayerT *v, AVFrame *frame); -static void _deinterlaceClose(VideoPlayerT *v); -static bool _srtAppend(char **text, size_t *used, size_t *room, const char *addition); -static char *_srtFromRect(const AVSubtitleRect *rect); -static void _srtTime(int64_t milliseconds, char *out, size_t size); -static bool _deinterlaceOpen(VideoPlayerT *v, const AVFrame *frame); static DecodeResultE _decodeFrame(VideoPlayerT *v, int64_t want); static void _decodeHere(VideoPlayerT *v); +static const AVFrame *_deinterlace(VideoPlayerT *v, AVFrame *frame); +static void _deinterlaceClose(VideoPlayerT *v); +static bool _deinterlaceOpen(VideoPlayerT *v, const AVFrame *frame); static int _decoderThread(void *data); // SDL thread entry. Not changing int. static void _feedAudio(VideoPlayerT *v); static int64_t _findFrameIndex(VideoPlayerT *v, int64_t pts); @@ -314,13 +311,16 @@ static bool _readIndexCache(VideoPlayerT *v, const char *indexName, co static void _reportKeyframes(VideoPlayerT *v, const char *filename); static void _requestFrame(VideoPlayerT *v); static void _resetClock(VideoPlayerT *v, uint64_t now); +static const AVCodec *_rkmppDecoder(const AVCodec *decoder); static void _seekVideo(VideoPlayerT *v, int64_t keyframe); static enum AVPixelFormat _selectPixelFormat(AVCodecContext *codec, const enum AVPixelFormat *formats); // libavcodec callback. +static bool _srtAppend(char **text, size_t *used, size_t *room, const char *addition); +static char *_srtFromRect(const AVSubtitleRect *rect); +static void _srtTime(int64_t milliseconds, char *out, size_t size); static int64_t _streamTimeToMs(int64_t ts, AVRational timeBase); static bool _takeDecodedFrame(VideoPlayerT *v); static void _trackMixed(void *udata, MIX_Track *track, const SDL_AudioSpec *spec, float *pcm, int32_t samples); static void _uploadFrame(VideoPlayerT *v); -static const AVCodec *_rkmppDecoder(const AVCodec *decoder); static const AVCodec *_v4l2Decoder(const AVCodec *decoder); static void _writeIndexCache(VideoPlayerT *v, const char *indexName, const IndexHeaderT *header); @@ -835,91 +835,6 @@ static void _convertFrame(VideoPlayerT *v, FrameBufferT *buffer, const AVFrame * } -// A frame ready to convert: the deinterlaced one when the picture is interlaced and the option -// allows it, and the frame itself otherwise. Laserdisc rips are the reason this exists -- a disc -// held interlaced fields, and a rip that kept them combs on every progressive display. -static const AVFrame *_deinterlace(VideoPlayerT *v, AVFrame *frame) { - if ((v->deinterlace == DEINTERLACE_OFF) || (frame->format == AV_PIX_FMT_NONE)) { - return frame; - } - // bwdif is told to leave progressive frames alone, so in automatic mode the graph is built only - // once something interlaced actually turns up and costs nothing on a progressive disc. - if ((v->deinterlace == DEINTERLACE_AUTO) && ((frame->flags & AV_FRAME_FLAG_INTERLACED) == 0) && (v->filterGraph == NULL)) { - return frame; - } - if ((v->filterGraph != NULL) && ((frame->format != v->filterFormat) || (frame->width != v->filterWidth) || (frame->height != v->filterHeight))) { - _deinterlaceClose(v); - } - if ((v->filterGraph == NULL) && !_deinterlaceOpen(v, frame)) { - return frame; - } - av_frame_unref(v->filterFrame); - if (av_buffersrc_add_frame_flags(v->filterSource, frame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0) { - return frame; - } - if (av_buffersink_get_frame(v->filterSink, v->filterFrame) < 0) { - return frame; - } - - return v->filterFrame; -} - - -static void _deinterlaceClose(VideoPlayerT *v) { - if (v->filterGraph != NULL) { - avfilter_graph_free(&v->filterGraph); - v->filterSource = NULL; - v->filterSink = NULL; - } -} - - -// buffer -> bwdif -> buffersink. bwdif runs in send_frame mode, one picture out for one in: a -// disc is addressed by frame number, and a filter that turned each frame into two fields would -// move every frame in the index. -static bool _deinterlaceOpen(VideoPlayerT *v, const AVFrame *frame) { - char arguments[FILTER_ARGUMENTS_MAX]; - const AVFilter *source = avfilter_get_by_name("buffer"); - const AVFilter *sink = avfilter_get_by_name("buffersink"); - const AVFilter *bwdif = avfilter_get_by_name("bwdif"); - AVFilterContext *filter = NULL; - AVRational ratio = (frame->sample_aspect_ratio.num > 0) ? frame->sample_aspect_ratio : (AVRational){ 1, 1 }; - - if ((source == NULL) || (sink == NULL) || (bwdif == NULL)) { - utilTrace("Video %d: this build has no deinterlacer.", v->id); - v->deinterlace = DEINTERLACE_OFF; - return false; - } - v->filterGraph = avfilter_graph_alloc(); - if (v->filterGraph == NULL) { - utilDie("Unable to allocate the deinterlacing graph."); - } - snprintf(arguments, sizeof(arguments), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", - frame->width, frame->height, frame->format, v->videoTimeBase.num, v->videoTimeBase.den, ratio.num, ratio.den); - if ((avfilter_graph_create_filter(&v->filterSource, source, "in", arguments, NULL, v->filterGraph) < 0) || - (avfilter_graph_create_filter(&filter, bwdif, "bwdif", (v->deinterlace == DEINTERLACE_ON) ? "mode=send_frame:parity=auto:deint=all" : "mode=send_frame:parity=auto:deint=interlaced", NULL, v->filterGraph) < 0) || - (avfilter_graph_create_filter(&v->filterSink, sink, "out", NULL, NULL, v->filterGraph) < 0) || - (avfilter_link(v->filterSource, 0, filter, 0) < 0) || - (avfilter_link(filter, 0, v->filterSink, 0) < 0) || - (avfilter_graph_config(v->filterGraph, NULL) < 0)) { - utilTrace("Video %d: the deinterlacing graph would not build; leaving the picture alone.", v->id); - _deinterlaceClose(v); - v->deinterlace = DEINTERLACE_OFF; - return false; - } - v->filterFormat = (enum AVPixelFormat)frame->format; - v->filterWidth = frame->width; - v->filterHeight = frame->height; - if (!v->filterReported) { - v->filterReported = true; - utilTrace("Video %d: deinterlacing with bwdif.", v->id); - } - - return true; -} - - - // Decodes frame "want" into the back buffer. Runs on the decoder thread. On DECODE_ERROR the // message is in threadErrMsg; the caller raises threadError under the lock, which publishes both. static DecodeResultE _decodeFrame(VideoPlayerT *v, int64_t want) { @@ -1084,6 +999,91 @@ static int _decoderThread(void *data) { } +// A frame ready to convert: the deinterlaced one when the picture is interlaced and the option +// allows it, and the frame itself otherwise. Laserdisc rips are the reason this exists -- a disc +// held interlaced fields, and a rip that kept them combs on every progressive display. +static const AVFrame *_deinterlace(VideoPlayerT *v, AVFrame *frame) { + if ((v->deinterlace == DEINTERLACE_OFF) || (frame->format == AV_PIX_FMT_NONE)) { + return frame; + } + // bwdif is told to leave progressive frames alone, so in automatic mode the graph is built only + // once something interlaced actually turns up and costs nothing on a progressive disc. + if ((v->deinterlace == DEINTERLACE_AUTO) && ((frame->flags & AV_FRAME_FLAG_INTERLACED) == 0) && (v->filterGraph == NULL)) { + return frame; + } + if ((v->filterGraph != NULL) && ((frame->format != v->filterFormat) || (frame->width != v->filterWidth) || (frame->height != v->filterHeight))) { + _deinterlaceClose(v); + } + if ((v->filterGraph == NULL) && !_deinterlaceOpen(v, frame)) { + return frame; + } + av_frame_unref(v->filterFrame); + if (av_buffersrc_add_frame_flags(v->filterSource, frame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0) { + return frame; + } + if (av_buffersink_get_frame(v->filterSink, v->filterFrame) < 0) { + return frame; + } + + return v->filterFrame; +} + + +static void _deinterlaceClose(VideoPlayerT *v) { + if (v->filterGraph != NULL) { + avfilter_graph_free(&v->filterGraph); + v->filterSource = NULL; + v->filterSink = NULL; + } +} + + +// buffer -> bwdif -> buffersink. bwdif runs in send_frame mode, one picture out for one in: a +// disc is addressed by frame number, and a filter that turned each frame into two fields would +// move every frame in the index. +static bool _deinterlaceOpen(VideoPlayerT *v, const AVFrame *frame) { + char arguments[FILTER_ARGUMENTS_MAX]; + const AVFilter *source = avfilter_get_by_name("buffer"); + const AVFilter *sink = avfilter_get_by_name("buffersink"); + const AVFilter *bwdif = avfilter_get_by_name("bwdif"); + AVFilterContext *filter = NULL; + AVRational ratio = (frame->sample_aspect_ratio.num > 0) ? frame->sample_aspect_ratio : (AVRational){ 1, 1 }; + + if ((source == NULL) || (sink == NULL) || (bwdif == NULL)) { + utilTrace("Video %d: this build has no deinterlacer.", v->id); + v->deinterlace = DEINTERLACE_OFF; + return false; + } + v->filterGraph = avfilter_graph_alloc(); + if (v->filterGraph == NULL) { + utilDie("Unable to allocate the deinterlacing graph."); + } + snprintf(arguments, sizeof(arguments), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", + frame->width, frame->height, frame->format, v->videoTimeBase.num, v->videoTimeBase.den, ratio.num, ratio.den); + if ((avfilter_graph_create_filter(&v->filterSource, source, "in", arguments, NULL, v->filterGraph) < 0) || + (avfilter_graph_create_filter(&filter, bwdif, "bwdif", (v->deinterlace == DEINTERLACE_ON) ? "mode=send_frame:parity=auto:deint=all" : "mode=send_frame:parity=auto:deint=interlaced", NULL, v->filterGraph) < 0) || + (avfilter_graph_create_filter(&v->filterSink, sink, "out", NULL, NULL, v->filterGraph) < 0) || + (avfilter_link(v->filterSource, 0, filter, 0) < 0) || + (avfilter_link(filter, 0, v->filterSink, 0) < 0) || + (avfilter_graph_config(v->filterGraph, NULL) < 0)) { + utilTrace("Video %d: the deinterlacing graph would not build; leaving the picture alone.", v->id); + _deinterlaceClose(v); + v->deinterlace = DEINTERLACE_OFF; + return false; + } + v->filterFormat = (enum AVPixelFormat)frame->format; + v->filterWidth = frame->width; + v->filterHeight = frame->height; + if (!v->filterReported) { + v->filterReported = true; + utilTrace("Video %d: deinterlacing with bwdif.", v->id); + } + + return true; +} + + + // Keeps the mixer track's stream topped up from the current audio track. static void _feedAudio(VideoPlayerT *v) { int32_t result = 0; @@ -1537,6 +1537,74 @@ static void _resetClock(VideoPlayerT *v, uint64_t now) { } +// The V4L2 memory-to-memory decoder for the stream's codec on a Pi build, NULL when there is none or +// hardware decoding is off. Whether the device exists is found out when the decoder opens. +// Rockchip boards decode through the vendor's Media Process Platform, which FFmpeg exposes as named +// decoders in the same way as V4L2's. They exist only when FFmpeg was built against that library, +// which is why this is compiled out everywhere else. Tried before V4L2: a Rockchip running its +// vendor kernel has both, and the MPP path is the one that works there. +static const AVCodec *_rkmppDecoder(const AVCodec *decoder) { + static const V4l2DecoderT table[] = { + { AV_CODEC_ID_H264, "h264_rkmpp" }, + { AV_CODEC_ID_HEVC, "hevc_rkmpp" } + }; + size_t x = 0; + + if (!RKMPP_DECODE || !_hardwareDecoding) { + return NULL; + } + for (x = 0; x < SDL_arraysize(table); x++) { + if (table[x].id == decoder->id) { + return avcodec_find_decoder_by_name(table[x].name); + } + } + + return NULL; +} + + +static void _seekVideo(VideoPlayerT *v, int64_t keyframe) { + if (av_seek_frame(v->videoFormat, v->videoStream, v->frames[keyframe].pts, AVSEEK_FLAG_BACKWARD) < 0) { + avformat_seek_file(v->videoFormat, v->videoStream, INT64_MIN, 0, INT64_MAX, 0); + keyframe = 0; + } + avcodec_flush_buffers(v->videoCodec); + if (v->packetPending) { + av_packet_unref(v->videoPacket); + v->packetPending = false; + } + // Streams without timestamps are counted from here; the rest are matched by timestamp. + v->nextDecodeFrame = keyframe; + v->seekKeyframe = keyframe; + v->videoDrained = false; +} + + +// libavcodec asks which of the formats it can produce we want; take the hardware one when offered. +// The list is every hardware format the build knows followed by the software one, so the fallback +// is the first entry that is not a hardware surface, not the first entry. +static enum AVPixelFormat _selectPixelFormat(AVCodecContext *codec, const enum AVPixelFormat *formats) { + VideoPlayerT *v = (VideoPlayerT *)codec->opaque; + const AVPixFmtDescriptor *desc = NULL; + int32_t x = 0; + + for (x = 0; formats[x] != AV_PIX_FMT_NONE; x++) { + if (formats[x] == v->hwPixelFormat) { + return formats[x]; + } + } + utilTrace("Video %d: hardware decoder declined the stream; decoding in software.", v->id); + for (x = 0; formats[x] != AV_PIX_FMT_NONE; x++) { + desc = av_pix_fmt_desc_get(formats[x]); + if ((desc != NULL) && !(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) { + return formats[x]; + } + } + + return formats[0]; +} + + // Positions the video demuxer at a keyframe and resets the decoder. Only ever called by whichever // thread is decoding: the decoder thread, or the frame loop in deterministic mode. // Appends to a growing string, doubling it as it goes. False when it will not grow. @@ -1644,48 +1712,6 @@ static void _srtTime(int64_t milliseconds, char *out, size_t size) { } -static void _seekVideo(VideoPlayerT *v, int64_t keyframe) { - if (av_seek_frame(v->videoFormat, v->videoStream, v->frames[keyframe].pts, AVSEEK_FLAG_BACKWARD) < 0) { - avformat_seek_file(v->videoFormat, v->videoStream, INT64_MIN, 0, INT64_MAX, 0); - keyframe = 0; - } - avcodec_flush_buffers(v->videoCodec); - if (v->packetPending) { - av_packet_unref(v->videoPacket); - v->packetPending = false; - } - // Streams without timestamps are counted from here; the rest are matched by timestamp. - v->nextDecodeFrame = keyframe; - v->seekKeyframe = keyframe; - v->videoDrained = false; -} - - -// libavcodec asks which of the formats it can produce we want; take the hardware one when offered. -// The list is every hardware format the build knows followed by the software one, so the fallback -// is the first entry that is not a hardware surface, not the first entry. -static enum AVPixelFormat _selectPixelFormat(AVCodecContext *codec, const enum AVPixelFormat *formats) { - VideoPlayerT *v = (VideoPlayerT *)codec->opaque; - const AVPixFmtDescriptor *desc = NULL; - int32_t x = 0; - - for (x = 0; formats[x] != AV_PIX_FMT_NONE; x++) { - if (formats[x] == v->hwPixelFormat) { - return formats[x]; - } - } - utilTrace("Video %d: hardware decoder declined the stream; decoding in software.", v->id); - for (x = 0; formats[x] != AV_PIX_FMT_NONE; x++) { - desc = av_pix_fmt_desc_get(formats[x]); - if ((desc != NULL) && !(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) { - return formats[x]; - } - } - - return formats[0]; -} - - static int64_t _streamTimeToMs(int64_t ts, AVRational timeBase) { return av_rescale(ts, (int64_t)timeBase.num * (int64_t)MS_PER_SECOND, timeBase.den); } @@ -1762,32 +1788,6 @@ static void _uploadFrame(VideoPlayerT *v) { } -// The V4L2 memory-to-memory decoder for the stream's codec on a Pi build, NULL when there is none or -// hardware decoding is off. Whether the device exists is found out when the decoder opens. -// Rockchip boards decode through the vendor's Media Process Platform, which FFmpeg exposes as named -// decoders in the same way as V4L2's. They exist only when FFmpeg was built against that library, -// which is why this is compiled out everywhere else. Tried before V4L2: a Rockchip running its -// vendor kernel has both, and the MPP path is the one that works there. -static const AVCodec *_rkmppDecoder(const AVCodec *decoder) { - static const V4l2DecoderT table[] = { - { AV_CODEC_ID_H264, "h264_rkmpp" }, - { AV_CODEC_ID_HEVC, "hevc_rkmpp" } - }; - size_t x = 0; - - if (!RKMPP_DECODE || !_hardwareDecoding) { - return NULL; - } - for (x = 0; x < SDL_arraysize(table); x++) { - if (table[x].id == decoder->id) { - return avcodec_find_decoder_by_name(table[x].name); - } - } - - return NULL; -} - - static const AVCodec *_v4l2Decoder(const AVCodec *decoder) { static const V4l2DecoderT table[] = { { AV_CODEC_ID_H263, "h263_v4l2m2m" }, @@ -1881,6 +1881,38 @@ int32_t videoGetAudioTracks(int32_t playerHandle) { } +// What the decoder will be, for the trace header: the platform's hardware decoders in the order +// they are tried, or software when there are none or --softwarevideo ruled them out. Which one a +// given video actually got is traced as that video opens, since a codec may offer neither. +const char *videoGetDecoderDescription(void) { + if (!_hardwareDecoding) { + return "software only (--softwarevideo)"; + } + if (V4L2_DECODE) { + // An ARM build may carry three: the stateless V4L2 decoders, Rockchip's own, and the + // stateful V4L2 ones. Which of them a board actually has is a question for the board. + if (RKMPP_DECODE && V4L2_REQUEST_DECODE) { + return "hardware v4l2 stateless, rkmpp or v4l2m2m where the board allows, software otherwise"; + } + if (V4L2_REQUEST_DECODE) { + return "hardware v4l2 stateless or v4l2m2m where the board allows, software otherwise"; + } + if (RKMPP_DECODE) { + return "hardware rkmpp or v4l2m2m where the board allows, software otherwise"; + } + + return "hardware v4l2m2m where the codec allows, software otherwise"; + } +#if defined(_WIN32) + return "hardware d3d11va or dxva2 where the codec allows, software otherwise"; +#elif defined(__APPLE__) + return "hardware videotoolbox where the codec allows, software otherwise"; +#else + return "hardware vaapi or vdpau where the codec allows, software otherwise"; +#endif +} + + // Frames per second of the loaded video, which is what a subtitle's timestamps are turned into // frame numbers with. double videoGetFps(int32_t playerHandle) { @@ -1890,178 +1922,6 @@ double videoGetFps(int32_t playerHandle) { } -// How many subtitle tracks the file holds, bitmap ones included: they are counted so that the -// numbering matches what a player or ffprobe shows, and videoReadSubtitles says which have words. -int32_t videoGetSubtitleTracks(int32_t playerHandle) { - VideoPlayerT *v = _getPlayer(playerHandle, "videoGetSubtitleTracks"); - AVFormatContext *format = _formatOpen(v->filename); - int32_t count = 0; - uint32_t x = 0; - - if (format == NULL) { - return 0; - } - if (avformat_find_stream_info(format, NULL) >= 0) { - for (x = 0; x < format->nb_streams; x++) { - if (format->streams[x]->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { - count++; - } - } - } - _formatClose(&format); - - return count; -} - - -// The language of a subtitle track as the file labels it, "" when it says nothing. The pointer is -// good until the next call. -const char *videoGetSubtitleLanguage(int32_t playerHandle, int32_t track) { - static char language[LANGUAGE_CODE_BYTES]; - VideoPlayerT *v = _getPlayer(playerHandle, "videoGetSubtitleLanguage"); - AVFormatContext *format = _formatOpen(v->filename); - AVDictionaryEntry *entry = NULL; - int32_t seen = 0; - uint32_t x = 0; - - language[0] = '\0'; - if (format == NULL) { - return language; - } - if (avformat_find_stream_info(format, NULL) >= 0) { - for (x = 0; x < format->nb_streams; x++) { - if (format->streams[x]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) { - continue; - } - if (seen == track) { - entry = av_dict_get(format->streams[x]->metadata, "language", NULL, 0); - if ((entry != NULL) && (entry->value != NULL)) { - SDL_strlcpy(language, entry->value, sizeof(language)); - } - break; - } - seen++; - } - } - _formatClose(&format); - - return language; -} - - -// One subtitle track read out of the container as SubRip text, which is the form the engine's own -// subtitle loader already takes. The whole file is walked once, decoding nothing but the subtitle -// packets, so this costs a pass over the file and no picture decoding at all. NULL when the track -// does not exist, holds pictures rather than words, or yields nothing; free() what comes back. -char *videoReadSubtitles(int32_t playerHandle, int32_t track) { - VideoPlayerT *v = _getPlayer(playerHandle, "videoReadSubtitles"); - AVFormatContext *format = _formatOpen(v->filename); - AVCodecContext *codec = NULL; - const AVCodec *decoder = NULL; - AVPacket *packet = NULL; - AVSubtitle subtitle; - AVRational timeBase; - char *text = NULL; - char *words = NULL; - char line[SRT_CUE_MAX]; - char from[SRT_TIME_MAX]; - char to[SRT_TIME_MAX]; - size_t used = 0; - size_t room = 0; - int64_t start = 0; - int64_t end = 0; - int32_t stream = -1; - int32_t seen = 0; - int32_t cues = 0; - int got = 0; - uint32_t x = 0; - bool ok = true; - - if (format == NULL) { - return NULL; - } - if (avformat_find_stream_info(format, NULL) < 0) { - _formatClose(&format); - return NULL; - } - for (x = 0; x < format->nb_streams; x++) { - if (format->streams[x]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) { - continue; - } - if (seen == track) { - stream = (int32_t)x; - break; - } - seen++; - } - if (stream < 0) { - _formatClose(&format); - return NULL; - } - timeBase = format->streams[stream]->time_base; - decoder = avcodec_find_decoder(format->streams[stream]->codecpar->codec_id); - codec = (decoder != NULL) ? avcodec_alloc_context3(decoder) : NULL; - packet = av_packet_alloc(); - if ((codec == NULL) || (packet == NULL) || - (avcodec_parameters_to_context(codec, format->streams[stream]->codecpar) < 0) || - (avcodec_open2(codec, decoder, NULL) < 0)) { - utilTrace("Video %d: subtitle track %d has no decoder in this build.", v->id, track); - av_packet_free(&packet); - avcodec_free_context(&codec); - _formatClose(&format); - return NULL; - } - while (ok && (av_read_frame(format, packet) >= 0)) { - if (packet->stream_index != stream) { - av_packet_unref(packet); - continue; - } - memset(&subtitle, 0, sizeof(subtitle)); - got = 0; - if (avcodec_decode_subtitle2(codec, &subtitle, &got, packet) >= 0) { - if (got != 0) { - start = (int64_t)((double)packet->pts * av_q2d(timeBase) * MS_PER_SECOND) + (int64_t)subtitle.start_display_time; - if (subtitle.end_display_time > subtitle.start_display_time) { - end = start + (int64_t)(subtitle.end_display_time - subtitle.start_display_time); - } else { - end = start + (int64_t)((double)packet->duration * av_q2d(timeBase) * MS_PER_SECOND); - } - for (x = 0; (x < subtitle.num_rects) && ok; x++) { - words = _srtFromRect(subtitle.rects[x]); - if (words == NULL) { - continue; - } - cues++; - _srtTime(start, from, sizeof(from)); - _srtTime(end, to, sizeof(to)); - snprintf(line, sizeof(line), "%d\n", cues); - ok = _srtAppend(&text, &used, &room, line) && - _srtAppend(&text, &used, &room, from) && - _srtAppend(&text, &used, &room, " --> ") && - _srtAppend(&text, &used, &room, to) && - _srtAppend(&text, &used, &room, "\n") && - _srtAppend(&text, &used, &room, words) && - _srtAppend(&text, &used, &room, "\n\n"); - free(words); - } - } - avsubtitle_free(&subtitle); - } - av_packet_unref(packet); - } - av_packet_free(&packet); - avcodec_free_context(&codec); - _formatClose(&format); - if (!ok || (cues == 0)) { - free(text); - return NULL; - } - utilTrace("Video %d: subtitle track %d read as %d cues.", v->id, track, cues); - - return text; -} - - int64_t videoGetFrame(int32_t playerHandle) { return _getPlayer(playerHandle, "videoGetFrame")->frame; } @@ -2114,38 +1974,6 @@ MIX_Mixer *videoGetMixer(void) { } -// What the decoder will be, for the trace header: the platform's hardware decoders in the order -// they are tried, or software when there are none or --softwarevideo ruled them out. Which one a -// given video actually got is traced as that video opens, since a codec may offer neither. -const char *videoGetDecoderDescription(void) { - if (!_hardwareDecoding) { - return "software only (--softwarevideo)"; - } - if (V4L2_DECODE) { - // An ARM build may carry three: the stateless V4L2 decoders, Rockchip's own, and the - // stateful V4L2 ones. Which of them a board actually has is a question for the board. - if (RKMPP_DECODE && V4L2_REQUEST_DECODE) { - return "hardware v4l2 stateless, rkmpp or v4l2m2m where the board allows, software otherwise"; - } - if (V4L2_REQUEST_DECODE) { - return "hardware v4l2 stateless or v4l2m2m where the board allows, software otherwise"; - } - if (RKMPP_DECODE) { - return "hardware rkmpp or v4l2m2m where the board allows, software otherwise"; - } - - return "hardware v4l2m2m where the codec allows, software otherwise"; - } -#if defined(_WIN32) - return "hardware d3d11va or dxva2 where the codec allows, software otherwise"; -#elif defined(__APPLE__) - return "hardware videotoolbox where the codec allows, software otherwise"; -#else - return "hardware vaapi or vdpau where the codec allows, software otherwise"; -#endif -} - - // Reads one pixel of the frame being shown. Returns false if there is no frame yet. bool videoGetPixel(int32_t playerHandle, int32_t x, int32_t y, uint8_t *r, uint8_t *g, uint8_t *b) { VideoPlayerT *v = _getPlayer(playerHandle, "videoGetPixel"); @@ -2200,6 +2028,65 @@ bool videoGetPixels(int32_t playerHandle, const uint8_t **pixels, int32_t *pitch } +// The language of a subtitle track as the file labels it, "" when it says nothing. The pointer is +// good until the next call. +const char *videoGetSubtitleLanguage(int32_t playerHandle, int32_t track) { + static char language[LANGUAGE_CODE_BYTES]; + VideoPlayerT *v = _getPlayer(playerHandle, "videoGetSubtitleLanguage"); + AVFormatContext *format = _formatOpen(v->filename); + AVDictionaryEntry *entry = NULL; + int32_t seen = 0; + uint32_t x = 0; + + language[0] = '\0'; + if (format == NULL) { + return language; + } + if (avformat_find_stream_info(format, NULL) >= 0) { + for (x = 0; x < format->nb_streams; x++) { + if (format->streams[x]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) { + continue; + } + if (seen == track) { + entry = av_dict_get(format->streams[x]->metadata, "language", NULL, 0); + if ((entry != NULL) && (entry->value != NULL)) { + SDL_strlcpy(language, entry->value, sizeof(language)); + } + break; + } + seen++; + } + } + _formatClose(&format); + + return language; +} + + +// How many subtitle tracks the file holds, bitmap ones included: they are counted so that the +// numbering matches what a player or ffprobe shows, and videoReadSubtitles says which have words. +int32_t videoGetSubtitleTracks(int32_t playerHandle) { + VideoPlayerT *v = _getPlayer(playerHandle, "videoGetSubtitleTracks"); + AVFormatContext *format = _formatOpen(v->filename); + int32_t count = 0; + uint32_t x = 0; + + if (format == NULL) { + return 0; + } + if (avformat_find_stream_info(format, NULL) >= 0) { + for (x = 0; x < format->nb_streams; x++) { + if (format->streams[x]->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { + count++; + } + } + } + _formatClose(&format); + + return count; +} + + void videoGetVolume(int32_t playerHandle, int32_t *leftPercent, int32_t *rightPercent) { VideoPlayerT *v = _getPlayer(playerHandle, "videoGetVolume"); @@ -2397,6 +2284,119 @@ void videoQuit(void) { } +// One subtitle track read out of the container as SubRip text, which is the form the engine's own +// subtitle loader already takes. The whole file is walked once, decoding nothing but the subtitle +// packets, so this costs a pass over the file and no picture decoding at all. NULL when the track +// does not exist, holds pictures rather than words, or yields nothing; free() what comes back. +char *videoReadSubtitles(int32_t playerHandle, int32_t track) { + VideoPlayerT *v = _getPlayer(playerHandle, "videoReadSubtitles"); + AVFormatContext *format = _formatOpen(v->filename); + AVCodecContext *codec = NULL; + const AVCodec *decoder = NULL; + AVPacket *packet = NULL; + AVSubtitle subtitle; + AVRational timeBase; + char *text = NULL; + char *words = NULL; + char line[SRT_CUE_MAX]; + char from[SRT_TIME_MAX]; + char to[SRT_TIME_MAX]; + size_t used = 0; + size_t room = 0; + int64_t start = 0; + int64_t end = 0; + int32_t stream = -1; + int32_t seen = 0; + int32_t cues = 0; + int got = 0; + uint32_t x = 0; + bool ok = true; + + if (format == NULL) { + return NULL; + } + if (avformat_find_stream_info(format, NULL) < 0) { + _formatClose(&format); + return NULL; + } + for (x = 0; x < format->nb_streams; x++) { + if (format->streams[x]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) { + continue; + } + if (seen == track) { + stream = (int32_t)x; + break; + } + seen++; + } + if (stream < 0) { + _formatClose(&format); + return NULL; + } + timeBase = format->streams[stream]->time_base; + decoder = avcodec_find_decoder(format->streams[stream]->codecpar->codec_id); + codec = (decoder != NULL) ? avcodec_alloc_context3(decoder) : NULL; + packet = av_packet_alloc(); + if ((codec == NULL) || (packet == NULL) || + (avcodec_parameters_to_context(codec, format->streams[stream]->codecpar) < 0) || + (avcodec_open2(codec, decoder, NULL) < 0)) { + utilTrace("Video %d: subtitle track %d has no decoder in this build.", v->id, track); + av_packet_free(&packet); + avcodec_free_context(&codec); + _formatClose(&format); + return NULL; + } + while (ok && (av_read_frame(format, packet) >= 0)) { + if (packet->stream_index != stream) { + av_packet_unref(packet); + continue; + } + memset(&subtitle, 0, sizeof(subtitle)); + got = 0; + if (avcodec_decode_subtitle2(codec, &subtitle, &got, packet) >= 0) { + if (got != 0) { + start = (int64_t)((double)packet->pts * av_q2d(timeBase) * MS_PER_SECOND) + (int64_t)subtitle.start_display_time; + if (subtitle.end_display_time > subtitle.start_display_time) { + end = start + (int64_t)(subtitle.end_display_time - subtitle.start_display_time); + } else { + end = start + (int64_t)((double)packet->duration * av_q2d(timeBase) * MS_PER_SECOND); + } + for (x = 0; (x < subtitle.num_rects) && ok; x++) { + words = _srtFromRect(subtitle.rects[x]); + if (words == NULL) { + continue; + } + cues++; + _srtTime(start, from, sizeof(from)); + _srtTime(end, to, sizeof(to)); + snprintf(line, sizeof(line), "%d\n", cues); + ok = _srtAppend(&text, &used, &room, line) && + _srtAppend(&text, &used, &room, from) && + _srtAppend(&text, &used, &room, " --> ") && + _srtAppend(&text, &used, &room, to) && + _srtAppend(&text, &used, &room, "\n") && + _srtAppend(&text, &used, &room, words) && + _srtAppend(&text, &used, &room, "\n\n"); + free(words); + } + } + avsubtitle_free(&subtitle); + } + av_packet_unref(packet); + } + av_packet_free(&packet); + avcodec_free_context(&codec); + _formatClose(&format); + if (!ok || (cues == 0)) { + free(text); + return NULL; + } + utilTrace("Video %d: subtitle track %d read as %d cues.", v->id, track, cues); + + return text; +} + + // Replaces the audio side with another file (NULL: the video's own audio, or silence when it has // none) and realigns it at the current frame, keeping the volume, the play state and the track // index when the new file has it. False, with the old audio untouched, when the file cannot be read. @@ -2485,12 +2485,6 @@ void videoSetBlend(int32_t playerHandle, bool enabled) { } -// Chosen before any video loads; existing players keep whatever they opened with. -void videoSetHardwareDecoding(bool enabled) { - _hardwareDecoding = enabled; -} - - // Brightens or darkens the picture. The level runs 0 to LUMA_LEVEL_MAX with LUMA_LEVEL_NEUTRAL // changing nothing, and the distance from neutral is applied to every luma sample in eighths, so 0 // halves the brightness and 8 raises it by half. Turning it off, or asking for the neutral level, @@ -2500,6 +2494,12 @@ void videoSetDeinterlace(DeinterlaceE mode) { } +// Chosen before any video loads; existing players keep whatever they opened with. +void videoSetHardwareDecoding(bool enabled) { + _hardwareDecoding = enabled; +} + + void videoSetLuma(int32_t playerHandle, bool enabled, int32_t level) { VideoPlayerT *v = _getPlayer(playerHandle, "videoSetLuma"); diff --git a/testScripts/author/platformer.game b/testScripts/author/platformer.game index bb1d231d3..607809e8e 100644 --- a/testScripts/author/platformer.game +++ b/testScripts/author/platformer.game @@ -1,4 +1,4 @@ --- A game description: data, not code. Singe/AuthorCompile.singe turns it into a Singe game. +-- A game description: data, not code. Forge/AuthorCompile.singe turns it into a Singe game. -- -- Genre one of two. This one uses a single world2d layer and shares nothing with the QTE beside -- it -- no disc, no branching, no time windows -- which is the point: if the core can express diff --git a/testScripts/packedIo/packedIo.singe b/testScripts/packedIo/packedIo.singe index bb7baade3..ac7dc605c 100644 --- a/testScripts/packedIo/packedIo.singe +++ b/testScripts/packedIo/packedIo.singe @@ -63,6 +63,26 @@ check("f:lines count", count, 3) check("f:lines leaves it open", io.type(g), "file") g:close() +-- More than one format a step keeps every value, and a later format coming up empty hands back +-- what came before it with nil in its place, as Lua does; only an empty first read ends the loop. +local pairs = {} + +for a, b in io.lines("packedIo/numbers.txt", "n", "n") do + pairs[#pairs + 1] = { a, b } +end +check("lines n n steps", #pairs, 2) +check("lines n n first", pairs[1][1] + pairs[1][2], 46.5) +check("lines n n second", pairs[2][1], 16) +check("lines n n short", pairs[2][2], nil) + +-- read(0) is the test for the end of the file. +local z = io.open("packedIo/tail.txt") + +check("read 0 with more", z:read(0), "") +z:read("a") +check("read 0 at end", z:read(0), nil) +z:close() + -- Numbers, and the default input. local n = io.open("packedIo/numbers.txt") diff --git a/testScripts/scene52.singe b/testScripts/scene52.singe index f234c90db..06002f6fa 100644 --- a/testScripts/scene52.singe +++ b/testScripts/scene52.singe @@ -59,16 +59,16 @@ function onOverlayUpdate() frames = frames + 1 if not held then - authorKeyDown(0, SCANCODE.RIGHT) + authorKeyDown(0, SCANCODE.RIGHT.value) held = true end -- Jump once, when the hero is under way and has ground beneath it. if (not jumped) and (hx >= JUMP_AT) and playerIsOnGround(hero.node) then - authorKeyDown(0, SCANCODE.SPACE) + authorKeyDown(0, SCANCODE.SPACE.value) jumped = true elseif jumped then - authorKeyUp(0, SCANCODE.SPACE) + authorKeyUp(0, SCANCODE.SPACE.value) end local r = gameUpdate() diff --git a/testScripts/scene55.singe b/testScripts/scene55.singe index 9bb821210..e825248ee 100644 --- a/testScripts/scene55.singe +++ b/testScripts/scene55.singe @@ -77,7 +77,7 @@ local function step() if after == before then debugPrint("RULES FAIL the new rule did not reach the compiled game") - elseif after:find('authorKeyHeld%(SCANCODE%.UP%)') and after:find('authorOnGround%("hero"%)') and after:find('authorJump%("hero"%)') then + elseif after:find('authorKeyHeld%(SCANCODE%.UP%.value%)') and after:find('authorOnGround%("hero"%)') and after:find('authorJump%("hero"%)') then debugPrint("RULES the compiled game carries the new rule") else debugPrint("RULES FAIL the compiled game is missing part of the new rule") @@ -89,7 +89,7 @@ local function step() forgePartDelete() local after = compiled() - if after:find('authorKeyHeld%(SCANCODE%.UP%)') then + if after:find('authorKeyHeld%(SCANCODE%.UP%.value%)') then debugPrint("RULES FAIL the deleted condition is still compiled") else debugPrint("RULES the deleted condition is gone from the compiled game") diff --git a/testScripts/scene57.singe b/testScripts/scene57.singe new file mode 100644 index 000000000..c9836d792 --- /dev/null +++ b/testScripts/scene57.singe @@ -0,0 +1,236 @@ +-- Forge as a game (PLAN section 58): the chooser, the engine callbacks, and the editing that +-- needs no pointer -- entities added, renamed and deleted, a rule built from the pickers, undo. +-- +-- Unlike scene54 to 56 this does not set FORGE_LIBRARY: Forge installs its own callbacks, and +-- the scene drives them the way the engine would, two integers to onKeyPressed. What is +-- asserted is that every key reaches the description and that a rename reaches the rules. +dofile("Forge/Forge.singe") + +local forgeUpdate = onOverlayUpdate +local frames = 0 +local stage = 0 +local opened = nil + + +local function key(code, character) + onKeyPressed(character or 0, code.value) +end + + +local function typeIn(text) + for c in string.gmatch(text, ".") do + key(SCANCODE.A, string.byte(c)) + end +end + + +local function step() + if stage == 1 then + -- The chooser is up and offers a new game at least. + if FORGE.game ~= nil then + debugPrint("GAME FAIL the editor is open before anything was chosen") + end + debugPrint("GAME the chooser offers " .. #forgeFiles() .. " entries") + -- The last entry is always a new game. + for _ = 1, 20 do + key(SCANCODE.DOWN) + end + key(SCANCODE.RETURN) + if FORGE.game == nil then + debugPrint("GAME FAIL ENTER on the chooser opened nothing") + else + opened = FORGE.path + debugPrint("GAME opened " .. opened .. " with " .. #FORGE.game.entities .. " entities") + end + + elseif stage == 2 then + -- Add an entity, name it, and give it a look by typing the fields ENTER walks. + local count = #FORGE.game.entities + + key(SCANCODE.A) + if #FORGE.game.entities ~= count + 1 then + debugPrint("GAME FAIL A did not add an entity") + end + key(SCANCODE.RETURN) -- id + typeIn("coin") + key(SCANCODE.RETURN) -- x + typeIn("300") + key(SCANCODE.RETURN) -- y + typeIn("200") + key(SCANCODE.RETURN) -- kind + key(SCANCODE.ESCAPE) -- untouched + local coin = FORGE.game.entities[#FORGE.game.entities] + + if coin.id == "coin" and coin.x == 300 and coin.y == 200 and coin.look.kind == "box" then + debugPrint("GAME typed an entity in: " .. coin.id .. " at " .. coin.x .. "," .. coin.y) + else + debugPrint("GAME FAIL the typed entity is " .. tostring(coin.id) .. " at " .. tostring(coin.x) .. "," .. tostring(coin.y) .. " " .. tostring(coin.look.kind)) + end + + elseif stage == 3 then + -- Renaming the hero has to reach every rule that names it. + key(SCANCODE.UP) + key(SCANCODE.UP) + key(SCANCODE.UP) + key(SCANCODE.UP) + key(SCANCODE.DOWN) -- ground, then hero + key(SCANCODE.RETURN) + typeIn("player") + key(SCANCODE.ESCAPE) -- ESC puts it back: the hero keeps its name + if FORGE.game.entities[FORGE.selected].id ~= "hero" then + debugPrint("GAME FAIL escape did not put the name back") + end + key(SCANCODE.RETURN) + typeIn("player") + key(SCANCODE.RETURN) -- commits, moves on to x + key(SCANCODE.ESCAPE) + local named = 0 + + for _, rule in ipairs(FORGE.game.rules) do + for _, item in ipairs(rule.act) do + if item.entity == "player" then + named = named + 1 + end + end + end + if FORGE.game.entities[FORGE.selected].id == "player" and named == 3 then + debugPrint("GAME renamed the hero and " .. named .. " actions followed") + else + debugPrint("GAME FAIL rename: id " .. tostring(FORGE.game.entities[FORGE.selected].id) .. ", " .. named .. " actions renamed") + end + + elseif stage == 4 then + -- A rule from the pickers: N, then C picks the first condition, T the first action. + local rules = #FORGE.game.rules + + key(SCANCODE.TAB) + key(SCANCODE.N) + key(SCANCODE.C) + if FORGE.picker == nil then + debugPrint("GAME FAIL C opened no picker") + end + key(SCANCODE.DOWN) + key(SCANCODE.RETURN) + key(SCANCODE.T) + key(SCANCODE.RETURN) + local rule = FORGE.game.rules[#FORGE.game.rules] + + if #FORGE.game.rules == rules + 1 and #rule.when == 1 and #rule.act == 1 then + debugPrint("GAME built a rule from the pickers: when " .. rule.when[1][1] .. " then " .. rule.act[1][1]) + else + debugPrint("GAME FAIL the picked rule has " .. #(rule.when or {}) .. " conditions and " .. #(rule.act or {}) .. " actions") + end + + elseif stage == 5 then + -- Undo takes the action back, then the condition, then the rule; redo brings the rule + -- back, and a change by hand ends the redo history. + local rules = #FORGE.game.rules + + key(SCANCODE.U) + key(SCANCODE.U) + key(SCANCODE.U) + if #FORGE.game.rules == rules - 1 then + debugPrint("GAME three undos took the rule back out") + else + debugPrint("GAME FAIL after three undos there are " .. #FORGE.game.rules .. " rules, not " .. (rules - 1)) + end + key(SCANCODE.R) + if (#FORGE.game.rules == rules) and (#FORGE.game.rules[rules].when == 0) then + debugPrint("GAME redo brought the empty rule back") + else + debugPrint("GAME FAIL redo left " .. #FORGE.game.rules .. " rules") + end + key(SCANCODE.U) + key(SCANCODE.N) + key(SCANCODE.R) + if #FORGE.redo == 0 and #FORGE.game.rules == rules then + debugPrint("GAME a new change emptied the redo history") + else + debugPrint("GAME FAIL redo history survived a change: " .. #FORGE.redo .. " kept") + end + key(SCANCODE.U) + + elseif stage == 6 then + -- Duplicate and delete in the entity list. + key(SCANCODE.TAB) + local count = #FORGE.game.entities + + key(SCANCODE.D) + key(SCANCODE.DELETE) + if #FORGE.game.entities == count then + debugPrint("GAME duplicate then delete leaves " .. count .. " entities") + else + debugPrint("GAME FAIL duplicate then delete leaves " .. #FORGE.game.entities) + end + + elseif stage == 7 then + -- ESC on unsaved work warns, S saves, ESC then leaves; the chooser lists the file. + key(SCANCODE.ESCAPE) + if FORGE.game == nil then + debugPrint("GAME FAIL one ESC closed unsaved work") + end + key(SCANCODE.S) + key(SCANCODE.ESCAPE) + if FORGE.game ~= nil then + debugPrint("GAME FAIL ESC did not close a saved description") + end + local listed = false + + for _, entry in ipairs(forgeFiles()) do + if entry.path == opened then + listed = true + end + end + debugPrint("GAME closed; the chooser lists what was saved: " .. tostring(listed)) + + elseif stage == 8 then + -- Open it again from the chooser -- it is listed first -- and the rename is in the file. + for _ = 1, 20 do + key(SCANCODE.UP) + end + key(SCANCODE.RETURN) + if FORGE.game == nil then + debugPrint("GAME FAIL could not reopen the saved description") + else + local found = false + + for _, entity in ipairs(FORGE.game.entities) do + if entity.id == "player" then + found = true + end + end + debugPrint("GAME reopened " .. FORGE.path .. "; the rename was saved: " .. tostring(found)) + end + + elseif stage == 9 then + -- B builds beside the runtime, which is what makes the result playable. + key(SCANCODE.B) + local runtime = io.open(singeGetDataPath() .. "Author.singe", "r") + + if runtime == nil then + debugPrint("GAME FAIL build left no runtime beside the preview") + else + runtime:close() + debugPrint("GAME the build put the runtime beside the preview") + end + debugPrint("GAME RESULT done") + end +end + + +function onOverlayUpdate() + frames = frames + 1 + if frames % 12 == 0 and stage < 10 then + stage = stage + 1 + step() + end + forgeUpdate() + if frames == 6 or frames == 40 or frames == 60 or frames == 100 then + singeScreenshot() + end + if frames > 130 then + singeQuit() + end + + return OVERLAY_UPDATED +end