diff --git a/README.md b/README.md index 2722d74..c4724fc 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,9 @@ On non-Hermes based setups of React Native, this file usually contains minified/ ## Installation -The application itself only relies on the Python 3.x standard library for now. +The command-line application itself only relies on the Python 3.x standard library for now. + +The experimental GUI also relies on the a few external modules (listed in the `pyproject.toml` file below) to work. You can install the tool through one the following commands: diff --git a/docs/GUI index database format.md b/docs/GUI index database format.md new file mode 100644 index 0000000..987ab77 --- /dev/null +++ b/docs/GUI index database format.md @@ -0,0 +1,71 @@ +# GUI index database format + +This file summarizes and describes the data that should be indexed in order to speed up/allow for checking cross-refernces/dumping decompiled code over the `hermes-dec` GUI. + +## Summary of use/dataset pairs + +XX + +In order to *implement viewing cross-references for function cross-references (both from the side pane of the application and the decompiled code) of the `hermes-dec` web UI*, we should implement the following functionality: +- XX + +In order to *implement viewing cross-references for raw strings in the HBC file(both from the side pane of the application and the decompiled code) of the `hermes-dec` web UI*, we should implement the following functionality: +- XX + +In order to *implement searching for function names and offsets from the left side pane of the `hermes-dec` web UI*, we should implement the following functionality: +- XX + +In order to *implement searching for raw strings in the HBC file from the left side pane of the `hermes-dec` web UI*, we should implement the following functionality: +- XX + +In order to *implement viewing decompiled code with cross-references in the code browser of the `hermes-dec` web UI*, we should implement the following functionality: +- XX + + +## Cross-reference database + +### Code common to launching the indexer subprocess + +- In `server.py` entry point -> `WebsocketServer.handle_ws_client` + - `server.py` -> `WebsocketClient.create_file` + - `project_meta.py` -> `ProjectInstanceManager.new_with_name` + - `self.subdir_path` = `join(user_data_dir('HermesDec', 'P1Security'), self.gen_unique_dirname(name))` + - Creates: `self.subdir_path` = `/home/marin/.local/share/HermesDec/by-date/2023-09-06T20:53:55-samplehbc/' + '{index.android.bundle,metadata.json}` + - `server.py` -> `WebsocketClient.parse_file` + - `project_meta.py` -> `ProjectInstanceManager.save_to_disk` + - `project_meta.py` -> `ProjectInstanceManager.write_or_update_metadata` (writes `metadata.json` in the project directory) + - `ProjectInstanceManager.write_or_update_metadata` also called here for updating the bytecode version + - `server.py` -> `WebsocketClient.spawn_indexer` + - (xxx: code to write with ensure_future(asyncio.spawn_subprocess XX) + launching subprocess as described in the GUI index worker pipe IPC.md" file + watching the output and doing communication with the Websocket and local object state when needed) + +### Functions/function and function/built-in cross-references (SQLite DB) + +The application should maintain, in an SQLite database, a repository of cross-refences between HBC functions and the string IDs, function IDs, and built-in IDs they reference. + +This information should be referenced into the SQLite/SQLAlchemy defined in "`src/gui/index_worker/index_db.py`". + +In order to perform clean paralellization and to avoid daemon thread-related or GIL-related issues, indexing should be done into a subprocess that frequently reports its state of indexing through a `stdout`-based pipe mechanism, along with other indexing tasks described in the present document, as described in the `GUI index worker pipe IPC.md` document present in the current directory. + +XX Implementation notes +XX + +Call chain: + + + + +### Function strings cross-references (SQLite DB) + +XX + +## Functions list (free-text search/SQLite DB?) + +XX + +## Strings list (free-text search/SQLite DB?) + +XX + +## Pre-generated decompiled code with inline x-refs (SQLite DB/custom markup?) + +XX diff --git a/docs/GUI index worker pipe IPC.md b/docs/GUI index worker pipe IPC.md new file mode 100644 index 0000000..9c7a110 --- /dev/null +++ b/docs/GUI index worker pipe IPC.md @@ -0,0 +1,57 @@ +This document presents the interprocess communication used for the decompiler/index worker to communicate with the main Websocket server worker over the `hermes-dec` GUI. + +Communication is done over unidirectional unbuffered process pipe (stdout) communication, and passing CLI arguments + watching the present of an otherwise input pipe (stdin) for cancelling cleanly on the other side whenever the parent process gets terminated. + +## Subprocess args + +The subprocess shall have the following arguments: + +``` +orig_argv[0]: Python interpreter +orig_argv[1]: Worker script full path +orig_argv[2]: Data folder path +orig_argv[3]: Raw .HBC file path +``` + +## Cancel signal (parent->child) + +Just a SIGINT/simulated CTRL+C should be used for ending out the subprocess when needed (for example when the parent process gets interrupted itself) + +The child watches the presence of an otherwise unused stdin pipe in a background/daemon thread, and interrupts its main thread whenever the stdin pipe gets broken. + +## Child end signal (child->parent) + +The subprocess should terminate when the whole document has been indexed (or was already indexed and the indexing data can be loaded immediately). + +## Line-delimited JSON subprocess pipe communication spec (child->parent) + +The following message types shall be implemented, and serialized with a `\n` endline through stdout: + +- `indexing_state`: inform the Websocket worker process about the state of the indexing (immediately after he index has been loaded from disk if it was alreay generated earlier, or at every step of the data generation process) along with an estimated process rate ranging from 0 to 100 when finished +Example: ```json +{ + "type": "indexing_state", + "state": "indexing_functions" (Later: | "decompiling_code") | "fully_indexed" (Later: | "XXX"), + (Later: "process_percent": 95) +} +``` + +- (Later: ) `indexing_status_log`: transmit a readable log string about the indexing status of the document +Example: ``json +{ + "type": "indexing_status_log", + "message": "Text line to log" +} +``` + +- `` + +## Fetching the indexed and text chunk-decompiled data + +Fetching the indexed and text chunk-decompiled data is done in the process of the main worker, thanks to the shared SQLAlchemy ORM code with the main process. (Once all the code has been decompiled, the secondary/index worker emits its status and closes) + +## TODO also + +Check whether the pass1b/1c of the decompilation pipeline are still required when just decompiling and not rendering the UI graph. (=> Yes, it will be used to optionally reconstruct contron flow later) + +(Also obviously send their text status comments regularly to the new decompilation pipeline processing console at the bottom pane of the web UI in order to be able to debug where the background decompilation process takes times, freezes, crashes, etc.) \ No newline at end of file diff --git a/docs/GUI server websocket protocol.md b/docs/GUI server websocket protocol.md new file mode 100644 index 0000000..3297d73 --- /dev/null +++ b/docs/GUI server websocket protocol.md @@ -0,0 +1,101 @@ +# GUI Websocket protocol + +The browser/Websocket-based GUI of `hermes-dec` should implement the following JSON messages: + +``` +S->C {"type": "recent_files", recent_files: [ + { + "file_hash": ..., + "orig_name": ... + }, ... +]} (saved files w/ context, etc.) + +C->S {"type": "open_file_by_hash", "file_hash": ""} (load saved context) + +S->C {"type": "file_hash_unknown", "file_hash": ""} + +// (Implement first:) +C->S {"type": "begin_tranfer", "file_name": "x.index.bundle OR apk", "file_hash": ""} +(Raw binary buffer websocket transmission...) +C->S {"type": "end_transfer"} + +// (Maybe implement later): We should both prompt the user using our own file shell (in order to have the full path) and copy the file name + metadata + size + origin path + create a parameters file in ~/.hermes-dec: +C->S {"type": "open_file", "disk_path": "(...)/x.index.bundle"} + +S->C {"type": "pop_message", "icon": "error", "message_html": "Could not open file:

XXX
"} + +S->C { + "type": "file_opened", + "file_metadata": { + "bytecode_version": 76, + "db_created_time": "2023-04-07T08:33:57.200578", + "db_updated_time": "2023-07-18T13:53:13.827344", + "dir_disk_path": "/home/marin/.local/share/HermesDec/by-date/2023-04-07T08:33:56-indexandroidbundle", + "file_hash": "b2b514abac3dedbc83619d0e782ae61a0d958038a432f01a11b22d09537d7090", + "file_size": 3482132, + "orig_name": "index.android.bundle", + "raw_disk_path": "/home/marin/.local/share/HermesDec/by-date/2023-04-07T08:33:56-indexandroidbundle/index.android.bundle" + } +} + +S->C { + "type": "console_log", + "data": "Raw text\n" +} + + +S->C { + "type": "console_error_log", + "data": "Raw text\n" +} + +C->S +{ + "type": "get_table_data", + "table": "functions", + "text_filter": "xx" or null, + "current_row": 21, + "page": 42 +} + +S->C +{ + "type": "table_data", + "table": "functions", + "result_count": 452, + "pages": 12, + "current_page": 1, + "model": { + "has_search_bar": true, + "has_pagination": true, + "pagination_thresold": 350, + "columns": [ + 'Name', 'Offset' + ], + "has_visible_headers": true, + "has_selectable_rows": true + }, + "displayed_rows": [{ + "id": 42, + "cells": ["fun_000001", '%08x', '42'] + }, ...]} + +C->S {"type": "analyze_function", "function_id": 49} +// Will return disassembly graph + cross-references + decompiled code with clickable expandable "..." buttons for nested closures for a given function + +S->C {"type": "analyzed_function", "function_id": 49, "blocks": [{ + "grid_x": 1, // Used for rendering with the CSS grid layout + "grid_y": 2, + "text": "Assembly...\n", + "child_nodes": [index, index...], + "child_error_nodes": [index, index...], + "parent_nodes": [index, index...], + "parent_error_nodes": [index, index...] +}]} + +(+ a progress bar upstream packet?) + +(+ a search feature?) + +(+ a code export button?) +``` \ No newline at end of file diff --git a/docs/Pass1c Cycling detection + Block Distance from root Weighting Algo.md b/docs/Pass1c Cycling detection + Block Distance from root Weighting Algo.md new file mode 100644 index 0000000..ccfffc5 --- /dev/null +++ b/docs/Pass1c Cycling detection + Block Distance from root Weighting Algo.md @@ -0,0 +1,160 @@ +This file contains detailed write-up of the thoughts +produced while drafting the algorithm present in the +`src/decompilation/pass1c_visit_code_paths.py` code file (the +current algorithm is simpler than this). + +## Documentation of sub-algorithm 1 (draft -1) + + In sub-algo 1: + + At a given moment, a visitable node may either be + tip-of-branch or not tip-of-branch (branch = code exploration path). + + At a given moment, the tip-of-branches are stored in the + global state of the algorithm (through the fact that + the branches are stored in the "alg_state.branches" + list) + + The only initial branch is just composed of the root node of the tree. + + Branches fork when, a child has been linked (found/marked as + a visited child) in a tip-of-branch node, but other non-visited + children remain. + + Branches merge when, after any update of the ongoing branches, + the same node has gotten to be the end of two branches. + => This is done so that the new algorithm is *way* less greedy + than the former one. + + Branches end when, all the children of a given tip-of-branch + have been marked visited. They are then suppressed from "alg_state.branches". + + Child nodes are marked cycling during the update process of a + branch (simple update or consequential merge), when they + already been seen in a given branch. + + The sub-algorithm 1 ends when, all branches have ended in + the global state ("alg_state.branches" is empty). + + +----------------- + +## Documentation of the `VisitableNode.match_against_child` function (draft -1) + + + This function is called by the main routine of + sub-algorithm 1 when the current node has been + pre-identified as a potentiel child of any of + the current tip-of-branch nodes (see the definition above) + and pre-sorted so that we visit nodes upper in + the global basic blocks tree first, and that we + maintain less code exploration paths/branches in + memory in parallel, and that our algorithm is more + BFS than DFS and hence shorter to execute in the + use case of sub-algorithm 1 overall. + + It should first check whether the passed other node + of the tree is actually part of the unvisited + children of the present tip-of-branch node. + + If this is the case, + - We should mark the concerned child as visited + in our local state + - We should check whether all the children of the + current object are now considered visited in + our local state, and act upon it: + (This should be performed by "alg_state.do_needed_fork_and_merge_ops") + - In not, we should do fork action + (are there still unvisited nodes present within + the local state for the current object right now?) + - We should change the tip-of-branch the current + node is part of (leaving a forked copy if there + are still unvisited child nodes referenced in + the local state for the current node) + + [tricky - please clarify] + - We should check for necessity of merge action + (comparing all the branch present in the + "alg_state.branches" array and checking + whether, with the new tip-of-branches in + presence, there are now code paths to be + merged together) + - We should perform the merge if this is the + case, retaining nodes + [/tricky - please clarify] + + - We should check for potential cycling across + the branches having the checked over child + as their (new) tip, after the fork and + merge operations described above have been + conducted + - We should perform a partial burn action if there + is indeed cycling + - And check for the necessity of fork-and-merge + action again + (call "alg_state.do_needed_fork_and_merge_ops" again) + - We should perform the delete branch action if the + new tip-of-branch has no child + + def match_against_child(self, other_node): + + +------------------------- + + +# Tentative sub-algorithm 1 (only to detect and list cycling points) - draft -2 + +(explained more in the comments about the "VisitableNode" +class definition below) + + (Pre-step but seldom useful: If there is no backward jump + in the subroutine, there is no cycling, exit the current algorithm) + + Define a list of exploration paths, with the initial exploration + path only containing the root of the graph. + + We'll do a kind of weighted BFS-alike algorithm with a queue + of vertices (nodes) that we will, + a. at each iteration, process with + aa. filtering temporarily only which have a parent + in an existing exploration path AND had not + all their childs? parents? both? visited + ab. (and keeping these sorted upon origin position in + the code) + ac. Obviously process the nodes that have only one + extra parent to be visited first! + ad. (Put point which have been determined to cycle + the farthest possible in the list?) + + ^ Likely use a pattern like this + + sorted( + x, + ( + key1, + -key2, + -key3, + key4, + key5, + .. + ) + ) + + (...) + a3. Keep track of the max read instruction path + (still in the structure) in the meantime + + a4x. Whenever we branch to 2+ unvisited nodes in the + graph, split exploration paths + (Each exploration path having n parents with + split points, resembling a bit the principle + of Git commit trees) + a4y. Whenever a node/edge have had all their inbound + paths visited, visit back up to split points + and see which exploration paths to merge + + (...) + a4z. Merge cycling points whenever required + + (In the end we'll have a list of the cycling points in the graph + + possibly imperfect max insn weights calculated everywhere) diff --git a/pyproject.toml b/pyproject.toml index 6a22ac9..0e3a7ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,12 +26,17 @@ classifiers = [ ] license = "AGPL-3.0-or-later" license-files = ["LICENSE"] +dependencies = [ + "appdirs>=1.4.4", + "sqlalchemy>=2.0.48", + "unidecode>=1.4.0", + "websockets>=16.0", +] [project.optional-dependencies] codegen = ["clang==12.0.1"] test = [ "pytest==9.0.2" ] - [project.urls] Homepage = "https://github.com/P1sec/hermes-dec" Repository = "https://github.com/P1sec/hermes-dec" @@ -41,6 +46,7 @@ Issues = "https://github.com/P1sec/hermes-dec/issues" hbc-decompiler = "hermes_dec.decompilation.hbc_decompiler:main" hbc-disassembler = "hermes_dec.disassembly.hbc_disassembler:main" hbc-file-parser = "hermes_dec.parsers.hbc_file_parser:main" +hermes-dec-gui = "hermes_dec.gui.web_launcher:main" hermes-dec-regen-pydefs = "hermes_dec.utils.regen_hbc_opcodes:main" hermes-dec-regen-pydefs-regexp = "hermes_dec.utils.regen_hbc_regex_parser:main" hermes-dec-regen-html = "hermes_dec.utils.hermes_bytecode_structs_table_gen:main" diff --git a/src/hermes_dec/decompilation/defs.py b/src/hermes_dec/decompilation/defs.py index d827653..50c6286 100644 --- a/src/hermes_dec/decompilation/defs.py +++ b/src/hermes_dec/decompilation/defs.py @@ -1,8 +1,9 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 # -*- encoding: Utf-8 -*- from typing import List, Set, Dict, Tuple, Optional, Sequence, Union, Any from os.path import dirname, realpath from dataclasses import dataclass +from enum import IntEnum from re import sub import sys @@ -33,20 +34,76 @@ class HermesDecompiler: indent_level: int = 0 # Used while producing decompilation output +# Output to the upper-lever class (WIP) +class DecompiledFunctionTextChunkType(IntEnum): + RAW_TEXT = 1 + CLOSURE_REF = 2 + + +class DecompiledFunctionTextChunk: + function_id: int + + # WIP .. + + # This should encompass the scope for a HBC assembly-level basic # block (retranscribed as a for(;;) switch { case X: } case in -# the decompiled code, if not matching a particular NestingFrame +# the decompiled code, if not matching a particular NestedFrame # object as defined below) # This will provide the ability to construct a graph of # the basic assembly blocks in the disassembled/decompiled # output class BasicBlock: + # File offsets of the basic block in the Hermes bytecode file. start_address: int end_address: int + def __repr__(self): + out = '[' + out += '%08x' % self.start_address + ' -> ' + out += '%08x' % self.end_address + ' : ' + out += '%s' % self.insn_count + ' insns' + if self.max_acc_insn_weight: + out += ' weight ' + '%s' % self.max_acc_insn_weight + out += ']' + return out + + # Individual number of instructions in the basic block + insn_count: int + + # (pass1c algorithm:) + + # Number of instructions that will be gone through + # when taking the longest possible code path to + # the present basic block without cycling. + # + # => Calculated through, for each "parent_nodes", + # adding up the "insn_count" of each block where + # "max_acc_insn_weight" is not set with the + # "max_acc_insn_weight" value of the first block + # in the ascending block graph where it is set. + # + # => For the first/root block of the code graph, + # this value is 0. + # + # => Used for choosing which block should be + # preferred to be the main branch or the conditional + # branch in case of if() conditions, etc. (the branch + # going the block with the largest "max_acc_insn_weight" + # should be preferred to be main, and the block with + # the smallest value should be preferred to be conditional) + # + # (This is temporarily deprecated, and will + # be reintegrated if deemed useful) + max_acc_insn_weight: int = 0 + + # State that will be used in "pre_render_graph.py": + rendered: bool = False + marked_to_render: bool = False + # These flags should indicate whether we have # encountered cycling in - # "graph_traversers/step2_visite_code_paths.py" + # "graph_traversers/step1c_visit_code_paths.py" # (which indicates the presence of a loop): may_be_cycling_anchor: bool = False may_be_cycling_target: bool = False @@ -57,8 +114,8 @@ class BasicBlock: is_unconditional_throw_anchor: bool = False is_unconditional_return_end: bool = False - # These flags delimite blocks that have the - # code to continue somewhere else + # These flags delimite blocks that have exactly + # one determined branching end is_unconditional_jump_anchor: bool = False is_yield_action_anchor: bool = False @@ -68,6 +125,8 @@ class BasicBlock: is_conditional_jump_anchor: bool = False if_switch_action_anchor: bool = False + # When one the flags above are set, this provides + # insight about the instruction performing the jump anchor_instruction: Optional[ParsedInstruction] = None jump_targets_for_anchor: Optional[List[int]] = None @@ -96,7 +155,7 @@ class BasicBlock: # Whether this basic block should still be visible in the # decompiled code (switch this attribute to False when - # a NestingFrame totally overlaps the concerned block) + # a NestedFrame totally overlaps the concerned block) stay_visible: bool = True @@ -139,6 +198,10 @@ class DecompiledFunctionBody: ] # Unreacheable address associated with a jump instruction jump_targets: Set[int] # Jump target address + instruction_boundaries: List[ + int + ] # Offset for each instruction and end of code, the first item is zero + is_closure: bool = False is_async: bool = False is_generator: bool = False diff --git a/src/hermes_dec/decompilation/hbc_decompiler.py b/src/hermes_dec/decompilation/hbc_decompiler.py index 7df4e2f..dd7ccbe 100755 --- a/src/hermes_dec/decompilation/hbc_decompiler.py +++ b/src/hermes_dec/decompilation/hbc_decompiler.py @@ -1,7 +1,10 @@ #!/usr/bin/env python3 # -*- encoding: Utf-8 -*- +from logging import getLogger, debug, DEBUG from os.path import realpath, dirname from argparse import ArgumentParser +from datetime import datetime +from time import time import sys SCRIPT_DIR = dirname(realpath(__file__)) @@ -12,6 +15,12 @@ from hermes_dec.parsers.hbc_file_parser import HBCReader, FunctionKind from hermes_dec.decompilation.pass1_set_metadata import pass1_set_metadata +from hermes_dec.decompilation.pass1b_make_basic_blocks import ( + pass1b_make_basic_blocks, +) +from hermes_dec.decompilation.pass1c_visit_code_paths import ( + pass1c_visit_code_paths, +) from hermes_dec.decompilation.pass2_transform_code import pass2_transform_code from hermes_dec.decompilation.pass3_parse_forin_loops import ( pass3_parse_forin_loops, @@ -58,13 +67,34 @@ def decompile_function(state: HermesDecompiler, function_id: int, **kwargs): function_id ] - pass1_set_metadata(state, dehydrated) - - pass2_transform_code(state, dehydrated) - - pass3_parse_forin_loops(state, dehydrated) + debug('') + debug( + '[%s] Decompiling function #%d ("%s")...' + % ( + datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + function_id, + state.hbc_reader.strings[dehydrated.function_object.functionName], + ) + ) - pass4_name_closure_vars(state, dehydrated) + for step in [ + pass1_set_metadata, + pass1b_make_basic_blocks, + # pass1c_visit_code_paths, # <-- Commented right now + pass2_transform_code, + pass3_parse_forin_loops, + pass4_name_closure_vars, + ]: + start_time = time() + step(state, dehydrated) + debug( + '[%s] Ran "%s" in %0.5f seconds...' + % ( + datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + step.__name__, + time() - start_time, + ) + ) dehydrated.output_code(state) @@ -93,11 +123,16 @@ def main(): args = ArgumentParser() + args.add_argument('-d', '--debug', action='store_true') + args.add_argument('input_file') args.add_argument('output_file', nargs='?') args = args.parse_args() + if args.debug: + getLogger().setLevel(DEBUG) + state = HermesDecompiler() state.input_file = args.input_file state.output_file = args.output_file diff --git a/src/hermes_dec/decompilation/pass1_set_metadata.py b/src/hermes_dec/decompilation/pass1_set_metadata.py index a8a942d..2d0af6f 100644 --- a/src/hermes_dec/decompilation/pass1_set_metadata.py +++ b/src/hermes_dec/decompilation/pass1_set_metadata.py @@ -44,9 +44,13 @@ def pass1_set_metadata( # As well as Jump, Switch, Yield + function_body.instruction_boundaries = [] + for instruction in parse_hbc_bytecode( function_body.function_object, state.hbc_reader ): + function_body.instruction_boundaries.append(instruction.original_pos) + if instruction.inst.name[0] == 'J' or instruction.inst.name.startswith( 'SaveGenerator' ): @@ -69,144 +73,7 @@ def pass1_set_metadata( elif instruction.inst.name == 'Throw': function_body.throw_anchors[instruction.next_pos] = instruction - """ - Make basic blocks out of assembly - """ - - error_handlers = ( - state.hbc_reader.function_id_to_exc_handlers[function_body.function_id] - if function_body.function_object.hasExceptionHandler - else [] + # Add the end of code offset to "function_body.instruction_boundaries" + function_body.instruction_boundaries.append( + instruction.next_pos if function_body.instruction_boundaries else 0 ) - - basic_block_boundaries = ( - {0} - | function_body.try_starts.keys() - | function_body.try_ends.keys() - | function_body.catch_targets.keys() - | function_body.jump_anchors.keys() - | function_body.ret_anchors.keys() - | function_body.throw_anchors.keys() - | function_body.jump_targets - ) - - basic_blocks = function_body.basic_blocks = [] - - start_to_basic_block: Dict[int, BasicBlock] = {} - end_to_basic_block: Dict[int, BasicBlock] = {} - - # Create the basic blocks that will constitute the control - # flow graph for the current function - - basic_block_start = 0 - may_have_fallen_through: bool = False - for basic_block_end in sorted(basic_block_boundaries)[1:]: - basic_block = BasicBlock() - basic_block.start_address = basic_block_start - basic_block.end_address = basic_block_end - - start_to_basic_block[basic_block_start] = basic_block - end_to_basic_block[basic_block_end] = basic_block - - basic_block.child_nodes = [] - basic_block.parent_nodes = [] - basic_block.error_handling_child_nodes = [] - basic_block.error_handling_parent_nodes = [] - - if may_have_fallen_through: - basic_block.parent_nodes.append(basic_blocks[-1]) - basic_blocks[-1].child_nodes.append(basic_block) - - may_have_fallen_through = True - if basic_block_end in function_body.ret_anchors: - may_have_fallen_through = False - basic_block.anchor_instruction = function_body.ret_anchors[ - basic_block_end - ] - basic_block.is_unconditional_return_end = True - elif basic_block_end in function_body.throw_anchors: - may_have_fallen_through = False - basic_block.anchor_instruction = function_body.throw_anchors[ - basic_block_end - ] - basic_block.is_unconditional_throw_anchor = True - elif basic_block_end in function_body.jump_anchors: - op = function_body.jump_anchors[basic_block_end] - basic_block.anchor_instruction = op - op_name = op.inst.name - if op_name in ('Jmp', 'JmpLong'): - may_have_fallen_through = False - basic_block.jump_targets_for_anchor = [ - op.original_pos + op.arg1 - ] - basic_block.is_unconditional_jump_anchor = True - elif op_name == 'SwitchImm': - may_have_fallen_through = False - basic_block.jump_targets_for_anchor = sorted( - {op.original_pos + op.arg3, *op.switch_jump_table} - ) - basic_block.if_switch_action_anchor = True - elif op_name in ('SaveGenerator', 'SaveGeneratorLong'): - may_have_fallen_through = True - basic_block.jump_targets_for_anchor = [ - op.original_pos + op.arg1 - ] - basic_block.is_yield_action_anchor = True - elif op_name[0] == 'J': - may_have_fallen_through = True - basic_block.jump_targets_for_anchor = [ - op.original_pos + op.arg1 - ] - basic_block.is_conditional_jump_anchor = True - else: - raise ValueError - - basic_blocks.append(basic_block) - - basic_block_start = basic_block_end - - for basic_block in basic_blocks: - if basic_block.jump_targets_for_anchor: - # Link graphes between these in case of - # jump/switch/yield instructions - - for jump_target in basic_block.jump_targets_for_anchor: - jump_target_block = start_to_basic_block[jump_target] - if jump_target_block not in basic_block.child_nodes: - basic_block.child_nodes.append(jump_target_block) - if basic_block not in jump_target_block.parent_nodes: - jump_target_block.parent_nodes.append(basic_block) - - # Link graphes between these when error - # handling is present - - for error_handler in error_handlers: - if ( - basic_block.start_address - <= error_handler.start - < basic_block.end_address - ) or ( - basic_block.start_address - < error_handler.end - <= basic_block.end_address - ): - error_handler_block = start_to_basic_block[ - error_handler.target - ] - - if ( - error_handler_block - not in basic_block.error_handling_child_nodes - ): - basic_block.error_handling_child_nodes.append( - error_handler_block - ) - if ( - basic_block - not in error_handler_block.error_handling_parent_nodes - ): - error_handler_block.error_handling_parent_nodes.append( - basic_block - ) - - # WIP... diff --git a/src/hermes_dec/decompilation/pass1b_make_basic_blocks.py b/src/hermes_dec/decompilation/pass1b_make_basic_blocks.py new file mode 100644 index 0000000..9c28913 --- /dev/null +++ b/src/hermes_dec/decompilation/pass1b_make_basic_blocks.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +# -*- encoding: Utf-8 -*- +from typing import List, Tuple, Dict, Set, Sequence, Union, Optional, Any +from os.path import dirname, realpath +from collections import defaultdict + +from hermes_dec.decompilation.defs import ( + HermesDecompiler, + BasicBlock, + DecompiledFunctionBody, +) +from hermes_dec.parsers.hbc_bytecode_parser import parse_hbc_bytecode + + +def pass1b_make_basic_blocks( + state: HermesDecompiler, function_body: DecompiledFunctionBody +): + """ + Make basic blocks out of assembly + """ + + error_handlers = ( + state.hbc_reader.function_id_to_exc_handlers[function_body.function_id] + if function_body.function_object.hasExceptionHandler + else [] + ) + + basic_block_boundaries = ( + {0, function_body.instruction_boundaries[-1]} + | function_body.try_starts.keys() + | function_body.try_ends.keys() + | function_body.catch_targets.keys() + | function_body.jump_anchors.keys() + | function_body.ret_anchors.keys() + | function_body.throw_anchors.keys() + | function_body.jump_targets + ) + + basic_blocks = function_body.basic_blocks = [] + + start_to_basic_block: Dict[int, BasicBlock] = {} + end_to_basic_block: Dict[int, BasicBlock] = {} + + # Create the basic blocks that will constitute the control + # flow graph for the current function + + basic_block_start = 0 + may_have_fallen_through: bool = False + for basic_block_end in sorted(basic_block_boundaries)[1:]: + basic_block = BasicBlock() + basic_block.start_address = basic_block_start + basic_block.end_address = basic_block_end + + basic_block.insn_count = function_body.instruction_boundaries.index( + basic_block_end + ) - function_body.instruction_boundaries.index(basic_block_start) + + start_to_basic_block[basic_block_start] = basic_block + end_to_basic_block[basic_block_end] = basic_block + + basic_block.child_nodes = [] + basic_block.parent_nodes = [] + basic_block.error_handling_child_nodes = [] + basic_block.error_handling_parent_nodes = [] + + if may_have_fallen_through: + basic_block.parent_nodes.append(basic_blocks[-1]) + basic_blocks[-1].child_nodes.append(basic_block) + + may_have_fallen_through = True + if basic_block_end in function_body.ret_anchors: + may_have_fallen_through = False + basic_block.anchor_instruction = function_body.ret_anchors[ + basic_block_end + ] + basic_block.is_unconditional_return_end = True + elif basic_block_end in function_body.throw_anchors: + may_have_fallen_through = False + basic_block.anchor_instruction = function_body.throw_anchors[ + basic_block_end + ] + basic_block.is_unconditional_throw_anchor = True + elif basic_block_end in function_body.jump_anchors: + op = function_body.jump_anchors[basic_block_end] + basic_block.anchor_instruction = op + op_name = op.inst.name + if op_name in ('Jmp', 'JmpLong'): + may_have_fallen_through = False + basic_block.jump_targets_for_anchor = [ + op.original_pos + op.arg1 + ] + basic_block.is_unconditional_jump_anchor = True + elif op_name == 'SwitchImm': + may_have_fallen_through = False + basic_block.jump_targets_for_anchor = sorted( + {op.original_pos + op.arg3, *op.switch_jump_table} + ) + basic_block.if_switch_action_anchor = True + elif op_name in ('SaveGenerator', 'SaveGeneratorLong'): + may_have_fallen_through = True + basic_block.jump_targets_for_anchor = [ + op.original_pos + op.arg1 + ] + basic_block.is_yield_action_anchor = True + elif op_name[0] == 'J': + may_have_fallen_through = True + basic_block.jump_targets_for_anchor = [ + op.original_pos + op.arg1 + ] + basic_block.is_conditional_jump_anchor = True + else: + raise ValueError + + basic_blocks.append(basic_block) + + basic_block_start = basic_block_end + + if basic_blocks: + basic_blocks[0].max_acc_insn_weight = 0 + + for basic_block in basic_blocks: + if basic_block.jump_targets_for_anchor: + # Link graphes between these in case of + # jump/switch/yield instructions + + for jump_target in basic_block.jump_targets_for_anchor: + jump_target_block = start_to_basic_block[jump_target] + if jump_target_block not in basic_block.child_nodes: + basic_block.child_nodes.append(jump_target_block) + if basic_block not in jump_target_block.parent_nodes: + jump_target_block.parent_nodes.append(basic_block) + + # Link graphes between these when error + # handling is present + + for error_handler in error_handlers: + if ( + basic_block.start_address + <= error_handler.start + < basic_block.end_address + ) or ( + basic_block.start_address + < error_handler.end + <= basic_block.end_address + ): + error_handler_block = start_to_basic_block[ + error_handler.target + ] + + if ( + error_handler_block + not in basic_block.error_handling_child_nodes + ): + basic_block.error_handling_child_nodes.append( + error_handler_block + ) + if ( + basic_block + not in error_handler_block.error_handling_parent_nodes + ): + error_handler_block.error_handling_parent_nodes.append( + basic_block + ) + + # WIP... diff --git a/src/hermes_dec/decompilation/pass1c_visit_code_paths.py b/src/hermes_dec/decompilation/pass1c_visit_code_paths.py new file mode 100644 index 0000000..8dc40f7 --- /dev/null +++ b/src/hermes_dec/decompilation/pass1c_visit_code_paths.py @@ -0,0 +1,271 @@ +#!/usr/bin/env python3 +# -*- encoding: Utf-8 -*- +from typing import List, Tuple, Dict, Set, Sequence, Union, Optional, Any +from os.path import dirname, realpath +from collections import defaultdict + +from hermes_dec.decompilation.defs import ( + HermesDecompiler, + BasicBlock, + DecompiledFunctionBody, +) +from hermes_dec.parsers.hbc_bytecode_parser import parse_hbc_bytecode + +""" + Please see the "docs/Pass1c Cycling detection + Block Distance from root + Weighting Algo.md" file for documentation regarding the functioning of the + algorithms present in the current file. +""" + + +PER_BLOCK_PIXELS = 12 * 5 +PER_INSN_PIXELS = 12 + + +class CycleDetectorAlgState: + all_nodes: List['VisitableNode'] + + # State of sub-algo 1: + branches: List[ + 'CodeExplorationPath' + ] # Contains references to parents with fork points + + function_body: DecompiledFunctionBody + + def __init__(self, function_body: DecompiledFunctionBody): + self.function_body = function_body + self.all_nodes = [] + self.branches = [] + + def do_initial_linking(self): + for node in self.all_nodes: + node.do_initial_linking() + + +# Pass1c algorithm calculation steps: +# - Sub-algo 1: detect cycling points in the graph. +# - Sub-algo 2: iterate over the graph BFS, +# accumulating instruction weights just stopping +# at the cycle points. + + +class VisitableNode: + alg_state: CycleDetectorAlgState + + wrapped: BasicBlock + + unvisited_children: Set['VisitableNode'] + visited_children: Set['VisitableNode'] + + cycling_visited_children: Set['VisitableNode'] + + # The present object may only be the tip of + # one branch at the time + current_tip_of: Optional['CodeExplorationPath'] + + # part_of_code_paths : Set['CodeExplorationPath'] + + # Comment for now: Will only be useful in sub-algo 2 + # temp_max_insn_acc : int + + def __init__(self, node, alg_state): + self.wrapped = node + self.current_tip_of = None + self.cycling_visited_children = set() + # self.part_of_code_paths = set() + self.visited_children = set() + self.alg_state = alg_state + self.alg_state.all_nodes.append(self) + + def do_initial_linking(self): + self.unvisited_children = set( + self.alg_state.all_nodes[ + self.alg_state.function_body.basic_blocks.index(unwrapped_item) + ] + for unwrapped_item in self.wrapped.child_nodes + + self.wrapped.error_handling_child_nodes + ) + + # See the "docs/Pass1c Cycling detection + Block Distance + # from root Weighting Algo.md" documentation for a detailed + # description of the algorithm departing from this function + + def match_against_child(self, other_node): + assert other_node in self.unvisited_children + + self.unvisited_children.discard(other_node) + self.visited_children.add(other_node) + + assert self.current_tip_of + + # Do a forking merge op: + if self.unvisited_children and other_node.current_tip_of: + other_node.current_tip_of.copy_merge(self.current_tip_of) + # Do a non-forking merge op: + elif other_node.current_tip_of: + other_node.current_tip_of.copy_merge(self.current_tip_of) + self.current_tip_of.prune_self() + # Do a fork with modified tip op: + elif self.unvisited_children: + self.current_tip_of.fork_with_new_tip(other_node) + # Do a change tip op: + else: + self.current_tip_of.append_tip(other_node) + + # Do pruning if now cycling (and mark as cycling) + will_create_cycle = ( + other_node in other_node.current_tip_of.children[:-1] + ) + + if will_create_cycle: + self.cycling_visited_children.add(other_node) + self.wrapped.may_be_cycling_anchor = True + other_node.wrapped.may_be_cycling_target = True + + other_node.current_tip_of.prune_self() + + # Do pruning in all cases + if other_node.current_tip_of and not other_node.unvisited_children: + other_node.current_tip_of.prune_self() + + if self.current_tip_of and not self.unvisited_children: + self.current_tip_of.prune_self() + + # Props (i/o) to be used from the BasicBlocks object: + + # max_acc_insn_weight : int <-- output of sub-alg 2 + + # may_be_cycling_anchor : bool <-- output of sub-alg 1 + # may_be_cycling_target : bool <-- output of sub-alg 1 + + # child_nodes : List['BasicBlock'] <-- input + # parent_nodes : List['BasicBlock'] <-- input + + # error_handling_child_nodes : List['BasicBlock'] <-- input + # error_handling_parent_nodes : List['BasicBlock'] <-- input + + # ^ Use Sets or Lists? + + +class CodeExplorationPath: + children: List[VisitableNode] + + alg_state: CycleDetectorAlgState + + def __init__(self, children, alg_state): + + self.children = children + self.alg_state = alg_state + self.alg_state.branches.append(self) + + assert not children[-1].current_tip_of + children[-1].current_tip_of = self + + def copy_merge(self, other_branch: 'Self'): + non_common_descents = [ + node for node in self.children if node not in other_branch.children + ] + + other_branch.children = ( + other_branch.children[:-1] + + non_common_descents # Right? + + other_branch.children[-1:] + ) + + def prune_self(self): + assert self.children[-1].current_tip_of == self + + self.children[-1].current_tip_of = None + self.alg_state.branches.remove(self) + + def fork_with_new_tip(self, other_node: VisitableNode): + assert self.children[-1].current_tip_of == self + assert not other_node.current_tip_of + + CodeExplorationPath(self.children + [other_node], self.alg_state) + + def append_tip(self, other_node: VisitableNode): + assert self.children[-1].current_tip_of == self + assert not other_node.current_tip_of + + self.children[-1].current_tip_of = None + self.children.append(other_node) + other_node.current_tip_of = self + + +def pass1c_visit_code_paths( + state: HermesDecompiler, function_body: DecompiledFunctionBody +): + """ + Use a recursive algorithm to visit the basic blocks tree in + order to: + - Sub-algorithm 1: Mark cycling points of the code + - Useful for detecting loops in the control flow-enabled decompilation + process later + + - Sub-algorithm 2 (TODO): Calculate the "max_acc_insn_weight" value (see + defs.py) for each basic block + - Useful for visually sorting the order of blocks in the + web UI graph + - Useful for detecting condition definition priority in the + control flow-enabled decompilation process later + + """ + + all_nodes = function_body.basic_blocks + root_node = all_nodes[0] + + # Check whether there is no backward jump into the + # basic block graphes: in this case, there is no + # loop and hence no cycling point/no need to process + # further the decompiled function + + cycles = False + for node in all_nodes: + node_index = all_nodes.index(node) + for other_node in node.child_nodes + node.error_handling_child_nodes: + if all_nodes.index(other_node) < node_index: + cycles = True + break + if cycles: + break + if not cycles: + return + + # Build up state and create an initial exploration path + # containing the root node of the function basic blocks + # graph + + root_node.max_acc_insn_weight = root_node.insn_count + + state = CycleDetectorAlgState(function_body) + + for node in all_nodes: + VisitableNode(node, state) # <- state.all_nodes + CodeExplorationPath([state.all_nodes[0]], state) # <- state.branches + + state.do_initial_linking() + + while state.branches: + nodes_to_explore = set() + for branch in state.branches: + nodes_to_explore |= branch.children[-1].unvisited_children + + assert nodes_to_explore + + nodes_to_explore = sorted( + nodes_to_explore, + key=lambda block: ( + # TODO: Opti this + # len(block.wrapped.parent_nodes) + + # len(block.wrapped.error_handling_parent_nodes), + block.wrapped.start_address + ), + ) + + next_node = nodes_to_explore.pop(0) + + for branch in state.branches: + if next_node in branch.children[-1].unvisited_children: + branch.children[-1].match_against_child(next_node) + break diff --git a/src/hermes_dec/gui/index_worker/index_db.py b/src/hermes_dec/gui/index_worker/index_db.py new file mode 100644 index 0000000..0c804fb --- /dev/null +++ b/src/hermes_dec/gui/index_worker/index_db.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# -*- encoding: Utf-8 -*- + +from sqlalchemy import ( + Column, + Text, + Integer, + Boolean, + String, + ForeignKey, + DateTime, +) +from sqlalchemy.orm import relationship, sessionmaker, Session +from sqlalchemy.ext.declarative import declarative_base + +""" + This file contains the definition for the SQLite database + used into the "index_files.py" module. +""" + +# Increment in order to force an update of the database +# cross-reference +XREF_DB_FORMAT_VERSION = 1 + + +Base = declarative_base() + + +class DatabaseMetadataBlock(Base): + __tablename__ = 'database_metadata_block' + + # Single-row table containing the following fields: + database_format = Column(Integer, primary_key=True) + creation_time = Column(DateTime) + modification_time = Column(DateTime) + last_open_time = Column(DateTime) + + +class BaseFunctionXRef(Base): + __tablename__ = 'function_x_ref' + # WIP .. + + id = Column(Integer, primary_key=True) + + function_id = Column(Integer, index=True) + + type = Column(String, index=True) + insn_original_pos = Column(Integer) + insn_operand_idx = Column(Integer) + + __mapper_args__ = {'polymorphic_on': 'type'} + + +class FunctionStringXRef(BaseFunctionXRef): + ref_string_id = Column(Integer, index=True) + + __mapper_args__ = {'polymorphic_identity': 'string'} + + +class FunctionFunctionXRef(BaseFunctionXRef): + ref_function_id = Column(Integer, index=True) + + __mapper_args__ = {'polymorphic_identity': 'function'} + + +class FunctionBuiltinXRef(BaseFunctionXRef): + ref_builtin_id = Column(Integer, index=True) + + __mapper_args__ = {'polymorphic_identity': 'builtin'} diff --git a/src/hermes_dec/gui/index_worker/index_files.py b/src/hermes_dec/gui/index_worker/index_files.py new file mode 100755 index 0000000..3342a5e --- /dev/null +++ b/src/hermes_dec/gui/index_worker/index_files.py @@ -0,0 +1,329 @@ +#!/usr/bin/env python3 +# -*- encoding: Utf-8 -*- +from typing import List, Sequence, Tuple, Dict, Set, Union, Optional +from os.path import dirname, realpath, exists, join +from sys import path, stdin, argv +from tempfile import gettempdir +from datetime import datetime +from threading import Thread +from json import load, dump +from logging import warning +from os import getpid, kill +from random import randint +from bisect import bisect +from json import dumps + +try: + from signal import CTRL_C_EVENT as SIGINT +except ImportError: + from signal import SIGINT + +from sqlalchemy.orm import sessionmaker +from sqlalchemy import create_engine + +INDEX_WORKER_DIR = realpath(dirname(__file__)) +GUI_DIR = realpath(INDEX_WORKER_DIR + '/..') +SRC_DIR = realpath(GUI_DIR + '/..') +PARSERS_DIR = realpath(SRC_DIR + '/parsers') +HBC_OPCODES_DIR = realpath(PARSERS_DIR + '/hbc_opcodes') + +from index_db import ( + Base, + DatabaseMetadataBlock, + XREF_DB_FORMAT_VERSION, + FunctionStringXRef, + FunctionFunctionXRef, + FunctionBuiltinXRef, +) + +path.insert(0, PARSERS_DIR) + +from serialized_literal_parser import ( + unpack_slp_array, + SLPArray, + SLPValue, + TagType, +) +from hbc_bytecode_parser import parse_hbc_bytecode +from hbc_file_parser import HBCReader + +path.insert(0, HBC_OPCODES_DIR) + +from def_classes import Instruction, Operand, OperandMeaning + +""" + This class shall allow to store SQLite-backed data allowing + to search for string list/function list fast within the + hermes-dec web UI, and to search through this in-memory + data. + + + Such an index will carry information over both the list of strings contained in the .HBC + bytecode file and for the corresponding list of functions. + +""" + +INDEXER_ENTRY_SCRIPT = realpath(__file__) + + +class DatabaseIndexReader: + def __init__(self, reader: HBCReader, dir_path: str): + + self.reader = reader + self.file_path = dir_path + '/database.sqlite3' + + # To be implemented... + + +class DatabaseIndexGenerator: + def __init__(self, reader: HBCReader, dir_path: str): + + self.reader = reader + self.file_path = dir_path + '/database.sqlite3' + + self.engine = create_engine('sqlite:///' + realpath(self.file_path)) + self.Session = sessionmaker(self.engine) + + Base.metadata.create_all(bind=self.engine) + + self.indexate_xrefs() + + print( + dumps({'type': 'indexing_state', 'state': 'fully_indexed'}), + flush=True, + ) + + def indexate_xrefs(self): + with self.Session() as session: + meta_block = session.query(DatabaseMetadataBlock).first() + if ( + not meta_block + or meta_block.database_format < XREF_DB_FORMAT_VERSION + ): + print( + dumps( + { + 'type': 'indexing_state', + 'state': 'indexing_functions', + } + ), + flush=True, + ) + + DatabaseMetadataBlock.__table__.drop(self.engine) + Base.metadata.create_all(bind=self.engine) + # Register cross-reference from all across the file: + for function_count, function_header in enumerate( + self.reader.function_headers + ): + for insn in parse_hbc_bytecode( + function_header, self.reader + ): + for operand_index, operand in enumerate( + insn.inst.operands + ): + if operand.operand_meaning: + operand_value = getattr( + insn, 'arg%d' % (operand_index + 1), None + ) + if operand_value is not None: + if ( + operand.operand_meaning + == OperandMeaning.string_id + ): + # self.hbc_reader.strings[operand_value] + session.add( + FunctionStringXRef( + ref_string_id=operand_value, + function_id=function_count, + insn_original_pos=insn.original_pos, + insn_operand_idx=operand_index, + ) + ) + elif ( + operand.operand_meaning + == OperandMeaning.function_id + ): + session.add( + FunctionFunctionXRef( + ref_function_id=operand_value, + function_id=function_count, + insn_original_pos=insn.original_pos, + insn_operand_idx=operand_index, + ) + ) + + if insn.inst.name in ( + 'NewArrayWithBuffer', + 'NewArrayWithBufferLong', + ): + for item in unpack_slp_array( + self.reader.arrays[insn.arg4 :], insn.arg3 + ).items: + if item.tag_type in ( + TagType.LongStringTag, + TagType.ShortStringTag, + TagType.ByteStringTag, + ): + session.add( + FunctionStringXRef( + ref_string_id=item.value, + function_id=function_count, + insn_original_pos=insn.original_pos, + insn_operand_idx=4, + ) + ) + elif insn.inst.name in ( + 'NewObjectWithBuffer', + 'NewObjectWithBufferLong', + ): + for item in unpack_slp_array( + self.reader.object_keys[insn.arg4 :], insn.arg3 + ).items: + if item.tag_type in ( + TagType.LongStringTag, + TagType.ShortStringTag, + TagType.ByteStringTag, + ): + session.add( + FunctionStringXRef( + ref_string_id=item.value, + function_id=function_count, + insn_original_pos=insn.original_pos, + insn_operand_idx=4, + ) + ) + for item in unpack_slp_array( + self.reader.object_values[insn.arg5 :], + insn.arg3, + ).items: + if item.tag_type in ( + TagType.LongStringTag, + TagType.ShortStringTag, + TagType.ByteStringTag, + ): + session.add( + FunctionStringXRef( + ref_string_id=item.value, + function_id=function_count, + insn_original_pos=insn.original_pos, + insn_operand_idx=5, + ) + ) + elif insn.inst.name in ( + 'CallBuiltin', + 'CallBuiltinLong', + 'GetBuiltinClosure', + ): + builtin_number = insn.arg2 + session.add( + FunctionBuiltinXRef( + ref_builtin_id=operand_value, + function_id=function_count, + insn_original_pos=insn.original_pos, + insn_operand_idx=2, + ) + ) + + # Create a metadata block for the whole file: + session.add( + DatabaseMetadataBlock( + database_format=XREF_DB_FORMAT_VERSION, + creation_time=datetime.now(), + modification_time=datetime.now(), + last_open_time=datetime.now(), + ) + ) + else: + meta_block.last_open_time = datetime.now() + session.commit() + + +if __name__ == '__main__': + # Interrupt the indexing process in case of parent death + + def watch_for_parent_death_thread(): + try: + while stdin.readline(): + pass + finally: + kill(getpid(), SIGINT) + + Thread(target=watch_for_parent_death_thread, daemon=True).start() + + # Run the foreground indexer + + reader = HBCReader() + with open(argv[2], 'rb') as fd: + reader.read_whole_file(fd) + + DatabaseIndexGenerator(reader, argv[1]) + + +class MemoryIndex: + def __init__(self, reader: HBCReader): + + self.raw_strings_blob = '' + self.raw_strings_indexes: List[int] = [] + for string in reader.strings: + self.raw_strings_indexes.append(len(self.raw_strings_blob)) + self.raw_strings_blob += string.lower() + '\x00' + + self.raw_functions_blob = '' + self.raw_functions_indexes: List[int] = [] + for function_header in reader.function_headers: + self.raw_functions_indexes.append(len(self.raw_functions_blob)) + raw_func_name = reader.strings[function_header.functionName] + self.raw_functions_blob += ( + raw_func_name.lower() + + '\x00' + + '%08x' % function_header.offset + + '\x00' + ) + + # Return a list of possible string IDs from a given + # searched substring + def find_strings_from_substring(self, token: str) -> str: + + string_ids: Set[str] = set() + + haystack = token.lower() + pos = 0 + while True: + needle = self.raw_strings_blob.find(haystack, pos) + if needle == -1: + break + string_id = bisect(self.raw_strings_indexes, needle) - 1 + pos = needle + len(token) + + string_ids.add(string_id) + + return sorted(string_ids) + + # Return a list of possible function IDs from a given + # searched string, which may either represent a + # raw hex function address or a function name + def find_functions_from_substring(self, token: str) -> List[int]: + + function_ids: Set[int] = set() + + assert token + haystackes = {token.lower()} + + try: + haystackes.add('%08x' % int(token, 16)) + except ValueError: + pass + + for haystack in sorted(haystackes): + pos = 0 + while True: + needle = self.raw_functions_blob.find(haystack, pos) + if needle == -1: + break + function_id = bisect(self.raw_functions_indexes, needle) - 1 + pos = needle + len(token) + + function_ids.add(function_id) + + return sorted(function_ids) diff --git a/src/hermes_dec/gui/paginated_table.py b/src/hermes_dec/gui/paginated_table.py new file mode 100644 index 0000000..1a48ce8 --- /dev/null +++ b/src/hermes_dec/gui/paginated_table.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +# -*- encoding: Utf-8 -*- +from typing import List, Dict, Set, Tuple, Optional, Union, Any, Sequence +from dataclasses import dataclass +from math import ceil + +RAW_TABLE_MODEL_NAME_TO_OBJ = {} + + +def register_table_model(orig_class: 'TableModel'): + + RAW_TABLE_MODEL_NAME_TO_OBJ[orig_class.RAW_NAME] = orig_class + + return orig_class + + +def format_size(size, decimal_places=2): + for unit in ['B', 'KiB', 'MiB']: + if size < 1024.0 or unit == 'MiB': + return f'{size:.{decimal_places}f} {unit}' + size /= 1024.0 + + +@dataclass +class RawFetchResult: + result_count: int # Including filtered/other page entries + pages: int # 1-indexed + current_page: int # 1-indexed + displayed_rows: List[ + Dict[str, Any] + ] # [{'id': 49, 'cells': ['a', 'b', 'c']}, ...] + + +@dataclass +class ColumnModel: + readable_name: str + raw_name: str + + +class TableModel: + has_search_bar: bool = True + + has_pagination: bool = True + pagination_thresold: Optional[int] = 350 + + columns: List[ColumnModel] + has_visible_headers: bool = True + + has_selectable_rows: bool = True + + def query_rows( + self, + server_connection: 'WebsocketClient' = None, # From "server.py", allows to reference + # the "search_index" (index_files.Indexer) and "reader" (hbc_file_parser.HBCReader) + # attributes + current_row_idx: Optional[int] = None, # Current row if selected, + # 0-based (within the full table) + current_page_if_not_current_row: Optional[int] = None, # Current page + # if otherwise selected, 1-based (from the search results if searching) + search_query: Optional[str] = None, # Text search query, if any + ) -> RawFetchResult: + + raise NotImplementedError('Please subclass me') + + def get_json_response( + self, + server_connection: 'WebsocketClient' = None, # From "server.py", allows to reference + # the "search_index" (index_files.Indexer) and "reader" (hbc_file_parser.HBCReader) + # attributes + current_row_idx: Optional[int] = None, # Current row if selected, + # 0-based (within the full table) + current_page_if_not_current_row: Optional[int] = None, # Current page + # if otherwise selected, 1-based (from the search results if searching) + search_query: Optional[str] = None, # Text search query, if any + ) -> dict: + raw_result: RawFetchResult = self.query_rows( + server_connection, + current_row_idx, + current_page_if_not_current_row, + search_query, + ) + result = { + 'table': self.RAW_NAME, + 'result_count': raw_result.result_count, + 'pages': raw_result.pages, + 'current_page': raw_result.current_page, + 'model': { + 'has_search_bar': self.has_search_bar, + 'has_pagination': self.has_pagination, + 'pagination_thresold': self.pagination_thresold, + 'columns': [column.readable_name for column in self.columns], + 'has_visible_headers': self.has_visible_headers, + 'has_selectable_rows': self.has_selectable_rows, + }, + 'displayed_rows': raw_result.displayed_rows, + } + return result + + +@register_table_model +class StringsList(TableModel): + RAW_NAME = 'strings_list' + + def query_rows( + self, + server_connection: 'WebsocketClient', + current_row_idx: Optional[int] = None, + current_page_if_not_current_row: Optional[int] = None, + search_query: Optional[str] = None, + ) -> RawFetchResult: + + string_count = server_connection.reader.header.stringCount + + # Do search query filtering here + if search_query and search_query.strip(): + matching_string_ids: List[int] = ( + server_connection.search_index.find_strings_from_substring( + search_query.strip() + ) + ) + + # Handle the absence of search query + else: + matching_string_ids = [i for i in range(string_count)] + + # Do pagination filtering and counting here + + total_results = len(matching_string_ids) + if self.has_pagination: + page_count = max(1, ceil(total_results / self.pagination_thresold)) + current_page = min( + page_count, current_page_if_not_current_row or 1 + ) + + displayed_string_ids = matching_string_ids[ + (current_page - 1) * self.pagination_thresold : current_page + * self.pagination_thresold + ] + + else: + page_count = 1 + current_page = 1 + displayed_string_ids = matching_string_ids + + strings = server_connection.reader.strings + + displayed_rows: List[Dict[str, Any]] = [ + {'id': string_id, 'cells': (str(string_id), strings[string_id])} + for string_id in displayed_string_ids + ] # [{'id': 49, 'cells': ['a', 'b', 'c']}, ...] + + result = RawFetchResult( + result_count=total_results, + pages=page_count, + current_page=current_page, + displayed_rows=displayed_rows, + ) + + return result + + columns = [ColumnModel('Id', 'id'), ColumnModel('Name', 'name')] + + +@register_table_model +class FunctionsList(TableModel): + RAW_NAME = 'functions_list' + + def query_rows( + self, + server_connection: 'WebsocketClient', + current_row_idx: Optional[int] = None, + current_page_if_not_current_row: Optional[int] = None, + search_query: Optional[str] = None, + ) -> RawFetchResult: + + function_count = server_connection.reader.header.functionCount + + # Do search query filtering here + if search_query and search_query.strip(): + matching_function_ids: List[int] = ( + server_connection.search_index.find_functions_from_substring( + search_query.strip() + ) + ) + + # Handle the absence of search query + else: + matching_function_ids = [i for i in range(function_count)] + + # Do pagination filtering and counting here + + total_results = len(matching_function_ids) + if self.has_pagination: + page_count = max(1, ceil(total_results / self.pagination_thresold)) + current_page = min( + page_count, current_page_if_not_current_row or 1 + ) + + displayed_function_ids = matching_function_ids[ + (current_page - 1) * self.pagination_thresold : current_page + * self.pagination_thresold + ] + + else: + page_count = 1 + current_page = 1 + displayed_function_ids = matching_function_ids + + headers = server_connection.reader.function_headers + strings = server_connection.reader.strings + + displayed_rows: List[Dict[str, Any]] = [ + { + 'id': function_id, + 'cells': ( + strings[headers[function_id].functionName] + or 'fun_%08x' % headers[function_id].offset, + '%08x' % headers[function_id].offset, + str(headers[function_id].bytecodeSizeInBytes), + ), + } + for function_id in displayed_function_ids + ] # [{'id': 49, 'cells': ['a', 'b', 'c']}, ...] + + result = RawFetchResult( + result_count=total_results, + pages=page_count, + current_page=current_page, + displayed_rows=displayed_rows, + ) + + return result + + columns = [ + ColumnModel('Name', 'name'), + ColumnModel('Offset', 'offset'), + ColumnModel('Size', 'size'), + ] + + +@register_table_model +class HeaderInfo(TableModel): + RAW_NAME = 'header_info' + + has_search_bar = False + has_pagination = False + pagination_thresold = None + has_selectable_rows = False + + def query_rows( + self, + server_connection: 'WebsocketClient', + current_row_idx: Optional[int] = None, + current_page_if_not_current_row: Optional[int] = None, + search_query: Optional[str] = None, + ) -> RawFetchResult: + + header = server_connection.reader.header + + displayed_rows = [ + {'id': index, 'cells': cells} + for index, cells in enumerate( + [ + ('File size', format_size(header.fileLength)), + ('String count', str(header.stringCount)), + ('Function count', str(header.functionCount)), + ( + 'String section size', + format_size(header.stringStorageSize), + ), + ] + ) + ] + + result = RawFetchResult( + result_count=len(displayed_rows), + pages=1, + current_page=1, + displayed_rows=displayed_rows, + ) + + return result + + columns = [ColumnModel('Field', 'field'), ColumnModel('Value', 'value')] diff --git a/src/hermes_dec/gui/pre_render_graph.py b/src/hermes_dec/gui/pre_render_graph.py new file mode 100644 index 0000000..487fab2 --- /dev/null +++ b/src/hermes_dec/gui/pre_render_graph.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +# -*- encoding: Utf-8 -*- +from typing import Dict, List, Set, Tuple, Any, Optional, Sequence +from os.path import dirname, realpath +from collections import defaultdict +from sys import path + +GUI_DIR = realpath(dirname(__file__)) +SRC_DIR = realpath(GUI_DIR + '/..') +DECOMPILATION_DIR = realpath(SRC_DIR + '/decompilation') +PARSERS_DIR = realpath(SRC_DIR + '/parsers') + +path.insert(0, DECOMPILATION_DIR) + +from defs import DecompiledFunctionBody, BasicBlock + +path.insert(0, PARSERS_DIR) + +from hbc_bytecode_parser import ParsedInstruction + + +def draw_stuff( + instructions: List[ParsedInstruction], dehydrated: DecompiledFunctionBody +) -> dict: + + current_x = 1 # The horizontal alighments of the basic blocks in the CSS grid layout should approximately + # match the indent levels in the future decompiled code for now. + current_y = 1 # One basic block per row will be rendered in the CSS grid layout for now. + + result = { + 'type': 'analyzed_function', + 'function_id': dehydrated.function_id, + 'blocks': [], + } # TODO Defined the layout of this JSON object that should eventually be sent back to the Websocket server + + # A list of the BasicBlock objects contained in the analyzed function, with + # list index matching the index in the result['blocks'] metadata array + block_objects: List[BasicBlock] = [] + + def process_basic_block(basic_block: BasicBlock): + + nonlocal current_x + nonlocal current_y + + if basic_block.rendered: + return + basic_block.rendered = True + + start_idx = dehydrated.instruction_boundaries.index( + basic_block.start_address + ) + end_idx = dehydrated.instruction_boundaries.index( + basic_block.end_address + ) + + current_block_text = '' + + for instruction in instructions[start_idx:end_idx]: + current_block_text += repr(instruction) + '\n' + + # pos = instruction.original_pos + # next_pos = instruction.next_pos + + # assert (basic_block.start_address <= pos < basic_block.end_address) + + # TODO Set up history API on the web UI too? + + # Output the current block and select the new block: + + result['blocks'].append( + { + 'grid_x': current_x, + 'grid_y': current_y, + 'text': current_block_text, + 'child_nodes': [], + 'child_error_nodes': [], + 'parent_nodes': [], + 'parent_error_nodes': [], + } + ) + + block_objects.append(basic_block) + + # Recursively iterate through children blocks: + + child_nodes = sorted( + basic_block.child_nodes + basic_block.error_handling_child_nodes, + key=lambda block: block.start_address, + ) # -block.max_acc_insn_weight, + # ^ TODO: Reintegrate this opti + + child_nodes = list( + filter( + lambda block: ( + (not block.rendered) and (not block.marked_to_render) + ), + child_nodes, + ) + ) + + for child_node in child_nodes: + child_node.marked_to_render = True + + orig_x = current_x + for x_offset, child_node in reversed(list(enumerate(child_nodes))): + current_y += 1 + + current_x = orig_x + x_offset + process_basic_block(child_node) + + if dehydrated.basic_blocks: + process_basic_block(dehydrated.basic_blocks[0]) + + # Reproduce the linked graph of basic blocks within the JSON metadata + # array using array indexes as a reference + + for index, basic_block in enumerate(block_objects): + result['blocks'][index]['child_nodes'].extend( + block_objects.index(node) for node in basic_block.child_nodes + ) + result['blocks'][index]['child_error_nodes'].extend( + block_objects.index(node) + for node in basic_block.error_handling_child_nodes + ) + result['blocks'][index]['parent_nodes'].extend( + block_objects.index(node) for node in basic_block.parent_nodes + ) + result['blocks'][index]['parent_error_nodes'].extend( + block_objects.index(node) + for node in basic_block.error_handling_parent_nodes + ) + + return result + + # WIP .. diff --git a/src/hermes_dec/gui/project_meta.py b/src/hermes_dec/gui/project_meta.py new file mode 100644 index 0000000..5f16c6d --- /dev/null +++ b/src/hermes_dec/gui/project_meta.py @@ -0,0 +1,337 @@ +#!/usr/bin/env python3 +# -*- encoding: Utf-8 -*- +from asyncio import ( + get_running_loop, + create_task, + wait_for, + Lock, + create_subprocess_exec, +) +from os.path import join, dirname, realpath, basename, exists, getsize +from typing import Optional, Any, List, Dict, Set, Union, Sequence +from os import makedirs, readlink, symlink, listdir +from websockets import WebSocketServerProtocol +from json import load, dump, loads, dumps +from asyncio.subprocess import Process +from subprocess import PIPE, DEVNULL +from appdirs import user_data_dir +from unidecode import unidecode +from datetime import datetime +from hashlib import sha256 +from shutil import rmtree +from sys import path +from re import sub +import sys + +GUI_DIR = realpath(dirname(__file__)) +INDEX_WORKER_DIR = realpath(GUI_DIR + '/index_worker') +SRC_DIR = realpath(GUI_DIR + '/..') +PARSERS_DIR = realpath(SRC_DIR + '/parsers') + +path.insert(0, INDEX_WORKER_DIR) +path.insert(0, PARSERS_DIR) + +from index_files import INDEXER_ENTRY_SCRIPT, MemoryIndex, DatabaseIndexReader +from hbc_file_parser import HBCReader + +""" + TODO : + + Create the following folders: + ./by-hash/ -> symlink to /by-date/- + ./by-date/- -> actual contents containing + "index.android.bundle" and "metadata.json" files + + TODO : Also support parsing and extraing APK files, set appropriate metadata + onto .JSON files, etc. + + TODO : Create a "Recent files" list based off the JSON metadata + + TODO : Improve JS/Python error-handling client-side in the web UI etc. +""" + +LOCAL_SHARE_PATH = user_data_dir('HermesDec', 'P1Security') + +BY_HASH_PATH = join(LOCAL_SHARE_PATH, 'by-hash') +BY_DATE_PATH = join(LOCAL_SHARE_PATH, 'by-date') + +_hash_to_handle: Dict[str, 'ProjectInstanceManager'] = {} + + +class ProjectInstanceManager: + file_hash: str + + web_console_backlog: List[ + Dict[str, str] + ] # List of logged "indexing_state" or "indexing_status_log" socket messages + sockets: List[ + WebSocketServerProtocol + ] # See https://websockets.readthedocs.io/en/stable/reference/asyncio/server.html#websockets.server.WebSocketServerProtocol + + subdir_path: str + metadata_path: str + + reader: HBCReader = None + memory_index: MemoryIndex = None + database_index: DatabaseIndexReader = None + + child: Process = None + + def __init__(self, file_hash: str): + + assert file_hash.isalnum() and len(file_hash) == 64 + + self.file_hash = file_hash + + _hash_to_handle[file_hash] = self + + self.file_parsing_lock = Lock() + + self.web_console_backlog = [] + self.sockets = [] + + self.subdir_path = realpath(join(BY_HASH_PATH, file_hash)) + self.metadata_path = join(self.subdir_path, 'metadata.json') + + async def register_socket(self, socket: WebSocketServerProtocol): + if socket not in self.sockets: + self.sockets.append(socket) + for message in self.web_console_backlog: + await socket.send(dumps(message)) + + # Todo use this everywhere needed in the code: + async def unregister_socket(self, socket: WebSocketServerProtocol): + if socket in self.sockets: + self.sockets.remove(socket) + if not self.sockets and self.file_hash in _hash_to_handle: + del _hash_to_handle[self.file_hash] + if self.child: + # Closing stdin within the child should have + # it interrupt + await self.child.wait() + + async def broadcast_message(self, message: Dict[str, str]): + self.web_console_backlog.append(message) + for socket in list(self.sockets): + await socket.send(dumps(message)) + + @classmethod + def new_with_name(cls, file_hash: str, name: str): + + self = cls(file_hash) + datebased_name = self.gen_datebased_dirname(name) + + makedirs(join(BY_DATE_PATH, datebased_name), exist_ok=False) + makedirs(BY_HASH_PATH, exist_ok=True) + symlink( + join('..', 'by-date', datebased_name), + self.subdir_path, + ) + + self.file_buffer = open( + self.subdir_path + '/index.android.bundle', 'wb+' + ) + self.sha_state = sha256() + + json_data = { + 'orig_name': name, + 'file_hash': file_hash, + 'file_size': 0, # <-- File not written to disk yet + 'db_created_time': datetime.now().isoformat(), + 'raw_disk_path': self.file_buffer.name, + 'dir_disk_path': self.subdir_path, + 'db_updated_time': datetime.now().isoformat(), + } + + with open(self.metadata_path, 'w') as fd: + dump(json_data, fd, indent=4) + fd.write('\n') + + # TODO IMPLEMENT EXTRA CORRUPTION CHECK BEFORE/AFTER TRANSFER? + + return self + + @classmethod + def open_by_hash(cls, file_hash): + if file_hash in _hash_to_handle: + return _hash_to_handle[file_hash] + + self = cls(file_hash) + + self.file_buffer = open( + self.subdir_path + '/index.android.bundle', 'rb' + ) + + return self + + @staticmethod + def project_hash_exists(file_hash: str): + return exists(join(BY_HASH_PATH, file_hash)) + + @staticmethod + def get_recent_files_data() -> dict: + + recent_files = [] + + if exists(BY_DATE_PATH): + for file_entry in reversed(sorted(listdir(BY_DATE_PATH))): + meta_path = join(BY_DATE_PATH, file_entry, 'metadata.json') + with open(meta_path) as fd: + recent_files.append(load(fd)) + + return {'recent_files': recent_files} + + async def monitor_subprocess_stderr(self, child): + + while True: + try: + data = (await child.stderr.readline()).decode('utf-8') + except BrokenPipeError: + break + if not data: + break + + await self.broadcast_message( + {'type': 'console_error_log', 'data': data} + ) + + async def monitor_subprocess_stdout_or_exit(self, child): + + while True: + try: + data = (await child.stdout.readline()).decode('utf-8') + except BrokenPipeError: + break + if not data: + break + data = loads(data.strip()) + if data['type'] == 'indexing_state': + raw_message = { + 'indexing_functions': 'The file is being indexed...\n', + 'fully_indexed': 'The file has been fully indexed.\n', + }[data['state']] + + # XX Handle fully indexed state? + + await self.broadcast_message( + {'type': 'console_log', 'data': raw_message} + ) + + elif data['type'] == 'indexing_status_log': + await self.broadcast_message( + {'type': 'console_log', 'data': data['message']} + ) + + status_code = await wait_for(child.wait(), 60) + + if status_code == 0: + await self.broadcast_message( + { + 'type': 'console_log', + 'data': '(Indexer worker finished correctly.)\n', + } + ) + else: + await self.broadcast_message( + { + 'type': 'console_error_log', + 'data': '(Indexer worker exited with status: %d)\n' + % status_code, + } + ) + + async def launch_index_subprocess_task(self): + + PIPE_BUFFER_SIZE = 20 * 1024 * 1024 + + self.child = await create_subprocess_exec( + sys.executable, + INDEXER_ENTRY_SCRIPT, + self.subdir_path, + self.file_buffer.name, + stdin=PIPE, # This pipe is just for having a thread in the child + # process that have it interrupt on parent death (e.g. SIGKILL received) + stdout=PIPE, + stderr=PIPE, + limit=PIPE_BUFFER_SIZE, + ) + + create_task(self.monitor_subprocess_stderr(self.child)) + create_task(self.monitor_subprocess_stdout_or_exit(self.child)) + + def parse_file_blocking(self): + self.reader = HBCReader() + + self.file_buffer.seek(0) + + self.reader.read_whole_file(self.file_buffer) + self.write_or_update_metadata( + {'bytecode_version': self.reader.header.version} + ) + + self.memory_index = MemoryIndex(self.reader) + + async def parse_file(self): + # Parse file if not parsed yet, and exclusive lock + # this function for the moment this is done. + + async with self.file_parsing_lock: + if not self.reader: + await get_running_loop().run_in_executor( + None, self.parse_file_blocking + ) + + await self.launch_index_subprocess_task() + + self.database_index = DatabaseIndexReader( + self.reader, self.subdir_path + ) + + def write_to_file(self, data: bytes): + + self.file_buffer.write(data) + self.sha_state.update(data) + + def write_or_update_metadata(self, extra_args={}): + + with open(self.metadata_path) as fd: + json_data = load(fd) + + json_data['db_updated_time'] = datetime.now().isoformat() + + json_data.update(extra_args) + + with open(self.metadata_path, 'w') as fd: + dump(json_data, fd, indent=4) + fd.write('\n') + + def read_metadata(self) -> dict: + with open(self.metadata_path) as fd: + return load(fd) + + def end_transfer(self): + + self.file_buffer.flush() + self.file_buffer.seek(0) + + self.write_or_update_metadata( + {'file_size': getsize(self.file_buffer.name)} + ) + + assert self.file_hash == self.sha_state.hexdigest() + + def gen_datebased_dirname(self, orig_name: str): + + return '-'.join( + filter( + None, + [ + datetime.now().isoformat().split('.')[0], + sub( + r'[^a-z0-9\-]', + '', + sub(r'\s+', '-', unidecode(orig_name).lower().strip()), + ), + ], + ) + ) diff --git a/src/hermes_dec/gui/server.py b/src/hermes_dec/gui/server.py new file mode 100755 index 0000000..32e747a --- /dev/null +++ b/src/hermes_dec/gui/server.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python3 +# -*- encoding: Utf-8 -*- +from typing import Dict, Set, List, Sequence, Optional, Tuple +from websockets import serve, WebSocketServerProtocol +from websockets.exceptions import ConnectionClosed +from os.path import dirname, realpath +from asyncio import Future, run +from json import loads, dumps +from threading import Thread +from sys import path + +GUI_DIR = realpath(dirname(__file__)) +INDEX_WORKER_DIR = realpath(GUI_DIR + '/index_worker') +SRC_DIR = realpath(GUI_DIR + '/..') +DECOMPILATION_DIR = realpath(SRC_DIR + '/decompilation') +PARSERS_DIR = realpath(SRC_DIR + '/parsers') + +path.insert(0, DECOMPILATION_DIR) +path.insert(0, GUI_DIR) + +from paginated_table import RAW_TABLE_MODEL_NAME_TO_OBJ +from project_meta import ProjectInstanceManager +from pre_render_graph import draw_stuff + +from defs import HermesDecompiler, DecompiledFunctionBody +from pass1_set_metadata import pass1_set_metadata +from pass1b_make_basic_blocks import pass1b_make_basic_blocks +from pass1c_visit_code_paths import pass1c_visit_code_paths + +path.insert(0, PARSERS_DIR) + +from hbc_bytecode_parser import parse_hbc_bytecode +from hbc_file_parser import HBCReader + + +class WebsocketClient: + reader: HBCReader = None + project: ProjectInstanceManager = None + socket: WebSocketServerProtocol = None + + def __init__(self, socket: WebSocketServerProtocol): + self.socket = socket + + def open_by_hash(self, file_hash: str): + self.project = ProjectInstanceManager.open_by_hash(file_hash) + + def create_file(self, file_hash: str, file_name: str): + self.project = ProjectInstanceManager.new_with_name( + file_hash, file_name + ) + + def write_to_file(self, bytes_slice: bytes): + self.project.write_to_file(bytes_slice) + + def get_metadata(self) -> dict: + + return {'file_metadata': self.project.read_metadata()} + + async def handle(self): + + await self.socket.send( + dumps( + { + 'type': 'recent_files', + **ProjectInstanceManager.get_recent_files_data(), + } + ) + ) + + try: + async for msg in self.socket: + # Receive the next message. + + if type(msg) == bytes: + self.write_to_file(msg) + + else: + msg = loads(msg) + + print('DEBUG: Received: ', msg) + + msg_type = msg['type'] + + # Please see the "../../../docs/GUI server websocket protocol.md" + # file for documentation about the following messages. + + if msg_type == 'open_file_by_hash': + if ProjectInstanceManager.project_hash_exists( + msg['file_hash'] + ): + if self.project: + await self.project.unregister_socket( + self.socket + ) + self.open_by_hash(msg['file_hash']) + + await self.project.register_socket(self.socket) + await self.project.parse_file() + self.reader = self.project.reader + + await self.socket.send( + dumps( + { + 'type': 'file_opened', + **self.get_metadata(), + } + ) + ) + else: + await self.socket.send( + dumps( + { + 'type': 'file_hash_unknown', + 'file_hash': msg['file_hash'], + } + ) + ) + + elif msg_type == 'begin_transfer': + if self.project: + await self.project.unregister_socket(self.socket) + self.create_file(msg['file_hash'], msg['file_name']) + + elif msg_type == 'end_transfer': + self.project.end_transfer() + + await self.project.register_socket(self.socket) + await self.project.parse_file() + self.reader = self.project.reader + + await self.socket.send( + dumps( + {'type': 'file_opened', **self.get_metadata()} + ) + ) + + await self.socket.send( + dumps( + { + 'type': 'recent_files', + **ProjectInstanceManager.get_recent_files_data(), + } + ) + ) + + elif msg_type == 'get_table_data': + table_name = msg['table'] + + if table_name not in RAW_TABLE_MODEL_NAME_TO_OBJ: + raise NotImplementedError( + '[!] Unimplemented table model: %s' + % table_name + ) + + table_model = RAW_TABLE_MODEL_NAME_TO_OBJ[table_name]() + + await self.socket.send( + dumps( + { + 'type': 'table_data', + **table_model.get_json_response( + server_connection=self, + current_row_idx=msg.get('current_row'), + current_page_if_not_current_row=msg.get( + 'page' + ), + search_query=msg.get('text_filter'), + ), + } + ) + ) + + elif msg_type == 'analyze_function': + # - Internally disassemble the queried function. + + function_header = self.reader.function_headers[ + msg['function_id'] + ] + + instructions = list( + parse_hbc_bytecode(function_header, self.reader) + ) + + # - Use code from the decompilation module in order to + # construct the graph structures that will be used to + # pre-render/display the in-browser on-screen graph. + + state = HermesDecompiler() + state.hbc_reader = self.reader + state.function_header = function_header + + dehydrated = DecompiledFunctionBody() + + dehydrated.function_id = msg['function_id'] + dehydrated.function_object = function_header + dehydrated.is_global = ( + msg['function_id'] + == self.reader.header.globalCodeIndex + ) + + if dehydrated.function_object.hasExceptionHandler: + dehydrated.exc_handlers = ( + self.reader.function_id_to_exc_handlers[ + msg['function_id'] + ] + ) + + pass1_set_metadata(state, dehydrated) + pass1b_make_basic_blocks(state, dehydrated) + # pass1c_visit_code_paths(state, dehydrated) # Commented right now + + # - Return the resulting pre-rendered/displayed graph as + # serialized JSON. (WIP...) + + await self.socket.send( + dumps(draw_stuff(instructions, dehydrated)) + ) + + # WIP .. + + # elif msg_type == 'XX SEE. MD DOC': + # XX + + # await websocket.send(message) + + finally: + if self.project: + await self.project.unregister_socket(self.socket) + + +class WebsocketServer: + async def handle_ws_client(self, socket): + connection = WebsocketClient(socket) + + await connection.handle() + + +async def async_main(): + # TODO: Use a process (or thread but think of GIL) pool? + # TODO: Implement the client/server communication defined within the + # "docs/GUI server websocket protocol.md" file, as well as the GUI + + socket_server = WebsocketServer() + async with serve(socket_server.handle_ws_client, 'localhost', 49594): + await Future() # run forever + + +def main(): + run(async_main()) + + +if __name__ == '__main__': + main() diff --git a/src/hermes_dec/gui/static/graph_canvas/111_port.js b/src/hermes_dec/gui/static/graph_canvas/111_port.js new file mode 100644 index 0000000..ab536cc --- /dev/null +++ b/src/hermes_dec/gui/static/graph_canvas/111_port.js @@ -0,0 +1,31 @@ + + +// Is bound to a given side of a node (bottom = outwards, top = inwards) +class Port { + constructor(graph, node, is_error_handling, is_out) { + this.graph = graph; // Graph + this.node = node; // Node + + this.is_out = is_out; // Boolean + this.is_error_handling = is_error_handling; // Boolean + if(is_out) { + node.out_ports.push(this); + } + else { + node.in_ports.push(this); + } + this.edge = null; // Edge, to be filled when constructing the said Edge + } +} + +class OutPort extends Port { // Rendered at the bottom of nodes + constructor(graph, node, is_error_handling) { + super(graph, node, is_error_handling, true); + } +} + +class InPort extends Port { // Rendered at the top of nodes + constructor(graph, node, is_error_handling) { + super(graph, node, is_error_handling, false); + } +} \ No newline at end of file diff --git a/src/hermes_dec/gui/static/graph_canvas/11_node.js b/src/hermes_dec/gui/static/graph_canvas/11_node.js new file mode 100644 index 0000000..e3c09a4 --- /dev/null +++ b/src/hermes_dec/gui/static/graph_canvas/11_node.js @@ -0,0 +1,26 @@ + + +// Contains a list of OutPort of InPort objects, each linked +// to a given Edge +class Node { + constructor(graph, grid_element, orig_idx) { + this.graph = graph; // Graph + this.graph.nodes.push(this); + + // (Reminders about our CSS grid: indexes begin at 1; for a + // given entry, if both indexes are even it's a node, if + // either index is odd it's a lattice) + this.grid_x = parseInt(grid_element.style.gridColumn, 10); // Integer + this.grid_y = parseInt(grid_element.style.gridRow, 10); // Integer + + if(!this.graph.css_grid[this.grid_x]) { + this.graph.css_grid[this.grid_x] = {}; + } + this.graph.css_grid[this.grid_x][this.grid_y] = this; + + this.grid_element = grid_element; // DOMElement + this.orig_idx = orig_idx; // Integer + this.in_ports = []; // Array + this.out_ports = []; // Array + } +} \ No newline at end of file diff --git a/src/hermes_dec/gui/static/graph_canvas/12_edge.js b/src/hermes_dec/gui/static/graph_canvas/12_edge.js new file mode 100644 index 0000000..80cc5e2 --- /dev/null +++ b/src/hermes_dec/gui/static/graph_canvas/12_edge.js @@ -0,0 +1,281 @@ + + +const LINE_WIDTH = 2; +const SPACING_BETWEEN_EDGES_PX = 25; +const LANE_PX_SIZE = SPACING_BETWEEN_EDGES_PX + LINE_WIDTH; + +// Links an OutPort to a InPort, composed of a path of LatticeLane +// objects +class Edge { + constructor(graph, out_port, in_port) { + this.edge_color = 'hwb(' + Math.round((graph.edges.length * 62) % 360) + ' 10% 20%)'; + + this.graph = graph; // Graph + this.graph.edges.push(this); + + this.out_port = out_port; // OutPort + this.in_port = in_port; // InPort + out_port.edge = this; + in_port.edge = this; + + this.is_error_handling = out_port.is_error_handling; // Boolean + this.lattices = []; // Array + this.lattice_lanes = []; // Array + this.rendered_objects = []; // Array + } + + prerender() { + this.lattices = []; + this.lattice_lanes = []; + this.rendered_objects = []; + + // WIP... fill this.lattices and this.lattice_lanes using a + // pathfinding algorithm + + // Pathfinding algorithm: + // 1. The first way of the path is the horizontal Lattice (*) + // located under OutPort which starts the Edge. + // (We're reserving a first LatticeLane matched + // with the corresponding OutPort here.) + // 2. Then, pick or create 0+ vertical Lattice (**) objects + // in the grid up to the level of matching InPort + // 3. Then, pick or create 1+ horizontal Lattice (*) objects + // in the grid up to the level of the matching InPort + // + // (*) horizontal Lattice objects host horizontal LatticeLane + // objects which match (optionally: a vertical Line RenderedObject + // going out/in from a Port, plus in all cases) a horizontal Line + // RenderedObject going either leftwards or rightwards. + // + // (**) vertical Lattice objects host vertical LatticeLane + // objects which match a vertical Line RenderedObject + // connected to the horizontal Line of an horizontal Lattice. + // + // Our path is stored in the this.lattice_lanes object + // + // (Reminders about our CSS grid: indexes begin at 1; for a + // given entry, if both indexes are even it's a node, if + // either index is odd it's a lattice) + + const src_x = this.out_port.node.grid_x; + const src_y = this.out_port.node.grid_y + 1; + + const dst_x = this.in_port.node.grid_x; + const dst_y = this.in_port.node.grid_y - 1; + + const src_lattice = this.graph.get_lattice(src_x, src_y); + const dst_lattice = this.graph.get_lattice(dst_x, dst_y); + + var sort_key = dst_x * 1024 + dst_y; + + var cur_x = src_x; + var cur_y = src_y; + + // Step 0: Check for short paths (a block where we should just + // draw a vertical line towards the bottom neighbor) + + var is_short_path = false; + + if(src_x == dst_x) { + is_short_path = true; + + var x = dst_x; + var min_y = Math.min(src_y, dst_y) + 1; + var max_y = Math.max(src_y, dst_y); + + for(var y = min_y; y < max_y; y++) { + if(this.graph.css_grid[x] && this.graph.css_grid[x][y]) { + is_short_path = false; + break; + } + } + } + + // Step 1: + this.lattices.push(src_lattice); + + if(dst_x < src_x) { + cur_x--; // Should we get leftwards? + this.lattices.push(this.graph.get_lattice(cur_x, cur_y)); + } + else if(!is_short_path) { + cur_x++; // Should we get rightwards? + this.lattices.push(this.graph.get_lattice(cur_x, cur_y)); + } + + if(dst_y < src_y) { + while(dst_y < cur_y) { + cur_y--; // Get an even grid index + this.lattices.push(this.graph.get_lattice(cur_x, cur_y)); + cur_y--; + this.lattices.push(this.graph.get_lattice(cur_x, cur_y)); + } + } + else if(dst_y > src_y) { + while(dst_y > cur_y) { + cur_y++; // Get an even grid index + this.lattices.push(this.graph.get_lattice(cur_x, cur_y)); + cur_y++; + this.lattices.push(this.graph.get_lattice(cur_x, cur_y)); + } + } + + if(dst_x < src_x) { + while(dst_x + 1 < cur_x) { + cur_x--; // Get an even grid index + this.lattices.push(this.graph.get_lattice(cur_x, cur_y)); + cur_x--; + this.lattices.push(this.graph.get_lattice(cur_x, cur_y)); + } + } + else if(dst_x > src_x) { + while(dst_x - 1 > cur_x) { + cur_x++; // Get an even grid index + this.lattices.push(this.graph.get_lattice(cur_x, cur_y)); + cur_x++; + this.lattices.push(this.graph.get_lattice(cur_x, cur_y)); + } + } + + this.lattices.push(dst_lattice); + + /** + * Allocate this.lattice_lanes from this.lattices + * + * (TODO: Use a consistent insertion order of the lanes inside the + * lattices, as described below: + * -- "XXX TODO ") + */ + + var prev_x = -1; + var prev_y = -1; + for(var lattice_idx = 0; lattice_idx < this.lattices.length; lattice_idx++) { + var lattice = this.lattices[lattice_idx]; + var cur_x = lattice.grid_x; + var cur_y = lattice.grid_y; + if((cur_x !== prev_x || cur_y !== prev_y) && + lattice_idx === this.lattices.indexOf(lattice)) { // TODO: Useless soon? + this.lattice_lanes.push(new LatticeLane(this.edge, lattice, sort_key)); + } + prev_x = cur_x; + prev_y = cur_y; + } + + /** + * Adjust the lattice
heights according to the number of present + * lanes for each Lattice object (from this.lattices) + */ + + for(var lattice of this.lattices) { + lattice.grid_element.style.minHeight = (LANE_PX_SIZE * lattice.lanes.length + SPACING_BETWEEN_EDGES_PX) + 'px'; + lattice.grid_element.style.minWidth = (LANE_PX_SIZE * lattice.lanes.length + SPACING_BETWEEN_EDGES_PX) + 'px'; + } + } + + render() { + /** + * WIP... fill this.rendered_objects from this.lattice_lanes + */ + + // (Now we're thinking in pixels rather than grid units) + + // (Done ABOVE : Adjust the CSS pixel padding of lattices according + // to their number of lanes, if needed?) + + // Get the origin x,y position of the canvas: + + var canvas_div = this.graph.svg_root; + var canvas_bbox = canvas_div.getBoundingClientRect(); + + // Get the origin x1,y1,x2,y2 positions of the + // begin and destination blocks of the graph + + var src_div = this.out_port.node.grid_element.querySelector('.graph_node'); + var src_bbox = src_div.getBoundingClientRect(); + + var lane_px_offset = this.lattice_lanes[0].get_lane_index() * LANE_PX_SIZE; + var centered_lane_px_offset = lane_px_offset - (LANE_PX_SIZE * (this.lattices[0].lanes.length - 1) / 2); + + // The start of the edge we will draw: (bottom-center of the begin block) + var cur_x = src_bbox.x - canvas_bbox.x + src_bbox.width / 2 + centered_lane_px_offset; // In pixels now + var cur_y = src_bbox.y - canvas_bbox.y + src_bbox.height; + + // (Draw a first vertical Line) + + var first_ankle_height = lane_px_offset + LANE_PX_SIZE; + // console.log('Z ' + first_ankle_height, ' / ', this.lattice_lanes[0].get_lane_index()); // DEBUG + + this.rendered_objects.push(new Line( + this.graph.svg_root, this.edge_color, cur_x, cur_x, cur_y, cur_y + first_ankle_height)); + + cur_y += first_ankle_height; + + // DEBUG: Put debug points on the rendered map + /* for(var i = 0; i < this.lattice_lanes.length; i++) { + var lattice_lane = this.lattice_lanes[i]; + var div = lattice_lane.lattice.grid_element; + var bbox = div.getBoundingClientRect(); + console.log('TT / ', i, '//', bbox.x, '/', canvas_bbox.x); + var absolute_x = bbox.x - canvas_bbox.x; + var absolute_y = bbox.y - canvas_bbox.y; + + var x = absolute_x + 60 * lattice_lane.get_lane_index(); // DEBUG / + 20 + var y = absolute_y; // DEBUG / + 20 + + this.rendered_objects.push(new DebugMarkerCross(this.graph.svg_root, this.edge_color, x, y, i)); + } */ + + // (Draw a sequence of horizontal Line, ... + // (1+ vertical Line/vertical Line items), ... horizontal Line) + for(var lattice_lane of this.lattice_lanes) { + + var div = lattice_lane.lattice.grid_element; + var bbox = div.getBoundingClientRect(); + + var absolute_bbox_x_left = bbox.x - canvas_bbox.x; + var absolute_bbox_x_right = absolute_bbox_x_left + bbox.width; + var absolute_bbox_y_top = bbox.y - canvas_bbox.y; + var absolute_bbox_y_bottom = absolute_bbox_y_top + bbox.height; + + var old_x = cur_x; + + cur_x = (absolute_bbox_x_left + absolute_bbox_x_right) / 2 + lattice_lane.get_lane_index() * LANE_PX_SIZE; + cur_x -= (LANE_PX_SIZE * (lattice_lane.lattice.lanes.length - 1) / 2); + + var old_y = cur_y; + + cur_y = (absolute_bbox_y_top + absolute_bbox_y_bottom) / 2 + lattice_lane.get_lane_index() * LANE_PX_SIZE; + cur_y -= (LANE_PX_SIZE * (lattice_lane.lattice.lanes.length - 1) / 2); + + // if(old_x == cur_x || old_y == cur_y) { + this.rendered_objects.push(new Line(this.graph.svg_root, this.edge_color, old_x, cur_x, old_y, cur_y)); + /* } + else { + var is_horizontal = Math.abs(old_x - cur_x) > Math.abs(old_y - cur_y); + if((is_horizontal && old_y < cur_y) || (!is_horizontal && old_x > cur_x)) { + this.rendered_objects.push(new Curve(this.graph.svg_root, this.edge_color, old_x, cur_x, old_y, cur_y, false)); + } + else { + this.rendered_objects.push(new Curve(this.graph.svg_root, this.edge_color, old_x, cur_x, old_y, cur_y, true)); + } + } */ + + } + + var dst_div = this.in_port.node.grid_element.querySelector('.graph_node'); + var dst_bbox = dst_div.getBoundingClientRect(); + + // The end of the edge we will draw: (top-center of the destination block) + var target_y = dst_bbox.y - canvas_bbox.y; + + // (Draw a final vertical Line) + this.rendered_objects.push(new Line(this.graph.svg_root, this.edge_color, cur_x, cur_x, cur_y, target_y)); + + // (Draw the arrow towards the box) + this.rendered_objects.push(new BottomLeaningArrow(this.graph.svg_root, this.edge_color, cur_x, target_y)); + + for(let rendered_object of this.rendered_objects) { + rendered_object.render(); + } + } +} \ No newline at end of file diff --git a/src/hermes_dec/gui/static/graph_canvas/131_latticelane.js b/src/hermes_dec/gui/static/graph_canvas/131_latticelane.js new file mode 100644 index 0000000..b483dc2 --- /dev/null +++ b/src/hermes_dec/gui/static/graph_canvas/131_latticelane.js @@ -0,0 +1,23 @@ + + +// Part of a Lattice and of a specific Edge +class LatticeLane { + constructor(edge, lattice, sort_key) { + this.edge = edge; // Edge + this.lattice = lattice; // Lattice + this.sort_key = sort_key; + + this.lattice.lanes.push(this); + this.lattice.lanes.sort(function(a, b) { + return a.sort_key - b.sort_key; + }); + } + + get_lane_index() { + var ret = this.lattice.lanes.indexOf(this); + if(ret == -1) { + alert('[Error: indexOf returned -1]'); + } + return ret; + } +} diff --git a/src/hermes_dec/gui/static/graph_canvas/13_lattice.js b/src/hermes_dec/gui/static/graph_canvas/13_lattice.js new file mode 100644 index 0000000..118a246 --- /dev/null +++ b/src/hermes_dec/gui/static/graph_canvas/13_lattice.js @@ -0,0 +1,27 @@ + + +// May be linked with a side of Node +class Lattice { + constructor(graph, grid_x, grid_y) { + this.graph = graph; // Graph + graph.lattices.push(this); + + this.grid_x = grid_x; + this.grid_y = grid_y; + + this.grid_element = document.createElement('div'); + this.grid_element.style.gridColumn = this.grid_x; // Set by the caller constructor, 1-indexed + this.grid_element.style.gridRow = this.grid_y; // Same + + this.grid_element.className = 'lattice'; + + if(!this.graph.css_grid[this.grid_x]) { + this.graph.css_grid[this.grid_x] = {}; + } + this.graph.css_grid[this.grid_x][this.grid_y] = this; + + this.graph.grid_root.appendChild(this.grid_element); + + this.lanes = []; // Array + } +} diff --git a/src/hermes_dec/gui/static/graph_canvas/14_renderedobject.js b/src/hermes_dec/gui/static/graph_canvas/14_renderedobject.js new file mode 100644 index 0000000..3982407 --- /dev/null +++ b/src/hermes_dec/gui/static/graph_canvas/14_renderedobject.js @@ -0,0 +1,159 @@ + +const SVG_NS = 'http://www.w3.org/2000/svg'; + +// Will be rendered onto the SVG canvas, each LatticeLane contains +// one or more, has geometrical references to other objects +class RenderedObject { + constructor(svg_root) { + this.svg_root = svg_root; // DOMElement + } +} + +class Curve extends RenderedObject { + constructor(svg_root, css_color, x1, x2, y1, y2, invert) { + super(svg_root); + this.css_color = css_color; + this.x1 = x1; + this.x2 = x2; + this.y1 = y1; + this.y2 = y2; + this.invert = invert; + } + + render() { + var element = document.createElementNS(SVG_NS, 'path'); + + var x_middle = this.x1 + (this.x2 - this.x1) / 3; + var y_middle = this.y1 + (this.y2 - this.y1) / 3; + + var path_string = 'M ' + this.x1 + ' ' + this.y1 + ' '; + if(!this.invert) { + path_string += 'Q ' + this.x1 + ' ' + y_middle + ' ' + x_middle + ' ' + y_middle + ' '; + path_string += 'Q ' + this.x2 + ' ' + y_middle + ' ' + this.x2 + ' ' + this.y2; + } + else { + path_string += 'Q ' + x_middle + ' ' + this.y1 + ' ' + x_middle + ' ' + y_middle + ' '; + path_string += 'Q ' + x_middle + ' ' + this.y2 + ' ' + this.x2 + ' ' + this.y2; + } + + element.setAttribute('d', path_string); + element.setAttribute('fill', 'none'); + element.setAttribute('stroke', this.css_color); + element.setAttribute('stroke-width', LINE_WIDTH); + + this.svg_root.appendChild(element); + } +} + +class Line extends RenderedObject { + constructor(svg_root, css_color, x1, x2, y1, y2) { + super(svg_root); + this.css_color = css_color; + this.x1 = x1; + this.x2 = x2; + this.y1 = y1; + this.y2 = y2; + } + + render() { + var element = document.createElementNS(SVG_NS, 'line'); + + element.setAttribute('stroke', this.css_color); + element.setAttribute('stroke-width', LINE_WIDTH); + element.setAttribute('x1', Math.round(this.x1)); + element.setAttribute('x2', Math.round(this.x2)); + element.setAttribute('y1', Math.round(this.y1)); + element.setAttribute('y2', Math.round(this.y2)); + + this.svg_root.appendChild(element); + } + +} + +class DebugMarkerCross extends RenderedObject { + constructor(svg_root, css_color, x, y, text_label) { + super(svg_root); + this.css_color = css_color; + this.x = x; + this.y = y; + this.text_label = text_label; + } + + render() { + var element = document.createElementNS(SVG_NS, 'path'); + + var line_width = 2; // In pixels + var arm_size = 9; // In pixels + + var cur_x = this.x - arm_size; + var cur_y = this.y - arm_size; + var path = 'M ' + cur_x + ' ' + cur_y + ' '; + cur_x += arm_size * 2; + cur_y += arm_size * 2; + path += 'L ' + cur_x + ' ' + cur_y + ' '; + + cur_x -= arm_size * 2; + path += 'M ' + cur_x + ' ' + cur_y + ' '; + cur_y -= arm_size * 2; + cur_x += arm_size * 2; + path += 'L ' + cur_x + ' ' + cur_y + ' '; + + element.setAttribute('stroke', this.css_color); + element.setAttribute('stroke-width', line_width); + element.setAttribute('d', path); + + this.svg_root.appendChild(element); + + var element = document.createElementNS(SVG_NS, 'text'); + + element.appendChild(document.createTextNode(this.text_label)); + + element.setAttribute('dominant-baseline', 'middle'); + element.setAttribute('fill', this.css_color); + element.style.font = '24px sans-serif'; + element.setAttribute('x', this.x + arm_size * 2.5); + element.setAttribute('y', this.y); + + this.svg_root.appendChild(element); + } +} + +// All the arrows should be bottom-leaning I think +class BottomLeaningArrow extends RenderedObject { + constructor(svg_root, css_color, tip_x, tip_y) { + super(svg_root); + this.css_color = css_color; + this.tip_x = tip_x; + this.tip_y = tip_y; + } + + render() { + var element = document.createElementNS(SVG_NS, 'polygon'); + + var points = ''; + var line_size = 14; + + var x = this.tip_x - line_size / 2; + var y = this.tip_y - line_size / 2; + + points += x + ' ' + y + ','; + x += line_size; + points += x + ' ' + y + ','; + x -= line_size / 2; + y += line_size / 2; + points += x + ' ' + y; + + element.setAttribute('fill', this.css_color); + + element.setAttribute('points', points); + + this.svg_root.appendChild(element); + } +} + +// Later: +/** +class OverlapHalfCircle extends RenderedObject { + +} +*/ \ No newline at end of file diff --git a/src/hermes_dec/gui/static/graph_canvas/1_graph.js b/src/hermes_dec/gui/static/graph_canvas/1_graph.js new file mode 100644 index 0000000..afcc29d --- /dev/null +++ b/src/hermes_dec/gui/static/graph_canvas/1_graph.js @@ -0,0 +1,44 @@ + +// WIP ... + +// This abstracts the rendering of graph edges, that will be +// operated through drawing a SVG background under the +// grid-positioned divs of the disassembly output + +// Global state for the graph +class Graph { + constructor(svg_root, grid_root) { + this.svg_root = svg_root; // DOMElement + this.grid_root = grid_root; // DOMElement + + this.css_grid = {}; // Object>> - Nested/associative x,y 2D array + + this.nodes = []; // Array + this.edges = []; // Array + this.lattices = []; // Array + } + + get_lattice(grid_x, grid_y) { + if(this.css_grid[grid_x] && this.css_grid[grid_x][grid_y]) { + return this.css_grid[grid_x][grid_y]; + } + else { + return new Lattice(this, grid_x, grid_y); + } + } + + prerender() { + for(let edge of this.edges) { + edge.prerender(); + } + } + + render() { + for(let edge of this.edges) { + edge.render(); + } + + this.svg_root.setAttribute('width', this.grid_root.scrollWidth); + this.svg_root.setAttribute('height', this.grid_root.scrollHeight); + } +} \ No newline at end of file diff --git a/src/hermes_dec/gui/static/index.html b/src/hermes_dec/gui/static/index.html new file mode 100644 index 0000000..eec6a5a --- /dev/null +++ b/src/hermes_dec/gui/static/index.html @@ -0,0 +1,76 @@ + + + + + Graph viewer + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/hermes_dec/gui/static/spinner_gif.gif b/src/hermes_dec/gui/static/spinner_gif.gif new file mode 100644 index 0000000..4301102 Binary files /dev/null and b/src/hermes_dec/gui/static/spinner_gif.gif differ diff --git a/src/hermes_dec/gui/static/styles/11_homeview.css b/src/hermes_dec/gui/static/styles/11_homeview.css new file mode 100644 index 0000000..664ea52 --- /dev/null +++ b/src/hermes_dec/gui/static/styles/11_homeview.css @@ -0,0 +1,23 @@ +/** + * Home view styling + */ + + #open_button_container { + font-size: 28px; +} + +#open_button_container input { + font-size: 28px; + cursor: pointer; + margin-left: 8px; +} + +#home_view { + padding: 46px 38px; +} + +#recent_files_table tbody tr:hover { + background: #eee; + cursor: pointer; +} + diff --git a/src/hermes_dec/gui/static/styles/131_topbar.css b/src/hermes_dec/gui/static/styles/131_topbar.css new file mode 100644 index 0000000..5d5e7c6 --- /dev/null +++ b/src/hermes_dec/gui/static/styles/131_topbar.css @@ -0,0 +1,16 @@ +/** + * Top bar + */ + + #top_bar { + padding: 7px; + border-bottom: 1px solid #ccc; + font-family: monospace; + font-size: 14px; + grid-row: 1; + grid-column: 1 / 3; +} + +#top_bar_switch_file_button { + float: right; +} \ No newline at end of file diff --git a/src/hermes_dec/gui/static/styles/132_sidepane.css b/src/hermes_dec/gui/static/styles/132_sidepane.css new file mode 100644 index 0000000..9241e27 --- /dev/null +++ b/src/hermes_dec/gui/static/styles/132_sidepane.css @@ -0,0 +1,42 @@ +/** + * Side pane + */ + + #side_pane { + border-right: 1px solid #ccc; + width: 375px; + grid-row: 2; + grid-column: 1; +} + +/** + * Custom table widths + */ + + +.strings_table td:first-child { + width: 47px; +} +.strings_table td:last-child { + width: 237px; +} +.functions_table td { + width: 100px; +} +.functions_table { + margin-top: 5px; + border-top: 1px solid #ccc; +} +.functions_table td:first-child { + border-left: none; + width: 150px; +} +.functions_table td:last-child { + border-right: none; + width: 80px; +} + +.file_headers_table { + margin: 14px 18px; +} + diff --git a/src/hermes_dec/gui/static/styles/1331_disasmtab.css b/src/hermes_dec/gui/static/styles/1331_disasmtab.css new file mode 100644 index 0000000..362dfc9 --- /dev/null +++ b/src/hermes_dec/gui/static/styles/1331_disasmtab.css @@ -0,0 +1,39 @@ +/** + * Canvas styling + */ + + #canvas { + position: relative; + } + + #canvas_svg { + position: absolute; + top: 0; + left: 0; + z-index: 1; +} + +#canvas_grid { + position: absolute; + top: 0; + left: 0; + padding: 19px; + z-index: 2; + display: grid; + font-size: 12px; + font-family: 'Courier New', Courier, monospace; + grid-auto-columns: min-content minmax(60ch, max-content); + grid-auto-rows: minmax(19px, max-content) max-content; +} + +.lattice { + /* background: #C25EFF; */ /* Debug: Make the grid lattices visible enough */ + opacity: 0.4; +} + +#canvas_grid .graph_node { /* Basic block */ + border: 1px solid #ccc; + padding: 5px; + white-space: pre-line; + line-height: 1.2em; +} diff --git a/src/hermes_dec/gui/static/styles/134_logconsole.css b/src/hermes_dec/gui/static/styles/134_logconsole.css new file mode 100644 index 0000000..bf287af --- /dev/null +++ b/src/hermes_dec/gui/static/styles/134_logconsole.css @@ -0,0 +1,15 @@ +/** + * Log console (work view bottom pane) + */ + +#log_console { + border-top: 1px solid #ccc; + padding: 20px; + grid-row: 3; + grid-column: 1 / 3; + font-size: 12px; + font-family: 'Courier New', Courier, monospace; + line-height: 1.2em; + white-space: pre-wrap; + overflow-y: scroll; +} \ No newline at end of file diff --git a/src/hermes_dec/gui/static/styles/135_popup.css b/src/hermes_dec/gui/static/styles/135_popup.css new file mode 100644 index 0000000..9089687 --- /dev/null +++ b/src/hermes_dec/gui/static/styles/135_popup.css @@ -0,0 +1,18 @@ +.popup_underlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.3; + background: #444; + z-index: 100; +} + +.popup { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 10000; +} \ No newline at end of file diff --git a/src/hermes_dec/gui/static/styles/13_workview.css b/src/hermes_dec/gui/static/styles/13_workview.css new file mode 100644 index 0000000..06c78ef --- /dev/null +++ b/src/hermes_dec/gui/static/styles/13_workview.css @@ -0,0 +1,15 @@ +/** + * Work view styling + */ + + #work_view { + display: grid; + grid-template-rows: 32px 1fr 194px; + grid-template-columns: max-content 1fr; + + position: absolute; + top: 0; + left: 0; + width: 100vw; + height: 100vh; +} diff --git a/src/hermes_dec/gui/static/styles/1_approot.css b/src/hermes_dec/gui/static/styles/1_approot.css new file mode 100644 index 0000000..44fb990 --- /dev/null +++ b/src/hermes_dec/gui/static/styles/1_approot.css @@ -0,0 +1,25 @@ +/** + * Global page styling + */ + + html, body { + margin: 0; + padding: 0; + font-family: sans-serif; + font-size: 16px; +} + +/** + * General table styling + */ + + table { + font-family: 'Courier New', Courier, monospace; + font-size: 12px; + border-collapse: collapse; +} + +th, td { + border: 1px solid #ccc; + padding: 7px; +} diff --git a/src/hermes_dec/gui/static/styles/searcheable_table.css b/src/hermes_dec/gui/static/styles/searcheable_table.css new file mode 100644 index 0000000..d5f3419 --- /dev/null +++ b/src/hermes_dec/gui/static/styles/searcheable_table.css @@ -0,0 +1,47 @@ + + +/* + * Search bar styling + */ + +.search_bar { + padding: 9px; + padding-top: 4px; +} + + /** + * Table layouts + */ + + .searcheable_table_layout { + display: flex; + overflow: hidden; + flex-direction: column; + } + + .search_bar { + flex-grow: 0; + } + + .searcheable_table_layout .scrollable_area { + flex-grow: 1; + } + +.searchable_table { + table-layout: fixed; + word-break: break-all; +} + +.unselectable_table { + cursor: default; +} +.selectable_table tr.unselected_row:hover { + background: #eee; + cursor: pointer; +} +.selectable_table tr.selected_row { + background: #f1f1f1; + cursor: default; +} + +/* Todo... */ \ No newline at end of file diff --git a/src/hermes_dec/gui/static/styles/tabs.css b/src/hermes_dec/gui/static/styles/tabs.css new file mode 100644 index 0000000..e941305 --- /dev/null +++ b/src/hermes_dec/gui/static/styles/tabs.css @@ -0,0 +1,42 @@ +/** + * Tab system styling + */ + + .tab_view { + display: flex; + overflow: hidden; + flex-direction: column; +} + +.tab_list { + display: flex; + flex-direction: row; + flex-grow: 0; + justify-content: start; +} + +.tab { + background: #eee; + font-size: 16px; + padding: 9px; + cursor: default; + margin-right: 2px; +} + +.tab:not(.tab_current):hover { + background: #ddd; + cursor: pointer; +} + +.tab_current { + border-bottom: 2px solid aquamarine; +} + +.tab_contents { + flex-grow: 1; + margin-top: 9px; +} + +.scrollable_area { + overflow: auto; +} \ No newline at end of file diff --git a/src/hermes_dec/gui/static/utils.js b/src/hermes_dec/gui/static/utils.js new file mode 100644 index 0000000..31f255a --- /dev/null +++ b/src/hermes_dec/gui/static/utils.js @@ -0,0 +1,26 @@ +function format_size(size) { // Format a size in MiB or KiB + if(size >= 1024 * 1024) { // More than 1 MiB? + return Math.floor(size / 1024 / 1024 * 10) / 10 + ' MiB'; + } + else { + return Math.floor(size / 1024 / 1024 * 10) / 10 + ' KiB'; + } +}; + +function format_date(date) { + return date; // WIP +}; + +function to_hex(buf) { // buffer is an ArrayBuffer + return [...new Uint8Array(buf)] + .map(x => x.toString(16).padStart(2, '0')) + .join(''); +}; + + +async function hash_file_buffer(file_buffer) { + return to_hex( + await window.crypto.subtle.digest('SHA-256', file_buffer) + ); +}; + diff --git a/src/hermes_dec/gui/static/vue.global.prod.js b/src/hermes_dec/gui/static/vue.global.prod.js new file mode 100644 index 0000000..dffd3a1 --- /dev/null +++ b/src/hermes_dec/gui/static/vue.global.prod.js @@ -0,0 +1 @@ +var Vue=function(e){"use strict";function t(e,t){const n=Object.create(null),o=e.split(",");for(let r=0;r!!n[e.toLowerCase()]:e=>!!n[e]}const n={},o=[],r=()=>{},s=()=>!1,i=/^on[^a-z]/,l=e=>i.test(e),c=e=>e.startsWith("onUpdate:"),a=Object.assign,u=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},p=Object.prototype.hasOwnProperty,f=(e,t)=>p.call(e,t),d=Array.isArray,h=e=>"[object Map]"===C(e),m=e=>"[object Set]"===C(e),g=e=>"[object Date]"===C(e),v=e=>"function"==typeof e,y=e=>"string"==typeof e,_=e=>"symbol"==typeof e,b=e=>null!==e&&"object"==typeof e,S=e=>b(e)&&v(e.then)&&v(e.catch),x=Object.prototype.toString,C=e=>x.call(e),k=e=>"[object Object]"===C(e),w=e=>y(e)&&"NaN"!==e&&"-"!==e[0]&&""+parseInt(e,10)===e,T=t(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),E=t("bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo"),N=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},O=/-(\w)/g,$=N((e=>e.replace(O,((e,t)=>t?t.toUpperCase():"")))),P=/\B([A-Z])/g,A=N((e=>e.replace(P,"-$1").toLowerCase())),F=N((e=>e.charAt(0).toUpperCase()+e.slice(1))),R=N((e=>e?`on${F(e)}`:"")),M=(e,t)=>!Object.is(e,t),V=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},B=e=>{const t=parseFloat(e);return isNaN(t)?e:t},L=e=>{const t=y(e)?Number(e):NaN;return isNaN(t)?e:t};let j;const U=t("Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console");function D(e){if(d(e)){const t={};for(let n=0;n{if(e){const n=e.split(W);n.length>1&&(t[n[0].trim()]=n[1].trim())}})),t}function G(e){let t="";if(y(e))t=e;else if(d(e))for(let n=0;nX(e,t)))}const te=(e,t)=>t&&t.__v_isRef?te(e,t.value):h(t)?{[`Map(${t.size})`]:[...t.entries()].reduce(((e,[t,n])=>(e[`${t} =>`]=n,e)),{})}:m(t)?{[`Set(${t.size})`]:[...t.values()]}:!b(t)||d(t)||k(t)?t:String(t);let ne;class oe{constructor(e=!1){this.detached=e,this._active=!0,this.effects=[],this.cleanups=[],this.parent=ne,!e&&ne&&(this.index=(ne.scopes||(ne.scopes=[])).push(this)-1)}get active(){return this._active}run(e){if(this._active){const t=ne;try{return ne=this,e()}finally{ne=t}}}on(){ne=this}off(){ne=this.parent}stop(e){if(this._active){let t,n;for(t=0,n=this.effects.length;t{const t=new Set(e);return t.w=0,t.n=0,t},le=e=>(e.w&pe)>0,ce=e=>(e.n&pe)>0,ae=new WeakMap;let ue=0,pe=1;let fe;const de=Symbol(""),he=Symbol("");class me{constructor(e,t=null,n){this.fn=e,this.scheduler=t,this.active=!0,this.deps=[],this.parent=void 0,re(this,n)}run(){if(!this.active)return this.fn();let e=fe,t=ve;for(;e;){if(e===this)return;e=e.parent}try{return this.parent=fe,fe=this,ve=!0,pe=1<<++ue,ue<=30?(({deps:e})=>{if(e.length)for(let t=0;t{const{deps:t}=e;if(t.length){let n=0;for(let o=0;o{("length"===n||n>=e)&&l.push(t)}))}else switch(void 0!==n&&l.push(i.get(n)),t){case"add":d(e)?w(n)&&l.push(i.get("length")):(l.push(i.get(de)),h(e)&&l.push(i.get(he)));break;case"delete":d(e)||(l.push(i.get(de)),h(e)&&l.push(i.get(he)));break;case"set":h(e)&&l.push(i.get(de))}if(1===l.length)l[0]&&ke(l[0]);else{const e=[];for(const t of l)t&&e.push(...t);ke(ie(e))}}function ke(e,t){const n=d(e)?e:[...e];for(const o of n)o.computed&&we(o);for(const o of n)o.computed||we(o)}function we(e,t){(e!==fe||e.allowRecurse)&&(e.scheduler?e.scheduler():e.run())}const Te=t("__proto__,__v_isRef,__isVue"),Ee=new Set(Object.getOwnPropertyNames(Symbol).filter((e=>"arguments"!==e&&"caller"!==e)).map((e=>Symbol[e])).filter(_)),Ne=Me(),Oe=Me(!1,!0),$e=Me(!0),Pe=Me(!0,!0),Ae=Fe();function Fe(){const e={};return["includes","indexOf","lastIndexOf"].forEach((t=>{e[t]=function(...e){const n=xt(this);for(let t=0,r=this.length;t{e[t]=function(...e){_e();const n=xt(this)[t].apply(this,e);return be(),n}})),e}function Re(e){const t=xt(this);return Se(t,0,e),t.hasOwnProperty(e)}function Me(e=!1,t=!1){return function(n,o,r){if("__v_isReactive"===o)return!e;if("__v_isReadonly"===o)return e;if("__v_isShallow"===o)return t;if("__v_raw"===o&&r===(e?t?ft:pt:t?ut:at).get(n))return n;const s=d(n);if(!e){if(s&&f(Ae,o))return Reflect.get(Ae,o,r);if("hasOwnProperty"===o)return Re}const i=Reflect.get(n,o,r);return(_(o)?Ee.has(o):Te(o))?i:(e||Se(n,0,o),t?i:Nt(i)?s&&w(o)?i:i.value:b(i)?e?gt(i):ht(i):i)}}function Ve(e=!1){return function(t,n,o,r){let s=t[n];if(_t(s)&&Nt(s)&&!Nt(o))return!1;if(!e&&(bt(o)||_t(o)||(s=xt(s),o=xt(o)),!d(t)&&Nt(s)&&!Nt(o)))return s.value=o,!0;const i=d(t)&&w(n)?Number(n)!0,deleteProperty:(e,t)=>!0},Le=a({},Ie,{get:Oe,set:Ve(!0)}),je=a({},Be,{get:Pe}),Ue=e=>e,De=e=>Reflect.getPrototypeOf(e);function He(e,t,n=!1,o=!1){const r=xt(e=e.__v_raw),s=xt(t);n||(t!==s&&Se(r,0,t),Se(r,0,s));const{has:i}=De(r),l=o?Ue:n?wt:kt;return i.call(r,t)?l(e.get(t)):i.call(r,s)?l(e.get(s)):void(e!==r&&e.get(t))}function We(e,t=!1){const n=this.__v_raw,o=xt(n),r=xt(e);return t||(e!==r&&Se(o,0,e),Se(o,0,r)),e===r?n.has(e):n.has(e)||n.has(r)}function ze(e,t=!1){return e=e.__v_raw,!t&&Se(xt(e),0,de),Reflect.get(e,"size",e)}function Ke(e){e=xt(e);const t=xt(this);return De(t).has.call(t,e)||(t.add(e),Ce(t,"add",e,e)),this}function Ge(e,t){t=xt(t);const n=xt(this),{has:o,get:r}=De(n);let s=o.call(n,e);s||(e=xt(e),s=o.call(n,e));const i=r.call(n,e);return n.set(e,t),s?M(t,i)&&Ce(n,"set",e,t):Ce(n,"add",e,t),this}function qe(e){const t=xt(this),{has:n,get:o}=De(t);let r=n.call(t,e);r||(e=xt(e),r=n.call(t,e)),o&&o.call(t,e);const s=t.delete(e);return r&&Ce(t,"delete",e,void 0),s}function Je(){const e=xt(this),t=0!==e.size,n=e.clear();return t&&Ce(e,"clear",void 0,void 0),n}function Ze(e,t){return function(n,o){const r=this,s=r.__v_raw,i=xt(s),l=t?Ue:e?wt:kt;return!e&&Se(i,0,de),s.forEach(((e,t)=>n.call(o,l(e),l(t),r)))}}function Ye(e,t,n){return function(...o){const r=this.__v_raw,s=xt(r),i=h(s),l="entries"===e||e===Symbol.iterator&&i,c="keys"===e&&i,a=r[e](...o),u=n?Ue:t?wt:kt;return!t&&Se(s,0,c?he:de),{next(){const{value:e,done:t}=a.next();return t?{value:e,done:t}:{value:l?[u(e[0]),u(e[1])]:u(e),done:t}},[Symbol.iterator](){return this}}}}function Qe(e){return function(...t){return"delete"!==e&&this}}function Xe(){const e={get(e){return He(this,e)},get size(){return ze(this)},has:We,add:Ke,set:Ge,delete:qe,clear:Je,forEach:Ze(!1,!1)},t={get(e){return He(this,e,!1,!0)},get size(){return ze(this)},has:We,add:Ke,set:Ge,delete:qe,clear:Je,forEach:Ze(!1,!0)},n={get(e){return He(this,e,!0)},get size(){return ze(this,!0)},has(e){return We.call(this,e,!0)},add:Qe("add"),set:Qe("set"),delete:Qe("delete"),clear:Qe("clear"),forEach:Ze(!0,!1)},o={get(e){return He(this,e,!0,!0)},get size(){return ze(this,!0)},has(e){return We.call(this,e,!0)},add:Qe("add"),set:Qe("set"),delete:Qe("delete"),clear:Qe("clear"),forEach:Ze(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach((r=>{e[r]=Ye(r,!1,!1),n[r]=Ye(r,!0,!1),t[r]=Ye(r,!1,!0),o[r]=Ye(r,!0,!0)})),[e,n,t,o]}const[et,tt,nt,ot]=Xe();function rt(e,t){const n=t?e?ot:nt:e?tt:et;return(t,o,r)=>"__v_isReactive"===o?!e:"__v_isReadonly"===o?e:"__v_raw"===o?t:Reflect.get(f(n,o)&&o in t?n:t,o,r)}const st={get:rt(!1,!1)},it={get:rt(!1,!0)},lt={get:rt(!0,!1)},ct={get:rt(!0,!0)},at=new WeakMap,ut=new WeakMap,pt=new WeakMap,ft=new WeakMap;function dt(e){return e.__v_skip||!Object.isExtensible(e)?0:function(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}((e=>C(e).slice(8,-1))(e))}function ht(e){return _t(e)?e:vt(e,!1,Ie,st,at)}function mt(e){return vt(e,!1,Le,it,ut)}function gt(e){return vt(e,!0,Be,lt,pt)}function vt(e,t,n,o,r){if(!b(e))return e;if(e.__v_raw&&(!t||!e.__v_isReactive))return e;const s=r.get(e);if(s)return s;const i=dt(e);if(0===i)return e;const l=new Proxy(e,2===i?o:n);return r.set(e,l),l}function yt(e){return _t(e)?yt(e.__v_raw):!(!e||!e.__v_isReactive)}function _t(e){return!(!e||!e.__v_isReadonly)}function bt(e){return!(!e||!e.__v_isShallow)}function St(e){return yt(e)||_t(e)}function xt(e){const t=e&&e.__v_raw;return t?xt(t):e}function Ct(e){return I(e,"__v_skip",!0),e}const kt=e=>b(e)?ht(e):e,wt=e=>b(e)?gt(e):e;function Tt(e){ve&&fe&&xe((e=xt(e)).dep||(e.dep=ie()))}function Et(e,t){const n=(e=xt(e)).dep;n&&ke(n)}function Nt(e){return!(!e||!0!==e.__v_isRef)}function Ot(e){return $t(e,!1)}function $t(e,t){return Nt(e)?e:new Pt(e,t)}class Pt{constructor(e,t){this.__v_isShallow=t,this.dep=void 0,this.__v_isRef=!0,this._rawValue=t?e:xt(e),this._value=t?e:kt(e)}get value(){return Tt(this),this._value}set value(e){const t=this.__v_isShallow||bt(e)||_t(e);e=t?e:xt(e),M(e,this._rawValue)&&(this._rawValue=e,this._value=t?e:kt(e),Et(this))}}function At(e){return Nt(e)?e.value:e}const Ft={get:(e,t,n)=>At(Reflect.get(e,t,n)),set:(e,t,n,o)=>{const r=e[t];return Nt(r)&&!Nt(n)?(r.value=n,!0):Reflect.set(e,t,n,o)}};function Rt(e){return yt(e)?e:new Proxy(e,Ft)}class Mt{constructor(e){this.dep=void 0,this.__v_isRef=!0;const{get:t,set:n}=e((()=>Tt(this)),(()=>Et(this)));this._get=t,this._set=n}get value(){return this._get()}set value(e){this._set(e)}}class Vt{constructor(e,t,n){this._object=e,this._key=t,this._defaultValue=n,this.__v_isRef=!0}get value(){const e=this._object[this._key];return void 0===e?this._defaultValue:e}set value(e){this._object[this._key]=e}get dep(){return e=xt(this._object),t=this._key,null==(n=ae.get(e))?void 0:n.get(t);var e,t,n}}class It{constructor(e){this._getter=e,this.__v_isRef=!0,this.__v_isReadonly=!0}get value(){return this._getter()}}function Bt(e,t,n){const o=e[t];return Nt(o)?o:new Vt(e,t,n)}class Lt{constructor(e,t,n,o){this._setter=t,this.dep=void 0,this.__v_isRef=!0,this.__v_isReadonly=!1,this._dirty=!0,this.effect=new me(e,(()=>{this._dirty||(this._dirty=!0,Et(this))})),this.effect.computed=this,this.effect.active=this._cacheable=!o,this.__v_isReadonly=n}get value(){const e=xt(this);return Tt(e),!e._dirty&&e._cacheable||(e._dirty=!1,e._value=e.effect.run()),e._value}set value(e){this._setter(e)}}function jt(e,t,n,o){let r;try{r=o?e(...o):e()}catch(s){Dt(s,t,n)}return r}function Ut(e,t,n,o){if(v(e)){const r=jt(e,t,n,o);return r&&S(r)&&r.catch((e=>{Dt(e,t,n)})),r}const r=[];for(let s=0;s>>1;rn(zt[o])rn(e)-rn(t))),Jt=0;Jtnull==e.id?1/0:e.id,sn=(e,t)=>{const n=rn(e)-rn(t);if(0===n){if(e.pre&&!t.pre)return-1;if(t.pre&&!e.pre)return 1}return n};function ln(e){Wt=!1,Ht=!0,zt.sort(sn);try{for(Kt=0;Kty(e)?e.trim():e))),t&&(s=o.map(B))}let c,a=r[c=R(t)]||r[c=R($(t))];!a&&i&&(a=r[c=R(A(t))]),a&&Ut(a,e,6,s);const u=r[c+"Once"];if(u){if(e.emitted){if(e.emitted[c])return}else e.emitted={};e.emitted[c]=!0,Ut(u,e,6,s)}}function un(e,t,n=!1){const o=t.emitsCache,r=o.get(e);if(void 0!==r)return r;const s=e.emits;let i={},l=!1;if(!v(e)){const o=e=>{const n=un(e,t,!0);n&&(l=!0,a(i,n))};!n&&t.mixins.length&&t.mixins.forEach(o),e.extends&&o(e.extends),e.mixins&&e.mixins.forEach(o)}return s||l?(d(s)?s.forEach((e=>i[e]=null)):a(i,s),b(e)&&o.set(e,i),i):(b(e)&&o.set(e,null),null)}function pn(e,t){return!(!e||!l(t))&&(t=t.slice(2).replace(/Once$/,""),f(e,t[0].toLowerCase()+t.slice(1))||f(e,A(t))||f(e,t))}let fn=null,dn=null;function hn(e){const t=fn;return fn=e,dn=e&&e.type.__scopeId||null,t}function mn(e,t=fn,n){if(!t)return e;if(e._n)return e;const o=(...n)=>{o._d&&Pr(-1);const r=hn(t);let s;try{s=e(...n)}finally{hn(r),o._d&&Pr(1)}return s};return o._n=!0,o._c=!0,o._d=!0,o}function gn(e){const{type:t,vnode:n,proxy:o,withProxy:r,props:s,propsOptions:[i],slots:l,attrs:a,emit:u,render:p,renderCache:f,data:d,setupState:h,ctx:m,inheritAttrs:g}=e;let v,y;const _=hn(e);try{if(4&n.shapeFlag){const e=r||o;v=Wr(p.call(e,e,f,s,h,d,m)),y=a}else{const e=t;0,v=Wr(e(s,e.length>1?{attrs:a,slots:l,emit:u}:null)),y=t.props?a:vn(a)}}catch(S){Tr.length=0,Dt(S,e,1),v=jr(kr)}let b=v;if(y&&!1!==g){const e=Object.keys(y),{shapeFlag:t}=b;e.length&&7&t&&(i&&e.some(c)&&(y=yn(y,i)),b=Dr(b,y))}return n.dirs&&(b=Dr(b),b.dirs=b.dirs?b.dirs.concat(n.dirs):n.dirs),n.transition&&(b.transition=n.transition),v=b,hn(_),v}const vn=e=>{let t;for(const n in e)("class"===n||"style"===n||l(n))&&((t||(t={}))[n]=e[n]);return t},yn=(e,t)=>{const n={};for(const o in e)c(o)&&o.slice(9)in t||(n[o]=e[o]);return n};function _n(e,t,n){const o=Object.keys(t);if(o.length!==Object.keys(e).length)return!0;for(let r=0;re.__isSuspense,xn={name:"Suspense",__isSuspense:!0,process(e,t,n,o,r,s,i,l,c,a){null==e?function(e,t,n,o,r,s,i,l,c){const{p:a,o:{createElement:u}}=c,p=u("div"),f=e.suspense=kn(e,r,o,t,p,n,s,i,l,c);a(null,f.pendingBranch=e.ssContent,p,null,o,f,s,i),f.deps>0?(Cn(e,"onPending"),Cn(e,"onFallback"),a(null,e.ssFallback,t,n,o,null,s,i),En(f,e.ssFallback)):f.resolve(!1,!0)}(t,n,o,r,s,i,l,c,a):function(e,t,n,o,r,s,i,l,{p:c,um:a,o:{createElement:u}}){const p=t.suspense=e.suspense;p.vnode=t,t.el=e.el;const f=t.ssContent,d=t.ssFallback,{activeBranch:h,pendingBranch:m,isInFallback:g,isHydrating:v}=p;if(m)p.pendingBranch=f,Mr(f,m)?(c(m,f,p.hiddenContainer,null,r,p,s,i,l),p.deps<=0?p.resolve():g&&(c(h,d,n,o,r,null,s,i,l),En(p,d))):(p.pendingId++,v?(p.isHydrating=!1,p.activeBranch=m):a(m,r,p),p.deps=0,p.effects.length=0,p.hiddenContainer=u("div"),g?(c(null,f,p.hiddenContainer,null,r,p,s,i,l),p.deps<=0?p.resolve():(c(h,d,n,o,r,null,s,i,l),En(p,d))):h&&Mr(f,h)?(c(h,f,n,o,r,p,s,i,l),p.resolve(!0)):(c(null,f,p.hiddenContainer,null,r,p,s,i,l),p.deps<=0&&p.resolve()));else if(h&&Mr(f,h))c(h,f,n,o,r,p,s,i,l),En(p,f);else if(Cn(t,"onPending"),p.pendingBranch=f,p.pendingId++,c(null,f,p.hiddenContainer,null,r,p,s,i,l),p.deps<=0)p.resolve();else{const{timeout:e,pendingId:t}=p;e>0?setTimeout((()=>{p.pendingId===t&&p.fallback(d)}),e):0===e&&p.fallback(d)}}(e,t,n,o,r,i,l,c,a)},hydrate:function(e,t,n,o,r,s,i,l,c){const a=t.suspense=kn(t,o,n,e.parentNode,document.createElement("div"),null,r,s,i,l,!0),u=c(e,a.pendingBranch=t.ssContent,n,a,s,i);0===a.deps&&a.resolve(!1,!0);return u},create:kn,normalize:function(e){const{shapeFlag:t,children:n}=e,o=32&t;e.ssContent=wn(o?n.default:n),e.ssFallback=o?wn(n.fallback):jr(kr)}};function Cn(e,t){const n=e.props&&e.props[t];v(n)&&n()}function kn(e,t,n,o,r,s,i,l,c,a,u=!1){const{p:p,m:f,um:d,n:h,o:{parentNode:m,remove:g}}=a;let v;const y=function(e){var t;return null!=(null==(t=e.props)?void 0:t.suspensible)&&!1!==e.props.suspensible}(e);y&&(null==t?void 0:t.pendingBranch)&&(v=t.pendingId,t.deps++);const _=e.props?L(e.props.timeout):void 0,b={vnode:e,parent:t,parentComponent:n,isSVG:i,container:o,hiddenContainer:r,anchor:s,deps:0,pendingId:0,timeout:"number"==typeof _?_:-1,activeBranch:null,pendingBranch:null,isInFallback:!0,isHydrating:u,isUnmounted:!1,effects:[],resolve(e=!1,n=!1){const{vnode:o,activeBranch:r,pendingBranch:s,pendingId:i,effects:l,parentComponent:c,container:a}=b;if(b.isHydrating)b.isHydrating=!1;else if(!e){const e=r&&s.transition&&"out-in"===s.transition.mode;e&&(r.transition.afterLeave=()=>{i===b.pendingId&&f(s,a,t,0)});let{anchor:t}=b;r&&(t=h(r),d(r,c,b,!0)),e||f(s,a,t,0)}En(b,s),b.pendingBranch=null,b.isInFallback=!1;let u=b.parent,p=!1;for(;u;){if(u.pendingBranch){u.effects.push(...l),p=!0;break}u=u.parent}p||tn(l),b.effects=[],y&&t&&t.pendingBranch&&v===t.pendingId&&(t.deps--,0!==t.deps||n||t.resolve()),Cn(o,"onResolve")},fallback(e){if(!b.pendingBranch)return;const{vnode:t,activeBranch:n,parentComponent:o,container:r,isSVG:s}=b;Cn(t,"onFallback");const i=h(n),a=()=>{b.isInFallback&&(p(null,e,r,i,o,null,s,l,c),En(b,e))},u=e.transition&&"out-in"===e.transition.mode;u&&(n.transition.afterLeave=a),b.isInFallback=!0,d(n,o,null,!0),u||a()},move(e,t,n){b.activeBranch&&f(b.activeBranch,e,t,n),b.container=e},next:()=>b.activeBranch&&h(b.activeBranch),registerDep(e,t){const n=!!b.pendingBranch;n&&b.deps++;const o=e.vnode.el;e.asyncDep.catch((t=>{Dt(t,e,0)})).then((r=>{if(e.isUnmounted||b.isUnmounted||b.pendingId!==e.suspenseId)return;e.asyncResolved=!0;const{vnode:s}=e;is(e,r,!1),o&&(s.el=o);const l=!o&&e.subTree.el;t(e,s,m(o||e.subTree.el),o?null:h(e.subTree),b,i,c),l&&g(l),bn(e,s.el),n&&0==--b.deps&&b.resolve()}))},unmount(e,t){b.isUnmounted=!0,b.activeBranch&&d(b.activeBranch,n,e,t),b.pendingBranch&&d(b.pendingBranch,n,e,t)}};return b}function wn(e){let t;if(v(e)){const n=$r&&e._c;n&&(e._d=!1,Nr()),e=e(),n&&(e._d=!0,t=Er,Or())}if(d(e)){const t=function(e){let t;for(let n=0;nt!==e))),e}function Tn(e,t){t&&t.pendingBranch?d(e)?t.effects.push(...e):t.effects.push(e):tn(e)}function En(e,t){e.activeBranch=t;const{vnode:n,parentComponent:o}=e,r=n.el=t.el;o&&o.subTree===n&&(o.vnode.el=r,bn(o,r))}function Nn(e,t){return Pn(e,null,{flush:"post"})}const On={};function $n(e,t,n){return Pn(e,t,n)}function Pn(e,t,{immediate:o,deep:s,flush:i}=n){var l;const c=se()===(null==(l=Yr)?void 0:l.scope)?Yr:null;let a,p,f=!1,h=!1;if(Nt(e)?(a=()=>e.value,f=bt(e)):yt(e)?(a=()=>e,s=!0):d(e)?(h=!0,f=e.some((e=>yt(e)||bt(e))),a=()=>e.map((e=>Nt(e)?e.value:yt(e)?Rn(e):v(e)?jt(e,c,2):void 0))):a=v(e)?t?()=>jt(e,c,2):()=>{if(!c||!c.isUnmounted)return p&&p(),Ut(e,c,3,[m])}:r,t&&s){const e=a;a=()=>Rn(e())}let m=e=>{p=b.onStop=()=>{jt(e,c,4)}},g=h?new Array(e.length).fill(On):On;const y=()=>{if(b.active)if(t){const e=b.run();(s||f||(h?e.some(((e,t)=>M(e,g[t]))):M(e,g)))&&(p&&p(),Ut(t,c,3,[e,g===On?void 0:h&&g[0]===On?[]:g,m]),g=e)}else b.run()};let _;y.allowRecurse=!!t,"sync"===i?_=y:"post"===i?_=()=>ur(y,c&&c.suspense):(y.pre=!0,c&&(y.id=c.uid),_=()=>Xt(y));const b=new me(a,_);t?o?y():g=b.run():"post"===i?ur(b.run.bind(b),c&&c.suspense):b.run();return()=>{b.stop(),c&&c.scope&&u(c.scope.effects,b)}}function An(e,t,n){const o=this.proxy,r=y(e)?e.includes(".")?Fn(o,e):()=>o[e]:e.bind(o,o);let s;v(t)?s=t:(s=t.handler,n=t);const i=Yr;es(this);const l=Pn(r,s.bind(o),n);return i?es(i):ts(),l}function Fn(e,t){const n=t.split(".");return()=>{let t=e;for(let e=0;e{Rn(e,t)}));else if(k(e))for(const n in e)Rn(e[n],t);return e}function Mn(e,t,n,o){const r=e.dirs,s=t&&t.dirs;for(let i=0;i{e.isMounted=!0})),uo((()=>{e.isUnmounting=!0})),e}const In=[Function,Array],Bn={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:In,onEnter:In,onAfterEnter:In,onEnterCancelled:In,onBeforeLeave:In,onLeave:In,onAfterLeave:In,onLeaveCancelled:In,onBeforeAppear:In,onAppear:In,onAfterAppear:In,onAppearCancelled:In},Ln={name:"BaseTransition",props:Bn,setup(e,{slots:t}){const n=Qr(),o=Vn();let r;return()=>{const s=t.default&&zn(t.default(),!0);if(!s||!s.length)return;let i=s[0];if(s.length>1)for(const e of s)if(e.type!==kr){i=e;break}const l=xt(e),{mode:c}=l;if(o.isLeaving)return Dn(i);const a=Hn(i);if(!a)return Dn(i);const u=Un(a,l,o,n);Wn(a,u);const p=n.subTree,f=p&&Hn(p);let d=!1;const{getTransitionKey:h}=a.type;if(h){const e=h();void 0===r?r=e:e!==r&&(r=e,d=!0)}if(f&&f.type!==kr&&(!Mr(a,f)||d)){const e=Un(f,l,o,n);if(Wn(f,e),"out-in"===c)return o.isLeaving=!0,e.afterLeave=()=>{o.isLeaving=!1,!1!==n.update.active&&n.update()},Dn(i);"in-out"===c&&a.type!==kr&&(e.delayLeave=(e,t,n)=>{jn(o,f)[String(f.key)]=f,e._leaveCb=()=>{t(),e._leaveCb=void 0,delete u.delayedLeave},u.delayedLeave=n})}return i}}};function jn(e,t){const{leavingVNodes:n}=e;let o=n.get(t.type);return o||(o=Object.create(null),n.set(t.type,o)),o}function Un(e,t,n,o){const{appear:r,mode:s,persisted:i=!1,onBeforeEnter:l,onEnter:c,onAfterEnter:a,onEnterCancelled:u,onBeforeLeave:p,onLeave:f,onAfterLeave:h,onLeaveCancelled:m,onBeforeAppear:g,onAppear:v,onAfterAppear:y,onAppearCancelled:_}=t,b=String(e.key),S=jn(n,e),x=(e,t)=>{e&&Ut(e,o,9,t)},C=(e,t)=>{const n=t[1];x(e,t),d(e)?e.every((e=>e.length<=1))&&n():e.length<=1&&n()},k={mode:s,persisted:i,beforeEnter(t){let o=l;if(!n.isMounted){if(!r)return;o=g||l}t._leaveCb&&t._leaveCb(!0);const s=S[b];s&&Mr(e,s)&&s.el._leaveCb&&s.el._leaveCb(),x(o,[t])},enter(e){let t=c,o=a,s=u;if(!n.isMounted){if(!r)return;t=v||c,o=y||a,s=_||u}let i=!1;const l=e._enterCb=t=>{i||(i=!0,x(t?s:o,[e]),k.delayedLeave&&k.delayedLeave(),e._enterCb=void 0)};t?C(t,[e,l]):l()},leave(t,o){const r=String(e.key);if(t._enterCb&&t._enterCb(!0),n.isUnmounting)return o();x(p,[t]);let s=!1;const i=t._leaveCb=n=>{s||(s=!0,o(),x(n?m:h,[t]),t._leaveCb=void 0,S[r]===e&&delete S[r])};S[r]=e,f?C(f,[t,i]):i()},clone:e=>Un(e,t,n,o)};return k}function Dn(e){if(Jn(e))return(e=Dr(e)).children=null,e}function Hn(e){return Jn(e)?e.children?e.children[0]:void 0:e}function Wn(e,t){6&e.shapeFlag&&e.component?Wn(e.component.subTree,t):128&e.shapeFlag?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function zn(e,t=!1,n){let o=[],r=0;for(let s=0;s1)for(let s=0;sa({name:e.name},t,{setup:e}))():e}const Gn=e=>!!e.type.__asyncLoader;function qn(e,t){const{ref:n,props:o,children:r,ce:s}=t.vnode,i=jr(e,o,r);return i.ref=n,i.ce=s,delete t.vnode.ce,i}const Jn=e=>e.type.__isKeepAlive,Zn={name:"KeepAlive",__isKeepAlive:!0,props:{include:[String,RegExp,Array],exclude:[String,RegExp,Array],max:[String,Number]},setup(e,{slots:t}){const n=Qr(),o=n.ctx,r=new Map,s=new Set;let i=null;const l=n.suspense,{renderer:{p:c,m:a,um:u,o:{createElement:p}}}=o,f=p("div");function d(e){no(e),u(e,n,l,!0)}function h(e){r.forEach(((t,n)=>{const o=ps(t.type);!o||e&&e(o)||m(n)}))}function m(e){const t=r.get(e);i&&Mr(t,i)?i&&no(i):d(t),r.delete(e),s.delete(e)}o.activate=(e,t,n,o,r)=>{const s=e.component;a(e,t,n,0,l),c(s.vnode,e,t,n,s,l,o,e.slotScopeIds,r),ur((()=>{s.isDeactivated=!1,s.a&&V(s.a);const t=e.props&&e.props.onVnodeMounted;t&&qr(t,s.parent,e)}),l)},o.deactivate=e=>{const t=e.component;a(e,f,null,1,l),ur((()=>{t.da&&V(t.da);const n=e.props&&e.props.onVnodeUnmounted;n&&qr(n,t.parent,e),t.isDeactivated=!0}),l)},$n((()=>[e.include,e.exclude]),(([e,t])=>{e&&h((t=>Yn(e,t))),t&&h((e=>!Yn(t,e)))}),{flush:"post",deep:!0});let g=null;const v=()=>{null!=g&&r.set(g,oo(n.subTree))};return lo(v),ao(v),uo((()=>{r.forEach((e=>{const{subTree:t,suspense:o}=n,r=oo(t);if(e.type!==r.type||e.key!==r.key)d(e);else{no(r);const e=r.component.da;e&&ur(e,o)}}))})),()=>{if(g=null,!t.default)return null;const n=t.default(),o=n[0];if(n.length>1)return i=null,n;if(!(Rr(o)&&(4&o.shapeFlag||128&o.shapeFlag)))return i=null,o;let l=oo(o);const c=l.type,a=ps(Gn(l)?l.type.__asyncResolved||{}:c),{include:u,exclude:p,max:f}=e;if(u&&(!a||!Yn(u,a))||p&&a&&Yn(p,a))return i=l,o;const d=null==l.key?c:l.key,h=r.get(d);return l.el&&(l=Dr(l),128&o.shapeFlag&&(o.ssContent=l)),g=d,h?(l.el=h.el,l.component=h.component,l.transition&&Wn(l,l.transition),l.shapeFlag|=512,s.delete(d),s.add(d)):(s.add(d),f&&s.size>parseInt(f,10)&&m(s.values().next().value)),l.shapeFlag|=256,i=l,Sn(o.type)?o:l}}};function Yn(e,t){return d(e)?e.some((e=>Yn(e,t))):y(e)?e.split(",").includes(t):"[object RegExp]"===C(e)&&e.test(t)}function Qn(e,t){eo(e,"a",t)}function Xn(e,t){eo(e,"da",t)}function eo(e,t,n=Yr){const o=e.__wdc||(e.__wdc=()=>{let t=n;for(;t;){if(t.isDeactivated)return;t=t.parent}return e()});if(ro(t,o,n),n){let e=n.parent;for(;e&&e.parent;)Jn(e.parent.vnode)&&to(o,t,n,e),e=e.parent}}function to(e,t,n,o){const r=ro(t,e,o,!0);po((()=>{u(o[t],r)}),n)}function no(e){e.shapeFlag&=-257,e.shapeFlag&=-513}function oo(e){return 128&e.shapeFlag?e.ssContent:e}function ro(e,t,n=Yr,o=!1){if(n){const r=n[e]||(n[e]=[]),s=t.__weh||(t.__weh=(...o)=>{if(n.isUnmounted)return;_e(),es(n);const r=Ut(t,n,e,o);return ts(),be(),r});return o?r.unshift(s):r.push(s),s}}const so=e=>(t,n=Yr)=>(!ss||"sp"===e)&&ro(e,((...e)=>t(...e)),n),io=so("bm"),lo=so("m"),co=so("bu"),ao=so("u"),uo=so("bum"),po=so("um"),fo=so("sp"),ho=so("rtg"),mo=so("rtc");function go(e,t=Yr){ro("ec",e,t)}const vo="components";const yo=Symbol.for("v-ndc");function _o(e,t,n=!0,o=!1){const r=fn||Yr;if(r){const n=r.type;if(e===vo){const e=ps(n,!1);if(e&&(e===t||e===$(t)||e===F($(t))))return n}const s=bo(r[e]||n[e],t)||bo(r.appContext[e],t);return!s&&o?n:s}}function bo(e,t){return e&&(e[t]||e[$(t)]||e[F($(t))])}function So(e){return e.some((e=>!Rr(e)||e.type!==kr&&!(e.type===xr&&!So(e.children))))?e:null}const xo=e=>e?ns(e)?us(e)||e.proxy:xo(e.parent):null,Co=a(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>xo(e.parent),$root:e=>xo(e.root),$emit:e=>e.emit,$options:e=>Fo(e),$forceUpdate:e=>e.f||(e.f=()=>Xt(e.update)),$nextTick:e=>e.n||(e.n=Qt.bind(e.proxy)),$watch:e=>An.bind(e)}),ko=(e,t)=>e!==n&&!e.__isScriptSetup&&f(e,t),wo={get({_:e},t){const{ctx:o,setupState:r,data:s,props:i,accessCache:l,type:c,appContext:a}=e;let u;if("$"!==t[0]){const c=l[t];if(void 0!==c)switch(c){case 1:return r[t];case 2:return s[t];case 4:return o[t];case 3:return i[t]}else{if(ko(r,t))return l[t]=1,r[t];if(s!==n&&f(s,t))return l[t]=2,s[t];if((u=e.propsOptions[0])&&f(u,t))return l[t]=3,i[t];if(o!==n&&f(o,t))return l[t]=4,o[t];Oo&&(l[t]=0)}}const p=Co[t];let d,h;return p?("$attrs"===t&&Se(e,0,t),p(e)):(d=c.__cssModules)&&(d=d[t])?d:o!==n&&f(o,t)?(l[t]=4,o[t]):(h=a.config.globalProperties,f(h,t)?h[t]:void 0)},set({_:e},t,o){const{data:r,setupState:s,ctx:i}=e;return ko(s,t)?(s[t]=o,!0):r!==n&&f(r,t)?(r[t]=o,!0):!f(e.props,t)&&(("$"!==t[0]||!(t.slice(1)in e))&&(i[t]=o,!0))},has({_:{data:e,setupState:t,accessCache:o,ctx:r,appContext:s,propsOptions:i}},l){let c;return!!o[l]||e!==n&&f(e,l)||ko(t,l)||(c=i[0])&&f(c,l)||f(r,l)||f(Co,l)||f(s.config.globalProperties,l)},defineProperty(e,t,n){return null!=n.get?e._.accessCache[t]=0:f(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}},To=a({},wo,{get(e,t){if(t!==Symbol.unscopables)return wo.get(e,t,e)},has:(e,t)=>"_"!==t[0]&&!U(t)});function Eo(){const e=Qr();return e.setupContext||(e.setupContext=as(e))}function No(e){return d(e)?e.reduce(((e,t)=>(e[t]=null,e)),{}):e}let Oo=!0;function $o(e){const t=Fo(e),n=e.proxy,o=e.ctx;Oo=!1,t.beforeCreate&&Po(t.beforeCreate,e,"bc");const{data:s,computed:i,methods:l,watch:c,provide:a,inject:u,created:p,beforeMount:f,mounted:h,beforeUpdate:m,updated:g,activated:y,deactivated:_,beforeUnmount:S,unmounted:x,render:C,renderTracked:k,renderTriggered:w,errorCaptured:T,serverPrefetch:E,expose:N,inheritAttrs:O,components:$,directives:P}=t;if(u&&function(e,t,n=r){d(e)&&(e=Io(e));for(const o in e){const n=e[o];let r;r=b(n)?"default"in n?Ko(n.from||o,n.default,!0):Ko(n.from||o):Ko(n),Nt(r)?Object.defineProperty(t,o,{enumerable:!0,configurable:!0,get:()=>r.value,set:e=>r.value=e}):t[o]=r}}(u,o,null),l)for(const r in l){const e=l[r];v(e)&&(o[r]=e.bind(n))}if(s){const t=s.call(n,n);b(t)&&(e.data=ht(t))}if(Oo=!0,i)for(const d in i){const e=i[d],t=v(e)?e.bind(n,n):v(e.get)?e.get.bind(n,n):r,s=!v(e)&&v(e.set)?e.set.bind(n):r,l=fs({get:t,set:s});Object.defineProperty(o,d,{enumerable:!0,configurable:!0,get:()=>l.value,set:e=>l.value=e})}if(c)for(const r in c)Ao(c[r],o,n,r);if(a){const e=v(a)?a.call(n):a;Reflect.ownKeys(e).forEach((t=>{zo(t,e[t])}))}function A(e,t){d(t)?t.forEach((t=>e(t.bind(n)))):t&&e(t.bind(n))}if(p&&Po(p,e,"c"),A(io,f),A(lo,h),A(co,m),A(ao,g),A(Qn,y),A(Xn,_),A(go,T),A(mo,k),A(ho,w),A(uo,S),A(po,x),A(fo,E),d(N))if(N.length){const t=e.exposed||(e.exposed={});N.forEach((e=>{Object.defineProperty(t,e,{get:()=>n[e],set:t=>n[e]=t})}))}else e.exposed||(e.exposed={});C&&e.render===r&&(e.render=C),null!=O&&(e.inheritAttrs=O),$&&(e.components=$),P&&(e.directives=P)}function Po(e,t,n){Ut(d(e)?e.map((e=>e.bind(t.proxy))):e.bind(t.proxy),t,n)}function Ao(e,t,n,o){const r=o.includes(".")?Fn(n,o):()=>n[o];if(y(e)){const n=t[e];v(n)&&$n(r,n)}else if(v(e))$n(r,e.bind(n));else if(b(e))if(d(e))e.forEach((e=>Ao(e,t,n,o)));else{const o=v(e.handler)?e.handler.bind(n):t[e.handler];v(o)&&$n(r,o,e)}}function Fo(e){const t=e.type,{mixins:n,extends:o}=t,{mixins:r,optionsCache:s,config:{optionMergeStrategies:i}}=e.appContext,l=s.get(t);let c;return l?c=l:r.length||n||o?(c={},r.length&&r.forEach((e=>Ro(c,e,i,!0))),Ro(c,t,i)):c=t,b(t)&&s.set(t,c),c}function Ro(e,t,n,o=!1){const{mixins:r,extends:s}=t;s&&Ro(e,s,n,!0),r&&r.forEach((t=>Ro(e,t,n,!0)));for(const i in t)if(o&&"expose"===i);else{const o=Mo[i]||n&&n[i];e[i]=o?o(e[i],t[i]):t[i]}return e}const Mo={data:Vo,props:jo,emits:jo,methods:Lo,computed:Lo,beforeCreate:Bo,created:Bo,beforeMount:Bo,mounted:Bo,beforeUpdate:Bo,updated:Bo,beforeDestroy:Bo,beforeUnmount:Bo,destroyed:Bo,unmounted:Bo,activated:Bo,deactivated:Bo,errorCaptured:Bo,serverPrefetch:Bo,components:Lo,directives:Lo,watch:function(e,t){if(!e)return t;if(!t)return e;const n=a(Object.create(null),e);for(const o in t)n[o]=Bo(e[o],t[o]);return n},provide:Vo,inject:function(e,t){return Lo(Io(e),Io(t))}};function Vo(e,t){return t?e?function(){return a(v(e)?e.call(this,this):e,v(t)?t.call(this,this):t)}:t:e}function Io(e){if(d(e)){const t={};for(let n=0;n(s.has(e)||(e&&v(e.install)?(s.add(e),e.install(l,...t)):v(e)&&(s.add(e),e(l,...t))),l),mixin:e=>(r.mixins.includes(e)||r.mixins.push(e),l),component:(e,t)=>t?(r.components[e]=t,l):r.components[e],directive:(e,t)=>t?(r.directives[e]=t,l):r.directives[e],mount(s,c,a){if(!i){const u=jr(n,o);return u.appContext=r,c&&t?t(u,s):e(u,s,a),i=!0,l._container=s,s.__vue_app__=l,us(u.component)||u.component.proxy}},unmount(){i&&(e(null,l._container),delete l._container.__vue_app__)},provide:(e,t)=>(r.provides[e]=t,l),runWithContext(e){Wo=l;try{return e()}finally{Wo=null}}};return l}}let Wo=null;function zo(e,t){if(Yr){let n=Yr.provides;const o=Yr.parent&&Yr.parent.provides;o===n&&(n=Yr.provides=Object.create(o)),n[e]=t}else;}function Ko(e,t,n=!1){const o=Yr||fn;if(o||Wo){const r=o?null==o.parent?o.vnode.appContext&&o.vnode.appContext.provides:o.parent.provides:Wo._context.provides;if(r&&e in r)return r[e];if(arguments.length>1)return n&&v(t)?t.call(o&&o.proxy):t}}function Go(e,t,o,r){const[s,i]=e.propsOptions;let l,c=!1;if(t)for(let n in t){if(T(n))continue;const a=t[n];let u;s&&f(s,u=$(n))?i&&i.includes(u)?(l||(l={}))[u]=a:o[u]=a:pn(e.emitsOptions,n)||n in r&&a===r[n]||(r[n]=a,c=!0)}if(i){const t=xt(o),r=l||n;for(let n=0;n{p=!0;const[n,o]=Jo(e,t,!0);a(c,n),o&&u.push(...o)};!r&&t.mixins.length&&t.mixins.forEach(n),e.extends&&n(e.extends),e.mixins&&e.mixins.forEach(n)}if(!l&&!p)return b(e)&&s.set(e,o),o;if(d(l))for(let o=0;o-1,o[1]=n<0||t-1||f(o,"default"))&&u.push(e)}}}const h=[c,u];return b(e)&&s.set(e,h),h}function Zo(e){return"$"!==e[0]}function Yo(e){const t=e&&e.toString().match(/^\s*(function|class) (\w+)/);return t?t[2]:null===e?"null":""}function Qo(e,t){return Yo(e)===Yo(t)}function Xo(e,t){return d(t)?t.findIndex((t=>Qo(t,e))):v(t)&&Qo(t,e)?0:-1}const er=e=>"_"===e[0]||"$stable"===e,tr=e=>d(e)?e.map(Wr):[Wr(e)],nr=(e,t,n)=>{if(t._n)return t;const o=mn(((...e)=>tr(t(...e))),n);return o._c=!1,o},or=(e,t,n)=>{const o=e._ctx;for(const r in e){if(er(r))continue;const n=e[r];if(v(n))t[r]=nr(0,n,o);else if(null!=n){const e=tr(n);t[r]=()=>e}}},rr=(e,t)=>{const n=tr(t);e.slots.default=()=>n};function sr(e,t,o,r,s=!1){if(d(e))return void e.forEach(((e,n)=>sr(e,t&&(d(t)?t[n]:t),o,r,s)));if(Gn(r)&&!s)return;const i=4&r.shapeFlag?us(r.component)||r.component.proxy:r.el,l=s?null:i,{i:c,r:a}=e,p=t&&t.r,h=c.refs===n?c.refs={}:c.refs,m=c.setupState;if(null!=p&&p!==a&&(y(p)?(h[p]=null,f(m,p)&&(m[p]=null)):Nt(p)&&(p.value=null)),v(a))jt(a,c,12,[l,h]);else{const t=y(a),n=Nt(a);if(t||n){const r=()=>{if(e.f){const n=t?f(m,a)?m[a]:h[a]:a.value;s?d(n)&&u(n,i):d(n)?n.includes(i)||n.push(i):t?(h[a]=[i],f(m,a)&&(m[a]=h[a])):(a.value=[i],e.k&&(h[e.k]=a.value))}else t?(h[a]=l,f(m,a)&&(m[a]=l)):n&&(a.value=l,e.k&&(h[e.k]=l))};l?(r.id=-1,ur(r,o)):r()}}}let ir=!1;const lr=e=>/svg/.test(e.namespaceURI)&&"foreignObject"!==e.tagName,cr=e=>8===e.nodeType;function ar(e){const{mt:t,p:n,o:{patchProp:o,createText:r,nextSibling:s,parentNode:i,remove:c,insert:a,createComment:u}}=e,p=(n,o,l,c,u,v=!1)=>{const y=cr(n)&&"["===n.data,_=()=>m(n,o,l,c,u,y),{type:b,ref:S,shapeFlag:x,patchFlag:C}=o;let k=n.nodeType;o.el=n,-2===C&&(v=!1,o.dynamicChildren=null);let w=null;switch(b){case Cr:3!==k?""===o.children?(a(o.el=r(""),i(n),n),w=n):w=_():(n.data!==o.children&&(ir=!0,n.data=o.children),w=s(n));break;case kr:w=8!==k||y?_():s(n);break;case wr:if(y&&(k=(n=s(n)).nodeType),1===k||3===k){w=n;const e=!o.children.length;for(let t=0;t{i=i||!!t.dynamicChildren;const{type:a,props:u,patchFlag:p,shapeFlag:f,dirs:h}=t,m="input"===a&&h||"option"===a;if(m||-1!==p){if(h&&Mn(t,null,n,"created"),u)if(m||!i||48&p)for(const t in u)(m&&t.endsWith("value")||l(t)&&!T(t))&&o(e,t,null,u[t],!1,void 0,n);else u.onClick&&o(e,"onClick",null,u.onClick,!1,void 0,n);let a;if((a=u&&u.onVnodeBeforeMount)&&qr(a,n,t),h&&Mn(t,null,n,"beforeMount"),((a=u&&u.onVnodeMounted)||h)&&Tn((()=>{a&&qr(a,n,t),h&&Mn(t,null,n,"mounted")}),r),16&f&&(!u||!u.innerHTML&&!u.textContent)){let o=d(e.firstChild,t,e,n,r,s,i);for(;o;){ir=!0;const e=o;o=o.nextSibling,c(e)}}else 8&f&&e.textContent!==t.children&&(ir=!0,e.textContent=t.children)}return e.nextSibling},d=(e,t,o,r,s,i,l)=>{l=l||!!t.dynamicChildren;const c=t.children,a=c.length;for(let u=0;u{const{slotScopeIds:c}=t;c&&(r=r?r.concat(c):c);const p=i(e),f=d(s(e),t,p,n,o,r,l);return f&&cr(f)&&"]"===f.data?s(t.anchor=f):(ir=!0,a(t.anchor=u("]"),p,f),f)},m=(e,t,o,r,l,a)=>{if(ir=!0,t.el=null,a){const t=g(e);for(;;){const n=s(e);if(!n||n===t)break;c(n)}}const u=s(e),p=i(e);return c(e),n(null,t,p,u,o,r,lr(p),l),u},g=e=>{let t=0;for(;e;)if((e=s(e))&&cr(e)&&("["===e.data&&t++,"]"===e.data)){if(0===t)return s(e);t--}return e};return[(e,t)=>{if(!t.hasChildNodes())return n(null,e,t),on(),void(t._vnode=e);ir=!1,p(t.firstChild,e,null,null,null),on(),t._vnode=e,ir&&console.error("Hydration completed but contains mismatches.")},p]}const ur=Tn;function pr(e){return dr(e)}function fr(e){return dr(e,ar)}function dr(e,t){(j||(j="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{})).__VUE__=!0;const{insert:s,remove:i,patchProp:l,createElement:c,createText:u,createComment:p,setText:d,setElementText:h,parentNode:m,nextSibling:g,setScopeId:v=r,insertStaticContent:y}=e,_=(e,t,n,o=null,r=null,s=null,i=!1,l=null,c=!!t.dynamicChildren)=>{if(e===t)return;e&&!Mr(e,t)&&(o=Q(e),G(e,r,s,!0),e=null),-2===t.patchFlag&&(c=!1,t.dynamicChildren=null);const{type:a,ref:u,shapeFlag:p}=t;switch(a){case Cr:b(e,t,n,o);break;case kr:x(e,t,n,o);break;case wr:null==e&&C(t,n,o,i);break;case xr:R(e,t,n,o,r,s,i,l,c);break;default:1&p?k(e,t,n,o,r,s,i,l,c):6&p?M(e,t,n,o,r,s,i,l,c):(64&p||128&p)&&a.process(e,t,n,o,r,s,i,l,c,ee)}null!=u&&r&&sr(u,e&&e.ref,s,t||e,!t)},b=(e,t,n,o)=>{if(null==e)s(t.el=u(t.children),n,o);else{const n=t.el=e.el;t.children!==e.children&&d(n,t.children)}},x=(e,t,n,o)=>{null==e?s(t.el=p(t.children||""),n,o):t.el=e.el},C=(e,t,n,o)=>{[e.el,e.anchor]=y(e.children,t,n,o,e.el,e.anchor)},k=(e,t,n,o,r,s,i,l,c)=>{i=i||"svg"===t.type,null==e?w(t,n,o,r,s,i,l,c):O(e,t,r,s,i,l,c)},w=(e,t,n,o,r,i,a,u)=>{let p,f;const{type:d,props:m,shapeFlag:g,transition:v,dirs:y}=e;if(p=e.el=c(e.type,i,m&&m.is,m),8&g?h(p,e.children):16&g&&N(e.children,p,null,o,r,i&&"foreignObject"!==d,a,u),y&&Mn(e,null,o,"created"),E(p,e,e.scopeId,a,o),m){for(const t in m)"value"===t||T(t)||l(p,t,null,m[t],i,e.children,o,r,Y);"value"in m&&l(p,"value",null,m.value),(f=m.onVnodeBeforeMount)&&qr(f,o,e)}y&&Mn(e,null,o,"beforeMount");const _=(!r||r&&!r.pendingBranch)&&v&&!v.persisted;_&&v.beforeEnter(p),s(p,t,n),((f=m&&m.onVnodeMounted)||_||y)&&ur((()=>{f&&qr(f,o,e),_&&v.enter(p),y&&Mn(e,null,o,"mounted")}),r)},E=(e,t,n,o,r)=>{if(n&&v(e,n),o)for(let s=0;s{for(let a=c;a{const a=t.el=e.el;let{patchFlag:u,dynamicChildren:p,dirs:f}=t;u|=16&e.patchFlag;const d=e.props||n,m=t.props||n;let g;o&&hr(o,!1),(g=m.onVnodeBeforeUpdate)&&qr(g,o,t,e),f&&Mn(t,e,o,"beforeUpdate"),o&&hr(o,!0);const v=s&&"foreignObject"!==t.type;if(p?P(e.dynamicChildren,p,a,o,r,v,i):c||H(e,t,a,null,o,r,v,i,!1),u>0){if(16&u)F(a,t,d,m,o,r,s);else if(2&u&&d.class!==m.class&&l(a,"class",null,m.class,s),4&u&&l(a,"style",d.style,m.style,s),8&u){const n=t.dynamicProps;for(let t=0;t{g&&qr(g,o,t,e),f&&Mn(t,e,o,"updated")}),r)},P=(e,t,n,o,r,s,i)=>{for(let l=0;l{if(o!==r){if(o!==n)for(const n in o)T(n)||n in r||l(e,n,o[n],null,c,t.children,s,i,Y);for(const n in r){if(T(n))continue;const a=r[n],u=o[n];a!==u&&"value"!==n&&l(e,n,u,a,c,t.children,s,i,Y)}"value"in r&&l(e,"value",o.value,r.value)}},R=(e,t,n,o,r,i,l,c,a)=>{const p=t.el=e?e.el:u(""),f=t.anchor=e?e.anchor:u("");let{patchFlag:d,dynamicChildren:h,slotScopeIds:m}=t;m&&(c=c?c.concat(m):m),null==e?(s(p,n,o),s(f,n,o),N(t.children,n,f,r,i,l,c,a)):d>0&&64&d&&h&&e.dynamicChildren?(P(e.dynamicChildren,h,n,r,i,l,c),(null!=t.key||r&&t===r.subTree)&&mr(e,t,!0)):H(e,t,n,f,r,i,l,c,a)},M=(e,t,n,o,r,s,i,l,c)=>{t.slotScopeIds=l,null==e?512&t.shapeFlag?r.ctx.activate(t,n,o,i,c):B(t,n,o,r,s,i,c):L(e,t,c)},B=(e,t,o,r,s,i,l)=>{const c=e.component=function(e,t,o){const r=e.type,s=(t?t.appContext:e.appContext)||Jr,i={uid:Zr++,vnode:e,type:r,parent:t,appContext:s,root:null,next:null,subTree:null,effect:null,update:null,scope:new oe(!0),render:null,proxy:null,exposed:null,exposeProxy:null,withProxy:null,provides:t?t.provides:Object.create(s.provides),accessCache:null,renderCache:[],components:null,directives:null,propsOptions:Jo(r,s),emitsOptions:un(r,s),emit:null,emitted:null,propsDefaults:n,inheritAttrs:r.inheritAttrs,ctx:n,data:n,props:n,attrs:n,slots:n,refs:n,setupState:n,setupContext:null,attrsProxy:null,slotsProxy:null,suspense:o,suspenseId:o?o.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null,sp:null};i.ctx={_:i},i.root=t?t.root:i,i.emit=an.bind(null,i),e.ce&&e.ce(i);return i}(e,r,s);if(Jn(e)&&(c.ctx.renderer=ee),function(e,t=!1){ss=t;const{props:n,children:o}=e.vnode,r=ns(e);(function(e,t,n,o=!1){const r={},s={};I(s,Vr,1),e.propsDefaults=Object.create(null),Go(e,t,r,s);for(const i in e.propsOptions[0])i in r||(r[i]=void 0);e.props=n?o?r:mt(r):e.type.props?r:s,e.attrs=s})(e,n,r,t),((e,t)=>{if(32&e.vnode.shapeFlag){const n=t._;n?(e.slots=xt(t),I(t,"_",n)):or(t,e.slots={})}else e.slots={},t&&rr(e,t);I(e.slots,Vr,1)})(e,o);const s=r?function(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=Ct(new Proxy(e.ctx,wo));const{setup:o}=n;if(o){const n=e.setupContext=o.length>1?as(e):null;es(e),_e();const r=jt(o,e,0,[e.props,n]);if(be(),ts(),S(r)){if(r.then(ts,ts),t)return r.then((n=>{is(e,n,t)})).catch((t=>{Dt(t,e,0)}));e.asyncDep=r}else is(e,r,t)}else cs(e,t)}(e,t):void 0;ss=!1}(c),c.asyncDep){if(s&&s.registerDep(c,U),!e.el){const e=c.subTree=jr(kr);x(null,e,t,o)}}else U(c,e,t,o,s,i,l)},L=(e,t,n)=>{const o=t.component=e.component;if(function(e,t,n){const{props:o,children:r,component:s}=e,{props:i,children:l,patchFlag:c}=t,a=s.emitsOptions;if(t.dirs||t.transition)return!0;if(!(n&&c>=0))return!(!r&&!l||l&&l.$stable)||o!==i&&(o?!i||_n(o,i,a):!!i);if(1024&c)return!0;if(16&c)return o?_n(o,i,a):!!i;if(8&c){const e=t.dynamicProps;for(let t=0;tKt&&zt.splice(t,1)}(o.update),o.update()}else t.el=e.el,o.vnode=t},U=(e,t,n,o,r,s,i)=>{const l=e.effect=new me((()=>{if(e.isMounted){let t,{next:n,bu:o,u:l,parent:c,vnode:a}=e,u=n;hr(e,!1),n?(n.el=a.el,D(e,n,i)):n=a,o&&V(o),(t=n.props&&n.props.onVnodeBeforeUpdate)&&qr(t,c,n,a),hr(e,!0);const p=gn(e),f=e.subTree;e.subTree=p,_(f,p,m(f.el),Q(f),e,r,s),n.el=p.el,null===u&&bn(e,p.el),l&&ur(l,r),(t=n.props&&n.props.onVnodeUpdated)&&ur((()=>qr(t,c,n,a)),r)}else{let i;const{el:l,props:c}=t,{bm:a,m:u,parent:p}=e,f=Gn(t);if(hr(e,!1),a&&V(a),!f&&(i=c&&c.onVnodeBeforeMount)&&qr(i,p,t),hr(e,!0),l&&ne){const n=()=>{e.subTree=gn(e),ne(l,e.subTree,e,r,null)};f?t.type.__asyncLoader().then((()=>!e.isUnmounted&&n())):n()}else{const i=e.subTree=gn(e);_(null,i,n,o,e,r,s),t.el=i.el}if(u&&ur(u,r),!f&&(i=c&&c.onVnodeMounted)){const e=t;ur((()=>qr(i,p,e)),r)}(256&t.shapeFlag||p&&Gn(p.vnode)&&256&p.vnode.shapeFlag)&&e.a&&ur(e.a,r),e.isMounted=!0,t=n=o=null}}),(()=>Xt(c)),e.scope),c=e.update=()=>l.run();c.id=e.uid,hr(e,!0),c()},D=(e,t,o)=>{t.component=e;const r=e.vnode.props;e.vnode=t,e.next=null,function(e,t,n,o){const{props:r,attrs:s,vnode:{patchFlag:i}}=e,l=xt(r),[c]=e.propsOptions;let a=!1;if(!(o||i>0)||16&i){let o;Go(e,t,r,s)&&(a=!0);for(const s in l)t&&(f(t,s)||(o=A(s))!==s&&f(t,o))||(c?!n||void 0===n[s]&&void 0===n[o]||(r[s]=qo(c,l,s,void 0,e,!0)):delete r[s]);if(s!==l)for(const e in s)t&&f(t,e)||(delete s[e],a=!0)}else if(8&i){const n=e.vnode.dynamicProps;for(let o=0;o{const{vnode:r,slots:s}=e;let i=!0,l=n;if(32&r.shapeFlag){const e=t._;e?o&&1===e?i=!1:(a(s,t),o||1!==e||delete s._):(i=!t.$stable,or(t,s)),l=t}else t&&(rr(e,t),l={default:1});if(i)for(const n in s)er(n)||n in l||delete s[n]})(e,t.children,o),_e(),nn(),be()},H=(e,t,n,o,r,s,i,l,c=!1)=>{const a=e&&e.children,u=e?e.shapeFlag:0,p=t.children,{patchFlag:f,shapeFlag:d}=t;if(f>0){if(128&f)return void z(a,p,n,o,r,s,i,l,c);if(256&f)return void W(a,p,n,o,r,s,i,l,c)}8&d?(16&u&&Y(a,r,s),p!==a&&h(n,p)):16&u?16&d?z(a,p,n,o,r,s,i,l,c):Y(a,r,s,!0):(8&u&&h(n,""),16&d&&N(p,n,o,r,s,i,l,c))},W=(e,t,n,r,s,i,l,c,a)=>{const u=(e=e||o).length,p=(t=t||o).length,f=Math.min(u,p);let d;for(d=0;dp?Y(e,s,i,!0,!1,f):N(t,n,r,s,i,l,c,a,f)},z=(e,t,n,r,s,i,l,c,a)=>{let u=0;const p=t.length;let f=e.length-1,d=p-1;for(;u<=f&&u<=d;){const o=e[u],r=t[u]=a?zr(t[u]):Wr(t[u]);if(!Mr(o,r))break;_(o,r,n,null,s,i,l,c,a),u++}for(;u<=f&&u<=d;){const o=e[f],r=t[d]=a?zr(t[d]):Wr(t[d]);if(!Mr(o,r))break;_(o,r,n,null,s,i,l,c,a),f--,d--}if(u>f){if(u<=d){const e=d+1,o=ed)for(;u<=f;)G(e[u],s,i,!0),u++;else{const h=u,m=u,g=new Map;for(u=m;u<=d;u++){const e=t[u]=a?zr(t[u]):Wr(t[u]);null!=e.key&&g.set(e.key,u)}let v,y=0;const b=d-m+1;let S=!1,x=0;const C=new Array(b);for(u=0;u=b){G(o,s,i,!0);continue}let r;if(null!=o.key)r=g.get(o.key);else for(v=m;v<=d;v++)if(0===C[v-m]&&Mr(o,t[v])){r=v;break}void 0===r?G(o,s,i,!0):(C[r-m]=u+1,r>=x?x=r:S=!0,_(o,t[r],n,null,s,i,l,c,a),y++)}const k=S?function(e){const t=e.slice(),n=[0];let o,r,s,i,l;const c=e.length;for(o=0;o>1,e[n[l]]0&&(t[o]=n[s-1]),n[s]=o)}}s=n.length,i=n[s-1];for(;s-- >0;)n[s]=i,i=t[i];return n}(C):o;for(v=k.length-1,u=b-1;u>=0;u--){const e=m+u,o=t[e],f=e+1{const{el:i,type:l,transition:c,children:a,shapeFlag:u}=e;if(6&u)return void K(e.component.subTree,t,n,o);if(128&u)return void e.suspense.move(t,n,o);if(64&u)return void l.move(e,t,n,ee);if(l===xr){s(i,t,n);for(let e=0;e{let r;for(;e&&e!==t;)r=g(e),s(e,n,o),e=r;s(t,n,o)})(e,t,n);if(2!==o&&1&u&&c)if(0===o)c.beforeEnter(i),s(i,t,n),ur((()=>c.enter(i)),r);else{const{leave:e,delayLeave:o,afterLeave:r}=c,l=()=>s(i,t,n),a=()=>{e(i,(()=>{l(),r&&r()}))};o?o(i,l,a):a()}else s(i,t,n)},G=(e,t,n,o=!1,r=!1)=>{const{type:s,props:i,ref:l,children:c,dynamicChildren:a,shapeFlag:u,patchFlag:p,dirs:f}=e;if(null!=l&&sr(l,null,n,e,!0),256&u)return void t.ctx.deactivate(e);const d=1&u&&f,h=!Gn(e);let m;if(h&&(m=i&&i.onVnodeBeforeUnmount)&&qr(m,t,e),6&u)Z(e.component,n,o);else{if(128&u)return void e.suspense.unmount(n,o);d&&Mn(e,null,t,"beforeUnmount"),64&u?e.type.remove(e,t,n,r,ee,o):a&&(s!==xr||p>0&&64&p)?Y(a,t,n,!1,!0):(s===xr&&384&p||!r&&16&u)&&Y(c,t,n),o&&q(e)}(h&&(m=i&&i.onVnodeUnmounted)||d)&&ur((()=>{m&&qr(m,t,e),d&&Mn(e,null,t,"unmounted")}),n)},q=e=>{const{type:t,el:n,anchor:o,transition:r}=e;if(t===xr)return void J(n,o);if(t===wr)return void(({el:e,anchor:t})=>{let n;for(;e&&e!==t;)n=g(e),i(e),e=n;i(t)})(e);const s=()=>{i(n),r&&!r.persisted&&r.afterLeave&&r.afterLeave()};if(1&e.shapeFlag&&r&&!r.persisted){const{leave:t,delayLeave:o}=r,i=()=>t(n,s);o?o(e.el,s,i):i()}else s()},J=(e,t)=>{let n;for(;e!==t;)n=g(e),i(e),e=n;i(t)},Z=(e,t,n)=>{const{bum:o,scope:r,update:s,subTree:i,um:l}=e;o&&V(o),r.stop(),s&&(s.active=!1,G(i,e,t,n)),l&&ur(l,t),ur((()=>{e.isUnmounted=!0}),t),t&&t.pendingBranch&&!t.isUnmounted&&e.asyncDep&&!e.asyncResolved&&e.suspenseId===t.pendingId&&(t.deps--,0===t.deps&&t.resolve())},Y=(e,t,n,o=!1,r=!1,s=0)=>{for(let i=s;i6&e.shapeFlag?Q(e.component.subTree):128&e.shapeFlag?e.suspense.next():g(e.anchor||e.el),X=(e,t,n)=>{null==e?t._vnode&&G(t._vnode,null,null,!0):_(t._vnode||null,e,t,null,null,null,n),nn(),on(),t._vnode=e},ee={p:_,um:G,m:K,r:q,mt:B,mc:N,pc:H,pbc:P,n:Q,o:e};let te,ne;return t&&([te,ne]=t(ee)),{render:X,hydrate:te,createApp:Ho(X,te)}}function hr({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function mr(e,t,n=!1){const o=e.children,r=t.children;if(d(o)&&d(r))for(let s=0;se&&(e.disabled||""===e.disabled),vr=e=>"undefined"!=typeof SVGElement&&e instanceof SVGElement,yr=(e,t)=>{const n=e&&e.to;if(y(n)){if(t){return t(n)}return null}return n};function _r(e,t,n,{o:{insert:o},m:r},s=2){0===s&&o(e.targetAnchor,t,n);const{el:i,anchor:l,shapeFlag:c,children:a,props:u}=e,p=2===s;if(p&&o(i,t,n),(!p||gr(u))&&16&c)for(let f=0;f{16&v&&u(y,e,t,r,s,i,l,c)};g?_(n,a):p&&_(p,f)}else{t.el=e.el;const o=t.anchor=e.anchor,u=t.target=e.target,d=t.targetAnchor=e.targetAnchor,m=gr(e.props),v=m?n:u,y=m?o:d;if(i=i||vr(u),_?(f(e.dynamicChildren,_,v,r,s,i,l),mr(e,t,!0)):c||p(e,t,v,y,r,s,i,l,!1),g)m||_r(t,n,o,a,1);else if((t.props&&t.props.to)!==(e.props&&e.props.to)){const e=t.target=yr(t.props,h);e&&_r(t,e,null,a,0)}else m&&_r(t,u,d,a,1)}Sr(t)},remove(e,t,n,o,{um:r,o:{remove:s}},i){const{shapeFlag:l,children:c,anchor:a,targetAnchor:u,target:p,props:f}=e;if(p&&s(u),(i||!gr(f))&&(s(a),16&l))for(let d=0;d0?Er||o:null,Or(),$r>0&&Er&&Er.push(e),e}function Fr(e,t,n,o,r){return Ar(jr(e,t,n,o,r,!0))}function Rr(e){return!!e&&!0===e.__v_isVNode}function Mr(e,t){return e.type===t.type&&e.key===t.key}const Vr="__vInternal",Ir=({key:e})=>null!=e?e:null,Br=({ref:e,ref_key:t,ref_for:n})=>("number"==typeof e&&(e=""+e),null!=e?y(e)||Nt(e)||v(e)?{i:fn,r:e,k:t,f:!!n}:e:null);function Lr(e,t=null,n=null,o=0,r=null,s=(e===xr?0:1),i=!1,l=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Ir(t),ref:t&&Br(t),scopeId:dn,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:s,patchFlag:o,dynamicProps:r,dynamicChildren:null,appContext:null,ctx:fn};return l?(Kr(c,n),128&s&&e.normalize(c)):n&&(c.shapeFlag|=y(n)?8:16),$r>0&&!i&&Er&&(c.patchFlag>0||6&s)&&32!==c.patchFlag&&Er.push(c),c}const jr=function(e,t=null,n=null,o=0,r=null,s=!1){e&&e!==yo||(e=kr);if(Rr(e)){const o=Dr(e,t,!0);return n&&Kr(o,n),$r>0&&!s&&Er&&(6&o.shapeFlag?Er[Er.indexOf(e)]=o:Er.push(o)),o.patchFlag|=-2,o}i=e,v(i)&&"__vccOpts"in i&&(e=e.__vccOpts);var i;if(t){t=Ur(t);let{class:e,style:n}=t;e&&!y(e)&&(t.class=G(e)),b(n)&&(St(n)&&!d(n)&&(n=a({},n)),t.style=D(n))}const l=y(e)?1:Sn(e)?128:(e=>e.__isTeleport)(e)?64:b(e)?4:v(e)?2:0;return Lr(e,t,n,o,r,l,s,!0)};function Ur(e){return e?St(e)||Vr in e?a({},e):e:null}function Dr(e,t,n=!1){const{props:o,ref:r,patchFlag:s,children:i}=e,l=t?Gr(o||{},t):o;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:l,key:l&&Ir(l),ref:t&&t.ref?n&&r?d(r)?r.concat(Br(t)):[r,Br(t)]:Br(t):r,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:i,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==xr?-1===s?16:16|s:s,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Dr(e.ssContent),ssFallback:e.ssFallback&&Dr(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce}}function Hr(e=" ",t=0){return jr(Cr,null,e,t)}function Wr(e){return null==e||"boolean"==typeof e?jr(kr):d(e)?jr(xr,null,e.slice()):"object"==typeof e?zr(e):jr(Cr,null,String(e))}function zr(e){return null===e.el&&-1!==e.patchFlag||e.memo?e:Dr(e)}function Kr(e,t){let n=0;const{shapeFlag:o}=e;if(null==t)t=null;else if(d(t))n=16;else if("object"==typeof t){if(65&o){const n=t.default;return void(n&&(n._c&&(n._d=!1),Kr(e,n()),n._c&&(n._d=!0)))}{n=32;const o=t._;o||Vr in t?3===o&&fn&&(1===fn.slots._?t._=1:(t._=2,e.patchFlag|=1024)):t._ctx=fn}}else v(t)?(t={default:t,_ctx:fn},n=32):(t=String(t),64&o?(n=16,t=[Hr(t)]):n=8);e.children=t,e.shapeFlag|=n}function Gr(...e){const t={};for(let n=0;nYr||fn;let Xr;Xr=e=>{Yr=e};const es=e=>{Xr(e),e.scope.on()},ts=()=>{Yr&&Yr.scope.off(),Xr(null)};function ns(e){return 4&e.vnode.shapeFlag}let os,rs,ss=!1;function is(e,t,n){v(t)?e.render=t:b(t)&&(e.setupState=Rt(t)),cs(e,n)}function ls(e){os=e,rs=e=>{e.render._rc&&(e.withProxy=new Proxy(e.ctx,To))}}function cs(e,t,n){const o=e.type;if(!e.render){if(!t&&os&&!o.render){const t=o.template||Fo(e).template;if(t){const{isCustomElement:n,compilerOptions:r}=e.appContext.config,{delimiters:s,compilerOptions:i}=o,l=a(a({isCustomElement:n,delimiters:s},r),i);o.render=os(t,l)}}e.render=o.render||r,rs&&rs(e)}es(e),_e(),$o(e),be(),ts()}function as(e){const t=t=>{e.exposed=t||{}};return{get attrs(){return function(e){return e.attrsProxy||(e.attrsProxy=new Proxy(e.attrs,{get:(t,n)=>(Se(e,0,"$attrs"),t[n])}))}(e)},slots:e.slots,emit:e.emit,expose:t}}function us(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(Rt(Ct(e.exposed)),{get:(t,n)=>n in t?t[n]:n in Co?Co[n](e):void 0,has:(e,t)=>t in e||t in Co}))}function ps(e,t=!0){return v(e)?e.displayName||e.name:e.name||t&&e.__name}const fs=(e,t)=>function(e,t,n=!1){let o,s;const i=v(e);return i?(o=e,s=r):(o=e.get,s=e.set),new Lt(o,s,i||!s,n)}(e,0,ss);function ds(e,t,n){const o=arguments.length;return 2===o?b(t)&&!d(t)?Rr(t)?jr(e,null,[t]):jr(e,t):jr(e,null,t):(o>3?n=Array.prototype.slice.call(arguments,2):3===o&&Rr(n)&&(n=[n]),jr(e,t,n))}const hs=Symbol.for("v-scx");function ms(e,t){const n=e.memo;if(n.length!=t.length)return!1;for(let o=0;o0&&Er&&Er.push(e),!0}const gs="3.3.4",vs="undefined"!=typeof document?document:null,ys=vs&&vs.createElement("template"),_s={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,o)=>{const r=t?vs.createElementNS("http://www.w3.org/2000/svg",e):vs.createElement(e,n?{is:n}:void 0);return"select"===e&&o&&null!=o.multiple&&r.setAttribute("multiple",o.multiple),r},createText:e=>vs.createTextNode(e),createComment:e=>vs.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>vs.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,o,r,s){const i=n?n.previousSibling:t.lastChild;if(r&&(r===s||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),n),r!==s&&(r=r.nextSibling););else{ys.innerHTML=o?`${e}`:e;const r=ys.content;if(o){const e=r.firstChild;for(;e.firstChild;)r.appendChild(e.firstChild);r.removeChild(e)}t.insertBefore(r,n)}return[i?i.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}};const bs=/\s*!important$/;function Ss(e,t,n){if(d(n))n.forEach((n=>Ss(e,t,n)));else if(null==n&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const o=function(e,t){const n=Cs[t];if(n)return n;let o=$(t);if("filter"!==o&&o in e)return Cs[t]=o;o=F(o);for(let r=0;r{if(e._vts){if(e._vts<=n.attached)return}else e._vts=Date.now();Ut(function(e,t){if(d(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map((e=>t=>!t._stopped&&e&&e(t)))}return t}(e,n.value),t,5,[e])};return n.value=e,n.attached=(()=>Ns||(Os.then((()=>Ns=0)),Ns=Date.now()))(),n}(o,r);ws(e,n,i,l)}else i&&(!function(e,t,n,o){e.removeEventListener(t,n,o)}(e,n,i,l),s[t]=void 0)}}const Es=/(?:Once|Passive|Capture)$/;let Ns=0;const Os=Promise.resolve();const $s=/^on[a-z]/;function Ps(e,t){const n=Kn(e);class o extends Fs{constructor(e){super(n,e,t)}}return o.def=n,o}const As="undefined"!=typeof HTMLElement?HTMLElement:class{};class Fs extends As{constructor(e,t={},n){super(),this._def=e,this._props=t,this._instance=null,this._connected=!1,this._resolved=!1,this._numberProps=null,this.shadowRoot&&n?n(this._createVNode(),this.shadowRoot):(this.attachShadow({mode:"open"}),this._def.__asyncLoader||this._resolveProps(this._def))}connectedCallback(){this._connected=!0,this._instance||(this._resolved?this._update():this._resolveDef())}disconnectedCallback(){this._connected=!1,Qt((()=>{this._connected||($i(null,this.shadowRoot),this._instance=null)}))}_resolveDef(){this._resolved=!0;for(let n=0;n{for(const t of e)this._setAttr(t.attributeName)})).observe(this,{attributes:!0});const e=(e,t=!1)=>{const{props:n,styles:o}=e;let r;if(n&&!d(n))for(const s in n){const e=n[s];(e===Number||e&&e.type===Number)&&(s in this._props&&(this._props[s]=L(this._props[s])),(r||(r=Object.create(null)))[$(s)]=!0)}this._numberProps=r,t&&this._resolveProps(e),this._applyStyles(o),this._update()},t=this._def.__asyncLoader;t?t().then((t=>e(t,!0))):e(this._def)}_resolveProps(e){const{props:t}=e,n=d(t)?t:Object.keys(t||{});for(const o of Object.keys(this))"_"!==o[0]&&n.includes(o)&&this._setProp(o,this[o],!0,!1);for(const o of n.map($))Object.defineProperty(this,o,{get(){return this._getProp(o)},set(e){this._setProp(o,e)}})}_setAttr(e){let t=this.getAttribute(e);const n=$(e);this._numberProps&&this._numberProps[n]&&(t=L(t)),this._setProp(n,t,!1)}_getProp(e){return this._props[e]}_setProp(e,t,n=!0,o=!0){t!==this._props[e]&&(this._props[e]=t,o&&this._instance&&this._update(),n&&(!0===t?this.setAttribute(A(e),""):"string"==typeof t||"number"==typeof t?this.setAttribute(A(e),t+""):t||this.removeAttribute(A(e))))}_update(){$i(this._createVNode(),this.shadowRoot)}_createVNode(){const e=jr(this._def,a({},this._props));return this._instance||(e.ce=e=>{this._instance=e,e.isCE=!0;const t=(e,t)=>{this.dispatchEvent(new CustomEvent(e,{detail:t}))};e.emit=(e,...n)=>{t(e,n),A(e)!==e&&t(A(e),n)};let n=this;for(;n=n&&(n.parentNode||n.host);)if(n instanceof Fs){e.parent=n._instance,e.provides=n._instance.provides;break}}),e}_applyStyles(e){e&&e.forEach((e=>{const t=document.createElement("style");t.textContent=e,this.shadowRoot.appendChild(t)}))}}function Rs(e,t){if(128&e.shapeFlag){const n=e.suspense;e=n.activeBranch,n.pendingBranch&&!n.isHydrating&&n.effects.push((()=>{Rs(n.activeBranch,t)}))}for(;e.component;)e=e.component.subTree;if(1&e.shapeFlag&&e.el)Ms(e.el,t);else if(e.type===xr)e.children.forEach((e=>Rs(e,t)));else if(e.type===wr){let{el:n,anchor:o}=e;for(;n&&(Ms(n,t),n!==o);)n=n.nextSibling}}function Ms(e,t){if(1===e.nodeType){const n=e.style;for(const e in t)n.setProperty(`--${e}`,t[e])}}const Vs="transition",Is="animation",Bs=(e,{slots:t})=>ds(Ln,Hs(e),t);Bs.displayName="Transition";const Ls={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},js=Bs.props=a({},Bn,Ls),Us=(e,t=[])=>{d(e)?e.forEach((e=>e(...t))):e&&e(...t)},Ds=e=>!!e&&(d(e)?e.some((e=>e.length>1)):e.length>1);function Hs(e){const t={};for(const a in e)a in Ls||(t[a]=e[a]);if(!1===e.css)return t;const{name:n="v",type:o,duration:r,enterFromClass:s=`${n}-enter-from`,enterActiveClass:i=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:c=s,appearActiveClass:u=i,appearToClass:p=l,leaveFromClass:f=`${n}-leave-from`,leaveActiveClass:d=`${n}-leave-active`,leaveToClass:h=`${n}-leave-to`}=e,m=function(e){if(null==e)return null;if(b(e))return[Ws(e.enter),Ws(e.leave)];{const t=Ws(e);return[t,t]}}(r),g=m&&m[0],v=m&&m[1],{onBeforeEnter:y,onEnter:_,onEnterCancelled:S,onLeave:x,onLeaveCancelled:C,onBeforeAppear:k=y,onAppear:w=_,onAppearCancelled:T=S}=t,E=(e,t,n)=>{Ks(e,t?p:l),Ks(e,t?u:i),n&&n()},N=(e,t)=>{e._isLeaving=!1,Ks(e,f),Ks(e,h),Ks(e,d),t&&t()},O=e=>(t,n)=>{const r=e?w:_,i=()=>E(t,e,n);Us(r,[t,i]),Gs((()=>{Ks(t,e?c:s),zs(t,e?p:l),Ds(r)||Js(t,o,g,i)}))};return a(t,{onBeforeEnter(e){Us(y,[e]),zs(e,s),zs(e,i)},onBeforeAppear(e){Us(k,[e]),zs(e,c),zs(e,u)},onEnter:O(!1),onAppear:O(!0),onLeave(e,t){e._isLeaving=!0;const n=()=>N(e,t);zs(e,f),Xs(),zs(e,d),Gs((()=>{e._isLeaving&&(Ks(e,f),zs(e,h),Ds(x)||Js(e,o,v,n))})),Us(x,[e,n])},onEnterCancelled(e){E(e,!1),Us(S,[e])},onAppearCancelled(e){E(e,!0),Us(T,[e])},onLeaveCancelled(e){N(e),Us(C,[e])}})}function Ws(e){return L(e)}function zs(e,t){t.split(/\s+/).forEach((t=>t&&e.classList.add(t))),(e._vtc||(e._vtc=new Set)).add(t)}function Ks(e,t){t.split(/\s+/).forEach((t=>t&&e.classList.remove(t)));const{_vtc:n}=e;n&&(n.delete(t),n.size||(e._vtc=void 0))}function Gs(e){requestAnimationFrame((()=>{requestAnimationFrame(e)}))}let qs=0;function Js(e,t,n,o){const r=e._endId=++qs,s=()=>{r===e._endId&&o()};if(n)return setTimeout(s,n);const{type:i,timeout:l,propCount:c}=Zs(e,t);if(!i)return o();const a=i+"end";let u=0;const p=()=>{e.removeEventListener(a,f),s()},f=t=>{t.target===e&&++u>=c&&p()};setTimeout((()=>{u(n[e]||"").split(", "),r=o(`${Vs}Delay`),s=o(`${Vs}Duration`),i=Ys(r,s),l=o(`${Is}Delay`),c=o(`${Is}Duration`),a=Ys(l,c);let u=null,p=0,f=0;t===Vs?i>0&&(u=Vs,p=i,f=s.length):t===Is?a>0&&(u=Is,p=a,f=c.length):(p=Math.max(i,a),u=p>0?i>a?Vs:Is:null,f=u?u===Vs?s.length:c.length:0);return{type:u,timeout:p,propCount:f,hasTransform:u===Vs&&/\b(transform|all)(,|$)/.test(o(`${Vs}Property`).toString())}}function Ys(e,t){for(;e.lengthQs(t)+Qs(e[n]))))}function Qs(e){return 1e3*Number(e.slice(0,-1).replace(",","."))}function Xs(){return document.body.offsetHeight}const ei=new WeakMap,ti=new WeakMap,ni={name:"TransitionGroup",props:a({},js,{tag:String,moveClass:String}),setup(e,{slots:t}){const n=Qr(),o=Vn();let r,s;return ao((()=>{if(!r.length)return;const t=e.moveClass||`${e.name||"v"}-move`;if(!function(e,t,n){const o=e.cloneNode();e._vtc&&e._vtc.forEach((e=>{e.split(/\s+/).forEach((e=>e&&o.classList.remove(e)))}));n.split(/\s+/).forEach((e=>e&&o.classList.add(e))),o.style.display="none";const r=1===t.nodeType?t:t.parentNode;r.appendChild(o);const{hasTransform:s}=Zs(o);return r.removeChild(o),s}(r[0].el,n.vnode.el,t))return;r.forEach(ri),r.forEach(si);const o=r.filter(ii);Xs(),o.forEach((e=>{const n=e.el,o=n.style;zs(n,t),o.transform=o.webkitTransform=o.transitionDuration="";const r=n._moveCb=e=>{e&&e.target!==n||e&&!/transform$/.test(e.propertyName)||(n.removeEventListener("transitionend",r),n._moveCb=null,Ks(n,t))};n.addEventListener("transitionend",r)}))})),()=>{const i=xt(e),l=Hs(i);let c=i.tag||xr;r=s,s=t.default?zn(t.default()):[];for(let e=0;e{const t=e.props["onUpdate:modelValue"]||!1;return d(t)?e=>V(t,e):t};function ci(e){e.target.composing=!0}function ai(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const ui={created(e,{modifiers:{lazy:t,trim:n,number:o}},r){e._assign=li(r);const s=o||r.props&&"number"===r.props.type;ws(e,t?"change":"input",(t=>{if(t.target.composing)return;let o=e.value;n&&(o=o.trim()),s&&(o=B(o)),e._assign(o)})),n&&ws(e,"change",(()=>{e.value=e.value.trim()})),t||(ws(e,"compositionstart",ci),ws(e,"compositionend",ai),ws(e,"change",ai))},mounted(e,{value:t}){e.value=null==t?"":t},beforeUpdate(e,{value:t,modifiers:{lazy:n,trim:o,number:r}},s){if(e._assign=li(s),e.composing)return;if(document.activeElement===e&&"range"!==e.type){if(n)return;if(o&&e.value.trim()===t)return;if((r||"number"===e.type)&&B(e.value)===t)return}const i=null==t?"":t;e.value!==i&&(e.value=i)}},pi={deep:!0,created(e,t,n){e._assign=li(n),ws(e,"change",(()=>{const t=e._modelValue,n=gi(e),o=e.checked,r=e._assign;if(d(t)){const e=ee(t,n),s=-1!==e;if(o&&!s)r(t.concat(n));else if(!o&&s){const n=[...t];n.splice(e,1),r(n)}}else if(m(t)){const e=new Set(t);o?e.add(n):e.delete(n),r(e)}else r(vi(e,o))}))},mounted:fi,beforeUpdate(e,t,n){e._assign=li(n),fi(e,t,n)}};function fi(e,{value:t,oldValue:n},o){e._modelValue=t,d(t)?e.checked=ee(t,o.props.value)>-1:m(t)?e.checked=t.has(o.props.value):t!==n&&(e.checked=X(t,vi(e,!0)))}const di={created(e,{value:t},n){e.checked=X(t,n.props.value),e._assign=li(n),ws(e,"change",(()=>{e._assign(gi(e))}))},beforeUpdate(e,{value:t,oldValue:n},o){e._assign=li(o),t!==n&&(e.checked=X(t,o.props.value))}},hi={deep:!0,created(e,{value:t,modifiers:{number:n}},o){const r=m(t);ws(e,"change",(()=>{const t=Array.prototype.filter.call(e.options,(e=>e.selected)).map((e=>n?B(gi(e)):gi(e)));e._assign(e.multiple?r?new Set(t):t:t[0])})),e._assign=li(o)},mounted(e,{value:t}){mi(e,t)},beforeUpdate(e,t,n){e._assign=li(n)},updated(e,{value:t}){mi(e,t)}};function mi(e,t){const n=e.multiple;if(!n||d(t)||m(t)){for(let o=0,r=e.options.length;o-1:t.has(s);else if(X(gi(r),t))return void(e.selectedIndex!==o&&(e.selectedIndex=o))}n||-1===e.selectedIndex||(e.selectedIndex=-1)}}function gi(e){return"_value"in e?e._value:e.value}function vi(e,t){const n=t?"_trueValue":"_falseValue";return n in e?e[n]:t}const yi={created(e,t,n){_i(e,t,n,null,"created")},mounted(e,t,n){_i(e,t,n,null,"mounted")},beforeUpdate(e,t,n,o){_i(e,t,n,o,"beforeUpdate")},updated(e,t,n,o){_i(e,t,n,o,"updated")}};function _i(e,t,n,o,r){const s=function(e,t){switch(e){case"SELECT":return hi;case"TEXTAREA":return ui;default:switch(t){case"checkbox":return pi;case"radio":return di;default:return ui}}}(e.tagName,n.props&&n.props.type)[r];s&&s(e,t,n,o)}const bi=["ctrl","shift","alt","meta"],Si={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&0!==e.button,middle:e=>"button"in e&&1!==e.button,right:e=>"button"in e&&2!==e.button,exact:(e,t)=>bi.some((n=>e[`${n}Key`]&&!t.includes(n)))},xi={esc:"escape",space:" ",up:"arrow-up",left:"arrow-left",right:"arrow-right",down:"arrow-down",delete:"backspace"},Ci={beforeMount(e,{value:t},{transition:n}){e._vod="none"===e.style.display?"":e.style.display,n&&t?n.beforeEnter(e):ki(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:o}){!t!=!n&&(o?t?(o.beforeEnter(e),ki(e,!0),o.enter(e)):o.leave(e,(()=>{ki(e,!1)})):ki(e,t))},beforeUnmount(e,{value:t}){ki(e,t)}};function ki(e,t){e.style.display=t?e._vod:"none"}const wi=a({patchProp:(e,t,n,o,r=!1,s,i,a,u)=>{"class"===t?function(e,t,n){const o=e._vtc;o&&(t=(t?[t,...o]:[...o]).join(" ")),null==t?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}(e,o,r):"style"===t?function(e,t,n){const o=e.style,r=y(n);if(n&&!r){if(t&&!y(t))for(const e in t)null==n[e]&&Ss(o,e,"");for(const e in n)Ss(o,e,n[e])}else{const s=o.display;r?t!==n&&(o.cssText=n):t&&e.removeAttribute("style"),"_vod"in e&&(o.display=s)}}(e,n,o):l(t)?c(t)||Ts(e,t,0,o,i):("."===t[0]?(t=t.slice(1),1):"^"===t[0]?(t=t.slice(1),0):function(e,t,n,o){if(o)return"innerHTML"===t||"textContent"===t||!!(t in e&&$s.test(t)&&v(n));if("spellcheck"===t||"draggable"===t||"translate"===t)return!1;if("form"===t)return!1;if("list"===t&&"INPUT"===e.tagName)return!1;if("type"===t&&"TEXTAREA"===e.tagName)return!1;if($s.test(t)&&y(n))return!1;return t in e}(e,t,o,r))?function(e,t,n,o,r,s,i){if("innerHTML"===t||"textContent"===t)return o&&i(o,r,s),void(e[t]=null==n?"":n);const l=e.tagName;if("value"===t&&"PROGRESS"!==l&&!l.includes("-")){e._value=n;const o=null==n?"":n;return("OPTION"===l?e.getAttribute("value"):e.value)!==o&&(e.value=o),void(null==n&&e.removeAttribute(t))}let c=!1;if(""===n||null==n){const o=typeof e[t];"boolean"===o?n=Q(n):null==n&&"string"===o?(n="",c=!0):"number"===o&&(n=0,c=!0)}try{e[t]=n}catch(a){}c&&e.removeAttribute(t)}(e,t,o,s,i,a,u):("true-value"===t?e._trueValue=o:"false-value"===t&&(e._falseValue=o),function(e,t,n,o,r){if(o&&t.startsWith("xlink:"))null==n?e.removeAttributeNS(ks,t.slice(6,t.length)):e.setAttributeNS(ks,t,n);else{const o=Y(t);null==n||o&&!Q(n)?e.removeAttribute(t):e.setAttribute(t,o?"":n)}}(e,t,o,r))}},_s);let Ti,Ei=!1;function Ni(){return Ti||(Ti=pr(wi))}function Oi(){return Ti=Ei?Ti:fr(wi),Ei=!0,Ti}const $i=(...e)=>{Ni().render(...e)},Pi=(...e)=>{Oi().hydrate(...e)};function Ai(e){if(y(e)){return document.querySelector(e)}return e}const Fi=r;function Ri(e){throw e}function Mi(e){}function Vi(e,t,n,o){const r=new SyntaxError(String(e));return r.code=e,r.loc=t,r}const Ii=Symbol(""),Bi=Symbol(""),Li=Symbol(""),ji=Symbol(""),Ui=Symbol(""),Di=Symbol(""),Hi=Symbol(""),Wi=Symbol(""),zi=Symbol(""),Ki=Symbol(""),Gi=Symbol(""),qi=Symbol(""),Ji=Symbol(""),Zi=Symbol(""),Yi=Symbol(""),Qi=Symbol(""),Xi=Symbol(""),el=Symbol(""),tl=Symbol(""),nl=Symbol(""),ol=Symbol(""),rl=Symbol(""),sl=Symbol(""),il=Symbol(""),ll=Symbol(""),cl=Symbol(""),al=Symbol(""),ul=Symbol(""),pl=Symbol(""),fl=Symbol(""),dl=Symbol(""),hl=Symbol(""),ml=Symbol(""),gl=Symbol(""),vl=Symbol(""),yl=Symbol(""),_l=Symbol(""),bl=Symbol(""),Sl=Symbol(""),xl={[Ii]:"Fragment",[Bi]:"Teleport",[Li]:"Suspense",[ji]:"KeepAlive",[Ui]:"BaseTransition",[Di]:"openBlock",[Hi]:"createBlock",[Wi]:"createElementBlock",[zi]:"createVNode",[Ki]:"createElementVNode",[Gi]:"createCommentVNode",[qi]:"createTextVNode",[Ji]:"createStaticVNode",[Zi]:"resolveComponent",[Yi]:"resolveDynamicComponent",[Qi]:"resolveDirective",[Xi]:"resolveFilter",[el]:"withDirectives",[tl]:"renderList",[nl]:"renderSlot",[ol]:"createSlots",[rl]:"toDisplayString",[sl]:"mergeProps",[il]:"normalizeClass",[ll]:"normalizeStyle",[cl]:"normalizeProps",[al]:"guardReactiveProps",[ul]:"toHandlers",[pl]:"camelize",[fl]:"capitalize",[dl]:"toHandlerKey",[hl]:"setBlockTracking",[ml]:"pushScopeId",[gl]:"popScopeId",[vl]:"withCtx",[yl]:"unref",[_l]:"isRef",[bl]:"withMemo",[Sl]:"isMemoSame"};const Cl={source:"",start:{line:1,column:1,offset:0},end:{line:1,column:1,offset:0}};function kl(e,t,n,o,r,s,i,l=!1,c=!1,a=!1,u=Cl){return e&&(l?(e.helper(Di),e.helper(Rl(e.inSSR,a))):e.helper(Fl(e.inSSR,a)),i&&e.helper(el)),{type:13,tag:t,props:n,children:o,patchFlag:r,dynamicProps:s,directives:i,isBlock:l,disableTracking:c,isComponent:a,loc:u}}function wl(e,t=Cl){return{type:17,loc:t,elements:e}}function Tl(e,t=Cl){return{type:15,loc:t,properties:e}}function El(e,t){return{type:16,loc:Cl,key:y(e)?Nl(e,!0):e,value:t}}function Nl(e,t=!1,n=Cl,o=0){return{type:4,loc:n,content:e,isStatic:t,constType:t?3:o}}function Ol(e,t=Cl){return{type:8,loc:t,children:e}}function $l(e,t=[],n=Cl){return{type:14,loc:n,callee:e,arguments:t}}function Pl(e,t,n=!1,o=!1,r=Cl){return{type:18,params:e,returns:t,newline:n,isSlot:o,loc:r}}function Al(e,t,n,o=!0){return{type:19,test:e,consequent:t,alternate:n,newline:o,loc:Cl}}function Fl(e,t){return e||t?zi:Ki}function Rl(e,t){return e||t?Hi:Wi}function Ml(e,{helper:t,removeHelper:n,inSSR:o}){e.isBlock||(e.isBlock=!0,n(Fl(o,e.isComponent)),t(Di),t(Rl(o,e.isComponent)))}const Vl=e=>4===e.type&&e.isStatic,Il=(e,t)=>e===t||e===A(t);function Bl(e){return Il(e,"Teleport")?Bi:Il(e,"Suspense")?Li:Il(e,"KeepAlive")?ji:Il(e,"BaseTransition")?Ui:void 0}const Ll=/^\d|[^\$\w]/,jl=e=>!Ll.test(e),Ul=/[A-Za-z_$\xA0-\uFFFF]/,Dl=/[\.\?\w$\xA0-\uFFFF]/,Hl=/\s+[.[]\s*|\s*[.[]\s+/g,Wl=e=>{e=e.trim().replace(Hl,(e=>e.trim()));let t=0,n=[],o=0,r=0,s=null;for(let i=0;i4===e.key.type&&e.key.content===o))}return n}function sc(e,t){return`_${t}_${e.replace(/[^\w]/g,((t,n)=>"-"===t?"_":e.charCodeAt(n).toString()))}`}const ic=/&(gt|lt|amp|apos|quot);/g,lc={gt:">",lt:"<",amp:"&",apos:"'",quot:'"'},cc={delimiters:["{{","}}"],getNamespace:()=>0,getTextMode:()=>0,isVoidTag:s,isPreTag:s,isCustomElement:s,decodeEntities:e=>e.replace(ic,((e,t)=>lc[t])),onError:Ri,onWarn:Mi,comments:!1};function ac(e,t={}){const n=function(e,t){const n=a({},cc);let o;for(o in t)n[o]=void 0===t[o]?cc[o]:t[o];return{options:n,column:1,line:1,offset:0,originalSource:e,source:e,inPre:!1,inVPre:!1,onWarn:n.onWarn}}(e,t),o=kc(n);return function(e,t=Cl){return{type:0,children:e,helpers:new Set,components:[],directives:[],hoists:[],imports:[],cached:0,temps:0,codegenNode:void 0,loc:t}}(uc(n,0,[]),wc(n,o))}function uc(e,t,n){const o=Tc(n),r=o?o.ns:0,s=[];for(;!Ac(e,t,n);){const i=e.source;let l;if(0===t||1===t)if(!e.inVPre&&Ec(i,e.options.delimiters[0]))l=Sc(e,t);else if(0===t&&"<"===i[0])if(1===i.length);else if("!"===i[1])l=Ec(i,"\x3c!--")?dc(e):Ec(i,""===i[2]){Nc(e,3);continue}if(/[a-z]/i.test(i[2])){yc(e,gc.End,o);continue}Pc(e,12,2),l=hc(e)}else/[a-z]/i.test(i[1])?l=mc(e,n):"?"===i[1]&&(Pc(e,21,1),l=hc(e));if(l||(l=xc(e,t)),d(l))for(let e=0;e/.exec(e.source);if(o){n=e.source.slice(4,o.index);const t=e.source.slice(0,o.index);let r=1,s=0;for(;-1!==(s=t.indexOf("\x3c!--",r));)Nc(e,s-r+1),r=s+1;Nc(e,o.index+o[0].length-r+1)}else n=e.source.slice(4),Nc(e,e.source.length);return{type:3,content:n,loc:wc(e,t)}}function hc(e){const t=kc(e),n="?"===e.source[1]?1:2;let o;const r=e.source.indexOf(">");return-1===r?(o=e.source.slice(n),Nc(e,e.source.length)):(o=e.source.slice(n,r),Nc(e,r+1)),{type:3,content:o,loc:wc(e,t)}}function mc(e,t){const n=e.inPre,o=e.inVPre,r=Tc(t),s=yc(e,gc.Start,r),i=e.inPre&&!n,l=e.inVPre&&!o;if(s.isSelfClosing||e.options.isVoidTag(s.tag))return i&&(e.inPre=!1),l&&(e.inVPre=!1),s;t.push(s);const c=e.options.getTextMode(s,r),a=uc(e,c,t);if(t.pop(),s.children=a,Fc(e.source,s.tag))yc(e,gc.End,r);else if(0===e.source.length&&"script"===s.tag.toLowerCase()){const e=a[0];e&&Ec(e.loc.source,"\x3c!--")}return s.loc=wc(e,s.loc.start),i&&(e.inPre=!1),l&&(e.inVPre=!1),s}var gc=(e=>(e[e.Start=0]="Start",e[e.End=1]="End",e))(gc||{});const vc=t("if,else,else-if,for,slot");function yc(e,t,n){const o=kc(e),r=/^<\/?([a-z][^\t\r\n\f />]*)/i.exec(e.source),s=r[1],i=e.options.getNamespace(s,n);Nc(e,r[0].length),Oc(e);const l=kc(e),c=e.source;e.options.isPreTag(s)&&(e.inPre=!0);let u=_c(e,t);0===t&&!e.inVPre&&u.some((e=>7===e.type&&"pre"===e.name))&&(e.inVPre=!0,a(e,l),e.source=c,u=_c(e,t).filter((e=>"v-pre"!==e.name)));let p=!1;if(0===e.source.length||(p=Ec(e.source,"/>"),Nc(e,p?2:1)),1===t)return;let f=0;return e.inVPre||("slot"===s?f=2:"template"===s?u.some((e=>7===e.type&&vc(e.name)))&&(f=3):function(e,t,n){const o=n.options;if(o.isCustomElement(e))return!1;if("component"===e||/^[A-Z]/.test(e)||Bl(e)||o.isBuiltInComponent&&o.isBuiltInComponent(e)||o.isNativeTag&&!o.isNativeTag(e))return!0;for(let r=0;r0&&!Ec(e.source,">")&&!Ec(e.source,"/>");){if(Ec(e.source,"/")){Nc(e,1),Oc(e);continue}const r=bc(e,o);6===r.type&&r.value&&"class"===r.name&&(r.value.content=r.value.content.replace(/\s+/g," ").trim()),0===t&&n.push(r),/^[^\t\r\n\f />]/.test(e.source),Oc(e)}return n}function bc(e,t){var n;const o=kc(e),r=/^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(e.source)[0];t.has(r),t.add(r);{const t=/["'<]/g;let n;for(;n=t.exec(r);)Pc(e,17,n.index)}let s;Nc(e,r.length),/^[\t\r\n\f ]*=/.test(e.source)&&(Oc(e),Nc(e,1),Oc(e),s=function(e){const t=kc(e);let n;const o=e.source[0],r='"'===o||"'"===o;if(r){Nc(e,1);const t=e.source.indexOf(o);-1===t?n=Cc(e,e.source.length,4):(n=Cc(e,t,4),Nc(e,1))}else{const t=/^[^\t\r\n\f >]+/.exec(e.source);if(!t)return;const o=/["'<=`]/g;let r;for(;r=o.exec(t[0]);)Pc(e,18,r.index);n=Cc(e,t[0].length,4)}return{content:n,isQuoted:r,loc:wc(e,t)}}(e));const i=wc(e,o);if(!e.inVPre&&/^(v-[A-Za-z0-9-]|:|\.|@|#)/.test(r)){const t=/(?:^v-([a-z0-9-]+))?(?:(?::|^\.|^@|^#)(\[[^\]]+\]|[^\.]+))?(.+)?$/i.exec(r);let l,c=Ec(r,"."),a=t[1]||(c||Ec(r,":")?"bind":Ec(r,"@")?"on":"slot");if(t[2]){const s="slot"===a,i=r.lastIndexOf(t[2],r.length-((null==(n=t[3])?void 0:n.length)||0)),c=wc(e,$c(e,o,i),$c(e,o,i+t[2].length+(s&&t[3]||"").length));let u=t[2],p=!0;u.startsWith("[")?(p=!1,u.endsWith("]")?u=u.slice(1,u.length-1):(Pc(e,27),u=u.slice(1))):s&&(u+=t[3]||""),l={type:4,content:u,isStatic:p,constType:p?3:0,loc:c}}if(s&&s.isQuoted){const e=s.loc;e.start.offset++,e.start.column++,e.end=Kl(e.start,s.content),e.source=e.source.slice(1,-1)}const u=t[3]?t[3].slice(1).split("."):[];return c&&u.push("prop"),{type:7,name:a,exp:s&&{type:4,content:s.content,isStatic:!1,constType:0,loc:s.loc},arg:l,modifiers:u,loc:i}}return!e.inVPre&&Ec(r,"v-"),{type:6,name:r,value:s&&{type:2,content:s.content,loc:s.loc},loc:i}}function Sc(e,t){const[n,o]=e.options.delimiters,r=e.source.indexOf(o,n.length);if(-1===r)return;const s=kc(e);Nc(e,n.length);const i=kc(e),l=kc(e),c=r-n.length,a=e.source.slice(0,c),u=Cc(e,c,t),p=u.trim(),f=u.indexOf(p);f>0&&Gl(i,a,f);return Gl(l,a,c-(u.length-p.length-f)),Nc(e,o.length),{type:5,content:{type:4,isStatic:!1,constType:0,content:p,loc:wc(e,i,l)},loc:wc(e,s)}}function xc(e,t){const n=3===t?["]]>"]:["<",e.options.delimiters[0]];let o=e.source.length;for(let s=0;st&&(o=t)}const r=kc(e);return{type:2,content:Cc(e,o,t),loc:wc(e,r)}}function Cc(e,t,n){const o=e.source.slice(0,t);return Nc(e,t),2!==n&&3!==n&&o.includes("&")?e.options.decodeEntities(o,4===n):o}function kc(e){const{column:t,line:n,offset:o}=e;return{column:t,line:n,offset:o}}function wc(e,t,n){return{start:t,end:n=n||kc(e),source:e.originalSource.slice(t.offset,n.offset)}}function Tc(e){return e[e.length-1]}function Ec(e,t){return e.startsWith(t)}function Nc(e,t){const{source:n}=e;Gl(e,n,t),e.source=n.slice(t)}function Oc(e){const t=/^[\t\r\n\f ]+/.exec(e.source);t&&Nc(e,t[0].length)}function $c(e,t,n){return Kl(t,e.originalSource.slice(t.offset,n),n)}function Pc(e,t,n,o=kc(e)){n&&(o.offset+=n,o.column+=n),e.options.onError(Vi(t,{start:o,end:o,source:""}))}function Ac(e,t,n){const o=e.source;switch(t){case 0:if(Ec(o,"=0;--e)if(Fc(o,n[e].tag))return!0;break;case 1:case 2:{const e=Tc(n);if(e&&Fc(o,e.tag))return!0;break}case 3:if(Ec(o,"]]>"))return!0}return!o}function Fc(e,t){return Ec(e,"]/.test(e[2+t.length]||">")}function Rc(e,t){Vc(e,t,Mc(e,e.children[0]))}function Mc(e,t){const{children:n}=e;return 1===n.length&&1===t.type&&!ec(t)}function Vc(e,t,n=!1){const{children:o}=e,r=o.length;let s=0;for(let i=0;i0){if(o>=2){e.codegenNode.patchFlag="-1",e.codegenNode=t.hoist(e.codegenNode),s++;continue}}else{const n=e.codegenNode;if(13===n.type){const o=Dc(n);if((!o||512===o||1===o)&&jc(e,t)>=2){const o=Uc(e);o&&(n.props=t.hoist(o))}n.dynamicProps&&(n.dynamicProps=t.hoist(n.dynamicProps))}}}if(1===e.type){const n=1===e.tagType;n&&t.scopes.vSlot++,Vc(e,t),n&&t.scopes.vSlot--}else if(11===e.type)Vc(e,t,1===e.children.length);else if(9===e.type)for(let n=0;n1)for(let r=0;r`_${xl[T.helper(e)]}`,replaceNode(e){T.parent.children[T.childIndex]=T.currentNode=e},removeNode(e){const t=e?T.parent.children.indexOf(e):T.currentNode?T.childIndex:-1;e&&e!==T.currentNode?T.childIndex>t&&(T.childIndex--,T.onNodeRemoved()):(T.currentNode=null,T.onNodeRemoved()),T.parent.children.splice(t,1)},onNodeRemoved:()=>{},addIdentifiers(e){},removeIdentifiers(e){},hoist(e){y(e)&&(e=Nl(e)),T.hoists.push(e);const t=Nl(`_hoisted_${T.hoists.length}`,!1,e.loc,2);return t.hoisted=e,t},cache:(e,t=!1)=>function(e,t,n=!1){return{type:20,index:e,value:t,isVNode:n,loc:Cl}}(T.cached++,e,t)};return T}function Wc(e,t){const n=Hc(e,t);zc(e,n),t.hoistStatic&&Rc(e,n),t.ssr||function(e,t){const{helper:n}=t,{children:o}=e;if(1===o.length){const n=o[0];if(Mc(e,n)&&n.codegenNode){const o=n.codegenNode;13===o.type&&Ml(o,t),e.codegenNode=o}else e.codegenNode=n}else if(o.length>1){let o=64;e.codegenNode=kl(t,n(Ii),void 0,e.children,o+"",void 0,void 0,!0,void 0,!1)}}(e,n),e.helpers=new Set([...n.helpers.keys()]),e.components=[...n.components],e.directives=[...n.directives],e.imports=n.imports,e.hoists=n.hoists,e.temps=n.temps,e.cached=n.cached}function zc(e,t){t.currentNode=e;const{nodeTransforms:n}=t,o=[];for(let s=0;s{n--};for(;nt===e:t=>e.test(t);return(e,o)=>{if(1===e.type){const{props:r}=e;if(3===e.tagType&&r.some(Ql))return;const s=[];for(let i=0;i`${xl[e]}: _${xl[e]}`;function Jc(e,{mode:t="function",prefixIdentifiers:n="module"===t,sourceMap:o=!1,filename:r="template.vue.html",scopeId:s=null,optimizeImports:i=!1,runtimeGlobalName:l="Vue",runtimeModuleName:c="vue",ssrRuntimeModuleName:a="vue/server-renderer",ssr:u=!1,isTS:p=!1,inSSR:f=!1}){const d={mode:t,prefixIdentifiers:n,sourceMap:o,filename:r,scopeId:s,optimizeImports:i,runtimeGlobalName:l,runtimeModuleName:c,ssrRuntimeModuleName:a,ssr:u,isTS:p,inSSR:f,source:e.loc.source,code:"",column:1,line:1,offset:0,indentLevel:0,pure:!1,map:void 0,helper:e=>`_${xl[e]}`,push(e,t){d.code+=e},indent(){h(++d.indentLevel)},deindent(e=!1){e?--d.indentLevel:h(--d.indentLevel)},newline(){h(d.indentLevel)}};function h(e){d.push("\n"+" ".repeat(e))}return d}function Zc(e,t={}){const n=Jc(e,t);t.onContextCreated&&t.onContextCreated(n);const{mode:o,push:r,prefixIdentifiers:s,indent:i,deindent:l,newline:c,ssr:a}=n,u=Array.from(e.helpers),p=u.length>0,f=!s&&"module"!==o,d=n;!function(e,t){const{push:n,newline:o,runtimeGlobalName:r}=t,s=r,i=Array.from(e.helpers);if(i.length>0&&(n(`const _Vue = ${s}\n`),e.hoists.length)){n(`const { ${[zi,Ki,Gi,qi,Ji].filter((e=>i.includes(e))).map(qc).join(", ")} } = _Vue\n`)}(function(e,t){if(!e.length)return;t.pure=!0;const{push:n,newline:o}=t;o();for(let r=0;r0)&&c()),e.directives.length&&(Yc(e.directives,"directive",n),e.temps>0&&c()),e.temps>0){r("let ");for(let t=0;t0?", ":""}_temp${t}`)}return(e.components.length||e.directives.length||e.temps)&&(r("\n"),c()),a||r("return "),e.codegenNode?ea(e.codegenNode,n):r("null"),f&&(l(),r("}")),l(),r("}"),{ast:e,code:n.code,preamble:"",map:n.map?n.map.toJSON():void 0}}function Yc(e,t,{helper:n,push:o,newline:r,isTS:s}){const i=n("component"===t?Zi:Qi);for(let l=0;l3||!1;t.push("["),n&&t.indent(),Xc(e,t,n),n&&t.deindent(),t.push("]")}function Xc(e,t,n=!1,o=!0){const{push:r,newline:s}=t;for(let i=0;ie||"null"))}([s,i,l,c,a]),t),n(")"),p&&n(")");u&&(n(", "),ea(u,t),n(")"))}(e,t);break;case 14:!function(e,t){const{push:n,helper:o,pure:r}=t,s=y(e.callee)?e.callee:o(e.callee);r&&n(Gc);n(s+"(",e),Xc(e.arguments,t),n(")")}(e,t);break;case 15:!function(e,t){const{push:n,indent:o,deindent:r,newline:s}=t,{properties:i}=e;if(!i.length)return void n("{}",e);const l=i.length>1||!1;n(l?"{":"{ "),l&&o();for(let c=0;c "),(c||l)&&(n("{"),o());i?(c&&n("return "),d(i)?Qc(i,t):ea(i,t)):l&&ea(l,t);(c||l)&&(r(),n("}"));a&&n(")")}(e,t);break;case 19:!function(e,t){const{test:n,consequent:o,alternate:r,newline:s}=e,{push:i,indent:l,deindent:c,newline:a}=t;if(4===n.type){const e=!jl(n.content);e&&i("("),ta(n,t),e&&i(")")}else i("("),ea(n,t),i(")");s&&l(),t.indentLevel++,s||i(" "),i("? "),ea(o,t),t.indentLevel--,s&&a(),s||i(" "),i(": ");const u=19===r.type;u||t.indentLevel++;ea(r,t),u||t.indentLevel--;s&&c(!0)}(e,t);break;case 20:!function(e,t){const{push:n,helper:o,indent:r,deindent:s,newline:i}=t;n(`_cache[${e.index}] || (`),e.isVNode&&(r(),n(`${o(hl)}(-1),`),i());n(`_cache[${e.index}] = `),ea(e.value,t),e.isVNode&&(n(","),i(),n(`${o(hl)}(1),`),i(),n(`_cache[${e.index}]`),s());n(")")}(e,t);break;case 21:Xc(e.body,t,!0,!1)}}function ta(e,t){const{content:n,isStatic:o}=e;t.push(o?JSON.stringify(n):n,e)}function na(e,t){for(let n=0;nfunction(e,t,n,o){if(!("else"===t.name||t.exp&&t.exp.content.trim())){const o=t.exp?t.exp.loc:e.loc;n.onError(Vi(28,t.loc)),t.exp=Nl("true",!1,o)}if("if"===t.name){const r=sa(e,t),s={type:9,loc:e.loc,branches:[r]};if(n.replaceNode(s),o)return o(s,r,!0)}else{const r=n.parent.children;let s=r.indexOf(e);for(;s-- >=-1;){const i=r[s];if(i&&3===i.type)n.removeNode(i);else{if(!i||2!==i.type||i.content.trim().length){if(i&&9===i.type){"else-if"===t.name&&void 0===i.branches[i.branches.length-1].condition&&n.onError(Vi(30,e.loc)),n.removeNode();const r=sa(e,t);i.branches.push(r);const s=o&&o(i,r,!1);zc(r,n),s&&s(),n.currentNode=null}else n.onError(Vi(30,e.loc));break}n.removeNode(i)}}}}(e,t,n,((e,t,o)=>{const r=n.parent.children;let s=r.indexOf(e),i=0;for(;s-- >=0;){const e=r[s];e&&9===e.type&&(i+=e.branches.length)}return()=>{if(o)e.codegenNode=ia(t,i,n);else{const o=function(e){for(;;)if(19===e.type){if(19!==e.alternate.type)return e;e=e.alternate}else 20===e.type&&(e=e.value)}(e.codegenNode);o.alternate=ia(t,i+e.branches.length-1,n)}}}))));function sa(e,t){const n=3===e.tagType;return{type:10,loc:e.loc,condition:"else"===t.name?void 0:t.exp,children:n&&!ql(e,"for")?e.children:[e],userKey:Jl(e,"key"),isTemplateIf:n}}function ia(e,t,n){return e.condition?Al(e.condition,la(e,t,n),$l(n.helper(Gi),['""',"true"])):la(e,t,n)}function la(e,t,n){const{helper:o}=n,r=El("key",Nl(`${t}`,!1,Cl,2)),{children:s}=e,i=s[0];if(1!==s.length||1!==i.type){if(1===s.length&&11===i.type){const e=i.codegenNode;return oc(e,r,n),e}{let t=64;return kl(n,o(Ii),Tl([r]),s,t+"",void 0,void 0,!0,!1,!1,e.loc)}}{const e=i.codegenNode,t=14===(l=e).type&&l.callee===bl?l.arguments[1].returns:l;return 13===t.type&&Ml(t,n),oc(t,r,n),e}var l}const ca=Kc("for",((e,t,n)=>{const{helper:o,removeHelper:r}=n;return function(e,t,n,o){if(!t.exp)return void n.onError(Vi(31,t.loc));const r=fa(t.exp);if(!r)return void n.onError(Vi(32,t.loc));const{scopes:s}=n,{source:i,value:l,key:c,index:a}=r,u={type:11,loc:t.loc,source:i,valueAlias:l,keyAlias:c,objectIndexAlias:a,parseResult:r,children:Xl(e)?e.children:[e]};n.replaceNode(u),s.vFor++;const p=o&&o(u);return()=>{s.vFor--,p&&p()}}(e,t,n,(t=>{const s=$l(o(tl),[t.source]),i=Xl(e),l=ql(e,"memo"),c=Jl(e,"key"),a=c&&(6===c.type?Nl(c.value.content,!0):c.exp),u=c?El("key",a):null,p=4===t.source.type&&t.source.constType>0,f=p?64:c?128:256;return t.codegenNode=kl(n,o(Ii),void 0,s,f+"",void 0,void 0,!0,!p,!1,e.loc),()=>{let c;const{children:f}=t,d=1!==f.length||1!==f[0].type,h=ec(e)?e:i&&1===e.children.length&&ec(e.children[0])?e.children[0]:null;if(h?(c=h.codegenNode,i&&u&&oc(c,u,n)):d?c=kl(n,o(Ii),u?Tl([u]):void 0,e.children,"64",void 0,void 0,!0,void 0,!1):(c=f[0].codegenNode,i&&u&&oc(c,u,n),c.isBlock!==!p&&(c.isBlock?(r(Di),r(Rl(n.inSSR,c.isComponent))):r(Fl(n.inSSR,c.isComponent))),c.isBlock=!p,c.isBlock?(o(Di),o(Rl(n.inSSR,c.isComponent))):o(Fl(n.inSSR,c.isComponent))),l){const e=Pl(ha(t.parseResult,[Nl("_cached")]));e.body={type:21,body:[Ol(["const _memo = (",l.exp,")"]),Ol(["if (_cached",...a?[" && _cached.key === ",a]:[],` && ${n.helperString(Sl)}(_cached, _memo)) return _cached`]),Ol(["const _item = ",c]),Nl("_item.memo = _memo"),Nl("return _item")],loc:Cl},s.arguments.push(e,Nl("_cache"),Nl(String(n.cached++)))}else s.arguments.push(Pl(ha(t.parseResult),c,!0))}}))}));const aa=/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/,ua=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,pa=/^\(|\)$/g;function fa(e,t){const n=e.loc,o=e.content,r=o.match(aa);if(!r)return;const[,s,i]=r,l={source:da(n,i.trim(),o.indexOf(i,s.length)),value:void 0,key:void 0,index:void 0};let c=s.trim().replace(pa,"").trim();const a=s.indexOf(c),u=c.match(ua);if(u){c=c.replace(ua,"").trim();const e=u[1].trim();let t;if(e&&(t=o.indexOf(e,a+c.length),l.key=da(n,e,t)),u[2]){const r=u[2].trim();r&&(l.index=da(n,r,o.indexOf(r,l.key?t+e.length:a+c.length)))}}return c&&(l.value=da(n,c,a)),l}function da(e,t,n){return Nl(t,!1,zl(e,n,t.length))}function ha({value:e,key:t,index:n},o=[]){return function(e){let t=e.length;for(;t--&&!e[t];);return e.slice(0,t+1).map(((e,t)=>e||Nl("_".repeat(t+1),!1)))}([e,t,n,...o])}const ma=Nl("undefined",!1),ga=(e,t)=>{if(1===e.type&&(1===e.tagType||3===e.tagType)){const n=ql(e,"slot");if(n)return t.scopes.vSlot++,()=>{t.scopes.vSlot--}}},va=(e,t,n)=>Pl(e,t,!1,!0,t.length?t[0].loc:n);function ya(e,t,n=va){t.helper(vl);const{children:o,loc:r}=e,s=[],i=[];let l=t.scopes.vSlot>0||t.scopes.vFor>0;const c=ql(e,"slot",!0);if(c){const{arg:e,exp:t}=c;e&&!Vl(e)&&(l=!0),s.push(El(e||Nl("default",!0),n(t,o,r)))}let a=!1,u=!1;const p=[],f=new Set;let d=0;for(let g=0;gEl("default",n(e,t,r));a?p.length&&p.some((e=>Sa(e)))&&(u?t.onError(Vi(39,p[0].loc)):s.push(e(void 0,p))):s.push(e(void 0,o))}const h=l?2:ba(e.children)?3:1;let m=Tl(s.concat(El("_",Nl(h+"",!1))),r);return i.length&&(m=$l(t.helper(ol),[m,wl(i)])),{slots:m,hasDynamicSlots:l}}function _a(e,t,n){const o=[El("name",e),El("fn",t)];return null!=n&&o.push(El("key",Nl(String(n),!0))),Tl(o)}function ba(e){for(let t=0;tfunction(){if(1!==(e=t.currentNode).type||0!==e.tagType&&1!==e.tagType)return;const{tag:n,props:o}=e,r=1===e.tagType;let s=r?function(e,t,n=!1){let{tag:o}=e;const r=Ea(o),s=Jl(e,"is");if(s)if(r){const e=6===s.type?s.value&&Nl(s.value.content,!0):s.exp;if(e)return $l(t.helper(Yi),[e])}else 6===s.type&&s.value.content.startsWith("vue:")&&(o=s.value.content.slice(4));const i=!r&&ql(e,"is");if(i&&i.exp)return $l(t.helper(Yi),[i.exp]);const l=Bl(o)||t.isBuiltInComponent(o);if(l)return n||t.helper(l),l;return t.helper(Zi),t.components.add(o),sc(o,"component")}(e,t):`"${n}"`;const i=b(s)&&s.callee===Yi;let l,c,a,u,p,f,d=0,h=i||s===Bi||s===Li||!r&&("svg"===n||"foreignObject"===n);if(o.length>0){const n=ka(e,t,void 0,r,i);l=n.props,d=n.patchFlag,p=n.dynamicPropNames;const o=n.directives;f=o&&o.length?wl(o.map((e=>function(e,t){const n=[],o=xa.get(e);o?n.push(t.helperString(o)):(t.helper(Qi),t.directives.add(e.name),n.push(sc(e.name,"directive")));const{loc:r}=e;e.exp&&n.push(e.exp);e.arg&&(e.exp||n.push("void 0"),n.push(e.arg));if(Object.keys(e.modifiers).length){e.arg||(e.exp||n.push("void 0"),n.push("void 0"));const t=Nl("true",!1,r);n.push(Tl(e.modifiers.map((e=>El(e,t))),r))}return wl(n,e.loc)}(e,t)))):void 0,n.shouldUseBlock&&(h=!0)}if(e.children.length>0){s===ji&&(h=!0,d|=1024);if(r&&s!==Bi&&s!==ji){const{slots:n,hasDynamicSlots:o}=ya(e,t);c=n,o&&(d|=1024)}else if(1===e.children.length&&s!==Bi){const n=e.children[0],o=n.type,r=5===o||8===o;r&&0===Ic(n,t)&&(d|=1),c=r||2===o?n:e.children}else c=e.children}0!==d&&(a=String(d),p&&p.length&&(u=function(e){let t="[";for(let n=0,o=e.length;n0;let h=!1,m=0,g=!1,v=!1,y=!1,b=!1,S=!1,x=!1;const C=[],k=e=>{u.length&&(p.push(Tl(wa(u),c)),u=[]),e&&p.push(e)},w=({key:e,value:n})=>{if(Vl(e)){const s=e.content,i=l(s);if(!i||o&&!r||"onclick"===s.toLowerCase()||"onUpdate:modelValue"===s||T(s)||(b=!0),i&&T(s)&&(x=!0),20===n.type||(4===n.type||8===n.type)&&Ic(n,t)>0)return;"ref"===s?g=!0:"class"===s?v=!0:"style"===s?y=!0:"key"===s||C.includes(s)||C.push(s),!o||"class"!==s&&"style"!==s||C.includes(s)||C.push(s)}else S=!0};for(let l=0;l0&&u.push(El(Nl("ref_for",!0),Nl("true")))),"is"===n&&(Ea(i)||o&&o.content.startsWith("vue:")))continue;u.push(El(Nl(n,!0,zl(e,0,n.length)),Nl(o?o.content:"",s,o?o.loc:e)))}else{const{name:n,arg:l,exp:a,loc:m}=r,g="bind"===n,v="on"===n;if("slot"===n){o||t.onError(Vi(40,m));continue}if("once"===n||"memo"===n)continue;if("is"===n||g&&Zl(l,"is")&&Ea(i))continue;if(v&&s)continue;if((g&&Zl(l,"key")||v&&d&&Zl(l,"vue:before-update"))&&(h=!0),g&&Zl(l,"ref")&&t.scopes.vFor>0&&u.push(El(Nl("ref_for",!0),Nl("true"))),!l&&(g||v)){S=!0,a?g?(k(),p.push(a)):k({type:14,loc:m,callee:t.helper(ul),arguments:o?[a]:[a,"true"]}):t.onError(Vi(g?34:35,m));continue}const y=t.directiveTransforms[n];if(y){const{props:n,needRuntime:o}=y(r,e,t);!s&&n.forEach(w),v&&l&&!Vl(l)?k(Tl(n,c)):u.push(...n),o&&(f.push(r),_(o)&&xa.set(r,o))}else E(n)||(f.push(r),d&&(h=!0))}}let N;if(p.length?(k(),N=p.length>1?$l(t.helper(sl),p,c):p[0]):u.length&&(N=Tl(wa(u),c)),S?m|=16:(v&&!o&&(m|=2),y&&!o&&(m|=4),C.length&&(m|=8),b&&(m|=32)),h||0!==m&&32!==m||!(g||x||f.length>0)||(m|=512),!t.inSSR&&N)switch(N.type){case 15:let e=-1,n=-1,o=!1;for(let t=0;t{if(ec(e)){const{children:n,loc:o}=e,{slotName:r,slotProps:s}=function(e,t){let n,o='"default"';const r=[];for(let s=0;s0){const{props:o,directives:s}=ka(e,t,r,!1,!1);n=o,s.length&&t.onError(Vi(36,s[0].loc))}return{slotName:o,slotProps:n}}(e,t),i=[t.prefixIdentifiers?"_ctx.$slots":"$slots",r,"{}","undefined","true"];let l=2;s&&(i[2]=s,l=3),n.length&&(i[3]=Pl([],n,!1,!1,o),l=4),t.scopeId&&!t.slotted&&(l=5),i.splice(l),e.codegenNode=$l(t.helper(nl),i,o)}};const Oa=/^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/,$a=(e,t,n,o)=>{const{loc:r,modifiers:s,arg:i}=e;let l;if(4===i.type)if(i.isStatic){let e=i.content;e.startsWith("vue:")&&(e=`vnode-${e.slice(4)}`);l=Nl(0!==t.tagType||e.startsWith("vnode")||!/[A-Z]/.test(e)?R($(e)):`on:${e}`,!0,i.loc)}else l=Ol([`${n.helperString(dl)}(`,i,")"]);else l=i,l.children.unshift(`${n.helperString(dl)}(`),l.children.push(")");let c=e.exp;c&&!c.content.trim()&&(c=void 0);let a=n.cacheHandlers&&!c&&!n.inVOnce;if(c){const e=Wl(c.content),t=!(e||Oa.test(c.content)),n=c.content.includes(";");(t||a&&e)&&(c=Ol([`${t?"$event":"(...args)"} => ${n?"{":"("}`,c,n?"}":")"]))}let u={props:[El(l,c||Nl("() => {}",!1,r))]};return o&&(u=o(u)),a&&(u.props[0].value=n.cache(u.props[0].value)),u.props.forEach((e=>e.key.isHandlerKey=!0)),u},Pa=(e,t,n)=>{const{exp:o,modifiers:r,loc:s}=e,i=e.arg;return 4!==i.type?(i.children.unshift("("),i.children.push(') || ""')):i.isStatic||(i.content=`${i.content} || ""`),r.includes("camel")&&(4===i.type?i.content=i.isStatic?$(i.content):`${n.helperString(pl)}(${i.content})`:(i.children.unshift(`${n.helperString(pl)}(`),i.children.push(")"))),n.inSSR||(r.includes("prop")&&Aa(i,"."),r.includes("attr")&&Aa(i,"^")),!o||4===o.type&&!o.content.trim()?{props:[El(i,Nl("",!0,s))]}:{props:[El(i,o)]}},Aa=(e,t)=>{4===e.type?e.content=e.isStatic?t+e.content:`\`${t}\${${e.content}}\``:(e.children.unshift(`'${t}' + (`),e.children.push(")"))},Fa=(e,t)=>{if(0===e.type||1===e.type||11===e.type||10===e.type)return()=>{const n=e.children;let o,r=!1;for(let e=0;e7===e.type&&!t.directiveTransforms[e.name])))))for(let e=0;e{if(1===e.type&&ql(e,"once",!0)){if(Ra.has(e)||t.inVOnce||t.inSSR)return;return Ra.add(e),t.inVOnce=!0,t.helper(hl),()=>{t.inVOnce=!1;const e=t.currentNode;e.codegenNode&&(e.codegenNode=t.cache(e.codegenNode,!0))}}},Va=(e,t,n)=>{const{exp:o,arg:r}=e;if(!o)return n.onError(Vi(41,e.loc)),Ia();const s=o.loc.source,i=4===o.type?o.content:s,l=n.bindingMetadata[s];if("props"===l||"props-aliased"===l)return Ia();if(!i.trim()||!Wl(i))return n.onError(Vi(42,o.loc)),Ia();const c=r||Nl("modelValue",!0),a=r?Vl(r)?`onUpdate:${$(r.content)}`:Ol(['"onUpdate:" + ',r]):"onUpdate:modelValue";let u;u=Ol([`${n.isTS?"($event: any)":"$event"} => ((`,o,") = $event)"]);const p=[El(c,e.exp),El(a,u)];if(e.modifiers.length&&1===t.tagType){const t=e.modifiers.map((e=>(jl(e)?e:JSON.stringify(e))+": true")).join(", "),n=r?Vl(r)?`${r.content}Modifiers`:Ol([r,' + "Modifiers"']):"modelModifiers";p.push(El(n,Nl(`{ ${t} }`,!1,e.loc,2)))}return Ia(p)};function Ia(e=[]){return{props:e}}const Ba=new WeakSet,La=(e,t)=>{if(1===e.type){const n=ql(e,"memo");if(!n||Ba.has(e))return;return Ba.add(e),()=>{const o=e.codegenNode||t.currentNode.codegenNode;o&&13===o.type&&(1!==e.tagType&&Ml(o,t),e.codegenNode=$l(t.helper(bl),[n.exp,Pl(void 0,o),"_cache",String(t.cached++)]))}}};function ja(e,t={}){const n=t.onError||Ri,o="module"===t.mode;!0===t.prefixIdentifiers?n(Vi(47)):o&&n(Vi(48));t.cacheHandlers&&n(Vi(49)),t.scopeId&&!o&&n(Vi(50));const r=y(e)?ac(e,t):e,[s,i]=[[Ma,ra,La,ca,Na,Ca,ga,Fa],{on:$a,bind:Pa,model:Va}];return Wc(r,a({},t,{prefixIdentifiers:false,nodeTransforms:[...s,...t.nodeTransforms||[]],directiveTransforms:a({},i,t.directiveTransforms||{})})),Zc(r,a({},t,{prefixIdentifiers:false}))}const Ua=Symbol(""),Da=Symbol(""),Ha=Symbol(""),Wa=Symbol(""),za=Symbol(""),Ka=Symbol(""),Ga=Symbol(""),qa=Symbol(""),Ja=Symbol(""),Za=Symbol("");var Ya;let Qa;Ya={[Ua]:"vModelRadio",[Da]:"vModelCheckbox",[Ha]:"vModelText",[Wa]:"vModelSelect",[za]:"vModelDynamic",[Ka]:"withModifiers",[Ga]:"withKeys",[qa]:"vShow",[Ja]:"Transition",[Za]:"TransitionGroup"},Object.getOwnPropertySymbols(Ya).forEach((e=>{xl[e]=Ya[e]}));const Xa=t("style,iframe,script,noscript",!0),eu={isVoidTag:Z,isNativeTag:e=>q(e)||J(e),isPreTag:e=>"pre"===e,decodeEntities:function(e,t=!1){return Qa||(Qa=document.createElement("div")),t?(Qa.innerHTML=`
`,Qa.children[0].getAttribute("foo")):(Qa.innerHTML=e,Qa.textContent)},isBuiltInComponent:e=>Il(e,"Transition")?Ja:Il(e,"TransitionGroup")?Za:void 0,getNamespace(e,t){let n=t?t.ns:0;if(t&&2===n)if("annotation-xml"===t.tag){if("svg"===e)return 1;t.props.some((e=>6===e.type&&"encoding"===e.name&&null!=e.value&&("text/html"===e.value.content||"application/xhtml+xml"===e.value.content)))&&(n=0)}else/^m(?:[ions]|text)$/.test(t.tag)&&"mglyph"!==e&&"malignmark"!==e&&(n=0);else t&&1===n&&("foreignObject"!==t.tag&&"desc"!==t.tag&&"title"!==t.tag||(n=0));if(0===n){if("svg"===e)return 1;if("math"===e)return 2}return n},getTextMode({tag:e,ns:t}){if(0===t){if("textarea"===e||"title"===e)return 1;if(Xa(e))return 2}return 0}},tu=(e,t)=>{const n=K(e);return Nl(JSON.stringify(n),!1,t,3)};function nu(e,t){return Vi(e,t)}const ou=t("passive,once,capture"),ru=t("stop,prevent,self,ctrl,shift,alt,meta,exact,middle"),su=t("left,right"),iu=t("onkeyup,onkeydown,onkeypress",!0),lu=(e,t)=>Vl(e)&&"onclick"===e.content.toLowerCase()?Nl(t,!0):4!==e.type?Ol(["(",e,`) === "onClick" ? "${t}" : (`,e,")"]):e,cu=(e,t)=>{1!==e.type||0!==e.tagType||"script"!==e.tag&&"style"!==e.tag||t.removeNode()},au=[e=>{1===e.type&&e.props.forEach(((t,n)=>{6===t.type&&"style"===t.name&&t.value&&(e.props[n]={type:7,name:"bind",arg:Nl("style",!0,t.loc),exp:tu(t.value.content,t.loc),modifiers:[],loc:t.loc})}))}],uu={cloak:()=>({props:[]}),html:(e,t,n)=>{const{exp:o,loc:r}=e;return o||n.onError(nu(53,r)),t.children.length&&(n.onError(nu(54,r)),t.children.length=0),{props:[El(Nl("innerHTML",!0,r),o||Nl("",!0))]}},text:(e,t,n)=>{const{exp:o,loc:r}=e;return o||n.onError(nu(55,r)),t.children.length&&(n.onError(nu(56,r)),t.children.length=0),{props:[El(Nl("textContent",!0),o?Ic(o,n)>0?o:$l(n.helperString(rl),[o],r):Nl("",!0))]}},model:(e,t,n)=>{const o=Va(e,t,n);if(!o.props.length||1===t.tagType)return o;e.arg&&n.onError(nu(58,e.arg.loc));const{tag:r}=t,s=n.isCustomElement(r);if("input"===r||"textarea"===r||"select"===r||s){let i=Ha,l=!1;if("input"===r||s){const o=Jl(t,"type");if(o){if(7===o.type)i=za;else if(o.value)switch(o.value.content){case"radio":i=Ua;break;case"checkbox":i=Da;break;case"file":l=!0,n.onError(nu(59,e.loc))}}else(function(e){return e.props.some((e=>!(7!==e.type||"bind"!==e.name||e.arg&&4===e.arg.type&&e.arg.isStatic)))})(t)&&(i=za)}else"select"===r&&(i=Wa);l||(o.needRuntime=n.helper(i))}else n.onError(nu(57,e.loc));return o.props=o.props.filter((e=>!(4===e.key.type&&"modelValue"===e.key.content))),o},on:(e,t,n)=>$a(e,t,n,(t=>{const{modifiers:o}=e;if(!o.length)return t;let{key:r,value:s}=t.props[0];const{keyModifiers:i,nonKeyModifiers:l,eventOptionModifiers:c}=((e,t,n,o)=>{const r=[],s=[],i=[];for(let l=0;l{const{exp:o,loc:r}=e;return o||n.onError(nu(61,r)),{props:[],needRuntime:n.helper(qa)}}};const pu=Object.create(null);function fu(e,t){if(!y(e)){if(!e.nodeType)return r;e=e.innerHTML}const n=e,o=pu[n];if(o)return o;if("#"===e[0]){const t=document.querySelector(e);e=t?t.innerHTML:""}const s=a({hoistStatic:!0,onError:void 0,onWarn:r},t);s.isCustomElement||"undefined"==typeof customElements||(s.isCustomElement=e=>!!customElements.get(e));const{code:i}=function(e,t={}){return ja(e,a({},eu,t,{nodeTransforms:[cu,...au,...t.nodeTransforms||[]],directiveTransforms:a({},uu,t.directiveTransforms||{}),transformHoist:null}))}(e,s),l=new Function(i)();return l._rc=!0,pu[n]=l}return ls(fu),e.BaseTransition=Ln,e.BaseTransitionPropsValidators=Bn,e.Comment=kr,e.EffectScope=oe,e.Fragment=xr,e.KeepAlive=Zn,e.ReactiveEffect=me,e.Static=wr,e.Suspense=xn,e.Teleport=br,e.Text=Cr,e.Transition=Bs,e.TransitionGroup=oi,e.VueElement=Fs,e.assertNumber=function(e,t){},e.callWithAsyncErrorHandling=Ut,e.callWithErrorHandling=jt,e.camelize=$,e.capitalize=F,e.cloneVNode=Dr,e.compatUtils=null,e.compile=fu,e.computed=fs,e.createApp=(...e)=>{const t=Ni().createApp(...e),{mount:n}=t;return t.mount=e=>{const o=Ai(e);if(!o)return;const r=t._component;v(r)||r.render||r.template||(r.template=o.innerHTML),o.innerHTML="";const s=n(o,!1,o instanceof SVGElement);return o instanceof Element&&(o.removeAttribute("v-cloak"),o.setAttribute("data-v-app","")),s},t},e.createBlock=Fr,e.createCommentVNode=function(e="",t=!1){return t?(Nr(),Fr(kr,null,e)):jr(kr,null,e)},e.createElementBlock=function(e,t,n,o,r,s){return Ar(Lr(e,t,n,o,r,s,!0))},e.createElementVNode=Lr,e.createHydrationRenderer=fr,e.createPropsRestProxy=function(e,t){const n={};for(const o in e)t.includes(o)||Object.defineProperty(n,o,{enumerable:!0,get:()=>e[o]});return n},e.createRenderer=pr,e.createSSRApp=(...e)=>{const t=Oi().createApp(...e),{mount:n}=t;return t.mount=e=>{const t=Ai(e);if(t)return n(t,!0,t instanceof SVGElement)},t},e.createSlots=function(e,t){for(let n=0;n{const t=o.fn(...e);return t&&(t.key=o.key),t}:o.fn)}return e},e.createStaticVNode=function(e,t){const n=jr(wr,null,e);return n.staticCount=t,n},e.createTextVNode=Hr,e.createVNode=jr,e.customRef=function(e){return new Mt(e)},e.defineAsyncComponent=function(e){v(e)&&(e={loader:e});const{loader:t,loadingComponent:n,errorComponent:o,delay:r=200,timeout:s,suspensible:i=!0,onError:l}=e;let c,a=null,u=0;const p=()=>{let e;return a||(e=a=t().catch((e=>{if(e=e instanceof Error?e:new Error(String(e)),l)return new Promise(((t,n)=>{l(e,(()=>t((u++,a=null,p()))),(()=>n(e)),u+1)}));throw e})).then((t=>e!==a&&a?a:(t&&(t.__esModule||"Module"===t[Symbol.toStringTag])&&(t=t.default),c=t,t))))};return Kn({name:"AsyncComponentWrapper",__asyncLoader:p,get __asyncResolved(){return c},setup(){const e=Yr;if(c)return()=>qn(c,e);const t=t=>{a=null,Dt(t,e,13,!o)};if(i&&e.suspense)return p().then((t=>()=>qn(t,e))).catch((e=>(t(e),()=>o?jr(o,{error:e}):null)));const l=Ot(!1),u=Ot(),f=Ot(!!r);return r&&setTimeout((()=>{f.value=!1}),r),null!=s&&setTimeout((()=>{if(!l.value&&!u.value){const e=new Error(`Async component timed out after ${s}ms.`);t(e),u.value=e}}),s),p().then((()=>{l.value=!0,e.parent&&Jn(e.parent.vnode)&&Xt(e.parent.update)})).catch((e=>{t(e),u.value=e})),()=>l.value&&c?qn(c,e):u.value&&o?jr(o,{error:u.value}):n&&!f.value?jr(n):void 0}})},e.defineComponent=Kn,e.defineCustomElement=Ps,e.defineEmits=function(){return null},e.defineExpose=function(e){},e.defineModel=function(){},e.defineOptions=function(e){},e.defineProps=function(){return null},e.defineSSRCustomElement=e=>Ps(e,Pi),e.defineSlots=function(){return null},e.effect=function(e,t){e.effect&&(e=e.effect.fn);const n=new me(e);t&&(a(n,t),t.scope&&re(n,t.scope)),t&&t.lazy||n.run();const o=n.run.bind(n);return o.effect=n,o},e.effectScope=function(e){return new oe(e)},e.getCurrentInstance=Qr,e.getCurrentScope=se,e.getTransitionRawChildren=zn,e.guardReactiveProps=Ur,e.h=ds,e.handleError=Dt,e.hasInjectionContext=function(){return!!(Yr||fn||Wo)},e.hydrate=Pi,e.initCustomFormatter=function(){},e.initDirectivesForSSR=Fi,e.inject=Ko,e.isMemoSame=ms,e.isProxy=St,e.isReactive=yt,e.isReadonly=_t,e.isRef=Nt,e.isRuntimeOnly=()=>!os,e.isShallow=bt,e.isVNode=Rr,e.markRaw=Ct,e.mergeDefaults=function(e,t){const n=No(e);for(const o in t){if(o.startsWith("__skip"))continue;let e=n[o];e?d(e)||v(e)?e=n[o]={type:e,default:t[o]}:e.default=t[o]:null===e&&(e=n[o]={default:t[o]}),e&&t[`__skip_${o}`]&&(e.skipFactory=!0)}return n},e.mergeModels=function(e,t){return e&&t?d(e)&&d(t)?e.concat(t):a({},No(e),No(t)):e||t},e.mergeProps=Gr,e.nextTick=Qt,e.normalizeClass=G,e.normalizeProps=function(e){if(!e)return null;let{class:t,style:n}=e;return t&&!y(t)&&(e.class=G(t)),n&&(e.style=D(n)),e},e.normalizeStyle=D,e.onActivated=Qn,e.onBeforeMount=io,e.onBeforeUnmount=uo,e.onBeforeUpdate=co,e.onDeactivated=Xn,e.onErrorCaptured=go,e.onMounted=lo,e.onRenderTracked=mo,e.onRenderTriggered=ho,e.onScopeDispose=function(e){ne&&ne.cleanups.push(e)},e.onServerPrefetch=fo,e.onUnmounted=po,e.onUpdated=ao,e.openBlock=Nr,e.popScopeId=function(){dn=null},e.provide=zo,e.proxyRefs=Rt,e.pushScopeId=function(e){dn=e},e.queuePostFlushCb=tn,e.reactive=ht,e.readonly=gt,e.ref=Ot,e.registerRuntimeCompiler=ls,e.render=$i,e.renderList=function(e,t,n,o){let r;const s=n&&n[o];if(d(e)||y(e)){r=new Array(e.length);for(let n=0,o=e.length;nt(e,n,void 0,s&&s[n])));else{const n=Object.keys(e);r=new Array(n.length);for(let o=0,i=n.length;oe.devtools.emit(t,...n))),cn=[];else if("undefined"!=typeof window&&window.HTMLElement&&!(null==(s=null==(r=window.navigator)?void 0:r.userAgent)?void 0:s.includes("jsdom"))){(o.__VUE_DEVTOOLS_HOOK_REPLAY__=o.__VUE_DEVTOOLS_HOOK_REPLAY__||[]).push((e=>{t(e,o)})),setTimeout((()=>{e.devtools||(o.__VUE_DEVTOOLS_HOOK_REPLAY__=null,cn=[])}),3e3)}else cn=[]},e.setTransitionHooks=Wn,e.shallowReactive=mt,e.shallowReadonly=function(e){return vt(e,!0,je,ct,ft)},e.shallowRef=function(e){return $t(e,!0)},e.ssrContextKey=hs,e.ssrUtils=null,e.stop=function(e){e.effect.stop()},e.toDisplayString=e=>y(e)?e:null==e?"":d(e)||b(e)&&(e.toString===x||!v(e.toString))?JSON.stringify(e,te,2):String(e),e.toHandlerKey=R,e.toHandlers=function(e,t){const n={};for(const o in e)n[t&&/[A-Z]/.test(o)?`on:${o}`:R(o)]=e[o];return n},e.toRaw=xt,e.toRef=function(e,t,n){return Nt(e)?e:v(e)?new It(e):b(e)&&arguments.length>1?Bt(e,t,n):Ot(e)},e.toRefs=function(e){const t=d(e)?new Array(e.length):{};for(const n in e)t[n]=Bt(e,n);return t},e.toValue=function(e){return v(e)?e():At(e)},e.transformVNodeArgs=function(e){},e.triggerRef=function(e){Et(e)},e.unref=At,e.useAttrs=function(){return Eo().attrs},e.useCssModule=function(e="$style"){return n},e.useCssVars=function(e){const t=Qr();if(!t)return;const n=t.ut=(n=e(t.proxy))=>{Array.from(document.querySelectorAll(`[data-v-owner="${t.uid}"]`)).forEach((e=>Ms(e,n)))},o=()=>{const o=e(t.proxy);Rs(t.subTree,o),n(o)};Nn(o),lo((()=>{const e=new MutationObserver(o);e.observe(t.subTree.el.parentNode,{childList:!0}),po((()=>e.disconnect()))}))},e.useModel=function(e,t,n){const o=Qr();if(n&&n.local){const n=Ot(e[t]);return $n((()=>e[t]),(e=>n.value=e)),$n(n,(n=>{n!==e[t]&&o.emit(`update:${t}`,n)})),n}return{__v_isRef:!0,get value(){return e[t]},set value(e){o.emit(`update:${t}`,e)}}},e.useSSRContext=()=>{},e.useSlots=function(){return Eo().slots},e.useTransitionState=Vn,e.vModelCheckbox=pi,e.vModelDynamic=yi,e.vModelRadio=di,e.vModelSelect=hi,e.vModelText=ui,e.vShow=Ci,e.version=gs,e.warn=function(e,...t){},e.watch=$n,e.watchEffect=function(e,t){return Pn(e,null,t)},e.watchPostEffect=Nn,e.watchSyncEffect=function(e,t){return Pn(e,null,{flush:"sync"})},e.withAsyncContext=function(e){const t=Qr();let n=e();return ts(),S(n)&&(n=n.catch((e=>{throw es(t),e}))),[n,()=>es(t)]},e.withCtx=mn,e.withDefaults=function(e,t){return null},e.withDirectives=function(e,t){const o=fn;if(null===o)return e;const r=us(o)||o.proxy,s=e.dirs||(e.dirs=[]);for(let i=0;in=>{if(!("key"in n))return;const o=A(n.key);return t.some((e=>e===o||xi[e]===o))?e(n):void 0},e.withMemo=function(e,t,n,o){const r=n[o];if(r&&ms(r,e))return r;const s=t();return s.memo=e.slice(),n[o]=s},e.withModifiers=(e,t)=>(n,...o)=>{for(let e=0;emn,e}({}); diff --git a/src/hermes_dec/gui/static/vue_templates/111_filepicker.js b/src/hermes_dec/gui/static/vue_templates/111_filepicker.js new file mode 100644 index 0000000..8beb8ec --- /dev/null +++ b/src/hermes_dec/gui/static/vue_templates/111_filepicker.js @@ -0,0 +1,39 @@ +var FilePicker = { + emits: [ + 'upload_file' + ], + + methods: { + pick_file(event) { + console.log('DEBUG: File picker result: => ', event); + + if(event.target.files.length) { + let file = event.target.files[0]; + + let self = this; + file.arrayBuffer().then(function(array_buffer) { + hash_file_buffer(array_buffer).then(function(file_hash) { + + self.$emit('upload_file', file, file_hash); + + }); + }); + } + }, + + trigger_click() { + this.$refs.file_picker.click(); + } + }, + + template: `
+ + +
` +}; \ No newline at end of file diff --git a/src/hermes_dec/gui/static/vue_templates/112_recentfiles.js b/src/hermes_dec/gui/static/vue_templates/112_recentfiles.js new file mode 100644 index 0000000..57a45e5 --- /dev/null +++ b/src/hermes_dec/gui/static/vue_templates/112_recentfiles.js @@ -0,0 +1,42 @@ +var RecentFiles = { + emits: [ + 'open_hash' + ], + + props: { + recent_files: Array + }, + + methods: { + format_date(data) { + return format_date(data); + }, + format_size(data) { + return format_size(data); + } + }, + + template: `

Recently opened files

+
+ + + + + + + + + + + + + + + + + + + +
File nameFile sizeFile hashUploaded dateModified date
{{ recent_file.orig_name }}{{ format_size(recent_file.file_size) }}{{ recent_file.file_hash.substr(0, 7) }}{{ format_date(recent_file.db_created_time) }}{{ format_date(recent_file.db_updated_time) }}
` +}; \ No newline at end of file diff --git a/src/hermes_dec/gui/static/vue_templates/11_homeview.js b/src/hermes_dec/gui/static/vue_templates/11_homeview.js new file mode 100644 index 0000000..d08f923 --- /dev/null +++ b/src/hermes_dec/gui/static/vue_templates/11_homeview.js @@ -0,0 +1,35 @@ +var HomeView = { + props: { + recent_files: Array + }, + + emits: [ + 'open_hash', + 'upload_file' + ], + + methods: { + open_hash(file_hash) { + this.$emit('open_hash', file_hash); + }, + + upload_file(file, file_hash) { + this.$emit('upload_file', file, file_hash); + } + }, + + components: { + FilePicker, + RecentFiles + }, + + // File open/recently opened options + template: `
+ + +
` +}; \ No newline at end of file diff --git a/src/hermes_dec/gui/static/vue_templates/12_loadview.js b/src/hermes_dec/gui/static/vue_templates/12_loadview.js new file mode 100644 index 0000000..0bbbed4 --- /dev/null +++ b/src/hermes_dec/gui/static/vue_templates/12_loadview.js @@ -0,0 +1,9 @@ +var LoadView = { + emits: ['open_hash_command'], + + mounted() { + this.$emit('open_hash_command'); + }, + + template: `

Loading file...

` +}; \ No newline at end of file diff --git a/src/hermes_dec/gui/static/vue_templates/131_topbar.js b/src/hermes_dec/gui/static/vue_templates/131_topbar.js new file mode 100644 index 0000000..86528ca --- /dev/null +++ b/src/hermes_dec/gui/static/vue_templates/131_topbar.js @@ -0,0 +1,27 @@ +var TopBar = { + props: { + file_metadata: Object + }, + + emits: ['switch_file'], + + methods: { + format_date(date) { + return format_date(date); + }, + format_size(size) { + return format_size(size); + } + }, + + template: `
+ + "{{ file_metadata.orig_name }}" | + Opened {{ format_date(file_metadata.db_created_time) }} | + Hermes bytecode v{{ file_metadata.bytecode_version }} | + {{ format_size(file_metadata.file_size) }} + + +
` +}; \ No newline at end of file diff --git a/src/hermes_dec/gui/static/vue_templates/132_sidepane.js b/src/hermes_dec/gui/static/vue_templates/132_sidepane.js new file mode 100644 index 0000000..1d4409e --- /dev/null +++ b/src/hermes_dec/gui/static/vue_templates/132_sidepane.js @@ -0,0 +1,94 @@ +var SidePane = { + data() { + return { + current_tab: 'functions_list', + + // Static data: + + tab_names: [ + {raw: 'functions_list', readable: 'Functions'}, + {raw: 'strings_list', readable: 'Strings'}, + {raw: 'file_headers', readable: 'Headers'} + ], + + functions_list_columns: [ + {name: 'Name', raw: 'name', is_searcheable: 'rawstring'}, + {name: 'Offset', raw: 'offset', is_searcheable: 'offset'}, + {name: 'Size', raw: 'size', is_searcheable: 'no'} + ], + + file_headers_columns: [ + {name: 'Field', raw: 'field'}, + {name: 'Value', raw: 'value'} + ] + }; + }, + + props: { + table_data_map: Object, + current_function: Number + }, + + emits: [ + 'select_function', + 'load_table' + ], + + components: { + SearchableTable + }, + + methods: { + load_table(...args) { + this.$emit('load_table', ...args); + }, + select_string(string_id) { + alert('WIP display x-refs here'); + }, + select_function(function_id) { + this.$emit('select_function', function_id); + } + }, + + template: `
+
+ +
+
+ + + +
+
` +}; \ No newline at end of file diff --git a/src/hermes_dec/gui/static/vue_templates/1331_disasmtab.js b/src/hermes_dec/gui/static/vue_templates/1331_disasmtab.js new file mode 100644 index 0000000..a847125 --- /dev/null +++ b/src/hermes_dec/gui/static/vue_templates/1331_disasmtab.js @@ -0,0 +1,65 @@ +var DisasmTab = { + props: { + current_function: Number, + disasm_blocks: Object + }, + + mounted() { + const canvas_grid = document.querySelector('#canvas_grid'); + canvas_grid.innerHTML = ''; + + const svg_tag = document.querySelector('#canvas_svg'); + svg_tag.innerHTML = ''; + + var graph = new Graph(svg_tag, canvas_grid); + var node_objects = []; + + for(var block_idx = 0; block_idx < this.disasm_blocks.length; block_idx++) { + var block = this.disasm_blocks[block_idx]; + + var html_block = document.createElement('div'); + + // We're using a 1-indexed CSS grid layout. + // When both indexes are even it's a node, + // when either index (row or column) is odd it's a lattice + html_block.style.gridColumn = block.grid_x * 2; + html_block.style.gridRow = block.grid_y * 2; + + var node_div = document.createElement('div'); + node_div.className = 'graph_node'; + node_div.textContent = block.text; + html_block.appendChild(node_div); + + var node = new Node(graph, html_block, block_idx); + node_objects.push(node); + + canvas_grid.appendChild(html_block); + } + + for(var block_idx = 0; block_idx < this.disasm_blocks.length; block_idx++) { + var block = this.disasm_blocks[block_idx]; + var node = node_objects[block_idx]; + + for(var child_port_idx of block.child_nodes) { + var out_port = new OutPort(graph, node, false); + var in_port = new InPort(graph, node_objects[child_port_idx], false); + new Edge(graph, out_port, in_port); + } + for(var child_port_idx of block.child_error_nodes) { + var out_port = new OutPort(graph, node, true); + var in_port = new InPort(graph, node_objects[child_port_idx], true); + new Edge(graph, out_port, in_port); + } + } + + graph.prerender(); + graph.render(); + + // WIP fix display introduce graph ordering introduce links pathfinding ... + }, + + template: `
+ +
+
` +}; \ No newline at end of file diff --git a/src/hermes_dec/gui/static/vue_templates/133_mainpane.js b/src/hermes_dec/gui/static/vue_templates/133_mainpane.js new file mode 100644 index 0000000..23b2a2d --- /dev/null +++ b/src/hermes_dec/gui/static/vue_templates/133_mainpane.js @@ -0,0 +1,47 @@ +var MainPane = { + props: { + current_function: Number, + current_tab: String, + function_is_syncing: Boolean, + disasm_blocks: Object + }, + + data() { + return { + // Static data: + + tab_names: [ + {raw: 'disasm_view', readable: 'Disassembly'}, + {raw: 'string_view', readable: 'Strings'}, + {raw: 'decompile_view', readable: 'Decompiled code'} + ] + } + }, + + components: { + DisasmTab + }, + + emits: ['set_current_tab'], + + template: `
+
+ +
+ +
+ +
+
` +}; \ No newline at end of file diff --git a/src/hermes_dec/gui/static/vue_templates/134_logconsole.js b/src/hermes_dec/gui/static/vue_templates/134_logconsole.js new file mode 100644 index 0000000..c9ddefb --- /dev/null +++ b/src/hermes_dec/gui/static/vue_templates/134_logconsole.js @@ -0,0 +1,7 @@ +var LogConsole = { + props: { + data: String + }, + + template: `
{{ data }}
` +}; \ No newline at end of file diff --git a/src/hermes_dec/gui/static/vue_templates/135_popup.js b/src/hermes_dec/gui/static/vue_templates/135_popup.js new file mode 100644 index 0000000..43a8e17 --- /dev/null +++ b/src/hermes_dec/gui/static/vue_templates/135_popup.js @@ -0,0 +1,6 @@ +var Popup = { + template: ` +` +}; \ No newline at end of file diff --git a/src/hermes_dec/gui/static/vue_templates/13_workview.js b/src/hermes_dec/gui/static/vue_templates/13_workview.js new file mode 100644 index 0000000..8d0015e --- /dev/null +++ b/src/hermes_dec/gui/static/vue_templates/13_workview.js @@ -0,0 +1,69 @@ +var WorkView = { + props: { + file_metadata: Object, + table_data_map: Object, + current_function: Number, + function_is_syncing: Boolean, + current_tab: String, + disasm_blocks: Object, + console_text: String + }, + + emits: [ + 'switch_file', + 'select_function', + 'select_function_command', + 'load_table', + 'set_current_tab' + ], + + watch: { + current_function: { + handler(new_value) { + this.$emit('select_function_command'); + }, + immediate: true + } + }, + + methods: { + load_table(...args) { + this.$emit('load_table', ...args); + }, + + select_function(function_id) { + this.$emit('select_function', function_id); + }, + + set_current_tab(tab_name) { + this.$emit('set_current_tab', tab_name); + } + }, + + components: { + TopBar, + SidePane, + LogConsole, + MainPane + }, + + template: `
+ + + + +
` +}; \ No newline at end of file diff --git a/src/hermes_dec/gui/static/vue_templates/1_approot.js b/src/hermes_dec/gui/static/vue_templates/1_approot.js new file mode 100644 index 0000000..67765dd --- /dev/null +++ b/src/hermes_dec/gui/static/vue_templates/1_approot.js @@ -0,0 +1,289 @@ +// WIP.. +var AppRoot = { + data() { + return { + // Websocket connection handling: + socket: null, // The websocket object to be created itself + is_connected: false, // Reactive state for syncing with the Websocket state + is_retrying: false, + parsed_url_initially: false, + current_file_obj: null, + + // URL router handling (be sure to set all defaults to "null"): + hash_data: { // URL hash JSON data as currently present in the page's URL + file_hash: null, // SHA-256 lowercase hex string if set + current_function: null, + + current_tab: null, // Not listed in sync_data + // ^ Enum string, any of: "disasm_view" (default), + // "string_view", "decompile_view" + // (see "133_tabview.js" for the full definition) + }, + sync_data: { // URL hash JSON data as it matches the content effectively + // retrieved from the WebSocket's URL + file_hash: null, + current_function: null, + + }, + + // Data to be downloaded from the upstream: + dl: { + current_message: null, // (Any view, popover - Retrieved from the "pop_message" wire message) + current_message_icon: null, // (same) + + recent_files: null, // (Home view) (Retrieved from the "recent_files" wire message) + + file_metadata: null, // (Main view > Top bar) (Retrieved from the "file_opened" wire message) + table_data_map: null, // (Main view > Sidebar) Maps {table_name: table_data_obj} for each retrieved table + + disasm_blocks: null, // (Main view > Body > Disasm tab) (Retrieved from the "analyzed_function" wire message), + + console_text: null // (Main view > Log console) (Concatenated and retrieved from the "console_log" and "console_error_log" wire messages) + } + } + }, + + components: { + HomeView, + LoadView, + WorkView + }, + + watch: { + hash_data: { + handler(new_value, same_value) { + if(!this.parsed_url_initially) { + return; + } + + var fields = []; + for(var field in this.hash_data) { + if(field && this.hash_data[field] != null) { + fields.push(field + '=' + this.hash_data[field]); + } + } + + var replacement_str = fields.join('/'); + var current_str = decodeURIComponent(location.hash.slice(1)); + + if(replacement_str !== current_str) { + location.hash = '#' + replacement_str; + } + }, + deep: true + } + }, + + methods: { + open_socket() { + this.socket = new WebSocket('ws://localhost:49594'); + + this.socket.onopen = this.socket_open.bind(this); + this.socket.onmessage = this.socket_message.bind(this); + this.socket.onclose = this.socket_closed.bind(this); + }, + socket_open() { + this.is_connected = true; + this.is_retrying = false; + }, + socket_message(event) { + console.log('DEBUG: WS received: ' + event.data); + var message = JSON.parse(event.data); + + switch(message.type) { + case 'recent_files': + this.dl.recent_files = message.recent_files; + break; + + case 'file_hash_unknown': + var file = this.current_file_obj; + + this.socket.send(JSON.stringify({ + type: 'begin_transfer', + file_name: file.name, + file_hash: message.file_hash + })); + + var chunk_size = 1 * 1024 * 1024; + for(var pos = 0; pos < file.size; pos += chunk_size) { + this.socket.send(file.slice(pos, Math.min(file.size, pos + chunk_size))); + } + + this.socket.send(JSON.stringify({ + type: 'end_transfer' + })); + break; + + case 'file_opened': + this.sync_data.file_hash = message.file_metadata.file_hash; + + this.dl.file_metadata = message.file_metadata; + + this.hash_data.current_function = parseInt(this.hash_data.current_function, 10) || 0; + this.hash_data.current_tab = this.hash_data.current_tab || 'disasm_view'; + break; + + case 'console_log': + if(!this.dl.console_text) this.dl.console_text = ''; + this.dl.console_text += message.data.trimRight() + '\n'; + break; + + case 'console_error_log': + if(!this.dl.console_text) this.dl.console_text = ''; + this.dl.console_text += '[!] ' + message.data.trimRight() + '\n'; + break; + + case 'table_data': + if(!this.dl.table_data_map) { + this.dl.table_data_map = {}; + } + this.dl.table_data_map[message.table] = message; + // WIP ... REFERENCE TABLE_DATA_MAP + break; + + case 'analyzed_function': + this.sync_data.current_function = message.function_id; + this.dl.disasm_blocks = message.blocks; + + break; + } + }, + socket_closed() { + this.is_connected = false; + this.is_retrying = true; + + for(var key in this.dl) { + this.dl[key] = null; + } + for(var key in this.sync_data) { + this.sync_data[key] = null; + } + + const RETRY_DELAY = 5000; + window.setTimeout(function(self) { + self.open_socket(); + }, RETRY_DELAY, this); + }, + + sync_hash_from_url() { + var new_data = {}; + for(var field of location.hash.slice(1).split('/')) { + var split_field = field.split('='); + if(split_field.length == 2) { + new_data[split_field[0]] = split_field[1]; + } + } + for(var prop in new_data) { + if(new_data[prop] != this.hash_data[prop]) { + Object.assign(this.hash_data, new_data); + break; + } + } + this.parsed_url_initially = true; + }, + + set_current_tab(tab_name) { + this.hash_data.current_tab = tab_name; + }, + + select_function(function_id) { + if(this.hash_data.current_function != function_id) { + this.hash_data.current_function = function_id; + } + }, + select_function_command() { + if(this.hash_data.current_function != this.sync_data.current_function) { + this.socket.send(JSON.stringify({ + type: 'analyze_function', + function_id: parseInt(this.hash_data.current_function, 10) || 0 + })); + } + }, + + switch_file() { + for(var key in this.dl) { + if(key !== 'recent_files') { + this.dl[key] = null; + } + } + for(var key in this.sync_data) { + this.sync_data[key] = null; + } + for(var key in this.hash_data) { + if(this.hash_data[key] != null) { + this.hash_data[key] = null; + } + } + }, + + upload_file(file, file_hash) { + this.current_file_obj = file; + this.open_hash(file_hash); + }, + load_table(table_name, text_filter, current_row, page) { + if(this.dl.table_data_map && this.dl.table_data_map[table_name]) { + // Ensure that the loading spinner is displayed: + this.dl.table_data_map[table_name].reloading = true; + } + this.socket.send(JSON.stringify({ + type: 'get_table_data', + table: table_name, + text_filter: text_filter, + current_row: current_row, + page: page + })); + }, + open_hash(file_hash) { + this.hash_data.file_hash = file_hash; + }, + open_hash_command() { + this.socket.send(JSON.stringify({ + type: 'open_file_by_hash', + file_hash: this.hash_data.file_hash + })); + } + }, + + created() { + window.onhashchange = this.sync_hash_from_url.bind(this); + this.sync_hash_from_url(); + + this.open_socket(); + }, + + template: ` + ` + // template: (WIP.) + //