diff --git a/.eslintignore b/.eslintignore index 1974d1c021..8b4ad3e97d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -5,6 +5,7 @@ dist/** mnemonic_languages/** # Generated files +js/curve/* js/components.js js/libtextsecure.js js/libloki.js diff --git a/.eslintrc.js b/.eslintrc.js index 34ab52aaaf..4ce192bbb7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,6 +21,10 @@ module.exports = { }, ], + // Enforce curlies always + curly: ['error', 'all'], + 'brace-style': ['error', '1tbs'], + // prevents us from accidentally checking in exclusive tests (`.only`): 'mocha/no-exclusive-tests': 'error', @@ -45,7 +49,7 @@ module.exports = { quotes: [ 'error', 'single', - { avoidEscape: true, allowTemplateLiterals: false }, + { avoidEscape: true, allowTemplateLiterals: true }, ], // Prettier overrides: @@ -61,6 +65,7 @@ module.exports = { // We still want to limit comments as before: comments: 90, ignoreUrls: true, + ignoreRegExpLiterals: true, }, ], }, diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6a1fe20aef..2168326492 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us improve title: '' labels: '' assignees: '' - --- **Describe the bug** @@ -17,6 +16,7 @@ Steps to reproduce the behavior: If applicable, add screenshots or log files to help explain your problem. **Other information (please complete the following information):** - - Device: [e.g. PC, Mac] - - OS: [e.g. Ubuntu 16.04, Windows 10] - - Loki messenger Version or Git commit hash: + +* Device: [e.g. PC, Mac] +* OS: [e.g. Ubuntu 16.04, Windows 10] +* Session Version or Git commit hash: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..650a4416a1 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,36 @@ + + + + +### First time contributor checklist: + +* [ ] I have read the [README](https://github.com/loki-project/loki-messenger/blob/master/README.md) and [Contributor Guidelines](https://github.com/loki-project/loki-messenger/blob/master/CONTRIBUTING.md) + +### Contributor checklist: + +* [ ] My commits are in nice logical chunks with [good commit messages](http://chris.beams.io/posts/git-commit/) +* [ ] My changes are [rebased](https://medium.freecodecamp.org/git-rebase-and-the-golden-rule-explained-70715eccc372) on the latest [`clearnet`](https://github.com/loki-project/loki-messenger/tree/development) branch +* [ ] A `yarn ready` run passes successfully ([more about tests here](https://github.com/loki-project/loki-messenger/blob/master/CONTRIBUTING.md#tests)) +* [ ] My changes are ready to be shipped to users + +### Description + + diff --git a/.gitignore b/.gitignore index aeb8140203..59d4c87215 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules .sass-cache +.eslintcache coverage/* build/curve25519_compiled.js build/icons/* @@ -30,3 +31,9 @@ test/test.js # React / TypeScript ts/**/*.js ts/protobuf/*.d.ts + +# Swapfiles +**/*.swp + +# Ctags +tags diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 69656b2375..76d3a0c03a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,7 +10,7 @@ linux: - yarn install --frozen-lockfile - export SIGNAL_ENV=production - yarn generate - - $(yarn bin)/build --config.extraMetadata.environment=$SIGNAL_ENV --config.mac.bundleVersion='$CI_COMMIT_REF_SLUG' --publish=never --config.directories.output=release + - $(yarn bin)/electron-builder --config.extraMetadata.environment=$SIGNAL_ENV --config.mac.bundleVersion='$CI_COMMIT_REF_SLUG' --publish=never --config.directories.output=release cache: paths: - node_modules/ @@ -27,7 +27,7 @@ osx: - yarn install --frozen-lockfile - export SIGNAL_ENV=production - yarn generate - - $(yarn bin)/build --config.extraMetadata.environment=$SIGNAL_ENV --config.mac.bundleVersion='$CI_COMMIT_REF_SLUG' --publish=never --config.directories.output=release + - $(yarn bin)/electron-builder --config.extraMetadata.environment=$SIGNAL_ENV --config.mac.bundleVersion='$CI_COMMIT_REF_SLUG' --publish=never --config.directories.output=release cache: paths: - node_modules/ @@ -51,7 +51,7 @@ windows: - call yarn generate - call node build\grunt.js - call yarn prepare-beta-build - - call node_modules\.bin\build --config.extraMetadata.environment=%SIGNAL_ENV% --publish=never --config.directories.output=release + - call node_modules\.bin\electron-builder --config.extraMetadata.environment=%SIGNAL_ENV% --publish=never --config.directories.output=release - call node build\grunt.js test-release:win cache: paths: diff --git a/.prettierignore b/.prettierignore index eb59d7d0b1..ff08c3b1f1 100644 --- a/.prettierignore +++ b/.prettierignore @@ -26,6 +26,7 @@ libloki/test/components.js # Third-party files node_modules/** components/** +js/curve/** js/Mp3LameEncoder.min.js js/WebAudioRecorderMp3.js js/libsignal-protocol-worker.js diff --git a/.travis.yml b/.travis.yml index 9de89d951a..5f2a7a5a6d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,14 +31,3 @@ matrix: - export LC_ALL=en_US - name: 'OSX' os: osx - - name: 'Windows' - os: windows - cache: false - env: - - YARN_GPG=no - before_install: - - cd ../.. - - mv $TRAVIS_REPO_SLUG _old - - git config --global core.autocrlf false - - git clone --depth=50 _old $TRAVIS_REPO_SLUG - - cd $TRAVIS_REPO_SLUG diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..9a92b58d54 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,58 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Mocha Tests", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "--recursive", + "--exit", + "test/app", + "test/modules", + "ts/test", + "libloki/test/node" + ], + "internalConsoleOptions": "openOnSessionStart" + }, + { + "type": "node", + "request": "launch", + "name": "Launch node Program", + "program": "${file}" + }, + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "program": "${file}" + }, + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${file}" + }, + { + "name": "Debug Main Process", + "type": "node", + "request": "launch", + "env": { + "NODE_APP_INSTANCE": "1" + }, + "cwd": "${workspaceRoot}", + "console": "integratedTerminal", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", + "windows": { + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" + }, + "args": ["."], + "sourceMaps": true, + "outputCapture": "std" + } + ] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e4ba71e00e..9795e48b95 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,7 +63,7 @@ yarn install --frozen-lockfile # Install and build dependencies (this will take yarn grunt # Generate final JS and CSS assets yarn icon-gen # Generate full set of icons for Electron yarn test # A good idea to make sure tests run first -yarn start # Start Loki Messenger! +yarn start # Start Session! ``` You'll need to restart the application regularly to see your changes, as there @@ -84,16 +84,16 @@ yarn grunt dev # runs until you stop it, re-generating built assets on file chan ## Additional storage profiles -Since there is no registration for Loki Messenger, you can create as many accounts as you +Since there is no registration for Session, you can create as many accounts as you can public keys. To test the P2P functionality on the same machine, however, requries that each client binds their message server to a different port. You can use the following command to start a client bound to a different port. + ``` yarn start-multi ``` - For more than 2 clients, you can setup additional storage profiles and switch between them using the `NODE_APP_INSTANCE` environment variable and specifying a new localServerPort in the config. @@ -149,12 +149,15 @@ So you wanna make a pull request? Please observe the following guidelines. the translations in [Transifex](https://www.transifex.com/projects/p/signal-desktop). --> + +* First, make sure that your `yarn ready` run passes - it's very similar to what our + Continuous Integration servers do to test the app. * Never use plain strings right in the source code - pull them from `messages.json`! You **only** need to modify the default locale [`_locales/en/messages.json`](_locales/en/messages.json). - + * [Rebase](https://nathanleclaire.com/blog/2014/09/14/dont-be-scared-of-git-rebase/) your changes on the latest `development` branch, resolving any conflicts. This ensures that your changes will merge cleanly when you open your PR. diff --git a/Gruntfile.js b/Gruntfile.js index 1121a22b52..1665bbef8b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -182,7 +182,7 @@ module.exports = grunt => { tasks: ['sass'], }, transpile: { - files: ['./ts/**/*.ts', './ts/**/*.tsx'], + files: ['./ts/**/*.ts', './ts/**/*.tsx', './ts/**/**/*.tsx'], tasks: ['exec:transpile'], }, }, diff --git a/README.md b/README.md index 60be0da49b..766d7db528 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# Loki Messenger +# Session [![Build Status](https://travis-ci.org/loki-project/loki-messenger.svg?branch=development)](https://travis-ci.org/loki-project/loki-messenger) -Loki Messenger allows for truly decentralized and end to end and private encrypted chats, Loki Messenger is built to handle both online and fully Asynchronous offline messages , Loki messenger implements the Signal protocol for message encryption, Our Client interface is a fork of [Signal Messenger](https://signal.org/). All communication that passes through Loki messenger is routed through [Lokinet](https://github.com/loki-project/loki-network). +Session allows for truly decentralized, end to end, and private encrypted chats. Session is built to handle both online and fully Asynchronous offline messages. Session implements the Signal protocol for message encryption. Our Client interface is a fork of [Signal Messenger](https://signal.org/). All communication that passes through Session is routed through [Lokinet](https://github.com/loki-project/loki-network). ## Summary -Loki messenger integrates directly with Loki Service Nodes, which are a set of distributed, decentralized and Sybil resistant nodes. Service Nodes act as both federated servers which store messages offline, and a set of nodes which allow for onion routing functionality obfuscating users IP Addresses. For a full understanding of how Loki messenger works, read the [Loki whitepaper](https://loki.network/whitepaper) +Session integrates directly with Loki [Service Nodes](https://lokidocs.com/ServiceNodes/SNOverview/), which are a set of distributed, decentralized and Sybil resistant nodes. Service Nodes act as both federated servers which store messages offline, and a set of nodes which allow for onion routing functionality obfuscating users IP Addresses. For a full understanding of how Session works, read the [Loki whitepaper](https://loki.network/whitepaper). **Online Messages** @@ -14,11 +14,11 @@ If Alice and Bob are both online they can simply resolve each others public keys **Offline messages** -Offline messaging uses Swarms, given any users public key the user can resolve a public key to a specific grouping of Service Nodes (AKA Swarm) each user in Loki Messenger belongs to a Swarm. When routing a message offline the user selects a Service node in the destination users Swarm, when the user comes online they query any node in their Swarm, if the Swarm is holding any messages for the user they disseminate those messages to the user. +Offline messaging uses Swarms, given any users public key the user can resolve a public key to a specific grouping of Service Nodes (AKA Swarm) each user in Session belongs to a Swarm. When routing a message offline the user selects a Service node in the destination users Swarm, when the user comes online they query any node in their Swarm, if the Swarm is holding any messages for the user they disseminate those messages to the user. ![Swarm Messaging](https://i.imgur.com/o13Knds.png) -Spam protections for Loki Messenger are based on a Proof of Work which is attached to any message that exceeds a default size or Time To Live, this process is discussed further in the [Loki whitepaper](https://loki.network/whitepaper). +Spam protections for Session are based on a Proof of Work which is attached to any message that exceeds a default size or Time To Live, this process is discussed further in the [Loki whitepaper](https://loki.network/whitepaper). ## Want to Contribute? Found a Bug or Have a feature request? @@ -26,7 +26,7 @@ Please search for any [existing issues](https://github.com/loki-project/loki-mes ## Build instruction -build instructions can be found in [CONTRIBUTING.md](CONTRIBUTING.md) +Build instructions can be found in [CONTRIBUTING.md](CONTRIBUTING.md). ## License diff --git a/_locales/ar/messages.json b/_locales/ar/messages.json index 3ab1a6f062..4d8a837b56 100644 --- a/_locales/ar/messages.json +++ b/_locales/ar/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copy error and quit", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database Error", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Delete all data and restart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&File", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -36,7 +52,7 @@ "description": "Application menu command to show all application windows" }, "appMenuQuit": { - "message": "Quit Loki Messenger", + "message": "Quit Session", "description": "Application menu command to close the application" }, "editMenuUndo": { @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Archived Conversations", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "These conversations are archived and will only appear in the Inbox if new messages are received.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Choose folder", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "When including a non-image attachment, the limit is one attachment per message.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "You cannot mix non-image and image attachments in one message.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Quit", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Enter name or number", + "search": { + "message": "بحث", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "المحادثات", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "جهات الإتصال", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "الرسائل", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "مرحبا الى سيجنال Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "PO Box", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "التنزيل جارٍ...", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Download Attachment", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Photo", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "حسنا", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "إلى", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "عام", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Enable spell check of text entered in message composition box", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "انتهت صلاحية هذا الإصدار من سيغنال للحاسوب. الرجاء الارتقاء الى أحدث إصدار لمواصلة الرسائل.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "مستخدم أندرويد سيستقبل فقط أول 2000 حرف من هذه الرسالة", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "تحديث", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Dark", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Note to Self", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Hide menu bar", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Start conversation…", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1446,7 +1504,7 @@ "description": "Shown in the conversation history when someone updates the group" }, "titleIsNow": { - "message": "Title is now '$name$'", + "message": "Group name has been set to '$name$'", "description": "Shown in the conversation history when someone changes the title of the group", "placeholders": { "name": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Terms & Privacy Policy", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/bg/messages.json b/_locales/bg/messages.json index b653b30a78..9ad74f832b 100644 --- a/_locales/bg/messages.json +++ b/_locales/bg/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copy error and quit", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database Error", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Delete all data and restart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Файл", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -36,7 +52,7 @@ "description": "Application menu command to show all application windows" }, "appMenuQuit": { - "message": "Quit Loki Messenger", + "message": "Quit Session", "description": "Application menu command to close the application" }, "editMenuUndo": { @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Архивирани разговори", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "These conversations are archived and will only appear in the Inbox if new messages are received.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Изберете папка", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "When including a non-image attachment, the limit is one attachment per message.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "You cannot mix non-image and image attachments in one message.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Изход", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Сигнал Десктоп", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Въведете име или номер", + "search": { + "message": "Търсене", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Чатове", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Контакти", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Съобщения", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Добре дошли в Сигнал", "description": "" }, @@ -673,6 +727,10 @@ "message": "PO Box", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Downloading", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Download Attachment", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Изображение", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "Добре", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "До", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "General", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Enable spell check of text entered in message composition box", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Тази версия на Сигнал за компютър вече е остаряла. Моля, обновете до последната версия за да можете да изпращате съобщения.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Клиентите на Android получават само първите 2000 знака от това съобщение.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Обновяване", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Dark", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Note to Self", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Скрий лентата с менютата", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Започнете разговор...", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1446,7 +1504,7 @@ "description": "Shown in the conversation history when someone updates the group" }, "titleIsNow": { - "message": "Title is now '$name$'", + "message": "Group name has been set to '$name$'", "description": "Shown in the conversation history when someone changes the title of the group", "placeholders": { "name": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Terms & Privacy Policy", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/ca/messages.json b/_locales/ca/messages.json index d4b9e6dabc..5a526a26cc 100644 --- a/_locales/ca/messages.json +++ b/_locales/ca/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copia l'error i surt", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Grup desconegut", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Error de la base de dades", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Suprimeix totes les dades i reinicia", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Fitxer", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Converses arxivades", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Aquestes converses estan arxivades i només apareixeran a la bústia d'entrada si es reben missatges nous.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Arxiva la conversa", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Mou la conversa a la safata d'entrada", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Trieu una carpeta", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "Quan inclogueu un adjunt que no sigui una imatge, el límit és un adjunt per missatge.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "No podeu barrejar adjunts d'imatge i de no imatge en un mateix missatge.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Surt", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Introduïu un nom o número", + "search": { + "message": "Cerca", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No hi ha cap resultat per a \"$searchTerm$\".", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Converses", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Contactes", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Missatges", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Benvingut al Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "Apartat de correus", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "S'està baixant", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Baixa l'adjunt", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -702,7 +760,7 @@ "description": "In Android theme, shown in quote if you or someone else replies to you" }, "replyingTo": { - "message": "Es respon a $name$", + "message": "Resposta per a $name$", "description": "Shown in iOS theme when you or someone quotes to a message which is not from you", "placeholders": { "name": { @@ -735,6 +793,14 @@ "message": "Foto", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "D'acord", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "A", + "message": "a", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "General", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Envia previsualitzacions d'enllaços", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previsualitzacions estan disponibles per enllaços de Imgur, Reddit i Youtube.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Activa la comprovació ortogràfica del text introduït en el quadre d'edició de missatges", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Aquesta versió del Signal Desktop ha expirat. Actualitzeu a l'última versió per a poder seguir enviant i rebent missatges.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Els clients d'Android només rebran els primers 2000 caràcters d'aquest missatge.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Actualitza", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Fosc", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Notifica-m'ho", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Amaga la barra de menú", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Comenceu la conversa...", + "message": "Comença una conversa nova...", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Condicions de servei i política de privadesa", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/cs/messages.json b/_locales/cs/messages.json index f7ddeca67e..8682310215 100644 --- a/_locales/cs/messages.json +++ b/_locales/cs/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Zkopírovat chybu a ukončit", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Chyba databáze", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Smazat všechna data a restartovat", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Soubor", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -36,7 +52,7 @@ "description": "Application menu command to show all application windows" }, "appMenuQuit": { - "message": "Opustit Loki Messenger", + "message": "Opustit Session", "description": "Application menu command to close the application" }, "editMenuUndo": { @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Archivované konverzace", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Tyto konverzace jsou archivované a zobrazí se mezi doručenými zprávami, pokud budou přijaty nové zprávy.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Vybrat složku", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "When including a non-image attachment, the limit is one attachment per message.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "You cannot mix non-image and image attachments in one message.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Opustit", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Zadejte jméno nebo číslo", + "search": { + "message": "Hledat", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Konverzace", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Kontakty", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Zprávy", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Vítejte v Signalu", "description": "" }, @@ -673,6 +727,10 @@ "message": "P. O. BOX", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Downloading", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Stáhnout přílohu", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Fotografie", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Komu", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -908,7 +974,7 @@ "description": "Used in the guidance to help people find the 'link new device' area of their Signal mobile app" }, "plusButton": { - "message": "'+' Button", + "message": "Tlačítko '+'", "description": "The button used in Signal Android to add a new linked device" }, "linkNewDevice": { @@ -959,16 +1025,8 @@ "message": "Obecné", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Odeslat náhledy odkazu", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Náhledy jsou podporovány pro odkazy na Imgur, Instagram, Reddit, a YouTube.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { - "message": "Enable spell check of text entered in message composition box", + "message": "Kontrolovat pravopis při psaní zpráv", "description": "Description of the media permission description" }, "clearDataHeader": { @@ -1063,10 +1121,6 @@ "message": "Tato verze aplikace Signal Desktop je zastaralá. Abyste mohli dále komunikovat, aktualizujte ji prosím na nejnovější verzi.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Uživatelé Androidu dostanou pouze prvních 2000 znaků této zprávy.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Aktualizovat", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Tmavý", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Poznámka sobě", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Schovat lištu menu", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Zahájit konverzaci...", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Smluvní podmínky a zásady ochrany osobních údajů", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/da/messages.json b/_locales/da/messages.json index 9aaeaeaa51..f8bbe312e7 100644 --- a/_locales/da/messages.json +++ b/_locales/da/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Kopier fejl og afslut", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Ukendt gruppe", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Databasefejl", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Slet alle data og genstart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Filer", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Arkiveret samtaler", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Disse samtaler arkiveres og vises kun i indbakken, hvis der modtages nye meddelelser.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Arkiver samtalen", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Flyt samtalen til indbakken", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Vælg mappe", "description": "Button to allow the user to find a folder on disk" @@ -484,11 +516,11 @@ "description": "Shown in toast when user attempts to send .exe file, for example" }, "loadingPreview": { - "message": "Loading Preview...", + "message": "Indlæser eksempelvisning...", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area" }, "stagedPreviewThumbnail": { - "message": "Draft thumbnail link preview for $domain$", + "message": "Udkast miniaturebillede til eksempelvisning for $domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -498,7 +530,7 @@ } }, "previewThumbnail": { - "message": "Thumbnail link preview for $domain$", + "message": "Miniaturebillede til eksempelvisning for $domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -508,7 +540,7 @@ } }, "stagedImageAttachment": { - "message": "Draft image attachment: $path$", + "message": "Udkast af vedhæftet billedfil: $path$", "description": "Alt text for staged attachments", "placeholders": { "path": { @@ -521,7 +553,7 @@ "message": "Når der indgår en vedhæftet fil uden billed, er grænsen en vedhæftet fil pr. besked.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "Du kan ikke blande ikke-billed og billedvedhæftninger i en besked.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Afslut", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Indtast navn eller nummer", + "search": { + "message": "Søg", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "Ingen resultater for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Samtaler", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Kontakter", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Beskeder", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Velkommen til Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "Postboks", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Henter", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Hent vedhæftet fil", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Billede", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "Ok", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Til", + "message": "til", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Generelt", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Aktiver stavekontrol af beskeder", "description": "Description of the media permission description" @@ -1000,15 +1058,15 @@ "description": "Message shown to user when app is disconnected and data deleted" }, "notifications": { - "message": "Notifikationer", + "message": "Meddelelser", "description": "Header for notification settings" }, "notificationSettingsDialog": { - "message": "Når beskeder ankommer, vis notifikationer med:", + "message": "Når beskeder ankommer, vis meddelelser med:", "description": "Explain the purpose of the notification settings" }, "disableNotifications": { - "message": "Slå notifikationer fra", + "message": "Slå meddelelser fra", "description": "Label for disabling notifications" }, "nameAndMessage": { @@ -1063,10 +1121,6 @@ "message": "Denne version af Signal er forældet. Venligst opgrader til den seneste version.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Android-enheder vil kun modtage de første 2000 tegn af denne besked.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Opgrader", "description": "Label text for button to upgrade the app to the latest version" @@ -1334,7 +1388,7 @@ } }, "audioNotificationDescription": { - "message": "Afspil lydnotifikation", + "message": "Afspil lydmeddelelse", "description": "Description for audio notification setting" }, "safetyNumberChanged": { @@ -1373,12 +1427,16 @@ "message": "Mørk", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Note til dig selv", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Skjul menulinje", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Start samtale...", + "message": "Start ny samtale...", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Vilkår og privatlivspolitik", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/de/messages.json b/_locales/de/messages.json index bf65576747..6df957c3aa 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Fehler kopieren und beenden", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unbekannte Gruppe", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Datenbankfehler", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Alle Daten löschen und neu starten", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Datei", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Archivierte Unterhaltungen", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Diese Unterhaltungen sind archiviert und werden nur dann im Eingang erscheinen, falls neue Nachrichten empfangen werden.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Unterhaltung archivieren", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Unterhaltung in Eingang verschieben", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Ordner wählen", "description": "Button to allow the user to find a folder on disk" @@ -376,7 +408,7 @@ "description": "Link to open the issue tracker" }, "gotIt": { - "message": "Alles klar!", + "message": "Verstanden", "description": "Label for a button that dismisses a dialog. The user clicks it to confirm that they understand the message in the dialog." }, "submit": { @@ -521,7 +553,7 @@ "message": "Mehrere Anhänge je Nachricht sind ausschließlich bei Bildern erlaubt.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "Du kannst Bilder nicht gemeinsam mit anderen Anhängen in einer Nachricht kombinieren.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Beenden", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Name oder Rufnummer eingeben", + "search": { + "message": "Suchen", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "Keine Ergebnisse für »$searchTerm$«", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Unterhaltungen", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Kontakte", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Nachrichten", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Willkommen bei Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "Postfach", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Wird heruntergeladen", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Anhang herunterladen", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,8 +793,16 @@ "message": "Foto", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { - "message": "O. K.", + "message": "Okay", "description": "" }, "cancel": { @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "An", + "message": "an", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Allgemein", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Link-Vorschauen senden", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Vorschauen werden unterstützt für Links von Imgur, Instagram, Reddit und YouTube.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Rechtschreibprüfung für im Nachrichteneingabefeld eingegebenen Text aktivieren", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Diese Version von Signal Desktop ist veraltet. Bitte führe eine Aktualisierung auf die aktuellste Version durch, um weiterhin Nachrichten austauschen zu können.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Signal für Android kann nur die ersten 2.000 Zeichen dieser Nachricht empfangen.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Aktualisieren", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Dunkel", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Notiz an mich", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Menüleiste ausblenden", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Unterhaltung beginnen …", + "message": "Neue Unterhaltung beginnen …", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1410,7 +1468,7 @@ "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Zum Durchführen der Aktualisierungen klicke auf »Signal neu starten«.", + "message": "Zum Aktualisieren klicke auf »Signal neu starten«.", "description": "" }, "autoUpdateRestartButtonLabel": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Bedingungen & Datenschutzerklärung", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/el/messages.json b/_locales/el/messages.json index fb48054a81..6acf9812d1 100644 --- a/_locales/el/messages.json +++ b/_locales/el/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Αντιγραφή λάθους και έξοδος", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Λάθος της βάσης δεδομένων", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Διαγραφή όλων των δεδομένων και επανεκκίνηση", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Φάκελος", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,12 +161,28 @@ } } }, + "archivedConversations": { + "message": "Αρχειοθετημένες Συνομιλίες", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Αυτές οι συνομιλίες είναι αρχειοθετημένες και θα εμφανιστούν στα εισερχόμενα μόνο αν ληφθούν νέα μηνύματα.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Επιλογή φακέλου", "description": "Button to allow the user to find a folder on disk" }, "chooseFile": { - "message": "Choose file", + "message": "Επιλογή αρχείου", "description": "Button to allow the user to find a file on disk" }, "loadDataHeader": { @@ -484,11 +516,11 @@ "description": "Shown in toast when user attempts to send .exe file, for example" }, "loadingPreview": { - "message": "Loading Preview...", + "message": "Φόρτωση προεσκόπισης...", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area" }, "stagedPreviewThumbnail": { - "message": "Draft thumbnail link preview for $domain$", + "message": "Πρόχειρη μικρή προεσκόπιση συνδέσμου για $domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -498,7 +530,7 @@ } }, "previewThumbnail": { - "message": "Thumbnail link preview for $domain$", + "message": "Μικρή προεσκόπιση συνδέσμου για $domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -508,7 +540,7 @@ } }, "stagedImageAttachment": { - "message": "Draft image attachment: $path$", + "message": "Πρόχειρη συνημμένη εικόνα: $path$", "description": "Alt text for staged attachments", "placeholders": { "path": { @@ -518,15 +550,15 @@ } }, "oneNonImageAtATimeToast": { - "message": "When including a non-image attachment, the limit is one attachment per message.", + "message": "Για συνημμένα αρχεία που δεν είναι εικόνες, το όριο είναι ένα αρχείο ανά μήνυμα.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { - "message": "You cannot mix non-image and image attachments in one message.", + "cannotMixImageAndNonImageAttachments": { + "message": "Δεν είναι δυνατό να συνάψετε εικόνες μαζί με αρχεία άλλου τύπου στο ίδιο μήνυμα.", "description": "An error popup when the user has attempted to add an attachment" }, "maximumAttachments": { - "message": "You cannot add any more attachments to this message.", + "message": "Δεν είναι δυνατή η προσθήκη άλλων συνημμένων αρχείων σε αυτό το μήνυμα.", "description": "An error popup when the user has attempted to add an attachment" }, "fileSizeWarning": { @@ -611,15 +643,37 @@ "message": "Έξοδος", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Εισάγετε όνομα ή αριθμό", + "search": { + "message": "Αναζήτηση", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Συνομιλίες", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Επαφές", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Μηνύματα", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Καλώς ορίσατε στο Signal", "description": "" }, @@ -628,7 +682,7 @@ "description": "" }, "typingAlt": { - "message": "Typing animation for this conversation", + "message": "Κινούμενη εικόνα που δείχνει πληκτρολόγηση για αυτή τη συζήτηση", "description": "Used as the 'title' attibute for the typing animation" }, "contactAvatarAlt": { @@ -673,6 +727,10 @@ "message": "Τ.Θ.", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Downloading", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Λήψη Συνημμένου", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -694,7 +752,7 @@ "description": "Shown in toast if user clicks on quote references messages not loaded in view, but in database" }, "voiceNoteMustBeOnlyAttachment": { - "message": "A voice note must be the only attachment included in a message.", + "message": "Ένα μήνυμα φωνής πρέπει να είναι το μοναδικό συνημμένο αρχείο σε ένα μήνυμα.", "description": "Shown in toast if tries to record a voice note with any staged attachments" }, "you": { @@ -735,6 +793,14 @@ "message": "Φωτογραφία ", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Προς", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -862,7 +928,7 @@ "description": "Used in the alt tag for the image shown in a full-screen lightbox view" }, "imageCaptionIconAlt": { - "message": "Icon showing that this image has a caption", + "message": "Εικονίδιο που ενδεικνύει ότι αυτή η εικόνα έχει υπότιτλο", "description": "Used for the icon layered on top of an image in message bubbles" }, "addACaption": { @@ -956,17 +1022,9 @@ "description": "Description of the media permission description" }, "general": { - "message": "General", + "message": "Γενικά", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Ενεργοποίηση ορθογραφικού ελέγχου του κειμένου που εισάγεται στο παράθυρο σύνθεσης μηνύματος", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Αυτή η έκδοση του Signal Desktop έχει λήξει. Κάντε αναβάθμιση στην πιο πρόσφατη έκδοση για να συνεχίσετε την ανταλλαγή μηνυμάτων.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Οι παραλήπτες σε Android θα λάβουν μόνο τους πρώτους 2000 χαρακτήρες αυτού του μηνύματος.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Αναβάθμιση", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Σκοτεινό", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Να μην ξεχάσω ", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Απόκρυψη γραμμής μενού", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Έναρξη συνομιλίας...", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Όροι & Πολιτική Απορρήτου", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 1c8fa7e0d5..896892a4e7 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2,7 +2,26 @@ "privacyPolicy": { "message": "Terms & Privacy Policy", "description": - "Shown in the about box for the link to https://signal.org/legal" + "Shown in the about box for the link to https://getsession.org/privacy-policy/" + }, + "copyErrorAndQuit": { + "message": "Copy error and quit", + "description": + "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": + "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database Error", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Delete all data and restart", + "description": + "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" }, "mainMenuFile": { "message": "&File", @@ -47,7 +66,7 @@ "description": "Application menu command to show all application windows" }, "appMenuQuit": { - "message": "Quit Loki Messenger", + "message": "Quit Session", "description": "Application menu command to close the application" }, "editMenuUndo": { @@ -147,6 +166,11 @@ "description": "Only available on development modes, menu option to open up the standalone device setup sequence" }, + "connectingLoad": { + "message": "Connecting To Server", + "description": + "Message shown on the as a loading screen while we are connecting to something" + }, "loading": { "message": "Loading...", "description": @@ -168,6 +192,27 @@ } } }, + "archivedConversations": { + "message": "Archived Conversations", + "description": + "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": + "These conversations are archived and will only appear in the Inbox if new messages are received.", + "description": + "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": + "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": + "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Choose folder", "description": "Button to allow the user to find a folder on disk" @@ -220,7 +265,7 @@ "Header shown on the screen at the end of a successful import process" }, "importCompleteStartButton": { - "message": "Start using Loki Messenger", + "message": "Start using Session", "description": "Button shown at end of successful import process, nothing left but a restart" }, @@ -250,6 +295,9 @@ } } }, + "capsLockOn": { + "message": "Caps lock is on." + }, "me": { "message": "Me", "description": "The label for yourself when shown in a group member list" @@ -264,6 +312,11 @@ "description": "Displayed when a user can't send a message because they have left the group" }, + "youGotKickedFromGroup": { + "message": "You were removed from the group", + "description": + "Displayed when a user can't send a message because they have left the group" + }, "scrollDown": { "message": "Scroll to bottom of conversation", "description": @@ -345,6 +398,9 @@ "description": "When there are multiple previously-verified group members with safety number changes, a banner will be shown. The list of contacts with safety number changes is shown, and this text introduces that list." }, + "changedSinceVerifiedTitle": { + "message": "Safety Number Changed" + }, "changedSinceVerifiedMultiple": { "message": "Your safety numbers with multiple group members have changed since you last verified. This could mean that someone is trying to intercept your communication or that they have simply reinstalled Signal.", @@ -547,7 +603,7 @@ "description": "Name for a voice message attachment" }, "dangerousFileType": { - "message": "Attachment type not allowed for security reasons", + "message": "For security reasons, this file type cannot be sent", "description": "Shown in toast when user attempts to send .exe file, for example" }, @@ -590,25 +646,30 @@ }, "oneNonImageAtATimeToast": { "message": - "When including a non-image attachment, the limit is one attachment per message.", + "Sorry, there is a limit of one non-image attachment per message", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { - "message": "You cannot mix non-image and image attachments in one message.", + "cannotMixImageAndNonImageAttachments": { + "message": + "Sorry, you cannot mix images with other file types in one message", "description": "An error popup when the user has attempted to add an attachment" }, "maximumAttachments": { - "message": "You cannot add any more attachments to this message.", + "message": + "Maximum number of attachments reached. Please send remaining attachments in a separate message.", "description": "An error popup when the user has attempted to add an attachment" }, "fileSizeWarning": { - "message": "Sorry, the selected file exceeds message size restrictions." + "message": "Sorry, the selected attachment is too large" }, "unableToLoadAttachment": { - "message": "Unable to load selected attachment." + "message": "Unable to load attachment." + }, + "connect": { + "message": "Connect" }, "disconnected": { "message": "Disconnected", @@ -663,12 +724,12 @@ "Item under the Help menu, takes you to GitHub new issue form (title case)" }, "signalDesktopPreferences": { - "message": "Loki Messenger Preferences", + "message": "Session Preferences", "description": "Title of the window that pops up with Signal Desktop preferences in it" }, "aboutSignalDesktop": { - "message": "About Loki Messenger", + "message": "About Session", "description": "Item under the Help menu, which opens a small about window" }, "speech": { @@ -688,16 +749,45 @@ "message": "Quit", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { - "message": "Loki Messenger", - "description": "Tooltip for the tray icon" + "lokiMessenger": { + "message": "Session" }, - "searchForPeopleOrGroups": { - "message": "Enter name or public key", + "search": { + "message": "Search", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { - "message": "Welcome to Loki Messenger" + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Conversations", + "description": "Shown to separate the types of search results" + }, + "friendsHeader": { + "message": "Friends", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Contacts", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Session", + "description": "Shown to separate the types of search results" + }, + "settingsHeader": { + "message": "Settings", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { + "message": "Welcome to Session" }, "selectAContact": { "message": "Select a contact or group to start chatting." @@ -756,6 +846,11 @@ "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Downloading", + "description": + "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Download Attachment", "description": @@ -805,7 +900,7 @@ }, "audioPermissionNeeded": { "message": - "To send audio messages, allow Loki Messenger to access your microphone.", + "To send audio messages, allow Session to access your microphone.", "description": "Shown if the user attempts to send an audio message without audio permssions turned on" }, @@ -834,12 +929,97 @@ "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "cannotUpdateDetail": { + "message": + "Signal Desktop failed to update, but there is a new version available. Please go to https://getsession.org/ and install the new version manually, then either contact support or file a bug about this problem.", + "description": + "Shown if a general error happened while trying to install update package" + }, + "readOnlyVolume": { + "message": + "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": + "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK" }, + "enter": { + "message": "Enter" + }, + "yes": { + "message": "Yes" + }, "cancel": { "message": "Cancel" }, + "copy": { + "message": "Copy" + }, + "skip": { + "message": "Skip" + }, + "close": { + "message": "Close" + }, + "continue": { + "message": "Continue" + }, + "noThankyou": { + "message": "No, thank you" + }, + "noMessagesTitle": { + "message": "You don't have any messages, yet." + }, + "noMessagesSubtitle": { + "message": "Would you like to join Session's public chat?" + }, + "pairNewDevice": { + "message": "Pair New Device" + }, + "devicePairingAccepted": { + "message": "Device Pairing Accepted" + }, + "devicePairingReceived": { + "message": "Device Pairing Received" + }, + "waitingForDeviceToRegister": { + "message": "Waiting for device to register..." + }, + "pairNewDevicePrompt": { + "message": "Scan the QR Code on your secondary device" + }, + "pairedDevices": { + "message": "Paired Devices" + }, + "noPairedDevices": { + "message": "No paired devices" + }, + "allowPairing": { + "message": "Allow Pairing" + }, + "allowPairingWithDevice": { + "message": "Allow pairing with this device?" + }, + "provideDeviceAlias": { + "message": "Please provide an alias for this paired device" + }, + "showPairingWordsTitle": { + "message": "Pairing Secret Words" + }, + "confirmUnpairingTitle": { + "message": "Please confirm you want to unpair the following device:" + }, + "unpairDevice": { + "message": "Unpair Device" + }, + "deviceUnpaired": { + "message": "Device Unpaired" + }, "clear": { "message": "Clear" }, @@ -862,10 +1042,31 @@ "delete": { "message": "Delete" }, + "forwardMessage": { + "message": "Forward", + "description": "Text of Forward Message button" + }, + "deletePublicWarning": { + "message": + "Are you sure? Clicking 'delete' will permanently remove this message for everyone in this channel." + }, + "deleteMultiplePublicWarning": { + "message": + "Are you sure? Clicking 'delete' will permanently remove these messages for everyone in this channel." + }, "deleteWarning": { "message": "Are you sure? Clicking 'delete' will permanently remove this message from this device only." }, + "deleteMultipleWarning": { + "message": + "Are you sure? Clicking 'delete' will permanently remove these messages from this device only." + }, + "messageDeletionForbidden": { + "message": "You don’t have permission to delete others’ messages", + "description": + "Toast message explaining that the user doens't have the rights to delete other people's messages." + }, "deleteThisMessage": { "message": "Delete this message" }, @@ -874,7 +1075,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "To", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -886,19 +1087,33 @@ "description": "Label for the time a message was received" }, "sendMessage": { - "message": "Send a message", + "message": " Type your message", "description": "Placeholder text in the message entry field" }, + "secondaryDeviceDefaultFR": { + "message": "Please accept to enable messages to be synced across devices", + "description": + "Placeholder text in the message entry field when it is disabled because a secondary device conversation is visible" + }, + "sendMessageDisabledSecondary": { + "message": + "This pubkey belongs to a secondary device. You should never see this message", + "description": + "Placeholder text in the message entry field when it is disabled because a secondary device conversation is visible" + }, "sendMessageDisabled": { "message": "Waiting for friend request approval", "description": "Placeholder text in the message entry field when it is disabled while we are waiting for a friend request approval" }, "sendMessageFriendRequest": { - "message": "Hi there! This is !", + "message": "Send your first message", "description": "Placeholder text in the message entry field when it is the first message sent to that contact" }, + "sendMessageLeftGroup": { + "message": "You left this group" + }, "groupMembers": { "message": "Group members" }, @@ -906,12 +1121,12 @@ "message": "Show members" }, "resetSession": { - "message": "Reset session", + "message": "Reset Session", "description": "This is a menu item for resetting the session, using the imperative case, as in a command." }, "showSafetyNumber": { - "message": "View safety number" + "message": "View Safety Number" }, "viewAllMedia": { "message": "View all media", @@ -947,17 +1162,50 @@ "description": "Shown on the drop-down menu for an individual message, deletes single message" }, + "messages": { + "message": "Messages", + "description": "Message search result" + }, "deleteMessages": { - "message": "Delete messages", + "message": "Delete Messages", "description": "Menu item for deleting messages, title case." }, + "deletePublicConversationConfirmation": { + "message": + "Permanently delete the messages locally from this public channel?", + "description": + "Confirmation dialog text that asks the user if they really wish to delete the public channel messages locally. Answer buttons use the strings 'ok' and 'cancel'. The deletion is permanent, i.e. it cannot be undone." + }, "deleteConversationConfirmation": { - "message": "Permanently delete this conversation?", + "message": "Permanently delete the messages in this conversation?", "description": "Confirmation dialog text that asks the user if they really wish to delete the conversation. Answer buttons use the strings 'ok' and 'cancel'. The deletion is permanent, i.e. it cannot be undone." }, + "deletePublicChannel": { + "message": "Leave Channel", + "description": + "Confirmation dialog title that asks the user if they really wish to delete a public channel. Answer buttons use the strings 'ok' and 'cancel'. The deletion is permanent, i.e. it cannot be undone." + }, + "deletePublicChannelConfirmation": { + "message": "Leave this Open Group?", + "description": + "Confirmation dialog text that tells the user what will happen if they leave the public channel." + }, + "deleteAccount": { + "message": "Delete Account", + "description": "Text for button in settings view to delete account" + }, + "deleteAccountWarning": { + "message": "Are you sure you want to delete your account?", + "description": "Warning for account deletion in settings view" + }, + "deleteAccountWarningSub": { + "message": + "Delete all history, including all messages, sessions, and contacts. Once deleted, these cannot be restored.", + "description": "Warning for account deletion in settings view" + }, "deleteContact": { - "message": "Delete contact", + "message": "Delete Contact", "description": "Confirmation dialog title that asks the user if they really wish to delete the contact. Answer buttons use the strings 'ok' and 'cancel'. The deletion is permanent, i.e. it cannot be undone." }, @@ -981,6 +1229,19 @@ "description": "This is a past tense, informational message. In other words, your secure session has been reset." }, + "betaDisclaimerTitle": { + "message": "Thanks for using Session!", + "description": "Title for beta disclaimer modal" + }, + "betaDisclaimerSubtitle": { + "message": "This software is still in its beta phase.", + "description": "Subtitle for beta disclaimer modal" + }, + "betaDisclaimerDescription": { + "message": + "While your messages are secured with end-to-end encryption, third parties like your ISP can see who you're talking to while in the beta version. It is also possible that third parties could correlate your public key to your IP address and real identity if they learn your public key.", + "description": "Description for beta disclaimer modal" + }, "quoteThumbnailAlt": { "message": "Thumbnail of image from quoted message", "description": @@ -1030,7 +1291,7 @@ } }, "installWelcome": { - "message": "Welcome to Loki Messenger", + "message": "Welcome to Session", "description": "Welcome title on the install page" }, "installTagline": { @@ -1039,12 +1300,12 @@ "Tagline displayed under 'installWelcome' string on the install page" }, "linkYourPhone": { - "message": "Link your phone to Loki Messenger", + "message": "Link your phone to Session", "description": "Shown on the front page when the application first starst, above the QR code" }, "signalSettings": { - "message": "Loki Messenger Settings", + "message": "Session Settings", "description": "Used in the guidance to help people find the 'link new device' area of their Signal mobile app" }, @@ -1103,36 +1364,45 @@ "message": "Permissions", "description": "Header for permissions section of settings" }, - "mediaPermissionsDescription": { - "message": "Allow access to camera and microphone", - "description": "Description of the media permission description" - }, "general": { "message": "General", "description": "Header for general options on the settings screen" }, - "linkPreviews": { + "linkPreviewsTitle": { "message": "Link Previews", "description": "Option to control creation and send of link previews in setting screen" }, - "linkPreviewsDescription": { + "linkPreviewDescription": { "message": - "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": - "Additional detail provided for Link Previews option in settings screen" + "Link previews supported for Imgur, Instagram, Pinterest, Reddit, and YouTube.", + "description": "Description shown for the Link Preview option " + }, + "linkPreviewsConfirmTitle": { + "message": "Warning" }, - "linkPreviewsSettingDescription": { + "linkPreviewsConfirmMessage": { "message": - "Enable local link previews (Restart for changes to take effect).", - "description": "Description shown for the Link Preview option " + "You will not have full metadata protection when sending or receiving link previews." + }, + + "mediaPermissionsTitle": { + "message": "Microphone and Camera" + }, + "mediaPermissionsDescription": { + "message": "Allow access to camera and microphone", + "description": "Description of the media permission description" + }, + "spellCheckTitle": { + "message": "Spell Check", + "description": "Description of the media permission description" }, "spellCheckDescription": { "message": "Enable spell check of text entered in message composition box", "description": "Description of the media permission description" }, "clearDataHeader": { - "message": "Clear Data", + "message": "Clear All Local Data", "description": "Header in the settings dialog for the section dealing with data deletion" }, @@ -1142,7 +1412,7 @@ "description": "Text describing what the clear data button will do." }, "clearDataButton": { - "message": "Clear data", + "message": "Clear Data", "description": "Button in the settings dialog starting process to delete all data" }, @@ -1170,9 +1440,23 @@ "description": "Header for notification settings" }, "readReceiptSettingDescription": { - "message": "Enable the sending and receiving of read receipts", + "message": + "See and share when messages have been read (enables read receipts in all sessions).", "description": "Description of the read receipts setting" }, + "readReceiptSettingTitle": { + "message": "Read Receipts", + "description": "Title of the read receipts setting" + }, + "typingIndicatorsSettingDescription": { + "message": + "See and share when messages are being typed (applies to all sessions).", + "description": "Description of the typing indicators setting" + }, + "typingIndicatorsSettingTitle": { + "message": "Typing Indicators", + "description": "Title of the typing indicators setting" + }, "messageTTL": { "message": "Message TTL", "description": "Title of the Message TTL setting" @@ -1188,7 +1472,7 @@ "description": "Warning for the time to live setting" }, "notificationSettingsDialog": { - "message": "When messages arrive, display notifications that reveal:", + "message": "When messages arrive, display notifications that reveal...", "description": "Explain the purpose of the notification settings" }, "disableNotifications": { @@ -1196,17 +1480,17 @@ "description": "Label for disabling notifications" }, "nameAndMessage": { - "message": "Both sender name and message", + "message": "Name and content", "description": "Label for setting notifications to display name and message text" }, "noNameOrMessage": { - "message": "Neither name nor message", + "message": "No name or content", "description": "Label for setting notifications to display no name and no message text" }, "nameOnly": { - "message": "Only sender name", + "message": "Name Only", "description": "Label for setting notifications to display sender name only" }, "newMessage": { @@ -1251,15 +1535,15 @@ }, "expiredWarning": { "message": - "This version of Loki Messenger has expired. Please upgrade to the latest version to continue messaging.", + "This version of Session has expired. Please upgrade to the latest version to continue messaging.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { + "clockOutOfSync": { "message": - "Android clients will only receive the first 2000 characters of this message.", + "Your clock is out of sync. Please update your clock and try again.", "description": - "Warning that long messages could not get received completely by Android clients." + "Notifcation that user's clock is out of sync with Loki's servers." }, "upgrade": { "message": "Upgrade", @@ -1370,12 +1654,17 @@ "description": "Timestamp format string for displaying month and day (but not the year) of a date within the current year, ex: use 'MMM D' for 'Aug 8', or 'D MMM' for '8 Aug'." }, + "messageBodyTooLong": { + "message": "Message body is too long.", + "description": "Shown if the user tries to send more than 64kb of text" + }, "unblockToSend": { "message": "Unblock this contact to send a message.", "description": "Brief message shown when trying to message a blocked number" }, "unblockGroupToSend": { - "message": "Unblock this group to send a message.", + "message": + "This group is blocked. Unlock it if you would like to send a message.", "description": "Brief message shown when trying to message a blocked group" }, "youChangedTheTimer": { @@ -1480,7 +1769,7 @@ "description": "Conversation menu option to enable disappearing messages" }, "changeNickname": { - "message": "Change nickname", + "message": "Change Nickname", "description": "Conversation menu option to change user nickname" }, "clearNickname": { @@ -1621,12 +1910,26 @@ "message": "Dark", "description": "Label text for dark theme" }, - "hideMenuBar": { - "message": "Hide menu bar", + "themeToggleTitle": { + "message": "Light Mode" + }, + "themeToggleDescription": { + "message": "Choose the theme best suited to you" + }, + "noteToSelf": { + "message": "Note to Self", + "description": "Name for the conversation with your own phone number" + }, + "hideMenuBarTitle": { + "message": "Hide Menu Bar", + "description": "Label text for menu bar visibility setting" + }, + "hideMenuBarDescription": { + "message": "Toggle system menu bar visibility", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Start conversation…", + "message": "Start New Conversation", "description": "Label underneath number a user enters that is not an existing contact" }, @@ -1691,7 +1994,9 @@ "Shown in the conversation history when someone updates the group" }, "titleIsNow": { - "message": "Title is now '$name$'", + "comment": + "Do not add a period here, used as a fragment and will break unit test", + "message": "Group name has been set to '$name$'", "description": "Shown in the conversation history when someone changes the title of the group", "placeholders": { @@ -1723,6 +2028,28 @@ } } }, + "kickedFromTheGroup": { + "message": "$name$ was removed from the group", + "description": + "Shown in the conversation history when a single person is removed from the group", + "placeholders": { + "name": { + "content": "$1", + "example": "Alice" + } + } + }, + "multipleKickedFromTheGroup": { + "message": "$names$ were removed from the group", + "description": + "Shown in the conversation history when more than one person is removed from the group", + "placeholders": { + "names": { + "content": "$1", + "example": "Alice, Bob" + } + } + }, "friendRequestPending": { "message": "Friend request", "description": @@ -1734,7 +2061,7 @@ "Shown in the conversation history when the user accepts a friend request" }, "friendRequestDeclined": { - "message": "Friend request declined", + "message": "Session request declined", "description": "Shown in the conversation history when the user declines a friend request" }, @@ -1776,10 +2103,10 @@ } }, "blockUser": { - "message": "Block user" + "message": "Block User" }, "unblockUser": { - "message": "Unblock user" + "message": "Unblock User" }, "settingsUnblockHeader": { "message": "Blocked Users", @@ -1794,14 +2121,61 @@ "message": "Note: Your display name will be visible to your contacts", "description": "Shown to the user as a warning about setting display name" }, - "copyPublicKey": { - "message": "Copy public key", + "message": "Copy Public Key", "description": "Button action that the user can click to copy their public keys" }, + "banUser": { + "message": "Ban User", + "description": "Ban user from public chat by public key." + }, + "banUserConfirm": { + "message": "Are you sure you want to ban user?", + "description": "Message shown when confirming user ban." + }, + "userBanned": { + "message": "User banned successfully", + "description": "Toast on succesful user ban." + }, + "userBanFailed": { + "message": "Ban failed!", + "description": "Toast on unsuccesful user ban." + }, + "copyChatId": { + "message": "Copy Chat ID" + }, + "updateGroup": { + "message": "Update Group", + "description": + "Button action that the user can click to rename the group or add a new member" + }, + "leaveGroup": { + "message": "Leave Group", + "description": "Button action that the user can click to leave the group" + }, + "leaveOpenGroup": { + "message": "Leave Open Group", + "description": "Button action that the user can click to leave the group" + }, + "leaveClosedGroup": { + "message": "Leave Closed Group", + "description": "Button action that the user can click to leave the group" + }, + "leaveGroupDialogTitle": { + "message": "Are you sure you want to leave this group?", + "description": + "Title shown to the user to confirm they want to leave the group" + }, + "noContactsForGroup": { + "message": "You don't have any contacts to start a group with." + }, "copiedPublicKey": { - "message": "Copied public key", + "message": "Session ID copied", + "description": "A toast message telling the user that the key was copied" + }, + "copiedChatId": { + "message": "Copied chat ID", "description": "A toast message telling the user that the key was copied" }, "copyMessage": { @@ -1809,36 +2183,143 @@ "description": "Button action that the user can click to copy their public keys" }, + "selectMessage": { + "message": "Select message", + "description": "Button action that the user can click to select the message" + }, "copiedMessage": { - "message": "Copied message text", + "message": "Message text copied", "description": "A toast message telling the user that the message text was copied" }, - "editDisplayName": { - "message": "Edit display name", + "editProfile": { + "message": "Edit Profile", + "description": "Button action that the user can click to edit their profile" + }, + "createGroupDialogTitle": { + "message": "Creating a Private Group Chat", + "description": "Title for the dialog box used to create a new private group" + }, + "updateGroupDialogTitle": { + "message": "Updating a Private Group Chat", "description": - "Button action that the user can click to edit their display name" + "Title for the dialog box used to update an existing private group" + }, + "updatePublicGroupDialogTitle": { + "message": "Updating a Public Chat Channel", + "description": + "Title for the dialog box used to update an existing public chat channel" }, "showSeed": { - "message": "Show seed", + "message": "Show Seed", "description": "Button action that the user can click to view their unique seed" }, - + "yourSessionID": { + "message": "Your Session ID" + }, + "setStatus": { + "message": "Set a status..." + }, + "setAccountPasswordTitle": { + "message": "Set Account Password", + "description": "Prompt for user to set account password in settings view" + }, + "setAccountPasswordDescription": { + "message": + "Require password to unlock Session’s screen. You can still receive message notifications while Screen Lock is enabled. Session’s notification settings allow you to customize information that is displayed", + "description": "Description for set account password setting view" + }, + "changeAccountPasswordTitle": { + "message": "Change Account Password", + "description": "Prompt for user to change account password in settings view" + }, + "changeAccountPasswordDescription": { + "message": "Change your password", + "description": "Description for change account password setting view" + }, + "removeAccountPasswordTitle": { + "message": "Remove Account Password", + "description": "Prompt for user to remove account password in settings view" + }, + "removeAccountPasswordDescription": { + "message": "Remove the password associated with your account", + "description": "Description for remove account password setting view" + }, + "enterPassword": { + "message": "Please enter your password" + }, + "confirmPassword": { + "message": "Confirm password" + }, + "showSeedPasswordRequest": { + "message": "Please enter your password", + "description": "Request for user to enter password to show seed." + }, + "seedSavePromptMain": { + "message": "Please save the seed below in a safe location.", + "description": + "Prompt on seed modal requesting user to save their seed. Line one" + }, + "seedSavePromptAlt": { + "message": + "They can be used to restore your account if you lose access or migrate to a new device.", + "description": + "Prompt on seed modal requesting user to save their seed. Line two" + }, + "QRCodeTitle": { + "message": "View My QR Code", + "description": "Title given to QR Code modal" + }, + "QRCodeDescription": { + "message": + "This is your unique public QR Code.
Other users may scan this in order to begin a conversation with you.", + "description": "Description given to QRCode modal" + }, + "showQRCode": { + "message": "Show QR Code", + "description": "Button action that the user can click to view their QR code" + }, + "showAddServer": { + "message": "Add Public Server", + "description": + "Button action that the user can click to connect to a new public server" + }, + "serverUrl": { + "message": "Server URL", + "description": "Placeholder for server URL input" + }, + "noServerURL": { + "message": "Please enter a server URL", + "description": "Error message when no server url entered" + }, + "addServerDialogTitle": { + "message": "Connect To New Public Server", + "description": + "Title for the dialog box used to connect to a new public server" + }, + "createPrivateGroup": { + "message": "Create Private Group", + "description": + "Button action that the user can click to show a dialog for creating a new private group chat" + }, "seedViewTitle": { "message": "Please save the seed below in a safe location. They can be used to restore your account if you lose access or migrate to a new device.", "description": "The title shown when the user views their seeds" }, - "copiedMnemonic": { - "message": "Copied seed to clipboard", + "message": "Recovery phrase copied successfully", "description": "A toast message telling the user that the mnemonic seed was copied" }, - + "copiedSessionID": { + "message": "Copied Session ID to clipboard", + "description": + "A toast message telling the user that their Session ID was copied" + }, "passwordViewTitle": { - "message": "Type in your password", + "message": "Type In Your Password", "description": "The title shown when user needs to type in a password to unlock the messenger" }, @@ -1850,7 +2331,10 @@ "description": "A button action that the user can click to reset the database" }, - + "password": { + "message": "Password", + "description": "Placeholder for password input" + }, "setPassword": { "message": "Set Password", "description": "Button action that the user can click to set a password" @@ -1863,6 +2347,9 @@ "message": "Remove Password", "description": "Button action that the user can click to remove a password" }, + "maxPasswordAttempts": { + "message": "Invalid Password. Would you like to reset the database?" + }, "typeInOldPassword": { "message": "Please type in your old password" }, @@ -1872,9 +2359,48 @@ "invalidPassword": { "message": "Invalid password" }, + "noGivenPassword": { + "message": "Please enter your password" + }, "passwordsDoNotMatch": { "message": "Passwords do not match" }, + "setPasswordInvalid": { + "message": "Passwords do not match" + }, + "changePasswordInvalid": { + "message": "The old password you entered is incorrect" + }, + "removePasswordInvalid": { + "message": "Incorrect password" + }, + "setPasswordTitle": { + "message": "Set Password" + }, + "changePasswordTitle": { + "message": "Changed Password" + }, + "removePasswordTitle": { + "message": "Removed Password" + }, + "setPasswordToastDescription": { + "message": "Your password has been set. Please keep it safe." + }, + "changePasswordToastDescription": { + "message": "Your password has been changed. Please keep it safe." + }, + "removePasswordToastDescription": { + "message": "You have removed your password." + }, + "publicChatExists": { + "message": "You are already connected to this public channel" + }, + "connectToServerFail": { + "message": "Failed to connect to server. Check URL" + }, + "connectToServerSuccess": { + "message": "Successfully connected to new public chat server" + }, "setPasswordFail": { "message": "Failed to set password" }, @@ -1916,5 +2442,347 @@ }, "remove": { "message": "Remove" + }, + "invalidHexId": { + "message": "Invalid Hex ID", + "description": + "Error string shown when user type an invalid pubkey hex string" + }, + "invalidPubkeyFormat": { + "message": "Invalid Pubkey Format", + "description": "Error string shown when user types an invalid pubkey format" + }, + "conversationsTab": { + "message": "Conversations", + "description": "conversation tab title" + }, + "friendsTab": { + "message": "Friends", + "description": "friend tab title" + }, + "pendingAcceptance": { + "message": "Pending Acceptance", + "description": "Indicates that a friend request is pending" + }, + "notFriends": { + "message": "not friends", + "description": "Indicates that a conversation is not friends with us" + }, + "emptyGroupNameError": { + "message": "Group Name cannot be empty", + "description": "Error message displayed on empty group name" + }, + "emptyProfileNameError": { + "message": "Profile name cannot be empty", + "description": "Error message displayed on empty profile name" + }, + "maxGroupMembersError": { + "message": "Max number of members for small group chats is: " + }, + "nonAdminDeleteMember": { + "message": "Only group admin can remove members!" + }, + "editProfileDialogTitle": { + "message": "Editing Profile" + }, + + "editProfileModalTitle": { + "message": "Profile", + "description": "Title for the Edit Profile modal" + }, + + "profileName": { + "message": "Profile Name" + }, + "groupNamePlaceholder": { + "message": "Group Name" + }, + "inviteFriends": { + "message": "Invite Friends" + }, + "manageModerators": { + "message": "Manage Moderators" + }, + "addModerators": { + "message": "Add Moderators" + }, + "removeModerators": { + "message": "Remove Moderators" + }, + "add": { + "message": "Add" + }, + "groupInvitation": { + "message": "Group Invitation" + }, + "addingFriends": { + "message": "Adding friends to" + }, + "noFriendsToAdd": { + "message": "No friends to add" + }, + "noModeratorsToRemove": { + "message": "no moderators to remove" + }, + "couldNotDecryptMessage": { + "message": "Couldn't decrypt a message" + }, + "confirmSessionRestore": { + "message": + "Would you like to start a new session with $pubkey$? Only do so if you know this pubkey.", + "placeholders": { + "pubkey": { + "content": "$1", + "example": "" + } + } + }, + "createAccount": { + "message": "Create Account" + }, + "signIn": { + "message": "Sign In" + }, + "yourUniqueSessionID": { + "message": "Say hello to your Session ID" + }, + "allUsersAreRandomly...": { + "message": + "Your Session ID is the unique address people can use to contact you on Session. Your Session ID is totally private, anonymous, and has no connection to your real identity." + }, + "getStarted": { + "message": "Get started" + }, + "generateSessionID": { + "message": "Create Session ID" + }, + "mnemonicSeed": { + "message": "Mnemonic Seed" + }, + "enterSeed": { + "message": "Enter Recovery Phrase" + }, + "displayName": { + "message": "Display Name" + }, + "enterDisplayName": { + "message": "Enter a display name" + }, + "optionalPassword": { + "message": "Verify Password" + }, + "enterOptionalPassword": { + "message": "Enter password (optional)" + }, + "verifyPassword": { + "message": "Verify Password" + }, + "devicePairingHeader": { + "message": + "Open Session on your other device and navigate to the Linked Devices section in your user account screen. Select Link a Device to prepare your other device for pairing, then enter your Session ID below to link this device to your Session ID." + }, + "enterSessionIDHere": { + "message": "Enter other device’s Session ID here" + }, + "continueYourSession": { + "message": "Link Device" + }, + "restoreSessionID": { + "message": "Restore Session ID" + }, + "restoreUsingSeed": { + "message": "Restore From Recovery Phrase" + }, + "linkDeviceToExistingAccount": { + "message": "Link Device to Existing Session ID" + }, + "or": { + "message": "or" + }, + "ByUsingThisService...": { + "message": + "By using this service, you agree to our Terms and Conditions and Privacy Statement" + }, + "beginYourSession": { + "message": "Begin
your
Session." + }, + "welcomeToSession": { + "message": "Welcome to Session" + }, + "welcomeToYourSession": { + "message": "Welcome to your Session" + }, + "completeSignUp": { + "message": "Complete Sign Up" + }, + "compose": { + "message": "Compose" + }, + "newSession": { + "message": "New Session" + }, + "searchForAKeyPhrase": { + "message": "Search for a key phrase or contact" + }, + "enterRecipient": { + "message": "Enter Recipient" + }, + "enterSessionID": { + "message": "Enter Session ID" + }, + "pasteSessionIDRecipient": { + "message": "Enter a Session ID" + }, + "usersCanShareTheir...": { + "message": + "Users can share their Session ID from their account settings, or by sharing their QR code." + }, + "searchByIDOrDisplayName": { + "message": "Search by ID # or Display Name" + }, + "message": { + "message": "Message" + }, + "lists": { + "message": "Lists" + }, + "edit": { + "message": "Edit" + }, + "addContact": { + "message": "Add Contact" + }, + "createGroup": { + "message": "Create Group" + }, + "yourPublicKey": { + "message": "Your Session ID" + }, + "accept": { + "message": "Accept" + }, + "decline": { + "message": "Decline" + }, + "appearanceSettingsTitle": { + "message": "Appearance" + }, + "appearanceSettingsDescription": { + "message": "Appearance and interface options" + }, + "accountSettingsTitle": { + "message": "Account" + }, + "accountSettingsDescription": { + "message": "Manage your account" + }, + "permissionSettingsTitle": { + "message": "Permissions" + }, + "permissionSettingsDescription": { + "message": "Set Session's permissions" + }, + "privacySettingsTitle": { + "message": "Privacy" + }, + "privacySettingsDescription": { + "message": "Manage your privacy settings" + }, + "notificationSettingsTitle": { + "message": "Notifications" + }, + "notificationSettingsDescription": { + "message": "Configure notification options" + }, + "devicesSettingsTitle": { + "message": "Devices" + }, + "devicesSettingsDescription": { + "message": "Manage your linked devices" + }, + "mnemonicEmpty": { + "message": "Seed is mandatory" + }, + "displayNameEmpty": { + "message": "Display Name Is Mandatory" + }, + "youHaveFriendRequestFrom": { + "message": "You have friend requests from..." + }, + "members": { + "message": "$count$ members", + "placeholders": { + "count": { + "content": "$1", + "example": "26" + } + } + }, + "channels": { + "message": "Channels" + }, + "groups": { + "message": "Groups" + }, + "addChannel": { + "message": "Join Open Group" + }, + "joinOpenGroup": { + "message": "Join Open Group" + }, + "newClosedGroup": { + "message": "New Closed Group" + }, + "createClosedGroup": { + "message": "Create Closed Group" + }, + "createClosedGroupDescription": { + "message": + "Closed groups are end-to-end encrypted group chats for up to 10 members. They provide the same privacy protections as one-on-one sessions." + }, + "createClosedGroupNamePrompt": { + "message": "Group Name" + }, + "createClosedGroupPlaceholder": { + "message": "Enter a group name" + }, + "closedGroupCreatedToastTitle": { + "message": "Group created successfully" + }, + "enterChannelURL": { + "message": "Enter Open Group URL" + }, + "channelUrlPlaceholder": { + "message": "chat.getsession.org" + }, + "addChannelDescription": { + "message": "Enter an open group URL." + }, + "joinChannel": { + "message": "Join Open Group" + }, + "joinPublicChat": { + "message": "Join Public Chat" + }, + "next": { + "message": "Next" + }, + "description": { + "message": "Description" + }, + "filterReceivedRequests": { + "message": "Filter received requests" + }, + "secretWords": { + "message": "Secret words:" + }, + "pairingDevice": { + "message": "Pairing Device" + }, + "gotPairingRequest": { + "message": "Pairing request received" + }, + "devicePairedSuccessfully": { + "message": "Device linked successfully" } } diff --git a/_locales/eo/messages.json b/_locales/eo/messages.json index 1fba6cb58d..5c8d79e3f2 100644 --- a/_locales/eo/messages.json +++ b/_locales/eo/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Kopii la eraron kaj eliri", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Nekonata grupo", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Datumbaza eraro", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Forigi ĉiujn datumojn kaj restarti", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Dosiero", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -60,7 +76,7 @@ "description": "Edit menu command to insert text from clipboard at cursor location" }, "editMenuPasteAndMatchStyle": { - "message": "Alglui kaj akordigi je stilo", + "message": "Alglui nur tekston", "description": "Edit menu command to insert text from clipboard at cursor location, taking only text and not style information" }, "editMenuDelete": { @@ -108,35 +124,35 @@ "description": "View menu command to make everything smaller" }, "viewMenuToggleFullScreen": { - "message": "Ŝalti/malŝalti plenekranan reĝimon", + "message": "Baskuligi plenekranan reĝimon", "description": "View menu command to enter or leave Full Screen mode" }, "viewMenuToggleDevTools": { - "message": "Ŝalti/malŝalti programistajn ilojn", + "message": "Baskuligi programistajn ilojn", "description": "View menu command to show or hide the developer tools" }, "menuSetupWithImport": { - "message": "Set Up with Import", + "message": "Agordi kun importo", "description": "When the application is not yet set up, menu option to start up the import sequence" }, "menuSetupAsNewDevice": { - "message": "Set Up as New Device", + "message": "Agordi kiel novan aparaton", "description": "When the application is not yet set up, menu option to start up the set up as fresh device" }, "menuSetupAsStandalone": { - "message": "Set Up as Standalone Device", + "message": "Agordi kiel memstaran aparaton", "description": "Only available on development modes, menu option to open up the standalone device setup sequence" }, "loading": { - "message": "Ŝarĝante…", + "message": "Ŝargante…", "description": "Message shown on the loading screen before we've loaded any messages" }, "optimizingApplication": { - "message": "Optimizing application...", + "message": "Optimumigo de la aplikaĵo...", "description": "Message shown on the loading screen while we are doing application optimizations" }, "migratingToSQLCipher": { - "message": "Optimizing messages... $status$ complete.", + "message": "Optimumigo de la mesaĝoj... $status$ plenumitaj.", "description": "Message shown on the loading screen while we are doing application optimizations", "placeholders": { "status": { @@ -145,44 +161,60 @@ } } }, + "archivedConversations": { + "message": "Enarĥivigitaj interparoloj", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Tiuj interparoloj estas enarĥivigitaj kaj aperos en la ricevujo, nur se novaj mesaĝoj riceviĝas.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Enarĥivigi interparolon", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Movi interparolon al la ricevujo", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { - "message": "Elekti dosierejon", + "message": "Elekti dosierujon", "description": "Button to allow the user to find a folder on disk" }, "chooseFile": { - "message": "Choose file", + "message": "Elekti dosieron", "description": "Button to allow the user to find a file on disk" }, "loadDataHeader": { - "message": "Ŝarĝu viajn datumojn", + "message": "Ŝargu viajn datumojn", "description": "Header shown on the first screen in the data import process" }, "loadDataDescription": { - "message": "You've just gone through the export process, and your contacts and messages are waiting patiently on your computer. Select the folder that contains your saved Signal data.", + "message": "Vi ĵus plenumis la eksportan procezon, kaj viaj kontaktoj kaj mesaĝoj atendas pacience ĉe via komputilo. Elektu la dosierujon, kiu enhavas viajn konservitajn Signal-datumojn.", "description": "Introduction to the process of importing messages and contacts from disk" }, "importChooserTitle": { - "message": "Elektu dosierejon je eksportitaj datumoj", + "message": "Elektu dosierujon kun eksportitaj datumoj", "description": "Title of the popup window used to select data previously exported" }, "importErrorHeader": { - "message": "Ho ve! Io eraris!", + "message": "Ho ve! Okazis eraro!", "description": "Header of the error screen after a failed import" }, "importingHeader": { - "message": "Ŝarĝante kontaktojn kaj mesaĝojn", + "message": "Ŝargante kontaktojn kaj mesaĝojn", "description": "Header of screen shown as data is import" }, "importErrorFirst": { - "message": "Make sure you have chosen the correct directory that contains your saved Signal data. Its name should begin with 'Signal Export.' You can also save a new copy of your data from the Chrome App.", + "message": "Certigu, ke vi elektis la ĝustan dosierujon, kiu enhavas viajn konservitajn Signal-datumojn. Ties nomo komencu per „Signal Export“. Vi ankaŭ povas konservi novan kopion el viaj datumoj per la aplikaĵo Chrome.", "description": "Message shown if the import went wrong; first paragraph" }, "importErrorSecond": { - "message": "If these steps don't work for you, please submit a debug log (View -> Debug Log) so that we can help you get migrated!", + "message": "Se tiuj paŝoj ne funkcias, bv. sendi sencimigan protokolon (Vidi → Sencimiga protokolo), por ke ni helpu vin kun la transirado!", "description": "Message shown if the import went wrong; second paragraph" }, "importAgain": { - "message": "Elektu dosierejon kaj klopodu denove", + "message": "Elektu dosierujon kaj reprovu", "description": "Button shown if the user runs into an error during import, allowing them to start over" }, "importCompleteHeader": { @@ -202,11 +234,11 @@ "description": "Message shown as the export location if we didn't capture the target directory" }, "upgradingDatabase": { - "message": "Upgrading database. This may take some time...", + "message": "Ĝisdatigo de la datumbazo. Tio povas daŭri...", "description": "Message shown on the loading screen when we're changing database structure on first run of a new version" }, "loadingMessages": { - "message": "Ŝarĝante mesaĝojn. Jam $count$…", + "message": "Ŝargante mesaĝojn. $count$ ĝis nun…", "description": "Message shown on the loading screen when we're catching up on the backlog of messages", "placeholders": { "count": { @@ -228,11 +260,11 @@ "description": "Displayed when a user can't send a message because they have left the group" }, "scrollDown": { - "message": "Scroll to bottom of conversation", + "message": "Rulumi al interparola malsupro", "description": "Alt text for button to take user down to bottom of conversation, shown when user scrolls up" }, "messageBelow": { - "message": "Novaj mesaĝo suben", + "message": "Nova mesaĝo suben", "description": "Alt text for button to take user down to bottom of conversation with a new message out of screen" }, "messagesBelow": { @@ -244,7 +276,7 @@ "description": "Text for unread message separator, just one message" }, "unreadMessages": { - "message": "$count$ nelegata mesaĝoj", + "message": "$count$ nelegataj mesaĝoj", "description": "Text for unread message separator, with count", "placeholders": { "count": { @@ -254,7 +286,7 @@ } }, "youMarkedAsVerified": { - "message": "Vi markis la sekurnumero de $name$ kontrolita.", + "message": "Vi markis vian sekurigan numeron kun $name$ konfirmita.", "description": "Shown in the conversation history when the user marks a contact as verified.", "placeholders": { "name": { @@ -264,7 +296,7 @@ } }, "youMarkedAsNotVerified": { - "message": "Vi markis la sekurnumero de $name$ nekontrolita.", + "message": "Vi markis vian sekurigan numeron kun $name$ kiel nekonfirmita.", "description": "Shown in the conversation history when the user marks a contact as not verified, whether on the Safety Number screen or by dismissing a banner or dialog.", "placeholders": { "name": { @@ -274,7 +306,7 @@ } }, "youMarkedAsVerifiedOtherDevice": { - "message": "Vi markis la sekurnumero de $name$ kontrolita de alia aparato.", + "message": "Vi markis, el alia aparato, vian sekurigan numeron kun $name$ kiel konfirmita.", "description": "Shown in the conversation history when we discover that the user marked a contact as verified on another device.", "placeholders": { "name": { @@ -284,7 +316,7 @@ } }, "youMarkedAsNotVerifiedOtherDevice": { - "message": "Vi markis la sekurnumero de $name$ nekontrolita de alia aparato.", + "message": "Vi markis, el alia aparato, vian sekurigan numeron kun $name$ kiel nekonfirmita.", "description": "Shown in the conversation history when we discover that the user marked a contact as not verified on another device.", "placeholders": { "name": { @@ -294,15 +326,15 @@ } }, "membersNeedingVerification": { - "message": "Your safety numbers with these group members have changed since you last verified. Click a group member to see your new safety number with them.", + "message": "Viaj sekurigaj numeroj kun tiuj grupanoj ŝanĝigis de post via lasta kontrolo. Alklaku grupanon por vidi la novan sekurigan numeron kun li aŭ ŝi.", "description": "When there are multiple previously-verified group members with safety number changes, a banner will be shown. The list of contacts with safety number changes is shown, and this text introduces that list." }, "changedSinceVerifiedMultiple": { - "message": "Your safety numbers with multiple group members have changed since you last verified. This could mean that someone is trying to intercept your communication or that they have simply reinstalled Signal.", + "message": "Viaj sekurigaj numeroj kun pluraj grupanoj ŝanĝiĝis de post via lasta kontrolo. Tio povas aŭ signifi, ke iu provas interkapti viajn komunikojn, aŭ ke ili simple reinstalis Signal-on.", "description": "Shown on confirmation dialog when user attempts to send a message" }, "changedSinceVerified": { - "message": "La sekurnumero de $name$ ŝanĝigis post kiam vi laste kontrolis ĝin. Tio eble signifas, ke oni klopodas forkapti viajn komunikaĵojn aŭ $name$ nur denove instalis Signal-on.", + "message": "La sekuriga numero de $name$ ŝanĝiĝis post kiam vi laste kontrolis ĝin. Tio eble signifas, ke oni klopodas forkapti viajn komunikaĵojn, aŭ ke $name$ nur denove instalis Signal-on.", "description": "Shown on confirmation dialog when user attempts to send a message", "placeholders": { "name": { @@ -312,7 +344,7 @@ } }, "changedRightAfterVerify": { - "message": "The safety number you are trying to verify has changed. Please review your new safety number with $name$. Remember, this change could mean that someone is trying to intercept your communication or that $name$ has simply reinstalled Signal.", + "message": "La sekuriga numero, kiun vi kontrolas, ŝanĝiĝis. Bv. kontroli vian sekurigan numeron kun $name$. Atentu, ke tiu ŝanĝo eble signifas, ke iu klopodas forkapti viajn komunikojn, aŭ ke $name$ simple re-instalis Signal-on.", "description": "Shown on the safety number screen when the user has selected to verify/unverify a contact's safety number, and we immediately discover a safety number change", "placeholders": { "name": { @@ -322,11 +354,11 @@ } }, "changedRecentlyMultiple": { - "message": "Your safety numbers with multiple group members have changed recently. This could mean that someone is trying to intercept your communication or that they have simply reinstalled Signal.", + "message": "Viaj sekurigaj numeroj kun pluraj grupanoj ŝanĝiĝis antaŭnelonge. Tio povas aŭ signifi, ke iu provas interkapti viajn komunikojn, aŭ ke ili simple reinstalis Signal-on.", "description": "Shown on confirmation dialog when user attempts to send a message" }, "changedRecently": { - "message": "Your safety number with $name$ has changed recently. This could mean that someone is trying to intercept your communication or that $name$ has simply reinstalled Signal.", + "message": "Via sekuriga numero kun $name$ ŝanĝiĝis. Tio povas aŭ signifi, ke iu provas interkapti viajn komunikojn, aŭ ke $name$ simple reinstalis Signal-on.", "description": "Shown on confirmation dialog when user attempts to send a message", "placeholders": { "name": { @@ -336,7 +368,7 @@ } }, "identityKeyErrorOnSend": { - "message": "Your safety number with $name$ has changed. This could either mean that someone is trying to intercept your communication or that $name$ has simply reinstalled Signal. You may wish to verify your saftey number with this contact.", + "message": "Via sekuriga numero kun $name$ ŝanĝiĝis. Tio povas aŭ signifi, ke iu provas interkapti viajn komunikojn, aŭ ke $name$ simple reinstalis Signal-on. Vi certe volos kontroli la sekurigan numeron kun tiu ĉi kontakto.", "description": "Shown when user clicks on a failed recipient in the message detail view after an identity key change", "placeholders": { "name": { @@ -346,11 +378,11 @@ } }, "sendAnyway": { - "message": "Ĉu ankoraŭ sendu", + "message": "Tamen sendi", "description": "Used on a warning dialog to make it clear that it might be risky to send the message." }, "noLongerVerified": { - "message": "Your safety number with $name$ has changed and is no longer verified. Click to show.", + "message": "Via sekuriga numero kun $name$ ŝanĝiĝis kaj ne plu estas konfirmita. Alklaku por montri ĝin.", "description": "Shown in converation banner when user's safety number has changed, but they were previously verified.", "placeholders": { "name": { @@ -360,15 +392,15 @@ } }, "multipleNoLongerVerified": { - "message": "Your safety numbers with multiple members of this group have changed and are no longer verified. Click to show.", + "message": "Viaj sekurigaj numeroj kun pluraj anoj el tiu grupo ŝanĝiĝis kaj ne plu estas konfirmita. Alklaku por montri ilin.", "description": "Shown in conversation banner when more than one group member's safety number has changed, but they were previously verified." }, "debugLogExplanation": { - "message": "This log will be posted publicly online for contributors to view. You may examine and edit it before submitting.", + "message": "Tiu protokolo estos afiŝita publike en la reto, por ke kontribuantoj vidu ĝin; vi povas revizii ĝin antaŭ ol sendi ĝin.", "description": "" }, "debugLogError": { - "message": "Something went wrong with the upload! Please consider manually adding your log to the bug you file.", + "message": "Eraro dum la alŝuto! Bv. konsideri mane aldoni la protokolo, al problemo, kiun vi raportas.", "description": "" }, "reportIssue": { @@ -380,7 +412,7 @@ "description": "Label for a button that dismisses a dialog. The user clicks it to confirm that they understand the message in the dialog." }, "submit": { - "message": "Proponi", + "message": "Sendi", "description": "" }, "acceptNewKey": { @@ -388,15 +420,15 @@ "description": "Label for a button to accept a new safety number" }, "verify": { - "message": "Marki kontrolita", + "message": "Marki konfirmita", "description": "" }, "unverify": { - "message": "Marki nekontrolita", + "message": "Marki nekonfirmita", "description": "" }, "isVerified": { - "message": "Vi jam kontrolis la sekurnumeron de $name$.", + "message": "Vi jam konfirmis la sekurigan numeron kun $name$.", "description": "Summary state shown at top of the safety number screen if user has verified contact.", "placeholders": { "name": { @@ -406,7 +438,7 @@ } }, "isNotVerified": { - "message": "Vi ne jam kontrolis la sekurnumeron de $name$.", + "message": "Vi ankoraŭ ne konfirmis la sekurigan numeron kun $name$.", "description": "Summary state shown at top of the safety number screen if user has not verified contact.", "placeholders": { "name": { @@ -416,27 +448,27 @@ } }, "verified": { - "message": "Kontrolita", + "message": "Konfirmita", "description": "" }, "newIdentity": { - "message": "Nova sekurnumero", + "message": "Nova sekuriga numero", "description": "Header for a key change dialog" }, "identityChanged": { - "message": "Your safety number with this contact has changed. This could either mean that someone is trying to intercept your communication, or this contact simply reinstalled Signal. You may wish to verify the new safety number below.", + "message": "Via sekuriga numero kun tiu ĉi kontakto ŝanĝiĝis. Tio povas aŭ signifi, ke iu provas interkapti viajn komunikojn, aŭ ke tiu kontakto simple reinstalis Signal-on. Vi certe volos kontroli la novan sekurigan numeron ĉi-suban.", "description": "" }, "incomingError": { - "message": "Okazis eraron ricevante mesaĝon", + "message": "Okazis eraro dum ricevo de mesaĝo", "description": "" }, "media": { - "message": "Enmetitaĵoj", + "message": "Aŭdvidaĵo", "description": "Header of the default pane in the media gallery, showing images and videos" }, "mediaEmptyState": { - "message": "You don’t have any media in this conversation", + "message": "Vi havas neniun aŭdvidaĵon en tiu interparolo", "description": "Message shown to user in the media gallery when there are no messages with media attachments (images or video)" }, "documents": { @@ -444,7 +476,7 @@ "description": "Header of the secondary pane in the media gallery, showing every non-media attachment" }, "documentsEmptyState": { - "message": "You don’t have any documents in this conversation", + "message": "Vi havas neniun dokumenton en tiu interparolo", "description": "Message shown to user in the media gallery when there are no messages with document attachments (anything other than images or video)" }, "today": { @@ -456,15 +488,15 @@ "description": "Section header in the media gallery" }, "thisWeek": { - "message": "Ĉi-semajnon", + "message": "Ĉisemajne", "description": "Section header in the media gallery" }, "thisMonth": { - "message": "Ĉi-monaton", + "message": "Ĉimonate", "description": "Section header in the media gallery" }, "unsupportedAttachment": { - "message": "Unsupported attachment type. Click to save.", + "message": "Nesubtenata tipo de kunsendaĵo. Alklaku por konservi ĝin.", "description": "Displayed for incoming unsupported attachment" }, "clickToSave": { @@ -472,23 +504,23 @@ "description": "Hover text for attachment filenames" }, "unnamedFile": { - "message": "Nenomita dosiero", + "message": "Sennoma dosiero", "description": "Hover text for attachment filenames" }, "voiceMessage": { - "message": "Voĉmesaĝo", + "message": "Voĉa mesaĝo", "description": "Name for a voice message attachment" }, "dangerousFileType": { - "message": "Attachment type not allowed for security reasons", + "message": "Kunsendaĵa tipo ne permesata por sekurecaj kialoj", "description": "Shown in toast when user attempts to send .exe file, for example" }, "loadingPreview": { - "message": "Loading Preview...", + "message": "Ŝargante antaŭrigardon...", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area" }, "stagedPreviewThumbnail": { - "message": "Draft thumbnail link preview for $domain$", + "message": "Preparante miniaturan antaŭrigardon de ligilo pri $domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -498,7 +530,7 @@ } }, "previewThumbnail": { - "message": "Thumbnail link preview for $domain$", + "message": "Miniatura antaŭrigardo de ligilo pri $domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -508,7 +540,7 @@ } }, "stagedImageAttachment": { - "message": "Draft image attachment: $path$", + "message": "Preparante bildan kunsendaĵon: $path$", "description": "Alt text for staged attachments", "placeholders": { "path": { @@ -518,23 +550,23 @@ } }, "oneNonImageAtATimeToast": { - "message": "When including a non-image attachment, the limit is one attachment per message.", + "message": "Kiam oni enmetas nebildan kunsendaĵon, limo estas po unu kunsendaĵo mesaĝe.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { - "message": "You cannot mix non-image and image attachments in one message.", + "cannotMixImageAndNonImageAttachments": { + "message": "Vi ne povas kunmeti nebildajn kaj bildajn kunsendaĵojn en unu mesaĝo.", "description": "An error popup when the user has attempted to add an attachment" }, "maximumAttachments": { - "message": "You cannot add any more attachments to this message.", + "message": "Ne plu eblas aldoni kunsendaĵojn al tiu mesaĝo.", "description": "An error popup when the user has attempted to add an attachment" }, "fileSizeWarning": { - "message": "Bedaŭrinde, la elektita dosiero tro grandas.", + "message": "Bedaŭrinde, la elektita dosiero transpasas limigon pri grandeco de mesaĝo.", "description": "" }, "unableToLoadAttachment": { - "message": "Unable to load selected attachment.", + "message": "Ne eblas ŝargi la elektitan kunsendaĵon.", "description": "" }, "disconnected": { @@ -542,7 +574,7 @@ "description": "Displayed when the desktop client cannot connect to the server." }, "connecting": { - "message": "Konektigante", + "message": "Konektante", "description": "Displayed when the desktop client is currently connecting to the server." }, "offline": { @@ -550,11 +582,11 @@ "description": "Displayed when the desktop client has no network connection." }, "checkNetworkConnection": { - "message": "Check your network connection.", + "message": "Kontrolu vian retkonekton.", "description": "Obvious instructions for when a user's computer loses its network connection" }, "attemptingReconnection": { - "message": "Attempting reconnect in $reconnect_duration_in_seconds$ seconds", + "message": "Provo rekonekti post $reconnect_duration_in_seconds$ sekundoj", "description": "", "placeholders": { "reconnect_duration_in_seconds": { @@ -611,16 +643,38 @@ "message": "Fini", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Entajpu nomon aŭ numeron", + "search": { + "message": "Serĉi", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { - "message": "Bonvenu al Signal", + "noSearchResults": { + "message": "Neniu rezulto pri „$searchTerm$“", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Interparoloj", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Kontaktaro", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Mesaĝoj", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { + "message": "Bonvenon al Signal", "description": "" }, "selectAContact": { @@ -628,11 +682,11 @@ "description": "" }, "typingAlt": { - "message": "Typing animation for this conversation", + "message": "Tajp-indikiloj por tiu interparolo", "description": "Used as the 'title' attibute for the typing animation" }, "contactAvatarAlt": { - "message": "Avatar for contact $name$", + "message": "Avataro el kontakto $name$", "description": "Used in the alt tag for the image avatar of a contact", "placeholders": { "name": { @@ -670,15 +724,19 @@ "description": "Generic label shown if contact address has custom type but no label" }, "poBox": { - "message": "poŝtoficeja skatalo", + "message": "abonkesto", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Elŝutado", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { - "message": "Download Attachment", + "message": "Elŝuti kunsendaĵon", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" }, "replyToMessage": { - "message": "Respondi al la mesaĝon", + "message": "Respondi al la mesaĝo", "description": "Shown in triple-dot menu next to message to allow user to start crafting a message with a quotation" }, "originalMessageNotFound": { @@ -690,11 +748,11 @@ "description": "Shown in toast if user clicks on quote that references message no longer in database" }, "messageFoundButNotLoaded": { - "message": "Original message found, but not loaded. Scroll up to load it.", + "message": "Origina mesaĝo trovebla sed ne ŝargita. Rulumu supren por ŝargi ĝin.", "description": "Shown in toast if user clicks on quote references messages not loaded in view, but in database" }, "voiceNoteMustBeOnlyAttachment": { - "message": "A voice note must be the only attachment included in a message.", + "message": "Voĉa noto estu la sola kunsendaĵo en mesaĝo.", "description": "Shown in toast if tries to record a voice note with any staged attachments" }, "you": { @@ -702,7 +760,7 @@ "description": "In Android theme, shown in quote if you or someone else replies to you" }, "replyingTo": { - "message": "Respondante al $name$", + "message": "Respondo al $name$", "description": "Shown in iOS theme when you or someone quotes to a message which is not from you", "placeholders": { "name": { @@ -712,11 +770,11 @@ } }, "audioPermissionNeeded": { - "message": "Por sendi sonmesaĝojn, permesu Signal Desktop-on uzi la mikrofonon.", + "message": "Por sendi aŭdajn mesaĝojn, donu al Signal Desktop permeson uzi vian mikrofonon.", "description": "Shown if the user attempts to send an audio message without audio permssions turned on" }, "allowAccess": { - "message": "Permisi", + "message": "Permesi", "description": "Button shown in popup asking to enable microphon/video permissions to send audio messages" }, "showSettings": { @@ -728,13 +786,21 @@ "description": "Shown in a quotation of a message containing an audio attachment if no text was originally provided with that attachment" }, "video": { - "message": "Filmeto", + "message": "Videaĵo", "description": "Shown in a quotation of a message containing a video if no text was originally provided with that video" }, "photo": { "message": "Foto", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "Bone", "description": "" @@ -744,7 +810,7 @@ "description": "" }, "failedToSend": { - "message": "Malsukcesis sendi al iom da ricevontoj. Kontrolu la retkonekton.", + "message": "Malsukcesis sendi al iuj ricevontoj. Kontrolu la retkonekton.", "description": "" }, "error": { @@ -760,7 +826,7 @@ "description": "" }, "deleteWarning": { - "message": "Ĉu vi certas? Se vi alklakus 'forigi', la mesaĝo neŝanĝeble forigos nur de ĉi tiu aparato.", + "message": "Ĉu vi pricertas? Per alklako de „Forigi“, tiu mesaĝo porĉiame foriĝos nur de ĉi tiu aparato.", "description": "" }, "deleteThisMessage": { @@ -768,11 +834,11 @@ "description": "" }, "from": { - "message": "De", + "message": "El", "description": "Label for the sender of a message" }, "to": { - "message": "Al", + "message": "al", "description": "Label for the receiver of a message" }, "sent": { @@ -784,7 +850,7 @@ "description": "Label for the time a message was received" }, "sendMessage": { - "message": "Sendu mesaĝon", + "message": "Sendi mesaĝon", "description": "Placeholder text in the message entry field" }, "groupMembers": { @@ -796,19 +862,19 @@ "description": "" }, "resetSession": { - "message": "Forviŝi sesion", + "message": "Forviŝi seancon", "description": "This is a menu item for resetting the session, using the imperative case, as in a command." }, "showSafetyNumber": { - "message": "Montri sekurnumeron", + "message": "Montri sekurigan numeron", "description": "" }, "viewAllMedia": { - "message": "Montri ĉiujn enmetaĵojn", + "message": "Montru ĉiujn aŭdvidaĵojn", "description": "This is a menu item for viewing all media (images + video) in a conversation, using the imperative case, as in a command." }, "verifyHelp": { - "message": "If you wish to verify the security of your end-to-end encryption with $name$, compare the numbers above with the numbers on their device.", + "message": "Se vi volas kontroli la sekurecon de via tutvoja ĉifrado kun $name$, komparu la ĉi-superajn nombrojn kun la nombroj el lia aŭ ŝia aparato.", "description": "", "placeholders": { "name": { @@ -818,15 +884,15 @@ } }, "theirIdentityUnknown": { - "message": "Vi ne jam interŝanĝis mesaĝojn de tiu ĉi kontakto. La sekurnumero de tiu disponeblos post la unua mesaĝo.", + "message": "Vi ankoraŭ ne interŝanĝis mesaĝojn kun tiu ĉi kontakto. La sekuriga numero nur disponeblos post la unua mesaĝo.", "description": "" }, "moreInfo": { - "message": "Pli da inforomoj…", + "message": "Pli da informoj…", "description": "Shown on the drop-down menu for an individual message, takes you to message detail screen" }, "retrySend": { - "message": "Klopodi denove sendi", + "message": "Klopodi resendi", "description": "Shown on the drop-down menu for an indinvidaul message, but only if it is an outgoing message that failed to send" }, "deleteMessage": { @@ -838,35 +904,35 @@ "description": "Menu item for deleting messages, title case." }, "deleteConversationConfirmation": { - "message": "Senŝanĝeble forigi tiun ĉi konversacion?", + "message": "Ĉu porĉiame forigi tiun ĉi tutan interparolon?", "description": "Confirmation dialog text that asks the user if they really wish to delete the conversation. Answer buttons use the strings 'ok' and 'cancel'. The deletion is permanent, i.e. it cannot be undone." }, "sessionEnded": { - "message": "Oni forviŝis la sekura sesio", + "message": "Sekura seanco restariĝis", "description": "This is a past tense, informational message. In other words, your secure session has been reset." }, "quoteThumbnailAlt": { - "message": "Thumbnail of image from quoted message", + "message": "Bildominiaturo el citita mesaĝo", "description": "Used in alt tag of thumbnail images inside of an embedded message quote" }, "imageAttachmentAlt": { - "message": "Enmetigita bildo", + "message": "Bildo kunsendita kun la mesaĝo", "description": "Used in alt tag of image attachment" }, "videoAttachmentAlt": { - "message": "Enmetigita ekrankopio de filmeto", + "message": "Ekrankopio de videaĵo kunsendita kun la mesaĝo", "description": "Used in alt tag of video attachment preview" }, "lightboxImageAlt": { - "message": "Sendita bildo", + "message": "Bildo sendita en interparolo", "description": "Used in the alt tag for the image shown in a full-screen lightbox view" }, "imageCaptionIconAlt": { - "message": "Icon showing that this image has a caption", + "message": "Piktogramo montranta, ke tiu bildo havas priskribon", "description": "Used for the icon layered on top of an image in message bubbles" }, "addACaption": { - "message": "Aldoni klarigon...", + "message": "Aldoni priskribon...", "description": "" }, "save": { @@ -874,11 +940,11 @@ "description": "" }, "fileIconAlt": { - "message": "File icon", + "message": "Dosierpiktogramo", "description": "Used in the media gallery documents tab to visually represent a file" }, "emojiAlt": { - "message": "Emoji image of '$title$'", + "message": "Emoĝibildo de „$title$“", "description": "Used in the alt tag of all emoji images", "placeholders": { "title": { @@ -888,11 +954,11 @@ } }, "installWelcome": { - "message": "Bonvenu al Signal Desktop", + "message": "Bonvenon al Signal Desktop", "description": "Welcome title on the install page" }, "installTagline": { - "message": "Privateco eblas. Signal faras tion facila.", + "message": "Privateco eblas. Signal tion faciligas.", "description": "Tagline displayed under 'installWelcome' string on the install page" }, "linkYourPhone": { @@ -908,7 +974,7 @@ "description": "Used in the guidance to help people find the 'link new device' area of their Signal mobile app" }, "plusButton": { - "message": "'+' butono", + "message": "Butono „+“", "description": "The button used in Signal Android to add a new linked device" }, "linkNewDevice": { @@ -928,7 +994,7 @@ "description": "The text on the button to finish the linking process, after choosing the device name" }, "initialSync": { - "message": "Akordigante kontaktojn kaj grupojn", + "message": "Sinkronigo de kontaktoj kaj grupoj", "description": "Shown during initial link while contacts and groups are being pulled from mobile device" }, "installConnectionFailed": { @@ -936,7 +1002,7 @@ "description": "Displayed when we can't connect to the server." }, "installTooManyDevices": { - "message": "Sorry, you have too many devices linked already. Try removing some.", + "message": "Bedaŭrinde, vi havas tro da ligitaj aparatoj. Provu malligi kelkajn.", "description": "" }, "settings": { @@ -944,31 +1010,23 @@ "description": "Menu item and header for global settings" }, "theme": { - "message": "Theme", + "message": "Etoso", "description": "Header for theme settings" }, "permissions": { - "message": "Permisoj", + "message": "Permesoj", "description": "Header for permissions section of settings" }, "mediaPermissionsDescription": { - "message": "Allow access to camera and microphone", + "message": "Permesi aliron al la fotilo kaj la mikrofono", "description": "Description of the media permission description" }, "general": { - "message": "General", + "message": "Ĝenerala", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { - "message": "Ŝalti literumkontrolo de teksto entajpita en mesaĝa verkejo", + "message": "Ŝalti literumilon de teksto entajpita en mesaĝa verkejo", "description": "Description of the media permission description" }, "clearDataHeader": { @@ -976,7 +1034,7 @@ "description": "Header in the settings dialog for the section dealing with data deletion" }, "clearDataExplanation": { - "message": "Tiun ĉi forviŝus ĉiujn datumojn en la programo, forigante ĉiujn mesaĝojn kaj konservitajn kontinformojn", + "message": "Tio forviŝos ĉiujn datumojn en la programo, forigante ĉiujn mesaĝojn kaj konservitajn kontinformojn.", "description": "Text describing what the clear data button will do." }, "clearDataButton": { @@ -988,7 +1046,7 @@ "description": "Header of the full-screen delete data confirmation screen" }, "deleteAllDataBody": { - "message": "You are about to delete all of this application's saved account information, including all contacts and all messages. You can always link with your mobile device again, but that will not restore deleted messages.", + "message": "Vi estas forigonta ĉiujn kontajn informojn el tiu ĉi aplikaĵo, inkluzive de ĉiuj kontaktoj kaj ĉiuj mesaĝoj. Vi povos re-ligi vian porteblan aparaton, sed tio ne restaŭros forigitajn mesaĝojn.", "description": "Text describing what exactly will happen if the user clicks the button to delete all data" }, "deleteAllDataButton": { @@ -1004,7 +1062,7 @@ "description": "Header for notification settings" }, "notificationSettingsDialog": { - "message": "Kiam mesaĝoj alvenas, montru sciigojn, kiuj malkaŝas:", + "message": "Kiam mesaĝoj alvenas, montri sciigojn, kiuj malkaŝas:", "description": "Explain the purpose of the notification settings" }, "disableNotifications": { @@ -1012,7 +1070,7 @@ "description": "Label for disabling notifications" }, "nameAndMessage": { - "message": "Ambaŭ la nomon de la sendanto kaj la mesaĝon", + "message": "Ambaŭ sendanto-nomon kaj mesaĝon", "description": "Label for setting notifications to display name and message text" }, "noNameOrMessage": { @@ -1020,7 +1078,7 @@ "description": "Label for setting notifications to display no name and no message text" }, "nameOnly": { - "message": "Nur la nomon de la sendanto", + "message": "Nur la sendanto-nomon", "description": "Label for setting notifications to display sender name only" }, "newMessage": { @@ -1056,17 +1114,13 @@ "description": "Hides the details of a key change" }, "learnMore": { - "message": "Learn more about verifying safety numbers", + "message": "Lernu pli pri kontrolo de sekurigaj numeroj", "description": "Text that links to a support article on verifying safety numbers" }, "expiredWarning": { "message": "Tiu ĉi versio de Signal Desktop jam nevalidas. Bonvolu ĝisdatigi al la nuna versio por daŭrigi sendi mesaĝojn.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Android clients will only receive the first 2000 characters of this message.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Ĝisdatigi", "description": "Label text for button to upgrade the app to the latest version" @@ -1084,7 +1138,7 @@ "description": "Label for contact and group sync settings" }, "syncExplanation": { - "message": "Import all Signal groups and contacts from your mobile device.", + "message": "Importi ĉiujn Signal-grupojn kaj kontaktojn el via portebla aparato.", "description": "Explanatory text for sync settings" }, "lastSynced": { @@ -1100,7 +1154,7 @@ "description": "Label for a disabled sync button while sync is in progress." }, "syncFailed": { - "message": "Import failed. Make sure your computer and your phone are connected to the internet.", + "message": "Importado malsukcesis. Certigu, ke viaj komputilo kaj telefono estas konektitaj al la interreto.", "description": "Informational text displayed if a sync operation times out." }, "timestamp_s": { @@ -1108,15 +1162,15 @@ "description": "Brief timestamp for messages sent less than a minute ago. Displayed in the conversation list and message bubble." }, "timestamp_m": { - "message": "1 minuton", + "message": "1 minuton", "description": "Brief timestamp for messages sent about one minute ago. Displayed in the conversation list and message bubble." }, "timestamp_h": { - "message": "1 horon", + "message": "1 horon", "description": "Brief timestamp for messages sent about one hour ago. Displayed in the conversation list and message bubble." }, "hoursAgoShort": { - "message": "$hours$ hr", + "message": "$hours$ h", "description": "Even further contracted form of 'X hours ago' which works both for singular and plural, used in the left pane", "placeholders": { "hours": { @@ -1126,7 +1180,7 @@ } }, "hoursAgo": { - "message": "antaŭ $hours$ hr", + "message": "antaŭ $hours$ h", "description": "Contracted form of 'X hours ago' which works both for singular and plural", "placeholders": { "hours": { @@ -1136,7 +1190,7 @@ } }, "minutesAgoShort": { - "message": "$minutes$ min", + "message": "$minutes$ min", "description": "Even further contracted form of 'X minutes ago' which works both for singular and plural, used in the left pane", "placeholders": { "minutes": { @@ -1146,7 +1200,7 @@ } }, "minutesAgo": { - "message": "antaŭ $minutes$ min", + "message": "antaŭ $minutes$ min", "description": "Contracted form of 'X minutes ago' which works both for singular and plural", "placeholders": { "minutes": { @@ -1160,19 +1214,19 @@ "description": "Shown if a message is very recent, less than 60 seconds old" }, "timestampFormat_M": { - "message": "MMM T", + "message": "D MMM", "description": "Timestamp format string for displaying month and day (but not the year) of a date within the current year, ex: use 'MMM D' for 'Aug 8', or 'D MMM' for '8 Aug'." }, "unblockToSend": { - "message": "Unblock this contact to send a message.", + "message": "Malbloki tiun kontakton por sendi mesaĝon.", "description": "Brief message shown when trying to message a blocked number" }, "unblockGroupToSend": { - "message": "Unblock this group to send a message.", + "message": "Malbloki tiun grupon por sendi mesaĝon.", "description": "Brief message shown when trying to message a blocked group" }, "youChangedTheTimer": { - "message": "You set the disappearing message timer to $time$", + "message": "Vi agordis la malaperon de la memviŝontaj mesaĝoj al $time$", "description": "Message displayed when you change the message expiration timer in a conversation.", "placeholders": { "time": { @@ -1182,7 +1236,7 @@ } }, "timerSetOnSync": { - "message": "Updated disappearing message timer to $time$", + "message": "Ĝisdatiĝis la malapero de la memviŝontaj mesaĝoj al $time$", "description": "Message displayed when timer is set on initial link of desktop device.", "placeholders": { "time": { @@ -1192,7 +1246,7 @@ } }, "theyChangedTheTimer": { - "message": "$name$ set the disappearing message timer to $time$", + "message": "$name$ agordis la malaperon de la memviŝontaj mesaĝoj al $time$", "description": "Message displayed when someone else changes the message expiration timer in a conversation.", "placeholders": { "name": { @@ -1210,47 +1264,47 @@ "description": "Label for option to turn off message expiration in the timer menu" }, "timerOption_5_seconds": { - "message": "5 sekundojn", + "message": "5 sekundojn", "description": "Label for a selectable option in the message expiration timer menu" }, "timerOption_10_seconds": { - "message": "10 sekundojn", + "message": "10 sekundojn", "description": "Label for a selectable option in the message expiration timer menu" }, "timerOption_30_seconds": { - "message": "30 sekundojn", + "message": "30 sekundojn", "description": "Label for a selectable option in the message expiration timer menu" }, "timerOption_1_minute": { - "message": "1 minuton", + "message": "1 minuton", "description": "Label for a selectable option in the message expiration timer menu" }, "timerOption_5_minutes": { - "message": "5 minutojn", + "message": "5 minutojn", "description": "Label for a selectable option in the message expiration timer menu" }, "timerOption_30_minutes": { - "message": "30 minutojn", + "message": "30 minutojn", "description": "Label for a selectable option in the message expiration timer menu" }, "timerOption_1_hour": { - "message": "1 horon", + "message": "1 horon", "description": "Label for a selectable option in the message expiration timer menu" }, "timerOption_6_hours": { - "message": "6 horojn", + "message": "6 horojn", "description": "Label for a selectable option in the message expiration timer menu" }, "timerOption_12_hours": { - "message": "12 horojn", + "message": "12 horojn", "description": "Label for a selectable option in the message expiration timer menu" }, "timerOption_1_day": { - "message": "1 tagon", + "message": "1 tagon", "description": "Label for a selectable option in the message expiration timer menu" }, "timerOption_1_week": { - "message": "1 semajnon", + "message": "1 semajnon", "description": "Label for a selectable option in the message expiration timer menu" }, "disappearingMessages": { @@ -1306,7 +1360,7 @@ "description": "Very short format indicating current timer setting in the conversation header" }, "disappearingMessagesDisabled": { - "message": "Memviŝontaj mesaĝoj malŝaltita", + "message": "Memviŝontaj mesaĝoj malŝaltitaj", "description": "Displayed in the left pane when the timer is turned off" }, "disabledDisappearingMessages": { @@ -1324,7 +1378,7 @@ "description": "Displayed in the conversation list when the timer is turned off" }, "timerSetTo": { - "message": "Timer set to $time$", + "message": "Malapero de la memviŝontaj mesaĝoj post $time$", "description": "Displayed in the conversation list when the timer is updated by some automatic action, or in the left pane", "placeholders": { "time": { @@ -1334,15 +1388,15 @@ } }, "audioNotificationDescription": { - "message": "Play audio notification", + "message": "Ludi sonan sciigon", "description": "Description for audio notification setting" }, "safetyNumberChanged": { - "message": "Sekurnumero ŝanĝiĝis", + "message": "Sekuriga numero ŝanĝiĝis", "description": "A notification shown in the conversation when a contact reinstalls" }, "safetyNumberChangedGroup": { - "message": "Sekurnumero de $name$ ŝanĝiĝis", + "message": "Sekuriga numero kun $name$ ŝanĝiĝis", "description": "A notification shown in a group conversation when a contact reinstalls, showing the contact name", "placeholders": { "name": { @@ -1352,11 +1406,11 @@ } }, "verifyNewNumber": { - "message": "Montri sekurnumeron", + "message": "Kontroli sekurigan numeron", "description": "Label on button included with safety number change notification in the conversation" }, "yourSafetyNumberWith": { - "message": "La sekurnumero de $name$:", + "message": "Via sekuriga numero kun $name$:", "description": "Heading for safety number view", "placeholders": { "name": { @@ -1373,12 +1427,16 @@ "message": "Malhela", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Noto al mi mem", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { - "message": "Hide menu bar", + "message": "Kaŝi la menubreton", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Komenci konversacion…", + "message": "Krei novan interparolon...", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1390,7 +1448,7 @@ "description": "When a person inputs a number that is invalid" }, "unlinkedWarning": { - "message": "Denove ligu Signal Desktop-on al la portebla aparato por daŭrigi sendadi mesaĝojn", + "message": "Re-ligi aplikaĵon Signal Desktop al via portebla aparato por daŭrigi mesaĝadon.", "description": "" }, "unlinked": { @@ -1398,7 +1456,7 @@ "description": "" }, "relink": { - "message": "Refoje ligi", + "message": "Re-ligi", "description": "" }, "autoUpdateNewVersionTitle": { @@ -1410,11 +1468,11 @@ "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Premu 'Reŝarĝi Signal-on' por ĝisdatumigi.", + "message": "Premu „Restartigi Signal-on“ por ĝisdatigi.", "description": "" }, "autoUpdateRestartButtonLabel": { - "message": "Reŝalti Signal-on", + "message": "Restartigi Signal-on", "description": "" }, "autoUpdateLaterButtonLabel": { @@ -1422,7 +1480,7 @@ "description": "" }, "leftTheGroup": { - "message": "$name$ lasis la grupon", + "message": "$name$ forlasis la grupon", "description": "Shown in the conversation history when a single person leaves the group", "placeholders": { "name": { @@ -1432,7 +1490,7 @@ } }, "multipleLeftTheGroup": { - "message": "$name$ lasis la grupon", + "message": "$name$ forlasis la grupon", "description": "Shown in the conversation history when multiple people leave the group", "placeholders": { "name": { @@ -1446,7 +1504,7 @@ "description": "Shown in the conversation history when someone updates the group" }, "titleIsNow": { - "message": "Titolo nun estas '$name$'", + "message": "Titolo nun estas „$name$“", "description": "Shown in the conversation history when someone changes the title of the group", "placeholders": { "name": { @@ -1456,7 +1514,7 @@ } }, "joinedTheGroup": { - "message": "$name$ aliĝis la grupon", + "message": "$name$ grupaniĝis", "description": "Shown in the conversation history when a single person joins the group", "placeholders": { "name": { @@ -1466,7 +1524,7 @@ } }, "multipleJoinedTheGroup": { - "message": "$names$ aliĝis la grupon", + "message": "$names$ grupaniĝis", "description": "Shown in the conversation history when more than one person joins the group", "placeholders": { "names": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Terms & Privacy Policy", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/es/messages.json b/_locales/es/messages.json index 54e5ba1099..bf33c4d97c 100644 --- a/_locales/es/messages.json +++ b/_locales/es/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copiar fallo y cerrar Signal", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Grupo sin nombre", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Fallo en la base de datos", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Borrar todos los datos y reiniciar", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Archivo", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Chats archivados", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Estos chats están archivados y sólo aparecerán en el buzón de entrada si recibes nuevos mensajes.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archivar chat", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Devolver chat al buzón de entrada", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Seleccionar carpeta", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "Al incluír un adjunto que no es una imagen, el límite es un adjunto por mensaje.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "No se pueden combinar adjuntos de otro tipo junto a imágenes.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Cerrar", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Introducir nombre o número", + "search": { + "message": "Buscar", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "Sin resultados para «$searchTerm$»", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Chats", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Contactos", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Mensajes", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Bienvenida a Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "apdo. de correos", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Descargando", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Descargar adjunto", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Foto", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "Aceptar", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "A", + "message": "para", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "General", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Enviar previsualización", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Al activar, las previsualizaciones de enlaces de Imgur, Instagram, Reddit y YouTube se envian automáticamente.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Comprobar la ortografía al escribir el mensaje", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Esta versión de Signal Desktop ha caducado. Por favor, actualiza a la última versión para seguir enviando mensajes.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Contactos con un teléfono Android solo recibirán los primeros 2000 caracteres de este mensaje.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Actualizar", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Oscuro", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Notas personales", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Ocultar barra de menú", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Iniciar chat ...", + "message": "Comienza con un chat ...", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Términos y política de privacidad", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/es_419/messages.json b/_locales/es_419/messages.json index 7c71670946..fddcf20eac 100644 --- a/_locales/es_419/messages.json +++ b/_locales/es_419/messages.json @@ -36,7 +36,7 @@ "description": "Application menu command to show all application windows" }, "appMenuQuit": { - "message": "Quit Loki Messenger", + "message": "Quit Session", "description": "Application menu command to close the application" }, "editMenuUndo": { @@ -565,7 +565,7 @@ "message": "Enter name or number", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "welcomeToSession": { "message": "Bienvenido a Signal", "description": "" }, @@ -1368,7 +1368,7 @@ "description": "Shown in the conversation history when someone updates the group" }, "titleIsNow": { - "message": "Title is now '$name$'", + "message": "Group name has been set to '$name$'", "description": "Shown in the conversation history when someone changes the title of the group", "placeholders": { "name": { @@ -1396,9 +1396,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Terms & Privacy Policy", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/et/messages.json b/_locales/et/messages.json index cb9888493a..ac83ec863a 100644 --- a/_locales/et/messages.json +++ b/_locales/et/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copy error and quit", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database Error", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Delete all data and restart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Fail", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Arhiveeritud vestlused", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Need vestlused on arhiveeritud ja ilmuvad sisendkausta, kui saabub uusi sõnumeid.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Vali kaust", "description": "Button to allow the user to find a folder on disk" @@ -484,11 +516,11 @@ "description": "Shown in toast when user attempts to send .exe file, for example" }, "loadingPreview": { - "message": "Loading Preview...", + "message": "Eelvaate laadimine...", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area" }, "stagedPreviewThumbnail": { - "message": "Draft thumbnail link preview for $domain$", + "message": "Lingi eelvaate pisipildi mustand: $domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -498,7 +530,7 @@ } }, "previewThumbnail": { - "message": "Thumbnail link preview for $domain$", + "message": "Lingi eelvaate pisipilt: $domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -508,7 +540,7 @@ } }, "stagedImageAttachment": { - "message": "Draft image attachment: $path$", + "message": "Piltmanuse mustand: $path$", "description": "Alt text for staged attachments", "placeholders": { "path": { @@ -518,15 +550,15 @@ } }, "oneNonImageAtATimeToast": { - "message": "When including a non-image attachment, the limit is one attachment per message.", + "message": "Mittepildilisi manuseid sõnumile lisades kehtiv ühe manuse piirang.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { - "message": "You cannot mix non-image and image attachments in one message.", + "cannotMixImageAndNonImageAttachments": { + "message": "Piltmanuseid ja teisi manuseid ei saa koos ühte sõnumisse panna.", "description": "An error popup when the user has attempted to add an attachment" }, "maximumAttachments": { - "message": "You cannot add any more attachments to this message.", + "message": "Sellele sõnumile pole võimalik rohkem manuseid lisada.", "description": "An error popup when the user has attempted to add an attachment" }, "fileSizeWarning": { @@ -611,15 +643,37 @@ "message": "Välju", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Sisesta nimi või number", + "search": { + "message": "Otsi", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Vestlused", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Kontaktid", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Sõnumid", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Tere tulemast Signalisse", "description": "" }, @@ -673,6 +727,10 @@ "message": "postkast", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Laadin alla", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Laadi manus alla", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -694,7 +752,7 @@ "description": "Shown in toast if user clicks on quote references messages not loaded in view, but in database" }, "voiceNoteMustBeOnlyAttachment": { - "message": "A voice note must be the only attachment included in a message.", + "message": "Häälmärkmed peavad olema sõnumi ainuke manus.", "description": "Shown in toast if tries to record a voice note with any staged attachments" }, "you": { @@ -735,6 +793,14 @@ "message": "Foto", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "Sobib", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Saaja", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Üldine", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Luba sõnumite kasti teksti õigekirja kontroll", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "See Signal Desktopi versioon on aegunud. Palun uuenda uusimale versioonile sõnumite saatmiseks jätkamiseks.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Androidi kasutajad saavad ainult selle sõnumi esimesed 2000 märki.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Uuenda", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Tume", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Märkus endale", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Peida menüüriba", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Alusta vestlust...", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Tingimused ja privaatsuspoliitika", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/fa/messages.json b/_locales/fa/messages.json index 75c56ce99f..00d691d046 100644 --- a/_locales/fa/messages.json +++ b/_locales/fa/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copy error and quit", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database Error", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Delete all data and restart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&فایل", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "مکالمه های آرشیو شده", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "این مکالمه ها بایگانی شده اند و اگر پیام های جدید دریافت شوند فقط در صندوق به نمایش در می آیند.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "انتخاب پوشه", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "When including a non-image attachment, the limit is one attachment per message.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "You cannot mix non-image and image attachments in one message.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "خروج", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "نام یا شماره وارد کنید", + "search": { + "message": "جستجو", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "مکالمه ها", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "مخاطبین", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "پیام ها", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "به Signal خوش‌آمدید", "description": "" }, @@ -673,6 +727,10 @@ "message": "صندوق پست", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "در حال دانلود", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "دانلود ضمیمه", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "تصویر", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "باشه", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "به", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "عمومی", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "فعال سازی کنترل املاء متن وارد شده در باکس پیام نویسی", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "این نسخه‌ی Signal قدیمی است. برای ارسال پیام لطفا آن را به آخرین نسخه ارتقاء دهید.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "سیستم‌های اندرویدی فقط 2000 کاراکتر اول این پیام را دریافت خواهند کرد.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "ارتقاء", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "تاریک", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Note to Self", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "پنهان کردن نوار منو", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "شروع مکالمه...", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "شرایط و سیاست های حفظ حریم خصوصی", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/fi/messages.json b/_locales/fi/messages.json index f1ab2e6e79..32bc5f796e 100644 --- a/_locales/fi/messages.json +++ b/_locales/fi/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Kopioi virheilmoitus ja lopeta", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Tuntematon ryhmä", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Tietokantavirhe", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Tuhoa kaikki tiedot ja käynnistä uudelleen", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Tiedosto", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Arkistoidut keskustelut", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Nämä keskustelut ovat arkistoituja. Ne siirtyvät takaisin postilaatikkoon, jos niihin tulee uusia viestejä", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Arkistoi keskustelu", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Siirrä keskustelu takaisin postilaatikkoon", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Valitse kansio", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "Muille kuin kuvatiedostoille raja on yksi liitetiedosto per viesti.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "Kuvatiedostoa ja muun tyyppistä liitetiedostoa ei voi lisätä samaan viestiin.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Lopeta", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Syötä nimi tai numero", + "search": { + "message": "Hae", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "Ei tuloksia haulle: \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Keskustelut", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Yhteystiedot", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Viestit", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Tervetuloa Signaliin", "description": "" }, @@ -673,6 +727,10 @@ "message": "Postilokero", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Ladataan", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Lataa liite", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Kuva", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Vastaanottajat", + "message": "vastaanottaja", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Yleistä", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Lähetä esikatselukuva linkeistä", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Esikatselu tukee sivustoja Imgur, Instagram, Reddit ja YouTube.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Ota käyttöön viestilaatikkoon kirjoitetun tekstin oikeinkirjoituksen tarkistus", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Käyttämäsi versio Signal Desktopista on vanhentunut. Viestien lähettäminen ja vastaanottaminen eivät enää toimi, ennen kuin olet päiväittänyt uusimpaan versioon.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Signalin Android-käyttäjät näkevät vain ensimmäiset 2000 merkkiä tästä viestistä.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Päivitä", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Tumma", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Viestit itselleni", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Piilota valikkopalkki", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Aloita keskustelu", + "message": "Aloita uusi keskustelu...", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Käyttöehdot ja tietosuoja", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 2902e5b14c..7c5f03ea99 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copier l’erreur et quitter", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Groupe inconnu", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Erreur de base de données", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Supprimer toutes les données et relancer", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Fichier", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Conversations archivées", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Ces conversations sont archivées et n’apparaîtront dans la boîte de réception que si de nouveaux messages sont reçus.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archiver la conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Déplacer la conversation vers la boite de réception", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Sélectionner un dossier", "description": "Button to allow the user to find a folder on disk" @@ -484,11 +516,11 @@ "description": "Shown in toast when user attempts to send .exe file, for example" }, "loadingPreview": { - "message": "Loading Preview...", + "message": "Chargement de l’aperçu…", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area" }, "stagedPreviewThumbnail": { - "message": "Draft thumbnail link preview for $domain$", + "message": "Brouillon d’imagette d’aperçu de lien pour $domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -498,7 +530,7 @@ } }, "previewThumbnail": { - "message": "Thumbnail link preview for $domain$", + "message": "Imagette d’aperçu de lien pour $domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -508,7 +540,7 @@ } }, "stagedImageAttachment": { - "message": "Draft image attachment: $path$", + "message": "Brouillon d’image jointe : $path$", "description": "Alt text for staged attachments", "placeholders": { "path": { @@ -518,11 +550,11 @@ } }, "oneNonImageAtATimeToast": { - "message": "When including a non-image attachment, the limit is one attachment per message.", + "message": "Pour un fichier joint qui n’est pas une image, la limite est un fichier joint par message.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { - "message": "You cannot mix non-image and image attachments in one message.", + "cannotMixImageAndNonImageAttachments": { + "message": "Vous ne pouvez pas joindre à la fois image et non image dans le même message.", "description": "An error popup when the user has attempted to add an attachment" }, "maximumAttachments": { @@ -611,15 +643,37 @@ "message": "Quitter", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop pour ordinateur", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Saisir le nom ou le numéro", + "search": { + "message": "Chercher", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "Aucun résultat pour « $searchTerm$ »", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Conversations", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Contacts", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Messages", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Bienvenue sur Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "Boîte postale", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Téléchargement", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Télécharger la pièce jointe", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -694,7 +752,7 @@ "description": "Shown in toast if user clicks on quote references messages not loaded in view, but in database" }, "voiceNoteMustBeOnlyAttachment": { - "message": "A voice note must be the only attachment included in a message.", + "message": "Une note vocale doit être le seul fichier joint à un message.", "description": "Shown in toast if tries to record a voice note with any staged attachments" }, "you": { @@ -735,6 +793,14 @@ "message": "Photo", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "Valider", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "À", + "message": "à", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Général", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Envoyer des aperçus de liens.", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Les aperçus sont pris en charge pour les liens Imgur, Instagram, Reddit et YouTube.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Activer la vérification de l’orthographe du texte saisi dans la fenêtre de rédaction des messages", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Cette version de Signal Desktop pour ordinateur est expirée. Veuillez la mettre à niveau vers la version la plus récente afin de continuer à échanger.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Les clients pour Android ne recevront que les 2 000 premiers caractères de ce message.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Mettre à niveau", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Sombre", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Note à mon intention", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Cacher la barre de menu", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Lancer la conversation…", + "message": "Lancer une nouvelle conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Conditions générales d’utilisation et politique de confidentialité", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/he/messages.json b/_locales/he/messages.json index 0dcb9ba3fb..0d510335da 100644 --- a/_locales/he/messages.json +++ b/_locales/he/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "העתק שגיאה וצא", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "קבוצה לא ידועה", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "שגיאת מסד נתונים", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "מחק את כל הנתונים והפעל מחדש", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&קובץ", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "שיחות מאורכבות", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "שיחות אלו מאורכבות ויופיעו בתיבה הנכנסת רק אם מתקבלות הודעות חדשות.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "ארכב שיחה", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "העבר שיחה אל תיבה נכנסת", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "בחר תיקייה", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "בעת הכללת צרופת אי־תמונה, המגבלה היא צרופה אחת להודעה.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "אינך יכול לערבב צרופות של אי־תמונה ותמונה בהודעה אחת.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "צא", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop עבודה", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "הכנס שם או מספר", + "search": { + "message": "חיפוש", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "אין תוצאות עבור \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "שיחות", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "אנשי קשר", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "הודעות", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "ברוכים הבאים לסיגנל", "description": "" }, @@ -673,6 +727,10 @@ "message": "תא דואר", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "מוריד", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "הורד צרופה", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "תצלום", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "אישור", "description": "" @@ -959,14 +1025,6 @@ "message": "כללי", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "שלח קדם־תצוגות של קישורים", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "קדם־תצוגות נתמכות עבור קישורים של Imgur, Instagram, Reddit ו־YouTube.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "אפשר בדיקת איות של מלל המוכנס בתיבת חיבור הודעה", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "גרסה זו של Signal Desktop עבודה פגה. אנא שדרג אל הגרסה האחרונה כדי להמשיך בשליחת הודעות.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "לקוחות Android יקבלו רק את 2000 התווים הראשונים של הודעה זו.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "שדרג", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "כהה", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "הערה לעצמי", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "הסתר שורת תפריט", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "התחל שיחה...", + "message": "התחל שיחה חדשה...", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "תנאים ומדיניות פרטיות", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/hi/messages.json b/_locales/hi/messages.json index f02e13b36e..4cdb26f48d 100644 --- a/_locales/hi/messages.json +++ b/_locales/hi/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copy error and quit", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database Error", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Delete all data and restart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&File", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -36,7 +52,7 @@ "description": "Application menu command to show all application windows" }, "appMenuQuit": { - "message": "Quit Loki Messenger", + "message": "Quit Session", "description": "Application menu command to close the application" }, "editMenuUndo": { @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Archived Conversations", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "These conversations are archived and will only appear in the Inbox if new messages are received.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "फोल्डर को चुनो", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "When including a non-image attachment, the limit is one attachment per message.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "You cannot mix non-image and image attachments in one message.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Quit", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Enter name or number", + "search": { + "message": "सर्च", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "संवाद", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "संपर्क", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "संदेश", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "सिग्‍नल में आपका स्वागत है ", "description": "" }, @@ -673,6 +727,10 @@ "message": "PO Box", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Downloading", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Download Attachment", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Photo", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "ठीक", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "किस को", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "General", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Enable spell check of text entered in message composition box", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Signal डेस्कटॉप का यह संस्करण समाप्त हो गया है। संदेश जारी रखने के लिए कृपया नवीनतम संस्करण में नवीनीकृत करें।", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "एंड्रॉयड क्लाइंट केवल इस संदेश के पहले 2000 वर्ण प्राप्त करेंगे", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "अपग्रेड", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Dark", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Note to Self", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "मेनू बार छुपाएं", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Start conversation…", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1446,7 +1504,7 @@ "description": "Shown in the conversation history when someone updates the group" }, "titleIsNow": { - "message": "Title is now '$name$'", + "message": "Group name has been set to '$name$'", "description": "Shown in the conversation history when someone changes the title of the group", "placeholders": { "name": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Terms & Privacy Policy", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/hr/messages.json b/_locales/hr/messages.json index 8f895d6e94..4205980dfc 100644 --- a/_locales/hr/messages.json +++ b/_locales/hr/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copy error and quit", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database Error", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Delete all data and restart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&File", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -36,7 +52,7 @@ "description": "Application menu command to show all application windows" }, "appMenuQuit": { - "message": "Quit Loki Messenger", + "message": "Quit Session", "description": "Application menu command to close the application" }, "editMenuUndo": { @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Arhivirani razgovori", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "These conversations are archived and will only appear in the Inbox if new messages are received.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Odaberi direktorij", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "When including a non-image attachment, the limit is one attachment per message.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "You cannot mix non-image and image attachments in one message.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Quit", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Enter name or number", + "search": { + "message": "Traži", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Conversations", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Kontakti", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Poruke", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Dobrodošli u Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "PO Box", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Preuzimanje", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Download Attachment", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Photo", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Za", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Općenito", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Enable spell check of text entered in message composition box", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Ova inačica Signal Desktopa je istekla. Molimo vas da za nastavak dopisivanja nadogradite na najnoviju verziju.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Android uređaji primit će samo prvih 2000 znakova ove poruke.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Nadogradi", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Dark", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Note to Self", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Hide menu bar", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Start conversation…", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1446,7 +1504,7 @@ "description": "Shown in the conversation history when someone updates the group" }, "titleIsNow": { - "message": "Title is now '$name$'", + "message": "Group name has been set to '$name$'", "description": "Shown in the conversation history when someone changes the title of the group", "placeholders": { "name": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Uvjeti i pravila o privatnosti", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/hu/messages.json b/_locales/hu/messages.json index b2c5d0cb50..3eeebb67f1 100644 --- a/_locales/hu/messages.json +++ b/_locales/hu/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Hiba másolása és kilépés", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Ismeretlen csoport", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Adatbázishiba", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Összes adat törlése és újraindítás", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Fájl", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Archív beszélgetések", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Ezek a beszélgetések archiválva vannak, ezért csak akkor jelennek meg újra a bejövő üzenet közt, ha újabb üzenet érkezik.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Beszélgetés archiválása", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Beszélgetés áthelyezése a beérkezett üzenetek közé", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Mappa kiválasztása", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "Nem-kép csatolmány esetén üzenetenként egy melléklet csatolható.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "Nem keverheted a nem-kép és kép típusú csatolmányokat üzeneten belül.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Kilépés", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Név vagy telefonszám", + "search": { + "message": "Keresés", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "Nincs találat a \"$searchTerm$\" keresőkifejezése", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Beszélgetések", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Kontaktok", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Üzenetek", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Üdvözöl a Signal!", "description": "" }, @@ -673,6 +727,10 @@ "message": "Postafiók", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Letöltés", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Csatolmány letöltése", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Kép", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Címzett", + "message": "címzett", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Általános", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Hivatkozások előnézeti képének küldése", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Az előnézeti képek az Imgur, Instagram, Reddit és YouTube szolgáltatásokhoz érhetőek el.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Az üzenetíró dobozba gépelt szöveg helyesírás-ellenőrzésének engedélyezése", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "A Signal Desktop ezen verziója elavult. Kérlek frissíts a legújabb verzióra, hogy folytatni tudd a beszélgetést!", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Az Androidos kliensek csak az üzenet első 2000 karakterét fogják megkapni.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Frissítés", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Sötét", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Privát feljegyzés", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Menü elrejtése", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Beszélgetés megkezdése...", + "message": "Új beszélgetés megkezdése…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Adatvédelmi és Általános Szerződési Feltételek", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/id/messages.json b/_locales/id/messages.json index 893e2f6660..9c4dc3b65c 100644 --- a/_locales/id/messages.json +++ b/_locales/id/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copy error and quit", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database Error", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Delete all data and restart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Berkas", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Percakapan Terarsipkan", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "These conversations are archived and will only appear in the Inbox if new messages are received.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Pilih map", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "Ketika menyertakan lampiran bukan gambar, batasnya adalah satu lampiran per pesan.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "Anda tidak bisa mencampur lampiran gambar dan bukan gambar dalam satu pesan.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Berhenti", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Masukkan nama atau nomor ", + "search": { + "message": "Pencarian", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Percakapan", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Kontak", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Pesan", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Selamat datang di Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "PO Box", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Mengunduh", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Unduh Lampiran", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Foto", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Kepada", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -866,7 +932,7 @@ "description": "Used for the icon layered on top of an image in message bubbles" }, "addACaption": { - "message": "Add a caption...", + "message": "Tambah keterangan...", "description": "" }, "save": { @@ -959,14 +1025,6 @@ "message": "Umum", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Aktifkan pengecek ejaan dalam kotak pesan", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Versi Signal Desktop ini telah kedaluwarsa. Mohom memutakhirkan ke versi terbaru untuk melanjutkan pengiriman pesan.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Klien Android hanya akan menerima 2000 karakter pertama dari pesan ini.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Pemutakhiran", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Gelap", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Catatan Pribadi", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Sembunyikan kolom menu", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Mulai percakapan...", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Syarat & Kebijakan Privasi", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/it/messages.json b/_locales/it/messages.json index a369f78049..e40da8f415 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copia l'errore ed esci", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Gruppo sconosciuto", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Errore del database", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Elimina tutti i dati e riavvia", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&File", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Conversazioni archiviate", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Queste conversazioni sono archiviate e compariranno nella lista di chat solo se verranno ricevuti nuovi messaggi.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archivia conversazione", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Ripristina conversazione", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Scegli la cartella", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "Quando includi un allegato che non è un'immagine, il limite è un solo allegato per messaggio.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "Non puoi allegare insieme immagini ad altri file in un messaggio.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Esci", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Inserisci il nome o il numero", + "search": { + "message": "Cerca", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "Nessun risultato per \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Conversazioni", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Contatti", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Messaggi", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Benvenuto in Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "Casella postale", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Caricamento in corso", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Scarica allegato", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Foto", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "A", + "message": "a", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Generale", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Invia anteprime link", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Le anteprime sono supportate per i link di Imgur, Instagram, Reddit e YouTube.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Abilita il controllo ortografico del testo inserito nella casella di composizione", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Questa versione di Signal Desktop è scaduta. Per continuare a chattare aggiornala all'ultima versione.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "I client Android riceveranno solo i primi 2000 caratteri di questo messaggio.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Aggiorna", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Scuro", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Note personali", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Nascondi la barra del menu", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Inizia la conversazione...", + "message": "Inizia una nuova conversazione...", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Termini e privacy policy", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json index 172b482e7c..25acf6561c 100644 --- a/_locales/ja/messages.json +++ b/_locales/ja/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copy error and quit", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database Error", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Delete all data and restart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "ファイル (&F)", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "保存済みの会話", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "この会話は保存されます。新しいメッセージが届いた場合だけ受信箱に表示されます。", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "フォルダを選択", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "When including a non-image attachment, the limit is one attachment per message.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "You cannot mix non-image and image attachments in one message.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "終了", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "名前か番号を入力", + "search": { + "message": "検索", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "会話", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "連絡先", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "メッセージ", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Signalにようこそ", "description": "" }, @@ -673,6 +727,10 @@ "message": "私書箱", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "ダウンロード中", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "添付ファイルをダウンロード", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "写真", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "宛先", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "一般", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "リンクプレビューを送る", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "次のサービスのプレビューに対応しています: Imgur, Instagram, Reddit, YouTube", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "メッセージボックスに入力されたテキストのスペルチェックを有効にする", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "アプリのバージョンが古すぎます。最新版にアップデートしてください。", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Android端末ではこのメッセージの最初の2000字しか受信されません", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "アップデート", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "ダーク", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "自分のためのメモ", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "メニューバーを最小化", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "会話を開始する...", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "使用条件とプライバシーポリシー", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/km/messages.json b/_locales/km/messages.json index bf9c32f4f3..9f41528e1b 100644 --- a/_locales/km/messages.json +++ b/_locales/km/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "ចម្លងបញ្ហា និងចាកចេញ", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "ក្រុមមិនស្គាល់", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "បញ្ហាទិន្នន័យ", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "លុបទិន្នន័យទាំងអស់ និងបើកឡើងវិញ", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&ឯកសារ", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,12 +161,28 @@ } } }, + "archivedConversations": { + "message": "បណ្ណសារសន្ទនា", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "ការសន្ទនាទាំងនេះ នឹងត្រូវធ្វើបណ្ណសារ និងបង្ហាញក្នុងប្រអប់សំបុត្រតែប៉ុណ្ណោះ ប្រសិនបើទទួលបានសារថ្មី។", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "បណ្ណសារការសន្ទនា", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "ផ្លាស់ប្តូរការសន្ទនាទៅប្រអប់សារ", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "ជ្រើសរើសទីតាំងផ្ទុក", "description": "Button to allow the user to find a folder on disk" }, "chooseFile": { - "message": "Choose file", + "message": "ជ្រើសរើសឯកសារ", "description": "Button to allow the user to find a file on disk" }, "loadDataHeader": { @@ -480,15 +512,15 @@ "description": "Name for a voice message attachment" }, "dangerousFileType": { - "message": "Attachment type not allowed for security reasons", + "message": "ប្រភេទឯកសារភ្ជាប់មិនអនុញ្ញាតសម្រាប់ហេតុផលសុវត្ថិភាព", "description": "Shown in toast when user attempts to send .exe file, for example" }, "loadingPreview": { - "message": "Loading Preview...", + "message": "កំពុងផ្ទុកការមើល...", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area" }, "stagedPreviewThumbnail": { - "message": "Draft thumbnail link preview for $domain$", + "message": "ការមើលតំណជារូបភាពតូចៗព្រាងសម្រាប់ $domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -498,7 +530,7 @@ } }, "previewThumbnail": { - "message": "Thumbnail link preview for $domain$", + "message": "ការមើលតំណជារូបភាពតូចៗសម្រាប់$domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -508,7 +540,7 @@ } }, "stagedImageAttachment": { - "message": "Draft image attachment: $path$", + "message": "រូបភាពឯកសារភ្ជាប់ព្រាង៖ $path$", "description": "Alt text for staged attachments", "placeholders": { "path": { @@ -518,15 +550,15 @@ } }, "oneNonImageAtATimeToast": { - "message": "When including a non-image attachment, the limit is one attachment per message.", + "message": "ពេលបញ្ចូលឯកសារភ្ជាប់មិន-រូបភាពមួយ ការកំណត់គឺឯកសារភ្ជាប់មួយ សម្រាប់សារមួយ។", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { - "message": "You cannot mix non-image and image attachments in one message.", + "cannotMixImageAndNonImageAttachments": { + "message": "អ្នកមិនអាចលាយឡំឯកសារភ្ជាប់ មិន-រូបភាព និងរូបភាព ក្នុងសារតែមួយទេ។", "description": "An error popup when the user has attempted to add an attachment" }, "maximumAttachments": { - "message": "You cannot add any more attachments to this message.", + "message": "អ្នកមិនអាចបន្ថែមឯកសារភ្ជាប់ទៅកាន់សារនេះទេ។", "description": "An error popup when the user has attempted to add an attachment" }, "fileSizeWarning": { @@ -611,15 +643,37 @@ "message": "ចាកចេញ", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "បញ្ចូល ឈ្មោះ ឬ លេខ", + "search": { + "message": "ស្វែងរក", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "គ្មានលទ្ធផលសម្រាប់ \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "ការសន្ទនា", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "បញ្ជីទំនាក់ទំនង", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "សារ", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "ស្វាគមន៍មកកាន់ Signal", "description": "" }, @@ -628,7 +682,7 @@ "description": "" }, "typingAlt": { - "message": "Typing animation for this conversation", + "message": "ការវាយចលនាសម្រាប់ការសន្ទនានេះ", "description": "Used as the 'title' attibute for the typing animation" }, "contactAvatarAlt": { @@ -673,6 +727,10 @@ "message": "ប្រអប់សំបុត្រ", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "កំពុងទាញយក", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "ទាញយកឯកសារភ្ជាប់", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -694,7 +752,7 @@ "description": "Shown in toast if user clicks on quote references messages not loaded in view, but in database" }, "voiceNoteMustBeOnlyAttachment": { - "message": "A voice note must be the only attachment included in a message.", + "message": "ការចំណាំជាសំឡេងត្រូវតែជាឯកសារភ្ជាប់ នៅក្នុងសារមួយ។", "description": "Shown in toast if tries to record a voice note with any staged attachments" }, "you": { @@ -735,6 +793,14 @@ "message": "រូបភាព", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "យល់ព្រម", "description": "" @@ -862,11 +928,11 @@ "description": "Used in the alt tag for the image shown in a full-screen lightbox view" }, "imageCaptionIconAlt": { - "message": "Icon showing that this image has a caption", + "message": "រូបតំណាងបង្ហាញថារូបភាពនេះមានចំណងជើង", "description": "Used for the icon layered on top of an image in message bubbles" }, "addACaption": { - "message": "Add a caption...", + "message": "ដាក់ចំណងជើង...", "description": "" }, "save": { @@ -956,17 +1022,9 @@ "description": "Description of the media permission description" }, "general": { - "message": "General", + "message": "ទូទៅ", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "បើកការត្រួតពិនិត្យអក្ខរាវិរុទ្ធនៃពាក្យដែលបានបញ្ចូលក្នុងប្រអប់ផ្ញើសារ", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "ជំនាន់ Signal Desktop នេះ បានហួសសុពលភាព។ សូមដំឡើងទីកាន់ជំនាន់ចុងក្រោយ ដើម្បីបន្តការផ្ញើសារ។", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "អ្នកប្រើប្រាស់ Android នឹងទទួលបានអក្សរ 2000 តួ ដំបូងនៃសារនេះតែប៉ុណ្ណោះ។", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "ដំឡើង", "description": "Label text for button to upgrade the app to the latest version" @@ -1168,7 +1222,7 @@ "description": "Brief message shown when trying to message a blocked number" }, "unblockGroupToSend": { - "message": "Unblock this group to send a message.", + "message": "មិនការទប់ស្កាត់ក្រុមនេះ ដើម្បីផ្ញើសារ។", "description": "Brief message shown when trying to message a blocked group" }, "youChangedTheTimer": { @@ -1373,12 +1427,16 @@ "message": "ងងឹត", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "កំណត់ចំណាំ", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "លាក់របារម៉ឺនុយ", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "ចាប់ផ្តើមការសន្ទនា...", + "message": "ចាប់ផ្តើមការសន្ទនាថ្មី...", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "លក្ខខណ្ឌ និងគោលនយោបាយឯកជនភាព", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/kn/messages.json b/_locales/kn/messages.json index 4b15387cbe..3b3af59cfa 100644 --- a/_locales/kn/messages.json +++ b/_locales/kn/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copy error and quit", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database Error", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Delete all data and restart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&File", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -36,7 +52,7 @@ "description": "Application menu command to show all application windows" }, "appMenuQuit": { - "message": "Quit Loki Messenger", + "message": "Quit Session", "description": "Application menu command to close the application" }, "editMenuUndo": { @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Archived Conversations", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "These conversations are archived and will only appear in the Inbox if new messages are received.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Choose folder", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "When including a non-image attachment, the limit is one attachment per message.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "You cannot mix non-image and image attachments in one message.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Quit", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Enter name or number", + "search": { + "message": "ಹುಡುಕಿ", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Conversations", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "ಸಂಪರ್ಕಗಳು", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Messages", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "ಸಿಗ್ನಲ್‌ಗೆ ಸ್ವಾಗತ", "description": "" }, @@ -673,6 +727,10 @@ "message": "PO Box", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Downloading", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Download Attachment", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Photo", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "ಸರಿ", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "ಗೆ", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "General", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Enable spell check of text entered in message composition box", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Signal ಡೆಸ್ಕ್ಟಾಪ್ ಈ ಆವೃತ್ತಿಯ ಅವಧಿ ಮುಗಿದಿದೆ. ಸಂದೇಶ ಮುಂದುವರೆಸಲು ಹೊಸ ಆವೃತ್ತಿಗೆ ಅಪ್ಗ್ರೇಡ್ ಮಾಡಿ.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Android clients will only receive the first 2000 characters of this message.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "ಅಪ್ಗ್ರೇಡ್", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Dark", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Note to Self", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Hide menu bar", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Start conversation…", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1446,7 +1504,7 @@ "description": "Shown in the conversation history when someone updates the group" }, "titleIsNow": { - "message": "Title is now '$name$'", + "message": "Group name has been set to '$name$'", "description": "Shown in the conversation history when someone changes the title of the group", "placeholders": { "name": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Terms & Privacy Policy", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/ko/messages.json b/_locales/ko/messages.json index 1199f9f395..968abaec22 100644 --- a/_locales/ko/messages.json +++ b/_locales/ko/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copy error and quit", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database Error", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Delete all data and restart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&File", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -36,7 +52,7 @@ "description": "Application menu command to show all application windows" }, "appMenuQuit": { - "message": "Quit Loki Messenger", + "message": "Quit Session", "description": "Application menu command to close the application" }, "editMenuUndo": { @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Archived Conversations", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "These conversations are archived and will only appear in the Inbox if new messages are received.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Choose folder", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "When including a non-image attachment, the limit is one attachment per message.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "You cannot mix non-image and image attachments in one message.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Quit", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Enter name or number", + "search": { + "message": "검색", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "대화", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "연락처", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Messages", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Welcome to Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "PO Box", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "다운로드 중", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Download Attachment", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Photo", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "To", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "일반 설정", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Enable spell check of text entered in message composition box", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "This version of Signal Desktop has expired. Please upgrade to the latest version to continue messaging.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Android clients will only receive the first 2000 characters of this message.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Upgrade", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Dark", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Note to Self", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Hide menu bar", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Start conversation…", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1446,7 +1504,7 @@ "description": "Shown in the conversation history when someone updates the group" }, "titleIsNow": { - "message": "Title is now '$name$'", + "message": "Group name has been set to '$name$'", "description": "Shown in the conversation history when someone changes the title of the group", "placeholders": { "name": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Terms & Privacy Policy", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/lt/messages.json b/_locales/lt/messages.json index d7b34ee290..b57915c90f 100644 --- a/_locales/lt/messages.json +++ b/_locales/lt/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Kopijuoti klaidą ir išeiti", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Nežinoma grupė", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Duomenų bazės klaida", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Ištrinti visus duomenis ir paleisti iš naujo", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Failas", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Archyvuoti pokalbiai", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Šie pokalbiai yra archyvuoti ir atsiras skyrelyje Gauta tik tuomet, jei bus gautos naujos žinutės.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archyvuoti pokalbį", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Perkelti pokalbį į skyrelį Gauta", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Pasirinkti aplanką", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "Įtraukiant ne paveikslų priedus, vienoje žinutėje galimas tik vienas priedas.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "Negalite vienoje žinutėje maišyti paveikslų ir ne paveikslų priedus.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Išeiti", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Įveskite vardą ar numerį", + "search": { + "message": "Ieškoti", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "\"$searchTerm$\" negrąžino jokių rezultatų", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Pokalbiai", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Kontaktai", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Žinutės", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Sveiki atvykę į Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "Pašto dėžutė", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Atsisiunčiama", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Atsisiųsti priedą", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Nuotrauka", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "Gerai", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Kam", + "message": "skirta", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Bendra", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Siųsti nuorodų peržiūras", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Peržiūros yra palaikomos Imgur, Instagram, Reddit, ir YouTube nuorodoms.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Įjungti rašybos tikrinimą tekstui, kuris įvedamas į žinutės rašymo langelį", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Pasibaigė šios Signal Desktop skirtos versijos galiojimas. Norint tęsti susirašinėjimą, prašome atsinaujinti iki naujausios versijos.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Android klientai gaus tik pirmuosius 2000 šios žinutės simbolių.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Naujinti", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Tamsi", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Pastabos sau", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Slėpti meniu juostą", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Pradėti pokalbį…", + "message": "Pradėti naują pokalbį…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Sąlygos ir Privatumo politika", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/mk/messages.json b/_locales/mk/messages.json index 13d300a8d2..3987e9b00f 100644 --- a/_locales/mk/messages.json +++ b/_locales/mk/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copy error and quit", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database Error", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Delete all data and restart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&File", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -36,7 +52,7 @@ "description": "Application menu command to show all application windows" }, "appMenuQuit": { - "message": "Quit Loki Messenger", + "message": "Quit Session", "description": "Application menu command to close the application" }, "editMenuUndo": { @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Archived Conversations", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "These conversations are archived and will only appear in the Inbox if new messages are received.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Choose folder", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "When including a non-image attachment, the limit is one attachment per message.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "You cannot mix non-image and image attachments in one message.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Quit", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Enter name or number", + "search": { + "message": "Барај", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Разговори", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Контакти", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Messages", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Добредојдовте во Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "PO Box", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Превземање", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Download Attachment", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Photo", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "Во ред", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "To", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Општо", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Enable spell check of text entered in message composition box", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "This version of Signal Desktop has expired. Please upgrade to the latest version to continue messaging.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Android clients will only receive the first 2000 characters of this message.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Upgrade", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Dark", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Note to Self", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Hide menu bar", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Start conversation…", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1446,7 +1504,7 @@ "description": "Shown in the conversation history when someone updates the group" }, "titleIsNow": { - "message": "Title is now '$name$'", + "message": "Group name has been set to '$name$'", "description": "Shown in the conversation history when someone changes the title of the group", "placeholders": { "name": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Terms & Privacy Policy", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/nb/messages.json b/_locales/nb/messages.json index 2a33448508..41343bccc0 100644 --- a/_locales/nb/messages.json +++ b/_locales/nb/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Kopier feil og avslutt", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Ukjent gruppe", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database feil", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Slett alle data og restart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Fil", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Arkiverte Samtaler", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Disse samtalene er arkiverte og vil dukke opp igjen i innboksen om de får nye meldinger.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Arkiver samtalen", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Flytt samtalen til innboksen", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Velg mappe", "description": "Button to allow the user to find a folder on disk" @@ -484,11 +516,11 @@ "description": "Shown in toast when user attempts to send .exe file, for example" }, "loadingPreview": { - "message": "Loading Preview...", + "message": "Laster forhåndsvisning...", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area" }, "stagedPreviewThumbnail": { - "message": "Draft thumbnail link preview for $domain$", + "message": "Utkast miniatyrbilde til linkforhåndsvisning for $domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -498,7 +530,7 @@ } }, "previewThumbnail": { - "message": "Thumbnail link preview for $domain$", + "message": "Miniatyrbilde til linkforhåndsvisning for $domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -508,7 +540,7 @@ } }, "stagedImageAttachment": { - "message": "Draft image attachment: $path$", + "message": "Utkast av vedlagt bildefil: $path$", "description": "Alt text for staged attachments", "placeholders": { "path": { @@ -521,7 +553,7 @@ "message": "Når du inkluderer et vedlegg som ikke er et bilde, er begrensningen ett vedlegg per melding.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "Du kan ikke blande vedlegg som er bilder med vedlegg som ikke er bilder i samme melding.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Avslutt", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Legg inn navn eller nummer", + "search": { + "message": "Søk", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "Ingen resultater for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Samtaler", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Kontakter", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Meldinger", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Velkommen til Signal!", "description": "" }, @@ -673,6 +727,10 @@ "message": "Postboks", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Laster ned", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Last ned Vedlegg", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Bilde", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Til", + "message": "til", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Generelt", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Slå på stavekontroll av tekst du skriver i meldingsvinduet", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Denne versjonen av Signal Desktop er foreldet. Oppgrader til siste versjon for å fortsette.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Android-klienter mottar bare de første 2000 tegnene i denne meldingen.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Oppgrader", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Mørkt", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Notat til meg selv", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Skjul menylinje", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Start samtale...", + "message": "Start en ny samtale...", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Vilkår og personvernerklæring", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/nl/messages.json b/_locales/nl/messages.json index 5b86c8d8bf..70739c8755 100644 --- a/_locales/nl/messages.json +++ b/_locales/nl/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Foutmelding kopiëren en afsluiten", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Onbekende groep", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Databasefout", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Alle gegevens wissen en herstarten", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Bestand", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -64,7 +80,7 @@ "description": "Edit menu command to insert text from clipboard at cursor location, taking only text and not style information" }, "editMenuDelete": { - "message": "Verwijderen", + "message": "Wissen", "description": "Edit menu command to remove the selected text" }, "editMenuSelectAll": { @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Gearchiveerde gesprekken", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Deze gesprekken worden gearchiveerd en zullen alleen in Postvak IN verschijnen als je nieuwe berichten ontvangt.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Gesprek archiveren", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Gesprek verplaatsen naar Postvak IN", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Map kiezen", "description": "Button to allow the user to find a folder on disk" @@ -174,7 +206,7 @@ "description": "Header of screen shown as data is import" }, "importErrorFirst": { - "message": "Zorg dat je de juiste map met je opgeslagen Signal-gegevens kiest. De naam begint normaal gezien met ‘Signal Export’. Je kan ook een nieuwe kopie van je gegevens uit de Chrome-app opslaan.", + "message": "Zorg dat je de juiste map met je opgeslagen Signal-gegevens kiest. De naam begint normaal gezien met ‘Signal Export’. Je kunt ook een nieuwe kopie van je gegevens uit de Chrome-app opslaan.", "description": "Message shown if the import went wrong; first paragraph" }, "importErrorSecond": { @@ -186,7 +218,7 @@ "description": "Button shown if the user runs into an error during import, allowing them to start over" }, "importCompleteHeader": { - "message": "Klaar!", + "message": "Geslaagd!", "description": "Header shown on the screen at the end of a successful import process" }, "importCompleteStartButton": { @@ -254,7 +286,7 @@ } }, "youMarkedAsVerified": { - "message": "Je hebt aangegeven dat je veiligheidsnummer met $name$is geverifieerd ", + "message": "Je hebt het veiligheidsnummer met $name$ als geverifieerd gemarkeerd", "description": "Shown in the conversation history when the user marks a contact as verified.", "placeholders": { "name": { @@ -264,7 +296,7 @@ } }, "youMarkedAsNotVerified": { - "message": "Je hebt het veiligheidsnummer met $name$ als niet geverifiëerd aangegeven", + "message": "Je hebt het veiligheidsnummer met $name$ als niet geverifieerd gemarkeerd", "description": "Shown in the conversation history when the user marks a contact as not verified, whether on the Safety Number screen or by dismissing a banner or dialog.", "placeholders": { "name": { @@ -274,7 +306,7 @@ } }, "youMarkedAsVerifiedOtherDevice": { - "message": "Je hebt het veiligheidsnummer met $name$ geverifiëerd vanaf een ander apparaat", + "message": "Je hebt het veiligheidsnummer met $name$ vanaf een ander apparaat als geverifieerd gemarkeerd", "description": "Shown in the conversation history when we discover that the user marked a contact as verified on another device.", "placeholders": { "name": { @@ -284,7 +316,7 @@ } }, "youMarkedAsNotVerifiedOtherDevice": { - "message": "Je hebt het veiligheidsnummer met $name$ als niet geverifiëerd aangegeven vanaf een ander apparaat", + "message": "Je hebt het veiligheidsnummer met $name$ vanaf een ander apparaat als niet geverifieerd gemarkeerd", "description": "Shown in the conversation history when we discover that the user marked a contact as not verified on another device.", "placeholders": { "name": { @@ -294,15 +326,15 @@ } }, "membersNeedingVerification": { - "message": "Je veiligheidsnummers met deze groepsleden zijn veranderd sinds de laatste verificatie. Klik op een groepslid om je nieuwe veiligheidsnummer met hen te bekijken.", + "message": "Je veiligheidsnummers met deze groepsleden zijn veranderd sinds de laatste verificatie. Klik op een groepslid om je nieuwe veiligheidsnummer met hem te bekijken.", "description": "When there are multiple previously-verified group members with safety number changes, a banner will be shown. The list of contacts with safety number changes is shown, and this text introduces that list." }, "changedSinceVerifiedMultiple": { - "message": "Je veiligheidsnummers met meerdere groepsleden zijn veranderd sinds de laatste verificatie. Dit kan betekenen dat iemand je gesprekken probeert te onderscheppen, of gewoonweg dat ze Signal opnieuw hebben geïnstalleerd.", + "message": "Je veiligheidsnummers met meerdere groepsleden zijn veranderd sinds de laatste verificatie. Dit kan betekenen dat iemand je gesprekken probeert te onderscheppen, of gewoonweg dat sommige groepsleden Signal opnieuw hebben geïnstalleerd.", "description": "Shown on confirmation dialog when user attempts to send a message" }, "changedSinceVerified": { - "message": "Je veiligheidsnummer met $name$ is veranderd sinds de laatste verificatie. Dit kan betekenen dat iemand je gesprekken probeert te onderscheppen, of gewoonweg dat $name$ opnieuw heeft geïnstalleerd.", + "message": "Je veiligheidsnummer met $name$ is veranderd sinds de laatste verificatie. Dit kan betekenen dat iemand je gesprekken probeert te onderscheppen, of gewoonweg dat $name$ Signal opnieuw heeft geïnstalleerd.", "description": "Shown on confirmation dialog when user attempts to send a message", "placeholders": { "name": { @@ -322,7 +354,7 @@ } }, "changedRecentlyMultiple": { - "message": "Je veiligheidsnummers met meerdere groepsleden zijn onlangs veranderd. Dit kan betekenen dat iemand je gesprekken probeert te onderscheppen, of gewoonweg dat ze Signal opnieuw hebben geïnstalleerd.", + "message": "Je veiligheidsnummers met meerdere groepsleden zijn recentelijk veranderd. Dit kan betekenen dat iemand je gesprekken probeert te onderscheppen, of gewoonweg dat sommige groepsleden Signal opnieuw hebben geïnstalleerd.", "description": "Shown on confirmation dialog when user attempts to send a message" }, "changedRecently": { @@ -336,7 +368,7 @@ } }, "identityKeyErrorOnSend": { - "message": "Je veiligheidsnummer met $name$ is veranderd. Dit kan betekenen dat iemand je gesprekken probeert te onderscheppen, of gewoonweg dat $name$ Signal opnieuw heeft geïnstalleerd. Je verifieert best je nieuwe veiligheidsnummer met dit contact.", + "message": "Je veiligheidsnummer met $name$ is veranderd. Dit kan betekenen dat iemand je gesprekken probeert te onderscheppen, of gewoonweg dat $name$ Signal opnieuw heeft geïnstalleerd. Het wordt aanbevolen je nieuwe veiligheidsnummer met dit contactpersoon te verifiëren.", "description": "Shown when user clicks on a failed recipient in the message detail view after an identity key change", "placeholders": { "name": { @@ -350,7 +382,7 @@ "description": "Used on a warning dialog to make it clear that it might be risky to send the message." }, "noLongerVerified": { - "message": "Je veiligheidsnummer met $name$ is veranderd en niet meer geverifieerd. Klik om het te tonen.", + "message": "Je veiligheidsnummer met $name$ is veranderd en het is daarom niet langer geverifieerd. Klik om het te tonen.", "description": "Shown in converation banner when user's safety number has changed, but they were previously verified.", "placeholders": { "name": { @@ -360,7 +392,7 @@ } }, "multipleNoLongerVerified": { - "message": "Je veiligheidsnummers met meerdere leden van deze groep zijn veranderd en bijgevolg niet meer geverifieerd. Klik om deze te tonen.", + "message": "Je veiligheidsnummers met meerdere leden van deze groep zijn veranderd en ze zijn daarom niet langer geverifieerd. Klik om de veiligheidsnummers te tonen.", "description": "Shown in conversation banner when more than one group member's safety number has changed, but they were previously verified." }, "debugLogExplanation": { @@ -424,7 +456,7 @@ "description": "Header for a key change dialog" }, "identityChanged": { - "message": "Je veiligheidsnummer met dit contact is veranderd. Dit kan betekenen dat iemand je gesprekken probeert te onderscheppen, of gewoonweg dat je contact Signal opnieuw heeft geïnstalleerd. Je verifieert best het nieuwe veiligheidsnummer hieronder.", + "message": "Je veiligheidsnummer met deze contactpersoon is veranderd. Dit kan betekenen dat iemand je gesprekken probeert te onderscheppen, of gewoonweg dat je contactpersoon Signal opnieuw heeft geïnstalleerd. Het wordt aanbevolen om het nieuwe veiligheidsnummer hieronder met deze persoon te verifiëren.", "description": "" }, "incomingError": { @@ -521,7 +553,7 @@ "message": "Wanneer je een bijlage insluit die geen afbeelding is, is de limiet één bijlage per bericht.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "Je kunt geen combinatie van afbeeldingen en niet-afbeeldingen insluiten als bijlagen van één bericht.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,20 +643,42 @@ "message": "Afsluiten", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Voer naam of nummer in", + "search": { + "message": "Zoeken", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "Geen resultaten voor ‘$searchTerm$’", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Gesprekken", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Contacten", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Berichten", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Welkom bij Signal", "description": "" }, "selectAContact": { - "message": "Kies een contact of groep om te starten met chatten.", + "message": "Selecteer een contactpersoon of groep om een gesprek te openen.", "description": "" }, "typingAlt": { @@ -673,6 +727,10 @@ "message": "postbus", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Wordt gedownload", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Bijlage downloaden", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -702,7 +760,7 @@ "description": "In Android theme, shown in quote if you or someone else replies to you" }, "replyingTo": { - "message": "Antwoord op $name$", + "message": "In reactie op $name$", "description": "Shown in iOS theme when you or someone quotes to a message which is not from you", "placeholders": { "name": { @@ -735,8 +793,16 @@ "message": "Foto", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { - "message": "Oké", + "message": "Begrepen", "description": "" }, "cancel": { @@ -756,15 +822,15 @@ "description": "" }, "delete": { - "message": "Verwijderen", + "message": "Wissen", "description": "" }, "deleteWarning": { - "message": "Weet je het zeker? Door op ‘Verwijderen’ te klikken, wordt dit bericht voorgoed van enkel dit apparaat verwijderd.", + "message": "Weet je het zeker? Door op ‘Wissen’ te klikken, wordt dit bericht voorgoed van enkel dit apparaat gewist.", "description": "" }, "deleteThisMessage": { - "message": "Dit bericht verwijderen", + "message": "Dit bericht wissen", "description": "" }, "from": { @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Naar", + "message": "aan", "description": "Label for the receiver of a message" }, "sent": { @@ -818,7 +884,7 @@ } }, "theirIdentityUnknown": { - "message": "Je hebt nog geen berichten uitgewisseld met dit contact. Je veiligheidsnummer met hen zal beschikbaar zijn na het eerste bericht.", + "message": "Je hebt nog geen berichten uitgewisseld met deze contactpersoon. Je veiligheidsnummer met hem zal beschikbaar zijn na het eerste bericht.", "description": "" }, "moreInfo": { @@ -826,19 +892,19 @@ "description": "Shown on the drop-down menu for an individual message, takes you to message detail screen" }, "retrySend": { - "message": "Opnieuw proberen verzenden", + "message": "Opnieuw proberen te verzenden", "description": "Shown on the drop-down menu for an indinvidaul message, but only if it is an outgoing message that failed to send" }, "deleteMessage": { - "message": "Bericht verwijderen", + "message": "Bericht wissen", "description": "Shown on the drop-down menu for an individual message, deletes single message" }, "deleteMessages": { - "message": "Berichten verwijderen", + "message": "Berichten wissen", "description": "Menu item for deleting messages, title case." }, "deleteConversationConfirmation": { - "message": "Dit gesprek voorgoed verwijderen?", + "message": "Dit gesprek voorgoed wissen?", "description": "Confirmation dialog text that asks the user if they really wish to delete the conversation. Answer buttons use the strings 'ok' and 'cancel'. The deletion is permanent, i.e. it cannot be undone." }, "sessionEnded": { @@ -878,7 +944,7 @@ "description": "Used in the media gallery documents tab to visually represent a file" }, "emojiAlt": { - "message": "Emoji-afbeelding van ‘$title$’", + "message": "Emoji-afbeelding ‘$title$’", "description": "Used in the alt tag of all emoji images", "placeholders": { "title": { @@ -959,16 +1025,8 @@ "message": "Algemeen", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Voorbeeldweergaven verzenden", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Voorbeelden worden ondersteund voor koppelingen naar Imgur, Instagram, Reddit en YouTube.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { - "message": "Gebruik een spellingscontrole voor de tekst in het berichtinvoerveld", + "message": "Gebruik spellingscontrole voor de tekst in het berichtinvoerveld", "description": "Description of the media permission description" }, "clearDataHeader": { @@ -984,15 +1042,15 @@ "description": "Button in the settings dialog starting process to delete all data" }, "deleteAllDataHeader": { - "message": "Alle gegevens verwijderen?", + "message": "Alle gegevens wissen?", "description": "Header of the full-screen delete data confirmation screen" }, "deleteAllDataBody": { - "message": "Je staat op het punt alle opgeslagen accountgegevens van deze toepassing te verwijderen, inclusief alle contacten en alle berichten. Je kan altijd opnieuw koppelen met je mobiele apparaat, maar je verwijderde berichten kunnen niet hersteld worden.", + "message": "Je staat op het punt alle opgeslagen accountgegevens van deze toepassing te wissen, inclusief alle contacten en alle berichten. Je kunt altijd opnieuw koppelen met je mobiele apparaat, maar je gewiste berichten kunnen niet hersteld worden.", "description": "Text describing what exactly will happen if the user clicks the button to delete all data" }, "deleteAllDataButton": { - "message": "Alle gegevens verwijderen", + "message": "Alle gegevens wissen", "description": "Text of the button that deletes all data" }, "deleteAllDataProgress": { @@ -1063,10 +1121,6 @@ "message": "Deze versie van Signal Desktop is verouderd. Opwaardeer naar de laatste versie om verder te chatten.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Contacten die Signal op een Android-telefoon gebruiken zullen enkel de eerste 2000 tekens van dit bericht kunnen lezen.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Opwaarderen", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Donker", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Notitie aan mezelf", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Menubalk verbergen", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Begin een gesprek…", + "message": "Begin een nieuw gesprek…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1390,7 +1448,7 @@ "description": "When a person inputs a number that is invalid" }, "unlinkedWarning": { - "message": "Herkoppel Signal Desktop met je mobiel apparaat om verder te kunnen chatten.", + "message": "Koppel Signal Desktop opnieuw aan je mobiele apparaat om verder te kunnen chatten.", "description": "" }, "unlinked": { @@ -1398,7 +1456,7 @@ "description": "" }, "relink": { - "message": "Herkoppelen", + "message": "Opnieuw koppelen", "description": "" }, "autoUpdateNewVersionTitle": { @@ -1422,7 +1480,7 @@ "description": "" }, "leftTheGroup": { - "message": "$name$heeft de groep verlaten", + "message": "$name$ heeft de groep verlaten", "description": "Shown in the conversation history when a single person leaves the group", "placeholders": { "name": { @@ -1442,11 +1500,11 @@ } }, "updatedTheGroup": { - "message": "De groep is bijgewerkt", + "message": "De groep is aangepast", "description": "Shown in the conversation history when someone updates the group" }, "titleIsNow": { - "message": "De titel is nu \"$name$\"", + "message": "De titel is nu “$name$”", "description": "Shown in the conversation history when someone changes the title of the group", "placeholders": { "name": { @@ -1456,7 +1514,7 @@ } }, "joinedTheGroup": { - "message": "$name$is lid geworden van de groep", + "message": "$name$ is lid geworden van de groep", "description": "Shown in the conversation history when a single person joins the group", "placeholders": { "name": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Gebruiksvoorwaarden & privacybeleid", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/nn/messages.json b/_locales/nn/messages.json new file mode 100644 index 0000000000..b169d1f371 --- /dev/null +++ b/_locales/nn/messages.json @@ -0,0 +1,1536 @@ +{ + "copyErrorAndQuit": { + "message": "Kopier feil og avslutt", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Databasefeil", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Slett all data og start på nytt", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, + "mainMenuFile": { + "message": "&Fil", + "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." + }, + "mainMenuEdit": { + "message": "&Rediger", + "description": "The label that is used for the Edit menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." + }, + "mainMenuView": { + "message": "Vi&s", + "description": "The label that is used for the View menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." + }, + "mainMenuWindow": { + "message": "&Vindauge", + "description": "The label that is used for the Window menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." + }, + "mainMenuHelp": { + "message": "&Hjelp", + "description": "The label that is used for the Help menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." + }, + "mainMenuSettings": { + "message": "Innstillingar …", + "description": "The label that is used for the Preferences menu in the program main menu. This should be consistent with the standard naming for ‘Preferences’ on the operating system." + }, + "appMenuHide": { + "message": "Skjul", + "description": "Application menu command to hide the window" + }, + "appMenuHideOthers": { + "message": "Gøym andre", + "description": "Application menu command to hide all other windows" + }, + "appMenuUnhide": { + "message": "Vis alle", + "description": "Application menu command to show all application windows" + }, + "appMenuQuit": { + "message": "Avslutt Signal", + "description": "Application menu command to close the application" + }, + "editMenuUndo": { + "message": "Angra", + "description": "Edit menu command to remove recently-typed text" + }, + "editMenuRedo": { + "message": "Gjenopprett", + "description": "Edit menu command to restore previously undone typed text" + }, + "editMenuCut": { + "message": "Klipp ut", + "description": "Edit menu command to remove selected text and add it to clipboard" + }, + "editMenuCopy": { + "message": "Kopier", + "description": "Edit menu command to add selected text to clipboard" + }, + "editMenuPaste": { + "message": "Lim inn", + "description": "Edit menu command to insert text from clipboard at cursor location" + }, + "editMenuPasteAndMatchStyle": { + "message": "Lim inn utan formatering", + "description": "Edit menu command to insert text from clipboard at cursor location, taking only text and not style information" + }, + "editMenuDelete": { + "message": "Slett", + "description": "Edit menu command to remove the selected text" + }, + "editMenuSelectAll": { + "message": "Vel alt", + "description": "Edit menu comand to select all of the text in selected text box" + }, + "editMenuStartSpeaking": { + "message": "Begynn å snakka", + "description": "Edit menu item under 'speech' to start dictation" + }, + "editMenuStopSpeaking": { + "message": "Slutt å snakka", + "description": "Edit menu item under 'speech' to stop dictation" + }, + "windowMenuClose": { + "message": "Lukk vindauget", + "description": "Window menu command to close the current window" + }, + "windowMenuMinimize": { + "message": "Minimer", + "description": "Window menu command to minimize the current window" + }, + "windowMenuZoom": { + "message": "Zoom", + "description": "Window menu command to make the current window the size of the whole screen" + }, + "windowMenuBringAllToFront": { + "message": "Hent fram alle vindauge", + "description": "Window menu command to bring all windows of current applicatinon to front" + }, + "viewMenuResetZoom": { + "message": "Faktisk storleik", + "description": "View menu command to go back to the default zoom" + }, + "viewMenuZoomIn": { + "message": "Zoom inn", + "description": "View menu command to make everything bigger" + }, + "viewMenuZoomOut": { + "message": "Zoom ut", + "description": "View menu command to make everything smaller" + }, + "viewMenuToggleFullScreen": { + "message": "Skru av/på fullskjerm", + "description": "View menu command to enter or leave Full Screen mode" + }, + "viewMenuToggleDevTools": { + "message": "Skru av/på utviklarverktøy", + "description": "View menu command to show or hide the developer tools" + }, + "menuSetupWithImport": { + "message": "Set opp med import", + "description": "When the application is not yet set up, menu option to start up the import sequence" + }, + "menuSetupAsNewDevice": { + "message": "Set opp som ny eining", + "description": "When the application is not yet set up, menu option to start up the set up as fresh device" + }, + "menuSetupAsStandalone": { + "message": "Set opp som frittståande eining", + "description": "Only available on development modes, menu option to open up the standalone device setup sequence" + }, + "loading": { + "message": "Lastar …", + "description": "Message shown on the loading screen before we've loaded any messages" + }, + "optimizingApplication": { + "message": "Optimaliserer applikasjon …", + "description": "Message shown on the loading screen while we are doing application optimizations" + }, + "migratingToSQLCipher": { + "message": "Optimaliserer meldingar … $status$fullført.", + "description": "Message shown on the loading screen while we are doing application optimizations", + "placeholders": { + "status": { + "content": "$1", + "example": "45/200" + } + } + }, + "archivedConversations": { + "message": "Archived Conversations", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "These conversations are archived and will only appear in the Inbox if new messages are received.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, + "chooseDirectory": { + "message": "Vel mappe", + "description": "Button to allow the user to find a folder on disk" + }, + "chooseFile": { + "message": "Vel fil", + "description": "Button to allow the user to find a file on disk" + }, + "loadDataHeader": { + "message": "Last inn dine data", + "description": "Header shown on the first screen in the data import process" + }, + "loadDataDescription": { + "message": "Du har akkurat gått gjennom eksportprosessen; kontaktane og meldingane dine ventar tålmodig på datamaskina di. Vel mappa som inneheld den lagra Signal-eksporten.", + "description": "Introduction to the process of importing messages and contacts from disk" + }, + "importChooserTitle": { + "message": "Vel mappa med eksporterte data", + "description": "Title of the popup window used to select data previously exported" + }, + "importErrorHeader": { + "message": "Noko gjekk gale!", + "description": "Header of the error screen after a failed import" + }, + "importingHeader": { + "message": "Lastar kontaktar og meldingar", + "description": "Header of screen shown as data is import" + }, + "importErrorFirst": { + "message": "Stadfest at du har valt mappa som inneheld den lagra Signal-eksporten din. Namnet skal byrja med «Signal Export». Du kan òg lagra ein ny kopi av eksporten frå den gamle Chrome-appen.", + "description": "Message shown if the import went wrong; first paragraph" + }, + "importErrorSecond": { + "message": "Viss desse stega ikkje fungerte for deg, send oss ein feilsøkingslogg (Vis -> Feilsøkingslogg) slik at vi kan hjelpa deg med migreringa.", + "description": "Message shown if the import went wrong; second paragraph" + }, + "importAgain": { + "message": "Vel mappe og prøv igjen", + "description": "Button shown if the user runs into an error during import, allowing them to start over" + }, + "importCompleteHeader": { + "message": "Fullført!", + "description": "Header shown on the screen at the end of a successful import process" + }, + "importCompleteStartButton": { + "message": "Start å bruka Signal Desktop", + "description": "Button shown at end of successful import process, nothing left but a restart" + }, + "importCompleteLinkButton": { + "message": "Kopla denne eininga til telefonen din", + "description": "Button shown at end of successful 'light' import process, so the standard linking process still needs to happen" + }, + "selectedLocation": { + "message": "din valde posisjon", + "description": "Message shown as the export location if we didn't capture the target directory" + }, + "upgradingDatabase": { + "message": "Oppgraderer database. Dette kan ta litt tid …", + "description": "Message shown on the loading screen when we're changing database structure on first run of a new version" + }, + "loadingMessages": { + "message": "Lastar meldingar. $count$så langt …", + "description": "Message shown on the loading screen when we're catching up on the backlog of messages", + "placeholders": { + "count": { + "content": "$1", + "example": "5" + } + } + }, + "me": { + "message": "Meg", + "description": "The label for yourself when shown in a group member list" + }, + "view": { + "message": "Sjå", + "description": "Used as a label on a button allowing user to see more information" + }, + "youLeftTheGroup": { + "message": "Du forlét gruppa", + "description": "Displayed when a user can't send a message because they have left the group" + }, + "scrollDown": { + "message": "Rull til botnen av samtalen", + "description": "Alt text for button to take user down to bottom of conversation, shown when user scrolls up" + }, + "messageBelow": { + "message": "Ny melding nedanfor", + "description": "Alt text for button to take user down to bottom of conversation with a new message out of screen" + }, + "messagesBelow": { + "message": "Nye meldingar nedanfor", + "description": "Alt text for button to take user down to bottom of conversation with more than one message out of screen" + }, + "unreadMessage": { + "message": "1 ulesen melding", + "description": "Text for unread message separator, just one message" + }, + "unreadMessages": { + "message": "$count$ ulesne meldingar", + "description": "Text for unread message separator, with count", + "placeholders": { + "count": { + "content": "$1", + "example": "5" + } + } + }, + "youMarkedAsVerified": { + "message": "Du markerte tryggingsnummeret med $name$ som godkjent", + "description": "Shown in the conversation history when the user marks a contact as verified.", + "placeholders": { + "name": { + "content": "$1", + "example": "Bob" + } + } + }, + "youMarkedAsNotVerified": { + "message": "Du markerte tryggingsnummeret med $name$ som ikkje godkjent", + "description": "Shown in the conversation history when the user marks a contact as not verified, whether on the Safety Number screen or by dismissing a banner or dialog.", + "placeholders": { + "name": { + "content": "$1", + "example": "Bob" + } + } + }, + "youMarkedAsVerifiedOtherDevice": { + "message": "Du markerte tryggingsnummeret med $name$ som godkjent på ei anna eining", + "description": "Shown in the conversation history when we discover that the user marked a contact as verified on another device.", + "placeholders": { + "name": { + "content": "$1", + "example": "Bob" + } + } + }, + "youMarkedAsNotVerifiedOtherDevice": { + "message": "Du markerte tryggingsnummeret med $name$ som ikkje godkjent på ei anna eining", + "description": "Shown in the conversation history when we discover that the user marked a contact as not verified on another device.", + "placeholders": { + "name": { + "content": "$1", + "example": "Bob" + } + } + }, + "membersNeedingVerification": { + "message": "Tryggingsnummera dine med desse gruppemedlemmene er endra sidan førre gong du godkjente. Klikk på ein gruppemedlem for å sjå dei nye tryggingsnummera deira.", + "description": "When there are multiple previously-verified group members with safety number changes, a banner will be shown. The list of contacts with safety number changes is shown, and this text introduces that list." + }, + "changedSinceVerifiedMultiple": { + "message": "Tryggingsnummeret ditt med fleire gruppemedlemmer er endra sidan førre gong du godkjente. Dette kan bety at nokon prøver å avlytta kommunikasjonen, eller at dei berre har installert Signal på nytt.", + "description": "Shown on confirmation dialog when user attempts to send a message" + }, + "changedSinceVerified": { + "message": "Tryggingsnummeret ditt saman med $name$ har endra seg sidan sist dykk godkjende. Dette kan bety at nokon prøver å overvaka kommunikasjonen eller at $name$ heilt enkelt har installert Signal på nytt.", + "description": "Shown on confirmation dialog when user attempts to send a message", + "placeholders": { + "name": { + "content": "$1", + "example": "Bob" + } + } + }, + "changedRightAfterVerify": { + "message": "Tryggingsnummeret du prøver å godkjenna har endra seg. Du må godkjenna det nye tryggingsnummeret til $name$. Hugs at dette kan bety at nokon prøver å avlytta kommunikasjonen, eller at $name$ berre har reinstallert Signal.", + "description": "Shown on the safety number screen when the user has selected to verify/unverify a contact's safety number, and we immediately discover a safety number change", + "placeholders": { + "name": { + "content": "$1", + "example": "Bob" + } + } + }, + "changedRecentlyMultiple": { + "message": "Tryggingsnummera dine for fleire gruppemedlemmer er endra i det siste. Dette kan bety at nokon prøver å avlytta kommunikasjonen, eller at dei berre har reinstallert Signal.", + "description": "Shown on confirmation dialog when user attempts to send a message" + }, + "changedRecently": { + "message": "Tryggingsnummeret ditt med $name$ er endra i det siste. Dette kan bety at nokon prøver å avlytta kommunikasjonen, eller at $name$ berre har reinstallert Signal.", + "description": "Shown on confirmation dialog when user attempts to send a message", + "placeholders": { + "name": { + "content": "$1", + "example": "Bob" + } + } + }, + "identityKeyErrorOnSend": { + "message": "Tryggingsnummeret ditt med $name$ er endra. Dette kan enten bety at nokon avlyttar kommunikasjonen deira, eller at $name$ berre har reinstallert Signal. Du vil kanskje stadfesta tryggingsnummeret ditt med denne kontakten.", + "description": "Shown when user clicks on a failed recipient in the message detail view after an identity key change", + "placeholders": { + "name": { + "content": "$1", + "example": "Bob" + } + } + }, + "sendAnyway": { + "message": "Send likevel", + "description": "Used on a warning dialog to make it clear that it might be risky to send the message." + }, + "noLongerVerified": { + "message": "Tryggingsnummeret ditt med $name$ er endra og er ikkje lenger godkjent. Klikk for å visa nummeret.", + "description": "Shown in converation banner when user's safety number has changed, but they were previously verified.", + "placeholders": { + "name": { + "content": "$1", + "example": "Bob" + } + } + }, + "multipleNoLongerVerified": { + "message": "Tryggingsnummera dine med fleire medlemmer i gruppa er endra, og er ikkje lenger godkjende. Klikk for å visa.", + "description": "Shown in conversation banner when more than one group member's safety number has changed, but they were previously verified." + }, + "debugLogExplanation": { + "message": "Denne logg-meldinga vil bli publisert på nettet for deltakarar i prosjektet. Du kan sjå på og redigera ho før du sender ho inn.", + "description": "" + }, + "debugLogError": { + "message": "Noko gjekk gale med opplastinga! Fint om du vurderer å legga loggen til feilrapporten manuelt.", + "description": "" + }, + "reportIssue": { + "message": "Rapporter eit problem", + "description": "Link to open the issue tracker" + }, + "gotIt": { + "message": "Perfekt!", + "description": "Label for a button that dismisses a dialog. The user clicks it to confirm that they understand the message in the dialog." + }, + "submit": { + "message": "Send", + "description": "" + }, + "acceptNewKey": { + "message": "Godta", + "description": "Label for a button to accept a new safety number" + }, + "verify": { + "message": "Marker som godkjent", + "description": "" + }, + "unverify": { + "message": "Marker som ikkje godkjent", + "description": "" + }, + "isVerified": { + "message": "Du har godkjent tryggingsnummeret ditt med $name$.", + "description": "Summary state shown at top of the safety number screen if user has verified contact.", + "placeholders": { + "name": { + "content": "$1", + "example": "Bob" + } + } + }, + "isNotVerified": { + "message": "Du har ikkje godkjent tryggingsnummeret ditt med $name$.", + "description": "Summary state shown at top of the safety number screen if user has not verified contact.", + "placeholders": { + "name": { + "content": "$1", + "example": "Bob" + } + } + }, + "verified": { + "message": "Godkjent", + "description": "" + }, + "newIdentity": { + "message": "Nytt tryggingsnummer", + "description": "Header for a key change dialog" + }, + "identityChanged": { + "message": "Tryggingsnummeret ditt med denne kontakten er endra. Dette kan enten bety at nokon prøver å fanga opp kommunikasjonen din, eller denne kontakten kan ha installert Signal på nytt. Du vil kanskje godkjenna det nye tryggingsnummeret nedanfor.", + "description": "" + }, + "incomingError": { + "message": "Feil ved handtering av innkommande melding", + "description": "" + }, + "media": { + "message": "Media", + "description": "Header of the default pane in the media gallery, showing images and videos" + }, + "mediaEmptyState": { + "message": "Du har ikkje noko media i denne samtalen", + "description": "Message shown to user in the media gallery when there are no messages with media attachments (images or video)" + }, + "documents": { + "message": "Dokument", + "description": "Header of the secondary pane in the media gallery, showing every non-media attachment" + }, + "documentsEmptyState": { + "message": "Du har ingen dokument i denne samtalen", + "description": "Message shown to user in the media gallery when there are no messages with document attachments (anything other than images or video)" + }, + "today": { + "message": "I dag", + "description": "Section header in the media gallery" + }, + "yesterday": { + "message": "I går", + "description": "Section header in the media gallery" + }, + "thisWeek": { + "message": "Denne veka", + "description": "Section header in the media gallery" + }, + "thisMonth": { + "message": "Denne månaden", + "description": "Section header in the media gallery" + }, + "unsupportedAttachment": { + "message": "Vedleggstypen er ikkje støtta. Klikk for å lagra det.", + "description": "Displayed for incoming unsupported attachment" + }, + "clickToSave": { + "message": "Klikk for å lagra", + "description": "Hover text for attachment filenames" + }, + "unnamedFile": { + "message": "Namnlaus fil", + "description": "Hover text for attachment filenames" + }, + "voiceMessage": { + "message": "Talemelding", + "description": "Name for a voice message attachment" + }, + "dangerousFileType": { + "message": "Vedleggstypen er ikkje tillaten av tryggleiksårsaker", + "description": "Shown in toast when user attempts to send .exe file, for example" + }, + "loadingPreview": { + "message": "Lastar førehandsvisning …", + "description": "Shown while Signal Desktop is fetching metadata for a url in composition area" + }, + "stagedPreviewThumbnail": { + "message": "Førehandsvisning (kladd) av lenkje til $domain$", + "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", + "placeholders": { + "path": { + "content": "$1", + "example": "instagram.com" + } + } + }, + "previewThumbnail": { + "message": "Førehandsvisning av lenkje til $domain$", + "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", + "placeholders": { + "path": { + "content": "$1", + "example": "instagram.com" + } + } + }, + "stagedImageAttachment": { + "message": "Bildevedlegg (kladd): $path$", + "description": "Alt text for staged attachments", + "placeholders": { + "path": { + "content": "$1", + "example": "dog.jpg" + } + } + }, + "oneNonImageAtATimeToast": { + "message": "Når du inkluderer eit vedlegg som ikkje er eit bilde, er grensa eitt vedlegg per melding.", + "description": "An error popup when the user has attempted to add an attachment" + }, + "cannotMixImageAndNonImageAttachments": { + "message": "Du kan ikkje blanda vedlegg som er bilde med vedlegg som ikkje er bilde i same melding.", + "description": "An error popup when the user has attempted to add an attachment" + }, + "maximumAttachments": { + "message": "Du kan ikkje legga til fleire vedlegg i denne meldinga.", + "description": "An error popup when the user has attempted to add an attachment" + }, + "fileSizeWarning": { + "message": "Beklagar, den valde fila går over grensa for meldingsstorleik.", + "description": "" + }, + "unableToLoadAttachment": { + "message": "Kunne ikkje lasta vald vedlegg.", + "description": "" + }, + "disconnected": { + "message": "Kopla frå", + "description": "Displayed when the desktop client cannot connect to the server." + }, + "connecting": { + "message": "Koplar til", + "description": "Displayed when the desktop client is currently connecting to the server." + }, + "offline": { + "message": "Fråkopla", + "description": "Displayed when the desktop client has no network connection." + }, + "checkNetworkConnection": { + "message": "Sjekk nettverkstilkoplinga di.", + "description": "Obvious instructions for when a user's computer loses its network connection" + }, + "attemptingReconnection": { + "message": "Prøver å kopla til på nytt om $reconnect_duration_in_seconds$ sekund", + "description": "", + "placeholders": { + "reconnect_duration_in_seconds": { + "content": "$1", + "example": "10" + } + } + }, + "submitDebugLog": { + "message": "Feilsøkingslogg", + "description": "Menu item and header text for debug log modal (sentence case)" + }, + "debugLog": { + "message": "Feilsøkingslogg", + "description": "View menu item to open the debug log (title case)" + }, + "goToReleaseNotes": { + "message": "Sjå endringsloggen", + "description": "" + }, + "goToForums": { + "message": "Gå til foruma", + "description": "Item under the Help menu, takes you to the forums" + }, + "goToSupportPage": { + "message": "Gå til brukarstøttesida", + "description": "Item under the Help menu, takes you to the support page" + }, + "menuReportIssue": { + "message": "Rapporter eit problem", + "description": "Item under the Help menu, takes you to GitHub new issue form (title case)" + }, + "signalDesktopPreferences": { + "message": "Innstillingar for Signal Desktop", + "description": "Title of the window that pops up with Signal Desktop preferences in it" + }, + "aboutSignalDesktop": { + "message": "Om Signal Desktop", + "description": "Item under the Help menu, which opens a small about window" + }, + "speech": { + "message": "Tale", + "description": "Item under the Edit menu, with 'start/stop speaking' items below it" + }, + "show": { + "message": "Vis", + "description": "Command under Window menu, to show the window" + }, + "hide": { + "message": "Skjul", + "description": "Command in the tray icon menu, to hide the window" + }, + "quit": { + "message": "Avslutt", + "description": "Command in the tray icon menu, to quit the application" + }, + "signalDesktop": { + "message": "Signal Desktop", + "description": "Tooltip for the tray icon" + }, + "search": { + "message": "Søk", + "description": "Placeholder text in the search input" + }, + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Samtalar", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Kontakt", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Meldingar", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { + "message": "Velkommen til Signal!", + "description": "" + }, + "selectAContact": { + "message": "Vel ein kontakt eller ei gruppe for å prata", + "description": "" + }, + "typingAlt": { + "message": "Markør som viser at nokon held på å skriva i samtalen", + "description": "Used as the 'title' attibute for the typing animation" + }, + "contactAvatarAlt": { + "message": "Bilde for kontakt $name$", + "description": "Used in the alt tag for the image avatar of a contact", + "placeholders": { + "name": { + "content": "$1", + "example": "John" + } + } + }, + "sendMessageToContact": { + "message": "Send melding", + "description": "Shown when you are sent a contact and that contact has a signal account" + }, + "home": { + "message": "heim", + "description": "Shown on contact detail screen as a label for an address/phone/email" + }, + "work": { + "message": "jobb", + "description": "Shown on contact detail screen as a label for an address/phone/email" + }, + "mobile": { + "message": "mobil", + "description": "Shown on contact detail screen as a label for aa phone or email" + }, + "email": { + "message": "e-post", + "description": "Generic label shown if contact email has custom type but no label" + }, + "phone": { + "message": "telefon", + "description": "Generic label shown if contact phone has custom type but no label" + }, + "address": { + "message": "adresse", + "description": "Generic label shown if contact address has custom type but no label" + }, + "poBox": { + "message": "postboks", + "description": "When rendering an address, used to provide context to a post office box" + }, + "downloading": { + "message": "Lastar ned", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, + "downloadAttachment": { + "message": "Last ned vedlegg", + "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" + }, + "replyToMessage": { + "message": "Svar på meldinga", + "description": "Shown in triple-dot menu next to message to allow user to start crafting a message with a quotation" + }, + "originalMessageNotFound": { + "message": "Fann ikkje den opphavlege meldinga", + "description": "Shown in quote if reference message was not found as message was initially downloaded and processed" + }, + "originalMessageNotAvailable": { + "message": "Den opphavlege meldinga er ikkje tilgjengeleg lenger", + "description": "Shown in toast if user clicks on quote that references message no longer in database" + }, + "messageFoundButNotLoaded": { + "message": "Fann den opphavlege meldinga, men klarte ikkje å lasta ho. Rull opp for å lasta inn.", + "description": "Shown in toast if user clicks on quote references messages not loaded in view, but in database" + }, + "voiceNoteMustBeOnlyAttachment": { + "message": "Ei talemelding må vera einaste vedlegg i ei melding.", + "description": "Shown in toast if tries to record a voice note with any staged attachments" + }, + "you": { + "message": "Du", + "description": "In Android theme, shown in quote if you or someone else replies to you" + }, + "replyingTo": { + "message": "Svarer $name$", + "description": "Shown in iOS theme when you or someone quotes to a message which is not from you", + "placeholders": { + "name": { + "content": "$1", + "example": "John" + } + } + }, + "audioPermissionNeeded": { + "message": "Gi Signal Desktop tilgang til mikrofonen din for å senda lydmeldingar.", + "description": "Shown if the user attempts to send an audio message without audio permssions turned on" + }, + "allowAccess": { + "message": "Gi tilgang", + "description": "Button shown in popup asking to enable microphon/video permissions to send audio messages" + }, + "showSettings": { + "message": "Vis innstillngar", + "description": "A button shown in dialog requesting the user to turn on audio permissions" + }, + "audio": { + "message": "Lyd", + "description": "Shown in a quotation of a message containing an audio attachment if no text was originally provided with that attachment" + }, + "video": { + "message": "Video", + "description": "Shown in a quotation of a message containing a video if no text was originally provided with that video" + }, + "photo": { + "message": "Bilde", + "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" + }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, + "ok": { + "message": "OK", + "description": "" + }, + "cancel": { + "message": "Avbryt", + "description": "" + }, + "failedToSend": { + "message": "Kunne ikkje senda til visse mottakarar. Sjekk nettverkstilkoplinga.", + "description": "" + }, + "error": { + "message": "Feil", + "description": "" + }, + "messageDetail": { + "message": "Meldingsdetaljar", + "description": "" + }, + "delete": { + "message": "Slett", + "description": "" + }, + "deleteWarning": { + "message": "Er du sikker? Om du trykker «slett», vil meldinga fjernast frå berre denne eininga.", + "description": "" + }, + "deleteThisMessage": { + "message": "Slett denne meldinga", + "description": "" + }, + "from": { + "message": "Frå", + "description": "Label for the sender of a message" + }, + "to": { + "message": "to", + "description": "Label for the receiver of a message" + }, + "sent": { + "message": "Sendt", + "description": "Label for the time a message was sent" + }, + "received": { + "message": "Motteken", + "description": "Label for the time a message was received" + }, + "sendMessage": { + "message": "Send ei melding", + "description": "Placeholder text in the message entry field" + }, + "groupMembers": { + "message": "Gruppemedlemmer", + "description": "" + }, + "showMembers": { + "message": "Vis medlemmer", + "description": "" + }, + "resetSession": { + "message": "Nullstill økta", + "description": "This is a menu item for resetting the session, using the imperative case, as in a command." + }, + "showSafetyNumber": { + "message": "Vis tryggingsnummer", + "description": "" + }, + "viewAllMedia": { + "message": "Sjå all media", + "description": "This is a menu item for viewing all media (images + video) in a conversation, using the imperative case, as in a command." + }, + "verifyHelp": { + "message": "Viss du vil stadfesta sikkerheita på ende-til-ende-krypteringa med $name$, kan du samanlikna tala over med tala på eininga di.", + "description": "", + "placeholders": { + "name": { + "content": "$1", + "example": "John" + } + } + }, + "theirIdentityUnknown": { + "message": "Du har ikkje utveksla nokon meldingar med denne kontakten enno. Tryggingsnummeret ditt med dei vil vera tilgjengeleg etter den første meldinga.", + "description": "" + }, + "moreInfo": { + "message": "Meir info …", + "description": "Shown on the drop-down menu for an individual message, takes you to message detail screen" + }, + "retrySend": { + "message": "Send på nytt", + "description": "Shown on the drop-down menu for an indinvidaul message, but only if it is an outgoing message that failed to send" + }, + "deleteMessage": { + "message": "Slett meldinga", + "description": "Shown on the drop-down menu for an individual message, deletes single message" + }, + "deleteMessages": { + "message": "Slett meldingar", + "description": "Menu item for deleting messages, title case." + }, + "deleteConversationConfirmation": { + "message": "Vil du sletta denne samtalen?", + "description": "Confirmation dialog text that asks the user if they really wish to delete the conversation. Answer buttons use the strings 'ok' and 'cancel'. The deletion is permanent, i.e. it cannot be undone." + }, + "sessionEnded": { + "message": "Sikker økt nullstillt", + "description": "This is a past tense, informational message. In other words, your secure session has been reset." + }, + "quoteThumbnailAlt": { + "message": "Miniatyrbilde i sitert melding", + "description": "Used in alt tag of thumbnail images inside of an embedded message quote" + }, + "imageAttachmentAlt": { + "message": "Bilde lagt ved i melding", + "description": "Used in alt tag of image attachment" + }, + "videoAttachmentAlt": { + "message": "Skjermbilde av video vedlagt i melding", + "description": "Used in alt tag of video attachment preview" + }, + "lightboxImageAlt": { + "message": "Bilde sendt i samtale", + "description": "Used in the alt tag for the image shown in a full-screen lightbox view" + }, + "imageCaptionIconAlt": { + "message": "Ikon som viser at bildet har bildetekst", + "description": "Used for the icon layered on top of an image in message bubbles" + }, + "addACaption": { + "message": "Legg til bildetekst …", + "description": "" + }, + "save": { + "message": "Lagra", + "description": "" + }, + "fileIconAlt": { + "message": "Filikon", + "description": "Used in the media gallery documents tab to visually represent a file" + }, + "emojiAlt": { + "message": "Emoji-bilde av «$title$»", + "description": "Used in the alt tag of all emoji images", + "placeholders": { + "title": { + "content": "$1", + "example": "grinning" + } + } + }, + "installWelcome": { + "message": "Velkommen til Signal Desktop", + "description": "Welcome title on the install page" + }, + "installTagline": { + "message": "Personvern er mogleg. Signal gjer det enkelt.", + "description": "Tagline displayed under 'installWelcome' string on the install page" + }, + "linkYourPhone": { + "message": "Kopla til telefonen din til Signal Desktop", + "description": "Shown on the front page when the application first starst, above the QR code" + }, + "signalSettings": { + "message": "Instillingar for Signal", + "description": "Used in the guidance to help people find the 'link new device' area of their Signal mobile app" + }, + "linkedDevices": { + "message": "Tilkopla einingar", + "description": "Used in the guidance to help people find the 'link new device' area of their Signal mobile app" + }, + "plusButton": { + "message": "«+»-knapp", + "description": "The button used in Signal Android to add a new linked device" + }, + "linkNewDevice": { + "message": "Kopla til ei ny eining", + "description": "The menu option shown in Signal iOS to add a new linked device" + }, + "deviceName": { + "message": "Einingsnamn", + "description": "The label in settings panel shown for the user-provided name for this desktop instance" + }, + "chooseDeviceName": { + "message": "Vel namnet til denne eininga", + "description": "The header shown on the 'choose device name' screen in the device linking process" + }, + "finishLinkingPhone": { + "message": "Fullfør tilkoplinga av telefon", + "description": "The text on the button to finish the linking process, after choosing the device name" + }, + "initialSync": { + "message": "Synkroniserer kontaktar og grupper", + "description": "Shown during initial link while contacts and groups are being pulled from mobile device" + }, + "installConnectionFailed": { + "message": "Kunne ikkje kopla til tenaren.", + "description": "Displayed when we can't connect to the server." + }, + "installTooManyDevices": { + "message": "Beklagar. Du har for mange einingar tilkopla. Prøv å fjerna nokre.", + "description": "" + }, + "settings": { + "message": "Innstillingar", + "description": "Menu item and header for global settings" + }, + "theme": { + "message": "Tema", + "description": "Header for theme settings" + }, + "permissions": { + "message": "Tillatingar", + "description": "Header for permissions section of settings" + }, + "mediaPermissionsDescription": { + "message": "Tillat tilgang til kamera og mikrofon", + "description": "Description of the media permission description" + }, + "general": { + "message": "Generelt", + "description": "Header for general options on the settings screen" + }, + "spellCheckDescription": { + "message": "Slå på stavekontroll av tekst du skriv i meldingsvindauget", + "description": "Description of the media permission description" + }, + "clearDataHeader": { + "message": "Slett data", + "description": "Header in the settings dialog for the section dealing with data deletion" + }, + "clearDataExplanation": { + "message": "Dette vil sletta all data i appen, inkludert alle meldingar og lagra kontoinformasjon.", + "description": "Text describing what the clear data button will do." + }, + "clearDataButton": { + "message": "Slett data", + "description": "Button in the settings dialog starting process to delete all data" + }, + "deleteAllDataHeader": { + "message": "Slett all data?", + "description": "Header of the full-screen delete data confirmation screen" + }, + "deleteAllDataBody": { + "message": "Du er i ferd med å sletta alt av den lagra brukarinformasjonen til denne applikasjonen, inkludert alle kontaktar og meldingar. Du kan alltids kopla til telefonen din igjen, men dette vil ikkje retta opp igjen sletta meldingar.", + "description": "Text describing what exactly will happen if the user clicks the button to delete all data" + }, + "deleteAllDataButton": { + "message": "Slett all data", + "description": "Text of the button that deletes all data" + }, + "deleteAllDataProgress": { + "message": "Koplar frå og slettar all data", + "description": "Message shown to user when app is disconnected and data deleted" + }, + "notifications": { + "message": "Varslingar", + "description": "Header for notification settings" + }, + "notificationSettingsDialog": { + "message": "Når meldingar kjem, vis varslingar som avslører:", + "description": "Explain the purpose of the notification settings" + }, + "disableNotifications": { + "message": "Deaktiver varslingar", + "description": "Label for disabling notifications" + }, + "nameAndMessage": { + "message": "Både namn og melding", + "description": "Label for setting notifications to display name and message text" + }, + "noNameOrMessage": { + "message": "Verken namn eller melding", + "description": "Label for setting notifications to display no name and no message text" + }, + "nameOnly": { + "message": "Berre namn", + "description": "Label for setting notifications to display sender name only" + }, + "newMessage": { + "message": "Ny melding", + "description": "Displayed in notifications for only 1 message" + }, + "newMessages": { + "message": "Nye meldingar", + "description": "Displayed in notifications for multiple messages" + }, + "notificationMostRecentFrom": { + "message": "Nyaste frå:", + "description": "Displayed in notifications when setting is 'name only' and more than one message is waiting" + }, + "notificationFrom": { + "message": "Frå:", + "description": "Displayed in notifications when setting is 'name only' and one message is waiting" + }, + "notificationMostRecent": { + "message": "Siste:", + "description": "Displayed in notifications when setting is 'name and message' and more than one message is waiting" + }, + "sendFailed": { + "message": "Sending feila", + "description": "Shown on outgoing message if it fails to send" + }, + "showMore": { + "message": "Detaljar", + "description": "Displays the details of a key change" + }, + "showLess": { + "message": "Skjul detaljar", + "description": "Hides the details of a key change" + }, + "learnMore": { + "message": "Lær meir om å godkjenna tryggingsnummer", + "description": "Text that links to a support article on verifying safety numbers" + }, + "expiredWarning": { + "message": "Denne utgåva av Signal Desktop er forelda. Oppgrader til siste utgåve for å fortsetja.", + "description": "Warning notification that this version of the app has expired" + }, + "upgrade": { + "message": "Oppgrader", + "description": "Label text for button to upgrade the app to the latest version" + }, + "mediaMessage": { + "message": "Mediemelding", + "description": "Description of a message that has an attachment and no text, displayed in the conversation list as a preview." + }, + "unregisteredUser": { + "message": "Nummeret er ikkje registrert", + "description": "Error message displayed when sending to an unregistered user." + }, + "sync": { + "message": "Kontakt", + "description": "Label for contact and group sync settings" + }, + "syncExplanation": { + "message": "Importer alle Signal-grupper og kontaktar frå den mobile eininga di.", + "description": "Explanatory text for sync settings" + }, + "lastSynced": { + "message": "Siste import var", + "description": "Label for date and time of last sync operation" + }, + "syncNow": { + "message": "Importer no", + "description": "Label for a button that syncs contacts and groups from your phone" + }, + "syncing": { + "message": "Importerer …", + "description": "Label for a disabled sync button while sync is in progress." + }, + "syncFailed": { + "message": "Klarte ikkje importera. Sørg for at datamaskina og telefonen din er kopla til internett.", + "description": "Informational text displayed if a sync operation times out." + }, + "timestamp_s": { + "message": "no", + "description": "Brief timestamp for messages sent less than a minute ago. Displayed in the conversation list and message bubble." + }, + "timestamp_m": { + "message": "1 minutt", + "description": "Brief timestamp for messages sent about one minute ago. Displayed in the conversation list and message bubble." + }, + "timestamp_h": { + "message": "1 time", + "description": "Brief timestamp for messages sent about one hour ago. Displayed in the conversation list and message bubble." + }, + "hoursAgoShort": { + "message": "$hours$ t", + "description": "Even further contracted form of 'X hours ago' which works both for singular and plural, used in the left pane", + "placeholders": { + "hours": { + "content": "$1", + "example": "2" + } + } + }, + "hoursAgo": { + "message": "$hours$ t sidan", + "description": "Contracted form of 'X hours ago' which works both for singular and plural", + "placeholders": { + "hours": { + "content": "$1", + "example": "2" + } + } + }, + "minutesAgoShort": { + "message": "$minutes$ min", + "description": "Even further contracted form of 'X minutes ago' which works both for singular and plural, used in the left pane", + "placeholders": { + "minutes": { + "content": "$1", + "example": "10" + } + } + }, + "minutesAgo": { + "message": "$minutes$ min sidan", + "description": "Contracted form of 'X minutes ago' which works both for singular and plural", + "placeholders": { + "minutes": { + "content": "$1", + "example": "10" + } + } + }, + "justNow": { + "message": "no", + "description": "Shown if a message is very recent, less than 60 seconds old" + }, + "timestampFormat_M": { + "message": "D. MMM", + "description": "Timestamp format string for displaying month and day (but not the year) of a date within the current year, ex: use 'MMM D' for 'Aug 8', or 'D MMM' for '8 Aug'." + }, + "unblockToSend": { + "message": "Opphev blokkeringa på denne kontakten for å senda ei melding.", + "description": "Brief message shown when trying to message a blocked number" + }, + "unblockGroupToSend": { + "message": "Opphev blokkeringa av denne gruppa for å senda.", + "description": "Brief message shown when trying to message a blocked group" + }, + "youChangedTheTimer": { + "message": "Du sette utløpstida for meldingane i samtalen til $time$", + "description": "Message displayed when you change the message expiration timer in a conversation.", + "placeholders": { + "time": { + "content": "$1", + "example": "10m" + } + } + }, + "timerSetOnSync": { + "message": "Oppdaterte utløpstida til $time$", + "description": "Message displayed when timer is set on initial link of desktop device.", + "placeholders": { + "time": { + "content": "$1", + "example": "10m" + } + } + }, + "theyChangedTheTimer": { + "message": "$name$oppdaterte utløpstida til $time$", + "description": "Message displayed when someone else changes the message expiration timer in a conversation.", + "placeholders": { + "name": { + "content": "$1", + "example": "Bob" + }, + "time": { + "content": "$2", + "example": "10m" + } + } + }, + "timerOption_0_seconds": { + "message": "av", + "description": "Label for option to turn off message expiration in the timer menu" + }, + "timerOption_5_seconds": { + "message": "5 sekund", + "description": "Label for a selectable option in the message expiration timer menu" + }, + "timerOption_10_seconds": { + "message": "10 sekund", + "description": "Label for a selectable option in the message expiration timer menu" + }, + "timerOption_30_seconds": { + "message": "30 sekund", + "description": "Label for a selectable option in the message expiration timer menu" + }, + "timerOption_1_minute": { + "message": "1 minutt", + "description": "Label for a selectable option in the message expiration timer menu" + }, + "timerOption_5_minutes": { + "message": "5 minutt", + "description": "Label for a selectable option in the message expiration timer menu" + }, + "timerOption_30_minutes": { + "message": "30 minutt", + "description": "Label for a selectable option in the message expiration timer menu" + }, + "timerOption_1_hour": { + "message": "1 time", + "description": "Label for a selectable option in the message expiration timer menu" + }, + "timerOption_6_hours": { + "message": "6 timar", + "description": "Label for a selectable option in the message expiration timer menu" + }, + "timerOption_12_hours": { + "message": "12 timar", + "description": "Label for a selectable option in the message expiration timer menu" + }, + "timerOption_1_day": { + "message": "1 dag", + "description": "Label for a selectable option in the message expiration timer menu" + }, + "timerOption_1_week": { + "message": "1 veke", + "description": "Label for a selectable option in the message expiration timer menu" + }, + "disappearingMessages": { + "message": "Forsvinnande meldingar", + "description": "Conversation menu option to enable disappearing messages" + }, + "timerOption_0_seconds_abbreviated": { + "message": "av", + "description": "Short format indicating current timer setting in the conversation list snippet" + }, + "timerOption_5_seconds_abbreviated": { + "message": "5s", + "description": "Very short format indicating current timer setting in the conversation header" + }, + "timerOption_10_seconds_abbreviated": { + "message": "10s", + "description": "Very short format indicating current timer setting in the conversation header" + }, + "timerOption_30_seconds_abbreviated": { + "message": "30s", + "description": "Very short format indicating current timer setting in the conversation header" + }, + "timerOption_1_minute_abbreviated": { + "message": "1m", + "description": "Very short format indicating current timer setting in the conversation header" + }, + "timerOption_5_minutes_abbreviated": { + "message": "5m", + "description": "Very short format indicating current timer setting in the conversation header" + }, + "timerOption_30_minutes_abbreviated": { + "message": "30m", + "description": "Very short format indicating current timer setting in the conversation header" + }, + "timerOption_1_hour_abbreviated": { + "message": "1t", + "description": "Very short format indicating current timer setting in the conversation header" + }, + "timerOption_6_hours_abbreviated": { + "message": "6t", + "description": "Very short format indicating current timer setting in the conversation header" + }, + "timerOption_12_hours_abbreviated": { + "message": "12t", + "description": "Very short format indicating current timer setting in the conversation header" + }, + "timerOption_1_day_abbreviated": { + "message": "1d", + "description": "Very short format indicating current timer setting in the conversation header" + }, + "timerOption_1_week_abbreviated": { + "message": "1v", + "description": "Very short format indicating current timer setting in the conversation header" + }, + "disappearingMessagesDisabled": { + "message": "Forsvinnande meldingar er slått av", + "description": "Displayed in the left pane when the timer is turned off" + }, + "disabledDisappearingMessages": { + "message": "$name$slo av forsvinnande meldingar", + "description": "Displayed in the conversation list when the timer is turned off", + "placeholders": { + "name": { + "content": "$1", + "example": "John" + } + } + }, + "youDisabledDisappearingMessages": { + "message": "Du slo av forsvinnande meldingar", + "description": "Displayed in the conversation list when the timer is turned off" + }, + "timerSetTo": { + "message": "Tidtakar sett til $time$", + "description": "Displayed in the conversation list when the timer is updated by some automatic action, or in the left pane", + "placeholders": { + "time": { + "content": "$1", + "example": "1w" + } + } + }, + "audioNotificationDescription": { + "message": "Spel av lydvarsling", + "description": "Description for audio notification setting" + }, + "safetyNumberChanged": { + "message": "Tryggingsnummeret er endra", + "description": "A notification shown in the conversation when a contact reinstalls" + }, + "safetyNumberChangedGroup": { + "message": "Tryggingsnummeret til $name$ er endra", + "description": "A notification shown in a group conversation when a contact reinstalls, showing the contact name", + "placeholders": { + "name": { + "content": "$1", + "example": "John" + } + } + }, + "verifyNewNumber": { + "message": "Godkjenn tryggingsnummer", + "description": "Label on button included with safety number change notification in the conversation" + }, + "yourSafetyNumberWith": { + "message": "Tryggingsnummera dine med $name$:", + "description": "Heading for safety number view", + "placeholders": { + "name": { + "content": "$1", + "example": "John" + } + } + }, + "themeLight": { + "message": "Lyst", + "description": "Label text for light theme (normal)" + }, + "themeDark": { + "message": "Mørkt", + "description": "Label text for dark theme" + }, + "noteToSelf": { + "message": "Notat til meg sjølv", + "description": "Name for the conversation with your own phone number" + }, + "hideMenuBar": { + "message": "Skjul menylinje", + "description": "Label text for menu bar visibility setting" + }, + "startConversation": { + "message": "Start new conversation…", + "description": "Label underneath number a user enters that is not an existing contact" + }, + "newPhoneNumber": { + "message": "Skriv inn eit telefonnummer for å legga til ein kontakt.", + "description": "Placeholder for adding a new number to a contact" + }, + "invalidNumberError": { + "message": "Ugyldig nummer", + "description": "When a person inputs a number that is invalid" + }, + "unlinkedWarning": { + "message": "Kopla saman Signal Desktop med mobileininga di på nytt for å fortsetja.", + "description": "" + }, + "unlinked": { + "message": "Fråkopla eining", + "description": "" + }, + "relink": { + "message": "Kopla til på nytt", + "description": "" + }, + "autoUpdateNewVersionTitle": { + "message": "Signal-oppdatering tilgjengeleg", + "description": "" + }, + "autoUpdateNewVersionMessage": { + "message": "Ei ny utgåve av Signal er tilgjengeleg", + "description": "" + }, + "autoUpdateNewVersionInstructions": { + "message": "Trykk «Start Signal på nytt» for å fullføra oppgraderinga.", + "description": "" + }, + "autoUpdateRestartButtonLabel": { + "message": "Start Signal på nytt", + "description": "" + }, + "autoUpdateLaterButtonLabel": { + "message": "Seinare", + "description": "" + }, + "leftTheGroup": { + "message": "$name$ forlét gruppa", + "description": "Shown in the conversation history when a single person leaves the group", + "placeholders": { + "name": { + "content": "$1", + "example": "Bob" + } + } + }, + "multipleLeftTheGroup": { + "message": "$name$ forlét gruppa", + "description": "Shown in the conversation history when multiple people leave the group", + "placeholders": { + "name": { + "content": "$1", + "example": "Alice, Bob" + } + } + }, + "updatedTheGroup": { + "message": "Gruppa er oppdatert", + "description": "Shown in the conversation history when someone updates the group" + }, + "titleIsNow": { + "message": "Tittelen er no «$name$»", + "description": "Shown in the conversation history when someone changes the title of the group", + "placeholders": { + "name": { + "content": "$1", + "example": "Book Club" + } + } + }, + "joinedTheGroup": { + "message": "$name$ vart med i gruppa", + "description": "Shown in the conversation history when a single person joins the group", + "placeholders": { + "name": { + "content": "$1", + "example": "Alice" + } + } + }, + "multipleJoinedTheGroup": { + "message": "$names$ vart med i gruppa", + "description": "Shown in the conversation history when more than one person joins the group", + "placeholders": { + "names": { + "content": "$1", + "example": "Alice, Bob" + } + } + } +} diff --git a/_locales/no/messages.json b/_locales/no/messages.json index 2a33448508..617d1bb17e 100644 --- a/_locales/no/messages.json +++ b/_locales/no/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copy error and quit", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database Error", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Delete all data and restart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Fil", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Archived Conversations", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "These conversations are archived and will only appear in the Inbox if new messages are received.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Velg mappe", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "Når du inkluderer et vedlegg som ikke er et bilde, er begrensningen ett vedlegg per melding.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "Du kan ikke blande vedlegg som er bilder med vedlegg som ikke er bilder i samme melding.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Avslutt", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Legg inn navn eller nummer", + "search": { + "message": "Søk", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Conversations", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Kontakt", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Messages", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Velkommen til Signal!", "description": "" }, @@ -673,6 +727,10 @@ "message": "Postboks", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Laster ned", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Last ned Vedlegg", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Bilde", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Til", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Generelt", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Slå på stavekontroll av tekst du skriver i meldingsvinduet", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Denne versjonen av Signal Desktop er foreldet. Oppgrader til siste versjon for å fortsette.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Android-klienter mottar bare de første 2000 tegnene i denne meldingen.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Oppgrader", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Mørkt", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Note to Self", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Skjul menylinje", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Start samtale...", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Vilkår og personvernerklæring", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/pl/messages.json b/_locales/pl/messages.json index fa6f85f8de..d3b3d45c69 100644 --- a/_locales/pl/messages.json +++ b/_locales/pl/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Skopiuj błąd i zakończ", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Nieznana grupa", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Błąd bazy danych", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Usuń wszystkie dane i uruchom ponownie", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Plik", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Zarchiwizowane rozmowy", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Te rozmowy są zarchiwizowane i pojawią się w Odebranych tylko po otrzymaniu nowych wiadomości.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archiwum konwersacji", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Przenieś konwersacje do skrzynki odbiorczej", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Wybierz folder", "description": "Button to allow the user to find a folder on disk" @@ -254,7 +286,7 @@ } }, "youMarkedAsVerified": { - "message": "Oznaczyłeś(aś) Twój numer bezpieczeństwa $name$ jako zweryfikowany", + "message": "Oznaczyłeś(aś) Twój numer bezpieczeństwa z $name$ jako zweryfikowany", "description": "Shown in the conversation history when the user marks a contact as verified.", "placeholders": { "name": { @@ -264,7 +296,7 @@ } }, "youMarkedAsNotVerified": { - "message": "Oznaczyłeś(aś) Twój numer bezpieczeństwa $name$ jako niezweryfikowany", + "message": "Oznaczyłeś(aś) Twój numer bezpieczeństwa z $name$ jako niezweryfikowany", "description": "Shown in the conversation history when the user marks a contact as not verified, whether on the Safety Number screen or by dismissing a banner or dialog.", "placeholders": { "name": { @@ -488,7 +520,7 @@ "description": "Shown while Signal Desktop is fetching metadata for a url in composition area" }, "stagedPreviewThumbnail": { - "message": "Draft thumbnail link preview for $domain$", + "message": "Projekt miniaturki podglądu linku do $domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -498,7 +530,7 @@ } }, "previewThumbnail": { - "message": "Thumbnail link preview for $domain$", + "message": "Podgląd miniaturki linku dla $domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -508,7 +540,7 @@ } }, "stagedImageAttachment": { - "message": "Draft image attachment: $path$", + "message": "Projekt załącznika obrazu: $path$", "description": "Alt text for staged attachments", "placeholders": { "path": { @@ -521,7 +553,7 @@ "message": "W przypadku dołączenia załącznika innego niż obraz, limit wynosi jeden załącznik na wiadomość.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "Nie można łączyć obrazów z innymi załącznikami w jednej wiadomości.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Wyjdź", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Wprowadź nazwę lub numer", + "search": { + "message": "Szukaj", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "Brak wyników dla \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Rozmowy", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Kontakty", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Wiadomości", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Witamy w Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "Skrzynka pocztowa", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Pobieranie", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Pobierz załącznik", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Zdjęcie", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Do", + "message": "do", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Ogólne", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Podgląd linków", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Podglądy linków są obsługiwane dla Imgur, Instagram, Reddit i YouTube.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Włącz sprawdzanie pisowni podczas pisania wiadomości", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Ta wersja Signal Desktop jest przestarzała. Aby nadal wysyłać wiadomości, wykonaj aktualizację.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "W aplikacji na Androida widoczne będzie tylko pierwsze 2000 znaków z tej wiadomości.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Aktualizuj", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Ciemny", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Moje notatki", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Ukryj pasek menu", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Rozpocznij rozmowę...", + "message": "Rozpocznij nową rozmowę...", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Warunki korzystania & Polityka prywatności", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/pt_BR/messages.json b/_locales/pt_BR/messages.json index a6fd095129..f7deb63b2c 100644 --- a/_locales/pt_BR/messages.json +++ b/_locales/pt_BR/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copiar erro e sair", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Grupo desconhecido", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Erro na base de dados", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Apagar todos os dados e reiniciar", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Arquivo", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,12 +161,28 @@ } } }, + "archivedConversations": { + "message": "Conversas arquivadas", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Estas conversas estão arquivadas. Elas aparecerão na caixa de entrada somente se novas mensagens forem recebidas.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Arquivar conversa", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Mover conversa para Caixa de Entrada", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Escolher pasta", "description": "Button to allow the user to find a folder on disk" }, "chooseFile": { - "message": "Choose file", + "message": "Escolher arquivo", "description": "Button to allow the user to find a file on disk" }, "loadDataHeader": { @@ -484,11 +516,11 @@ "description": "Shown in toast when user attempts to send .exe file, for example" }, "loadingPreview": { - "message": "Loading Preview...", + "message": "Carregando pré-visualização...", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area" }, "stagedPreviewThumbnail": { - "message": "Draft thumbnail link preview for $domain$", + "message": "Pré-visualização em miniatura do link $domain$ (rascunho)", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -498,7 +530,7 @@ } }, "previewThumbnail": { - "message": "Thumbnail link preview for $domain$", + "message": "Pré-visualização em miniatura do link $domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -508,7 +540,7 @@ } }, "stagedImageAttachment": { - "message": "Draft image attachment: $path$", + "message": "Rascunho de imagem anexa: $path$", "description": "Alt text for staged attachments", "placeholders": { "path": { @@ -518,15 +550,15 @@ } }, "oneNonImageAtATimeToast": { - "message": "When including a non-image attachment, the limit is one attachment per message.", + "message": "O limite para inclusão de anexos que não sejam imagens é de um por mensagem.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { - "message": "You cannot mix non-image and image attachments in one message.", + "cannotMixImageAndNonImageAttachments": { + "message": "Você não pode combinar imagens e anexos que não sejam imagens na mesma mensagem.", "description": "An error popup when the user has attempted to add an attachment" }, "maximumAttachments": { - "message": "You cannot add any more attachments to this message.", + "message": "Você não pode adicionar mais nenhum anexo a esta mensagem.", "description": "An error popup when the user has attempted to add an attachment" }, "fileSizeWarning": { @@ -611,15 +643,37 @@ "message": "Sair", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Inserir nome ou número", + "search": { + "message": "Buscar", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "Sem resultados para \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Conversas", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Contatos", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Mensagens", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Boas-vindas ao Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "Caixa postal", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "A transferir", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Baixar anexo", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -694,7 +752,7 @@ "description": "Shown in toast if user clicks on quote references messages not loaded in view, but in database" }, "voiceNoteMustBeOnlyAttachment": { - "message": "A voice note must be the only attachment included in a message.", + "message": "Um áudio deve ser o único anexo incluso em uma mensagem.", "description": "Shown in toast if tries to record a voice note with any staged attachments" }, "you": { @@ -735,6 +793,14 @@ "message": "Foto", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Para", + "message": "para", "description": "Label for the receiver of a message" }, "sent": { @@ -866,7 +932,7 @@ "description": "Used for the icon layered on top of an image in message bubbles" }, "addACaption": { - "message": "Add a caption...", + "message": "Adicionar legenda...", "description": "" }, "save": { @@ -959,14 +1025,6 @@ "message": "Geral", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Ativar a verificação ortográfica do texto digitado na caixa de edição de mensagens", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Esta versão do Signal Desktop expirou. Por favor, atualize-o para a versão mais recente para continuar enviando mensagens.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Clientes do Android receberão apenas os primeiros 2000 caracteres desta mensagem.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Atualizar", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Escuro", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Recado para mim", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Ocultar a barra de opções", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Iniciar conversa...", + "message": "Iniciar uma conversa…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Termos & Política de Privacidade", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/pt_PT/messages.json b/_locales/pt_PT/messages.json index 83d604385d..1cf05c89d9 100644 --- a/_locales/pt_PT/messages.json +++ b/_locales/pt_PT/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copiar erro e sair", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Grupo desconhecido", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Erro na base de dados", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Eliminar todos os dados e reiniciar", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Ficheiro", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -24,15 +40,15 @@ "description": "The label that is used for the Preferences menu in the program main menu. This should be consistent with the standard naming for ‘Preferences’ on the operating system." }, "appMenuHide": { - "message": "Esconder", + "message": "Ocultar", "description": "Application menu command to hide the window" }, "appMenuHideOthers": { - "message": "Esconder Outros", + "message": "Ocultar outros", "description": "Application menu command to hide all other windows" }, "appMenuUnhide": { - "message": "Mostrar Tudo", + "message": "Exibir tudo", "description": "Application menu command to show all application windows" }, "appMenuQuit": { @@ -64,11 +80,11 @@ "description": "Edit menu command to insert text from clipboard at cursor location, taking only text and not style information" }, "editMenuDelete": { - "message": "Apagar", + "message": "Eliminar", "description": "Edit menu command to remove the selected text" }, "editMenuSelectAll": { - "message": "Seleccionar Tudo", + "message": "Selecionar tudo", "description": "Edit menu comand to select all of the text in selected text box" }, "editMenuStartSpeaking": { @@ -80,7 +96,7 @@ "description": "Edit menu item under 'speech' to stop dictation" }, "windowMenuClose": { - "message": "Fechar Janela", + "message": "Fechar janela", "description": "Window menu command to close the current window" }, "windowMenuMinimize": { @@ -92,39 +108,39 @@ "description": "Window menu command to make the current window the size of the whole screen" }, "windowMenuBringAllToFront": { - "message": "Trazer Todos para a Frente", + "message": "Trazer todos para a frente", "description": "Window menu command to bring all windows of current applicatinon to front" }, "viewMenuResetZoom": { - "message": "Tamanho Real", + "message": "Tamanho real", "description": "View menu command to go back to the default zoom" }, "viewMenuZoomIn": { - "message": "Zoom in", + "message": "Aumentar", "description": "View menu command to make everything bigger" }, "viewMenuZoomOut": { - "message": "Zoom Out", + "message": "Diminuir", "description": "View menu command to make everything smaller" }, "viewMenuToggleFullScreen": { - "message": "Activar Ecrã Completo", + "message": "Ativar ecrã completo", "description": "View menu command to enter or leave Full Screen mode" }, "viewMenuToggleDevTools": { - "message": "Activar Ferramentas de Desenvolvimento", + "message": "Ativar ferramentas do programador", "description": "View menu command to show or hide the developer tools" }, "menuSetupWithImport": { - "message": "Configurar com Importação", + "message": "Configurar com a importação", "description": "When the application is not yet set up, menu option to start up the import sequence" }, "menuSetupAsNewDevice": { - "message": "Configurar como Novo Dispositivo", + "message": "Configurar como novo dispositivo", "description": "When the application is not yet set up, menu option to start up the set up as fresh device" }, "menuSetupAsStandalone": { - "message": "Configurar como Dispositivo Autónomo", + "message": "Configurar como dispositivo autónomo", "description": "Only available on development modes, menu option to open up the standalone device setup sequence" }, "loading": { @@ -132,11 +148,11 @@ "description": "Message shown on the loading screen before we've loaded any messages" }, "optimizingApplication": { - "message": "A optimizar aplicação...", + "message": "A otimizar aplicação...", "description": "Message shown on the loading screen while we are doing application optimizations" }, "migratingToSQLCipher": { - "message": "A optimizar mensagens... $status$ completo.", + "message": "A otimizar mensagens... $status$ completo.", "description": "Message shown on the loading screen while we are doing application optimizations", "placeholders": { "status": { @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Conversas arquivadas", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Estas conversas estão arquivadas e apenas irão aparecer na caixa de entrada caso receba novas mensagens.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Arquivar conversa", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Mover conversas para a caixa de entrada", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Escolher pasta", "description": "Button to allow the user to find a folder on disk" @@ -158,27 +190,27 @@ "description": "Header shown on the first screen in the data import process" }, "loadDataDescription": { - "message": "Acabou de passar pelo processo de exportação, e os seus contactos e mensagens estão a aguardar pacientemente no seu computados. Seleccione a pasta que contém os seus dados Signal guardados.", + "message": "Acabou de passar pelo processo de exportação, e os seus contactos e mensagens estão a aguardar pacientemente no seu computados. Selecione a pasta que contém os seus dados Signal guardados.", "description": "Introduction to the process of importing messages and contacts from disk" }, "importChooserTitle": { - "message": "Escolha a pasta com dados exportados", + "message": "Escolha a pasta com os dados exportados", "description": "Title of the popup window used to select data previously exported" }, "importErrorHeader": { - "message": "Algo correu mal!", + "message": "Ocorreu algo de errado!", "description": "Header of the error screen after a failed import" }, "importingHeader": { - "message": "Carregar contactos e mensagens", + "message": "A carregar contactos e mensagens", "description": "Header of screen shown as data is import" }, "importErrorFirst": { - "message": "Assegure-se que escolheu a pasta correcta onde se encontram os seus dados Signal. O nome da pasta deverá começar por 'Signal Export'. Também poderá copiar os seus dados desde a aplicação Chrome. ", + "message": "Assegure-se que escolheu a diretoria correta onde se encontram os seus dados do Signal. O nome da diretoria deverá começar por 'Signal Export'. Também poderá copiar os seus dados a partir da aplicação Chrome. ", "description": "Message shown if the import went wrong; first paragraph" }, "importErrorSecond": { - "message": "Se estes passos não funcionarem, por favor envie um relatório de erros (Ver -> Relatório de Erros) para que possamos ajudar-lhe com a migração!", + "message": "Se estes passos não funcionarem, envie por favor um relatório de erros (Ver -> Relatório de erros) para que possamos ajudar-lhe com a migração!", "description": "Message shown if the import went wrong; second paragraph" }, "importAgain": { @@ -190,7 +222,7 @@ "description": "Header shown on the screen at the end of a successful import process" }, "importCompleteStartButton": { - "message": "Começar a usar Signal Desktop", + "message": "Começar a utilizar o Signal Desktop", "description": "Button shown at end of successful import process, nothing left but a restart" }, "importCompleteLinkButton": { @@ -198,11 +230,11 @@ "description": "Button shown at end of successful 'light' import process, so the standard linking process still needs to happen" }, "selectedLocation": { - "message": "a sua localização seleccionada", + "message": "a sua localização selecionada", "description": "Message shown as the export location if we didn't capture the target directory" }, "upgradingDatabase": { - "message": "A actualizar a base de dados, poderá demorar algum tempo...", + "message": "A atualizar a base de dados. Isto poderá demorar algum tempo...", "description": "Message shown on the loading screen when we're changing database structure on first run of a new version" }, "loadingMessages": { @@ -224,11 +256,11 @@ "description": "Used as a label on a button allowing user to see more information" }, "youLeftTheGroup": { - "message": "Saiu do grupo", + "message": "Você saiu do grupo", "description": "Displayed when a user can't send a message because they have left the group" }, "scrollDown": { - "message": "Ir para o fundo da conversa", + "message": "Ir para o fim da conversa", "description": "Alt text for button to take user down to bottom of conversation, shown when user scrolls up" }, "messageBelow": { @@ -240,11 +272,11 @@ "description": "Alt text for button to take user down to bottom of conversation with more than one message out of screen" }, "unreadMessage": { - "message": "1 Mensagem Não Lida", + "message": "1 mensagem por ler", "description": "Text for unread message separator, just one message" }, "unreadMessages": { - "message": "$count$ Mensagens Não Lidas", + "message": "$count$ mensagens por ler", "description": "Text for unread message separator, with count", "placeholders": { "count": { @@ -298,11 +330,11 @@ "description": "When there are multiple previously-verified group members with safety number changes, a banner will be shown. The list of contacts with safety number changes is shown, and this text introduces that list." }, "changedSinceVerifiedMultiple": { - "message": "Os números de segurança com vários membros do grupo mudaram desde a última vez que os verificou. Isto poderá querer dizer que alguém está a tentar interceptar as suas comunicações ou simplesmente que esses membros reinstalaram o Signal.", + "message": "Os números de segurança com vários membros do grupo mudaram desde a última vez que os verificou. Isto poderá querer dizer que alguém está a tentar intercetar as suas comunicações ou simplesmente que esses membros reinstalaram o Signal.", "description": "Shown on confirmation dialog when user attempts to send a message" }, "changedSinceVerified": { - "message": "O número de segurança com $name$ mudou desde a última verificação. Isto poderá querer dizer que alguém está a tentar interceptar as suas comunicações ou que $name$ simplesmente reinstalou o Signal.", + "message": "O seu número de segurança com $name$ mudou desde a última verificação. Isto poderá querer dizer que alguém está a tentar intercetar as suas comunicações ou simplesmente que $name$ reinstalou o Signal.", "description": "Shown on confirmation dialog when user attempts to send a message", "placeholders": { "name": { @@ -312,7 +344,7 @@ } }, "changedRightAfterVerify": { - "message": "O número de segurança que está a tentar verificar mudou. Por favor, reveja o seu novo número de segurança com $name$. Lembre-se, esta alteração poderá significar que alguém está a tentar interceptar as suas comunicações ou que $name$ simplesmente reinstalou o Signal.", + "message": "O número de segurança que está a tentar verificar mudou. Por favor, reveja o seu novo número de segurança com $name$. Lembre-se, esta alteração poderá significar que alguém está a tentar intercetar as suas comunicações ou simplesmente que $name$ reinstalou o Signal.", "description": "Shown on the safety number screen when the user has selected to verify/unverify a contact's safety number, and we immediately discover a safety number change", "placeholders": { "name": { @@ -322,11 +354,11 @@ } }, "changedRecentlyMultiple": { - "message": "Os números de segurança com vários membros do grupo mudaram recentemente. Isto pode significar que alguém está a tentar interceptar as suas comunicações ou simplesmente os membros do grupo reinstalaram o Signal.", + "message": "Os números de segurança com vários membros do grupo mudaram recentemente. Isto pode significar que alguém está a tentar intercetar as suas comunicações ou simplesmente que os membros do grupo reinstalaram o Signal.", "description": "Shown on confirmation dialog when user attempts to send a message" }, "changedRecently": { - "message": "O número de segurança com $name$ mudou recentemente. Isto poderá querer dizer que alguém está a tentar interceptar as suas comunicações ou que $name$ simplesmente reinstalou o Signal.", + "message": "O número de segurança com $name$ mudou recentemente. Isto poderá querer dizer que alguém está a tentar intercetar as suas comunicações ou simplesmente que $name$ reinstalou o Signal.", "description": "Shown on confirmation dialog when user attempts to send a message", "placeholders": { "name": { @@ -336,7 +368,7 @@ } }, "identityKeyErrorOnSend": { - "message": "O número de segurança com $name$ mudou. Isto poderá querer dizer que alguém está a tentar interceptar as suas comunicações ou que $name$ simplesmente reinstalou o Signal. Poderá querer verificar os seus números de segurança com o seu contacto.", + "message": "O número de segurança com $name$ mudou. Isto poderá querer dizer que alguém está a tentar intercetar as suas comunicações ou simplesmente que $name$ reinstalou o Signal. Poderá querer verificar os seus números de segurança com o seu contacto.", "description": "Shown when user clicks on a failed recipient in the message detail view after an identity key change", "placeholders": { "name": { @@ -346,7 +378,7 @@ } }, "sendAnyway": { - "message": "Enviar ainda assim", + "message": "Enviar mesmo assim", "description": "Used on a warning dialog to make it clear that it might be risky to send the message." }, "noLongerVerified": { @@ -364,11 +396,11 @@ "description": "Shown in conversation banner when more than one group member's safety number has changed, but they were previously verified." }, "debugLogExplanation": { - "message": "Este relatório será publicado online para os contribuidores o analisarem. É possível examiná-lo e editá-lo antes de o submeter.", + "message": "Este relatório será publicado on-line para que os contribuidores o analisem. É possível examiná-lo e editá-lo antes de o submeter.", "description": "" }, "debugLogError": { - "message": "Algo correu mal ao carregar! Por favor considere adicionar um relatório de erros para o Erro que vai enviar.", + "message": "Algo correu mal ao carregar! Por favor considere que adicionar um relatório de erros para o Erro que vai enviar.", "description": "" }, "reportIssue": { @@ -380,7 +412,7 @@ "description": "Label for a button that dismisses a dialog. The user clicks it to confirm that they understand the message in the dialog." }, "submit": { - "message": "Enviar", + "message": "Submeter", "description": "" }, "acceptNewKey": { @@ -424,7 +456,7 @@ "description": "Header for a key change dialog" }, "identityChanged": { - "message": "O número de segurança com este contacto foi alterado. Isto pode significar que alguém está a tentar interceptar a sua comunicação, o que o seu contacto simplesmente reinstalou o Signal. Poderá querer verificar o seu novo número de segurança abaixo.", + "message": "O número de segurança com este contacto foi alterado. Isto pode significar que alguém está a tentar intercetar a sua comunicação, ou que o seu contacto simplesmente reinstalou o Signal. Poderá querer verificar o seu novo número de segurança abaixo.", "description": "" }, "incomingError": { @@ -436,7 +468,7 @@ "description": "Header of the default pane in the media gallery, showing images and videos" }, "mediaEmptyState": { - "message": "Não tem média nesta conversa", + "message": "Não tem nenhum multimédia nesta conversa", "description": "Message shown to user in the media gallery when there are no messages with media attachments (images or video)" }, "documents": { @@ -456,11 +488,11 @@ "description": "Section header in the media gallery" }, "thisWeek": { - "message": "Esta Semana", + "message": "Esta semana", "description": "Section header in the media gallery" }, "thisMonth": { - "message": "Este Mês", + "message": "Este mês", "description": "Section header in the media gallery" }, "unsupportedAttachment": { @@ -472,7 +504,7 @@ "description": "Hover text for attachment filenames" }, "unnamedFile": { - "message": "Ficheiro Sem Nome", + "message": "Ficheiro sem nome", "description": "Hover text for attachment filenames" }, "voiceMessage": { @@ -480,7 +512,7 @@ "description": "Name for a voice message attachment" }, "dangerousFileType": { - "message": "Tipo de ficheiro deste anexo não é permitido por razões de segurança", + "message": "O tipo de ficheiro deste anexo não é permitido por razões de segurança", "description": "Shown in toast when user attempts to send .exe file, for example" }, "loadingPreview": { @@ -488,7 +520,7 @@ "description": "Shown while Signal Desktop is fetching metadata for a url in composition area" }, "stagedPreviewThumbnail": { - "message": "Pré-visualização da hiperligação do esboço da miniatura $domain$", + "message": "Pré-visualização da hiperligação da miniatura do esboço $domain$", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -518,10 +550,10 @@ } }, "oneNonImageAtATimeToast": { - "message": "Se o anexo não é uma imagem, apenas pode enviar um anexo por mensagem.", + "message": "Se o anexo não for uma imagem, apenas pode enviar um anexo por mensagem.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "Não pode misturar imagens com outro tipo de anexos numa só mensagem.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -530,11 +562,11 @@ "description": "An error popup when the user has attempted to add an attachment" }, "fileSizeWarning": { - "message": "Lamentamos, o ficheiro seleccionado excede o tamanho máximo permitido.", + "message": "Lamentamos, mas o ficheiro selecionado excede o tamanho máximo permitido.", "description": "" }, "unableToLoadAttachment": { - "message": "Não foi possível carregar o anexo seleccionado.", + "message": "Não foi possível carregar o anexo selecionado.", "description": "" }, "disconnected": { @@ -546,7 +578,7 @@ "description": "Displayed when the desktop client is currently connecting to the server." }, "offline": { - "message": "Desligado", + "message": "Offline", "description": "Displayed when the desktop client has no network connection." }, "checkNetworkConnection": { @@ -554,7 +586,7 @@ "description": "Obvious instructions for when a user's computer loses its network connection" }, "attemptingReconnection": { - "message": "Tentativa de ligação em $reconnect_duration_in_seconds$ segundos", + "message": "Nova tentativa de ligação dentro de $reconnect_duration_in_seconds$ segundos", "description": "", "placeholders": { "reconnect_duration_in_seconds": { @@ -564,35 +596,35 @@ } }, "submitDebugLog": { - "message": "Enviar relatório de erros", + "message": "Enviar relatório de depuração", "description": "Menu item and header text for debug log modal (sentence case)" }, "debugLog": { - "message": "Relatório de Erros", + "message": "Relatório de depuração", "description": "View menu item to open the debug log (title case)" }, "goToReleaseNotes": { - "message": "Ir para Notas de Lançamento", + "message": "Ir para as notas de lançamento", "description": "" }, "goToForums": { - "message": "Ir para Fóruns", + "message": "Ir para os fóruns", "description": "Item under the Help menu, takes you to the forums" }, "goToSupportPage": { - "message": "Ir para Página de Suporte", + "message": "Ir para a página de suporte", "description": "Item under the Help menu, takes you to the support page" }, "menuReportIssue": { - "message": "Reportar um Problema", + "message": "Reportar um problema", "description": "Item under the Help menu, takes you to GitHub new issue form (title case)" }, "signalDesktopPreferences": { - "message": "Definições Signal Desktop", + "message": "Definições do Signal Desktop", "description": "Title of the window that pops up with Signal Desktop preferences in it" }, "aboutSignalDesktop": { - "message": "Acerca de Signal Desktop", + "message": "Acerca do Signal Desktop", "description": "Item under the Help menu, which opens a small about window" }, "speech": { @@ -604,31 +636,53 @@ "description": "Command under Window menu, to show the window" }, "hide": { - "message": "Esconder", + "message": "Ocultar", "description": "Command in the tray icon menu, to hide the window" }, "quit": { "message": "Sair", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Insira nome ou número", + "search": { + "message": "Procurar", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { - "message": "Bem-vindo ao Signal", + "noSearchResults": { + "message": "Sem resultados para \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Conversas", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Contactos", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Mensagens", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { + "message": "Bem-vindo(a) ao Signal", "description": "" }, "selectAContact": { - "message": "Seleccione um contacto ou grupo para trocar mensagens.", + "message": "Selecione um contacto ou grupo para começar a trocar mensagens.", "description": "" }, "typingAlt": { - "message": "Animação de escrever para esta conversa.", + "message": "Apresentar escrita com animação para esta conversa.", "description": "Used as the 'title' attibute for the typing animation" }, "contactAvatarAlt": { @@ -642,7 +696,7 @@ } }, "sendMessageToContact": { - "message": "Enviar Mensagem", + "message": "Enviar mensagem", "description": "Shown when you are sent a contact and that contact has a signal account" }, "home": { @@ -670,19 +724,23 @@ "description": "Generic label shown if contact address has custom type but no label" }, "poBox": { - "message": "Código Postal", + "message": "Código postal", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "A descarregar", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { - "message": "Descarregar Anexo", + "message": "Descarregar anexo", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" }, "replyToMessage": { - "message": "Responder à Mensagem", + "message": "Responder à mensagem", "description": "Shown in triple-dot menu next to message to allow user to start crafting a message with a quotation" }, "originalMessageNotFound": { - "message": "A mensagem original não foi encontrada", + "message": "Não foi encontrada a mensagem original", "description": "Shown in quote if reference message was not found as message was initially downloaded and processed" }, "originalMessageNotAvailable": { @@ -690,11 +748,11 @@ "description": "Shown in toast if user clicks on quote that references message no longer in database" }, "messageFoundButNotLoaded": { - "message": "Mensagem Original foi encontrada mas não carregada. Mova o texto para cima para a carregar.", + "message": "A mensagem original foi encontrada mas não carregada. Mova o texto para cima para a carregar.", "description": "Shown in toast if user clicks on quote references messages not loaded in view, but in database" }, "voiceNoteMustBeOnlyAttachment": { - "message": "Uma nota de voz é o único anexo que pode ser incluído numa mensagem.", + "message": "Uma nota de voz deverá ser o único anexo incluído numa mensagem.", "description": "Shown in toast if tries to record a voice note with any staged attachments" }, "you": { @@ -716,11 +774,11 @@ "description": "Shown if the user attempts to send an audio message without audio permssions turned on" }, "allowAccess": { - "message": "Permitir Acesso", + "message": "Permitir o acesso", "description": "Button shown in popup asking to enable microphon/video permissions to send audio messages" }, "showSettings": { - "message": "Mostrar Definições", + "message": "Mostrar definições", "description": "A button shown in dialog requesting the user to turn on audio permissions" }, "audio": { @@ -735,6 +793,14 @@ "message": "Foto", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -756,15 +822,15 @@ "description": "" }, "delete": { - "message": "Apagar", + "message": "Eliminar", "description": "" }, "deleteWarning": { - "message": "Tem a certeza? Clicar em 'apagar' irá remover permanentemente esta mensagem deste dispositivo.", + "message": "Tem a certeza? Clicar em 'Eliminar' irá remover permanentemente esta mensagem deste dispositivo.", "description": "" }, "deleteThisMessage": { - "message": "Apagar esta mensagem", + "message": "Eliminar esta mensagem", "description": "" }, "from": { @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Para", + "message": "para", "description": "Label for the receiver of a message" }, "sent": { @@ -808,7 +874,7 @@ "description": "This is a menu item for viewing all media (images + video) in a conversation, using the imperative case, as in a command." }, "verifyHelp": { - "message": "Se deseja verificar a segurança da sua cifragem ponto-a-ponto com $name$, compare os números acima com os números no dispositivo.", + "message": "Se deseja verificar a segurança da sua cifragem ponto-a-ponto com $name$, compare os números acima com os números existentes no dispositivo dessa pessoa.", "description": "", "placeholders": { "name": { @@ -826,19 +892,19 @@ "description": "Shown on the drop-down menu for an individual message, takes you to message detail screen" }, "retrySend": { - "message": "Tentar Enviar Novamente ", + "message": "Tentar enviar novamente ", "description": "Shown on the drop-down menu for an indinvidaul message, but only if it is an outgoing message that failed to send" }, "deleteMessage": { - "message": "Apagar Mensagem", + "message": "Eliminar mensagem", "description": "Shown on the drop-down menu for an individual message, deletes single message" }, "deleteMessages": { - "message": "Apagar mensagens", + "message": "Eliminar mensagens", "description": "Menu item for deleting messages, title case." }, "deleteConversationConfirmation": { - "message": "Deseja apagar esta conversa definitivamente?", + "message": "Deseja eliminar definitivamente esta conversa?", "description": "Confirmation dialog text that asks the user if they really wish to delete the conversation. Answer buttons use the strings 'ok' and 'cancel'. The deletion is permanent, i.e. it cannot be undone." }, "sessionEnded": { @@ -846,7 +912,7 @@ "description": "This is a past tense, informational message. In other words, your secure session has been reset." }, "quoteThumbnailAlt": { - "message": "Thumbnail da imagem da mensagem", + "message": "Miniatura da imagem da mensagem", "description": "Used in alt tag of thumbnail images inside of an embedded message quote" }, "imageAttachmentAlt": { @@ -854,7 +920,7 @@ "description": "Used in alt tag of image attachment" }, "videoAttachmentAlt": { - "message": "Screenshot do video anexado à mensagem", + "message": "Captura do ecrã do video anexado à mensagem", "description": "Used in alt tag of video attachment preview" }, "lightboxImageAlt": { @@ -874,7 +940,7 @@ "description": "" }, "fileIconAlt": { - "message": "Ícone do Ficheiro", + "message": "Ícone do ficheiro", "description": "Used in the media gallery documents tab to visually represent a file" }, "emojiAlt": { @@ -888,7 +954,7 @@ } }, "installWelcome": { - "message": "O Signal Desktop dá-lhe as Boas-vindas!", + "message": "O Signal Desktop dá-lhe as boas-vindas!", "description": "Welcome title on the install page" }, "installTagline": { @@ -904,7 +970,7 @@ "description": "Used in the guidance to help people find the 'link new device' area of their Signal mobile app" }, "linkedDevices": { - "message": "Dispositivos Associados", + "message": "Dispositivos associados", "description": "Used in the guidance to help people find the 'link new device' area of their Signal mobile app" }, "plusButton": { @@ -952,31 +1018,23 @@ "description": "Header for permissions section of settings" }, "mediaPermissionsDescription": { - "message": "Permitir acesso à câmara e ao microfone", + "message": "Permitir o acesso à câmara e ao microfone", "description": "Description of the media permission description" }, "general": { "message": "Geral", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Enviar pré-visualização de hiperligações", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "As pré-visualizações são suportadas em hiperligações do Imgur, Instagram, Reddit, e YouTube.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { - "message": "Active a verificação ortográfica para o texto introduzido", + "message": "Ative a verificação ortográfica para o texto introduzido", "description": "Description of the media permission description" }, "clearDataHeader": { - "message": "Limpar Dados", + "message": "Limpar dados", "description": "Header in the settings dialog for the section dealing with data deletion" }, "clearDataExplanation": { - "message": "Isto limpará todos os dados da aplicação, removendo todas as mensagens e dados da sua conta.", + "message": "Isto limpará todos os dados da aplicação, removendo todas as mensagens e os dados guardados da sua conta.", "description": "Text describing what the clear data button will do." }, "clearDataButton": { @@ -984,19 +1042,19 @@ "description": "Button in the settings dialog starting process to delete all data" }, "deleteAllDataHeader": { - "message": "Apagar todos os dados?", + "message": "Eliminar todos os dados?", "description": "Header of the full-screen delete data confirmation screen" }, "deleteAllDataBody": { - "message": "Está prestes a apagar todos os dados de conta da sua aplicação, incluindo todos os contactos e todas as mensagens. Pode sempre ligar ao seu telemóvel outra vez, mas não irá recuperar as mensagens apagadas.", + "message": "Está prestes a eliminar todos os dados de conta guardados da sua aplicação, incluindo todos os contactos e todas as mensagens. Pode sempre associar novamente ao seu telemóvel, mas não irá recuperar as mensagens eliminadas.", "description": "Text describing what exactly will happen if the user clicks the button to delete all data" }, "deleteAllDataButton": { - "message": "Apagar todos os dados", + "message": "Eliminar todos os dados", "description": "Text of the button that deletes all data" }, "deleteAllDataProgress": { - "message": "A desligar e a apagar todos os dados", + "message": "A desligar e a eliminar todos os dados", "description": "Message shown to user when app is disconnected and data deleted" }, "notifications": { @@ -1008,7 +1066,7 @@ "description": "Explain the purpose of the notification settings" }, "disableNotifications": { - "message": "Desactivar notificações", + "message": "Desativar notificações", "description": "Label for disabling notifications" }, "nameAndMessage": { @@ -1016,11 +1074,11 @@ "description": "Label for setting notifications to display name and message text" }, "noNameOrMessage": { - "message": "Nem o remetente nem mensagem", + "message": "Nem o remetente nem a mensagem", "description": "Label for setting notifications to display no name and no message text" }, "nameOnly": { - "message": "Apenas remetente", + "message": "Apenas o remetente", "description": "Label for setting notifications to display sender name only" }, "newMessage": { @@ -1052,7 +1110,7 @@ "description": "Displays the details of a key change" }, "showLess": { - "message": "Esconder detalhes", + "message": "Ocultar detalhes", "description": "Hides the details of a key change" }, "learnMore": { @@ -1063,12 +1121,8 @@ "message": "Esta versão do Signal Desktop expirou. Para poder continuar a enviar mensagens, actualize para a última versão .", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Os dispositivos Android receberão apenas os primeiros 2000 caracteres desta mensagem.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { - "message": "Actualizar", + "message": "Atualizar", "description": "Label text for button to upgrade the app to the latest version" }, "mediaMessage": { @@ -1088,7 +1142,7 @@ "description": "Explanatory text for sync settings" }, "lastSynced": { - "message": "Última importação em ", + "message": "Última importação a ", "description": "Label for date and time of last sync operation" }, "syncNow": { @@ -1100,7 +1154,7 @@ "description": "Label for a disabled sync button while sync is in progress." }, "syncFailed": { - "message": "Falha na importação. Por favor verifique que o seu computador e o seu dispositivos estão ligados à Internet.", + "message": "Falha na importação. Por favor, confirme que o seu computador e o seu telemóvel se encontram ligados à Internet.", "description": "Informational text displayed if a sync operation times out." }, "timestamp_s": { @@ -1164,7 +1218,7 @@ "description": "Timestamp format string for displaying month and day (but not the year) of a date within the current year, ex: use 'MMM D' for 'Aug 8', or 'D MMM' for '8 Aug'." }, "unblockToSend": { - "message": "Desbloqueie este contacto para enviar mensagens.", + "message": "Desbloqueie este contacto para enviar uma mensagem.", "description": "Brief message shown when trying to message a blocked number" }, "unblockGroupToSend": { @@ -1182,7 +1236,7 @@ } }, "timerSetOnSync": { - "message": "Temporizador de destruição de mensagens actualizado para $time$", + "message": "Temporizador de destruição de mensagens atualizado para $time$", "description": "Message displayed when timer is set on initial link of desktop device.", "placeholders": { "time": { @@ -1306,11 +1360,11 @@ "description": "Very short format indicating current timer setting in the conversation header" }, "disappearingMessagesDisabled": { - "message": "Destruição de mensagens desactivada", + "message": "Destruição de mensagens desativada", "description": "Displayed in the left pane when the timer is turned off" }, "disabledDisappearingMessages": { - "message": "$name$ desactivou a destruição de mensagens", + "message": "$name$ desativou a destruição de mensagens", "description": "Displayed in the conversation list when the timer is turned off", "placeholders": { "name": { @@ -1320,7 +1374,7 @@ } }, "youDisabledDisappearingMessages": { - "message": "Desactivou a destruição de mensagens", + "message": "Desativou a destruição de mensagens", "description": "Displayed in the conversation list when the timer is turned off" }, "timerSetTo": { @@ -1338,7 +1392,7 @@ "description": "Description for audio notification setting" }, "safetyNumberChanged": { - "message": "Número de segurança mudou", + "message": "O número de segurança foi alterado", "description": "A notification shown in the conversation when a contact reinstalls" }, "safetyNumberChangedGroup": { @@ -1352,7 +1406,7 @@ } }, "verifyNewNumber": { - "message": "Verificar número de segurança", + "message": "Verificar o número de segurança", "description": "Label on button included with safety number change notification in the conversation" }, "yourSafetyNumberWith": { @@ -1373,12 +1427,16 @@ "message": "Escuro", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Nota para mim", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Ocultar menu", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Iniciar conversa...", + "message": "Iniciar uma nova conversa…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1402,7 +1460,7 @@ "description": "" }, "autoUpdateNewVersionTitle": { - "message": "Há uma actualização para o Signal", + "message": "Existe uma actualização disponível para o Signal", "description": "" }, "autoUpdateNewVersionMessage": { @@ -1410,7 +1468,7 @@ "description": "" }, "autoUpdateNewVersionInstructions": { - "message": "Pressione Reiniciar o Signal para aplicar as atualizações.", + "message": "Pressione 'Reiniciar o Signal' para aplicar as atualizações.", "description": "" }, "autoUpdateRestartButtonLabel": { @@ -1422,7 +1480,7 @@ "description": "" }, "leftTheGroup": { - "message": "$name$ deixou o grupo", + "message": "$name$ abandonou o grupo", "description": "Shown in the conversation history when a single person leaves the group", "placeholders": { "name": { @@ -1432,7 +1490,7 @@ } }, "multipleLeftTheGroup": { - "message": "$name$ deixou o grupo", + "message": "$name$ abandonou o grupo", "description": "Shown in the conversation history when multiple people leave the group", "placeholders": { "name": { @@ -1442,7 +1500,7 @@ } }, "updatedTheGroup": { - "message": "Grupo actualizado", + "message": "Grupo atualizado", "description": "Shown in the conversation history when someone updates the group" }, "titleIsNow": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Termos e Política de Privacidade", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/ro/messages.json b/_locales/ro/messages.json index 0288be37d3..dfc8ed7f12 100644 --- a/_locales/ro/messages.json +++ b/_locales/ro/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copiază eroare și închide aplicația", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Grup necunoscut", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Eroare de bază de date", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Șterg toate datele și repornește", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Fișier", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Conversații arhivate", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Conversațiile sunt arhivate și vor apărea doar în Inbox dacă sunt primite mesaje noi.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Arhivați conversația", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Mutați conversația în Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Alege directorul", "description": "Button to allow the user to find a folder on disk" @@ -480,7 +512,7 @@ "description": "Name for a voice message attachment" }, "dangerousFileType": { - "message": "Attachment type not allowed for security reasons", + "message": "Tipul de atașament nu este permis din motive de securitate", "description": "Shown in toast when user attempts to send .exe file, for example" }, "loadingPreview": { @@ -521,12 +553,12 @@ "message": "When including a non-image attachment, the limit is one attachment per message.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "You cannot mix non-image and image attachments in one message.", "description": "An error popup when the user has attempted to add an attachment" }, "maximumAttachments": { - "message": "You cannot add any more attachments to this message.", + "message": "Nu mai poți adăuga alte atașamente la acest mesaj.", "description": "An error popup when the user has attempted to add an attachment" }, "fileSizeWarning": { @@ -611,15 +643,37 @@ "message": "Închide", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Introdu nume sau număr", + "search": { + "message": "Căutați", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "Nici un rezultat pentru \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Conversații", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Contacte", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Mesaje", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Bun venit la Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "Căsuță poștală", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Se descarcă", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Descarcă atașamentul", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Poză", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Către", + "message": "către", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "General", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Trimite previzualizarea link-urilor", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previzualizarea este suportată pentru link-uri Imgur, Instagram, Reddit și YouTube", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Enable spell check of text entered in message composition box", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Această versiune de Signal Desktop a expirat. Te rog actualizează aplicația la ultima versiune pentru a putea trimite mesaje în continuare.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Clienții Android vor primi doar primele 2000 de caractere din acest mesaj.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Actualizează", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Închisă", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Notă personală", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Ascunde bara de meniu", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Pornește conversația...", + "message": "Pornește o conversație nouă...", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Termeni și politica de confidențialitate", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json index e553f237ab..6e20d85959 100644 --- a/_locales/ru/messages.json +++ b/_locales/ru/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Скопировать ошибку и выйти", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Неизвестная группа", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Ошибка базы данных", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Удалить все данные и перезапустить", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Файл", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Архивированные разговоры", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Эти разговоры архивированы и будут появляться во Входящих только когда получены новые сообщения.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Архивировать разговор", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Переместить разговор во Входящие", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Выберите папку", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "При включении в сообщение вложений, не относящихся к изображениям, ограничение составляет по одному вложению на сообщение.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "Нельзя смешивать вложения, не относящиеся к изображениям, с изображениями в одном сообщении.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Выйти", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Введите имя или номер", + "search": { + "message": "Поиск", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "Нет результатов для \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Разговоры", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Контакты", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Сообщения", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Добро пожаловать в Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "почтовый ящик", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Скачивание", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Загрузить вложение", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Фото", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Кому", + "message": "->", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Общие", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Отправлять предпросмотр ссылки", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Предварительный просмотр поддерживается для ссылок Imgur, Instagram, Reddit и YouTube.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Включить проверку орфографии текста, введенного в поле создания сообщения", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Эта версия Signal Desktop устарела. Выполните обновление до последней версии, чтобы продолжить обмен сообщениями.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Пользователи Android получат только первые 2000 символов этого сообщения.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Обновить", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Темная", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Заметка для себя", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Скрыть строку меню", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Начать разговор…", + "message": "Начать новый разговор…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Условия и политика конфиденциальности", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/sk/messages.json b/_locales/sk/messages.json index 5083b33426..4e79bb4067 100644 --- a/_locales/sk/messages.json +++ b/_locales/sk/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Kopírovať a ukončiť", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Neznáma skupina", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Chyba databázy", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Vymazať všetky dáta a reštartovať", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Súbor", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Archivované konverzácie", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "These conversations are archived and will only appear in the Inbox if new messages are received.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archivovať konverzáciu", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Presunúť konverzáciu do doručených", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Zvoľte priečinok", "description": "Button to allow the user to find a folder on disk" @@ -484,7 +516,7 @@ "description": "Shown in toast when user attempts to send .exe file, for example" }, "loadingPreview": { - "message": "Loading Preview...", + "message": "Načítavam náhľad...", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area" }, "stagedPreviewThumbnail": { @@ -521,8 +553,8 @@ "message": "When including a non-image attachment, the limit is one attachment per message.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { - "message": "You cannot mix non-image and image attachments in one message.", + "cannotMixImageAndNonImageAttachments": { + "message": "V jednej správe nemôže byť súčasne príloha s obrázkom a príloha bez obrázku.", "description": "An error popup when the user has attempted to add an attachment" }, "maximumAttachments": { @@ -611,15 +643,37 @@ "message": "Ukončiť", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Zadajte meno alebo číslo", + "search": { + "message": "Hľadať", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "Žiadne výsledky pre \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Konverzácie", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Kontakty", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Správy", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Vitajte v aplikácii Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "P.O.Box", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Sťahujem", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Stiahnuť prílohu", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Fotka", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Pre", + "message": "pre", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Všeobecné", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Povoliť kontrolu pravopisu textu zadaného do textového poľa", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Táto verzia aplikácie Signal Desktop je zastaralá. Pre pokračovanie ju prosím aktualizujte na najnovšiu verziu.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Klienti na systému Android obdržia len prvých 2000 znakov tejto správy.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Aktualizovať", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Tmavý", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Poznámka pre seba", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Skryť ponuku", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Začať rozhovor...", + "message": "Začať nový rozhovor…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Podmienky a Ochrana osobných údajov", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/sl/messages.json b/_locales/sl/messages.json index 5296dcf83a..81250e0b58 100644 --- a/_locales/sl/messages.json +++ b/_locales/sl/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Kopiraj napako in zapusti aplikacijo", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Neznana skupina", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Napaka v bazi podatkov", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Izbris vseh podatkov in ponovni zagon", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Datoteka", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Arhivirani pogovori", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Ti pogovori so bili premaknjeni v arhiv in se bodo znova pojavili v nabiralniku, če prejmete novo sporočilo.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Premakni pogovor v arhiv", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Več pogovorov v nabiralnik", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Izberite mapo", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "Neslikovne datoteke so omejene na eno priponko na sporočilo.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "V istem sporočilu ne morete kombinirati neslikovnih in slikovnih priponk.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Končaj", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Vnesite ime ali številko", + "search": { + "message": "Iskanje", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "Ni rezultata za \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Pogovori", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Stiki", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Sporočila", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Dobrodošli v aplikaciji Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "p. p.", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Prenašam", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Prenesi priponko", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Slika", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Naslovnik", + "message": "v", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Splošno", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Pošiljanje povezav s predogledom", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Predogled je na voljo za povezave na Imgur, Instagram, Reddit in YouTube", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Vklop črkovalnika v okencu za vnašanje sporočila", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Pričujoča različica programa Signal Desktop je potekla. Za ponovno delovanje morate program posodobiti na zadnjo različico.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Odjemalec za Android bo prejel le prvih 2000 znakov tega sporočila.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Posodobi", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Temna", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Osebna zabeležka", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Skrij menijsko vrstico", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Začetek pogovora ...", + "message": "Začni nov pogovor ...", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Pogoji uporabe in Pravilnik o zasebnosti", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/sq/messages.json b/_locales/sq/messages.json index f9ddf73de5..06ee6e4f3c 100644 --- a/_locales/sq/messages.json +++ b/_locales/sq/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Kopjo gabimin dhe dil", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Grup i panjohur", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Gabim Baze të Dhënash", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Fshiji krejt të dhënat dhe rifillo", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Kartelë", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Biseda të Arkivuara", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Këto biseda janë të arkivuara dhe do të shfaqen te Të marrë vetëm nëse merren mesazhe të rinj.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Arkivojeni Bisedën", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Kaloje Bisedën te Të marrë", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Zgjidhni dosje", "description": "Button to allow the user to find a folder on disk" @@ -178,7 +210,7 @@ "description": "Message shown if the import went wrong; first paragraph" }, "importErrorSecond": { - "message": "Nëse këto hapa nuk bëjnë punë për ju, ju lutemi, parashtroni një regjistrim diagnostikimi (Shihni -> Regjistër Diagnostikimesh) që t’ju ndihmojmë të migroni!", + "message": "Nëse këto hapa s’ju bëjnë punë, ju lutemi, parashtroni një regjistrim diagnostikimi (Shihni -> Regjistër Diagnostikimesh) që t’ju ndihmojmë të migroni!", "description": "Message shown if the import went wrong; second paragraph" }, "importAgain": { @@ -521,7 +553,7 @@ "message": "Kur përfshihet një bashkëngjitje që s’është figurë, kufiri është një bashkëngjitje për mesazh.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "S’mund të përzieni brenda të njëjtit mesazh bashkëngjitje figurë dhe jo figurë.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Dil", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Jepni emër ose numër", + "search": { + "message": "Kërko", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "S’ka përfundime për \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Biseda.", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Kontakte", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Mesazhe", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Mirë se vini te Signal-i", "description": "" }, @@ -673,6 +727,10 @@ "message": "Kuti Postare", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Po shkarkohet", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Shkarko Bashkëngjitjen", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Foto", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Për", + "message": "për", "description": "Label for the receiver of a message" }, "sent": { @@ -956,17 +1022,9 @@ "description": "Description of the media permission description" }, "general": { - "message": "Përgjithshëm", + "message": "Të përgjithshme", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Dërgo Paraparje Lidhjesh", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Paraparjet mbulohen për lidhje Imgur, Instagram, Reddit, dhe YouTube.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Aktivizo kontroll drejtshkrimi të tekstit të dhënë te kutia e hartimit të mesazheve", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Ky version i Signal-it për Desktop ka skaduar. Ju lutemi, që të vazhdoni të shkëmbeni mesazhe, përmirësojeni me versionin më të ri.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Klientët me Android do të marrin vetëm 2000 shenjat e para të këtij mesazhi.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Përmirësojeni", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "E errët", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Shënim për Veten", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Fshihe shtyllën e menusë", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Nisni bisedë…", + "message": "Filloni bisedë të re…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Kushte & Rregulla Privatësie", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/sr/messages.json b/_locales/sr/messages.json index 6bd676ee9e..e58b1496e7 100644 --- a/_locales/sr/messages.json +++ b/_locales/sr/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copy error and quit", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database Error", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Delete all data and restart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&File", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -36,7 +52,7 @@ "description": "Application menu command to show all application windows" }, "appMenuQuit": { - "message": "Quit Loki Messenger", + "message": "Quit Session", "description": "Application menu command to close the application" }, "editMenuUndo": { @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Archived Conversations", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "These conversations are archived and will only appear in the Inbox if new messages are received.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Одаберите фолдер", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "When including a non-image attachment, the limit is one attachment per message.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "You cannot mix non-image and image attachments in one message.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Quit", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Enter name or number", + "search": { + "message": "Тражи", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Преписке", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Контакти", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Поруке", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Добродошли у Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "PO Box", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Преузимам", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Download Attachment", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Photo", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "У реду", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "За", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Опште", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Enable spell check of text entered in message composition box", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Ваша верзија Signal-а за десктоп је застарела. Да би сте наставили дописивање, молимо вас да ажурирате програм.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Андроид клијент ће примити само првих 2000 карактера ове поруке", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Ажурирај", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Dark", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Note to Self", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Hide menu bar", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Start conversation…", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1446,7 +1504,7 @@ "description": "Shown in the conversation history when someone updates the group" }, "titleIsNow": { - "message": "Title is now '$name$'", + "message": "Group name has been set to '$name$'", "description": "Shown in the conversation history when someone changes the title of the group", "placeholders": { "name": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Terms & Privacy Policy", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/sv/messages.json b/_locales/sv/messages.json index 1a5b0f6fad..4af4a82952 100644 --- a/_locales/sv/messages.json +++ b/_locales/sv/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Kopiera felmeddelande och avsluta", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Okänd grupp", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Databasfel", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Radera all data och starta om", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Arkiv", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Arkiverade konversationer", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Konversationerna här är arkiverade och kommer bara visas i inkorgen ifall nya meddelanden tas emot.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Arkivera konversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Flytta konversationen till Inkorg", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Välj mapp", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "När man inkluderar bilagor som inte är bilder, är begränsningen en bilaga per meddelande.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "Du kan inte blanda bilagor som både är, och inte är, bilder i ett meddelande.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Avsluta", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Skriv in namn eller nummer", + "search": { + "message": "Sök", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "Inga resultat för \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Konversationer", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Kontakter", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Meddelanden", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Välkommen till Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "PO-låda", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Laddar ner", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Hämta bifogad fil", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Foto", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "OK", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Till", + "message": "till", "description": "Label for the receiver of a message" }, "sent": { @@ -959,16 +1025,8 @@ "message": "Generell", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Skicka länkförhandsvisningar", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Förhandsvisningar stöds för Imgur-, Instagram-, Reddit- och YouTube-länkar.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { - "message": "Slå på stavningskontroll för text skrivet i meddelandefältet", + "message": "Slå på stavningskontroll för text som anges i meddelandefältet", "description": "Description of the media permission description" }, "clearDataHeader": { @@ -1063,10 +1121,6 @@ "message": "Den här versionen av Signal Desktop har utgått. Vänligen uppgradera till den senaste versionen för att fortsätta chatta.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Androidklienter kommer bara ta emot de första 2000 tecknen av detta meddelande.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Uppgradera", "description": "Label text for button to upgrade the app to the latest version" @@ -1146,7 +1200,7 @@ } }, "minutesAgo": { - "message": "$minutes$ minuter sen", + "message": "$minutes$ minuter sedan", "description": "Contracted form of 'X minutes ago' which works both for singular and plural", "placeholders": { "minutes": { @@ -1366,23 +1420,27 @@ } }, "themeLight": { - "message": "Ljus", + "message": "Ljust", "description": "Label text for light theme (normal)" }, "themeDark": { - "message": "Mörk", + "message": "Mörkt", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Notera till mig själv", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Göm menyraden", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Påbörja konversation...", + "message": "Starta en ny konversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { - "message": "Skriv in ett telefonnummer för att lägga till en kontakt.", + "message": "Ange ett telefonnummer för att lägga till en kontakt.", "description": "Placeholder for adding a new number to a contact" }, "invalidNumberError": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Licens- och sekretessvillkor", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/th/messages.json b/_locales/th/messages.json index ff965f5e30..d487a04266 100644 --- a/_locales/th/messages.json +++ b/_locales/th/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copy error and quit", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database Error", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Delete all data and restart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "ไ&ฟล์", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Archived Conversations", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "These conversations are archived and will only appear in the Inbox if new messages are received.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "เลือกโฟลเดอร์", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "When including a non-image attachment, the limit is one attachment per message.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "You cannot mix non-image and image attachments in one message.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "ออก", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "ป้อนชื่อหรือหมายเลข", + "search": { + "message": "ค้นหา", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "การสนทนา", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "ผู้ติดต่อ", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "ข้อความ", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "ยินดีต้อนรับสู่ Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "ตู้ไปรษณีย์", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Downloading", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Download Attachment", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "รูปภาพ", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "ตกลง", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "ถึง", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "General", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Enable spell check of text entered in message composition box", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Signal Desktop เวอร์ชันนี้หมดอายุแล้ว โปรดอัพเกรดไปเป็นเวอร์ชันล่าสุดเพื่อดำเนินการส่งข้อความต่อ", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "สำหรับไคลเอ็นต์ Android จะได้รับข้อความ 2000 ตัวอักษรแรกเท่านั้น", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "อัพเกรด", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Dark", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Note to Self", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "ซ่อนแถบเมนู", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "เริ่มต้นการสนทนา", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "เงื่อนไขและนโยบายความเป็นส่วนตัว", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/tr/messages.json b/_locales/tr/messages.json index c045a749af..a217b4a75a 100644 --- a/_locales/tr/messages.json +++ b/_locales/tr/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Hatayı kopyala ve çık", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Bilinmeyen grup", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Veritabanı hatası", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Tüm verileri sil ve yeniden başlat", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Dosya", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -136,7 +152,7 @@ "description": "Message shown on the loading screen while we are doing application optimizations" }, "migratingToSQLCipher": { - "message": "Mesajlar optimize ediliyor... $status$ tamamlandı.", + "message": "İletiler optimize ediliyor... $status$ tamamlandı.", "description": "Message shown on the loading screen while we are doing application optimizations", "placeholders": { "status": { @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Arşivlenmiş Sohbetler", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Bu sohbetler arşivlendi ve sadece yeni iletiler alınırsa gelen kutusunda görünecekler.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Sohbeti Arşivle", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Sohbeti Gelen Kutusuna Taşı", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Klasör seçin", "description": "Button to allow the user to find a folder on disk" @@ -158,7 +190,7 @@ "description": "Header shown on the first screen in the data import process" }, "loadDataDescription": { - "message": "Dışa aktarma işlemini tamamladınız ve kişileriniz ile mesajlarınız sabırla bilgisayarınızda bekliyor. Kaydettiğiniz Signal verilerinizi içeren klasörü seçin.", + "message": "Dışa aktarma işlemini tamamladınız ve kişileriniz ile iletileriniz sabırla bilgisayarınızda bekliyor. Kaydettiğiniz Signal verilerinizi içeren klasörü seçin.", "description": "Introduction to the process of importing messages and contacts from disk" }, "importChooserTitle": { @@ -170,7 +202,7 @@ "description": "Header of the error screen after a failed import" }, "importingHeader": { - "message": "Kişiler ve mesajlar yükleniyor", + "message": "Kişiler ve iletiler yükleniyor", "description": "Header of screen shown as data is import" }, "importErrorFirst": { @@ -206,7 +238,7 @@ "description": "Message shown on the loading screen when we're changing database structure on first run of a new version" }, "loadingMessages": { - "message": "Mesajlar yükleniyor. Şimdiye kadar $count$...", + "message": "İletiler yükleniyor. Şimdiye kadar $count$...", "description": "Message shown on the loading screen when we're catching up on the backlog of messages", "placeholders": { "count": { @@ -232,19 +264,19 @@ "description": "Alt text for button to take user down to bottom of conversation, shown when user scrolls up" }, "messageBelow": { - "message": "Aşağıda yeni mesaj var", + "message": "Aşağıda yeni ileti var", "description": "Alt text for button to take user down to bottom of conversation with a new message out of screen" }, "messagesBelow": { - "message": "Aşağıda yeni mesajlar var", + "message": "Aşağıda yeni iletiler var", "description": "Alt text for button to take user down to bottom of conversation with more than one message out of screen" }, "unreadMessage": { - "message": "1 Okunmamış Mesaj", + "message": "1 Okunmamış İleti", "description": "Text for unread message separator, just one message" }, "unreadMessages": { - "message": "$count$ Okunmamış Mesaj", + "message": "$count$ Okunmamış İleti", "description": "Text for unread message separator, with count", "placeholders": { "count": { @@ -428,7 +460,7 @@ "description": "" }, "incomingError": { - "message": "Gelen mesaj işlenirken hata oluştu", + "message": "Gelen ileti işlenirken hata oluştu", "description": "" }, "media": { @@ -476,7 +508,7 @@ "description": "Hover text for attachment filenames" }, "voiceMessage": { - "message": "Sesli Mesaj", + "message": "Sesli İleti", "description": "Name for a voice message attachment" }, "dangerousFileType": { @@ -518,19 +550,19 @@ } }, "oneNonImageAtATimeToast": { - "message": "Görüntü olmayan eklentiler içerildiğinde, mesaj başı eklenti sınırı birdir.", + "message": "Görüntü olmayan eklentiler içerildiğinde, ileti başı eklenti sınırı birdir.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { - "message": "Görüntü ve görüntü olmayan eklentileri tek mesajda birleştiremezsiniz.", + "cannotMixImageAndNonImageAttachments": { + "message": "Görüntü ve görüntü olmayan eklentileri tek iletide birleştiremezsiniz.", "description": "An error popup when the user has attempted to add an attachment" }, "maximumAttachments": { - "message": "Bu mesaja daha fazla eklenti ekleyemezsiniz.", + "message": "Bu iletiye daha fazla eklenti ekleyemezsiniz.", "description": "An error popup when the user has attempted to add an attachment" }, "fileSizeWarning": { - "message": "Üzgünüz, seçilen dosya mesaj boyutu kısıtlamalarını aşıyor.", + "message": "Üzgünüz, seçilen dosya ileti boyutu kısıtlamalarını aşıyor.", "description": "" }, "unableToLoadAttachment": { @@ -611,15 +643,37 @@ "message": "Çıkış Yap", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "İsim ya da numara girin", + "search": { + "message": "Ara", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "\"$searchTerm$\" için arama sonucu yok", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Sohbetler", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Kişiler", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "İletiler", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Signal'e Hoşgeldin", "description": "" }, @@ -642,7 +696,7 @@ } }, "sendMessageToContact": { - "message": "Mesaj Gönder", + "message": "İleti Gönder", "description": "Shown when you are sent a contact and that contact has a signal account" }, "home": { @@ -673,28 +727,32 @@ "message": "Posta Kutusu", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "İndiriliyor", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Eklentiyi İndir", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" }, "replyToMessage": { - "message": "Mesajı Yanıtla", + "message": "İletiyi Yanıtla", "description": "Shown in triple-dot menu next to message to allow user to start crafting a message with a quotation" }, "originalMessageNotFound": { - "message": "Mesajın aslı bulunamadı", + "message": "İletinin aslı bulunamadı", "description": "Shown in quote if reference message was not found as message was initially downloaded and processed" }, "originalMessageNotAvailable": { - "message": "Mesajın aslı artık mevcut değil", + "message": "İletinin aslı artık mevcut değil", "description": "Shown in toast if user clicks on quote that references message no longer in database" }, "messageFoundButNotLoaded": { - "message": "Mesajın aslı bulundu, ama yüklenmedi. Yüklemek için yukarıya kaydırın.", + "message": "İletinin aslı bulundu, ama yüklenmedi. Yüklemek için yukarıya kaydırın.", "description": "Shown in toast if user clicks on quote references messages not loaded in view, but in database" }, "voiceNoteMustBeOnlyAttachment": { - "message": "Sesli not bir mesajda içerilen tek eklenti olmalıdır.", + "message": "Sesli not bir iletide içerilen tek eklenti olmalıdır.", "description": "Shown in toast if tries to record a voice note with any staged attachments" }, "you": { @@ -712,7 +770,7 @@ } }, "audioPermissionNeeded": { - "message": "Sesli mesajlar göndermek için, Signal Desktop'ın mikrofonunuza erişimine izin verin.", + "message": "Sesli iletiler göndermek için, Signal Desktop'ın mikrofonunuza erişimine izin verin.", "description": "Shown if the user attempts to send an audio message without audio permssions turned on" }, "allowAccess": { @@ -735,6 +793,14 @@ "message": "Fotoğraf", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "TAMAM", "description": "" @@ -752,7 +818,7 @@ "description": "" }, "messageDetail": { - "message": "Mesaj Detayı", + "message": "İleti Detayı", "description": "" }, "delete": { @@ -760,11 +826,11 @@ "description": "" }, "deleteWarning": { - "message": "Emin misiniz? 'Sil'e tıklamak bu mesajı sadece bu cihazdan kalıcı olarak silecektir.", + "message": "Emin misiniz? 'Sil'e tıklamak bu iletiyi sadece bu cihazdan kalıcı olarak silecektir.", "description": "" }, "deleteThisMessage": { - "message": "Bu mesajı sil", + "message": "Bu iletiyi sil", "description": "" }, "from": { @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Alıcı", + "message": "alıcı", "description": "Label for the receiver of a message" }, "sent": { @@ -784,7 +850,7 @@ "description": "Label for the time a message was received" }, "sendMessage": { - "message": "Mesaj gönder", + "message": "İleti gönder", "description": "Placeholder text in the message entry field" }, "groupMembers": { @@ -818,7 +884,7 @@ } }, "theirIdentityUnknown": { - "message": "Bu kişiyle herhangi bir mesajlaşmada bulunmadınız. Güvenlik numaralarınız ilk mesajdan sonra oluşturulacaktır.", + "message": "Bu kişiyle herhangi bir iletişimde bulunmadınız. Güvenlik numaralarınız ilk iletiden sonra oluşturulacaktır.", "description": "" }, "moreInfo": { @@ -830,11 +896,11 @@ "description": "Shown on the drop-down menu for an indinvidaul message, but only if it is an outgoing message that failed to send" }, "deleteMessage": { - "message": "Mesajı Sil", + "message": "İletiyi Sil", "description": "Shown on the drop-down menu for an individual message, deletes single message" }, "deleteMessages": { - "message": "Mesajları sil", + "message": "İletileri sil", "description": "Menu item for deleting messages, title case." }, "deleteConversationConfirmation": { @@ -846,15 +912,15 @@ "description": "This is a past tense, informational message. In other words, your secure session has been reset." }, "quoteThumbnailAlt": { - "message": "Alıntılanmış mesajdaki görüntünün önizlemesi", + "message": "Alıntılanmış iletideki görüntünün önizlemesi", "description": "Used in alt tag of thumbnail images inside of an embedded message quote" }, "imageAttachmentAlt": { - "message": "Mesaja görüntü eklenmiş", + "message": "İletiye görüntü eklenmiş", "description": "Used in alt tag of image attachment" }, "videoAttachmentAlt": { - "message": "Mesaja eklenen videonun ekran görüntüsü", + "message": "İletiye eklenen videonun ekran görüntüsü", "description": "Used in alt tag of video attachment preview" }, "lightboxImageAlt": { @@ -959,16 +1025,8 @@ "message": "Genel", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Bağlantı Ön İzlemelerini Gönder", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Ön izlemeler Imgur, Instagram, Reddit, ve YouTube bağlantıları için desteklenmektedir.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { - "message": "Mesaj kutusuna girilen sözcüklerin denetlenmesini etkinleştir", + "message": "İleti kutusuna girilen sözcüklerin denetlenmesini etkinleştir", "description": "Description of the media permission description" }, "clearDataHeader": { @@ -976,7 +1034,7 @@ "description": "Header in the settings dialog for the section dealing with data deletion" }, "clearDataExplanation": { - "message": "Bu, tüm mesajları ve kayıtlı hesap bilgilerini kaldırarak uygulamadaki tüm verileri temizler.", + "message": "Bu, tüm iletileri ve kayıtlı hesap bilgilerini kaldırarak uygulamadaki tüm verileri temizler.", "description": "Text describing what the clear data button will do." }, "clearDataButton": { @@ -988,7 +1046,7 @@ "description": "Header of the full-screen delete data confirmation screen" }, "deleteAllDataBody": { - "message": "Tüm kişilerin ve tüm mesajların dahil olduğu bu uygulamanın tüm kayıtlı hesap bilgilerini silmek üzeresiniz. İstediğiniz zaman mobil cihazınızla tekrar bağlayabilirsiniz, ancak silinen mesajlar geri yüklenmeyecektir.", + "message": "Tüm kişilerin ve tüm iletilerin dahil olduğu bu uygulamanın tüm kayıtlı hesap bilgilerini silmek üzeresiniz. İstediğiniz zaman mobil cihazınızla tekrar bağlayabilirsiniz, ancak silinen iletiler geri yüklenmeyecektir.", "description": "Text describing what exactly will happen if the user clicks the button to delete all data" }, "deleteAllDataButton": { @@ -1004,7 +1062,7 @@ "description": "Header for notification settings" }, "notificationSettingsDialog": { - "message": "Mesaj geldiğinde şunların olduğu bildirim göster:", + "message": "İleti geldiğinde şunların olduğu bildirim göster:", "description": "Explain the purpose of the notification settings" }, "disableNotifications": { @@ -1012,11 +1070,11 @@ "description": "Label for disabling notifications" }, "nameAndMessage": { - "message": "Gönderenin adını ve mesajını", + "message": "Gönderenin adını ve iletisini", "description": "Label for setting notifications to display name and message text" }, "noNameOrMessage": { - "message": "Ne gönderenin adını ne de mesajını", + "message": "Ne gönderenin adını ne de iletisini", "description": "Label for setting notifications to display no name and no message text" }, "nameOnly": { @@ -1024,11 +1082,11 @@ "description": "Label for setting notifications to display sender name only" }, "newMessage": { - "message": "Yeni Mesaj", + "message": "Yeni İleti", "description": "Displayed in notifications for only 1 message" }, "newMessages": { - "message": "Yeni Mesajlar", + "message": "Yeni İletiler", "description": "Displayed in notifications for multiple messages" }, "notificationMostRecentFrom": { @@ -1060,19 +1118,15 @@ "description": "Text that links to a support article on verifying safety numbers" }, "expiredWarning": { - "message": "Signal Desktop'ın bu sürümünün süresi doldu. Mesajlaşmaya devam etmek için lütfen en son sürüme geçin.", + "message": "Signal Desktop'ın bu sürümünün süresi doldu. İletişime devam etmek için lütfen en son sürüme geçin.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Android istemcileri bu mesajın yalnızca ilk 2000 karakterini alacaktır.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Yükselt", "description": "Label text for button to upgrade the app to the latest version" }, "mediaMessage": { - "message": "Medya mesajı", + "message": "Medya iletisi", "description": "Description of a message that has an attachment and no text, displayed in the conversation list as a preview." }, "unregisteredUser": { @@ -1164,15 +1218,15 @@ "description": "Timestamp format string for displaying month and day (but not the year) of a date within the current year, ex: use 'MMM D' for 'Aug 8', or 'D MMM' for '8 Aug'." }, "unblockToSend": { - "message": "Mesaj göndermek için bu kişinin engellenmesini kaldırın.", + "message": "İleti göndermek için bu kişinin engellenmesini kaldırın.", "description": "Brief message shown when trying to message a blocked number" }, "unblockGroupToSend": { - "message": "Mesaj göndermek için bu grubun engellenmesini kaldırın.", + "message": "İleti göndermek için bu grubun engellenmesini kaldırın.", "description": "Brief message shown when trying to message a blocked group" }, "youChangedTheTimer": { - "message": "Kaybolan mesaj zamanlayıcsını $time$ olarak ayarladınız", + "message": "Kaybolan ileti zamanlayıcsını $time$ olarak ayarladınız", "description": "Message displayed when you change the message expiration timer in a conversation.", "placeholders": { "time": { @@ -1182,7 +1236,7 @@ } }, "timerSetOnSync": { - "message": "Kaybolan mesaj zamanlayıcısı $time$ olarak güncellendi", + "message": "Kaybolan ileti zamanlayıcısı $time$ olarak güncellendi", "description": "Message displayed when timer is set on initial link of desktop device.", "placeholders": { "time": { @@ -1192,7 +1246,7 @@ } }, "theyChangedTheTimer": { - "message": "$name$ kaybolan mesaj zamanlayıcısını $time$ olarak ayarladı", + "message": "$name$ kaybolan ileti zamanlayıcısını $time$ olarak ayarladı", "description": "Message displayed when someone else changes the message expiration timer in a conversation.", "placeholders": { "name": { @@ -1254,7 +1308,7 @@ "description": "Label for a selectable option in the message expiration timer menu" }, "disappearingMessages": { - "message": "Kaybolan mesajlar", + "message": "Kaybolan iletiler", "description": "Conversation menu option to enable disappearing messages" }, "timerOption_0_seconds_abbreviated": { @@ -1306,11 +1360,11 @@ "description": "Very short format indicating current timer setting in the conversation header" }, "disappearingMessagesDisabled": { - "message": "Kaybolan mesajlar devre dışı", + "message": "Kaybolan iletiler devre dışı", "description": "Displayed in the left pane when the timer is turned off" }, "disabledDisappearingMessages": { - "message": "$name$ kaybolan mesajları devre dışı bıraktı", + "message": "$name$ kaybolan iletileri devre dışı bıraktı", "description": "Displayed in the conversation list when the timer is turned off", "placeholders": { "name": { @@ -1320,7 +1374,7 @@ } }, "youDisabledDisappearingMessages": { - "message": "Kaybolan mesajları devre dışı bıraktınız", + "message": "Kaybolan iletileri devre dışı bıraktınız", "description": "Displayed in the conversation list when the timer is turned off" }, "timerSetTo": { @@ -1373,12 +1427,16 @@ "message": "Koyu", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Kendime Not", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Menü çubuğunu gizle", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Sohbet başlat…", + "message": "Yeni sohbet başlat…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1390,7 +1448,7 @@ "description": "When a person inputs a number that is invalid" }, "unlinkedWarning": { - "message": "Mesajlaşmaya devam etmek için Signal Desktop'ı mobil cihazınıza yeniden bağlayın.", + "message": "İletişime devam etmek için Signal Desktop'ı mobil cihazınıza yeniden bağlayın.", "description": "" }, "unlinked": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Şartlar ve Gizlilik İlkesi", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/uk/messages.json b/_locales/uk/messages.json index 4d911759c0..5bc5dae147 100644 --- a/_locales/uk/messages.json +++ b/_locales/uk/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copy error and quit", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database Error", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Delete all data and restart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&Файл", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -36,7 +52,7 @@ "description": "Application menu command to show all application windows" }, "appMenuQuit": { - "message": "Quit Loki Messenger", + "message": "Quit Session", "description": "Application menu command to close the application" }, "editMenuUndo": { @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Архівовані розмови", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "Ці розмови — архівовані. Вони з’являтимуться у вхідних лише тоді, коли приходитимуть нові повідомлення.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Choose folder", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "When including a non-image attachment, the limit is one attachment per message.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "You cannot mix non-image and image attachments in one message.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Вийти", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Enter name or number", + "search": { + "message": "Пошук", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Розмови", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Контакти", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Повідомлення", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Ласкаво просимо до Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "PO Box", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Завантаження", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Download Attachment", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Photo", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "Добре", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Кому", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Загальні", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Enable spell check of text entered in message composition box", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Ця версія застаріла. Оновіться до останньої версії щоб продовжити спілкування.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "На Андроїді буде видно лише перші 2 000 знаків цього повідомлення.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Оновити", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Dark", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Note to Self", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Сховати стрічку меню", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Start conversation…", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1446,7 +1504,7 @@ "description": "Shown in the conversation history when someone updates the group" }, "titleIsNow": { - "message": "Title is now '$name$'", + "message": "Group name has been set to '$name$'", "description": "Shown in the conversation history when someone changes the title of the group", "placeholders": { "name": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Умови використання та політика конфіденційності", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/vi/messages.json b/_locales/vi/messages.json index bec7d70d83..496213f33b 100644 --- a/_locales/vi/messages.json +++ b/_locales/vi/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "Copy error and quit", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "Unknown group", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "Database Error", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "Delete all data and restart", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "&File", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -36,7 +52,7 @@ "description": "Application menu command to show all application windows" }, "appMenuQuit": { - "message": "Quit Loki Messenger", + "message": "Quit Session", "description": "Application menu command to close the application" }, "editMenuUndo": { @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "Archived Conversations", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "These conversations are archived and will only appear in the Inbox if new messages are received.", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Conversation to Inbox", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Choose folder", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "When including a non-image attachment, the limit is one attachment per message.", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "You cannot mix non-image and image attachments in one message.", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "Quit", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "Enter name or number", + "search": { + "message": "Tìm kiếm", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "No results for \"$searchTerm$\"", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "Conversations", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "Liên hệ", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "Messages", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "Chào mừng đến với Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "PO Box", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "Đang tải xuống", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "Download Attachment", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "Photo", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "Đồng ý", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "Tới", + "message": "to", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "Tổng quát", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "Enable spell check of text entered in message composition box", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Phiên bản này của Signal Desktop đã hết hạn. Vui lòng nâng cấp lên phiên bản mới nhất để tiếp tục nhắn tin.", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Thiết bị Android sẽ chỉ nhận được 2000 ký tự đầu tiên của tin nhắn này.", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "Nâng cấp", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "Dark", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "Note to Self", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "Hide menu bar", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "Start conversation…", + "message": "Start new conversation…", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1446,7 +1504,7 @@ "description": "Shown in the conversation history when someone updates the group" }, "titleIsNow": { - "message": "Title is now '$name$'", + "message": "Group name has been set to '$name$'", "description": "Shown in the conversation history when someone changes the title of the group", "placeholders": { "name": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "Terms & Privacy Policy", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index 1a545f834c..efa5343e74 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "复制出错信息并退出", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "未知群", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "数据库错误", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "删除所有数据并重启", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "文件(&F)", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "已归档对话", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "这些对话已被归档,仅当有新消息到达时,它们才会重新出现在收件箱中。", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "存档会话", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "移动会话到收件箱", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "选择文件夹", "description": "Button to allow the user to find a folder on disk" @@ -484,11 +516,11 @@ "description": "Shown in toast when user attempts to send .exe file, for example" }, "loadingPreview": { - "message": "Loading Preview...", + "message": "加载预览中...", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area" }, "stagedPreviewThumbnail": { - "message": "Draft thumbnail link preview for $domain$", + "message": "待发送的$domain$链接预览", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -498,7 +530,7 @@ } }, "previewThumbnail": { - "message": "Thumbnail link preview for $domain$", + "message": "$domain$的链接预览缩略图", "description": "Shown while Signal Desktop is fetching metadata for a url in composition area", "placeholders": { "path": { @@ -508,7 +540,7 @@ } }, "stagedImageAttachment": { - "message": "Draft image attachment: $path$", + "message": "待发送的图片附件:$path$", "description": "Alt text for staged attachments", "placeholders": { "path": { @@ -521,7 +553,7 @@ "message": "每条消息只能添加一个非图片的附件。", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "在一条消息中,可以同时添加一张图片和一个非图片附件。", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "退出", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "桌面版Signal", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "请输入名字或号码", + "search": { + "message": "搜索", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "没有找到 “$searchTerm$” 相关结果", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "对话", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "联系人", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "信息", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "欢迎来到Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "邮政信箱", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "下载中", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "下载附件", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "照片", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "无法更新", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "好", "description": "" @@ -772,7 +838,7 @@ "description": "Label for the sender of a message" }, "to": { - "message": "至", + "message": "向", "description": "Label for the receiver of a message" }, "sent": { @@ -959,14 +1025,6 @@ "message": "通用", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "Send Link Previews", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "Previews are supported for Imgur, Instagram, Reddit, and YouTube links.", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "启用输入框拼写检查", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "此桌面版Signal已过期,请升级到最新版本以继续发送消息。", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Android客户端仅能接收此消息的前2000个字符", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "升级", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "暗色", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "备忘录", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "隐藏菜单栏", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "开始对话...", + "message": "开始新会话...", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "协议与隐私政策", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/_locales/zh_TW/messages.json b/_locales/zh_TW/messages.json index f7dd6ff355..1576c87622 100644 --- a/_locales/zh_TW/messages.json +++ b/_locales/zh_TW/messages.json @@ -1,4 +1,20 @@ { + "copyErrorAndQuit": { + "message": "複製錯誤並離開。", + "description": "Shown in the top-level error popup, allowing user to copy the error text and close the app" + }, + "unknownGroup": { + "message": "未知的群組", + "description": "Shown as the name of a group if we don't have any information about it" + }, + "databaseError": { + "message": "資料庫錯誤", + "description": "Shown in a popup if the database cannot start up properly" + }, + "deleteAndRestart": { + "message": "刪除所有資料並重新開始。", + "description": "Shown in a popup if the database cannot start up properly; allows user to dalete database and restart" + }, "mainMenuFile": { "message": "檔案(&F)", "description": "The label that is used for the File menu in the program main menu. The '&' indicates that the following letter will be used as the keyboard 'shortcut letter' for accessing the menu with the Alt- combination." @@ -145,6 +161,22 @@ } } }, + "archivedConversations": { + "message": "封存對話", + "description": "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": "這些對話已經封存,並只有在收到新訊息時才會顯示在收件箱中。", + "description": "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "歸檔對話", + "description": "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "將對話移動到收件夾", + "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "選擇資料夾", "description": "Button to allow the user to find a folder on disk" @@ -521,7 +553,7 @@ "message": "當包含的附檔不是圖片,每個訊息限制只能有一個附檔。", "description": "An error popup when the user has attempted to add an attachment" }, - "cannotMixImageAdnNonImageAttachments": { + "cannotMixImageAndNonImageAttachments": { "message": "你無法在一個訊息中,同時附上非圖片及圖片檔案。", "description": "An error popup when the user has attempted to add an attachment" }, @@ -611,15 +643,37 @@ "message": "離開", "description": "Command in the tray icon menu, to quit the application" }, - "trayTooltip": { + "signalDesktop": { "message": "Signal Desktop", "description": "Tooltip for the tray icon" }, - "searchForPeopleOrGroups": { - "message": "輸入名稱或號碼", + "search": { + "message": "搜尋", "description": "Placeholder text in the search input" }, - "welcomeToSignal": { + "noSearchResults": { + "message": "無 \"$searchTerm$\" 的搜尋結果", + "description": "Shown in the search left pane when no results were found", + "placeholders": { + "searchTerm": { + "content": "$1", + "example": "dog" + } + } + }, + "conversationsHeader": { + "message": "對話", + "description": "Shown to separate the types of search results" + }, + "contactsHeader": { + "message": "聯絡人", + "description": "Shown to separate the types of search results" + }, + "messagesHeader": { + "message": "訊息", + "description": "Shown to separate the types of search results" + }, + "welcomeToSession": { "message": "歡迎來到 Signal", "description": "" }, @@ -673,6 +727,10 @@ "message": "郵箱號碼", "description": "When rendering an address, used to provide context to a post office box" }, + "downloading": { + "message": "下載中...", + "description": "Shown in the message bubble while a long message attachment is being downloaded" + }, "downloadAttachment": { "message": "下載附件", "description": "Shown in a message's triple-dot menu if there isn't room for a dedicated download button" @@ -735,6 +793,14 @@ "message": "照片", "description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image" }, + "cannotUpdate": { + "message": "Cannot Update", + "description": "Shown as the title of our update error dialogs on windows" + }, + "readOnlyVolume": { + "message": "Signal Desktop is likely in a macOS quarantine, and will not be able to auto-update. Please try moving Signal.app to /Applications with Finder.", + "description": "Shown on MacOS if running on a read-only volume and we cannot update" + }, "ok": { "message": "好", "description": "" @@ -959,14 +1025,6 @@ "message": "一般", "description": "Header for general options on the settings screen" }, - "sendLinkPreviews": { - "message": "傳送連結預覽", - "description": "Option to control creation and send of link previews in setting screen" - }, - "linkPreviewsDescription": { - "message": "預覽功能支援 Imgur、Instagram、 Reddit 及 YouTube 的連結。", - "description": "Additional detail provided for Link Previews option in settings screen" - }, "spellCheckDescription": { "message": "在訊息撰寫時啟用文字輸入的拼字檢查", "description": "Description of the media permission description" @@ -1063,10 +1121,6 @@ "message": "Signal Desktop版的版本已經過期。請更新到最新版來傳送訊息。", "description": "Warning notification that this version of the app has expired" }, - "androidMessageLengthWarning": { - "message": "Android 使用者將只會收到訊息中的前 2000 個字", - "description": "Warning that long messages could not get received completely by Android clients." - }, "upgrade": { "message": "升級", "description": "Label text for button to upgrade the app to the latest version" @@ -1373,12 +1427,16 @@ "message": "暗色系", "description": "Label text for dark theme" }, + "noteToSelf": { + "message": "給自己的筆記", + "description": "Name for the conversation with your own phone number" + }, "hideMenuBar": { "message": "隱藏選單列", "description": "Label text for menu bar visibility setting" }, "startConversation": { - "message": "開始對話…", + "message": "開始新的對話...", "description": "Label underneath number a user enters that is not an existing contact" }, "newPhoneNumber": { @@ -1474,9 +1532,5 @@ "example": "Alice, Bob" } } - }, - "privacyPolicy": { - "message": "服務條款與隱私政策", - "description": "Shown in the about box for the link to https://signal.org/legal" } } diff --git a/app/auto_update.js b/app/auto_update.js deleted file mode 100644 index 6725cf3cdd..0000000000 --- a/app/auto_update.js +++ /dev/null @@ -1,89 +0,0 @@ -const { autoUpdater } = require('electron-updater'); -const { dialog } = require('electron'); - -const config = require('./config'); -const windowState = require('./window_state'); - -const hour = 60 * 60; -const autoUpdaterInterval = hour * 1000; - -const RESTART_BUTTON = 0; -const LATER_BUTTON = 1; - -function autoUpdateDisabled() { - return ( - process.platform === 'linux' || - process.mas || - config.get('disableAutoUpdate') - ); -} - -async function checkForUpdates() { - try { - await autoUpdater.checkForUpdates(); - } catch (error) { - console.log('checkForUpdates error:', error.stack); - } -} - -let showingDialog = false; -function showUpdateDialog(mainWindow, messages) { - if (showingDialog || !mainWindow) { - return; - } - showingDialog = true; - - const options = { - type: 'info', - buttons: [ - messages.autoUpdateRestartButtonLabel.message, - messages.autoUpdateLaterButtonLabel.message, - ], - title: messages.autoUpdateNewVersionTitle.message, - message: messages.autoUpdateNewVersionMessage.message, - detail: messages.autoUpdateNewVersionInstructions.message, - defaultId: LATER_BUTTON, - cancelId: RESTART_BUTTON, - }; - - dialog.showMessageBox(mainWindow, options, response => { - if (response === RESTART_BUTTON) { - // We delay these update calls because they don't seem to work in this - // callback - but only if the message box has a parent window. - // Fixes this bug: https://github.com/signalapp/Signal-Desktop/issues/1864 - setTimeout(() => { - windowState.markShouldQuit(); - autoUpdater.quitAndInstall(); - }, 200); - } - - showingDialog = false; - }); -} - -function onError(error) { - console.log('Got an error while updating:', error.stack); -} - -function initialize(getMainWindow, messages) { - if (!messages) { - throw new Error('auto-update initialize needs localized messages'); - } - - if (autoUpdateDisabled()) { - return; - } - - autoUpdater.addListener('update-downloaded', () => { - showUpdateDialog(getMainWindow(), messages); - }); - autoUpdater.addListener('error', onError); - - checkForUpdates(); - - setInterval(checkForUpdates, autoUpdaterInterval); -} - -module.exports = { - initialize, -}; diff --git a/app/config.js b/app/config.js index b5ef815003..3a865109d1 100644 --- a/app/config.js +++ b/app/config.js @@ -20,9 +20,12 @@ if (environment === 'production') { process.env.NODE_CONFIG = ''; process.env.NODE_CONFIG_STRICT_MODE = true; process.env.HOSTNAME = ''; - process.env.NODE_APP_INSTANCE = ''; process.env.ALLOW_CONFIG_MUTATIONS = ''; process.env.SUPPRESS_NO_CONFIG_WARNING = ''; + process.env.NODE_TLS_REJECT_UNAUTHORIZED = ''; + if (!process.env.LOKI_DEV) { + process.env.NODE_APP_INSTANCE = ''; + } } // We load config after we've made our modifications to NODE_ENV diff --git a/app/global_errors.js b/app/global_errors.js index 2966ab7939..85ddee387a 100644 --- a/app/global_errors.js +++ b/app/global_errors.js @@ -3,10 +3,11 @@ const electron = require('electron'); const Errors = require('../js/modules/types/errors'); const { app, dialog, clipboard } = electron; +const { redactAll } = require('../js/modules/privacy'); -// We're using hard-coded strings in this file because it needs to be ready -// to report errors before we do anything in the app. Also, we expect users to directly -// paste this text into search engines to find the bugs on GitHub. +// We use hard-coded strings until we're able to update these strings from the locale. +let quitText = 'Quit'; +let copyErrorAndQuitText = 'Copy error and quit'; function handleError(prefix, error) { console.error(`${prefix}:`, Errors.toLogFormat(error)); @@ -14,24 +15,29 @@ function handleError(prefix, error) { if (app.isReady()) { // title field is not shown on macOS, so we don't use it const buttonIndex = dialog.showMessageBox({ - buttons: ['OK', 'Copy error'], + buttons: [quitText, copyErrorAndQuitText], defaultId: 0, - detail: error.stack, + detail: redactAll(error.stack), message: prefix, noLink: true, type: 'error', }); if (buttonIndex === 1) { - clipboard.writeText(`${prefix}\n${error.stack}`); + clipboard.writeText(`${prefix}\n\n${redactAll(error.stack)}`); } } else { dialog.showErrorBox(prefix, error.stack); } - app.quit(); + app.exit(1); } +exports.updateLocale = messages => { + quitText = messages.quit.message; + copyErrorAndQuitText = messages.copyErrorAndQuit.message; +}; + exports.addHandler = () => { process.on('uncaughtException', error => { handleError('Unhandled Error', error); diff --git a/app/logging.js b/app/logging.js index a9492be27c..7897b28791 100644 --- a/app/logging.js +++ b/app/logging.js @@ -12,6 +12,8 @@ const readFirstLine = require('firstline'); const readLastLines = require('read-last-lines').read; const rimraf = require('rimraf'); +const { redactAll } = require('../js/modules/privacy'); + const { app, ipcMain: ipc } = electron; const LEVELS = ['fatal', 'error', 'warn', 'info', 'debug', 'trace']; let logger; @@ -102,21 +104,31 @@ async function deleteAllLogs(logPath) { }); } -function cleanupLogs(logPath) { +async function cleanupLogs(logPath) { const now = new Date(); const earliestDate = new Date( Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - 3) ); - return eliminateOutOfDateFiles(logPath, earliestDate).then(remaining => { + try { + const remaining = await eliminateOutOfDateFiles(logPath, earliestDate); const files = _.filter(remaining, file => !file.start && file.end); if (!files.length) { - return null; + return; } - return eliminateOldEntries(files, earliestDate); - }); + await eliminateOldEntries(files, earliestDate); + } catch (error) { + console.error( + 'Error cleaning logs; deleting and starting over from scratch.', + error.stack + ); + + // delete and re-create the log directory + await deleteAllLogs(logPath); + mkdirp.sync(logPath); + } } function isLineAfterDate(line, date) { @@ -247,7 +259,7 @@ function logAtLevel(level, ...args) { return item; }); - logger[level](str.join(' ')); + logger[level](redactAll(str.join(' '))); } else { console._log(...args); } diff --git a/app/menu.js b/app/menu.js index f690c5105b..840bc92145 100644 --- a/app/menu.js +++ b/app/menu.js @@ -16,17 +16,12 @@ exports.createTemplate = (options, messages) => { setupWithImport, showAbout, showDebugLog, - showSettings, } = options; const template = [ { label: messages.mainMenuFile.message, submenu: [ - { - label: messages.mainMenuSettings.message, - click: showSettings, - }, { type: 'separator', }, @@ -84,6 +79,7 @@ exports.createTemplate = (options, messages) => { label: messages.viewMenuResetZoom.message, }, { + accelerator: platform === 'darwin' ? 'Command+=' : 'Control+Plus', role: 'zoomin', label: messages.viewMenuZoomIn.message, }, @@ -192,7 +188,6 @@ function updateForMac(template, messages, options) { setupAsStandalone, setupWithImport, showAbout, - showSettings, showWindow, } = options; @@ -232,6 +227,7 @@ function updateForMac(template, messages, options) { // Add the OSX-specific Signal Desktop menu at the far left template.unshift({ + label: messages.lokiMessenger.message, submenu: [ { label: messages.aboutSignalDesktop.message, @@ -240,11 +236,6 @@ function updateForMac(template, messages, options) { { type: 'separator', }, - { - label: messages.mainMenuSettings.message, - accelerator: 'CommandOrControl+,', - click: showSettings, - }, { type: 'separator', }, diff --git a/app/profile_images.js b/app/profile_images.js index 339f80fed6..f8c8a26f03 100644 --- a/app/profile_images.js +++ b/app/profile_images.js @@ -1,8 +1,6 @@ const fs = require('fs'); const mkdirp = require('mkdirp'); const path = require('path'); -const Identicon = require('identicon.js'); -const sha224 = require('js-sha512').sha512_224; const { app } = require('electron').remote; @@ -13,12 +11,6 @@ mkdirp.sync(PATH); const hasImage = pubKey => fs.existsSync(getImagePath(pubKey)); const getImagePath = pubKey => `${PATH}/${pubKey}.png`; -const getOrCreateImagePath = pubKey => { - // If the image doesn't exist then create it - if (!hasImage(pubKey)) return generateImage(pubKey); - - return getImagePath(pubKey); -}; const removeImage = pubKey => { if (hasImage(pubKey)) { @@ -39,25 +31,14 @@ const removeImagesNotInArray = pubKeyArray => { .forEach(i => removeImage(i)); }; -const generateImage = pubKey => { +const writePNGImage = (base64String, pubKey) => { const imagePath = getImagePath(pubKey); - - /* - We hash the pubKey and then pass that into Identicon. - This is to avoid getting the same image - if 2 public keys start with the same 15 characters. - */ - const png = new Identicon(sha224(pubKey), { - margin: 0.2, - background: [0, 0, 0, 0], - }).toString(); - fs.writeFileSync(imagePath, png, 'base64'); + fs.writeFileSync(imagePath, base64String, 'base64'); return imagePath; }; module.exports = { - generateImage, - getOrCreateImagePath, + writePNGImage, getImagePath, hasImage, removeImage, diff --git a/app/protocol_filter.js b/app/protocol_filter.js index d510831cb9..a065e61c8a 100644 --- a/app/protocol_filter.js +++ b/app/protocol_filter.js @@ -25,7 +25,11 @@ function _createFileHandler({ userDataPath, installPath, isWindows }) { return (request, callback) => { // normalize() is primarily useful here for switching / to \ on windows const target = path.normalize(_urlToPath(request.url, { isWindows })); + // here we attempt to follow symlinks to the ultimate final path, reflective of what + // we do in main.js on userDataPath and installPath const realPath = fs.existsSync(target) ? fs.realpathSync(target) : target; + // finally we do case-insensitive checks on windows + const properCasing = isWindows ? realPath.toLowerCase() : realPath; if (!path.isAbsolute(realPath)) { console.log( @@ -35,8 +39,12 @@ function _createFileHandler({ userDataPath, installPath, isWindows }) { } if ( - !realPath.startsWith(userDataPath) && - !realPath.startsWith(installPath) + !properCasing.startsWith( + isWindows ? userDataPath.toLowerCase() : userDataPath + ) && + !properCasing.startsWith( + isWindows ? installPath.toLowerCase() : installPath + ) ) { console.log( `Warning: denying request to path '${realPath}' (userDataPath: '${userDataPath}', installPath: '${installPath}')` diff --git a/app/sql.js b/app/sql.js index 44d5ebf6d2..e9cb2390c4 100644 --- a/app/sql.js +++ b/app/sql.js @@ -2,9 +2,22 @@ const path = require('path'); const mkdirp = require('mkdirp'); const rimraf = require('rimraf'); const sql = require('@journeyapps/sqlcipher'); +const { app, dialog, clipboard } = require('electron'); +const { redactAll } = require('../js/modules/privacy'); +const { remove: removeUserConfig } = require('./user_config'); +const config = require('./config'); + const pify = require('pify'); const uuidv4 = require('uuid/v4'); -const { map, isString, fromPairs, forEach, last, isEmpty } = require('lodash'); +const { + map, + isString, + fromPairs, + forEach, + last, + isEmpty, + isObject, +} = require('lodash'); // To get long stack traces // https://github.com/mapbox/node-sqlite3/wiki/API#sqlite3verbose @@ -21,19 +34,12 @@ module.exports = { savePasswordHash, removePasswordHash, - createOrUpdateGroup, - getGroupById, - getAllGroupIds, - getAllGroups, - bulkAddGroups, - removeGroupById, - removeAllGroups, - createOrUpdateIdentityKey, getIdentityKeyById, bulkAddIdentityKeys, removeIdentityKeyById, removeAllIdentityKeys, + getAllIdentityKeys, createOrUpdatePreKey, getPreKeyById, @@ -41,6 +47,7 @@ module.exports = { bulkAddPreKeys, removePreKeyById, removeAllPreKeys, + getAllPreKeys, createOrUpdateSignedPreKey, getSignedPreKeyById, @@ -66,6 +73,14 @@ module.exports = { removeContactSignedPreKeyByIdentityKey, removeAllContactSignedPreKeys, + createOrUpdatePairingAuthorisation, + removePairingAuthorisationForSecondaryPubKey, + getAuthorisationForSecondaryPubKey, + getGrantAuthorisationsForPrimaryPubKey, + getSecondaryDevicesFor, + getPrimaryDeviceFor, + getPairedDevicesFor, + createOrUpdateItem, getItemById, getAllItems, @@ -80,6 +95,7 @@ module.exports = { removeSessionById, removeSessionsByNumber, removeAllSessions, + getAllSessions, getSwarmNodesByPubkey, @@ -87,13 +103,22 @@ module.exports = { saveConversation, saveConversations, getConversationById, + savePublicServerToken, + getPublicServerTokenByServerUrl, updateConversation, removeConversation, getAllConversations, getPubKeysWithFriendStatus, + getConversationsWithFriendStatus, + getAllRssFeedConversations, + getAllPublicConversations, + getPublicConversationsByServer, + getPubkeysInPublicConversation, getAllConversationIds, getAllPrivateConversations, getAllGroupsInvolvingId, + removeAllConversations, + removeAllPrivateConversations, searchConversations, searchMessages, @@ -110,6 +135,7 @@ module.exports = { removeMessage, getUnreadByConversation, getMessageBySender, + getMessageByServerId, getMessageById, getAllMessages, getAllMessageIds, @@ -125,11 +151,20 @@ module.exports = { getUnprocessedCount, getAllUnprocessed, saveUnprocessed, + updateUnprocessedAttempts, + updateUnprocessedWithData, getUnprocessedById, saveUnprocesseds, removeUnprocessed, removeAllUnprocessed, + getNextAttachmentDownloadJobs, + saveAttachmentDownloadJob, + setAttachmentDownloadJobPending, + resetAttachmentDownloadPending, + removeAttachmentDownloadJob, + removeAllAttachmentDownloadJobs, + removeAll, removeAllConfiguration, @@ -619,6 +654,108 @@ async function updateToSchemaVersion8(currentVersion, instance) { console.log('updateToSchemaVersion8: success!'); } +async function updateToSchemaVersion9(currentVersion, instance) { + if (currentVersion >= 9) { + return; + } + console.log('updateToSchemaVersion9: starting...'); + await instance.run('BEGIN TRANSACTION;'); + + await instance.run(`CREATE TABLE attachment_downloads( + id STRING primary key, + timestamp INTEGER, + pending INTEGER, + json TEXT + );`); + + await instance.run(`CREATE INDEX attachment_downloads_timestamp + ON attachment_downloads ( + timestamp + ) WHERE pending = 0;`); + await instance.run(`CREATE INDEX attachment_downloads_pending + ON attachment_downloads ( + pending + ) WHERE pending != 0;`); + + await instance.run('PRAGMA schema_version = 9;'); + await instance.run('COMMIT TRANSACTION;'); + console.log('updateToSchemaVersion9: success!'); +} + +async function updateToSchemaVersion10(currentVersion, instance) { + if (currentVersion >= 10) { + return; + } + console.log('updateToSchemaVersion10: starting...'); + await instance.run('BEGIN TRANSACTION;'); + + await instance.run('DROP INDEX unprocessed_id;'); + await instance.run('DROP INDEX unprocessed_timestamp;'); + await instance.run('ALTER TABLE unprocessed RENAME TO unprocessed_old;'); + + await instance.run(`CREATE TABLE unprocessed( + id STRING, + timestamp INTEGER, + version INTEGER, + attempts INTEGER, + envelope TEXT, + decrypted TEXT, + source TEXT, + sourceDevice TEXT, + serverTimestamp INTEGER + );`); + + await instance.run(`CREATE INDEX unprocessed_id ON unprocessed ( + id + );`); + await instance.run(`CREATE INDEX unprocessed_timestamp ON unprocessed ( + timestamp + );`); + + await instance.run(`INSERT INTO unprocessed ( + id, + timestamp, + version, + attempts, + envelope, + decrypted, + source, + sourceDevice, + serverTimestamp + ) SELECT + id, + timestamp, + json_extract(json, '$.version'), + json_extract(json, '$.attempts'), + json_extract(json, '$.envelope'), + json_extract(json, '$.decrypted'), + json_extract(json, '$.source'), + json_extract(json, '$.sourceDevice'), + json_extract(json, '$.serverTimestamp') + FROM unprocessed_old; + `); + + await instance.run('DROP TABLE unprocessed_old;'); + + await instance.run('PRAGMA schema_version = 10;'); + await instance.run('COMMIT TRANSACTION;'); + console.log('updateToSchemaVersion10: success!'); +} + +async function updateToSchemaVersion11(currentVersion, instance) { + if (currentVersion >= 11) { + return; + } + console.log('updateToSchemaVersion11: starting...'); + await instance.run('BEGIN TRANSACTION;'); + + await instance.run('DROP TABLE groups;'); + + await instance.run('PRAGMA schema_version = 11;'); + await instance.run('COMMIT TRANSACTION;'); + console.log('updateToSchemaVersion11: success!'); +} + const SCHEMA_VERSIONS = [ updateToSchemaVersion1, updateToSchemaVersion2, @@ -628,6 +765,9 @@ const SCHEMA_VERSIONS = [ updateToSchemaVersion6, updateToSchemaVersion7, updateToSchemaVersion8, + updateToSchemaVersion9, + updateToSchemaVersion10, + updateToSchemaVersion11, ]; async function updateSchema(instance) { @@ -649,6 +789,232 @@ async function updateSchema(instance) { // eslint-disable-next-line no-await-in-loop await runSchemaUpdate(schemaVersion, instance); } + await updateLokiSchema(instance); +} + +const LOKI_SCHEMA_VERSIONS = [ + updateToLokiSchemaVersion1, + updateToLokiSchemaVersion2, +]; + +async function updateToLokiSchemaVersion1(currentVersion, instance) { + if (currentVersion >= 1) { + return; + } + console.log('updateToLokiSchemaVersion1: starting...'); + await instance.run('BEGIN TRANSACTION;'); + + await instance.run( + `ALTER TABLE messages + ADD COLUMN serverId INTEGER;` + ); + + await instance.run( + `CREATE TABLE servers( + serverUrl STRING PRIMARY KEY ASC, + token TEXT + );` + ); + + const initConversation = async data => { + // eslint-disable-next-line camelcase + const { id, active_at, type, name, friendRequestStatus } = data; + await instance.run( + `INSERT INTO conversations ( + id, + json, + active_at, + type, + members, + name, + friendRequestStatus + ) values ( + $id, + $json, + $active_at, + $type, + $members, + $name, + $friendRequestStatus + );`, + { + $id: id, + $json: objectToJSON(data), + $active_at: active_at, + $type: type, + $members: null, + $name: name, + $friendRequestStatus: friendRequestStatus, + } + ); + }; + + const lokiPublicServerData = { + // make sure we don't have a trailing slash just in case + serverUrl: config.get('defaultPublicChatServer').replace(/\/*$/, ''), + token: null, + }; + console.log('lokiPublicServerData', lokiPublicServerData); + + const baseData = { + active_at: Date.now(), + friendRequestStatus: 4, // Friends + sealedSender: 0, + sessionResetStatus: 0, + swarmNodes: [], + type: 'group', + unlockTimestamp: null, + unreadCount: 0, + verified: 0, + version: 2, + }; + + const publicChatData = { + ...baseData, + id: `publicChat:1@${lokiPublicServerData.serverUrl.replace( + /^https?:\/\//i, + '' + )}`, + server: lokiPublicServerData.serverUrl, + name: 'Loki Public Chat', + channelId: '1', + }; + + const { serverUrl, token } = lokiPublicServerData; + + await instance.run( + `INSERT INTO servers ( + serverUrl, + token + ) values ( + $serverUrl, + $token + );`, + { + $serverUrl: serverUrl, + $token: token, + } + ); + + const newsRssFeedData = { + ...baseData, + id: 'rss://loki.network/feed/', + rssFeed: 'https://loki.network/feed/', + closable: true, + name: 'Loki.network News', + profileAvatar: 'images/session/session_chat_icon.png', + }; + + const updatesRssFeedData = { + ...baseData, + id: 'rss://loki.network/category/messenger-updates/feed/', + rssFeed: 'https://loki.network/category/messenger-updates/feed/', + closable: false, + name: 'Messenger updates', + profileAvatar: 'images/session/session_chat_icon.png', + }; + + const autoJoinLokiChats = false; + + if (autoJoinLokiChats) { + await initConversation(publicChatData); + } + + await initConversation(newsRssFeedData); + await initConversation(updatesRssFeedData); + + await instance.run( + `INSERT INTO loki_schema ( + version + ) values ( + 1 + );` + ); + await instance.run('COMMIT TRANSACTION;'); + console.log('updateToLokiSchemaVersion1: success!'); +} + +async function updateToLokiSchemaVersion2(currentVersion, instance) { + if (currentVersion >= 2) { + return; + } + console.log('updateToLokiSchemaVersion2: starting...'); + await instance.run('BEGIN TRANSACTION;'); + + await instance.run( + `CREATE TABLE pairingAuthorisations( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + primaryDevicePubKey VARCHAR(255), + secondaryDevicePubKey VARCHAR(255), + isGranted BOOLEAN, + json TEXT, + UNIQUE(primaryDevicePubKey, secondaryDevicePubKey) + );` + ); + + await instance.run( + `INSERT INTO loki_schema ( + version + ) values ( + 2 + );` + ); + await instance.run('COMMIT TRANSACTION;'); + console.log('updateToLokiSchemaVersion2: success!'); +} + +async function updateLokiSchema(instance) { + const result = await instance.get( + "SELECT name FROM sqlite_master WHERE type = 'table' AND name='loki_schema';" + ); + if (!result) { + await createLokiSchemaTable(instance); + } + const lokiSchemaVersion = await getLokiSchemaVersion(instance); + console.log( + 'updateLokiSchema:', + `Current loki schema version: ${lokiSchemaVersion};`, + `Most recent schema version: ${LOKI_SCHEMA_VERSIONS.length};` + ); + for ( + let index = 0, max = LOKI_SCHEMA_VERSIONS.length; + index < max; + index += 1 + ) { + const runSchemaUpdate = LOKI_SCHEMA_VERSIONS[index]; + + // Yes, we really want to do this asynchronously, in order + // eslint-disable-next-line no-await-in-loop + await runSchemaUpdate(lokiSchemaVersion, instance); + } +} + +async function getLokiSchemaVersion(instance) { + const result = await instance.get( + 'SELECT MAX(version) as version FROM loki_schema;' + ); + if (!result || !result.version) { + return 0; + } + return result.version; +} + +async function createLokiSchemaTable(instance) { + await instance.run('BEGIN TRANSACTION;'); + await instance.run( + `CREATE TABLE loki_schema( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + version INTEGER + );` + ); + await instance.run( + `INSERT INTO loki_schema ( + version + ) values ( + 0 + );` + ); + await instance.run('COMMIT TRANSACTION;'); } let db; @@ -664,7 +1030,7 @@ function _initializePaths(configDir) { filePath = path.join(dbDir, 'db.sqlite'); } -async function initialize({ configDir, key }) { +async function initialize({ configDir, key, messages, passwordAttempt }) { if (db) { throw new Error('Cannot initialize more than once!'); } @@ -673,32 +1039,72 @@ async function initialize({ configDir, key }) { throw new Error('initialize: configDir is required!'); } if (!isString(key)) { - throw new Error('initialize: key` is required!'); + throw new Error('initialize: key is required!'); + } + if (!isObject(messages)) { + throw new Error('initialize: message is required!'); } _initializePaths(configDir); - const sqlInstance = await openDatabase(filePath); - const promisified = promisify(sqlInstance); + try { + const sqlInstance = await openDatabase(filePath); + const promisified = promisify(sqlInstance); + + // promisified.on('trace', async statement => { + // if (!db || statement.startsWith('--')) { + // console._log(statement); + // return; + // } + // const data = await db.get(`EXPLAIN QUERY PLAN ${statement}`); + // console._log(`EXPLAIN QUERY PLAN ${statement}\n`, data && data.detail); + // }); + + try { + await setupSQLCipher(promisified, { key }); + await updateSchema(promisified); + } catch (e) { + await promisified.close(); + throw e; + } + + db = promisified; - // promisified.on('trace', async statement => { - // if (!db || statement.startsWith('--')) { - // console._log(statement); - // return; - // } - // const data = await db.get(`EXPLAIN QUERY PLAN ${statement}`); - // console._log(`EXPLAIN QUERY PLAN ${statement}\n`, data && data.detail); - // }); + // test database + await getMessageCount(); + } catch (error) { + if (passwordAttempt) { + throw error; + } + console.log('Database startup error:', error.stack); + const buttonIndex = dialog.showMessageBox({ + buttons: [ + messages.copyErrorAndQuit.message, + messages.deleteAndRestart.message, + ], + defaultId: 0, + detail: redactAll(error.stack), + message: messages.databaseError.message, + noLink: true, + type: 'error', + }); - try { - await setupSQLCipher(promisified, { key }); - await updateSchema(promisified); - } catch (e) { - await promisified.close(); - throw e; + if (buttonIndex === 0) { + clipboard.writeText( + `Database startup error:\n\n${redactAll(error.stack)}` + ); + } else { + await close(); + await removeDB(); + removeUserConfig(); + app.relaunch(); + } + + app.exit(1); + return false; } - db = promisified; + return true; } async function close() { @@ -752,33 +1158,6 @@ async function removePasswordHash() { return removeItemById(PASS_HASH_ID); } -// Groups - -const GROUPS_TABLE = 'groups'; -async function createOrUpdateGroup(data) { - return createOrUpdate(GROUPS_TABLE, data); -} -async function getGroupById(id) { - return getById(GROUPS_TABLE, id); -} -async function getAllGroupIds() { - const rows = await db.all('SELECT id FROM groups ORDER BY id ASC;'); - return map(rows, row => row.id); -} -async function getAllGroups() { - const rows = await db.all('SELECT id FROM groups ORDER BY id ASC;'); - return map(rows, row => jsonToObject(row.json)); -} -async function bulkAddGroups(array) { - return bulkAdd(GROUPS_TABLE, array); -} -async function removeGroupById(id) { - return removeById(GROUPS_TABLE, id); -} -async function removeAllGroups() { - return removeAllFromTable(GROUPS_TABLE); -} - const IDENTITY_KEYS_TABLE = 'identityKeys'; async function createOrUpdateIdentityKey(data) { return createOrUpdate(IDENTITY_KEYS_TABLE, data); @@ -795,6 +1174,9 @@ async function removeIdentityKeyById(id) { async function removeAllIdentityKeys() { return removeAllFromTable(IDENTITY_KEYS_TABLE); } +async function getAllIdentityKeys() { + return getAllFromTable(IDENTITY_KEYS_TABLE); +} const PRE_KEYS_TABLE = 'preKeys'; async function createOrUpdatePreKey(data) { @@ -846,6 +1228,9 @@ async function removePreKeyById(id) { async function removeAllPreKeys() { return removeAllFromTable(PRE_KEYS_TABLE); } +async function getAllPreKeys() { + return getAllFromTable(PRE_KEYS_TABLE); +} const CONTACT_PRE_KEYS_TABLE = 'contactPreKeys'; async function createOrUpdateContactPreKey(data) { @@ -935,7 +1320,7 @@ async function getContactSignedPreKeyById(id) { } async function getContactSignedPreKeyByIdentityKey(key) { const row = await db.get( - `SELECT * FROM ${CONTACT_SIGNED_PRE_KEYS_TABLE} WHERE identityKeyString = $identityKeyString;`, + `SELECT * FROM ${CONTACT_SIGNED_PRE_KEYS_TABLE} WHERE identityKeyString = $identityKeyString ORDER BY keyId DESC;`, { $identityKeyString: key, } @@ -995,6 +1380,114 @@ async function removeAllSignedPreKeys() { return removeAllFromTable(SIGNED_PRE_KEYS_TABLE); } +const PAIRING_AUTHORISATIONS_TABLE = 'pairingAuthorisations'; +async function getAuthorisationForSecondaryPubKey(pubKey, options) { + const granted = options && options.granted; + let filter = ''; + if (granted) { + filter = 'AND isGranted = 1'; + } + const row = await db.get( + `SELECT json FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE secondaryDevicePubKey = $secondaryDevicePubKey ${filter};`, + { + $secondaryDevicePubKey: pubKey, + } + ); + + if (!row) { + return null; + } + + return jsonToObject(row.json); +} + +async function getGrantAuthorisationsForPrimaryPubKey(primaryDevicePubKey) { + const rows = await db.all( + `SELECT json FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE primaryDevicePubKey = $primaryDevicePubKey AND isGranted = 1 ORDER BY secondaryDevicePubKey ASC;`, + { + $primaryDevicePubKey: primaryDevicePubKey, + } + ); + return map(rows, row => jsonToObject(row.json)); +} + +async function createOrUpdatePairingAuthorisation(data) { + const { primaryDevicePubKey, secondaryDevicePubKey, grantSignature } = data; + + await db.run( + `INSERT OR REPLACE INTO ${PAIRING_AUTHORISATIONS_TABLE} ( + primaryDevicePubKey, + secondaryDevicePubKey, + isGranted, + json + ) values ( + $primaryDevicePubKey, + $secondaryDevicePubKey, + $isGranted, + $json + )`, + { + $primaryDevicePubKey: primaryDevicePubKey, + $secondaryDevicePubKey: secondaryDevicePubKey, + $isGranted: Boolean(grantSignature), + $json: objectToJSON(data), + } + ); +} + +async function removePairingAuthorisationForSecondaryPubKey(pubKey) { + await db.run( + `DELETE FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE secondaryDevicePubKey = $secondaryDevicePubKey;`, + { + $secondaryDevicePubKey: pubKey, + } + ); +} + +async function getSecondaryDevicesFor(primaryDevicePubKey) { + const authorisations = await getGrantAuthorisationsForPrimaryPubKey( + primaryDevicePubKey + ); + return map(authorisations, row => row.secondaryDevicePubKey); +} + +async function getPrimaryDeviceFor(secondaryDevicePubKey) { + const row = await db.get( + `SELECT primaryDevicePubKey FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE secondaryDevicePubKey = $secondaryDevicePubKey AND isGranted = 1;`, + { + $secondaryDevicePubKey: secondaryDevicePubKey, + } + ); + + if (!row) { + return null; + } + + return row.primaryDevicePubKey; +} + +// Return all the paired pubkeys for a specific pubkey (excluded), +// irrespective of their Primary or Secondary status. +async function getPairedDevicesFor(pubKey) { + let results = []; + + // get primary pubkey (only works if the pubkey is a secondary pubkey) + const primaryPubKey = await getPrimaryDeviceFor(pubKey); + if (primaryPubKey) { + results.push(primaryPubKey); + } + // get secondary pubkeys (only works if the pubkey is a primary pubkey) + const secondaryPubKeys = await getSecondaryDevicesFor( + primaryPubKey || pubKey + ); + results = results.concat(secondaryPubKeys); + + // ensure the input pubkey is not in the results + results = results.filter(x => x !== pubKey); + + return results; +} + const ITEMS_TABLE = 'items'; async function createOrUpdateItem(data) { return createOrUpdate(ITEMS_TABLE, data); @@ -1070,6 +1563,9 @@ async function removeSessionsByNumber(number) { async function removeAllSessions() { return removeAllFromTable(SESSIONS_TABLE); } +async function getAllSessions() { + return getAllFromTable(SESSIONS_TABLE); +} async function createOrUpdate(table, data) { const { id } = data; @@ -1139,6 +1635,11 @@ async function removeAllFromTable(table) { await db.run(`DELETE FROM ${table};`); } +async function getAllFromTable(table) { + const rows = await db.all(`SELECT json FROM ${table};`); + return rows.map(row => jsonToObject(row.json)); +} + // Conversations async function getSwarmNodesByPubkey(pubkey) { @@ -1153,11 +1654,14 @@ async function getSwarmNodesByPubkey(pubkey) { return jsonToObject(row.json).swarmNodes; } +const CONVERSATIONS_TABLE = 'conversations'; async function getConversationCount() { - const row = await db.get('SELECT count(*) from conversations;'); + const row = await db.get(`SELECT count(*) from ${CONVERSATIONS_TABLE};`); if (!row) { - throw new Error('getMessageCount: Unable to get count of conversations'); + throw new Error( + `getConversationCount: Unable to get count of ${CONVERSATIONS_TABLE}` + ); } return row['count(*)']; @@ -1176,7 +1680,7 @@ async function saveConversation(data) { } = data; await db.run( - `INSERT INTO conversations ( + `INSERT INTO ${CONVERSATIONS_TABLE} ( id, json, @@ -1240,7 +1744,7 @@ async function updateConversation(data) { } = data; await db.run( - `UPDATE conversations SET + `UPDATE ${CONVERSATIONS_TABLE} SET json = $json, active_at = $active_at, @@ -1266,7 +1770,9 @@ async function updateConversation(data) { async function removeConversation(id) { if (!Array.isArray(id)) { - await db.run('DELETE FROM conversations WHERE id = $id;', { $id: id }); + await db.run(`DELETE FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`, { + $id: id, + }); return; } @@ -1276,17 +1782,52 @@ async function removeConversation(id) { // Our node interface doesn't seem to allow you to replace one single ? with an array await db.run( - `DELETE FROM conversations WHERE id IN ( ${id + `DELETE FROM ${CONVERSATIONS_TABLE} WHERE id IN ( ${id .map(() => '?') .join(', ')} );`, id ); } +async function savePublicServerToken(data) { + const { serverUrl, token } = data; + await db.run( + `INSERT OR REPLACE INTO servers ( + serverUrl, + token + ) values ( + $serverUrl, + $token + )`, + { + $serverUrl: serverUrl, + $token: token, + } + ); +} + +async function getPublicServerTokenByServerUrl(serverUrl) { + const row = await db.get( + 'SELECT * FROM servers WHERE serverUrl = $serverUrl;', + { + $serverUrl: serverUrl, + } + ); + + if (!row) { + return null; + } + + return row.token; +} + async function getConversationById(id) { - const row = await db.get('SELECT * FROM conversations WHERE id = $id;', { - $id: id, - }); + const row = await db.get( + `SELECT * FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`, + { + $id: id, + } + ); if (!row) { return null; @@ -1296,14 +1837,17 @@ async function getConversationById(id) { } async function getAllConversations() { - const rows = await db.all('SELECT json FROM conversations ORDER BY id ASC;'); + const rows = await db.all( + `SELECT json FROM ${CONVERSATIONS_TABLE} ORDER BY id ASC;` + ); return map(rows, row => jsonToObject(row.json)); } async function getPubKeysWithFriendStatus(status) { const rows = await db.all( - `SELECT id FROM conversations WHERE + `SELECT id FROM ${CONVERSATIONS_TABLE} WHERE friendRequestStatus = $status + AND type = 'private' ORDER BY id ASC;`, { $status: status, @@ -1312,14 +1856,29 @@ async function getPubKeysWithFriendStatus(status) { return map(rows, row => row.id); } +async function getConversationsWithFriendStatus(status) { + const rows = await db.all( + `SELECT * FROM ${CONVERSATIONS_TABLE} WHERE + friendRequestStatus = $status + AND type = 'private' + ORDER BY id ASC;`, + { + $status: status, + } + ); + return map(rows, row => jsonToObject(row.json)); +} + async function getAllConversationIds() { - const rows = await db.all('SELECT id FROM conversations ORDER BY id ASC;'); + const rows = await db.all( + `SELECT id FROM ${CONVERSATIONS_TABLE} ORDER BY id ASC;` + ); return map(rows, row => row.id); } async function getAllPrivateConversations() { const rows = await db.all( - `SELECT json FROM conversations WHERE + `SELECT json FROM ${CONVERSATIONS_TABLE} WHERE type = 'private' ORDER BY id ASC;` ); @@ -1327,9 +1886,57 @@ async function getAllPrivateConversations() { return map(rows, row => jsonToObject(row.json)); } -async function getAllGroupsInvolvingId(id) { +async function getAllRssFeedConversations() { + const rows = await db.all( + `SELECT json FROM conversations WHERE + type = 'group' AND + id LIKE 'rss://%' + ORDER BY id ASC;` + ); + + return map(rows, row => jsonToObject(row.json)); +} + +async function getAllPublicConversations() { const rows = await db.all( `SELECT json FROM conversations WHERE + type = 'group' AND + id LIKE 'publicChat:%' + ORDER BY id ASC;` + ); + + return map(rows, row => jsonToObject(row.json)); +} + +async function getPublicConversationsByServer(server) { + const rows = await db.all( + `SELECT * FROM conversations WHERE + server = $server + ORDER BY id ASC;`, + { + $server: server, + } + ); + + return map(rows, row => jsonToObject(row.json)); +} + +async function getPubkeysInPublicConversation(id) { + const rows = await db.all( + `SELECT DISTINCT source FROM messages WHERE + conversationId = $conversationId + ORDER BY id ASC;`, + { + $conversationId: id, + } + ); + + return map(rows, row => row.source); +} + +async function getAllGroupsInvolvingId(id) { + const rows = await db.all( + `SELECT json FROM ${CONVERSATIONS_TABLE} WHERE type = 'group' AND members LIKE $id ORDER BY id ASC;`, @@ -1341,19 +1948,21 @@ async function getAllGroupsInvolvingId(id) { return map(rows, row => jsonToObject(row.json)); } -async function searchConversations(query) { +async function searchConversations(query, { limit } = {}) { const rows = await db.all( - `SELECT json FROM conversations WHERE + `SELECT json FROM ${CONVERSATIONS_TABLE} WHERE ( id LIKE $id OR name LIKE $name OR profileName LIKE $profileName ) - ORDER BY id ASC;`, + ORDER BY id ASC + LIMIT $limit`, { $id: `%${query}%`, $name: `%${query}%`, $profileName: `%${query}%`, + $limit: limit || 50, } ); @@ -1432,6 +2041,7 @@ async function saveMessage(data, { forceSave } = {}) { hasFileAttachments, hasVisualMediaAttachments, id, + serverId, // eslint-disable-next-line camelcase received_at, schemaVersion, @@ -1450,6 +2060,7 @@ async function saveMessage(data, { forceSave } = {}) { $id: id, $json: objectToJSON(data), + $serverId: serverId, $body: body, $conversationId: conversationId, $expirationStartTimestamp: expirationStartTimestamp, @@ -1472,6 +2083,7 @@ async function saveMessage(data, { forceSave } = {}) { await db.run( `UPDATE messages SET json = $json, + serverId = $serverId, body = $body, conversationId = $conversationId, expirationStartTimestamp = $expirationStartTimestamp, @@ -1506,6 +2118,7 @@ async function saveMessage(data, { forceSave } = {}) { id, json, + serverId, body, conversationId, expirationStartTimestamp, @@ -1526,6 +2139,7 @@ async function saveMessage(data, { forceSave } = {}) { $id, $json, + $serverId, $body, $conversationId, $expirationStartTimestamp, @@ -1648,6 +2262,24 @@ async function removeMessage(id) { ); } +async function getMessageByServerId(serverId, conversationId) { + const row = await db.get( + `SELECT * FROM messages WHERE + serverId = $serverId AND + conversationId = $conversationId;`, + { + $serverId: serverId, + $conversationId: conversationId, + } + ); + + if (!row) { + return null; + } + + return jsonToObject(row.json); +} + async function getMessageById(id) { const row = await db.get('SELECT * FROM messages WHERE id = $id;', { $id: id, @@ -1712,6 +2344,7 @@ async function getUnreadByConversation(conversationId) { return map(rows, row => jsonToObject(row.json)); } +// Note: Sorting here is necessary for getting the last message (with limit 1) async function getMessagesByConversation( conversationId, { limit = 100, receivedAt = Number.MAX_VALUE, type = '%' } = {} @@ -1722,7 +2355,7 @@ async function getMessagesByConversation( conversationId = $conversationId AND received_at < $received_at AND type LIKE $type - ORDER BY received_at DESC + ORDER BY sent_at DESC LIMIT $limit; `, { @@ -1812,23 +2445,32 @@ async function getNextExpiringMessage() { } async function saveUnprocessed(data, { forceSave } = {}) { - const { id, timestamp } = data; + const { id, timestamp, version, attempts, envelope } = data; + if (!id) { + throw new Error('saveUnprocessed: id was falsey'); + } if (forceSave) { await db.run( `INSERT INTO unprocessed ( id, timestamp, - json + version, + attempts, + envelope ) values ( $id, $timestamp, - $json + $version, + $attempts, + $envelope );`, { $id: id, $timestamp: timestamp, - $json: objectToJSON(data), + $version: version, + $attempts: attempts, + $envelope: envelope, } ); @@ -1837,13 +2479,17 @@ async function saveUnprocessed(data, { forceSave } = {}) { await db.run( `UPDATE unprocessed SET - json = $json, - timestamp = $timestamp + timestamp = $timestamp, + version = $version, + attempts = $attempts, + envelope = $envelope WHERE id = $id;`, { $id: id, $timestamp: timestamp, - $json: objectToJSON(data), + $version: version, + $attempts: attempts, + $envelope: envelope, } ); @@ -1866,16 +2512,38 @@ async function saveUnprocesseds(arrayOfUnprocessed, { forceSave } = {}) { await promise; } -async function getUnprocessedById(id) { - const row = await db.get('SELECT json FROM unprocessed WHERE id = $id;', { +async function updateUnprocessedAttempts(id, attempts) { + await db.run('UPDATE unprocessed SET attempts = $attempts WHERE id = $id;', { $id: id, + $attempts: attempts, }); +} +async function updateUnprocessedWithData(id, data = {}) { + const { source, sourceDevice, serverTimestamp, decrypted } = data; - if (!row) { - return null; - } + await db.run( + `UPDATE unprocessed SET + source = $source, + sourceDevice = $sourceDevice, + serverTimestamp = $serverTimestamp, + decrypted = $decrypted + WHERE id = $id;`, + { + $id: id, + $source: source, + $sourceDevice: sourceDevice, + $serverTimestamp: serverTimestamp, + $decrypted: decrypted, + } + ); +} - return jsonToObject(row.json); +async function getUnprocessedById(id) { + const row = await db.get('SELECT * FROM unprocessed WHERE id = $id;', { + $id: id, + }); + + return row; } async function getUnprocessedCount() { @@ -1890,10 +2558,10 @@ async function getUnprocessedCount() { async function getAllUnprocessed() { const rows = await db.all( - 'SELECT json FROM unprocessed ORDER BY timestamp ASC;' + 'SELECT * FROM unprocessed ORDER BY timestamp ASC;' ); - return map(rows, row => jsonToObject(row.json)); + return rows; } async function removeUnprocessed(id) { @@ -1917,6 +2585,72 @@ async function removeAllUnprocessed() { await db.run('DELETE FROM unprocessed;'); } +const ATTACHMENT_DOWNLOADS_TABLE = 'attachment_downloads'; +async function getNextAttachmentDownloadJobs(limit, options = {}) { + const timestamp = options.timestamp || Date.now(); + + const rows = await db.all( + `SELECT json FROM attachment_downloads + WHERE pending = 0 AND timestamp < $timestamp + ORDER BY timestamp DESC + LIMIT $limit;`, + { + $limit: limit, + $timestamp: timestamp, + } + ); + + return map(rows, row => jsonToObject(row.json)); +} +async function saveAttachmentDownloadJob(job) { + const { id, pending, timestamp } = job; + if (!id) { + throw new Error( + 'saveAttachmentDownloadJob: Provided job did not have a truthy id' + ); + } + + await db.run( + `INSERT OR REPLACE INTO attachment_downloads ( + id, + pending, + timestamp, + json + ) values ( + $id, + $pending, + $timestamp, + $json + )`, + { + $id: id, + $pending: pending, + $timestamp: timestamp, + $json: objectToJSON(job), + } + ); +} +async function setAttachmentDownloadJobPending(id, pending) { + await db.run( + 'UPDATE attachment_downloads SET pending = $pending WHERE id = $id;', + { + $id: id, + $pending: pending, + } + ); +} +async function resetAttachmentDownloadPending() { + await db.run( + 'UPDATE attachment_downloads SET pending = 0 WHERE pending != 0;' + ); +} +async function removeAttachmentDownloadJob(id) { + return removeById(ATTACHMENT_DOWNLOADS_TABLE, id); +} +async function removeAllAttachmentDownloadJobs() { + return removeAllFromTable(ATTACHMENT_DOWNLOADS_TABLE); +} + // All data in database async function removeAll() { let promise; @@ -1925,7 +2659,6 @@ async function removeAll() { promise = Promise.all([ db.run('BEGIN TRANSACTION;'), db.run('DELETE FROM conversations;'), - db.run('DELETE FROM groups;'), db.run('DELETE FROM identityKeys;'), db.run('DELETE FROM items;'), db.run('DELETE FROM messages;'), @@ -1935,6 +2668,8 @@ async function removeAll() { db.run('DELETE FROM unprocessed;'), db.run('DELETE FROM contactPreKeys;'), db.run('DELETE FROM contactSignedPreKeys;'), + db.run('DELETE FROM attachment_downloads;'), + db.run('DELETE FROM messages_fts;'), db.run('COMMIT TRANSACTION;'), ]); }); @@ -1964,6 +2699,14 @@ async function removeAllConfiguration() { await promise; } +async function removeAllConversations() { + await removeAllFromTable(CONVERSATIONS_TABLE); +} + +async function removeAllPrivateConversations() { + await db.run(`DELETE FROM ${CONVERSATIONS_TABLE} WHERE type = 'private'`); +} + async function getMessagesNeedingUpgrade(limit, { maxVersion }) { const rows = await db.all( `SELECT json FROM messages diff --git a/app/tray_icon.js b/app/tray_icon.js index 407c2584ec..e05781723a 100644 --- a/app/tray_icon.js +++ b/app/tray_icon.js @@ -1,5 +1,6 @@ const path = require('path'); +const fs = require('fs'); const { app, Menu, Tray } = require('electron'); let trayContextMenu = null; @@ -78,19 +79,25 @@ function createTrayIcon(getMainWindow, messages) { }; tray.updateIcon = unreadCount => { + let image; + if (unreadCount > 0) { const filename = `${String(unreadCount >= 10 ? 10 : unreadCount)}.png`; - tray.setImage( - path.join(__dirname, '..', 'images', 'alert', iconSize, filename) - ); + image = path.join(__dirname, '..', 'images', 'alert', iconSize, filename); } else { - tray.setImage(iconNoNewMessages); + image = iconNoNewMessages; + } + + if (!fs.existsSync(image)) { + console.log('tray.updateIcon: Image for tray update does not exist!'); + return; } + tray.setImage(image); }; tray.on('click', tray.showWindow); - tray.setToolTip(messages.trayTooltip.message); + tray.setToolTip(messages.lokiMessenger.message); tray.updateContextMenu(); return tray; diff --git a/app/window_state.d.ts b/app/window_state.d.ts new file mode 100644 index 0000000000..90714af01a --- /dev/null +++ b/app/window_state.d.ts @@ -0,0 +1,2 @@ +export function markShouldQuit(): void; +export function shouldQuit(): void; diff --git a/appveyor.yml b/appveyor.yml index 9496140f9a..6b3167e518 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,19 +12,13 @@ install: - yarn install --frozen-lockfile build_script: + - node build\grunt.js - yarn generate - yarn lint-windows - yarn test-node - - node build\grunt.js - - type package.json | findstr /v certificateSubjectName > temp.json - - move temp.json package.json - - yarn prepare-beta-build - - node_modules\.bin\build --config.extraMetadata.environment=%SIGNAL_ENV% --publish=never --config.directories.output=release test_script: - node build\grunt.js test - - set NODE_ENV=production - - node build\grunt.js test-release:win environment: SIGNAL_ENV: production diff --git a/aptly.sh b/aptly.sh index ed03ea5d58..a761b806fb 100755 --- a/aptly.sh +++ b/aptly.sh @@ -26,8 +26,8 @@ echo "Releasing $NAME build version $VERSION" REPO=signal-desktop -CURRENT=artful -PREVIOUS=xenial +CURRENT=xenial +# PREVIOUS=xenial ENDPOINT=signal-desktop-apt # Matches endpoint name in .aptly.conf SNAPSHOT=signal-desktop_v$VERSION GPG_KEYID=57F6FB06 @@ -46,7 +46,7 @@ aptly snapshot create $SNAPSHOT from repo $REPO # these update already-published repos, run every time after that # https://www.aptly.info/doc/aptly/publish/switch/ aptly publish switch -gpg-key=$GPG_KEYID $CURRENT $SNAPSHOT -aptly publish switch -gpg-key=$GPG_KEYID $PREVIOUS $SNAPSHOT +# aptly publish switch -gpg-key=$GPG_KEYID $PREVIOUS $SNAPSHOT aptly publish switch -gpg-key=$GPG_KEYID -config=.aptly.conf $CURRENT s3:$ENDPOINT: $SNAPSHOT -aptly publish switch -gpg-key=$GPG_KEYID -config=.aptly.conf $PREVIOUS s3:$ENDPOINT: $SNAPSHOT +# aptly publish switch -gpg-key=$GPG_KEYID -config=.aptly.conf $PREVIOUS s3:$ENDPOINT: $SNAPSHOT diff --git a/background.html b/background.html index 2914b81c9e..35f38a92bf 100644 --- a/background.html +++ b/background.html @@ -19,7 +19,7 @@ script-src 'self'; style-src 'self' 'unsafe-inline';" > - Loki Messenger + Session @@ -29,96 +29,59 @@ + + - + + + + - - + + + - + + - - - - - - - - - - + + + + + - - - - - - + - @@ -751,7 +529,6 @@

Register a new account

- @@ -759,44 +536,55 @@

Register a new account

- + + + + + + + - - - - - - + + - - + + + - + - + + + + + + + + + @@ -805,13 +593,7 @@

Register a new account

- -
- - - -
-
+
diff --git a/build/entitlements.mac.plist b/build/entitlements.mac.plist new file mode 100644 index 0000000000..c64d264b28 --- /dev/null +++ b/build/entitlements.mac.plist @@ -0,0 +1,11 @@ + + + + + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-library-validation + + + diff --git a/build/entitlements.mas.plist b/build/entitlements.mas.plist index 5bfb52be41..cc799ebf9a 100644 --- a/build/entitlements.mas.plist +++ b/build/entitlements.mas.plist @@ -2,6 +2,7 @@ + com.apple.security.app-sandbox com.apple.security.network.client diff --git a/build/icon.ico b/build/icon.ico index a04920e4a5..e69de29bb2 100644 Binary files a/build/icon.ico and b/build/icon.ico differ diff --git a/build/icon.png b/build/icon.png new file mode 100644 index 0000000000..85a6799de2 Binary files /dev/null and b/build/icon.png differ diff --git a/build/notarize.js b/build/notarize.js new file mode 100644 index 0000000000..ac5a2c1384 --- /dev/null +++ b/build/notarize.js @@ -0,0 +1,28 @@ +const { notarize } = require('electron-notarize'); + +/* + Pre-requisites: https://github.com/electron/electron-notarize#prerequisites + 1. Generate an app specific password + 2. Export SIGNING_APPLE_ID, SIGNING_APP_PASSWORD, SIGNING_TEAM_ID environment variables +*/ + +/* + Notarizing: https://kilianvalkhof.com/2019/electron/notarizing-your-electron-application/ +*/ + +exports.default = async function notarizing(context) { + const { electronPlatformName, appOutDir } = context; + if (electronPlatformName !== 'darwin') { + return; + } + + const appName = context.packager.appInfo.productFilename; + + return notarize({ + appBundleId: 'com.loki-project.messenger-desktop', + appPath: `${appOutDir}/${appName}.app`, + appleId: process.env.SIGNING_APPLE_ID, + appleIdPassword: process.env.SIGNING_APP_PASSWORD, + ascProvider: process.env.SIGNING_TEAM_ID, + }); +}; diff --git a/components/mp3lameencoder/lib/Mp3LameEncoder.js b/components/mp3lameencoder/lib/Mp3LameEncoder.js index 71fc756a89..572ec8628d 100644 --- a/components/mp3lameencoder/lib/Mp3LameEncoder.js +++ b/components/mp3lameencoder/lib/Mp3LameEncoder.js @@ -44293,7 +44293,7 @@ function _memset(ptr, value, num) { } } while ((ptr|0) < (stop4|0)) { - HEAP32[((ptr)>>2)]=value4; + HEAP32[ptr>>2]=value4; ptr = (ptr+4)|0; } } @@ -44349,14 +44349,14 @@ function _memcpy(dest, src, num) { num = (num-1)|0; } while ((num|0) >= 4) { - HEAP32[((dest)>>2)]=((HEAP32[((src)>>2)])|0); + HEAP32[dest>>2]=((HEAP32[src>>2])|0); dest = (dest+4)|0; src = (src+4)|0; num = (num-4)|0; } } while ((num|0) > 0) { - HEAP8[((dest)>>0)]=((HEAP8[((src)>>0)])|0); + HEAP8[dest>>0]=((HEAP8[src>>0])|0); dest = (dest+1)|0; src = (src+1)|0; num = (num-1)|0; diff --git a/config/default.json b/config/default.json index 0699b7a06d..e0b61dac0a 100644 --- a/config/default.json +++ b/config/default.json @@ -2,14 +2,39 @@ "serverUrl": "random.snode", "localUrl": "localhost.loki", "cdnUrl": "random.snode", + "contentProxyUrl": "", "localServerPort": "8081", - "snodeServerPort": "8080", - "disableAutoUpdate": false, + "defaultPoWDifficulty": "1", + "seedNodeList": [ + { + "ip": "public.loki.foundation", + "port": "22023" + }, + { + "ip": "storage.seed1.loki.network", + "port": "22023" + }, + { + "ip": "storage.seed2.loki.network", + "port": "38157" + }, + { + "ip": "imaginary.stream", + "port": "38157" + } + ], + "disableAutoUpdate": true, + "updatesUrl": "TODO", + "updatesPublicKey": + "fd7dd3de7149dc0a127909fee7de0f7620ddd0de061b37a2c303e37de802a401", + "updatesEnabled": false, "openDevTools": false, "buildExpiration": 0, "commitHash": "", "certificateAuthority": "-----BEGIN CERTIFICATE-----\nMIID7zCCAtegAwIBAgIJAIm6LatK5PNiMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYD\nVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j\naXNjbzEdMBsGA1UECgwUT3BlbiBXaGlzcGVyIFN5c3RlbXMxHTAbBgNVBAsMFE9w\nZW4gV2hpc3BlciBTeXN0ZW1zMRMwEQYDVQQDDApUZXh0U2VjdXJlMB4XDTEzMDMy\nNTIyMTgzNVoXDTIzMDMyMzIyMTgzNVowgY0xCzAJBgNVBAYTAlVTMRMwEQYDVQQI\nDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMR0wGwYDVQQKDBRP\ncGVuIFdoaXNwZXIgU3lzdGVtczEdMBsGA1UECwwUT3BlbiBXaGlzcGVyIFN5c3Rl\nbXMxEzARBgNVBAMMClRleHRTZWN1cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\nggEKAoIBAQDBSWBpOCBDF0i4q2d4jAXkSXUGpbeWugVPQCjaL6qD9QDOxeW1afvf\nPo863i6Crq1KDxHpB36EwzVcjwLkFTIMeo7t9s1FQolAt3mErV2U0vie6Ves+yj6\ngrSfxwIDAcdsKmI0a1SQCZlr3Q1tcHAkAKFRxYNawADyps5B+Zmqcgf653TXS5/0\nIPPQLocLn8GWLwOYNnYfBvILKDMItmZTtEbucdigxEA9mfIvvHADEbteLtVgwBm9\nR5vVvtwrD6CCxI3pgH7EH7kMP0Od93wLisvn1yhHY7FuYlrkYqdkMvWUrKoASVw4\njb69vaeJCUdU+HCoXOSP1PQcL6WenNCHAgMBAAGjUDBOMB0GA1UdDgQWBBQBixjx\nP/s5GURuhYa+lGUypzI8kDAfBgNVHSMEGDAWgBQBixjxP/s5GURuhYa+lGUypzI8\nkDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQB+Hr4hC56m0LvJAu1R\nK6NuPDbTMEN7/jMojFHxH4P3XPFfupjR+bkDq0pPOU6JjIxnrD1XD/EVmTTaTVY5\niOheyv7UzJOefb2pLOc9qsuvI4fnaESh9bhzln+LXxtCrRPGhkxA1IMIo3J/s2WF\n/KVYZyciu6b4ubJ91XPAuBNZwImug7/srWvbpk0hq6A6z140WTVSKtJG7EP41kJe\n/oF4usY5J7LPkxK3LWzMJnb5EIJDmRvyH8pyRwWg6Qm6qiGFaI4nL8QU4La1x2en\n4DGXRaLMPRwjELNgQPodR38zoCMuA8gHZfZYYoZ7D7Q1wNUiVHcxuFrEeBaYJbLE\nrwLV\n-----END CERTIFICATE-----\n", "import": false, - "serverTrustRoot": "BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx" + "serverTrustRoot": "BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx", + "defaultPublicChatServer": "https://chat.getsession.org", + "defaultFileServer": "https://file.getsession.org" } diff --git a/config/development-1.json b/config/development-1.json index 57b4796c57..2ff9302380 100644 --- a/config/development-1.json +++ b/config/development-1.json @@ -1,6 +1,16 @@ { "storageProfile": "development1", "localServerPort": "8082", - "disableAutoUpdate": true, - "openDevTools": true + "seedNodeList": [ + { + "ip": "public.loki.foundation", + "port": "38157" + }, + { + "ip": "storage.testnetseed1.loki.network", + "port": "38157" + } + ], + "openDevTools": true, + "defaultPublicChatServer": "https://chat-dev.lokinet.org/" } diff --git a/config/development-2.json b/config/development-2.json new file mode 100644 index 0000000000..bf4c932588 --- /dev/null +++ b/config/development-2.json @@ -0,0 +1,16 @@ +{ + "storageProfile": "development2", + "localServerPort": "8083", + "seedNodeList": [ + { + "ip": "public.loki.foundation", + "port": "38157" + }, + { + "ip": "storage.testnetseed1.loki.network", + "port": "38157" + } + ], + "openDevTools": true, + "defaultPublicChatServer": "https://chat-dev.lokinet.org/" +} diff --git a/config/development.json b/config/development.json index f0324abfe3..89ac6c6f7e 100644 --- a/config/development.json +++ b/config/development.json @@ -1,5 +1,15 @@ { "storageProfile": "development", - "disableAutoUpdate": true, - "openDevTools": true + "seedNodeList": [ + { + "ip": "public.loki.foundation", + "port": "38157" + }, + { + "ip": "storage.testnetseed1.loki.network", + "port": "38157" + } + ], + "openDevTools": true, + "defaultPublicChatServer": "https://chat-dev.lokinet.org/" } diff --git a/config/local-devprod.json b/config/local-devprod.json new file mode 100644 index 0000000000..d4f765b434 --- /dev/null +++ b/config/local-devprod.json @@ -0,0 +1,4 @@ +{ + "storageProfile": "devprodProfile", + "openDevTools": true +} diff --git a/config/local-devprod1.json b/config/local-devprod1.json new file mode 100644 index 0000000000..3c808479bf --- /dev/null +++ b/config/local-devprod1.json @@ -0,0 +1,5 @@ +{ + "storageProfile": "devprod1Profile", + "localServerPort": "8082", + "openDevTools": true +} diff --git a/config/staging.json b/config/staging.json index acc5ac72de..f74f4b48e6 100644 --- a/config/staging.json +++ b/config/staging.json @@ -1,5 +1,4 @@ { "storageProfile": "staging", - "disableAutoUpdate": true, "openDevTools": true } diff --git a/config/swarm-testing.json b/config/swarm-testing.json new file mode 100644 index 0000000000..9df9b33405 --- /dev/null +++ b/config/swarm-testing.json @@ -0,0 +1,11 @@ +{ + "storageProfile": "swarm-testing", + "seedNodeList": [ + { + "ip": "localhost", + "port": "22129" + } + ], + "openDevTools": true, + "defaultPublicChatServer": "https://team-chat.lokinet.org/" +} diff --git a/config/swarm-testing2.json b/config/swarm-testing2.json new file mode 100644 index 0000000000..5469d9daba --- /dev/null +++ b/config/swarm-testing2.json @@ -0,0 +1,11 @@ +{ + "storageProfile": "swarm-testing2", + "seedNodeList": [ + { + "ip": "localhost", + "port": "22129" + } + ], + "openDevTools": true, + "defaultPublicChatServer": "https://team-chat.lokinet.org/" +} diff --git a/config/test-lib.json b/config/test-lib.json index d0e5c25d8c..ade27eef3f 100644 --- a/config/test-lib.json +++ b/config/test-lib.json @@ -1,5 +1,4 @@ { "storageProfile": "test", - "disableAutoUpdate": true, "openDevTools": false } diff --git a/config/test.json b/config/test.json index d0e5c25d8c..ade27eef3f 100644 --- a/config/test.json +++ b/config/test.json @@ -1,5 +1,4 @@ { "storageProfile": "test", - "disableAutoUpdate": true, "openDevTools": false } diff --git a/fonts/SFProDisplay-Regular.otf b/fonts/SFProDisplay-Regular.otf new file mode 100644 index 0000000000..09aaca9fcc Binary files /dev/null and b/fonts/SFProDisplay-Regular.otf differ diff --git a/fonts/SFProText-Regular.ttf b/fonts/SFProText-Regular.ttf new file mode 100644 index 0000000000..b63615e93d Binary files /dev/null and b/fonts/SFProText-Regular.ttf differ diff --git a/fonts/SpaceMono-Bold.ttf b/fonts/SpaceMono-Bold.ttf new file mode 100644 index 0000000000..4acd36ac38 Binary files /dev/null and b/fonts/SpaceMono-Bold.ttf differ diff --git a/fonts/SpaceMono-BoldItalic.ttf b/fonts/SpaceMono-BoldItalic.ttf new file mode 100644 index 0000000000..070247553a Binary files /dev/null and b/fonts/SpaceMono-BoldItalic.ttf differ diff --git a/fonts/SpaceMono-Italic.ttf b/fonts/SpaceMono-Italic.ttf new file mode 100644 index 0000000000..87997387fb Binary files /dev/null and b/fonts/SpaceMono-Italic.ttf differ diff --git a/fonts/SpaceMono-Regular.ttf b/fonts/SpaceMono-Regular.ttf new file mode 100644 index 0000000000..28d7ff7177 Binary files /dev/null and b/fonts/SpaceMono-Regular.ttf differ diff --git a/fonts/Wasa-Bold.otf b/fonts/Wasa-Bold.otf new file mode 100755 index 0000000000..53058507d6 Binary files /dev/null and b/fonts/Wasa-Bold.otf differ diff --git a/images/crown.svg b/images/crown.svg new file mode 100644 index 0000000000..9c9a36643f --- /dev/null +++ b/images/crown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icon-paste.svg b/images/icon-paste.svg new file mode 100644 index 0000000000..d7de9a489f --- /dev/null +++ b/images/icon-paste.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icon_1024.png b/images/icon_1024.png index 37c4eb40be..5fe3a21d36 100644 Binary files a/images/icon_1024.png and b/images/icon_1024.png differ diff --git a/images/icon_128.png b/images/icon_128.png index 3e19b69885..f581c523c1 100644 Binary files a/images/icon_128.png and b/images/icon_128.png differ diff --git a/images/icon_16.png b/images/icon_16.png index 427c6f5b4f..e7a2f5150b 100644 Binary files a/images/icon_16.png and b/images/icon_16.png differ diff --git a/images/icon_256.png b/images/icon_256.png index 826bd79844..027e60116c 100644 Binary files a/images/icon_256.png and b/images/icon_256.png differ diff --git a/images/icon_32.png b/images/icon_32.png index 93455816b7..8c138ea7e7 100644 Binary files a/images/icon_32.png and b/images/icon_32.png differ diff --git a/images/icon_48.png b/images/icon_48.png index 147727effb..a9397fc88e 100644 Binary files a/images/icon_48.png and b/images/icon_48.png differ diff --git a/images/loki/loki_icon_128.png b/images/loki/loki_icon_128.png deleted file mode 100644 index 1940490ebf..0000000000 Binary files a/images/loki/loki_icon_128.png and /dev/null differ diff --git a/images/loki/loki_icon_text.png b/images/loki/loki_icon_text.png deleted file mode 100644 index 9d79d60912..0000000000 Binary files a/images/loki/loki_icon_text.png and /dev/null differ diff --git a/images/loki/session_icon.png b/images/loki/session_icon.png new file mode 100644 index 0000000000..8555d93fe9 Binary files /dev/null and b/images/loki/session_icon.png differ diff --git a/images/loki/session_icon_128.png b/images/loki/session_icon_128.png new file mode 100644 index 0000000000..e3a410547c Binary files /dev/null and b/images/loki/session_icon_128.png differ diff --git a/images/note-28.svg b/images/note-28.svg new file mode 100644 index 0000000000..a37aefc54d --- /dev/null +++ b/images/note-28.svg @@ -0,0 +1 @@ +note-28 \ No newline at end of file diff --git a/images/search.svg b/images/search.svg index 747e21d26f..1704b4082b 100644 --- a/images/search.svg +++ b/images/search.svg @@ -1,4 +1 @@ - - - - +search-16 \ No newline at end of file diff --git a/images/session/Contacts.svg b/images/session/Contacts.svg new file mode 100644 index 0000000000..0001cb87af --- /dev/null +++ b/images/session/Contacts.svg @@ -0,0 +1,17 @@ + + + + Combined Shape + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/images/session/arrow.svg b/images/session/arrow.svg new file mode 100644 index 0000000000..402156e464 --- /dev/null +++ b/images/session/arrow.svg @@ -0,0 +1,11 @@ + + + + 1A + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/images/session/brand.svg b/images/session/brand.svg new file mode 100644 index 0000000000..beb9de0e10 --- /dev/null +++ b/images/session/brand.svg @@ -0,0 +1,31 @@ + +image/svg+xml \ No newline at end of file diff --git a/images/session/chat-bubbles.svg b/images/session/chat-bubbles.svg new file mode 100644 index 0000000000..0f06f1f531 --- /dev/null +++ b/images/session/chat-bubbles.svg @@ -0,0 +1,17 @@ + + + + Combined Shape + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/images/session/chatbubble.svg b/images/session/chatbubble.svg new file mode 100644 index 0000000000..56dc52bab5 --- /dev/null +++ b/images/session/chatbubble.svg @@ -0,0 +1,17 @@ + + + + Shape + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/images/session/chevron.svg b/images/session/chevron.svg new file mode 100644 index 0000000000..407132c4a4 --- /dev/null +++ b/images/session/chevron.svg @@ -0,0 +1,17 @@ + + + + Shape + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/images/session/circleplus.svg b/images/session/circleplus.svg new file mode 100644 index 0000000000..075c7b7f21 --- /dev/null +++ b/images/session/circleplus.svg @@ -0,0 +1,17 @@ + + + + plus + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/images/session/ellipses.svg b/images/session/ellipses.svg new file mode 100644 index 0000000000..c39d23a2bf --- /dev/null +++ b/images/session/ellipses.svg @@ -0,0 +1,15 @@ + + + + vertical + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/images/session/emoji.svg b/images/session/emoji.svg new file mode 100644 index 0000000000..ac698d29eb --- /dev/null +++ b/images/session/emoji.svg @@ -0,0 +1,17 @@ + + + + Happy + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/images/session/eye.svg b/images/session/eye.svg new file mode 100644 index 0000000000..dac9adea17 --- /dev/null +++ b/images/session/eye.svg @@ -0,0 +1,17 @@ + + + + Combined Shape + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/images/session/file.svg b/images/session/file.svg new file mode 100644 index 0000000000..2800cb4d5d --- /dev/null +++ b/images/session/file.svg @@ -0,0 +1,17 @@ + + + + Combined Shape + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/images/session/full-logo.svg b/images/session/full-logo.svg new file mode 100644 index 0000000000..01fb1cd361 --- /dev/null +++ b/images/session/full-logo.svg @@ -0,0 +1,64 @@ + +image/svg+xml \ No newline at end of file diff --git a/images/session/gear.svg b/images/session/gear.svg new file mode 100644 index 0000000000..b4777e2ad8 --- /dev/null +++ b/images/session/gear.svg @@ -0,0 +1,17 @@ + + + + Combined Shape + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/images/session/globe.svg b/images/session/globe.svg new file mode 100644 index 0000000000..54ef2c20b2 --- /dev/null +++ b/images/session/globe.svg @@ -0,0 +1,11 @@ + + + + Style + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/images/session/icon-attachment.svg b/images/session/icon-attachment.svg new file mode 100644 index 0000000000..4bad555ff1 --- /dev/null +++ b/images/session/icon-attachment.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/session/icon-back.svg b/images/session/icon-back.svg new file mode 100644 index 0000000000..7a9bdeeab4 --- /dev/null +++ b/images/session/icon-back.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/session/icon-check.svg b/images/session/icon-check.svg new file mode 100644 index 0000000000..b33e4335ae --- /dev/null +++ b/images/session/icon-check.svg @@ -0,0 +1,19 @@ + + + + check + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/images/session/icon-exit.svg b/images/session/icon-exit.svg new file mode 100644 index 0000000000..183e31b3fb --- /dev/null +++ b/images/session/icon-exit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/session/icon-favorite.svg b/images/session/icon-favorite.svg new file mode 100644 index 0000000000..13b82fbb5c --- /dev/null +++ b/images/session/icon-favorite.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/session/icon-group.svg b/images/session/icon-group.svg new file mode 100644 index 0000000000..98452d2cf5 --- /dev/null +++ b/images/session/icon-group.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/session/icon-menu.svg b/images/session/icon-menu.svg new file mode 100644 index 0000000000..96c42f0867 --- /dev/null +++ b/images/session/icon-menu.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/session/icon-message.svg b/images/session/icon-message.svg new file mode 100644 index 0000000000..1ced1a524b --- /dev/null +++ b/images/session/icon-message.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/session/icon-microphone.svg b/images/session/icon-microphone.svg new file mode 100644 index 0000000000..1018297e38 --- /dev/null +++ b/images/session/icon-microphone.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/session/icon-network.svg b/images/session/icon-network.svg new file mode 100644 index 0000000000..62d5450750 --- /dev/null +++ b/images/session/icon-network.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/session/icon-options.svg b/images/session/icon-options.svg new file mode 100644 index 0000000000..d007b55300 --- /dev/null +++ b/images/session/icon-options.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/session/icon-search.svg b/images/session/icon-search.svg new file mode 100644 index 0000000000..408a25ce3a --- /dev/null +++ b/images/session/icon-search.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/session/icon-theme.svg b/images/session/icon-theme.svg new file mode 100644 index 0000000000..95f578573d --- /dev/null +++ b/images/session/icon-theme.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/session/icon_256.png b/images/session/icon_256.png new file mode 100644 index 0000000000..027e60116c Binary files /dev/null and b/images/session/icon_256.png differ diff --git a/images/session/icon_64.png b/images/session/icon_64.png new file mode 100644 index 0000000000..fedcd0d8d2 Binary files /dev/null and b/images/session/icon_64.png differ diff --git a/images/session/magnifyingglass.svg b/images/session/magnifyingglass.svg new file mode 100644 index 0000000000..8fa6a5afec --- /dev/null +++ b/images/session/magnifyingglass.svg @@ -0,0 +1,30 @@ + + + + search + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/images/session/microphone.svg b/images/session/microphone.svg new file mode 100644 index 0000000000..94a5b20360 --- /dev/null +++ b/images/session/microphone.svg @@ -0,0 +1,17 @@ + + + + microphone + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/images/session/moon.svg b/images/session/moon.svg new file mode 100644 index 0000000000..8698a251c4 --- /dev/null +++ b/images/session/moon.svg @@ -0,0 +1,17 @@ + + + + Shape + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/images/session/reply.svg b/images/session/reply.svg new file mode 100644 index 0000000000..b8663b0686 --- /dev/null +++ b/images/session/reply.svg @@ -0,0 +1,17 @@ + + + + Combined Shape + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/images/session/send.svg b/images/session/send.svg new file mode 100644 index 0000000000..f92934910c --- /dev/null +++ b/images/session/send.svg @@ -0,0 +1,33 @@ + + + + send + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/images/session/session_chat_icon.png b/images/session/session_chat_icon.png new file mode 100644 index 0000000000..cb40720896 Binary files /dev/null and b/images/session/session_chat_icon.png differ diff --git a/images/session/star.svg b/images/session/star.svg new file mode 100644 index 0000000000..486551da0f --- /dev/null +++ b/images/session/star.svg @@ -0,0 +1,17 @@ + + + + Shape + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/images/spinner-24.svg b/images/spinner-24.svg new file mode 100644 index 0000000000..392aa714f3 --- /dev/null +++ b/images/spinner-24.svg @@ -0,0 +1,9 @@ + + + + Interderminate Spinner - 24 + Created with Sketch. + + + + \ No newline at end of file diff --git a/images/spinner-56.svg b/images/spinner-56.svg new file mode 100644 index 0000000000..0badeb1095 --- /dev/null +++ b/images/spinner-56.svg @@ -0,0 +1,9 @@ + + + + Interderminate Spinner - 56 + Created with Sketch. + + + + \ No newline at end of file diff --git a/images/spinner-track-24.svg b/images/spinner-track-24.svg new file mode 100644 index 0000000000..0ab7cac2ab --- /dev/null +++ b/images/spinner-track-24.svg @@ -0,0 +1,9 @@ + + + + Interderminate Track - 24 + Created with Sketch. + + + + \ No newline at end of file diff --git a/images/spinner-track-56.svg b/images/spinner-track-56.svg new file mode 100644 index 0000000000..d0aad7dac4 --- /dev/null +++ b/images/spinner-track-56.svg @@ -0,0 +1,9 @@ + + + + Interderminate Track - 56 + Created with Sketch. + + + + \ No newline at end of file diff --git a/images/x-16.svg b/images/x-16.svg index 6554bf274f..00c43c4146 100644 --- a/images/x-16.svg +++ b/images/x-16.svg @@ -1 +1 @@ -x-16 \ No newline at end of file +x-16 diff --git a/images/x.svg b/images/x.svg index 40b5e39d6f..9166cbe37f 100644 --- a/images/x.svg +++ b/images/x.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/index.html b/index.html index d4864a9b6e..2d781b9113 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - Signal + Session @@ -14,13 +14,7 @@
- -
- - - -
-
+
diff --git a/js/Mp3LameEncoder.min.js b/js/Mp3LameEncoder.min.js index 71fc756a89..572ec8628d 100644 --- a/js/Mp3LameEncoder.min.js +++ b/js/Mp3LameEncoder.min.js @@ -44293,7 +44293,7 @@ function _memset(ptr, value, num) { } } while ((ptr|0) < (stop4|0)) { - HEAP32[((ptr)>>2)]=value4; + HEAP32[ptr>>2]=value4; ptr = (ptr+4)|0; } } @@ -44349,14 +44349,14 @@ function _memcpy(dest, src, num) { num = (num-1)|0; } while ((num|0) >= 4) { - HEAP32[((dest)>>2)]=((HEAP32[((src)>>2)])|0); + HEAP32[dest>>2]=((HEAP32[src>>2])|0); dest = (dest+4)|0; src = (src+4)|0; num = (num-4)|0; } } while ((num|0) > 0) { - HEAP8[((dest)>>0)]=((HEAP8[((src)>>0)])|0); + HEAP8[dest>>0]=((HEAP8[src>>0])|0); dest = (dest+1)|0; src = (src+1)|0; num = (num-1)|0; diff --git a/js/background.js b/js/background.js index 7ee457b05e..85f040d995 100644 --- a/js/background.js +++ b/js/background.js @@ -8,7 +8,10 @@ storage, textsecure, Whisper, - BlockedNumberController + libloki, + libsignal, + StringView, + BlockedNumberController, */ // eslint-disable-next-line func-names @@ -55,6 +58,7 @@ 'check.svg', 'clock.svg', 'close-circle.svg', + 'crown.svg', 'delete.svg', 'dots-horizontal.svg', 'double-check.svg', @@ -116,10 +120,13 @@ 'warning.svg', 'x.svg', 'x_white.svg', - 'loki/loki_icon_text.png', - 'loki/loki_icon_128.png', + 'icon-paste.svg', + 'loki/session_icon_128.png', ]); + // Set server-client time difference + window.LokiPublicChatAPI.setClockParams(); + // We add this to window here because the default Node context is erased at the end // of preload.js processing window.setImmediate = window.nodeSetImmediate; @@ -170,6 +177,8 @@ return -1; }; Whisper.events = _.clone(Backbone.Events); + Whisper.events.isListenedTo = eventName => + Whisper.events._events ? !!Whisper.events._events[eventName] : false; let accountManager; window.getAccountManager = () => { if (!accountManager) { @@ -177,6 +186,13 @@ const PASSWORD = storage.get('password'); accountManager = new textsecure.AccountManager(USERNAME, PASSWORD); accountManager.addEventListener('registration', () => { + const user = { + regionCode: window.storage.get('regionCode'), + ourNumber: textsecure.storage.user.getNumber(), + isSecondaryDevice: !!textsecure.storage.get('isSecondaryDevice'), + }; + Whisper.events.trigger('userChanged', user); + Whisper.Registration.markDone(); window.log.info('dispatching registration event'); Whisper.events.trigger('registration_done'); @@ -197,6 +213,56 @@ window.log.info('Storage fetch'); storage.fetch(); + let specialConvInited = false; + const initSpecialConversations = async () => { + if (specialConvInited) { + return; + } + const rssFeedConversations = await window.Signal.Data.getAllRssFeedConversations( + { + ConversationCollection: Whisper.ConversationCollection, + } + ); + rssFeedConversations.forEach(conversation => { + window.feeds.push(new window.LokiRssAPI(conversation.getRssSettings())); + }); + const publicConversations = await window.Signal.Data.getAllPublicConversations( + { + ConversationCollection: Whisper.ConversationCollection, + } + ); + publicConversations.forEach(conversation => { + // weird but create the object and does everything we need + conversation.getPublicSendData(); + }); + specialConvInited = true; + }; + + const initAPIs = () => { + if (window.initialisedAPI) { + return; + } + const ourKey = textsecure.storage.user.getNumber(); + window.feeds = []; + window.lokiMessageAPI = new window.LokiMessageAPI(ourKey); + // singleton to relay events to libtextsecure/message_receiver + window.lokiPublicChatAPI = new window.LokiPublicChatAPI(ourKey); + // singleton to interface the File server + // If already exists we registered as a secondary device + if (!window.lokiFileServerAPI) { + window.lokiFileServerAPIFactory = new window.LokiFileServerAPI(ourKey); + window.lokiFileServerAPI = window.lokiFileServerAPIFactory.establishHomeConnection( + window.getDefaultFileServer() + ); + } + + window.initialisedAPI = true; + + if (storage.get('isSecondaryDevice')) { + window.lokiFileServerAPI.updateOurDeviceMapping(); + } + }; + function mapOldThemeToNew(theme) { switch (theme) { case 'dark': @@ -219,19 +285,26 @@ return; } first = false; - window.lokiP2pAPI = new window.LokiP2pAPI( - textsecure.storage.user.getNumber() - ); - window.lokiP2pAPI.on('pingContact', pubKey => { - const isPing = true; - window.libloki.api.sendOnlineBroadcastMessage(pubKey, isPing); - }); + + const currentPoWDifficulty = storage.get('PoWDifficulty', null); + if (!currentPoWDifficulty) { + storage.put('PoWDifficulty', window.getDefaultPoWDifficulty()); + } + + // Ensure accounts created prior to 1.0.0-beta8 do have their + // 'primaryDevicePubKey' defined. + if ( + Whisper.Registration.isDone() && + !storage.get('primaryDevicePubKey', null) + ) { + storage.put('primaryDevicePubKey', textsecure.storage.user.getNumber()); + } // These make key operations available to IPC handlers created in preload.js window.Events = { getDeviceName: () => textsecure.storage.user.getDeviceName(), - getThemeSetting: () => storage.get('theme-setting', 'light'), + getThemeSetting: () => 'dark', // storage.get('theme-setting', 'dark') setThemeSetting: value => { storage.put('theme-setting', value); onChangeTheme(); @@ -255,8 +328,14 @@ setReadReceiptSetting: value => storage.put('read-receipt-setting', value), - getLinkPreviewSetting: () => storage.get('linkPreviews', false), - setLinkPreviewSetting: value => storage.put('linkPreviews', value), + getTypingIndicatorsSetting: () => + storage.get('typing-indicators-setting'), + setTypingIndicatorsSetting: value => + storage.put('typing-indicators-setting', value), + + getLinkPreviewSetting: () => storage.get('link-preview-setting', false), + setLinkPreviewSetting: value => + storage.put('link-preview-setting', value), getNotificationSetting: () => storage.get('notification-setting', 'message'), @@ -296,6 +375,19 @@ }, shutdown: async () => { + // Stop background processing + window.Signal.AttachmentDownloads.stop(); + if (idleDetector) { + idleDetector.stop(); + } + + // Stop processing incoming messages + if (messageReceiver) { + await messageReceiver.stopProcessing(); + messageReceiver = null; + } + + // Shut down the data interface cleanly await window.Signal.Data.shutdown(); }, }; @@ -339,69 +431,6 @@ Views.Initialization.setMessage(window.i18n('optimizingApplication')); - window.log.info('Cleanup: starting...'); - const results = await Promise.all([ - window.Signal.Data.getOutgoingWithoutExpiresAt({ - MessageCollection: Whisper.MessageCollection, - }), - window.Signal.Data.getAllUnsentMessages({ - MessageCollection: Whisper.MessageCollection, - }), - ]); - - // Combine the models - const messagesForCleanup = results.reduce( - (array, current) => array.concat(current.toArray()), - [] - ); - - window.log.info( - `Cleanup: Found ${messagesForCleanup.length} messages for cleanup` - ); - await Promise.all( - messagesForCleanup.map(async message => { - const delivered = message.get('delivered'); - const sentAt = message.get('sent_at'); - const expirationStartTimestamp = message.get( - 'expirationStartTimestamp' - ); - - // Make sure we only target outgoing messages - if ( - message.isFriendRequest() && - message.get('direction') === 'incoming' - ) { - return; - } - - if (message.isEndSession()) { - return; - } - - if (message.hasErrors()) { - return; - } - - if (delivered) { - window.log.info( - `Cleanup: Starting timer for delivered message ${sentAt}` - ); - message.set( - 'expirationStartTimestamp', - expirationStartTimestamp || sentAt - ); - await message.setToExpire(); - return; - } - - window.log.info(`Cleanup: Deleting unsent message ${sentAt}`); - await window.Signal.Data.removeMessage(message.id, { - Message: Whisper.Message, - }); - }) - ); - window.log.info('Cleanup: complete'); - if (newVersion) { await window.Signal.Data.cleanupOrphanedAttachments(); } @@ -458,7 +487,10 @@ window.Events.setThemeSetting(newThemeSetting); try { - await ConversationController.load(); + await Promise.all([ + ConversationController.load(), + textsecure.storage.protocol.hydrateCaches(), + ]); BlockedNumberController.refresh(); } catch (error) { window.log.error( @@ -470,16 +502,6 @@ } }); - Whisper.events.on('shutdown', async () => { - if (idleDetector) { - idleDetector.stop(); - } - if (messageReceiver) { - await messageReceiver.close(); - } - Whisper.events.trigger('shutdown-complete'); - }); - Whisper.events.on('setupWithImport', () => { const { appView } = window.owsDesktopApp; if (appView) { @@ -487,6 +509,28 @@ } }); + Whisper.events.on( + 'deleteLocalPublicMessage', + async ({ messageServerId, conversationId }) => { + const message = await window.Signal.Data.getMessageByServerId( + messageServerId, + conversationId, + { + Message: Whisper.Message, + } + ); + if (message) { + const conversation = ConversationController.get(conversationId); + if (conversation) { + conversation.removeMessage(message.id); + } + await window.Signal.Data.removeMessage(message.id, { + Message: Whisper.Message, + }); + } + } + ); + Whisper.events.on('setupAsNewDevice', () => { const { appView } = window.owsDesktopApp; if (appView) { @@ -511,10 +555,82 @@ manageExpiringData(); window.dispatchEvent(new Event('storage_ready')); + window.log.info('Cleanup: starting...'); + const results = await Promise.all([ + window.Signal.Data.getOutgoingWithoutExpiresAt({ + MessageCollection: Whisper.MessageCollection, + }), + window.Signal.Data.getAllUnsentMessages({ + MessageCollection: Whisper.MessageCollection, + }), + ]); + + // Combine the models + const messagesForCleanup = results.reduce( + (array, current) => array.concat(current.toArray()), + [] + ); + + window.log.info( + `Cleanup: Found ${messagesForCleanup.length} messages for cleanup` + ); + await Promise.all( + messagesForCleanup.map(async message => { + const delivered = message.get('delivered'); + const sentAt = message.get('sent_at'); + const expirationStartTimestamp = message.get( + 'expirationStartTimestamp' + ); + + // Make sure we only target outgoing messages + if ( + message.isFriendRequest() && + message.get('direction') === 'incoming' + ) { + return; + } + + if (message.isEndSession()) { + return; + } + + if (message.hasErrors()) { + return; + } + + if (delivered) { + window.log.info( + `Cleanup: Starting timer for delivered message ${sentAt}` + ); + message.set( + 'expirationStartTimestamp', + expirationStartTimestamp || sentAt + ); + await message.setToExpire(); + return; + } + + window.log.info(`Cleanup: Deleting unsent message ${sentAt}`); + await window.Signal.Data.removeMessage(message.id, { + Message: Whisper.Message, + }); + const conversation = message.getConversation(); + if (conversation) { + await conversation.updateLastMessage(); + } + }) + ); + window.log.info('Cleanup: complete'); + window.log.info('listening for registration events'); - Whisper.events.on('registration_done', () => { + Whisper.events.on('registration_done', async () => { window.log.info('handling registration event'); + // Disable link previews as default per Kee + storage.onready(async () => { + storage.put('link-preview-setting', false); + }); + // listeners Whisper.RotateSignedPreKeyListener.init(Whisper.events, newVersion); // window.Signal.RefreshSenderCertificate.initialize({ @@ -539,7 +655,10 @@ if (Whisper.Import.isIncomplete()) { window.log.info('Import was interrupted, showing import error screen'); appView.openImporter(); - } else if (Whisper.Registration.everDone()) { + } else if ( + Whisper.Registration.isDone() && + !Whisper.Registration.ongoingSecondaryDeviceRegistration() + ) { // listeners Whisper.RotateSignedPreKeyListener.init(Whisper.events, newVersion); // window.Signal.RefreshSenderCertificate.initialize({ @@ -577,9 +696,510 @@ window.addEventListener('focus', () => Whisper.Notifications.clear()); window.addEventListener('unload', () => Whisper.Notifications.fastClear()); - Whisper.events.on('showConversation', conversation => { + Whisper.events.on('showConversation', (id, messageId) => { + if (appView) { + appView.openConversation(id, messageId); + } + }); + + window.doUpdateGroup = async (groupId, groupName, members) => { + const ourKey = textsecure.storage.user.getNumber(); + + const ev = new Event('message'); + ev.confirm = () => {}; + + ev.data = { + source: ourKey, + timestamp: Date.now(), + message: { + group: { + id: groupId, + type: textsecure.protobuf.GroupContext.Type.UPDATE, + name: groupName, + members, + avatar: null, // TODO + }, + }, + }; + + const convo = await ConversationController.getOrCreateAndWait( + groupId, + 'group' + ); + + if (convo.isPublic()) { + const API = await convo.getPublicSendData(); + if (await API.setChannelName(groupName)) { + // queue update from server + // and let that set the conversation + API.pollForChannelOnce(); + // or we could just directly call + // convo.setGroupName(groupName); + // but gut is saying let the server be the definitive storage of the state + // and trickle down from there + } + return; + } + + const avatar = ''; + const options = {}; + + const recipients = _.union(convo.get('members'), members); + + await onMessageReceived(ev); + convo.updateGroup({ + groupId, + groupName, + avatar, + recipients, + members, + options, + }); + }; + + window.doCreateGroup = async (groupName, members) => { + const keypair = await libsignal.KeyHelper.generateIdentityKeyPair(); + const groupId = StringView.arrayBufferToHex(keypair.pubKey); + + const ev = new Event('group'); + + const ourKey = textsecure.storage.user.getNumber(); + + const allMembers = [ourKey, ...members]; + + ev.groupDetails = { + id: groupId, + name: groupName, + members: allMembers, + recipients: allMembers, + active: true, + expireTimer: 0, + avatar: '', + }; + + ev.confirm = () => {}; + + await onGroupReceived(ev); + + const convo = await ConversationController.getOrCreateAndWait( + groupId, + 'group' + ); + + convo.updateGroup(ev.groupDetails); + + // Group conversations are automatically 'friends' + // so that we can skip the friend request logic + convo.setFriendRequestStatus( + window.friends.friendRequestStatusEnum.friends + ); + + convo.updateGroupAdmins([ourKey]); + + appView.openConversation(groupId, {}); + }; + + window.confirmationDialog = params => { + const confirmDialog = new Whisper.SessionConfirmView({ + el: $('#session-confirm-container'), + title: params.title, + message: params.message, + messageSub: params.messageSub || undefined, + resolve: params.resolve || undefined, + reject: params.reject || undefined, + okText: params.okText || undefined, + okTheme: params.okTheme || undefined, + closeTheme: params.closeTheme || undefined, + cancelText: params.cancelText || undefined, + hideCancel: params.hideCancel || false, + }); + + confirmDialog.render(); + }; + + window.showQRDialog = window.owsDesktopApp.appView.showQRDialog; + window.showSeedDialog = window.owsDesktopApp.appView.showSeedDialog; + window.showPasswordDialog = window.owsDesktopApp.appView.showPasswordDialog; + window.showEditProfileDialog = async callback => { + const ourNumber = window.storage.get('primaryDevicePubKey'); + const conversation = await ConversationController.getOrCreateAndWait( + ourNumber, + 'private' + ); + + const readFile = attachment => + new Promise((resolve, reject) => { + const fileReader = new FileReader(); + fileReader.onload = e => { + const data = e.target.result; + resolve({ + ...attachment, + data, + size: data.byteLength, + }); + }; + fileReader.onerror = reject; + fileReader.onabort = reject; + fileReader.readAsArrayBuffer(attachment.file); + }); + + const avatarPath = conversation.getAvatarPath(); + const profile = conversation.getLokiProfile(); + const displayName = profile && profile.displayName; + + if (appView) { + appView.showEditProfileDialog({ + callback, + profileName: displayName, + pubkey: ourNumber, + avatarPath, + avatarColor: conversation.getColor(), + onOk: async (newName, avatar) => { + let newAvatarPath = ''; + let url = null; + let profileKey = null; + if (avatar) { + const data = await readFile({ file: avatar }); + + // For simplicity we use the same attachment pointer that would send to + // others, which means we need to wait for the database response. + // To avoid the wait, we create a temporary url for the local image + // and use it until we the the response from the server + const tempUrl = window.URL.createObjectURL(avatar); + conversation.setLokiProfile({ displayName: newName }); + conversation.set('avatar', tempUrl); + + // Encrypt with a new key every time + profileKey = libsignal.crypto.getRandomBytes(32); + const encryptedData = await textsecure.crypto.encryptProfile( + data.data, + profileKey + ); + + const avatarPointer = await textsecure.messaging.uploadAvatar({ + ...data, + data: encryptedData, + size: encryptedData.byteLength, + }); + + ({ url } = avatarPointer); + + storage.put('profileKey', profileKey); + + conversation.set('avatarPointer', url); + + const upgraded = await Signal.Migrations.processNewAttachment({ + isRaw: true, + data: data.data, + url, + }); + newAvatarPath = upgraded.path; + } + + // Replace our temporary image with the attachment pointer from the server: + conversation.set('avatar', null); + conversation.setLokiProfile({ + displayName: newName, + avatar: newAvatarPath, + }); + // inform all your registered public servers + // could put load on all the servers + // if they just keep changing their names without sending messages + // so we could disable this here + // or least it enable for the quickest response + window.lokiPublicChatAPI.setProfileName(newName); + window + .getConversations() + .filter(convo => convo.isPublic() && !convo.isRss()) + .forEach(convo => + convo.trigger('ourAvatarChanged', { url, profileKey }) + ); + }, + }); + } + }; + + // Set user's launch count. + const prevLaunchCount = window.getSettingValue('launch-count'); + const launchCount = !prevLaunchCount ? 1 : prevLaunchCount + 1; + window.setSettingValue('launch-count', launchCount); + + // On first launch + if (launchCount === 1) { + // Initialise default settings + window.setSettingValue('hide-menu-bar', true); + window.setSettingValue('link-preview-setting', false); + } + + // Render onboarding message from LeftPaneMessageSection + // unless user turns it off during their session + window.setSettingValue('render-message-onboarding', true); + + // Generates useful random ID for various purposes + window.generateID = () => + Math.random() + .toString(36) + .substring(3); + + window.toasts = new Map(); + window.pushToast = options => { + // Setting toasts with the same ID can be used to prevent identical + // toasts from appearing at once (stacking). + // If toast already exists, it will be reloaded (updated) + + const params = { + title: options.title, + id: options.id || window.generateID(), + description: options.description || '', + type: options.type || '', + icon: options.icon || '', + shouldFade: options.shouldFade, + }; + + // Give all toasts an ID. User may define. + let currentToast; + const toastID = params.id; + const toast = !!toastID && window.toasts.get(toastID); + if (toast) { + currentToast = window.toasts.get(toastID); + currentToast.update(params); + } else { + // Make new Toast + window.toasts.set( + toastID, + new Whisper.SessionToastView({ + el: $('#session-toast-container'), + }) + ); + + currentToast = window.toasts.get(toastID); + currentToast.render(); + currentToast.update(params); + } + + // Remove some toasts if too many exist + const maxToasts = 6; + while (window.toasts.size > maxToasts) { + const finalToastID = window.toasts.keys().next().value; + window.toasts.get(finalToastID).fadeToast(); + } + + return toastID; + }; + + window.getFriendsFromContacts = contacts => { + // To call from TypeScript, input / output are both + // of type Array + let friendList = contacts; + if (friendList !== undefined) { + friendList = friendList.filter( + friend => friend.type === 'direct' && !friend.isMe + ); + } + return friendList; + }; + + // Get memberlist. This function is not accurate >> + // window.getMemberList = window.lokiPublicChatAPI.getListOfMembers(); + + window.deleteAccount = async () => { + try { + window.log.info('Deleting everything!'); + + const { Logs } = window.Signal; + await Logs.deleteAll(); + + await window.Signal.Data.removeAll(); + await window.Signal.Data.close(); + await window.Signal.Data.removeDB(); + + await window.Signal.Data.removeOtherData(); + } catch (error) { + window.log.error( + 'Something went wrong deleting all data:', + error && error.stack ? error.stack : error + ); + } + window.restart(); + }; + + window.toggleTheme = () => { + const theme = window.Events.getThemeSetting(); + const updatedTheme = theme === 'dark' ? 'light' : 'dark'; + + $(document.body) + .removeClass('dark-theme') + .removeClass('light-theme') + .addClass(`${updatedTheme}-theme`); + window.Events.setThemeSetting(updatedTheme); + }; + + window.toggleMenuBar = () => { + const current = window.getSettingValue('hide-menu-bar'); + if (current === undefined) { + window.Events.setHideMenuBar(false); + return; + } + + window.Events.setHideMenuBar(!current); + }; + + window.toggleSpellCheck = () => { + const newValue = !window.getSettingValue('spell-check'); + window.Events.setSpellCheck(newValue); + }; + + window.toggleLinkPreview = () => { + const newValue = !window.getSettingValue('link-preview-setting'); + window.setSettingValue('link-preview-setting', newValue); + }; + + window.toggleMediaPermissions = () => { + const mediaPermissions = window.getMediaPermissions(); + window.setMediaPermissions(!mediaPermissions); + }; + + window.attemptConnection = async (serverURL, channelId) => { + let rawserverURL = serverURL + .replace(/^https?:\/\//i, '') + .replace(/[/\\]+$/i, ''); + rawserverURL = rawserverURL.toLowerCase(); + const sslServerURL = `https://${rawserverURL}`; + const conversationId = `publicChat:${channelId}@${rawserverURL}`; + + // quickly peak to make sure we don't already have it + const conversationExists = window.ConversationController.get( + conversationId + ); + if (conversationExists) { + // We are already a member of this public chat + return new Promise((_resolve, reject) => { + reject(window.i18n('publicChatExists')); + }); + } + + // get server + const serverAPI = await window.lokiPublicChatAPI.findOrCreateServer( + sslServerURL + ); + // SSL certificate failure or offline + if (!serverAPI) { + // Url incorrect or server not compatible + return new Promise((_resolve, reject) => { + reject(window.i18n('connectToServerFail')); + }); + } + + // create conversation + const conversation = await window.ConversationController.getOrCreateAndWait( + conversationId, + 'group' + ); + + // convert conversation to a public one + await conversation.setPublicSource(sslServerURL, channelId); + // set friend and appropriate SYNC messages for multidevice + await conversation.setFriendRequestStatus( + window.friends.friendRequestStatusEnum.friends, + { blockSync: true } + ); + + // and finally activate it + conversation.getPublicSendData(); // may want "await" if you want to use the API + + return conversation; + }; + + window.sendGroupInvitations = (serverInfo, pubkeys) => { + pubkeys.forEach(async pubkey => { + const convo = await ConversationController.getOrCreateAndWait( + pubkey, + 'private' + ); + + if (convo) { + convo.sendMessage('', null, null, null, { + serverName: serverInfo.name, + channelId: serverInfo.channelId, + serverAddress: serverInfo.address, + }); + } + }); + }; + + Whisper.events.on('createNewGroup', async () => { + if (appView) { + appView.showCreateGroup(); + } + }); + + Whisper.events.on('updateGroup', async groupConvo => { + if (appView) { + appView.showUpdateGroupDialog(groupConvo); + } + }); + + Whisper.events.on('inviteFriends', async groupConvo => { + if (appView) { + appView.showInviteFriendsDialog(groupConvo); + } + }); + + Whisper.events.on('addModerators', async groupConvo => { + if (appView) { + appView.showAddModeratorsDialog(groupConvo); + } + }); + + Whisper.events.on('removeModerators', async groupConvo => { if (appView) { - appView.openConversation(conversation); + appView.showRemoveModeratorsDialog(groupConvo); + } + }); + + Whisper.events.on( + 'publicChatInvitationAccepted', + async (serverAddress, channelId) => { + // To some degree this has been copy-pasted + // form connection_to_server_dialog_view.js: + const rawServerUrl = serverAddress + .replace(/^https?:\/\//i, '') + .replace(/[/\\]+$/i, ''); + const sslServerUrl = `https://${rawServerUrl}`; + const conversationId = `publicChat:${channelId}@${rawServerUrl}`; + + const conversationExists = ConversationController.get(conversationId); + if (conversationExists) { + window.log.warn('We are already a member of this public chat'); + return; + } + + const serverAPI = await window.lokiPublicChatAPI.findOrCreateServer( + sslServerUrl + ); + if (!serverAPI) { + window.log.warn(`Could not connect to ${serverAddress}`); + return; + } + + const conversation = await ConversationController.getOrCreateAndWait( + conversationId, + 'group' + ); + + serverAPI.findOrCreateChannel(channelId, conversationId); + await conversation.setPublicSource(sslServerUrl, channelId); + await conversation.setFriendRequestStatus( + window.friends.friendRequestStatusEnum.friends + ); + + appView.openConversation(conversationId, {}); + } + ); + + Whisper.events.on('leaveGroup', async groupConvo => { + if (appView) { + appView.showLeaveGroupDialog(groupConvo); } }); @@ -590,10 +1210,10 @@ }); }); - Whisper.Notifications.on('click', conversation => { + Whisper.Notifications.on('click', (id, messageId) => { window.showWindow(); - if (conversation) { - appView.openConversation(conversation); + if (id) { + appView.openConversation(id, messageId); } else { appView.openInbox({ initialLoadComplete, @@ -601,24 +1221,38 @@ } }); - Whisper.events.on('onEditProfile', () => { - const ourNumber = textsecure.storage.user.getNumber(); - const profile = storage.getLocalProfile(); - const displayName = profile && profile.name && profile.name.displayName; + Whisper.events.on('openInbox', () => { + appView.openInbox({ + initialLoadComplete, + }); + }); + + Whisper.events.on('onShowUserDetails', async ({ userPubKey }) => { + const isMe = userPubKey === textsecure.storage.user.getNumber(); + + if (isMe) { + Whisper.events.trigger('onEditProfile'); + return; + } + + const conversation = await ConversationController.getOrCreateAndWait( + userPubKey, + 'private' + ); + + const avatarPath = conversation.getAvatarPath(); + const profile = conversation.getLokiProfile(); + const displayName = profile && profile.displayName; + if (appView) { - appView.showNicknameDialog({ - title: window.i18n('editProfileTitle'), - message: window.i18n('editProfileDisplayNameWarning'), - nickname: displayName, - onOk: async newName => { - await storage.setProfileName(newName); - - // Update the conversation if we have it - const conversation = ConversationController.get(ourNumber); - if (conversation) { - const newProfile = storage.getLocalProfile(); - conversation.setProfile(newProfile); - } + appView.showUserDetailsDialog({ + profileName: displayName, + pubkey: userPubKey, + avatarPath, + avatarColor: conversation.getColor(), + isRss: conversation.isRss(), + onStartConversation: () => { + Whisper.events.trigger('showConversation', userPubKey); }, }); } @@ -644,23 +1278,40 @@ } }); + Whisper.events.on('showSessionRestoreConfirmation', options => { + if (appView) { + appView.showSessionRestoreConfirmation(options); + } + }); + Whisper.events.on('showNicknameDialog', options => { if (appView) { appView.showNicknameDialog(options); } }); - Whisper.events.on('showPasswordDialog', options => { + Whisper.events.on('showSeedDialog', async () => { if (appView) { - appView.showPasswordDialog(options); + appView.showSeedDialog(); } }); - Whisper.events.on('showSeedDialog', async () => { - const manager = await getAccountManager(); - if (appView && manager) { - const seed = manager.getCurrentMnemonic(); - appView.showSeedDialog(seed); + Whisper.events.on('showQRDialog', async () => { + if (appView) { + const ourNumber = textsecure.storage.user.getNumber(); + appView.showQRDialog(ourNumber); + } + }); + + Whisper.events.on('showDevicePairingDialog', async (options = {}) => { + if (appView) { + appView.showDevicePairingDialog(options); + } + }); + + Whisper.events.on('showDevicePairingWordsDialog', async () => { + if (appView) { + appView.showDevicePairingWordsDialog(); } }); @@ -673,32 +1324,54 @@ } }); - Whisper.events.on('p2pMessageSent', ({ pubKey, timestamp }) => { - try { - const conversation = ConversationController.get(pubKey); - conversation.onP2pMessageSent(pubKey, timestamp); - } catch (e) { - window.log.error('Error setting p2p on message'); + Whisper.events.on( + 'publicMessageSent', + ({ pubKey, timestamp, serverId }) => { + try { + const conversation = ConversationController.get(pubKey); + conversation.onPublicMessageSent(pubKey, timestamp, serverId); + } catch (e) { + window.log.error('Error setting public on message'); + } } - }); + ); Whisper.events.on('password-updated', () => { if (appView && appView.inboxView) { appView.inboxView.trigger('password-updated'); } }); + + Whisper.events.on('devicePairingRequestAccepted', async (pubKey, cb) => { + try { + await getAccountManager().authoriseSecondaryDevice(pubKey); + cb(null); + } catch (e) { + cb(e); + } + }); + + Whisper.events.on('devicePairingRequestRejected', async pubKey => { + await libloki.storage.removeContactPreKeyBundle(pubKey); + await libloki.storage.removePairingAuthorisationForSecondaryPubKey( + pubKey + ); + }); + + Whisper.events.on('deviceUnpairingRequested', async pubKey => { + await libloki.storage.removePairingAuthorisationForSecondaryPubKey( + pubKey + ); + await window.lokiFileServerAPI.updateOurDeviceMapping(); + // TODO: we should ensure the message was sent and retry automatically if not + await libloki.api.sendUnpairingMessageToSecondary(pubKey); + Whisper.events.trigger('refreshLinkedDeviceList'); + }); } window.getSyncRequest = () => new textsecure.SyncRequest(textsecure.messaging, messageReceiver); - Whisper.events.on('start-shutdown', async () => { - if (messageReceiver) { - await messageReceiver.close(); - } - Whisper.events.trigger('shutdown-complete'); - }); - let disconnectTimer = null; function onOffline() { window.log.info('offline'); @@ -739,15 +1412,16 @@ ); } - function disconnect() { + async function disconnect() { window.log.info('disconnect'); // Clear timer, since we're only called when the timer is expired disconnectTimer = null; if (messageReceiver) { - messageReceiver.close(); + await messageReceiver.close(); } + window.Signal.AttachmentDownloads.stop(); } let connectCount = 0; @@ -775,7 +1449,7 @@ } if (messageReceiver) { - messageReceiver.close(); + await messageReceiver.close(); } const USERNAME = storage.get('number_id'); @@ -790,7 +1464,32 @@ Whisper.Notifications.disable(); // avoid notification flood until empty - // initialize the socket and start listening for messages + if (Whisper.Registration.ongoingSecondaryDeviceRegistration()) { + const ourKey = textsecure.storage.user.getNumber(); + window.lokiMessageAPI = new window.LokiMessageAPI(ourKey); + window.lokiFileServerAPIFactory = new window.LokiFileServerAPI(ourKey); + window.lokiFileServerAPI = window.lokiFileServerAPIFactory.establishHomeConnection( + window.getDefaultFileServer() + ); + window.lokiPublicChatAPI = null; + window.feeds = []; + messageReceiver = new textsecure.MessageReceiver( + USERNAME, + PASSWORD, + mySignalingKey, + options + ); + messageReceiver.addEventListener('message', onMessageReceived); + messageReceiver.addEventListener('contact', onContactReceived); + window.textsecure.messaging = new textsecure.MessageSender( + USERNAME, + PASSWORD + ); + return; + } + + initAPIs(); + await initSpecialConversations(); messageReceiver = new textsecure.MessageReceiver( USERNAME, PASSWORD, @@ -812,6 +1511,15 @@ messageReceiver.addEventListener('configuration', onConfiguration); messageReceiver.addEventListener('typing', onTyping); + Whisper.events.on('endSession', source => { + messageReceiver.handleEndSession(source); + }); + + window.Signal.AttachmentDownloads.start({ + getMessageReceiver: () => messageReceiver, + logger: window.log, + }); + window.textsecure.messaging = new textsecure.MessageSender( USERNAME, PASSWORD @@ -908,6 +1616,8 @@ function onEmpty() { initialLoadComplete = true; + window.readyForUpdates(); + let interval = setInterval(() => { const view = window.owsDesktopApp.appView; if (view) { @@ -957,26 +1667,36 @@ } if (typingIndicators === true || typingIndicators === false) { - storage.put('typingIndicators', typingIndicators); + storage.put('typing-indicators-setting', typingIndicators); } if (linkPreviews === true || linkPreviews === false) { - storage.put('linkPreviews', linkPreviews); + storage.put('link-preview-setting', linkPreviews); } ev.confirm(); } - function onTyping(ev) { + async function onTyping(ev) { const { typing, sender, senderDevice } = ev; const { groupId, started } = typing || {}; // We don't do anything with incoming typing messages if the setting is disabled - if (!storage.get('typingIndicators')) { + if (!storage.get('typing-indicators-setting')) { return; } - const conversation = ConversationController.get(groupId || sender); + let primaryDevice = null; + const authorisation = await libloki.storage.getGrantAuthorisationForSecondaryPubKey( + sender + ); + if (authorisation) { + primaryDevice = authorisation.primaryDevicePubKey; + } + + const conversation = ConversationController.get( + groupId || primaryDevice || sender + ); if (conversation) { conversation.notifyTyping({ @@ -1024,6 +1744,28 @@ if (activeAt !== null) { activeAt = activeAt || Date.now(); } + const ourPrimaryKey = window.storage.get('primaryDevicePubKey'); + const ourDevices = await libloki.storage.getAllDevicePubKeysForPrimaryPubKey( + ourPrimaryKey + ); + // TODO: We should probably just *not* send any secondary devices and + // just load them all and send FRs when we get the mapping + const isOurSecondaryDevice = + id !== ourPrimaryKey && + ourDevices && + ourDevices.some(devicePubKey => devicePubKey === id); + + if (isOurSecondaryDevice) { + await conversation.setSecondaryStatus(true, ourPrimaryKey); + } + + if (conversation.isFriendRequestStatusNone()) { + // Will be replaced with automatic friend request + libloki.api.sendBackgroundMessage(conversation.id); + } else { + // Accept any pending friend requests if there are any + conversation.onAcceptFriendRequest({ blockSync: true }); + } if (details.profileKey) { const profileKey = window.Signal.Crypto.arrayBufferToBase64( @@ -1040,12 +1782,19 @@ } } + // Do not set name to allow working with lokiProfile and nicknames conversation.set({ - name: details.name, + // name: details.name, color: details.color, active_at: activeAt, }); + await conversation.setLokiProfile({ displayName: details.name }); + + if (details.nickname) { + await conversation.setNickname(details.nickname); + } + // Update the conversation avatar only if new avatar exists and hash differs const { avatar } = details; if (avatar && avatar.data) { @@ -1190,6 +1939,14 @@ const messageDescriptor = getMessageDescriptor(data); + // Funnel messages to primary device conversation if multi-device + const authorisation = await libloki.storage.getGrantAuthorisationForSecondaryPubKey( + messageDescriptor.id + ); + if (authorisation) { + messageDescriptor.id = authorisation.primaryDevicePubKey; + } + const { PROFILE_KEY_UPDATE } = textsecure.protobuf.DataMessage.Flags; // eslint-disable-next-line no-bitwise const isProfileUpdate = Boolean(data.message.flags & PROFILE_KEY_UPDATE); @@ -1197,110 +1954,43 @@ return handleProfileUpdate({ data, confirm, messageDescriptor }); } - const message = await createMessage(data); + const primaryDeviceKey = window.storage.get('primaryDevicePubKey'); + const allOurDevices = await libloki.storage.getAllDevicePubKeysForPrimaryPubKey( + primaryDeviceKey + ); + const descriptorId = await textsecure.MessageReceiver.arrayBufferToString( + messageDescriptor.id + ); + let message; + if ( + messageDescriptor.type === 'group' && + descriptorId.match(/^publicChat:/) && + allOurDevices.includes(data.source) + ) { + // Public chat messages from ourselves should be outgoing + message = await createSentMessage(data); + } else { + message = await createMessage(data); + } const isDuplicate = await isMessageDuplicate(message); if (isDuplicate) { - window.log.warn('Received duplicate message', message.idForLogging()); + // RSS expects duplciates, so squelch log + if (!descriptorId.match(/^rss:/)) { + window.log.warn('Received duplicate message', message.idForLogging()); + } return event.confirm(); } - const withQuoteReference = await copyFromQuotedMessage(data.message); - const upgradedMessage = await upgradeMessageSchema(withQuoteReference); - await ConversationController.getOrCreateAndWait( messageDescriptor.id, messageDescriptor.type ); - return message.handleDataMessage(upgradedMessage, event.confirm, { + return message.handleDataMessage(data.message, event.confirm, { initialLoadComplete, }); }; } - async function copyFromQuotedMessage(message) { - const { quote } = message; - if (!quote) { - return message; - } - - const { attachments, id, author } = quote; - const firstAttachment = attachments[0]; - - const collection = await window.Signal.Data.getMessagesBySentAt(id, { - MessageCollection: Whisper.MessageCollection, - }); - const queryMessage = collection.find(item => { - const messageAuthor = item.getContact(); - - return messageAuthor && author === messageAuthor.id; - }); - - if (!queryMessage) { - quote.referencedMessageNotFound = true; - return message; - } - - quote.text = queryMessage.get('body'); - if (firstAttachment) { - firstAttachment.thumbnail = null; - } - - if ( - !firstAttachment || - (!window.Signal.Util.GoogleChrome.isImageTypeSupported( - firstAttachment.contentType - ) && - !window.Signal.Util.GoogleChrome.isVideoTypeSupported( - firstAttachment.contentType - )) - ) { - return message; - } - - try { - if ( - queryMessage.get('schemaVersion') < Message.VERSION_NEEDED_FOR_DISPLAY - ) { - const upgradedMessage = await upgradeMessageSchema( - queryMessage.attributes - ); - queryMessage.set(upgradedMessage); - await window.Signal.Data.saveMessage(upgradedMessage, { - Message: Whisper.Message, - }); - } - } catch (error) { - window.log.error( - 'Problem upgrading message quoted message from database', - Errors.toLogFormat(error) - ); - return message; - } - - const queryAttachments = queryMessage.get('attachments') || []; - - if (queryAttachments.length > 0) { - const queryFirst = queryAttachments[0]; - const { thumbnail } = queryFirst; - - if (thumbnail && thumbnail.path) { - firstAttachment.thumbnail = thumbnail; - } - } - - const queryPreview = queryMessage.get('preview') || []; - if (queryPreview.length > 0) { - const queryFirst = queryPreview[0]; - const { image } = queryFirst; - - if (image && image.path) { - firstAttachment.thumbnail = image; - } - } - - return message; - } - // Received: async function handleMessageReceivedProfileUpdate({ data, @@ -1369,10 +2059,10 @@ return new Whisper.Message({ source: textsecure.storage.user.getNumber(), - sourceDevice: data.device, + sourceDevice: data.sourceDevice, sent_at: data.timestamp, sent_to: sentTo, - received_at: now, + received_at: data.isPublic ? data.receivedAt : now, conversationId: data.destination, type: 'outgoing', sent: true, @@ -1410,13 +2100,15 @@ let messageData = { source: data.source, sourceDevice: data.sourceDevice, + serverId: data.serverId, sent_at: data.timestamp, received_at: data.receivedAt || Date.now(), conversationId: data.source, unidentifiedDeliveryReceived: data.unidentifiedDeliveryReceived, type: 'incoming', unread: 1, - isP2p: data.isP2p, + isPublic: data.isPublic, + isRss: data.isRss, }; if (data.friendRequest) { @@ -1432,7 +2124,9 @@ // If we don't return early here, we can get into infinite error loops. So, no // delivery receipts for sealed sender errors. - if (isError || !data.unidentifiedDeliveryReceived) { + + // Note(LOKI): don't send receipt for FR as we don't have a session yet + if (isError || !data.unidentifiedDeliveryReceived || data.friendRequest) { return message; } @@ -1440,13 +2134,16 @@ const { wrap, sendOptions } = ConversationController.prepareForSend( data.source ); - await wrap( - textsecure.messaging.sendDeliveryReceipt( - data.source, - data.timestamp, - sendOptions - ) - ); + const isGroup = data && data.message && data.message.group; + if (!isGroup) { + await wrap( + textsecure.messaging.sendDeliveryReceipt( + data.source, + data.timestamp, + sendOptions + ) + ); + } } catch (error) { window.log.error( `Failed to send delivery receipt to ${data.source} for message ${ @@ -1460,6 +2157,48 @@ } async function onError(ev) { + const noSession = + ev.error && + ev.error.message && + ev.error.message.indexOf('No record for device') === 0; + const pubkey = ev.proto.source; + + if (noSession) { + const convo = await ConversationController.getOrCreateAndWait( + pubkey, + 'private' + ); + + if (!convo.get('sessionRestoreSeen')) { + convo.set({ sessionRestoreSeen: true }); + + await window.Signal.Data.updateConversation( + convo.id, + convo.attributes, + { Conversation: Whisper.Conversation } + ); + + window.Whisper.events.trigger('showSessionRestoreConfirmation', { + pubkey, + onOk: () => { + convo.sendMessage('', null, null, null, null, { + sessionRestoration: true, + }); + }, + }); + } else { + window.log.verbose( + `Already seen session restore for pubkey: ${pubkey}` + ); + if (ev.confirm) { + ev.confirm(); + } + } + + // We don't want to display any failed messages in the conversation: + return; + } + const { error } = ev; window.log.error('background onError:', Errors.toLogFormat(error)); @@ -1470,6 +2209,13 @@ ) { Whisper.events.trigger('unauthorized'); + if (messageReceiver) { + await messageReceiver.stopProcessing(); + messageReceiver = null; + } + + onEmpty(); + window.log.warn( 'Client is no longer authorized; deleting local configuration' ); diff --git a/js/blocked_number_controller.js b/js/blocked_number_controller.js index 918a21749b..7e1a30ab6b 100644 --- a/js/blocked_number_controller.js +++ b/js/blocked_number_controller.js @@ -42,7 +42,9 @@ storage.addBlockedNumber(number); // Make sure we don't add duplicates - if (blockedNumbers.getModel(number)) return; + if (blockedNumbers.getModel(number)) { + return; + } blockedNumbers.add({ number }); }, diff --git a/js/conversation_controller.js b/js/conversation_controller.js index 311f03d009..32b5e8e101 100644 --- a/js/conversation_controller.js +++ b/js/conversation_controller.js @@ -1,4 +1,4 @@ -/* global _, Whisper, Backbone, storage, lokiP2pAPI, textsecure, libsignal */ +/* global _, Whisper, Backbone, storage, textsecure, libsignal, log */ /* eslint-disable more/no-then */ @@ -11,8 +11,6 @@ const conversations = new Whisper.ConversationCollection(); const inboxCollection = new (Backbone.Collection.extend({ initialize() { - this.on('change:timestamp change:name change:number', this.sort); - this.listenTo(conversations, 'add change:active_at', this.addActive); this.listenTo(conversations, 'reset', () => this.reset([])); this.listenTo(conversations, 'remove', this.remove); @@ -22,25 +20,6 @@ _.debounce(this.updateUnreadCount.bind(this), 1000) ); this.startPruning(); - - this.collator = new Intl.Collator(); - }, - comparator(m1, m2) { - const timestamp1 = m1.get('timestamp'); - const timestamp2 = m2.get('timestamp'); - if (timestamp1 && !timestamp2) { - return -1; - } - if (timestamp2 && !timestamp1) { - return 1; - } - if (timestamp1 && timestamp2 && timestamp1 !== timestamp2) { - return timestamp2 - timestamp1; - } - - const title1 = m1.getTitle().toLowerCase(); - const title2 = m2.getTitle().toLowerCase(); - return this.collator.compare(title1, title2); }, addActive(model) { if (model.get('active_at')) { @@ -78,58 +57,9 @@ }))(); window.getInboxCollection = () => inboxCollection; - - const contactCollection = new (Backbone.Collection.extend({ - initialize() { - this.on( - 'change:timestamp change:name change:number change:profileName', - this.sort - ); - - this.listenTo( - conversations, - 'add change:active_at change:friendRequestStatus', - this.addActive - ); - this.listenTo(conversations, 'remove', this.remove); - this.listenTo(conversations, 'reset', () => this.reset([])); - - this.collator = new Intl.Collator(); - }, - comparator(m1, m2) { - const title1 = m1.getTitle().toLowerCase(); - const title2 = m2.getTitle().toLowerCase(); - return this.collator.compare(title1, title2); - }, - addActive(model) { - // We only want models which we are friends with - if (model.isFriend() && !model.isMe()) { - this.add(model); - model.updateLastMessage(); - } else { - this.remove(model); - } - }, - }))(); - - window.getContactCollection = () => contactCollection; + window.getConversations = () => conversations; window.ConversationController = { - getCollection() { - return conversations; - }, - markAsSelected(toSelect) { - conversations.each(conversation => { - const current = conversation.isSelected || false; - const newValue = conversation.id === toSelect.id; - - // eslint-disable-next-line no-param-reassign - conversation.isSelected = newValue; - if (current !== newValue) { - conversation.trigger('change'); - } - }); - }, get(id) { if (!this._initialFetchComplete) { throw new Error( @@ -206,10 +136,12 @@ conversation.initialPromise = create(); conversation.initialPromise.then(() => { - Promise.all([ - conversation.updateProfileAvatar(), - window.lokiSnodeAPI.refreshSwarmNodesForPubKey(id), - ]); + if (!conversation.isPublic() && !conversation.isRss()) { + Promise.all([ + conversation.updateProfileAvatar(), + window.lokiSnodeAPI.refreshSwarmNodesForPubKey(id), + ]); + } }); return conversation; @@ -229,6 +161,14 @@ if (!conversation) { return; } + if (conversation.isPublic()) { + const channelAPI = await conversation.getPublicSendData(); + if (channelAPI === null) { + log.warn(`Could not get API for public conversation ${id}`); + } else { + channelAPI.serverAPI.partChannel(channelAPI.channelId); + } + } await conversation.destroyMessages(); const deviceIds = await textsecure.storage.protocol.getDeviceIds(id); await Promise.all( @@ -288,14 +228,6 @@ async load() { window.log.info('ConversationController: starting initial fetch'); - // We setup online and offline listeners here because we want - // to minimize the amount of listeners we have to avoid memory leaks - if (!this.p2pListenersSet) { - lokiP2pAPI.on('online', this._handleOnline.bind(this)); - lokiP2pAPI.on('offline', this._handleOffline.bind(this)); - this.p2pListenersSet = true; - } - if (conversations.length) { throw new Error('ConversationController: Already loaded!'); } @@ -311,9 +243,12 @@ this._initialFetchComplete = true; const promises = []; conversations.forEach(conversation => { + if (!conversation.get('lastMessage')) { + promises.push(conversation.updateLastMessage()); + } + promises.concat([ - conversation.updateLastMessage(), - conversation.updateProfile(), + conversation.updateProfileName(), conversation.updateProfileAvatar(), conversation.resetPendingSend(), conversation.setFriendRequestExpiryTimeout(), @@ -340,13 +275,13 @@ return this._initialPromise; }, - _handleOnline(pubKey) { + _handleOnline: pubKey => { try { const conversation = this.get(pubKey); conversation.set({ isOnline: true }); } catch (e) {} // eslint-disable-line }, - _handleOffline(pubKey) { + _handleOffline: pubKey => { try { const conversation = this.get(pubKey); conversation.set({ isOnline: false }); diff --git a/js/curve/curve25519_compiled.js b/js/curve/curve25519_compiled.js new file mode 100644 index 0000000000..b6e8348cc7 --- /dev/null +++ b/js/curve/curve25519_compiled.js @@ -0,0 +1,73849 @@ +// The Module object: Our interface to the outside world. We import +// and export values on it, and do the work to get that through +// closure compiler if necessary. There are various ways Module can be used: +// 1. Not defined. We create it here +// 2. A function parameter, function(Module) { ..generated code.. } +// 3. pre-run appended it, var Module = {}; ..generated code.. +// 4. External script tag defines var Module. +// We need to do an eval in order to handle the closure compiler +// case, where this code here is minified but Module was defined +// elsewhere (e.g. case 4 above). We also need to check if Module +// already exists (e.g. case 3 above). +// Note that if you want to run closure, and also to use Module +// after the generated code, you will need to define var Module = {}; +// before the code. Then that object will be used in the code, and you +// can continue to use Module afterwards as well. +var Module; +if (!Module) Module = (typeof Module !== 'undefined' ? Module : null) || {}; + +// Sometimes an existing Module object exists with properties +// meant to overwrite the default module functionality. Here +// we collect those properties and reapply _after_ we configure +// the current environment's defaults to avoid having to be so +// defensive during initialization. +var moduleOverrides = {}; +for (var key in Module) { + if (Module.hasOwnProperty(key)) { + moduleOverrides[key] = Module[key]; + } +} + +// The environment setup code below is customized to use Module. +// *** Environment setup code *** +var ENVIRONMENT_IS_NODE = + typeof process === 'object' && typeof require === 'function'; +var ENVIRONMENT_IS_WEB = typeof window === 'object'; +var ENVIRONMENT_IS_WORKER = typeof importScripts === 'function'; +var ENVIRONMENT_IS_SHELL = + !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER; + +if (ENVIRONMENT_IS_NODE) { + // Expose functionality in the same simple way that the shells work + // Note that we pollute the global namespace here, otherwise we break in node + if (!Module['print']) + Module['print'] = function print(x) { + process['stdout'].write(x + '\n'); + }; + if (!Module['printErr']) + Module['printErr'] = function printErr(x) { + process['stderr'].write(x + '\n'); + }; + + var nodeFS = require('fs'); + var nodePath = require('path'); + + Module['read'] = function read(filename, binary) { + filename = nodePath['normalize'](filename); + var ret = nodeFS['readFileSync'](filename); + // The path is absolute if the normalized version is the same as the resolved. + if (!ret && filename != nodePath['resolve'](filename)) { + filename = path.join(__dirname, '..', 'src', filename); + ret = nodeFS['readFileSync'](filename); + } + if (ret && !binary) ret = ret.toString(); + return ret; + }; + + Module['readBinary'] = function readBinary(filename) { + return Module['read'](filename, true); + }; + + Module['load'] = function load(f) { + globalEval(read(f)); + }; + + if (process['argv'].length > 1) { + Module['thisProgram'] = process['argv'][1].replace(/\\/g, '/'); + } else { + Module['thisProgram'] = 'unknown-program'; + } + + Module['arguments'] = process['argv'].slice(2); + + if (typeof module !== 'undefined') { + module['exports'] = Module; + } + + process['on']('uncaughtException', function(ex) { + // suppress ExitStatus exceptions from showing an error + if (!(ex instanceof ExitStatus)) { + throw ex; + } + }); +} else if (ENVIRONMENT_IS_SHELL) { + if (!Module['print']) Module['print'] = print; + if (typeof printErr != 'undefined') Module['printErr'] = printErr; // not present in v8 or older sm + + if (typeof read != 'undefined') { + Module['read'] = read; + } else { + Module['read'] = function read() { + throw 'no read() available (jsc?)'; + }; + } + + Module['readBinary'] = function readBinary(f) { + if (typeof readbuffer === 'function') { + return new Uint8Array(readbuffer(f)); + } + var data = read(f, 'binary'); + assert(typeof data === 'object'); + return data; + }; + + if (typeof scriptArgs != 'undefined') { + Module['arguments'] = scriptArgs; + } else if (typeof arguments != 'undefined') { + Module['arguments'] = arguments; + } + + this['Module'] = Module; +} else if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { + Module['read'] = function read(url) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, false); + xhr.send(null); + return xhr.responseText; + }; + + if (typeof arguments != 'undefined') { + Module['arguments'] = arguments; + } + + if (typeof console !== 'undefined') { + if (!Module['print']) + Module['print'] = function print(x) { + console.log(x); + }; + if (!Module['printErr']) + Module['printErr'] = function printErr(x) { + console.log(x); + }; + } else { + // Probably a worker, and without console.log. We can do very little here... + var TRY_USE_DUMP = false; + if (!Module['print']) + Module['print'] = + TRY_USE_DUMP && typeof dump !== 'undefined' + ? function(x) { + dump(x); + } + : function(x) { + // self.postMessage(x); // enable this if you want stdout to be sent as messages + }; + } + + if (ENVIRONMENT_IS_WEB) { + window['Module'] = Module; + } else { + Module['load'] = importScripts; + } +} else { + // Unreachable because SHELL is dependant on the others + throw 'Unknown runtime environment. Where are we?'; +} + +function globalEval(x) { + eval.call(null, x); +} +if (!Module['load'] && Module['read']) { + Module['load'] = function load(f) { + globalEval(Module['read'](f)); + }; +} +if (!Module['print']) { + Module['print'] = function() {}; +} +if (!Module['printErr']) { + Module['printErr'] = Module['print']; +} +if (!Module['arguments']) { + Module['arguments'] = []; +} +if (!Module['thisProgram']) { + Module['thisProgram'] = './this.program'; +} + +// *** Environment setup code *** + +// Closure helpers +Module.print = Module['print']; +Module.printErr = Module['printErr']; + +// Callbacks +Module['preRun'] = []; +Module['postRun'] = []; + +// Merge back in the overrides +for (var key in moduleOverrides) { + if (moduleOverrides.hasOwnProperty(key)) { + Module[key] = moduleOverrides[key]; + } +} + +// === Preamble library stuff === + +// Documentation for the public APIs defined in this file must be updated in: +// site/source/docs/api_reference/preamble.js.rst +// A prebuilt local version of the documentation is available at: +// site/build/text/docs/api_reference/preamble.js.txt +// You can also build docs locally as HTML or other formats in site/ +// An online HTML version (which may be of a different version of Emscripten) +// is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html + +//======================================== +// Runtime code shared with compiler +//======================================== + +var Runtime = { + setTempRet0: function(value) { + tempRet0 = value; + }, + getTempRet0: function() { + return tempRet0; + }, + stackSave: function() { + return STACKTOP; + }, + stackRestore: function(stackTop) { + STACKTOP = stackTop; + }, + getNativeTypeSize: function(type) { + switch (type) { + case 'i1': + case 'i8': + return 1; + case 'i16': + return 2; + case 'i32': + return 4; + case 'i64': + return 8; + case 'float': + return 4; + case 'double': + return 8; + default: { + if (type[type.length - 1] === '*') { + return Runtime.QUANTUM_SIZE; // A pointer + } else if (type[0] === 'i') { + var bits = parseInt(type.substr(1)); + assert(bits % 8 === 0); + return bits / 8; + } else { + return 0; + } + } + } + }, + getNativeFieldSize: function(type) { + return Math.max(Runtime.getNativeTypeSize(type), Runtime.QUANTUM_SIZE); + }, + STACK_ALIGN: 16, + getAlignSize: function(type, size, vararg) { + // we align i64s and doubles on 64-bit boundaries, unlike x86 + if (!vararg && (type == 'i64' || type == 'double')) return 8; + if (!type) return Math.min(size, 8); // align structures internally to 64 bits + return Math.min( + size || (type ? Runtime.getNativeFieldSize(type) : 0), + Runtime.QUANTUM_SIZE + ); + }, + dynCall: function(sig, ptr, args) { + if (args && args.length) { + if (!args.splice) args = Array.prototype.slice.call(args); + args.splice(0, 0, ptr); + return Module['dynCall_' + sig].apply(null, args); + } else { + return Module['dynCall_' + sig].call(null, ptr); + } + }, + functionPointers: [], + addFunction: function(func) { + for (var i = 0; i < Runtime.functionPointers.length; i++) { + if (!Runtime.functionPointers[i]) { + Runtime.functionPointers[i] = func; + return 2 * (1 + i); + } + } + throw 'Finished up all reserved function pointers. Use a higher value for RESERVED_FUNCTION_POINTERS.'; + }, + removeFunction: function(index) { + Runtime.functionPointers[(index - 2) / 2] = null; + }, + getAsmConst: function(code, numArgs) { + // code is a constant string on the heap, so we can cache these + if (!Runtime.asmConstCache) Runtime.asmConstCache = {}; + var func = Runtime.asmConstCache[code]; + if (func) return func; + var args = []; + for (var i = 0; i < numArgs; i++) { + args.push(String.fromCharCode(36) + i); // $0, $1 etc + } + var source = Pointer_stringify(code); + if (source[0] === '"') { + // tolerate EM_ASM("..code..") even though EM_ASM(..code..) is correct + if (source.indexOf('"', 1) === source.length - 1) { + source = source.substr(1, source.length - 2); + } else { + // something invalid happened, e.g. EM_ASM("..code($0)..", input) + abort( + 'invalid EM_ASM input |' + + source + + '|. Please use EM_ASM(..code..) (no quotes) or EM_ASM({ ..code($0).. }, input) (to input values)' + ); + } + } + try { + // Module is the only 'upvar', which we provide directly. We also provide FS for legacy support. + var evalled = eval( + '(function(Module, FS) { return function(' + + args.join(',') + + '){ ' + + source + + ' } })' + )(Module, typeof FS !== 'undefined' ? FS : null); + } catch (e) { + Module.printErr( + 'error in executing inline EM_ASM code: ' + + e + + ' on: \n\n' + + source + + '\n\nwith args |' + + args + + '| (make sure to use the right one out of EM_ASM, EM_ASM_ARGS, etc.)' + ); + throw e; + } + return (Runtime.asmConstCache[code] = evalled); + }, + warnOnce: function(text) { + if (!Runtime.warnOnce.shown) Runtime.warnOnce.shown = {}; + if (!Runtime.warnOnce.shown[text]) { + Runtime.warnOnce.shown[text] = 1; + Module.printErr(text); + } + }, + funcWrappers: {}, + getFuncWrapper: function(func, sig) { + assert(sig); + if (!Runtime.funcWrappers[sig]) { + Runtime.funcWrappers[sig] = {}; + } + var sigCache = Runtime.funcWrappers[sig]; + if (!sigCache[func]) { + sigCache[func] = function dynCall_wrapper() { + return Runtime.dynCall(sig, func, arguments); + }; + } + return sigCache[func]; + }, + UTF8Processor: function() { + var buffer = []; + var needed = 0; + this.processCChar = function(code) { + code = code & 0xff; + + if (buffer.length == 0) { + if ((code & 0x80) == 0x00) { + // 0xxxxxxx + return String.fromCharCode(code); + } + buffer.push(code); + if ((code & 0xe0) == 0xc0) { + // 110xxxxx + needed = 1; + } else if ((code & 0xf0) == 0xe0) { + // 1110xxxx + needed = 2; + } else { + // 11110xxx + needed = 3; + } + return ''; + } + + if (needed) { + buffer.push(code); + needed--; + if (needed > 0) return ''; + } + + var c1 = buffer[0]; + var c2 = buffer[1]; + var c3 = buffer[2]; + var c4 = buffer[3]; + var ret; + if (buffer.length == 2) { + ret = String.fromCharCode(((c1 & 0x1f) << 6) | (c2 & 0x3f)); + } else if (buffer.length == 3) { + ret = String.fromCharCode( + ((c1 & 0x0f) << 12) | ((c2 & 0x3f) << 6) | (c3 & 0x3f) + ); + } else { + // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + var codePoint = + ((c1 & 0x07) << 18) | + ((c2 & 0x3f) << 12) | + ((c3 & 0x3f) << 6) | + (c4 & 0x3f); + ret = String.fromCharCode( + (((codePoint - 0x10000) / 0x400) | 0) + 0xd800, + (codePoint - 0x10000) % 0x400 + 0xdc00 + ); + } + buffer.length = 0; + return ret; + }; + this.processJSString = function processJSString(string) { + /* TODO: use TextEncoder when present, + var encoder = new TextEncoder(); + encoder['encoding'] = "utf-8"; + var utf8Array = encoder['encode'](aMsg.data); + */ + string = unescape(encodeURIComponent(string)); + var ret = []; + for (var i = 0; i < string.length; i++) { + ret.push(string.charCodeAt(i)); + } + return ret; + }; + }, + getCompilerSetting: function(name) { + throw 'You must build with -s RETAIN_COMPILER_SETTINGS=1 for Runtime.getCompilerSetting or emscripten_get_compiler_setting to work'; + }, + stackAlloc: function(size) { + var ret = STACKTOP; + STACKTOP = (STACKTOP + size) | 0; + STACKTOP = (STACKTOP + 15) & -16; + return ret; + }, + staticAlloc: function(size) { + var ret = STATICTOP; + STATICTOP = (STATICTOP + size) | 0; + STATICTOP = (STATICTOP + 15) & -16; + return ret; + }, + dynamicAlloc: function(size) { + var ret = DYNAMICTOP; + DYNAMICTOP = (DYNAMICTOP + size) | 0; + DYNAMICTOP = (DYNAMICTOP + 15) & -16; + if (DYNAMICTOP >= TOTAL_MEMORY) enlargeMemory(); + return ret; + }, + alignMemory: function(size, quantum) { + var ret = (size = + Math.ceil(size / (quantum ? quantum : 16)) * (quantum ? quantum : 16)); + return ret; + }, + makeBigInt: function(low, high, unsigned) { + var ret = unsigned + ? +(low >>> 0) + +(high >>> 0) * 4294967296.0 + : +(low >>> 0) + +(high | 0) * 4294967296.0; + return ret; + }, + GLOBAL_BASE: 8, + QUANTUM_SIZE: 4, + __dummy__: 0, +}; + +Module['Runtime'] = Runtime; + +//======================================== +// Runtime essentials +//======================================== + +var __THREW__ = 0; // Used in checking for thrown exceptions. + +var ABORT = false; // whether we are quitting the application. no code should run after this. set in exit() and abort() +var EXITSTATUS = 0; + +var undef = 0; +// tempInt is used for 32-bit signed values or smaller. tempBigInt is used +// for 32-bit unsigned values or more than 32 bits. TODO: audit all uses of tempInt +var tempValue, + tempInt, + tempBigInt, + tempInt2, + tempBigInt2, + tempPair, + tempBigIntI, + tempBigIntR, + tempBigIntS, + tempBigIntP, + tempBigIntD, + tempDouble, + tempFloat; +var tempI64, tempI64b; +var tempRet0, + tempRet1, + tempRet2, + tempRet3, + tempRet4, + tempRet5, + tempRet6, + tempRet7, + tempRet8, + tempRet9; + +function assert(condition, text) { + if (!condition) { + abort('Assertion failed: ' + text); + } +} + +var globalScope = this; + +// Returns the C function with a specified identifier (for C++, you need to do manual name mangling) +function getCFunc(ident) { + var func = Module['_' + ident]; // closure exported function + if (!func) { + try { + func = eval('_' + ident); // explicit lookup + } catch (e) {} + } + assert( + func, + 'Cannot call unknown function ' + + ident + + ' (perhaps LLVM optimizations or closure removed it?)' + ); + return func; +} + +var cwrap, ccall; +(function() { + var JSfuncs = { + // Helpers for cwrap -- it can't refer to Runtime directly because it might + // be renamed by closure, instead it calls JSfuncs['stackSave'].body to find + // out what the minified function name is. + stackSave: function() { + Runtime.stackSave(); + }, + stackRestore: function() { + Runtime.stackRestore(); + }, + // type conversion from js to c + arrayToC: function(arr) { + var ret = Runtime.stackAlloc(arr.length); + writeArrayToMemory(arr, ret); + return ret; + }, + stringToC: function(str) { + var ret = 0; + if (str !== null && str !== undefined && str !== 0) { + // null string + // at most 4 bytes per UTF-8 code point, +1 for the trailing '\0' + ret = Runtime.stackAlloc((str.length << 2) + 1); + writeStringToMemory(str, ret); + } + return ret; + }, + }; + // For fast lookup of conversion functions + var toC = { string: JSfuncs['stringToC'], array: JSfuncs['arrayToC'] }; + + // C calling interface. + ccall = function ccallFunc(ident, returnType, argTypes, args) { + var func = getCFunc(ident); + var cArgs = []; + var stack = 0; + if (args) { + for (var i = 0; i < args.length; i++) { + var converter = toC[argTypes[i]]; + if (converter) { + if (stack === 0) stack = Runtime.stackSave(); + cArgs[i] = converter(args[i]); + } else { + cArgs[i] = args[i]; + } + } + } + var ret = func.apply(null, cArgs); + if (returnType === 'string') ret = Pointer_stringify(ret); + if (stack !== 0) Runtime.stackRestore(stack); + return ret; + }; + + var sourceRegex = /^function\s*\(([^)]*)\)\s*{\s*([^*]*?)[\s;]*(?:return\s*(.*?)[;\s]*)?}$/; + function parseJSFunc(jsfunc) { + // Match the body and the return value of a javascript function source + var parsed = jsfunc + .toString() + .match(sourceRegex) + .slice(1); + return { arguments: parsed[0], body: parsed[1], returnValue: parsed[2] }; + } + var JSsource = {}; + for (var fun in JSfuncs) { + if (JSfuncs.hasOwnProperty(fun)) { + // Elements of toCsource are arrays of three items: + // the code, and the return value + JSsource[fun] = parseJSFunc(JSfuncs[fun]); + } + } + + cwrap = function cwrap(ident, returnType, argTypes) { + argTypes = argTypes || []; + var cfunc = getCFunc(ident); + // When the function takes numbers and returns a number, we can just return + // the original function + var numericArgs = argTypes.every(function(type) { + return type === 'number'; + }); + var numericRet = returnType !== 'string'; + if (numericRet && numericArgs) { + return cfunc; + } + // Creation of the arguments list (["$1","$2",...,"$nargs"]) + var argNames = argTypes.map(function(x, i) { + return '$' + i; + }); + var funcstr = '(function(' + argNames.join(',') + ') {'; + var nargs = argTypes.length; + if (!numericArgs) { + // Generate the code needed to convert the arguments from javascript + // values to pointers + funcstr += 'var stack = ' + JSsource['stackSave'].body + ';'; + for (var i = 0; i < nargs; i++) { + var arg = argNames[i], + type = argTypes[i]; + if (type === 'number') continue; + var convertCode = JSsource[type + 'ToC']; // [code, return] + funcstr += 'var ' + convertCode.arguments + ' = ' + arg + ';'; + funcstr += convertCode.body + ';'; + funcstr += arg + '=' + convertCode.returnValue + ';'; + } + } + + // When the code is compressed, the name of cfunc is not literally 'cfunc' anymore + var cfuncname = parseJSFunc(function() { + return cfunc; + }).returnValue; + // Call the function + funcstr += 'var ret = ' + cfuncname + '(' + argNames.join(',') + ');'; + if (!numericRet) { + // Return type can only by 'string' or 'number' + // Convert the result to a string + var strgfy = parseJSFunc(function() { + return Pointer_stringify; + }).returnValue; + funcstr += 'ret = ' + strgfy + '(ret);'; + } + if (!numericArgs) { + // If we had a stack, restore it + funcstr += JSsource['stackRestore'].body.replace('()', '(stack)') + ';'; + } + funcstr += 'return ret})'; + return eval(funcstr); + }; +})(); +Module['cwrap'] = cwrap; +Module['ccall'] = ccall; + +function setValue(ptr, value, type, noSafe) { + type = type || 'i8'; + if (type.charAt(type.length - 1) === '*') type = 'i32'; // pointers are 32-bit + switch (type) { + case 'i1': + HEAP8[ptr >> 0] = value; + break; + case 'i8': + HEAP8[ptr >> 0] = value; + break; + case 'i16': + HEAP16[ptr >> 1] = value; + break; + case 'i32': + HEAP32[ptr >> 2] = value; + break; + case 'i64': + (tempI64 = [ + value >>> 0, + ((tempDouble = value), + +Math_abs(tempDouble) >= 1.0 + ? tempDouble > 0.0 + ? (Math_min(+Math_floor(tempDouble / 4294967296.0), 4294967295.0) | + 0) >>> + 0 + : ~~+Math_ceil( + (tempDouble - +(~~tempDouble >>> 0)) / 4294967296.0 + ) >>> 0 + : 0), + ]), + (HEAP32[ptr >> 2] = tempI64[0]), + (HEAP32[(ptr + 4) >> 2] = tempI64[1]); + break; + case 'float': + HEAPF32[ptr >> 2] = value; + break; + case 'double': + HEAPF64[ptr >> 3] = value; + break; + default: + abort('invalid type for setValue: ' + type); + } +} +Module['setValue'] = setValue; + +function getValue(ptr, type, noSafe) { + type = type || 'i8'; + if (type.charAt(type.length - 1) === '*') type = 'i32'; // pointers are 32-bit + switch (type) { + case 'i1': + return HEAP8[ptr >> 0]; + case 'i8': + return HEAP8[ptr >> 0]; + case 'i16': + return HEAP16[ptr >> 1]; + case 'i32': + return HEAP32[ptr >> 2]; + case 'i64': + return HEAP32[ptr >> 2]; + case 'float': + return HEAPF32[ptr >> 2]; + case 'double': + return HEAPF64[ptr >> 3]; + default: + abort('invalid type for setValue: ' + type); + } + return null; +} +Module['getValue'] = getValue; + +var ALLOC_NORMAL = 0; // Tries to use _malloc() +var ALLOC_STACK = 1; // Lives for the duration of the current function call +var ALLOC_STATIC = 2; // Cannot be freed +var ALLOC_DYNAMIC = 3; // Cannot be freed except through sbrk +var ALLOC_NONE = 4; // Do not allocate +Module['ALLOC_NORMAL'] = ALLOC_NORMAL; +Module['ALLOC_STACK'] = ALLOC_STACK; +Module['ALLOC_STATIC'] = ALLOC_STATIC; +Module['ALLOC_DYNAMIC'] = ALLOC_DYNAMIC; +Module['ALLOC_NONE'] = ALLOC_NONE; + +// allocate(): This is for internal use. You can use it yourself as well, but the interface +// is a little tricky (see docs right below). The reason is that it is optimized +// for multiple syntaxes to save space in generated code. So you should +// normally not use allocate(), and instead allocate memory using _malloc(), +// initialize it with setValue(), and so forth. +// @slab: An array of data, or a number. If a number, then the size of the block to allocate, +// in *bytes* (note that this is sometimes confusing: the next parameter does not +// affect this!) +// @types: Either an array of types, one for each byte (or 0 if no type at that position), +// or a single type which is used for the entire block. This only matters if there +// is initial data - if @slab is a number, then this does not matter at all and is +// ignored. +// @allocator: How to allocate memory, see ALLOC_* +function allocate(slab, types, allocator, ptr) { + var zeroinit, size; + if (typeof slab === 'number') { + zeroinit = true; + size = slab; + } else { + zeroinit = false; + size = slab.length; + } + + var singleType = typeof types === 'string' ? types : null; + + var ret; + if (allocator == ALLOC_NONE) { + ret = ptr; + } else { + ret = [ + _malloc, + Runtime.stackAlloc, + Runtime.staticAlloc, + Runtime.dynamicAlloc, + ][allocator === undefined ? ALLOC_STATIC : allocator]( + Math.max(size, singleType ? 1 : types.length) + ); + } + + if (zeroinit) { + var ptr = ret, + stop; + assert((ret & 3) == 0); + stop = ret + (size & ~3); + for (; ptr < stop; ptr += 4) { + HEAP32[ptr >> 2] = 0; + } + stop = ret + size; + while (ptr < stop) { + HEAP8[ptr++ >> 0] = 0; + } + return ret; + } + + if (singleType === 'i8') { + if (slab.subarray || slab.slice) { + HEAPU8.set(slab, ret); + } else { + HEAPU8.set(new Uint8Array(slab), ret); + } + return ret; + } + + var i = 0, + type, + typeSize, + previousType; + while (i < size) { + var curr = slab[i]; + + if (typeof curr === 'function') { + curr = Runtime.getFunctionIndex(curr); + } + + type = singleType || types[i]; + if (type === 0) { + i++; + continue; + } + + if (type == 'i64') type = 'i32'; // special case: we have one i32 here, and one i32 later + + setValue(ret + i, curr, type); + + // no need to look up size unless type changes, so cache it + if (previousType !== type) { + typeSize = Runtime.getNativeTypeSize(type); + previousType = type; + } + i += typeSize; + } + + return ret; +} +Module['allocate'] = allocate; + +function Pointer_stringify(ptr, /* optional */ length) { + if (length === 0 || !ptr) return ''; + // TODO: use TextDecoder + // Find the length, and check for UTF while doing so + var hasUtf = false; + var t; + var i = 0; + while (1) { + t = HEAPU8[(ptr + i) >> 0]; + if (t >= 128) hasUtf = true; + else if (t == 0 && !length) break; + i++; + if (length && i == length) break; + } + if (!length) length = i; + + var ret = ''; + + if (!hasUtf) { + var MAX_CHUNK = 1024; // split up into chunks, because .apply on a huge string can overflow the stack + var curr; + while (length > 0) { + curr = String.fromCharCode.apply( + String, + HEAPU8.subarray(ptr, ptr + Math.min(length, MAX_CHUNK)) + ); + ret = ret ? ret + curr : curr; + ptr += MAX_CHUNK; + length -= MAX_CHUNK; + } + return ret; + } + + var utf8 = new Runtime.UTF8Processor(); + for (i = 0; i < length; i++) { + t = HEAPU8[(ptr + i) >> 0]; + ret += utf8.processCChar(t); + } + return ret; +} +Module['Pointer_stringify'] = Pointer_stringify; + +function UTF16ToString(ptr) { + var i = 0; + + var str = ''; + while (1) { + var codeUnit = HEAP16[(ptr + i * 2) >> 1]; + if (codeUnit == 0) return str; + ++i; + // fromCharCode constructs a character from a UTF-16 code unit, so we can pass the UTF16 string right through. + str += String.fromCharCode(codeUnit); + } +} +Module['UTF16ToString'] = UTF16ToString; + +function stringToUTF16(str, outPtr) { + for (var i = 0; i < str.length; ++i) { + // charCodeAt returns a UTF-16 encoded code unit, so it can be directly written to the HEAP. + var codeUnit = str.charCodeAt(i); // possibly a lead surrogate + HEAP16[(outPtr + i * 2) >> 1] = codeUnit; + } + // Null-terminate the pointer to the HEAP. + HEAP16[(outPtr + str.length * 2) >> 1] = 0; +} +Module['stringToUTF16'] = stringToUTF16; + +function UTF32ToString(ptr) { + var i = 0; + + var str = ''; + while (1) { + var utf32 = HEAP32[(ptr + i * 4) >> 2]; + if (utf32 == 0) return str; + ++i; + // Gotcha: fromCharCode constructs a character from a UTF-16 encoded code (pair), not from a Unicode code point! So encode the code point to UTF-16 for constructing. + if (utf32 >= 0x10000) { + var ch = utf32 - 0x10000; + str += String.fromCharCode(0xd800 | (ch >> 10), 0xdc00 | (ch & 0x3ff)); + } else { + str += String.fromCharCode(utf32); + } + } +} +Module['UTF32ToString'] = UTF32ToString; + +function stringToUTF32(str, outPtr) { + var iChar = 0; + for (var iCodeUnit = 0; iCodeUnit < str.length; ++iCodeUnit) { + // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code unit, not a Unicode code point of the character! We must decode the string to UTF-32 to the heap. + var codeUnit = str.charCodeAt(iCodeUnit); // possibly a lead surrogate + if (codeUnit >= 0xd800 && codeUnit <= 0xdfff) { + var trailSurrogate = str.charCodeAt(++iCodeUnit); + codeUnit = + (0x10000 + ((codeUnit & 0x3ff) << 10)) | (trailSurrogate & 0x3ff); + } + HEAP32[(outPtr + iChar * 4) >> 2] = codeUnit; + ++iChar; + } + // Null-terminate the pointer to the HEAP. + HEAP32[(outPtr + iChar * 4) >> 2] = 0; +} +Module['stringToUTF32'] = stringToUTF32; + +function demangle(func) { + var hasLibcxxabi = !!Module['___cxa_demangle']; + if (hasLibcxxabi) { + try { + var buf = _malloc(func.length); + writeStringToMemory(func.substr(1), buf); + var status = _malloc(4); + var ret = Module['___cxa_demangle'](buf, 0, 0, status); + if (getValue(status, 'i32') === 0 && ret) { + return Pointer_stringify(ret); + } + // otherwise, libcxxabi failed, we can try ours which may return a partial result + } catch (e) { + // failure when using libcxxabi, we can try ours which may return a partial result + } finally { + if (buf) _free(buf); + if (status) _free(status); + if (ret) _free(ret); + } + } + var i = 3; + // params, etc. + var basicTypes = { + v: 'void', + b: 'bool', + c: 'char', + s: 'short', + i: 'int', + l: 'long', + f: 'float', + d: 'double', + w: 'wchar_t', + a: 'signed char', + h: 'unsigned char', + t: 'unsigned short', + j: 'unsigned int', + m: 'unsigned long', + x: 'long long', + y: 'unsigned long long', + z: '...', + }; + var subs = []; + var first = true; + function dump(x) { + //return; + if (x) Module.print(x); + Module.print(func); + var pre = ''; + for (var a = 0; a < i; a++) pre += ' '; + Module.print(pre + '^'); + } + function parseNested() { + i++; + if (func[i] === 'K') i++; // ignore const + var parts = []; + while (func[i] !== 'E') { + if (func[i] === 'S') { + // substitution + i++; + var next = func.indexOf('_', i); + var num = func.substring(i, next) || 0; + parts.push(subs[num] || '?'); + i = next + 1; + continue; + } + if (func[i] === 'C') { + // constructor + parts.push(parts[parts.length - 1]); + i += 2; + continue; + } + var size = parseInt(func.substr(i)); + var pre = size.toString().length; + if (!size || !pre) { + i--; + break; + } // counter i++ below us + var curr = func.substr(i + pre, size); + parts.push(curr); + subs.push(curr); + i += pre + size; + } + i++; // skip E + return parts; + } + function parse(rawList, limit, allowVoid) { + // main parser + limit = limit || Infinity; + var ret = '', + list = []; + function flushList() { + return '(' + list.join(', ') + ')'; + } + var name; + if (func[i] === 'N') { + // namespaced N-E + name = parseNested().join('::'); + limit--; + if (limit === 0) return rawList ? [name] : name; + } else { + // not namespaced + if (func[i] === 'K' || (first && func[i] === 'L')) i++; // ignore const and first 'L' + var size = parseInt(func.substr(i)); + if (size) { + var pre = size.toString().length; + name = func.substr(i + pre, size); + i += pre + size; + } + } + first = false; + if (func[i] === 'I') { + i++; + var iList = parse(true); + var iRet = parse(true, 1, true); + ret += iRet[0] + ' ' + name + '<' + iList.join(', ') + '>'; + } else { + ret = name; + } + paramLoop: while (i < func.length && limit-- > 0) { + //dump('paramLoop'); + var c = func[i++]; + if (c in basicTypes) { + list.push(basicTypes[c]); + } else { + switch (c) { + case 'P': + list.push(parse(true, 1, true)[0] + '*'); + break; // pointer + case 'R': + list.push(parse(true, 1, true)[0] + '&'); + break; // reference + case 'L': { + // literal + i++; // skip basic type + var end = func.indexOf('E', i); + var size = end - i; + list.push(func.substr(i, size)); + i += size + 2; // size + 'EE' + break; + } + case 'A': { + // array + var size = parseInt(func.substr(i)); + i += size.toString().length; + if (func[i] !== '_') throw '?'; + i++; // skip _ + list.push(parse(true, 1, true)[0] + ' [' + size + ']'); + break; + } + case 'E': + break paramLoop; + default: + ret += '?' + c; + break paramLoop; + } + } + } + if (!allowVoid && list.length === 1 && list[0] === 'void') list = []; // avoid (void) + if (rawList) { + if (ret) { + list.push(ret + '?'); + } + return list; + } else { + return ret + flushList(); + } + } + var parsed = func; + try { + // Special-case the entry point, since its name differs from other name mangling. + if (func == 'Object._main' || func == '_main') { + return 'main()'; + } + if (typeof func === 'number') func = Pointer_stringify(func); + if (func[0] !== '_') return func; + if (func[1] !== '_') return func; // C function + if (func[2] !== 'Z') return func; + switch (func[3]) { + case 'n': + return 'operator new()'; + case 'd': + return 'operator delete()'; + } + parsed = parse(); + } catch (e) { + parsed += '?'; + } + if (parsed.indexOf('?') >= 0 && !hasLibcxxabi) { + Runtime.warnOnce( + 'warning: a problem occurred in builtin C++ name demangling; build with -s DEMANGLE_SUPPORT=1 to link in libcxxabi demangling' + ); + } + return parsed; +} + +function demangleAll(text) { + return text.replace(/__Z[\w\d_]+/g, function(x) { + var y = demangle(x); + return x === y ? x : x + ' [' + y + ']'; + }); +} + +function jsStackTrace() { + var err = new Error(); + if (!err.stack) { + // IE10+ special cases: It does have callstack info, but it is only populated if an Error object is thrown, + // so try that as a special-case. + try { + throw new Error(0); + } catch (e) { + err = e; + } + if (!err.stack) { + return '(no stack trace available)'; + } + } + return err.stack.toString(); +} + +function stackTrace() { + return demangleAll(jsStackTrace()); +} +Module['stackTrace'] = stackTrace; + +// Memory management + +var PAGE_SIZE = 4096; +function alignMemoryPage(x) { + return (x + 4095) & -4096; +} + +var HEAP; +var HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAPF64; + +var STATIC_BASE = 0, + STATICTOP = 0, + staticSealed = false; // static area +var STACK_BASE = 0, + STACKTOP = 0, + STACK_MAX = 0; // stack area +var DYNAMIC_BASE = 0, + DYNAMICTOP = 0; // dynamic area handled by sbrk + +function enlargeMemory() { + abort( + 'Cannot enlarge memory arrays. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value ' + + TOTAL_MEMORY + + ', (2) compile with ALLOW_MEMORY_GROWTH which adjusts the size at runtime but prevents some optimizations, or (3) set Module.TOTAL_MEMORY before the program runs.' + ); +} + +var TOTAL_STACK = Module['TOTAL_STACK'] || 5242880; +var TOTAL_MEMORY = Module['TOTAL_MEMORY'] || 16777216; +var FAST_MEMORY = Module['FAST_MEMORY'] || 2097152; + +var totalMemory = 64 * 1024; +while (totalMemory < TOTAL_MEMORY || totalMemory < 2 * TOTAL_STACK) { + if (totalMemory < 16 * 1024 * 1024) { + totalMemory *= 2; + } else { + totalMemory += 16 * 1024 * 1024; + } +} +if (totalMemory !== TOTAL_MEMORY) { + Module.printErr( + 'increasing TOTAL_MEMORY to ' + + totalMemory + + ' to be compliant with the asm.js spec' + ); + TOTAL_MEMORY = totalMemory; +} + +// Initialize the runtime's memory +// check for full engine support (use string 'subarray' to avoid closure compiler confusion) +assert( + typeof Int32Array !== 'undefined' && + typeof Float64Array !== 'undefined' && + !!new Int32Array(1)['subarray'] && + !!new Int32Array(1)['set'], + 'JS engine does not provide full typed array support' +); + +var buffer = new ArrayBuffer(TOTAL_MEMORY); +HEAP8 = new Int8Array(buffer); +HEAP16 = new Int16Array(buffer); +HEAP32 = new Int32Array(buffer); +HEAPU8 = new Uint8Array(buffer); +HEAPU16 = new Uint16Array(buffer); +HEAPU32 = new Uint32Array(buffer); +HEAPF32 = new Float32Array(buffer); +HEAPF64 = new Float64Array(buffer); + +// Endianness check (note: assumes compiler arch was little-endian) +HEAP32[0] = 255; +assert( + HEAPU8[0] === 255 && HEAPU8[3] === 0, + 'Typed arrays 2 must be run on a little-endian system' +); + +Module['HEAP'] = HEAP; +Module['buffer'] = buffer; +Module['HEAP8'] = HEAP8; +Module['HEAP16'] = HEAP16; +Module['HEAP32'] = HEAP32; +Module['HEAPU8'] = HEAPU8; +Module['HEAPU16'] = HEAPU16; +Module['HEAPU32'] = HEAPU32; +Module['HEAPF32'] = HEAPF32; +Module['HEAPF64'] = HEAPF64; + +function callRuntimeCallbacks(callbacks) { + while (callbacks.length > 0) { + var callback = callbacks.shift(); + if (typeof callback == 'function') { + callback(); + continue; + } + var func = callback.func; + if (typeof func === 'number') { + if (callback.arg === undefined) { + Runtime.dynCall('v', func); + } else { + Runtime.dynCall('vi', func, [callback.arg]); + } + } else { + func(callback.arg === undefined ? null : callback.arg); + } + } +} + +var __ATPRERUN__ = []; // functions called before the runtime is initialized +var __ATINIT__ = []; // functions called during startup +var __ATMAIN__ = []; // functions called when main() is to be run +var __ATEXIT__ = []; // functions called during shutdown +var __ATPOSTRUN__ = []; // functions called after the runtime has exited + +var runtimeInitialized = false; +var runtimeExited = false; + +function preRun() { + // compatibility - merge in anything from Module['preRun'] at this time + if (Module['preRun']) { + if (typeof Module['preRun'] == 'function') + Module['preRun'] = [Module['preRun']]; + while (Module['preRun'].length) { + addOnPreRun(Module['preRun'].shift()); + } + } + callRuntimeCallbacks(__ATPRERUN__); +} + +function ensureInitRuntime() { + if (runtimeInitialized) return; + runtimeInitialized = true; + callRuntimeCallbacks(__ATINIT__); +} + +function preMain() { + callRuntimeCallbacks(__ATMAIN__); +} + +function exitRuntime() { + callRuntimeCallbacks(__ATEXIT__); + runtimeExited = true; +} + +function postRun() { + // compatibility - merge in anything from Module['postRun'] at this time + if (Module['postRun']) { + if (typeof Module['postRun'] == 'function') + Module['postRun'] = [Module['postRun']]; + while (Module['postRun'].length) { + addOnPostRun(Module['postRun'].shift()); + } + } + callRuntimeCallbacks(__ATPOSTRUN__); +} + +function addOnPreRun(cb) { + __ATPRERUN__.unshift(cb); +} +Module['addOnPreRun'] = Module.addOnPreRun = addOnPreRun; + +function addOnInit(cb) { + __ATINIT__.unshift(cb); +} +Module['addOnInit'] = Module.addOnInit = addOnInit; + +function addOnPreMain(cb) { + __ATMAIN__.unshift(cb); +} +Module['addOnPreMain'] = Module.addOnPreMain = addOnPreMain; + +function addOnExit(cb) { + __ATEXIT__.unshift(cb); +} +Module['addOnExit'] = Module.addOnExit = addOnExit; + +function addOnPostRun(cb) { + __ATPOSTRUN__.unshift(cb); +} +Module['addOnPostRun'] = Module.addOnPostRun = addOnPostRun; + +// Tools + +function intArrayFromString(stringy, dontAddNull, length /* optional */) { + var ret = new Runtime.UTF8Processor().processJSString(stringy); + if (length) { + ret.length = length; + } + if (!dontAddNull) { + ret.push(0); + } + return ret; +} +Module['intArrayFromString'] = intArrayFromString; + +function intArrayToString(array) { + var ret = []; + for (var i = 0; i < array.length; i++) { + var chr = array[i]; + if (chr > 0xff) { + chr &= 0xff; + } + ret.push(String.fromCharCode(chr)); + } + return ret.join(''); +} +Module['intArrayToString'] = intArrayToString; + +function writeStringToMemory(string, buffer, dontAddNull) { + var array = intArrayFromString(string, dontAddNull); + var i = 0; + while (i < array.length) { + var chr = array[i]; + HEAP8[(buffer + i) >> 0] = chr; + i = i + 1; + } +} +Module['writeStringToMemory'] = writeStringToMemory; + +function writeArrayToMemory(array, buffer) { + for (var i = 0; i < array.length; i++) { + HEAP8[(buffer + i) >> 0] = array[i]; + } +} +Module['writeArrayToMemory'] = writeArrayToMemory; + +function writeAsciiToMemory(str, buffer, dontAddNull) { + for (var i = 0; i < str.length; i++) { + HEAP8[(buffer + i) >> 0] = str.charCodeAt(i); + } + if (!dontAddNull) HEAP8[(buffer + str.length) >> 0] = 0; +} +Module['writeAsciiToMemory'] = writeAsciiToMemory; + +function unSign(value, bits, ignore) { + if (value >= 0) { + return value; + } + return bits <= 32 + ? 2 * Math.abs(1 << (bits - 1)) + value // Need some trickery, since if bits == 32, we are right at the limit of the bits JS uses in bitshifts + : Math.pow(2, bits) + value; +} +function reSign(value, bits, ignore) { + if (value <= 0) { + return value; + } + var half = + bits <= 32 + ? Math.abs(1 << (bits - 1)) // abs is needed if bits == 32 + : Math.pow(2, bits - 1); + if (value >= half && (bits <= 32 || value > half)) { + // for huge values, we can hit the precision limit and always get true here. so don't do that + // but, in general there is no perfect solution here. With 64-bit ints, we get rounding and errors + // TODO: In i64 mode 1, resign the two parts separately and safely + value = -2 * half + value; // Cannot bitshift half, as it may be at the limit of the bits JS uses in bitshifts + } + return value; +} + +// check for imul support, and also for correctness ( https://bugs.webkit.org/show_bug.cgi?id=126345 ) +if (!Math['imul'] || Math['imul'](0xffffffff, 5) !== -5) + Math['imul'] = function imul(a, b) { + var ah = a >>> 16; + var al = a & 0xffff; + var bh = b >>> 16; + var bl = b & 0xffff; + return (al * bl + ((ah * bl + al * bh) << 16)) | 0; + }; +Math.imul = Math['imul']; + +var Math_abs = Math.abs; +var Math_cos = Math.cos; +var Math_sin = Math.sin; +var Math_tan = Math.tan; +var Math_acos = Math.acos; +var Math_asin = Math.asin; +var Math_atan = Math.atan; +var Math_atan2 = Math.atan2; +var Math_exp = Math.exp; +var Math_log = Math.log; +var Math_sqrt = Math.sqrt; +var Math_ceil = Math.ceil; +var Math_floor = Math.floor; +var Math_pow = Math.pow; +var Math_imul = Math.imul; +var Math_fround = Math.fround; +var Math_min = Math.min; + +// A counter of dependencies for calling run(). If we need to +// do asynchronous work before running, increment this and +// decrement it. Incrementing must happen in a place like +// PRE_RUN_ADDITIONS (used by emcc to add file preloading). +// Note that you can add dependencies in preRun, even though +// it happens right before run - run will be postponed until +// the dependencies are met. +var runDependencies = 0; +var runDependencyWatcher = null; +var dependenciesFulfilled = null; // overridden to take different actions when all run dependencies are fulfilled + +function addRunDependency(id) { + runDependencies++; + if (Module['monitorRunDependencies']) { + Module['monitorRunDependencies'](runDependencies); + } +} +Module['addRunDependency'] = addRunDependency; +function removeRunDependency(id) { + runDependencies--; + if (Module['monitorRunDependencies']) { + Module['monitorRunDependencies'](runDependencies); + } + if (runDependencies == 0) { + if (runDependencyWatcher !== null) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + } + if (dependenciesFulfilled) { + var callback = dependenciesFulfilled; + dependenciesFulfilled = null; + callback(); // can add another dependenciesFulfilled + } + } +} +Module['removeRunDependency'] = removeRunDependency; + +Module['preloadedImages'] = {}; // maps url to image data +Module['preloadedAudios'] = {}; // maps url to audio data + +var memoryInitializer = null; + +// === Body === + +STATIC_BASE = 8; + +STATICTOP = STATIC_BASE + 33040; +/* global initializers */ __ATINIT__.push(); + +/* memory initializer */ allocate( + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 133, + 59, + 140, + 1, + 189, + 241, + 36, + 255, + 248, + 37, + 195, + 1, + 96, + 220, + 55, + 0, + 183, + 76, + 62, + 255, + 195, + 66, + 61, + 0, + 50, + 76, + 164, + 1, + 225, + 164, + 76, + 255, + 76, + 61, + 163, + 255, + 117, + 62, + 31, + 0, + 81, + 145, + 64, + 255, + 118, + 65, + 14, + 0, + 162, + 115, + 214, + 255, + 6, + 138, + 46, + 0, + 124, + 230, + 244, + 255, + 10, + 138, + 143, + 0, + 52, + 26, + 194, + 0, + 184, + 244, + 76, + 0, + 129, + 143, + 41, + 1, + 190, + 244, + 19, + 255, + 123, + 170, + 122, + 255, + 98, + 129, + 68, + 0, + 121, + 213, + 147, + 0, + 86, + 101, + 30, + 255, + 161, + 103, + 155, + 0, + 140, + 89, + 67, + 255, + 239, + 229, + 190, + 1, + 67, + 11, + 181, + 0, + 198, + 240, + 137, + 254, + 238, + 69, + 188, + 255, + 67, + 151, + 238, + 0, + 19, + 42, + 108, + 255, + 229, + 85, + 113, + 1, + 50, + 68, + 135, + 255, + 17, + 106, + 9, + 0, + 50, + 103, + 1, + 255, + 80, + 1, + 168, + 1, + 35, + 152, + 30, + 255, + 16, + 168, + 185, + 1, + 56, + 89, + 232, + 255, + 101, + 210, + 252, + 0, + 41, + 250, + 71, + 0, + 204, + 170, + 79, + 255, + 14, + 46, + 239, + 255, + 80, + 77, + 239, + 0, + 189, + 214, + 75, + 255, + 17, + 141, + 249, + 0, + 38, + 80, + 76, + 255, + 190, + 85, + 117, + 0, + 86, + 228, + 170, + 0, + 156, + 216, + 208, + 1, + 195, + 207, + 164, + 255, + 150, + 66, + 76, + 255, + 175, + 225, + 16, + 255, + 141, + 80, + 98, + 1, + 76, + 219, + 242, + 0, + 198, + 162, + 114, + 0, + 46, + 218, + 152, + 0, + 155, + 43, + 241, + 254, + 155, + 160, + 104, + 255, + 51, + 187, + 165, + 0, + 2, + 17, + 175, + 0, + 66, + 84, + 160, + 1, + 247, + 58, + 30, + 0, + 35, + 65, + 53, + 254, + 69, + 236, + 191, + 0, + 45, + 134, + 245, + 1, + 163, + 123, + 221, + 0, + 32, + 110, + 20, + 255, + 52, + 23, + 165, + 0, + 186, + 214, + 71, + 0, + 233, + 176, + 96, + 0, + 242, + 239, + 54, + 1, + 57, + 89, + 138, + 0, + 83, + 0, + 84, + 255, + 136, + 160, + 100, + 0, + 92, + 142, + 120, + 254, + 104, + 124, + 190, + 0, + 181, + 177, + 62, + 255, + 250, + 41, + 85, + 0, + 152, + 130, + 42, + 1, + 96, + 252, + 246, + 0, + 151, + 151, + 63, + 254, + 239, + 133, + 62, + 0, + 32, + 56, + 156, + 0, + 45, + 167, + 189, + 255, + 142, + 133, + 179, + 1, + 131, + 86, + 211, + 0, + 187, + 179, + 150, + 254, + 250, + 170, + 14, + 255, + 210, + 163, + 78, + 0, + 37, + 52, + 151, + 0, + 99, + 77, + 26, + 0, + 238, + 156, + 213, + 255, + 213, + 192, + 209, + 1, + 73, + 46, + 84, + 0, + 20, + 65, + 41, + 1, + 54, + 206, + 79, + 0, + 201, + 131, + 146, + 254, + 170, + 111, + 24, + 255, + 177, + 33, + 50, + 254, + 171, + 38, + 203, + 255, + 78, + 247, + 116, + 0, + 209, + 221, + 153, + 0, + 133, + 128, + 178, + 1, + 58, + 44, + 25, + 0, + 201, + 39, + 59, + 1, + 189, + 19, + 252, + 0, + 49, + 229, + 210, + 1, + 117, + 187, + 117, + 0, + 181, + 179, + 184, + 1, + 0, + 114, + 219, + 0, + 48, + 94, + 147, + 0, + 245, + 41, + 56, + 0, + 125, + 13, + 204, + 254, + 244, + 173, + 119, + 0, + 44, + 221, + 32, + 254, + 84, + 234, + 20, + 0, + 249, + 160, + 198, + 1, + 236, + 126, + 234, + 255, + 47, + 99, + 168, + 254, + 170, + 226, + 153, + 255, + 102, + 179, + 216, + 0, + 226, + 141, + 122, + 255, + 122, + 66, + 153, + 254, + 182, + 245, + 134, + 0, + 227, + 228, + 25, + 1, + 214, + 57, + 235, + 255, + 216, + 173, + 56, + 255, + 181, + 231, + 210, + 0, + 119, + 128, + 157, + 255, + 129, + 95, + 136, + 255, + 110, + 126, + 51, + 0, + 2, + 169, + 183, + 255, + 7, + 130, + 98, + 254, + 69, + 176, + 94, + 255, + 116, + 4, + 227, + 1, + 217, + 242, + 145, + 255, + 202, + 173, + 31, + 1, + 105, + 1, + 39, + 255, + 46, + 175, + 69, + 0, + 228, + 47, + 58, + 255, + 215, + 224, + 69, + 254, + 207, + 56, + 69, + 255, + 16, + 254, + 139, + 255, + 23, + 207, + 212, + 255, + 202, + 20, + 126, + 255, + 95, + 213, + 96, + 255, + 9, + 176, + 33, + 0, + 200, + 5, + 207, + 255, + 241, + 42, + 128, + 254, + 35, + 33, + 192, + 255, + 248, + 229, + 196, + 1, + 129, + 17, + 120, + 0, + 251, + 103, + 151, + 255, + 7, + 52, + 112, + 255, + 140, + 56, + 66, + 255, + 40, + 226, + 245, + 255, + 217, + 70, + 37, + 254, + 172, + 214, + 9, + 255, + 72, + 67, + 134, + 1, + 146, + 192, + 214, + 255, + 44, + 38, + 112, + 0, + 68, + 184, + 75, + 255, + 206, + 90, + 251, + 0, + 149, + 235, + 141, + 0, + 181, + 170, + 58, + 0, + 116, + 244, + 239, + 0, + 92, + 157, + 2, + 0, + 102, + 173, + 98, + 0, + 233, + 137, + 96, + 1, + 127, + 49, + 203, + 0, + 5, + 155, + 148, + 0, + 23, + 148, + 9, + 255, + 211, + 122, + 12, + 0, + 34, + 134, + 26, + 255, + 219, + 204, + 136, + 0, + 134, + 8, + 41, + 255, + 224, + 83, + 43, + 254, + 85, + 25, + 247, + 0, + 109, + 127, + 0, + 254, + 169, + 136, + 48, + 0, + 238, + 119, + 219, + 255, + 231, + 173, + 213, + 0, + 206, + 18, + 254, + 254, + 8, + 186, + 7, + 255, + 126, + 9, + 7, + 1, + 111, + 42, + 72, + 0, + 111, + 52, + 236, + 254, + 96, + 63, + 141, + 0, + 147, + 191, + 127, + 254, + 205, + 78, + 192, + 255, + 14, + 106, + 237, + 1, + 187, + 219, + 76, + 0, + 175, + 243, + 187, + 254, + 105, + 89, + 173, + 0, + 85, + 25, + 89, + 1, + 162, + 243, + 148, + 0, + 2, + 118, + 209, + 254, + 33, + 158, + 9, + 0, + 139, + 163, + 46, + 255, + 93, + 70, + 40, + 0, + 108, + 42, + 142, + 254, + 111, + 252, + 142, + 255, + 155, + 223, + 144, + 0, + 51, + 229, + 167, + 255, + 73, + 252, + 155, + 255, + 94, + 116, + 12, + 255, + 152, + 160, + 218, + 255, + 156, + 238, + 37, + 255, + 179, + 234, + 207, + 255, + 197, + 0, + 179, + 255, + 154, + 164, + 141, + 0, + 225, + 196, + 104, + 0, + 10, + 35, + 25, + 254, + 209, + 212, + 242, + 255, + 97, + 253, + 222, + 254, + 184, + 101, + 229, + 0, + 222, + 18, + 127, + 1, + 164, + 136, + 135, + 255, + 30, + 207, + 140, + 254, + 146, + 97, + 243, + 0, + 129, + 192, + 26, + 254, + 201, + 84, + 33, + 255, + 111, + 10, + 78, + 255, + 147, + 81, + 178, + 255, + 4, + 4, + 24, + 0, + 161, + 238, + 215, + 255, + 6, + 141, + 33, + 0, + 53, + 215, + 14, + 255, + 41, + 181, + 208, + 255, + 231, + 139, + 157, + 0, + 179, + 203, + 221, + 255, + 255, + 185, + 113, + 0, + 189, + 226, + 172, + 255, + 113, + 66, + 214, + 255, + 202, + 62, + 45, + 255, + 102, + 64, + 8, + 255, + 78, + 174, + 16, + 254, + 133, + 117, + 68, + 255, + 182, + 120, + 89, + 255, + 133, + 114, + 211, + 0, + 189, + 110, + 21, + 255, + 15, + 10, + 106, + 0, + 41, + 192, + 1, + 0, + 152, + 232, + 121, + 255, + 188, + 60, + 160, + 255, + 153, + 113, + 206, + 255, + 0, + 183, + 226, + 254, + 180, + 13, + 72, + 255, + 176, + 160, + 14, + 254, + 211, + 201, + 134, + 255, + 158, + 24, + 143, + 0, + 127, + 105, + 53, + 0, + 96, + 12, + 189, + 0, + 167, + 215, + 251, + 255, + 159, + 76, + 128, + 254, + 106, + 101, + 225, + 255, + 30, + 252, + 4, + 0, + 146, + 12, + 174, + 0, + 89, + 241, + 178, + 254, + 10, + 229, + 166, + 255, + 123, + 221, + 42, + 254, + 30, + 20, + 212, + 0, + 82, + 128, + 3, + 0, + 48, + 209, + 243, + 0, + 119, + 121, + 64, + 255, + 50, + 227, + 156, + 255, + 0, + 110, + 197, + 1, + 103, + 27, + 144, + 0, + 133, + 59, + 140, + 1, + 189, + 241, + 36, + 255, + 248, + 37, + 195, + 1, + 96, + 220, + 55, + 0, + 183, + 76, + 62, + 255, + 195, + 66, + 61, + 0, + 50, + 76, + 164, + 1, + 225, + 164, + 76, + 255, + 76, + 61, + 163, + 255, + 117, + 62, + 31, + 0, + 81, + 145, + 64, + 255, + 118, + 65, + 14, + 0, + 162, + 115, + 214, + 255, + 6, + 138, + 46, + 0, + 124, + 230, + 244, + 255, + 10, + 138, + 143, + 0, + 52, + 26, + 194, + 0, + 184, + 244, + 76, + 0, + 129, + 143, + 41, + 1, + 190, + 244, + 19, + 255, + 123, + 170, + 122, + 255, + 98, + 129, + 68, + 0, + 121, + 213, + 147, + 0, + 86, + 101, + 30, + 255, + 161, + 103, + 155, + 0, + 140, + 89, + 67, + 255, + 239, + 229, + 190, + 1, + 67, + 11, + 181, + 0, + 198, + 240, + 137, + 254, + 238, + 69, + 188, + 255, + 234, + 113, + 60, + 255, + 37, + 255, + 57, + 255, + 69, + 178, + 182, + 254, + 128, + 208, + 179, + 0, + 118, + 26, + 125, + 254, + 3, + 7, + 214, + 255, + 241, + 50, + 77, + 255, + 85, + 203, + 197, + 255, + 211, + 135, + 250, + 255, + 25, + 48, + 100, + 255, + 187, + 213, + 180, + 254, + 17, + 88, + 105, + 0, + 83, + 209, + 158, + 1, + 5, + 115, + 98, + 0, + 4, + 174, + 60, + 254, + 171, + 55, + 110, + 255, + 217, + 181, + 17, + 255, + 20, + 188, + 170, + 0, + 146, + 156, + 102, + 254, + 87, + 214, + 174, + 255, + 114, + 122, + 155, + 1, + 233, + 44, + 170, + 0, + 127, + 8, + 239, + 1, + 214, + 236, + 234, + 0, + 175, + 5, + 219, + 0, + 49, + 106, + 61, + 255, + 6, + 66, + 208, + 255, + 2, + 106, + 110, + 255, + 81, + 234, + 19, + 255, + 215, + 107, + 192, + 255, + 67, + 151, + 238, + 0, + 19, + 42, + 108, + 255, + 229, + 85, + 113, + 1, + 50, + 68, + 135, + 255, + 17, + 106, + 9, + 0, + 50, + 103, + 1, + 255, + 80, + 1, + 168, + 1, + 35, + 152, + 30, + 255, + 16, + 168, + 185, + 1, + 56, + 89, + 232, + 255, + 101, + 210, + 252, + 0, + 41, + 250, + 71, + 0, + 204, + 170, + 79, + 255, + 14, + 46, + 239, + 255, + 80, + 77, + 239, + 0, + 189, + 214, + 75, + 255, + 17, + 141, + 249, + 0, + 38, + 80, + 76, + 255, + 190, + 85, + 117, + 0, + 86, + 228, + 170, + 0, + 156, + 216, + 208, + 1, + 195, + 207, + 164, + 255, + 150, + 66, + 76, + 255, + 175, + 225, + 16, + 255, + 141, + 80, + 98, + 1, + 76, + 219, + 242, + 0, + 198, + 162, + 114, + 0, + 46, + 218, + 152, + 0, + 155, + 43, + 241, + 254, + 155, + 160, + 104, + 255, + 178, + 9, + 252, + 254, + 100, + 110, + 212, + 0, + 14, + 5, + 167, + 0, + 233, + 239, + 163, + 255, + 28, + 151, + 157, + 1, + 101, + 146, + 10, + 255, + 254, + 158, + 70, + 254, + 71, + 249, + 228, + 0, + 88, + 30, + 50, + 0, + 68, + 58, + 160, + 255, + 191, + 24, + 104, + 1, + 129, + 66, + 129, + 255, + 192, + 50, + 85, + 255, + 8, + 179, + 138, + 255, + 38, + 250, + 201, + 0, + 115, + 80, + 160, + 0, + 131, + 230, + 113, + 0, + 125, + 88, + 147, + 0, + 90, + 68, + 199, + 0, + 253, + 76, + 158, + 0, + 28, + 255, + 118, + 0, + 113, + 250, + 254, + 0, + 66, + 75, + 46, + 0, + 230, + 218, + 43, + 0, + 229, + 120, + 186, + 1, + 148, + 68, + 43, + 0, + 136, + 124, + 238, + 1, + 187, + 107, + 197, + 255, + 84, + 53, + 246, + 255, + 51, + 116, + 254, + 255, + 51, + 187, + 165, + 0, + 2, + 17, + 175, + 0, + 66, + 84, + 160, + 1, + 247, + 58, + 30, + 0, + 35, + 65, + 53, + 254, + 69, + 236, + 191, + 0, + 45, + 134, + 245, + 1, + 163, + 123, + 221, + 0, + 32, + 110, + 20, + 255, + 52, + 23, + 165, + 0, + 186, + 214, + 71, + 0, + 233, + 176, + 96, + 0, + 242, + 239, + 54, + 1, + 57, + 89, + 138, + 0, + 83, + 0, + 84, + 255, + 136, + 160, + 100, + 0, + 92, + 142, + 120, + 254, + 104, + 124, + 190, + 0, + 181, + 177, + 62, + 255, + 250, + 41, + 85, + 0, + 152, + 130, + 42, + 1, + 96, + 252, + 246, + 0, + 151, + 151, + 63, + 254, + 239, + 133, + 62, + 0, + 32, + 56, + 156, + 0, + 45, + 167, + 189, + 255, + 142, + 133, + 179, + 1, + 131, + 86, + 211, + 0, + 187, + 179, + 150, + 254, + 250, + 170, + 14, + 255, + 68, + 113, + 21, + 255, + 222, + 186, + 59, + 255, + 66, + 7, + 241, + 1, + 69, + 6, + 72, + 0, + 86, + 156, + 108, + 254, + 55, + 167, + 89, + 0, + 109, + 52, + 219, + 254, + 13, + 176, + 23, + 255, + 196, + 44, + 106, + 255, + 239, + 149, + 71, + 255, + 164, + 140, + 125, + 255, + 159, + 173, + 1, + 0, + 51, + 41, + 231, + 0, + 145, + 62, + 33, + 0, + 138, + 111, + 93, + 1, + 185, + 83, + 69, + 0, + 144, + 115, + 46, + 0, + 97, + 151, + 16, + 255, + 24, + 228, + 26, + 0, + 49, + 217, + 226, + 0, + 113, + 75, + 234, + 254, + 193, + 153, + 12, + 255, + 182, + 48, + 96, + 255, + 14, + 13, + 26, + 0, + 128, + 195, + 249, + 254, + 69, + 193, + 59, + 0, + 132, + 37, + 81, + 254, + 125, + 106, + 60, + 0, + 214, + 240, + 169, + 1, + 164, + 227, + 66, + 0, + 210, + 163, + 78, + 0, + 37, + 52, + 151, + 0, + 99, + 77, + 26, + 0, + 238, + 156, + 213, + 255, + 213, + 192, + 209, + 1, + 73, + 46, + 84, + 0, + 20, + 65, + 41, + 1, + 54, + 206, + 79, + 0, + 201, + 131, + 146, + 254, + 170, + 111, + 24, + 255, + 177, + 33, + 50, + 254, + 171, + 38, + 203, + 255, + 78, + 247, + 116, + 0, + 209, + 221, + 153, + 0, + 133, + 128, + 178, + 1, + 58, + 44, + 25, + 0, + 201, + 39, + 59, + 1, + 189, + 19, + 252, + 0, + 49, + 229, + 210, + 1, + 117, + 187, + 117, + 0, + 181, + 179, + 184, + 1, + 0, + 114, + 219, + 0, + 48, + 94, + 147, + 0, + 245, + 41, + 56, + 0, + 125, + 13, + 204, + 254, + 244, + 173, + 119, + 0, + 44, + 221, + 32, + 254, + 84, + 234, + 20, + 0, + 249, + 160, + 198, + 1, + 236, + 126, + 234, + 255, + 143, + 62, + 221, + 0, + 129, + 89, + 214, + 255, + 55, + 139, + 5, + 254, + 68, + 20, + 191, + 255, + 14, + 204, + 178, + 1, + 35, + 195, + 217, + 0, + 47, + 51, + 206, + 1, + 38, + 246, + 165, + 0, + 206, + 27, + 6, + 254, + 158, + 87, + 36, + 0, + 217, + 52, + 146, + 255, + 125, + 123, + 215, + 255, + 85, + 60, + 31, + 255, + 171, + 13, + 7, + 0, + 218, + 245, + 88, + 254, + 252, + 35, + 60, + 0, + 55, + 214, + 160, + 255, + 133, + 101, + 56, + 0, + 224, + 32, + 19, + 254, + 147, + 64, + 234, + 0, + 26, + 145, + 162, + 1, + 114, + 118, + 125, + 0, + 248, + 252, + 250, + 0, + 101, + 94, + 196, + 255, + 198, + 141, + 226, + 254, + 51, + 42, + 182, + 0, + 135, + 12, + 9, + 254, + 109, + 172, + 210, + 255, + 197, + 236, + 194, + 1, + 241, + 65, + 154, + 0, + 48, + 156, + 47, + 255, + 153, + 67, + 55, + 255, + 218, + 165, + 34, + 254, + 74, + 180, + 179, + 0, + 218, + 66, + 71, + 1, + 88, + 122, + 99, + 0, + 212, + 181, + 219, + 255, + 92, + 42, + 231, + 255, + 239, + 0, + 154, + 0, + 245, + 77, + 183, + 255, + 94, + 81, + 170, + 1, + 18, + 213, + 216, + 0, + 171, + 93, + 71, + 0, + 52, + 94, + 248, + 0, + 18, + 151, + 161, + 254, + 197, + 209, + 66, + 255, + 174, + 244, + 15, + 254, + 162, + 48, + 183, + 0, + 49, + 61, + 240, + 254, + 182, + 93, + 195, + 0, + 199, + 228, + 6, + 1, + 200, + 5, + 17, + 255, + 137, + 45, + 237, + 255, + 108, + 148, + 4, + 0, + 90, + 79, + 237, + 255, + 39, + 63, + 77, + 255, + 53, + 82, + 207, + 1, + 142, + 22, + 118, + 255, + 101, + 232, + 18, + 1, + 92, + 26, + 67, + 0, + 5, + 200, + 88, + 255, + 33, + 168, + 138, + 255, + 149, + 225, + 72, + 0, + 2, + 209, + 27, + 255, + 44, + 245, + 168, + 1, + 220, + 237, + 17, + 255, + 30, + 211, + 105, + 254, + 141, + 238, + 221, + 0, + 128, + 80, + 245, + 254, + 111, + 254, + 14, + 0, + 222, + 95, + 190, + 1, + 223, + 9, + 241, + 0, + 146, + 76, + 212, + 255, + 108, + 205, + 104, + 255, + 63, + 117, + 153, + 0, + 144, + 69, + 48, + 0, + 35, + 228, + 111, + 0, + 192, + 33, + 193, + 255, + 112, + 214, + 190, + 254, + 115, + 152, + 151, + 0, + 23, + 102, + 88, + 0, + 51, + 74, + 248, + 0, + 226, + 199, + 143, + 254, + 204, + 162, + 101, + 255, + 208, + 97, + 189, + 1, + 245, + 104, + 18, + 0, + 230, + 246, + 30, + 255, + 23, + 148, + 69, + 0, + 110, + 88, + 52, + 254, + 226, + 181, + 89, + 255, + 208, + 47, + 90, + 254, + 114, + 161, + 80, + 255, + 33, + 116, + 248, + 0, + 179, + 152, + 87, + 255, + 69, + 144, + 177, + 1, + 88, + 238, + 26, + 255, + 58, + 32, + 113, + 1, + 1, + 77, + 69, + 0, + 59, + 121, + 52, + 255, + 152, + 238, + 83, + 0, + 52, + 8, + 193, + 0, + 231, + 39, + 233, + 255, + 199, + 34, + 138, + 0, + 222, + 68, + 173, + 0, + 91, + 57, + 242, + 254, + 220, + 210, + 127, + 255, + 192, + 7, + 246, + 254, + 151, + 35, + 187, + 0, + 195, + 236, + 165, + 0, + 111, + 93, + 206, + 0, + 212, + 247, + 133, + 1, + 154, + 133, + 209, + 255, + 155, + 231, + 10, + 0, + 64, + 78, + 38, + 0, + 122, + 249, + 100, + 1, + 30, + 19, + 97, + 255, + 62, + 91, + 249, + 1, + 248, + 133, + 77, + 0, + 197, + 63, + 168, + 254, + 116, + 10, + 82, + 0, + 184, + 236, + 113, + 254, + 212, + 203, + 194, + 255, + 61, + 100, + 252, + 254, + 36, + 5, + 202, + 255, + 119, + 91, + 153, + 255, + 129, + 79, + 29, + 0, + 103, + 103, + 171, + 254, + 237, + 215, + 111, + 255, + 216, + 53, + 69, + 0, + 239, + 240, + 23, + 0, + 194, + 149, + 221, + 255, + 38, + 225, + 222, + 0, + 232, + 255, + 180, + 254, + 118, + 82, + 133, + 255, + 57, + 209, + 177, + 1, + 139, + 232, + 133, + 0, + 158, + 176, + 46, + 254, + 194, + 115, + 46, + 0, + 88, + 247, + 229, + 1, + 28, + 103, + 191, + 0, + 221, + 222, + 175, + 254, + 149, + 235, + 44, + 0, + 151, + 228, + 25, + 254, + 218, + 105, + 103, + 0, + 142, + 85, + 210, + 0, + 149, + 129, + 190, + 255, + 213, + 65, + 94, + 254, + 117, + 134, + 224, + 255, + 82, + 198, + 117, + 0, + 157, + 221, + 220, + 0, + 163, + 101, + 36, + 0, + 197, + 114, + 37, + 0, + 104, + 172, + 166, + 254, + 11, + 182, + 0, + 0, + 81, + 72, + 188, + 255, + 97, + 188, + 16, + 255, + 69, + 6, + 10, + 0, + 199, + 147, + 145, + 255, + 8, + 9, + 115, + 1, + 65, + 214, + 175, + 255, + 217, + 173, + 209, + 0, + 80, + 127, + 166, + 0, + 247, + 229, + 4, + 254, + 167, + 183, + 124, + 255, + 90, + 28, + 204, + 254, + 175, + 59, + 240, + 255, + 11, + 41, + 248, + 1, + 108, + 40, + 51, + 255, + 144, + 177, + 195, + 254, + 150, + 250, + 126, + 0, + 138, + 91, + 65, + 1, + 120, + 60, + 222, + 255, + 245, + 193, + 239, + 0, + 29, + 214, + 189, + 255, + 128, + 2, + 25, + 0, + 80, + 154, + 162, + 0, + 77, + 220, + 107, + 1, + 234, + 205, + 74, + 255, + 54, + 166, + 103, + 255, + 116, + 72, + 9, + 0, + 228, + 94, + 47, + 255, + 30, + 200, + 25, + 255, + 35, + 214, + 89, + 255, + 61, + 176, + 140, + 255, + 83, + 226, + 163, + 255, + 75, + 130, + 172, + 0, + 128, + 38, + 17, + 0, + 95, + 137, + 152, + 255, + 215, + 124, + 159, + 1, + 79, + 93, + 0, + 0, + 148, + 82, + 157, + 254, + 195, + 130, + 251, + 255, + 40, + 202, + 76, + 255, + 251, + 126, + 224, + 0, + 157, + 99, + 62, + 254, + 207, + 7, + 225, + 255, + 96, + 68, + 195, + 0, + 140, + 186, + 157, + 255, + 131, + 19, + 231, + 255, + 42, + 128, + 254, + 0, + 52, + 219, + 61, + 254, + 102, + 203, + 72, + 0, + 141, + 7, + 11, + 255, + 186, + 164, + 213, + 0, + 31, + 122, + 119, + 0, + 133, + 242, + 145, + 0, + 208, + 252, + 232, + 255, + 91, + 213, + 182, + 255, + 143, + 4, + 250, + 254, + 249, + 215, + 74, + 0, + 165, + 30, + 111, + 1, + 171, + 9, + 223, + 0, + 229, + 123, + 34, + 1, + 92, + 130, + 26, + 255, + 77, + 155, + 45, + 1, + 195, + 139, + 28, + 255, + 59, + 224, + 78, + 0, + 136, + 17, + 247, + 0, + 108, + 121, + 32, + 0, + 79, + 250, + 189, + 255, + 96, + 227, + 252, + 254, + 38, + 241, + 62, + 0, + 62, + 174, + 125, + 255, + 155, + 111, + 93, + 255, + 10, + 230, + 206, + 1, + 97, + 197, + 40, + 255, + 0, + 49, + 57, + 254, + 65, + 250, + 13, + 0, + 18, + 251, + 150, + 255, + 220, + 109, + 210, + 255, + 5, + 174, + 166, + 254, + 44, + 129, + 189, + 0, + 235, + 35, + 147, + 255, + 37, + 247, + 141, + 255, + 72, + 141, + 4, + 255, + 103, + 107, + 255, + 0, + 247, + 90, + 4, + 0, + 53, + 44, + 42, + 0, + 2, + 30, + 240, + 0, + 4, + 59, + 63, + 0, + 88, + 78, + 36, + 0, + 113, + 167, + 180, + 0, + 190, + 71, + 193, + 255, + 199, + 158, + 164, + 255, + 58, + 8, + 172, + 0, + 77, + 33, + 12, + 0, + 65, + 63, + 3, + 0, + 153, + 77, + 33, + 255, + 172, + 254, + 102, + 1, + 228, + 221, + 4, + 255, + 87, + 30, + 254, + 1, + 146, + 41, + 86, + 255, + 138, + 204, + 239, + 254, + 108, + 141, + 17, + 255, + 187, + 242, + 135, + 0, + 210, + 208, + 127, + 0, + 68, + 45, + 14, + 254, + 73, + 96, + 62, + 0, + 81, + 60, + 24, + 255, + 170, + 6, + 36, + 255, + 3, + 249, + 26, + 0, + 35, + 213, + 109, + 0, + 22, + 129, + 54, + 255, + 21, + 35, + 225, + 255, + 234, + 61, + 56, + 255, + 58, + 217, + 6, + 0, + 143, + 124, + 88, + 0, + 236, + 126, + 66, + 0, + 209, + 38, + 183, + 255, + 34, + 238, + 6, + 255, + 174, + 145, + 102, + 0, + 95, + 22, + 211, + 0, + 196, + 15, + 153, + 254, + 46, + 84, + 232, + 255, + 117, + 34, + 146, + 1, + 231, + 250, + 74, + 255, + 27, + 134, + 100, + 1, + 92, + 187, + 195, + 255, + 170, + 198, + 112, + 0, + 120, + 28, + 42, + 0, + 209, + 70, + 67, + 0, + 29, + 81, + 31, + 0, + 29, + 168, + 100, + 1, + 169, + 173, + 160, + 0, + 107, + 35, + 117, + 0, + 62, + 96, + 59, + 255, + 81, + 12, + 69, + 1, + 135, + 239, + 190, + 255, + 220, + 252, + 18, + 0, + 163, + 220, + 58, + 255, + 137, + 137, + 188, + 255, + 83, + 102, + 109, + 0, + 96, + 6, + 76, + 0, + 234, + 222, + 210, + 255, + 185, + 174, + 205, + 1, + 60, + 158, + 213, + 255, + 13, + 241, + 214, + 0, + 172, + 129, + 140, + 0, + 93, + 104, + 242, + 0, + 192, + 156, + 251, + 0, + 43, + 117, + 30, + 0, + 225, + 81, + 158, + 0, + 127, + 232, + 218, + 0, + 226, + 28, + 203, + 0, + 233, + 27, + 151, + 255, + 117, + 43, + 5, + 255, + 242, + 14, + 47, + 255, + 33, + 20, + 6, + 0, + 137, + 251, + 44, + 254, + 27, + 31, + 245, + 255, + 183, + 214, + 125, + 254, + 40, + 121, + 149, + 0, + 186, + 158, + 213, + 255, + 89, + 8, + 227, + 0, + 69, + 88, + 0, + 254, + 203, + 135, + 225, + 0, + 201, + 174, + 203, + 0, + 147, + 71, + 184, + 0, + 18, + 121, + 41, + 254, + 94, + 5, + 78, + 0, + 224, + 214, + 240, + 254, + 36, + 5, + 180, + 0, + 251, + 135, + 231, + 1, + 163, + 138, + 212, + 0, + 210, + 249, + 116, + 254, + 88, + 129, + 187, + 0, + 19, + 8, + 49, + 254, + 62, + 14, + 144, + 255, + 159, + 76, + 211, + 0, + 214, + 51, + 82, + 0, + 109, + 117, + 228, + 254, + 103, + 223, + 203, + 255, + 75, + 252, + 15, + 1, + 154, + 71, + 220, + 255, + 23, + 13, + 91, + 1, + 141, + 168, + 96, + 255, + 181, + 182, + 133, + 0, + 250, + 51, + 55, + 0, + 234, + 234, + 212, + 254, + 175, + 63, + 158, + 0, + 39, + 240, + 52, + 1, + 158, + 189, + 36, + 255, + 213, + 40, + 85, + 1, + 32, + 180, + 247, + 255, + 19, + 102, + 26, + 1, + 84, + 24, + 97, + 255, + 69, + 21, + 222, + 0, + 148, + 139, + 122, + 255, + 220, + 213, + 235, + 1, + 232, + 203, + 255, + 0, + 121, + 57, + 147, + 0, + 227, + 7, + 154, + 0, + 53, + 22, + 147, + 1, + 72, + 1, + 225, + 0, + 82, + 134, + 48, + 254, + 83, + 60, + 157, + 255, + 145, + 72, + 169, + 0, + 34, + 103, + 239, + 0, + 198, + 233, + 47, + 0, + 116, + 19, + 4, + 255, + 184, + 106, + 9, + 255, + 183, + 129, + 83, + 0, + 36, + 176, + 230, + 1, + 34, + 103, + 72, + 0, + 219, + 162, + 134, + 0, + 245, + 42, + 158, + 0, + 32, + 149, + 96, + 254, + 165, + 44, + 144, + 0, + 202, + 239, + 72, + 254, + 215, + 150, + 5, + 0, + 42, + 66, + 36, + 1, + 132, + 215, + 175, + 0, + 86, + 174, + 86, + 255, + 26, + 197, + 156, + 255, + 49, + 232, + 135, + 254, + 103, + 182, + 82, + 0, + 253, + 128, + 176, + 1, + 153, + 178, + 122, + 0, + 245, + 250, + 10, + 0, + 236, + 24, + 178, + 0, + 137, + 106, + 132, + 0, + 40, + 29, + 41, + 0, + 50, + 30, + 152, + 255, + 124, + 105, + 38, + 0, + 230, + 191, + 75, + 0, + 143, + 43, + 170, + 0, + 44, + 131, + 20, + 255, + 44, + 13, + 23, + 255, + 237, + 255, + 155, + 1, + 159, + 109, + 100, + 255, + 112, + 181, + 24, + 255, + 104, + 220, + 108, + 0, + 55, + 211, + 131, + 0, + 99, + 12, + 213, + 255, + 152, + 151, + 145, + 255, + 238, + 5, + 159, + 0, + 97, + 155, + 8, + 0, + 33, + 108, + 81, + 0, + 1, + 3, + 103, + 0, + 62, + 109, + 34, + 255, + 250, + 155, + 180, + 0, + 32, + 71, + 195, + 255, + 38, + 70, + 145, + 1, + 159, + 95, + 245, + 0, + 69, + 229, + 101, + 1, + 136, + 28, + 240, + 0, + 79, + 224, + 25, + 0, + 78, + 110, + 121, + 255, + 248, + 168, + 124, + 0, + 187, + 128, + 247, + 0, + 2, + 147, + 235, + 254, + 79, + 11, + 132, + 0, + 70, + 58, + 12, + 1, + 181, + 8, + 163, + 255, + 79, + 137, + 133, + 255, + 37, + 170, + 11, + 255, + 141, + 243, + 85, + 255, + 176, + 231, + 215, + 255, + 204, + 150, + 164, + 255, + 239, + 215, + 39, + 255, + 46, + 87, + 156, + 254, + 8, + 163, + 88, + 255, + 172, + 34, + 232, + 0, + 66, + 44, + 102, + 255, + 27, + 54, + 41, + 254, + 236, + 99, + 87, + 255, + 41, + 123, + 169, + 1, + 52, + 114, + 43, + 0, + 117, + 134, + 40, + 0, + 155, + 134, + 26, + 0, + 231, + 207, + 91, + 254, + 35, + 132, + 38, + 255, + 19, + 102, + 125, + 254, + 36, + 227, + 133, + 255, + 118, + 3, + 113, + 255, + 29, + 13, + 124, + 0, + 152, + 96, + 74, + 1, + 88, + 146, + 206, + 255, + 167, + 191, + 220, + 254, + 162, + 18, + 88, + 255, + 182, + 100, + 23, + 0, + 31, + 117, + 52, + 0, + 81, + 46, + 106, + 1, + 12, + 2, + 7, + 0, + 69, + 80, + 201, + 1, + 209, + 246, + 172, + 0, + 12, + 48, + 141, + 1, + 224, + 211, + 88, + 0, + 116, + 226, + 159, + 0, + 122, + 98, + 130, + 0, + 65, + 236, + 234, + 1, + 225, + 226, + 9, + 255, + 207, + 226, + 123, + 1, + 89, + 214, + 59, + 0, + 112, + 135, + 88, + 1, + 90, + 244, + 203, + 255, + 49, + 11, + 38, + 1, + 129, + 108, + 186, + 0, + 89, + 112, + 15, + 1, + 101, + 46, + 204, + 255, + 127, + 204, + 45, + 254, + 79, + 255, + 221, + 255, + 51, + 73, + 18, + 255, + 127, + 42, + 101, + 255, + 241, + 21, + 202, + 0, + 160, + 227, + 7, + 0, + 105, + 50, + 236, + 0, + 79, + 52, + 197, + 255, + 104, + 202, + 208, + 1, + 180, + 15, + 16, + 0, + 101, + 197, + 78, + 255, + 98, + 77, + 203, + 0, + 41, + 185, + 241, + 1, + 35, + 193, + 124, + 0, + 35, + 155, + 23, + 255, + 207, + 53, + 192, + 0, + 11, + 125, + 163, + 1, + 249, + 158, + 185, + 255, + 4, + 131, + 48, + 0, + 21, + 93, + 111, + 255, + 61, + 121, + 231, + 1, + 69, + 200, + 36, + 255, + 185, + 48, + 185, + 255, + 111, + 238, + 21, + 255, + 39, + 50, + 25, + 255, + 99, + 215, + 163, + 255, + 87, + 212, + 30, + 255, + 164, + 147, + 5, + 255, + 128, + 6, + 35, + 1, + 108, + 223, + 110, + 255, + 194, + 76, + 178, + 0, + 74, + 101, + 180, + 0, + 243, + 47, + 48, + 0, + 174, + 25, + 43, + 255, + 82, + 173, + 253, + 1, + 54, + 114, + 192, + 255, + 40, + 55, + 91, + 0, + 215, + 108, + 176, + 255, + 11, + 56, + 7, + 0, + 224, + 233, + 76, + 0, + 209, + 98, + 202, + 254, + 242, + 25, + 125, + 0, + 44, + 193, + 93, + 254, + 203, + 8, + 177, + 0, + 135, + 176, + 19, + 0, + 112, + 71, + 213, + 255, + 206, + 59, + 176, + 1, + 4, + 67, + 26, + 0, + 14, + 143, + 213, + 254, + 42, + 55, + 208, + 255, + 60, + 67, + 120, + 0, + 193, + 21, + 163, + 0, + 99, + 164, + 115, + 0, + 10, + 20, + 118, + 0, + 156, + 212, + 222, + 254, + 160, + 7, + 217, + 255, + 114, + 245, + 76, + 1, + 117, + 59, + 123, + 0, + 176, + 194, + 86, + 254, + 213, + 15, + 176, + 0, + 78, + 206, + 207, + 254, + 213, + 129, + 59, + 0, + 233, + 251, + 22, + 1, + 96, + 55, + 152, + 255, + 236, + 255, + 15, + 255, + 197, + 89, + 84, + 255, + 93, + 149, + 133, + 0, + 174, + 160, + 113, + 0, + 234, + 99, + 169, + 255, + 152, + 116, + 88, + 0, + 144, + 164, + 83, + 255, + 95, + 29, + 198, + 255, + 34, + 47, + 15, + 255, + 99, + 120, + 134, + 255, + 5, + 236, + 193, + 0, + 249, + 247, + 126, + 255, + 147, + 187, + 30, + 0, + 50, + 230, + 117, + 255, + 108, + 217, + 219, + 255, + 163, + 81, + 166, + 255, + 72, + 25, + 169, + 254, + 155, + 121, + 79, + 255, + 28, + 155, + 89, + 254, + 7, + 126, + 17, + 0, + 147, + 65, + 33, + 1, + 47, + 234, + 253, + 0, + 26, + 51, + 18, + 0, + 105, + 83, + 199, + 255, + 163, + 196, + 230, + 0, + 113, + 248, + 164, + 0, + 226, + 254, + 218, + 0, + 189, + 209, + 203, + 255, + 164, + 247, + 222, + 254, + 255, + 35, + 165, + 0, + 4, + 188, + 243, + 1, + 127, + 179, + 71, + 0, + 37, + 237, + 254, + 255, + 100, + 186, + 240, + 0, + 5, + 57, + 71, + 254, + 103, + 72, + 73, + 255, + 244, + 18, + 81, + 254, + 229, + 210, + 132, + 255, + 238, + 6, + 180, + 255, + 11, + 229, + 174, + 255, + 227, + 221, + 192, + 1, + 17, + 49, + 28, + 0, + 163, + 215, + 196, + 254, + 9, + 118, + 4, + 255, + 51, + 240, + 71, + 0, + 113, + 129, + 109, + 255, + 76, + 240, + 231, + 0, + 188, + 177, + 127, + 0, + 125, + 71, + 44, + 1, + 26, + 175, + 243, + 0, + 94, + 169, + 25, + 254, + 27, + 230, + 29, + 0, + 15, + 139, + 119, + 1, + 168, + 170, + 186, + 255, + 172, + 197, + 76, + 255, + 252, + 75, + 188, + 0, + 137, + 124, + 196, + 0, + 72, + 22, + 96, + 255, + 45, + 151, + 249, + 1, + 220, + 145, + 100, + 0, + 64, + 192, + 159, + 255, + 120, + 239, + 226, + 0, + 129, + 178, + 146, + 0, + 0, + 192, + 125, + 0, + 235, + 138, + 234, + 0, + 183, + 157, + 146, + 0, + 83, + 199, + 192, + 255, + 184, + 172, + 72, + 255, + 73, + 225, + 128, + 0, + 77, + 6, + 250, + 255, + 186, + 65, + 67, + 0, + 104, + 246, + 207, + 0, + 188, + 32, + 138, + 255, + 218, + 24, + 242, + 0, + 67, + 138, + 81, + 254, + 237, + 129, + 121, + 255, + 20, + 207, + 150, + 1, + 41, + 199, + 16, + 255, + 6, + 20, + 128, + 0, + 159, + 118, + 5, + 0, + 181, + 16, + 143, + 255, + 220, + 38, + 15, + 0, + 23, + 64, + 147, + 254, + 73, + 26, + 13, + 0, + 87, + 228, + 57, + 1, + 204, + 124, + 128, + 0, + 43, + 24, + 223, + 0, + 219, + 99, + 199, + 0, + 22, + 75, + 20, + 255, + 19, + 27, + 126, + 0, + 157, + 62, + 215, + 0, + 110, + 29, + 230, + 0, + 179, + 167, + 255, + 1, + 54, + 252, + 190, + 0, + 221, + 204, + 182, + 254, + 179, + 158, + 65, + 255, + 81, + 157, + 3, + 0, + 194, + 218, + 159, + 0, + 170, + 223, + 0, + 0, + 224, + 11, + 32, + 255, + 38, + 197, + 98, + 0, + 168, + 164, + 37, + 0, + 23, + 88, + 7, + 1, + 164, + 186, + 110, + 0, + 96, + 36, + 134, + 0, + 234, + 242, + 229, + 0, + 250, + 121, + 19, + 0, + 242, + 254, + 112, + 255, + 3, + 47, + 94, + 1, + 9, + 239, + 6, + 255, + 81, + 134, + 153, + 254, + 214, + 253, + 168, + 255, + 67, + 124, + 224, + 0, + 245, + 95, + 74, + 0, + 28, + 30, + 44, + 254, + 1, + 109, + 220, + 255, + 178, + 89, + 89, + 0, + 252, + 36, + 76, + 0, + 24, + 198, + 46, + 255, + 76, + 77, + 111, + 0, + 134, + 234, + 136, + 255, + 39, + 94, + 29, + 0, + 185, + 72, + 234, + 255, + 70, + 68, + 135, + 255, + 231, + 102, + 7, + 254, + 77, + 231, + 140, + 0, + 167, + 47, + 58, + 1, + 148, + 97, + 118, + 255, + 16, + 27, + 225, + 1, + 166, + 206, + 143, + 255, + 110, + 178, + 214, + 255, + 180, + 131, + 162, + 0, + 143, + 141, + 225, + 1, + 13, + 218, + 78, + 255, + 114, + 153, + 33, + 1, + 98, + 104, + 204, + 0, + 175, + 114, + 117, + 1, + 167, + 206, + 75, + 0, + 202, + 196, + 83, + 1, + 58, + 64, + 67, + 0, + 138, + 47, + 111, + 1, + 196, + 247, + 128, + 255, + 137, + 224, + 224, + 254, + 158, + 112, + 207, + 0, + 154, + 100, + 255, + 1, + 134, + 37, + 107, + 0, + 198, + 128, + 79, + 255, + 127, + 209, + 155, + 255, + 163, + 254, + 185, + 254, + 60, + 14, + 243, + 0, + 31, + 219, + 112, + 254, + 29, + 217, + 65, + 0, + 200, + 13, + 116, + 254, + 123, + 60, + 196, + 255, + 224, + 59, + 184, + 254, + 242, + 89, + 196, + 0, + 123, + 16, + 75, + 254, + 149, + 16, + 206, + 0, + 69, + 254, + 48, + 1, + 231, + 116, + 223, + 255, + 209, + 160, + 65, + 1, + 200, + 80, + 98, + 0, + 37, + 194, + 184, + 254, + 148, + 63, + 34, + 0, + 139, + 240, + 65, + 255, + 217, + 144, + 132, + 255, + 56, + 38, + 45, + 254, + 199, + 120, + 210, + 0, + 108, + 177, + 166, + 255, + 160, + 222, + 4, + 0, + 220, + 126, + 119, + 254, + 165, + 107, + 160, + 255, + 82, + 220, + 248, + 1, + 241, + 175, + 136, + 0, + 144, + 141, + 23, + 255, + 169, + 138, + 84, + 0, + 160, + 137, + 78, + 255, + 226, + 118, + 80, + 255, + 52, + 27, + 132, + 255, + 63, + 96, + 139, + 255, + 152, + 250, + 39, + 0, + 188, + 155, + 15, + 0, + 232, + 51, + 150, + 254, + 40, + 15, + 232, + 255, + 240, + 229, + 9, + 255, + 137, + 175, + 27, + 255, + 75, + 73, + 97, + 1, + 218, + 212, + 11, + 0, + 135, + 5, + 162, + 1, + 107, + 185, + 213, + 0, + 2, + 249, + 107, + 255, + 40, + 242, + 70, + 0, + 219, + 200, + 25, + 0, + 25, + 157, + 13, + 0, + 67, + 82, + 80, + 255, + 196, + 249, + 23, + 255, + 145, + 20, + 149, + 0, + 50, + 72, + 146, + 0, + 94, + 76, + 148, + 1, + 24, + 251, + 65, + 0, + 31, + 192, + 23, + 0, + 184, + 212, + 201, + 255, + 123, + 233, + 162, + 1, + 247, + 173, + 72, + 0, + 162, + 87, + 219, + 254, + 126, + 134, + 89, + 0, + 159, + 11, + 12, + 254, + 166, + 105, + 29, + 0, + 73, + 27, + 228, + 1, + 113, + 120, + 183, + 255, + 66, + 163, + 109, + 1, + 212, + 143, + 11, + 255, + 159, + 231, + 168, + 1, + 255, + 128, + 90, + 0, + 57, + 14, + 58, + 254, + 89, + 52, + 10, + 255, + 253, + 8, + 163, + 1, + 0, + 145, + 210, + 255, + 10, + 129, + 85, + 1, + 46, + 181, + 27, + 0, + 103, + 136, + 160, + 254, + 126, + 188, + 209, + 255, + 34, + 35, + 111, + 0, + 215, + 219, + 24, + 255, + 212, + 11, + 214, + 254, + 101, + 5, + 118, + 0, + 232, + 197, + 133, + 255, + 223, + 167, + 109, + 255, + 237, + 80, + 86, + 255, + 70, + 139, + 94, + 0, + 158, + 193, + 191, + 1, + 155, + 15, + 51, + 255, + 15, + 190, + 115, + 0, + 78, + 135, + 207, + 255, + 249, + 10, + 27, + 1, + 181, + 125, + 233, + 0, + 95, + 172, + 13, + 254, + 170, + 213, + 161, + 255, + 39, + 236, + 138, + 255, + 95, + 93, + 87, + 255, + 190, + 128, + 95, + 0, + 125, + 15, + 206, + 0, + 166, + 150, + 159, + 0, + 227, + 15, + 158, + 255, + 206, + 158, + 120, + 255, + 42, + 141, + 128, + 0, + 101, + 178, + 120, + 1, + 156, + 109, + 131, + 0, + 218, + 14, + 44, + 254, + 247, + 168, + 206, + 255, + 212, + 112, + 28, + 0, + 112, + 17, + 228, + 255, + 90, + 16, + 37, + 1, + 197, + 222, + 108, + 0, + 254, + 207, + 83, + 255, + 9, + 90, + 243, + 255, + 243, + 244, + 172, + 0, + 26, + 88, + 115, + 255, + 205, + 116, + 122, + 0, + 191, + 230, + 193, + 0, + 180, + 100, + 11, + 1, + 217, + 37, + 96, + 255, + 154, + 78, + 156, + 0, + 235, + 234, + 31, + 255, + 206, + 178, + 178, + 255, + 149, + 192, + 251, + 0, + 182, + 250, + 135, + 0, + 246, + 22, + 105, + 0, + 124, + 193, + 109, + 255, + 2, + 210, + 149, + 255, + 169, + 17, + 170, + 0, + 0, + 96, + 110, + 255, + 117, + 9, + 8, + 1, + 50, + 123, + 40, + 255, + 193, + 189, + 99, + 0, + 34, + 227, + 160, + 0, + 48, + 80, + 70, + 254, + 211, + 51, + 236, + 0, + 45, + 122, + 245, + 254, + 44, + 174, + 8, + 0, + 173, + 37, + 233, + 255, + 158, + 65, + 171, + 0, + 122, + 69, + 215, + 255, + 90, + 80, + 2, + 255, + 131, + 106, + 96, + 254, + 227, + 114, + 135, + 0, + 205, + 49, + 119, + 254, + 176, + 62, + 64, + 255, + 82, + 51, + 17, + 255, + 241, + 20, + 243, + 255, + 130, + 13, + 8, + 254, + 128, + 217, + 243, + 255, + 162, + 27, + 1, + 254, + 90, + 118, + 241, + 0, + 246, + 198, + 246, + 255, + 55, + 16, + 118, + 255, + 200, + 159, + 157, + 0, + 163, + 17, + 1, + 0, + 140, + 107, + 121, + 0, + 85, + 161, + 118, + 255, + 38, + 0, + 149, + 0, + 156, + 47, + 238, + 0, + 9, + 166, + 166, + 1, + 75, + 98, + 181, + 255, + 50, + 74, + 25, + 0, + 66, + 15, + 47, + 0, + 139, + 225, + 159, + 0, + 76, + 3, + 142, + 255, + 14, + 238, + 184, + 0, + 11, + 207, + 53, + 255, + 183, + 192, + 186, + 1, + 171, + 32, + 174, + 255, + 191, + 76, + 221, + 1, + 247, + 170, + 219, + 0, + 25, + 172, + 50, + 254, + 217, + 9, + 233, + 0, + 203, + 126, + 68, + 255, + 183, + 92, + 48, + 0, + 127, + 167, + 183, + 1, + 65, + 49, + 254, + 0, + 16, + 63, + 127, + 1, + 254, + 21, + 170, + 255, + 59, + 224, + 127, + 254, + 22, + 48, + 63, + 255, + 27, + 78, + 130, + 254, + 40, + 195, + 29, + 0, + 250, + 132, + 112, + 254, + 35, + 203, + 144, + 0, + 104, + 169, + 168, + 0, + 207, + 253, + 30, + 255, + 104, + 40, + 38, + 254, + 94, + 228, + 88, + 0, + 206, + 16, + 128, + 255, + 212, + 55, + 122, + 255, + 223, + 22, + 234, + 0, + 223, + 197, + 127, + 0, + 253, + 181, + 181, + 1, + 145, + 102, + 118, + 0, + 236, + 153, + 36, + 255, + 212, + 217, + 72, + 255, + 20, + 38, + 24, + 254, + 138, + 62, + 62, + 0, + 152, + 140, + 4, + 0, + 230, + 220, + 99, + 255, + 1, + 21, + 212, + 255, + 148, + 201, + 231, + 0, + 244, + 123, + 9, + 254, + 0, + 171, + 210, + 0, + 51, + 58, + 37, + 255, + 1, + 255, + 14, + 255, + 244, + 183, + 145, + 254, + 0, + 242, + 166, + 0, + 22, + 74, + 132, + 0, + 121, + 216, + 41, + 0, + 95, + 195, + 114, + 254, + 133, + 24, + 151, + 255, + 156, + 226, + 231, + 255, + 247, + 5, + 77, + 255, + 246, + 148, + 115, + 254, + 225, + 92, + 81, + 255, + 222, + 80, + 246, + 254, + 170, + 123, + 89, + 255, + 74, + 199, + 141, + 0, + 29, + 20, + 8, + 255, + 138, + 136, + 70, + 255, + 93, + 75, + 92, + 0, + 221, + 147, + 49, + 254, + 52, + 126, + 226, + 0, + 229, + 124, + 23, + 0, + 46, + 9, + 181, + 0, + 205, + 64, + 52, + 1, + 131, + 254, + 28, + 0, + 151, + 158, + 212, + 0, + 131, + 64, + 78, + 0, + 206, + 25, + 171, + 0, + 0, + 230, + 139, + 0, + 191, + 253, + 110, + 254, + 103, + 247, + 167, + 0, + 64, + 40, + 40, + 1, + 42, + 165, + 241, + 255, + 59, + 75, + 228, + 254, + 124, + 243, + 189, + 255, + 196, + 92, + 178, + 255, + 130, + 140, + 86, + 255, + 141, + 89, + 56, + 1, + 147, + 198, + 5, + 255, + 203, + 248, + 158, + 254, + 144, + 162, + 141, + 0, + 11, + 172, + 226, + 0, + 130, + 42, + 21, + 255, + 1, + 167, + 143, + 255, + 144, + 36, + 36, + 255, + 48, + 88, + 164, + 254, + 168, + 170, + 220, + 0, + 98, + 71, + 214, + 0, + 91, + 208, + 79, + 0, + 159, + 76, + 201, + 1, + 166, + 42, + 214, + 255, + 69, + 255, + 0, + 255, + 6, + 128, + 125, + 255, + 190, + 1, + 140, + 0, + 146, + 83, + 218, + 255, + 215, + 238, + 72, + 1, + 122, + 127, + 53, + 0, + 189, + 116, + 165, + 255, + 84, + 8, + 66, + 255, + 214, + 3, + 208, + 255, + 213, + 110, + 133, + 0, + 195, + 168, + 44, + 1, + 158, + 231, + 69, + 0, + 162, + 64, + 200, + 254, + 91, + 58, + 104, + 0, + 182, + 58, + 187, + 254, + 249, + 228, + 136, + 0, + 203, + 134, + 76, + 254, + 99, + 221, + 233, + 0, + 75, + 254, + 214, + 254, + 80, + 69, + 154, + 0, + 64, + 152, + 248, + 254, + 236, + 136, + 202, + 255, + 157, + 105, + 153, + 254, + 149, + 175, + 20, + 0, + 22, + 35, + 19, + 255, + 124, + 121, + 233, + 0, + 186, + 250, + 198, + 254, + 132, + 229, + 139, + 0, + 137, + 80, + 174, + 255, + 165, + 125, + 68, + 0, + 144, + 202, + 148, + 254, + 235, + 239, + 248, + 0, + 135, + 184, + 118, + 0, + 101, + 94, + 17, + 255, + 122, + 72, + 70, + 254, + 69, + 130, + 146, + 0, + 127, + 222, + 248, + 1, + 69, + 127, + 118, + 255, + 30, + 82, + 215, + 254, + 188, + 74, + 19, + 255, + 229, + 167, + 194, + 254, + 117, + 25, + 66, + 255, + 65, + 234, + 56, + 254, + 213, + 22, + 156, + 0, + 151, + 59, + 93, + 254, + 45, + 28, + 27, + 255, + 186, + 126, + 164, + 255, + 32, + 6, + 239, + 0, + 127, + 114, + 99, + 1, + 219, + 52, + 2, + 255, + 99, + 96, + 166, + 254, + 62, + 190, + 126, + 255, + 108, + 222, + 168, + 1, + 75, + 226, + 174, + 0, + 230, + 226, + 199, + 0, + 60, + 117, + 218, + 255, + 252, + 248, + 20, + 1, + 214, + 188, + 204, + 0, + 31, + 194, + 134, + 254, + 123, + 69, + 192, + 255, + 169, + 173, + 36, + 254, + 55, + 98, + 91, + 0, + 223, + 42, + 102, + 254, + 137, + 1, + 102, + 0, + 157, + 90, + 25, + 0, + 239, + 122, + 64, + 255, + 252, + 6, + 233, + 0, + 7, + 54, + 20, + 255, + 82, + 116, + 174, + 0, + 135, + 37, + 54, + 255, + 15, + 186, + 125, + 0, + 227, + 112, + 175, + 255, + 100, + 180, + 225, + 255, + 42, + 237, + 244, + 255, + 244, + 173, + 226, + 254, + 248, + 18, + 33, + 0, + 171, + 99, + 150, + 255, + 74, + 235, + 50, + 255, + 117, + 82, + 32, + 254, + 106, + 168, + 237, + 0, + 207, + 109, + 208, + 1, + 228, + 9, + 186, + 0, + 135, + 60, + 169, + 254, + 179, + 92, + 143, + 0, + 244, + 170, + 104, + 255, + 235, + 45, + 124, + 255, + 70, + 99, + 186, + 0, + 117, + 137, + 183, + 0, + 224, + 31, + 215, + 0, + 40, + 9, + 100, + 0, + 26, + 16, + 95, + 1, + 68, + 217, + 87, + 0, + 8, + 151, + 20, + 255, + 26, + 100, + 58, + 255, + 176, + 165, + 203, + 1, + 52, + 118, + 70, + 0, + 7, + 32, + 254, + 254, + 244, + 254, + 245, + 255, + 167, + 144, + 194, + 255, + 125, + 113, + 23, + 255, + 176, + 121, + 181, + 0, + 136, + 84, + 209, + 0, + 138, + 6, + 30, + 255, + 89, + 48, + 28, + 0, + 33, + 155, + 14, + 255, + 25, + 240, + 154, + 0, + 141, + 205, + 109, + 1, + 70, + 115, + 62, + 255, + 20, + 40, + 107, + 254, + 138, + 154, + 199, + 255, + 94, + 223, + 226, + 255, + 157, + 171, + 38, + 0, + 163, + 177, + 25, + 254, + 45, + 118, + 3, + 255, + 14, + 222, + 23, + 1, + 209, + 190, + 81, + 255, + 118, + 123, + 232, + 1, + 13, + 213, + 101, + 255, + 123, + 55, + 123, + 254, + 27, + 246, + 165, + 0, + 50, + 99, + 76, + 255, + 140, + 214, + 32, + 255, + 97, + 65, + 67, + 255, + 24, + 12, + 28, + 0, + 174, + 86, + 78, + 1, + 64, + 247, + 96, + 0, + 160, + 135, + 67, + 0, + 66, + 55, + 243, + 255, + 147, + 204, + 96, + 255, + 26, + 6, + 33, + 255, + 98, + 51, + 83, + 1, + 153, + 213, + 208, + 255, + 2, + 184, + 54, + 255, + 25, + 218, + 11, + 0, + 49, + 67, + 246, + 254, + 18, + 149, + 72, + 255, + 13, + 25, + 72, + 0, + 42, + 79, + 214, + 0, + 42, + 4, + 38, + 1, + 27, + 139, + 144, + 255, + 149, + 187, + 23, + 0, + 18, + 164, + 132, + 0, + 245, + 84, + 184, + 254, + 120, + 198, + 104, + 255, + 126, + 218, + 96, + 0, + 56, + 117, + 234, + 255, + 13, + 29, + 214, + 254, + 68, + 47, + 10, + 255, + 167, + 154, + 132, + 254, + 152, + 38, + 198, + 0, + 66, + 178, + 89, + 255, + 200, + 46, + 171, + 255, + 13, + 99, + 83, + 255, + 210, + 187, + 253, + 255, + 170, + 45, + 42, + 1, + 138, + 209, + 124, + 0, + 214, + 162, + 141, + 0, + 12, + 230, + 156, + 0, + 102, + 36, + 112, + 254, + 3, + 147, + 67, + 0, + 52, + 215, + 123, + 255, + 233, + 171, + 54, + 255, + 98, + 137, + 62, + 0, + 247, + 218, + 39, + 255, + 231, + 218, + 236, + 0, + 247, + 191, + 127, + 0, + 195, + 146, + 84, + 0, + 165, + 176, + 92, + 255, + 19, + 212, + 94, + 255, + 17, + 74, + 227, + 0, + 88, + 40, + 153, + 1, + 198, + 147, + 1, + 255, + 206, + 67, + 245, + 254, + 240, + 3, + 218, + 255, + 61, + 141, + 213, + 255, + 97, + 183, + 106, + 0, + 195, + 232, + 235, + 254, + 95, + 86, + 154, + 0, + 209, + 48, + 205, + 254, + 118, + 209, + 241, + 255, + 240, + 120, + 223, + 1, + 213, + 29, + 159, + 0, + 163, + 127, + 147, + 255, + 13, + 218, + 93, + 0, + 85, + 24, + 68, + 254, + 70, + 20, + 80, + 255, + 189, + 5, + 140, + 1, + 82, + 97, + 254, + 255, + 99, + 99, + 191, + 255, + 132, + 84, + 133, + 255, + 107, + 218, + 116, + 255, + 112, + 122, + 46, + 0, + 105, + 17, + 32, + 0, + 194, + 160, + 63, + 255, + 68, + 222, + 39, + 1, + 216, + 253, + 92, + 0, + 177, + 105, + 205, + 255, + 149, + 201, + 195, + 0, + 42, + 225, + 11, + 255, + 40, + 162, + 115, + 0, + 9, + 7, + 81, + 0, + 165, + 218, + 219, + 0, + 180, + 22, + 0, + 254, + 29, + 146, + 252, + 255, + 146, + 207, + 225, + 1, + 180, + 135, + 96, + 0, + 31, + 163, + 112, + 0, + 177, + 11, + 219, + 255, + 133, + 12, + 193, + 254, + 43, + 78, + 50, + 0, + 65, + 113, + 121, + 1, + 59, + 217, + 6, + 255, + 110, + 94, + 24, + 1, + 112, + 172, + 111, + 0, + 7, + 15, + 96, + 0, + 36, + 85, + 123, + 0, + 71, + 150, + 21, + 255, + 208, + 73, + 188, + 0, + 192, + 11, + 167, + 1, + 213, + 245, + 34, + 0, + 9, + 230, + 92, + 0, + 162, + 142, + 39, + 255, + 215, + 90, + 27, + 0, + 98, + 97, + 89, + 0, + 94, + 79, + 211, + 0, + 90, + 157, + 240, + 0, + 95, + 220, + 126, + 1, + 102, + 176, + 226, + 0, + 36, + 30, + 224, + 254, + 35, + 31, + 127, + 0, + 231, + 232, + 115, + 1, + 85, + 83, + 130, + 0, + 210, + 73, + 245, + 255, + 47, + 143, + 114, + 255, + 68, + 65, + 197, + 0, + 59, + 72, + 62, + 255, + 183, + 133, + 173, + 254, + 93, + 121, + 118, + 255, + 59, + 177, + 81, + 255, + 234, + 69, + 173, + 255, + 205, + 128, + 177, + 0, + 220, + 244, + 51, + 0, + 26, + 244, + 209, + 1, + 73, + 222, + 77, + 255, + 163, + 8, + 96, + 254, + 150, + 149, + 211, + 0, + 158, + 254, + 203, + 1, + 54, + 127, + 139, + 0, + 161, + 224, + 59, + 0, + 4, + 109, + 22, + 255, + 222, + 42, + 45, + 255, + 208, + 146, + 102, + 255, + 236, + 142, + 187, + 0, + 50, + 205, + 245, + 255, + 10, + 74, + 89, + 254, + 48, + 79, + 142, + 0, + 222, + 76, + 130, + 255, + 30, + 166, + 63, + 0, + 236, + 12, + 13, + 255, + 49, + 184, + 244, + 0, + 187, + 113, + 102, + 0, + 218, + 101, + 253, + 0, + 153, + 57, + 182, + 254, + 32, + 150, + 42, + 0, + 25, + 198, + 146, + 1, + 237, + 241, + 56, + 0, + 140, + 68, + 5, + 0, + 91, + 164, + 172, + 255, + 78, + 145, + 186, + 254, + 67, + 52, + 205, + 0, + 219, + 207, + 129, + 1, + 109, + 115, + 17, + 0, + 54, + 143, + 58, + 1, + 21, + 248, + 120, + 255, + 179, + 255, + 30, + 0, + 193, + 236, + 66, + 255, + 1, + 255, + 7, + 255, + 253, + 192, + 48, + 255, + 19, + 69, + 217, + 1, + 3, + 214, + 0, + 255, + 64, + 101, + 146, + 1, + 223, + 125, + 35, + 255, + 235, + 73, + 179, + 255, + 249, + 167, + 226, + 0, + 225, + 175, + 10, + 1, + 97, + 162, + 58, + 0, + 106, + 112, + 171, + 1, + 84, + 172, + 5, + 255, + 133, + 140, + 178, + 255, + 134, + 245, + 142, + 0, + 97, + 90, + 125, + 255, + 186, + 203, + 185, + 255, + 223, + 77, + 23, + 255, + 192, + 92, + 106, + 0, + 15, + 198, + 115, + 255, + 217, + 152, + 248, + 0, + 171, + 178, + 120, + 255, + 228, + 134, + 53, + 0, + 176, + 54, + 193, + 1, + 250, + 251, + 53, + 0, + 213, + 10, + 100, + 1, + 34, + 199, + 106, + 0, + 151, + 31, + 244, + 254, + 172, + 224, + 87, + 255, + 14, + 237, + 23, + 255, + 253, + 85, + 26, + 255, + 127, + 39, + 116, + 255, + 172, + 104, + 100, + 0, + 251, + 14, + 70, + 255, + 212, + 208, + 138, + 255, + 253, + 211, + 250, + 0, + 176, + 49, + 165, + 0, + 15, + 76, + 123, + 255, + 37, + 218, + 160, + 255, + 92, + 135, + 16, + 1, + 10, + 126, + 114, + 255, + 70, + 5, + 224, + 255, + 247, + 249, + 141, + 0, + 68, + 20, + 60, + 1, + 241, + 210, + 189, + 255, + 195, + 217, + 187, + 1, + 151, + 3, + 113, + 0, + 151, + 92, + 174, + 0, + 231, + 62, + 178, + 255, + 219, + 183, + 225, + 0, + 23, + 23, + 33, + 255, + 205, + 181, + 80, + 0, + 57, + 184, + 248, + 255, + 67, + 180, + 1, + 255, + 90, + 123, + 93, + 255, + 39, + 0, + 162, + 255, + 96, + 248, + 52, + 255, + 84, + 66, + 140, + 0, + 34, + 127, + 228, + 255, + 194, + 138, + 7, + 1, + 166, + 110, + 188, + 0, + 21, + 17, + 155, + 1, + 154, + 190, + 198, + 255, + 214, + 80, + 59, + 255, + 18, + 7, + 143, + 0, + 72, + 29, + 226, + 1, + 199, + 217, + 249, + 0, + 232, + 161, + 71, + 1, + 149, + 190, + 201, + 0, + 217, + 175, + 95, + 254, + 113, + 147, + 67, + 255, + 138, + 143, + 199, + 255, + 127, + 204, + 1, + 0, + 29, + 182, + 83, + 1, + 206, + 230, + 155, + 255, + 186, + 204, + 60, + 0, + 10, + 125, + 85, + 255, + 232, + 96, + 25, + 255, + 255, + 89, + 247, + 255, + 213, + 254, + 175, + 1, + 232, + 193, + 81, + 0, + 28, + 43, + 156, + 254, + 12, + 69, + 8, + 0, + 147, + 24, + 248, + 0, + 18, + 198, + 49, + 0, + 134, + 60, + 35, + 0, + 118, + 246, + 18, + 255, + 49, + 88, + 254, + 254, + 228, + 21, + 186, + 255, + 182, + 65, + 112, + 1, + 219, + 22, + 1, + 255, + 22, + 126, + 52, + 255, + 189, + 53, + 49, + 255, + 112, + 25, + 143, + 0, + 38, + 127, + 55, + 255, + 226, + 101, + 163, + 254, + 208, + 133, + 61, + 255, + 137, + 69, + 174, + 1, + 190, + 118, + 145, + 255, + 60, + 98, + 219, + 255, + 217, + 13, + 245, + 255, + 250, + 136, + 10, + 0, + 84, + 254, + 226, + 0, + 201, + 31, + 125, + 1, + 240, + 51, + 251, + 255, + 31, + 131, + 130, + 255, + 2, + 138, + 50, + 255, + 215, + 215, + 177, + 1, + 223, + 12, + 238, + 255, + 252, + 149, + 56, + 255, + 124, + 91, + 68, + 255, + 72, + 126, + 170, + 254, + 119, + 255, + 100, + 0, + 130, + 135, + 232, + 255, + 14, + 79, + 178, + 0, + 250, + 131, + 197, + 0, + 138, + 198, + 208, + 0, + 121, + 216, + 139, + 254, + 119, + 18, + 36, + 255, + 29, + 193, + 122, + 0, + 16, + 42, + 45, + 255, + 213, + 240, + 235, + 1, + 230, + 190, + 169, + 255, + 198, + 35, + 228, + 254, + 110, + 173, + 72, + 0, + 214, + 221, + 241, + 255, + 56, + 148, + 135, + 0, + 192, + 117, + 78, + 254, + 141, + 93, + 207, + 255, + 143, + 65, + 149, + 0, + 21, + 18, + 98, + 255, + 95, + 44, + 244, + 1, + 106, + 191, + 77, + 0, + 254, + 85, + 8, + 254, + 214, + 110, + 176, + 255, + 73, + 173, + 19, + 254, + 160, + 196, + 199, + 255, + 237, + 90, + 144, + 0, + 193, + 172, + 113, + 255, + 200, + 155, + 136, + 254, + 228, + 90, + 221, + 0, + 137, + 49, + 74, + 1, + 164, + 221, + 215, + 255, + 209, + 189, + 5, + 255, + 105, + 236, + 55, + 255, + 42, + 31, + 129, + 1, + 193, + 255, + 236, + 0, + 46, + 217, + 60, + 0, + 138, + 88, + 187, + 255, + 226, + 82, + 236, + 255, + 81, + 69, + 151, + 255, + 142, + 190, + 16, + 1, + 13, + 134, + 8, + 0, + 127, + 122, + 48, + 255, + 81, + 64, + 156, + 0, + 171, + 243, + 139, + 0, + 237, + 35, + 246, + 0, + 122, + 143, + 193, + 254, + 212, + 122, + 146, + 0, + 95, + 41, + 255, + 1, + 87, + 132, + 77, + 0, + 4, + 212, + 31, + 0, + 17, + 31, + 78, + 0, + 39, + 45, + 173, + 254, + 24, + 142, + 217, + 255, + 95, + 9, + 6, + 255, + 227, + 83, + 6, + 0, + 98, + 59, + 130, + 254, + 62, + 30, + 33, + 0, + 8, + 115, + 211, + 1, + 162, + 97, + 128, + 255, + 7, + 184, + 23, + 254, + 116, + 28, + 168, + 255, + 248, + 138, + 151, + 255, + 98, + 244, + 240, + 0, + 186, + 118, + 130, + 0, + 114, + 248, + 235, + 255, + 105, + 173, + 200, + 1, + 160, + 124, + 71, + 255, + 94, + 36, + 164, + 1, + 175, + 65, + 146, + 255, + 238, + 241, + 170, + 254, + 202, + 198, + 197, + 0, + 228, + 71, + 138, + 254, + 45, + 246, + 109, + 255, + 194, + 52, + 158, + 0, + 133, + 187, + 176, + 0, + 83, + 252, + 154, + 254, + 89, + 189, + 221, + 255, + 170, + 73, + 252, + 0, + 148, + 58, + 125, + 0, + 36, + 68, + 51, + 254, + 42, + 69, + 177, + 255, + 168, + 76, + 86, + 255, + 38, + 100, + 204, + 255, + 38, + 53, + 35, + 0, + 175, + 19, + 97, + 0, + 225, + 238, + 253, + 255, + 81, + 81, + 135, + 0, + 210, + 27, + 255, + 254, + 235, + 73, + 107, + 0, + 8, + 207, + 115, + 0, + 82, + 127, + 136, + 0, + 84, + 99, + 21, + 254, + 207, + 19, + 136, + 0, + 100, + 164, + 101, + 0, + 80, + 208, + 77, + 255, + 132, + 207, + 237, + 255, + 15, + 3, + 15, + 255, + 33, + 166, + 110, + 0, + 156, + 95, + 85, + 255, + 37, + 185, + 111, + 1, + 150, + 106, + 35, + 255, + 166, + 151, + 76, + 0, + 114, + 87, + 135, + 255, + 159, + 194, + 64, + 0, + 12, + 122, + 31, + 255, + 232, + 7, + 101, + 254, + 173, + 119, + 98, + 0, + 154, + 71, + 220, + 254, + 191, + 57, + 53, + 255, + 168, + 232, + 160, + 255, + 224, + 32, + 99, + 255, + 218, + 156, + 165, + 0, + 151, + 153, + 163, + 0, + 217, + 13, + 148, + 1, + 197, + 113, + 89, + 0, + 149, + 28, + 161, + 254, + 207, + 23, + 30, + 0, + 105, + 132, + 227, + 255, + 54, + 230, + 94, + 255, + 133, + 173, + 204, + 255, + 92, + 183, + 157, + 255, + 88, + 144, + 252, + 254, + 102, + 33, + 90, + 0, + 159, + 97, + 3, + 0, + 181, + 218, + 155, + 255, + 240, + 114, + 119, + 0, + 106, + 214, + 53, + 255, + 165, + 190, + 115, + 1, + 152, + 91, + 225, + 255, + 88, + 106, + 44, + 255, + 208, + 61, + 113, + 0, + 151, + 52, + 124, + 0, + 191, + 27, + 156, + 255, + 110, + 54, + 236, + 1, + 14, + 30, + 166, + 255, + 39, + 127, + 207, + 1, + 229, + 199, + 28, + 0, + 188, + 228, + 188, + 254, + 100, + 157, + 235, + 0, + 246, + 218, + 183, + 1, + 107, + 22, + 193, + 255, + 206, + 160, + 95, + 0, + 76, + 239, + 147, + 0, + 207, + 161, + 117, + 0, + 51, + 166, + 2, + 255, + 52, + 117, + 10, + 254, + 73, + 56, + 227, + 255, + 152, + 193, + 225, + 0, + 132, + 94, + 136, + 255, + 101, + 191, + 209, + 0, + 32, + 107, + 229, + 255, + 198, + 43, + 180, + 1, + 100, + 210, + 118, + 0, + 114, + 67, + 153, + 255, + 23, + 88, + 26, + 255, + 89, + 154, + 92, + 1, + 220, + 120, + 140, + 255, + 144, + 114, + 207, + 255, + 252, + 115, + 250, + 255, + 34, + 206, + 72, + 0, + 138, + 133, + 127, + 255, + 8, + 178, + 124, + 1, + 87, + 75, + 97, + 0, + 15, + 229, + 92, + 254, + 240, + 67, + 131, + 255, + 118, + 123, + 227, + 254, + 146, + 120, + 104, + 255, + 145, + 213, + 255, + 1, + 129, + 187, + 70, + 255, + 219, + 119, + 54, + 0, + 1, + 19, + 173, + 0, + 45, + 150, + 148, + 1, + 248, + 83, + 72, + 0, + 203, + 233, + 169, + 1, + 142, + 107, + 56, + 0, + 247, + 249, + 38, + 1, + 45, + 242, + 80, + 255, + 30, + 233, + 103, + 0, + 96, + 82, + 70, + 0, + 23, + 201, + 111, + 0, + 81, + 39, + 30, + 255, + 161, + 183, + 78, + 255, + 194, + 234, + 33, + 255, + 68, + 227, + 140, + 254, + 216, + 206, + 116, + 0, + 70, + 27, + 235, + 255, + 104, + 144, + 79, + 0, + 164, + 230, + 93, + 254, + 214, + 135, + 156, + 0, + 154, + 187, + 242, + 254, + 188, + 20, + 131, + 255, + 36, + 109, + 174, + 0, + 159, + 112, + 241, + 0, + 5, + 110, + 149, + 1, + 36, + 165, + 218, + 0, + 166, + 29, + 19, + 1, + 178, + 46, + 73, + 0, + 93, + 43, + 32, + 254, + 248, + 189, + 237, + 0, + 102, + 155, + 141, + 0, + 201, + 93, + 195, + 255, + 241, + 139, + 253, + 255, + 15, + 111, + 98, + 255, + 108, + 65, + 163, + 254, + 155, + 79, + 190, + 255, + 73, + 174, + 193, + 254, + 246, + 40, + 48, + 255, + 107, + 88, + 11, + 254, + 202, + 97, + 85, + 255, + 253, + 204, + 18, + 255, + 113, + 242, + 66, + 0, + 110, + 160, + 194, + 254, + 208, + 18, + 186, + 0, + 81, + 21, + 60, + 0, + 188, + 104, + 167, + 255, + 124, + 166, + 97, + 254, + 210, + 133, + 142, + 0, + 56, + 242, + 137, + 254, + 41, + 111, + 130, + 0, + 111, + 151, + 58, + 1, + 111, + 213, + 141, + 255, + 183, + 172, + 241, + 255, + 38, + 6, + 196, + 255, + 185, + 7, + 123, + 255, + 46, + 11, + 246, + 0, + 245, + 105, + 119, + 1, + 15, + 2, + 161, + 255, + 8, + 206, + 45, + 255, + 18, + 202, + 74, + 255, + 83, + 124, + 115, + 1, + 212, + 141, + 157, + 0, + 83, + 8, + 209, + 254, + 139, + 15, + 232, + 255, + 172, + 54, + 173, + 254, + 50, + 247, + 132, + 0, + 214, + 189, + 213, + 0, + 144, + 184, + 105, + 0, + 223, + 254, + 248, + 0, + 255, + 147, + 240, + 255, + 23, + 188, + 72, + 0, + 7, + 51, + 54, + 0, + 188, + 25, + 180, + 254, + 220, + 180, + 0, + 255, + 83, + 160, + 20, + 0, + 163, + 189, + 243, + 255, + 58, + 209, + 194, + 255, + 87, + 73, + 60, + 0, + 106, + 24, + 49, + 0, + 245, + 249, + 220, + 0, + 22, + 173, + 167, + 0, + 118, + 11, + 195, + 255, + 19, + 126, + 237, + 0, + 110, + 159, + 37, + 255, + 59, + 82, + 47, + 0, + 180, + 187, + 86, + 0, + 188, + 148, + 208, + 1, + 100, + 37, + 133, + 255, + 7, + 112, + 193, + 0, + 129, + 188, + 156, + 255, + 84, + 106, + 129, + 255, + 133, + 225, + 202, + 0, + 14, + 236, + 111, + 255, + 40, + 20, + 101, + 0, + 172, + 172, + 49, + 254, + 51, + 54, + 74, + 255, + 251, + 185, + 184, + 255, + 93, + 155, + 224, + 255, + 180, + 249, + 224, + 1, + 230, + 178, + 146, + 0, + 72, + 57, + 54, + 254, + 178, + 62, + 184, + 0, + 119, + 205, + 72, + 0, + 185, + 239, + 253, + 255, + 61, + 15, + 218, + 0, + 196, + 67, + 56, + 255, + 234, + 32, + 171, + 1, + 46, + 219, + 228, + 0, + 208, + 108, + 234, + 255, + 20, + 63, + 232, + 255, + 165, + 53, + 199, + 1, + 133, + 228, + 5, + 255, + 52, + 205, + 107, + 0, + 74, + 238, + 140, + 255, + 150, + 156, + 219, + 254, + 239, + 172, + 178, + 255, + 251, + 189, + 223, + 254, + 32, + 142, + 211, + 255, + 218, + 15, + 138, + 1, + 241, + 196, + 80, + 0, + 28, + 36, + 98, + 254, + 22, + 234, + 199, + 0, + 61, + 237, + 220, + 255, + 246, + 57, + 37, + 0, + 142, + 17, + 142, + 255, + 157, + 62, + 26, + 0, + 43, + 238, + 95, + 254, + 3, + 217, + 6, + 255, + 213, + 25, + 240, + 1, + 39, + 220, + 174, + 255, + 154, + 205, + 48, + 254, + 19, + 13, + 192, + 255, + 244, + 34, + 54, + 254, + 140, + 16, + 155, + 0, + 240, + 181, + 5, + 254, + 155, + 193, + 60, + 0, + 166, + 128, + 4, + 255, + 36, + 145, + 56, + 255, + 150, + 240, + 219, + 0, + 120, + 51, + 145, + 0, + 82, + 153, + 42, + 1, + 140, + 236, + 146, + 0, + 107, + 92, + 248, + 1, + 189, + 10, + 3, + 0, + 63, + 136, + 242, + 0, + 211, + 39, + 24, + 0, + 19, + 202, + 161, + 1, + 173, + 27, + 186, + 255, + 210, + 204, + 239, + 254, + 41, + 209, + 162, + 255, + 182, + 254, + 159, + 255, + 172, + 116, + 52, + 0, + 195, + 103, + 222, + 254, + 205, + 69, + 59, + 0, + 53, + 22, + 41, + 1, + 218, + 48, + 194, + 0, + 80, + 210, + 242, + 0, + 210, + 188, + 207, + 0, + 187, + 161, + 161, + 254, + 216, + 17, + 1, + 0, + 136, + 225, + 113, + 0, + 250, + 184, + 63, + 0, + 223, + 30, + 98, + 254, + 77, + 168, + 162, + 0, + 59, + 53, + 175, + 0, + 19, + 201, + 10, + 255, + 139, + 224, + 194, + 0, + 147, + 193, + 154, + 255, + 212, + 189, + 12, + 254, + 1, + 200, + 174, + 255, + 50, + 133, + 113, + 1, + 94, + 179, + 90, + 0, + 173, + 182, + 135, + 0, + 94, + 177, + 113, + 0, + 43, + 89, + 215, + 255, + 136, + 252, + 106, + 255, + 123, + 134, + 83, + 254, + 5, + 245, + 66, + 255, + 82, + 49, + 39, + 1, + 220, + 2, + 224, + 0, + 97, + 129, + 177, + 0, + 77, + 59, + 89, + 0, + 61, + 29, + 155, + 1, + 203, + 171, + 220, + 255, + 92, + 78, + 139, + 0, + 145, + 33, + 181, + 255, + 169, + 24, + 141, + 1, + 55, + 150, + 179, + 0, + 139, + 60, + 80, + 255, + 218, + 39, + 97, + 0, + 2, + 147, + 107, + 255, + 60, + 248, + 72, + 0, + 173, + 230, + 47, + 1, + 6, + 83, + 182, + 255, + 16, + 105, + 162, + 254, + 137, + 212, + 81, + 255, + 180, + 184, + 134, + 1, + 39, + 222, + 164, + 255, + 221, + 105, + 251, + 1, + 239, + 112, + 125, + 0, + 63, + 7, + 97, + 0, + 63, + 104, + 227, + 255, + 148, + 58, + 12, + 0, + 90, + 60, + 224, + 255, + 84, + 212, + 252, + 0, + 79, + 215, + 168, + 0, + 248, + 221, + 199, + 1, + 115, + 121, + 1, + 0, + 36, + 172, + 120, + 0, + 32, + 162, + 187, + 255, + 57, + 107, + 49, + 255, + 147, + 42, + 21, + 0, + 106, + 198, + 43, + 1, + 57, + 74, + 87, + 0, + 126, + 203, + 81, + 255, + 129, + 135, + 195, + 0, + 140, + 31, + 177, + 0, + 221, + 139, + 194, + 0, + 3, + 222, + 215, + 0, + 131, + 68, + 231, + 0, + 177, + 86, + 178, + 254, + 124, + 151, + 180, + 0, + 184, + 124, + 38, + 1, + 70, + 163, + 17, + 0, + 249, + 251, + 181, + 1, + 42, + 55, + 227, + 0, + 226, + 161, + 44, + 0, + 23, + 236, + 110, + 0, + 51, + 149, + 142, + 1, + 93, + 5, + 236, + 0, + 218, + 183, + 106, + 254, + 67, + 24, + 77, + 0, + 40, + 245, + 209, + 255, + 222, + 121, + 153, + 0, + 165, + 57, + 30, + 0, + 83, + 125, + 60, + 0, + 70, + 38, + 82, + 1, + 229, + 6, + 188, + 0, + 109, + 222, + 157, + 255, + 55, + 118, + 63, + 255, + 205, + 151, + 186, + 0, + 227, + 33, + 149, + 255, + 254, + 176, + 246, + 1, + 227, + 177, + 227, + 0, + 34, + 106, + 163, + 254, + 176, + 43, + 79, + 0, + 106, + 95, + 78, + 1, + 185, + 241, + 122, + 255, + 185, + 14, + 61, + 0, + 36, + 1, + 202, + 0, + 13, + 178, + 162, + 255, + 247, + 11, + 132, + 0, + 161, + 230, + 92, + 1, + 65, + 1, + 185, + 255, + 212, + 50, + 165, + 1, + 141, + 146, + 64, + 255, + 158, + 242, + 218, + 0, + 21, + 164, + 125, + 0, + 213, + 139, + 122, + 1, + 67, + 71, + 87, + 0, + 203, + 158, + 178, + 1, + 151, + 92, + 43, + 0, + 152, + 111, + 5, + 255, + 39, + 3, + 239, + 255, + 217, + 255, + 250, + 255, + 176, + 63, + 71, + 255, + 74, + 245, + 77, + 1, + 250, + 174, + 18, + 255, + 34, + 49, + 227, + 255, + 246, + 46, + 251, + 255, + 154, + 35, + 48, + 1, + 125, + 157, + 61, + 255, + 106, + 36, + 78, + 255, + 97, + 236, + 153, + 0, + 136, + 187, + 120, + 255, + 113, + 134, + 171, + 255, + 19, + 213, + 217, + 254, + 216, + 94, + 209, + 255, + 252, + 5, + 61, + 0, + 94, + 3, + 202, + 0, + 3, + 26, + 183, + 255, + 64, + 191, + 43, + 255, + 30, + 23, + 21, + 0, + 129, + 141, + 77, + 255, + 102, + 120, + 7, + 1, + 194, + 76, + 140, + 0, + 188, + 175, + 52, + 255, + 17, + 81, + 148, + 0, + 232, + 86, + 55, + 1, + 225, + 48, + 172, + 0, + 134, + 42, + 42, + 255, + 238, + 50, + 47, + 0, + 169, + 18, + 254, + 0, + 20, + 147, + 87, + 255, + 14, + 195, + 239, + 255, + 69, + 247, + 23, + 0, + 238, + 229, + 128, + 255, + 177, + 49, + 112, + 0, + 168, + 98, + 251, + 255, + 121, + 71, + 248, + 0, + 243, + 8, + 145, + 254, + 246, + 227, + 153, + 255, + 219, + 169, + 177, + 254, + 251, + 139, + 165, + 255, + 12, + 163, + 185, + 255, + 164, + 40, + 171, + 255, + 153, + 159, + 27, + 254, + 243, + 109, + 91, + 255, + 222, + 24, + 112, + 1, + 18, + 214, + 231, + 0, + 107, + 157, + 181, + 254, + 195, + 147, + 0, + 255, + 194, + 99, + 104, + 255, + 89, + 140, + 190, + 255, + 177, + 66, + 126, + 254, + 106, + 185, + 66, + 0, + 49, + 218, + 31, + 0, + 252, + 174, + 158, + 0, + 188, + 79, + 230, + 1, + 238, + 41, + 224, + 0, + 212, + 234, + 8, + 1, + 136, + 11, + 181, + 0, + 166, + 117, + 83, + 255, + 68, + 195, + 94, + 0, + 46, + 132, + 201, + 0, + 240, + 152, + 88, + 0, + 164, + 57, + 69, + 254, + 160, + 224, + 42, + 255, + 59, + 215, + 67, + 255, + 119, + 195, + 141, + 255, + 36, + 180, + 121, + 254, + 207, + 47, + 8, + 255, + 174, + 210, + 223, + 0, + 101, + 197, + 68, + 255, + 255, + 82, + 141, + 1, + 250, + 137, + 233, + 0, + 97, + 86, + 133, + 1, + 16, + 80, + 69, + 0, + 132, + 131, + 159, + 0, + 116, + 93, + 100, + 0, + 45, + 141, + 139, + 0, + 152, + 172, + 157, + 255, + 90, + 43, + 91, + 0, + 71, + 153, + 46, + 0, + 39, + 16, + 112, + 255, + 217, + 136, + 97, + 255, + 220, + 198, + 25, + 254, + 177, + 53, + 49, + 0, + 222, + 88, + 134, + 255, + 128, + 15, + 60, + 0, + 207, + 192, + 169, + 255, + 192, + 116, + 209, + 255, + 106, + 78, + 211, + 1, + 200, + 213, + 183, + 255, + 7, + 12, + 122, + 254, + 222, + 203, + 60, + 255, + 33, + 110, + 199, + 254, + 251, + 106, + 117, + 0, + 228, + 225, + 4, + 1, + 120, + 58, + 7, + 255, + 221, + 193, + 84, + 254, + 112, + 133, + 27, + 0, + 189, + 200, + 201, + 255, + 139, + 135, + 150, + 0, + 234, + 55, + 176, + 255, + 61, + 50, + 65, + 0, + 152, + 108, + 169, + 255, + 220, + 85, + 1, + 255, + 112, + 135, + 227, + 0, + 162, + 26, + 186, + 0, + 207, + 96, + 185, + 254, + 244, + 136, + 107, + 0, + 93, + 153, + 50, + 1, + 198, + 97, + 151, + 0, + 110, + 11, + 86, + 255, + 143, + 117, + 174, + 255, + 115, + 212, + 200, + 0, + 5, + 202, + 183, + 0, + 237, + 164, + 10, + 254, + 185, + 239, + 62, + 0, + 236, + 120, + 18, + 254, + 98, + 123, + 99, + 255, + 168, + 201, + 194, + 254, + 46, + 234, + 214, + 0, + 191, + 133, + 49, + 255, + 99, + 169, + 119, + 0, + 190, + 187, + 35, + 1, + 115, + 21, + 45, + 255, + 249, + 131, + 72, + 0, + 112, + 6, + 123, + 255, + 214, + 49, + 181, + 254, + 166, + 233, + 34, + 0, + 92, + 197, + 102, + 254, + 253, + 228, + 205, + 255, + 3, + 59, + 201, + 1, + 42, + 98, + 46, + 0, + 219, + 37, + 35, + 255, + 169, + 195, + 38, + 0, + 94, + 124, + 193, + 1, + 156, + 43, + 223, + 0, + 95, + 72, + 133, + 254, + 120, + 206, + 191, + 0, + 122, + 197, + 239, + 255, + 177, + 187, + 79, + 255, + 254, + 46, + 2, + 1, + 250, + 167, + 190, + 0, + 84, + 129, + 19, + 0, + 203, + 113, + 166, + 255, + 249, + 31, + 189, + 254, + 72, + 157, + 202, + 255, + 208, + 71, + 73, + 255, + 207, + 24, + 72, + 0, + 10, + 16, + 18, + 1, + 210, + 81, + 76, + 255, + 88, + 208, + 192, + 255, + 126, + 243, + 107, + 255, + 238, + 141, + 120, + 255, + 199, + 121, + 234, + 255, + 137, + 12, + 59, + 255, + 36, + 220, + 123, + 255, + 148, + 179, + 60, + 254, + 240, + 12, + 29, + 0, + 66, + 0, + 97, + 1, + 36, + 30, + 38, + 255, + 115, + 1, + 93, + 255, + 96, + 103, + 231, + 255, + 197, + 158, + 59, + 1, + 192, + 164, + 240, + 0, + 202, + 202, + 57, + 255, + 24, + 174, + 48, + 0, + 89, + 77, + 155, + 1, + 42, + 76, + 215, + 0, + 244, + 151, + 233, + 0, + 23, + 48, + 81, + 0, + 239, + 127, + 52, + 254, + 227, + 130, + 37, + 255, + 248, + 116, + 93, + 1, + 124, + 132, + 118, + 0, + 173, + 254, + 192, + 1, + 6, + 235, + 83, + 255, + 110, + 175, + 231, + 1, + 251, + 28, + 182, + 0, + 129, + 249, + 93, + 254, + 84, + 184, + 128, + 0, + 76, + 181, + 62, + 0, + 175, + 128, + 186, + 0, + 100, + 53, + 136, + 254, + 109, + 29, + 226, + 0, + 221, + 233, + 58, + 1, + 20, + 99, + 74, + 0, + 0, + 22, + 160, + 0, + 134, + 13, + 21, + 0, + 9, + 52, + 55, + 255, + 17, + 89, + 140, + 0, + 175, + 34, + 59, + 0, + 84, + 165, + 119, + 255, + 224, + 226, + 234, + 255, + 7, + 72, + 166, + 255, + 123, + 115, + 255, + 1, + 18, + 214, + 246, + 0, + 250, + 7, + 71, + 1, + 217, + 220, + 185, + 0, + 212, + 35, + 76, + 255, + 38, + 125, + 175, + 0, + 189, + 97, + 210, + 0, + 114, + 238, + 44, + 255, + 41, + 188, + 169, + 254, + 45, + 186, + 154, + 0, + 81, + 92, + 22, + 0, + 132, + 160, + 193, + 0, + 121, + 208, + 98, + 255, + 13, + 81, + 44, + 255, + 203, + 156, + 82, + 0, + 71, + 58, + 21, + 255, + 208, + 114, + 191, + 254, + 50, + 38, + 147, + 0, + 154, + 216, + 195, + 0, + 101, + 25, + 18, + 0, + 60, + 250, + 215, + 255, + 233, + 132, + 235, + 255, + 103, + 175, + 142, + 1, + 16, + 14, + 92, + 0, + 141, + 31, + 110, + 254, + 238, + 241, + 45, + 255, + 153, + 217, + 239, + 1, + 97, + 168, + 47, + 255, + 249, + 85, + 16, + 1, + 28, + 175, + 62, + 255, + 57, + 254, + 54, + 0, + 222, + 231, + 126, + 0, + 166, + 45, + 117, + 254, + 18, + 189, + 96, + 255, + 228, + 76, + 50, + 0, + 200, + 244, + 94, + 0, + 198, + 152, + 120, + 1, + 68, + 34, + 69, + 255, + 12, + 65, + 160, + 254, + 101, + 19, + 90, + 0, + 167, + 197, + 120, + 255, + 68, + 54, + 185, + 255, + 41, + 218, + 188, + 0, + 113, + 168, + 48, + 0, + 88, + 105, + 189, + 1, + 26, + 82, + 32, + 255, + 185, + 93, + 164, + 1, + 228, + 240, + 237, + 255, + 66, + 182, + 53, + 0, + 171, + 197, + 92, + 255, + 107, + 9, + 233, + 1, + 199, + 120, + 144, + 255, + 78, + 49, + 10, + 255, + 109, + 170, + 105, + 255, + 90, + 4, + 31, + 255, + 28, + 244, + 113, + 255, + 74, + 58, + 11, + 0, + 62, + 220, + 246, + 255, + 121, + 154, + 200, + 254, + 144, + 210, + 178, + 255, + 126, + 57, + 129, + 1, + 43, + 250, + 14, + 255, + 101, + 111, + 28, + 1, + 47, + 86, + 241, + 255, + 61, + 70, + 150, + 255, + 53, + 73, + 5, + 255, + 30, + 26, + 158, + 0, + 209, + 26, + 86, + 0, + 138, + 237, + 74, + 0, + 164, + 95, + 188, + 0, + 142, + 60, + 29, + 254, + 162, + 116, + 248, + 255, + 187, + 175, + 160, + 0, + 151, + 18, + 16, + 0, + 209, + 111, + 65, + 254, + 203, + 134, + 39, + 255, + 88, + 108, + 49, + 255, + 131, + 26, + 71, + 255, + 221, + 27, + 215, + 254, + 104, + 105, + 93, + 255, + 31, + 236, + 31, + 254, + 135, + 0, + 211, + 255, + 143, + 127, + 110, + 1, + 212, + 73, + 229, + 0, + 233, + 67, + 167, + 254, + 195, + 1, + 208, + 255, + 132, + 17, + 221, + 255, + 51, + 217, + 90, + 0, + 67, + 235, + 50, + 255, + 223, + 210, + 143, + 0, + 179, + 53, + 130, + 1, + 233, + 106, + 198, + 0, + 217, + 173, + 220, + 255, + 112, + 229, + 24, + 255, + 175, + 154, + 93, + 254, + 71, + 203, + 246, + 255, + 48, + 66, + 133, + 255, + 3, + 136, + 230, + 255, + 23, + 221, + 113, + 254, + 235, + 111, + 213, + 0, + 170, + 120, + 95, + 254, + 251, + 221, + 2, + 0, + 45, + 130, + 158, + 254, + 105, + 94, + 217, + 255, + 242, + 52, + 180, + 254, + 213, + 68, + 45, + 255, + 104, + 38, + 28, + 0, + 244, + 158, + 76, + 0, + 161, + 200, + 96, + 255, + 207, + 53, + 13, + 255, + 187, + 67, + 148, + 0, + 170, + 54, + 248, + 0, + 119, + 162, + 178, + 255, + 83, + 20, + 11, + 0, + 42, + 42, + 192, + 1, + 146, + 159, + 163, + 255, + 183, + 232, + 111, + 0, + 77, + 229, + 21, + 255, + 71, + 53, + 143, + 0, + 27, + 76, + 34, + 0, + 246, + 136, + 47, + 255, + 219, + 39, + 182, + 255, + 92, + 224, + 201, + 1, + 19, + 142, + 14, + 255, + 69, + 182, + 241, + 255, + 163, + 118, + 245, + 0, + 9, + 109, + 106, + 1, + 170, + 181, + 247, + 255, + 78, + 47, + 238, + 255, + 84, + 210, + 176, + 255, + 213, + 107, + 139, + 0, + 39, + 38, + 11, + 0, + 72, + 21, + 150, + 0, + 72, + 130, + 69, + 0, + 205, + 77, + 155, + 254, + 142, + 133, + 21, + ], + 'i8', + ALLOC_NONE, + Runtime.GLOBAL_BASE +); +/* memory initializer */ allocate( + [ + 71, + 111, + 172, + 254, + 226, + 42, + 59, + 255, + 179, + 0, + 215, + 1, + 33, + 128, + 241, + 0, + 234, + 252, + 13, + 1, + 184, + 79, + 8, + 0, + 110, + 30, + 73, + 255, + 246, + 141, + 189, + 0, + 170, + 207, + 218, + 1, + 74, + 154, + 69, + 255, + 138, + 246, + 49, + 255, + 155, + 32, + 100, + 0, + 125, + 74, + 105, + 255, + 90, + 85, + 61, + 255, + 35, + 229, + 177, + 255, + 62, + 125, + 193, + 255, + 153, + 86, + 188, + 1, + 73, + 120, + 212, + 0, + 209, + 123, + 246, + 254, + 135, + 209, + 38, + 255, + 151, + 58, + 44, + 1, + 92, + 69, + 214, + 255, + 14, + 12, + 88, + 255, + 252, + 153, + 166, + 255, + 253, + 207, + 112, + 255, + 60, + 78, + 83, + 255, + 227, + 124, + 110, + 0, + 180, + 96, + 252, + 255, + 53, + 117, + 33, + 254, + 164, + 220, + 82, + 255, + 41, + 1, + 27, + 255, + 38, + 164, + 166, + 255, + 164, + 99, + 169, + 254, + 61, + 144, + 70, + 255, + 192, + 166, + 18, + 0, + 107, + 250, + 66, + 0, + 197, + 65, + 50, + 0, + 1, + 179, + 18, + 255, + 255, + 104, + 1, + 255, + 43, + 153, + 35, + 255, + 80, + 111, + 168, + 0, + 110, + 175, + 168, + 0, + 41, + 105, + 45, + 255, + 219, + 14, + 205, + 255, + 164, + 233, + 140, + 254, + 43, + 1, + 118, + 0, + 233, + 67, + 195, + 0, + 178, + 82, + 159, + 255, + 138, + 87, + 122, + 255, + 212, + 238, + 90, + 255, + 144, + 35, + 124, + 254, + 25, + 140, + 164, + 0, + 251, + 215, + 44, + 254, + 133, + 70, + 107, + 255, + 101, + 227, + 80, + 254, + 92, + 169, + 55, + 0, + 215, + 42, + 49, + 0, + 114, + 180, + 85, + 255, + 33, + 232, + 27, + 1, + 172, + 213, + 25, + 0, + 62, + 176, + 123, + 254, + 32, + 133, + 24, + 255, + 225, + 191, + 62, + 0, + 93, + 70, + 153, + 0, + 181, + 42, + 104, + 1, + 22, + 191, + 224, + 255, + 200, + 200, + 140, + 255, + 249, + 234, + 37, + 0, + 149, + 57, + 141, + 0, + 195, + 56, + 208, + 255, + 254, + 130, + 70, + 255, + 32, + 173, + 240, + 255, + 29, + 220, + 199, + 0, + 110, + 100, + 115, + 255, + 132, + 229, + 249, + 0, + 228, + 233, + 223, + 255, + 37, + 216, + 209, + 254, + 178, + 177, + 209, + 255, + 183, + 45, + 165, + 254, + 224, + 97, + 114, + 0, + 137, + 97, + 168, + 255, + 225, + 222, + 172, + 0, + 165, + 13, + 49, + 1, + 210, + 235, + 204, + 255, + 252, + 4, + 28, + 254, + 70, + 160, + 151, + 0, + 232, + 190, + 52, + 254, + 83, + 248, + 93, + 255, + 62, + 215, + 77, + 1, + 175, + 175, + 179, + 255, + 160, + 50, + 66, + 0, + 121, + 48, + 208, + 0, + 63, + 169, + 209, + 255, + 0, + 210, + 200, + 0, + 224, + 187, + 44, + 1, + 73, + 162, + 82, + 0, + 9, + 176, + 143, + 255, + 19, + 76, + 193, + 255, + 29, + 59, + 167, + 1, + 24, + 43, + 154, + 0, + 28, + 190, + 190, + 0, + 141, + 188, + 129, + 0, + 232, + 235, + 203, + 255, + 234, + 0, + 109, + 255, + 54, + 65, + 159, + 0, + 60, + 88, + 232, + 255, + 121, + 253, + 150, + 254, + 252, + 233, + 131, + 255, + 198, + 110, + 41, + 1, + 83, + 77, + 71, + 255, + 200, + 22, + 59, + 254, + 106, + 253, + 242, + 255, + 21, + 12, + 207, + 255, + 237, + 66, + 189, + 0, + 90, + 198, + 202, + 1, + 225, + 172, + 127, + 0, + 53, + 22, + 202, + 0, + 56, + 230, + 132, + 0, + 1, + 86, + 183, + 0, + 109, + 190, + 42, + 0, + 243, + 68, + 174, + 1, + 109, + 228, + 154, + 0, + 200, + 177, + 122, + 1, + 35, + 160, + 183, + 255, + 177, + 48, + 85, + 255, + 90, + 218, + 169, + 255, + 248, + 152, + 78, + 0, + 202, + 254, + 110, + 0, + 6, + 52, + 43, + 0, + 142, + 98, + 65, + 255, + 63, + 145, + 22, + 0, + 70, + 106, + 93, + 0, + 232, + 138, + 107, + 1, + 110, + 179, + 61, + 255, + 211, + 129, + 218, + 1, + 242, + 209, + 92, + 0, + 35, + 90, + 217, + 1, + 182, + 143, + 106, + 255, + 116, + 101, + 217, + 255, + 114, + 250, + 221, + 255, + 173, + 204, + 6, + 0, + 60, + 150, + 163, + 0, + 73, + 172, + 44, + 255, + 239, + 110, + 80, + 255, + 237, + 76, + 153, + 254, + 161, + 140, + 249, + 0, + 149, + 232, + 229, + 0, + 133, + 31, + 40, + 255, + 174, + 164, + 119, + 0, + 113, + 51, + 214, + 0, + 129, + 228, + 2, + 254, + 64, + 34, + 243, + 0, + 107, + 227, + 244, + 255, + 174, + 106, + 200, + 255, + 84, + 153, + 70, + 1, + 50, + 35, + 16, + 0, + 250, + 74, + 216, + 254, + 236, + 189, + 66, + 255, + 153, + 249, + 13, + 0, + 230, + 178, + 4, + 255, + 221, + 41, + 238, + 0, + 118, + 227, + 121, + 255, + 94, + 87, + 140, + 254, + 254, + 119, + 92, + 0, + 73, + 239, + 246, + 254, + 117, + 87, + 128, + 0, + 19, + 211, + 145, + 255, + 177, + 46, + 252, + 0, + 229, + 91, + 246, + 1, + 69, + 128, + 247, + 255, + 202, + 77, + 54, + 1, + 8, + 11, + 9, + 255, + 153, + 96, + 166, + 0, + 217, + 214, + 173, + 255, + 134, + 192, + 2, + 1, + 0, + 207, + 0, + 0, + 189, + 174, + 107, + 1, + 140, + 134, + 100, + 0, + 158, + 193, + 243, + 1, + 182, + 102, + 171, + 0, + 235, + 154, + 51, + 0, + 142, + 5, + 123, + 255, + 60, + 168, + 89, + 1, + 217, + 14, + 92, + 255, + 19, + 214, + 5, + 1, + 211, + 167, + 254, + 0, + 44, + 6, + 202, + 254, + 120, + 18, + 236, + 255, + 15, + 113, + 184, + 255, + 184, + 223, + 139, + 0, + 40, + 177, + 119, + 254, + 182, + 123, + 90, + 255, + 176, + 165, + 176, + 0, + 247, + 77, + 194, + 0, + 27, + 234, + 120, + 0, + 231, + 0, + 214, + 255, + 59, + 39, + 30, + 0, + 125, + 99, + 145, + 255, + 150, + 68, + 68, + 1, + 141, + 222, + 248, + 0, + 153, + 123, + 210, + 255, + 110, + 127, + 152, + 255, + 229, + 33, + 214, + 1, + 135, + 221, + 197, + 0, + 137, + 97, + 2, + 0, + 12, + 143, + 204, + 255, + 81, + 41, + 188, + 0, + 115, + 79, + 130, + 255, + 94, + 3, + 132, + 0, + 152, + 175, + 187, + 255, + 124, + 141, + 10, + 255, + 126, + 192, + 179, + 255, + 11, + 103, + 198, + 0, + 149, + 6, + 45, + 0, + 219, + 85, + 187, + 1, + 230, + 18, + 178, + 255, + 72, + 182, + 152, + 0, + 3, + 198, + 184, + 255, + 128, + 112, + 224, + 1, + 97, + 161, + 230, + 0, + 254, + 99, + 38, + 255, + 58, + 159, + 197, + 0, + 151, + 66, + 219, + 0, + 59, + 69, + 143, + 255, + 185, + 112, + 249, + 0, + 119, + 136, + 47, + 255, + 123, + 130, + 132, + 0, + 168, + 71, + 95, + 255, + 113, + 176, + 40, + 1, + 232, + 185, + 173, + 0, + 207, + 93, + 117, + 1, + 68, + 157, + 108, + 255, + 102, + 5, + 147, + 254, + 49, + 97, + 33, + 0, + 89, + 65, + 111, + 254, + 247, + 30, + 163, + 255, + 124, + 217, + 221, + 1, + 102, + 250, + 216, + 0, + 198, + 174, + 75, + 254, + 57, + 55, + 18, + 0, + 227, + 5, + 236, + 1, + 229, + 213, + 173, + 0, + 201, + 109, + 218, + 1, + 49, + 233, + 239, + 0, + 30, + 55, + 158, + 1, + 25, + 178, + 106, + 0, + 155, + 111, + 188, + 1, + 94, + 126, + 140, + 0, + 215, + 31, + 238, + 1, + 77, + 240, + 16, + 0, + 213, + 242, + 25, + 1, + 38, + 71, + 168, + 0, + 205, + 186, + 93, + 254, + 49, + 211, + 140, + 255, + 219, + 0, + 180, + 255, + 134, + 118, + 165, + 0, + 160, + 147, + 134, + 255, + 110, + 186, + 35, + 255, + 198, + 243, + 42, + 0, + 243, + 146, + 119, + 0, + 134, + 235, + 163, + 1, + 4, + 241, + 135, + 255, + 193, + 46, + 193, + 254, + 103, + 180, + 79, + 255, + 225, + 4, + 184, + 254, + 242, + 118, + 130, + 0, + 146, + 135, + 176, + 1, + 234, + 111, + 30, + 0, + 69, + 66, + 213, + 254, + 41, + 96, + 123, + 0, + 121, + 94, + 42, + 255, + 178, + 191, + 195, + 255, + 46, + 130, + 42, + 0, + 117, + 84, + 8, + 255, + 233, + 49, + 214, + 254, + 238, + 122, + 109, + 0, + 6, + 71, + 89, + 1, + 236, + 211, + 123, + 0, + 244, + 13, + 48, + 254, + 119, + 148, + 14, + 0, + 114, + 28, + 86, + 255, + 75, + 237, + 25, + 255, + 145, + 229, + 16, + 254, + 129, + 100, + 53, + 255, + 134, + 150, + 120, + 254, + 168, + 157, + 50, + 0, + 23, + 72, + 104, + 255, + 224, + 49, + 14, + 0, + 255, + 123, + 22, + 255, + 151, + 185, + 151, + 255, + 170, + 80, + 184, + 1, + 134, + 182, + 20, + 0, + 41, + 100, + 101, + 1, + 153, + 33, + 16, + 0, + 76, + 154, + 111, + 1, + 86, + 206, + 234, + 255, + 192, + 160, + 164, + 254, + 165, + 123, + 93, + 255, + 1, + 216, + 164, + 254, + 67, + 17, + 175, + 255, + 169, + 11, + 59, + 255, + 158, + 41, + 61, + 255, + 73, + 188, + 14, + 255, + 195, + 6, + 137, + 255, + 22, + 147, + 29, + 255, + 20, + 103, + 3, + 255, + 246, + 130, + 227, + 255, + 122, + 40, + 128, + 0, + 226, + 47, + 24, + 254, + 35, + 36, + 32, + 0, + 152, + 186, + 183, + 255, + 69, + 202, + 20, + 0, + 195, + 133, + 195, + 0, + 222, + 51, + 247, + 0, + 169, + 171, + 94, + 1, + 183, + 0, + 160, + 255, + 64, + 205, + 18, + 1, + 156, + 83, + 15, + 255, + 197, + 58, + 249, + 254, + 251, + 89, + 110, + 255, + 50, + 10, + 88, + 254, + 51, + 43, + 216, + 0, + 98, + 242, + 198, + 1, + 245, + 151, + 113, + 0, + 171, + 236, + 194, + 1, + 197, + 31, + 199, + 255, + 229, + 81, + 38, + 1, + 41, + 59, + 20, + 0, + 253, + 104, + 230, + 0, + 152, + 93, + 14, + 255, + 246, + 242, + 146, + 254, + 214, + 169, + 240, + 255, + 240, + 102, + 108, + 254, + 160, + 167, + 236, + 0, + 154, + 218, + 188, + 0, + 150, + 233, + 202, + 255, + 27, + 19, + 250, + 1, + 2, + 71, + 133, + 255, + 175, + 12, + 63, + 1, + 145, + 183, + 198, + 0, + 104, + 120, + 115, + 255, + 130, + 251, + 247, + 0, + 17, + 212, + 167, + 255, + 62, + 123, + 132, + 255, + 247, + 100, + 189, + 0, + 155, + 223, + 152, + 0, + 143, + 197, + 33, + 0, + 155, + 59, + 44, + 255, + 150, + 93, + 240, + 1, + 127, + 3, + 87, + 255, + 95, + 71, + 207, + 1, + 167, + 85, + 1, + 255, + 188, + 152, + 116, + 255, + 10, + 23, + 23, + 0, + 137, + 195, + 93, + 1, + 54, + 98, + 97, + 0, + 240, + 0, + 168, + 255, + 148, + 188, + 127, + 0, + 134, + 107, + 151, + 0, + 76, + 253, + 171, + 0, + 90, + 132, + 192, + 0, + 146, + 22, + 54, + 0, + 224, + 66, + 54, + 254, + 230, + 186, + 229, + 255, + 39, + 182, + 196, + 0, + 148, + 251, + 130, + 255, + 65, + 131, + 108, + 254, + 128, + 1, + 160, + 0, + 169, + 49, + 167, + 254, + 199, + 254, + 148, + 255, + 251, + 6, + 131, + 0, + 187, + 254, + 129, + 255, + 85, + 82, + 62, + 0, + 178, + 23, + 58, + 255, + 254, + 132, + 5, + 0, + 164, + 213, + 39, + 0, + 134, + 252, + 146, + 254, + 37, + 53, + 81, + 255, + 155, + 134, + 82, + 0, + 205, + 167, + 238, + 255, + 94, + 45, + 180, + 255, + 132, + 40, + 161, + 0, + 254, + 111, + 112, + 1, + 54, + 75, + 217, + 0, + 179, + 230, + 221, + 1, + 235, + 94, + 191, + 255, + 23, + 243, + 48, + 1, + 202, + 145, + 203, + 255, + 39, + 118, + 42, + 255, + 117, + 141, + 253, + 0, + 254, + 0, + 222, + 0, + 43, + 251, + 50, + 0, + 54, + 169, + 234, + 1, + 80, + 68, + 208, + 0, + 148, + 203, + 243, + 254, + 145, + 7, + 135, + 0, + 6, + 254, + 0, + 0, + 252, + 185, + 127, + 0, + 98, + 8, + 129, + 255, + 38, + 35, + 72, + 255, + 211, + 36, + 220, + 1, + 40, + 26, + 89, + 0, + 168, + 64, + 197, + 254, + 3, + 222, + 239, + 255, + 2, + 83, + 215, + 254, + 180, + 159, + 105, + 0, + 58, + 115, + 194, + 0, + 186, + 116, + 106, + 255, + 229, + 247, + 219, + 255, + 129, + 118, + 193, + 0, + 202, + 174, + 183, + 1, + 166, + 161, + 72, + 0, + 201, + 107, + 147, + 254, + 237, + 136, + 74, + 0, + 233, + 230, + 106, + 1, + 105, + 111, + 168, + 0, + 64, + 224, + 30, + 1, + 1, + 229, + 3, + 0, + 102, + 151, + 175, + 255, + 194, + 238, + 228, + 255, + 254, + 250, + 212, + 0, + 187, + 237, + 121, + 0, + 67, + 251, + 96, + 1, + 197, + 30, + 11, + 0, + 183, + 95, + 204, + 0, + 205, + 89, + 138, + 0, + 64, + 221, + 37, + 1, + 255, + 223, + 30, + 255, + 178, + 48, + 211, + 255, + 241, + 200, + 90, + 255, + 167, + 209, + 96, + 255, + 57, + 130, + 221, + 0, + 46, + 114, + 200, + 255, + 61, + 184, + 66, + 0, + 55, + 182, + 24, + 254, + 110, + 182, + 33, + 0, + 171, + 190, + 232, + 255, + 114, + 94, + 31, + 0, + 18, + 221, + 8, + 0, + 47, + 231, + 254, + 0, + 255, + 112, + 83, + 0, + 118, + 15, + 215, + 255, + 173, + 25, + 40, + 254, + 192, + 193, + 31, + 255, + 238, + 21, + 146, + 255, + 171, + 193, + 118, + 255, + 101, + 234, + 53, + 254, + 131, + 212, + 112, + 0, + 89, + 192, + 107, + 1, + 8, + 208, + 27, + 0, + 181, + 217, + 15, + 255, + 231, + 149, + 232, + 0, + 140, + 236, + 126, + 0, + 144, + 9, + 199, + 255, + 12, + 79, + 181, + 254, + 147, + 182, + 202, + 255, + 19, + 109, + 182, + 255, + 49, + 212, + 225, + 0, + 74, + 163, + 203, + 0, + 175, + 233, + 148, + 0, + 26, + 112, + 51, + 0, + 193, + 193, + 9, + 255, + 15, + 135, + 249, + 0, + 150, + 227, + 130, + 0, + 204, + 0, + 219, + 1, + 24, + 242, + 205, + 0, + 238, + 208, + 117, + 255, + 22, + 244, + 112, + 0, + 26, + 229, + 34, + 0, + 37, + 80, + 188, + 255, + 38, + 45, + 206, + 254, + 240, + 90, + 225, + 255, + 29, + 3, + 47, + 255, + 42, + 224, + 76, + 0, + 186, + 243, + 167, + 0, + 32, + 132, + 15, + 255, + 5, + 51, + 125, + 0, + 139, + 135, + 24, + 0, + 6, + 241, + 219, + 0, + 172, + 229, + 133, + 255, + 246, + 214, + 50, + 0, + 231, + 11, + 207, + 255, + 191, + 126, + 83, + 1, + 180, + 163, + 170, + 255, + 245, + 56, + 24, + 1, + 178, + 164, + 211, + 255, + 3, + 16, + 202, + 1, + 98, + 57, + 118, + 255, + 141, + 131, + 89, + 254, + 33, + 51, + 24, + 0, + 243, + 149, + 91, + 255, + 253, + 52, + 14, + 0, + 35, + 169, + 67, + 254, + 49, + 30, + 88, + 255, + 179, + 27, + 36, + 255, + 165, + 140, + 183, + 0, + 58, + 189, + 151, + 0, + 88, + 31, + 0, + 0, + 75, + 169, + 66, + 0, + 66, + 101, + 199, + 255, + 24, + 216, + 199, + 1, + 121, + 196, + 26, + 255, + 14, + 79, + 203, + 254, + 240, + 226, + 81, + 255, + 94, + 28, + 10, + 255, + 83, + 193, + 240, + 255, + 204, + 193, + 131, + 255, + 94, + 15, + 86, + 0, + 218, + 40, + 157, + 0, + 51, + 193, + 209, + 0, + 0, + 242, + 177, + 0, + 102, + 185, + 247, + 0, + 158, + 109, + 116, + 0, + 38, + 135, + 91, + 0, + 223, + 175, + 149, + 0, + 220, + 66, + 1, + 255, + 86, + 60, + 232, + 0, + 25, + 96, + 37, + 255, + 225, + 122, + 162, + 1, + 215, + 187, + 168, + 255, + 158, + 157, + 46, + 0, + 56, + 171, + 162, + 0, + 232, + 240, + 101, + 1, + 122, + 22, + 9, + 0, + 51, + 9, + 21, + 255, + 53, + 25, + 238, + 255, + 217, + 30, + 232, + 254, + 125, + 169, + 148, + 0, + 13, + 232, + 102, + 0, + 148, + 9, + 37, + 0, + 165, + 97, + 141, + 1, + 228, + 131, + 41, + 0, + 222, + 15, + 243, + 255, + 254, + 18, + 17, + 0, + 6, + 60, + 237, + 1, + 106, + 3, + 113, + 0, + 59, + 132, + 189, + 0, + 92, + 112, + 30, + 0, + 105, + 208, + 213, + 0, + 48, + 84, + 179, + 255, + 187, + 121, + 231, + 254, + 27, + 216, + 109, + 255, + 162, + 221, + 107, + 254, + 73, + 239, + 195, + 255, + 250, + 31, + 57, + 255, + 149, + 135, + 89, + 255, + 185, + 23, + 115, + 1, + 3, + 163, + 157, + 255, + 18, + 112, + 250, + 0, + 25, + 57, + 187, + 255, + 161, + 96, + 164, + 0, + 47, + 16, + 243, + 0, + 12, + 141, + 251, + 254, + 67, + 234, + 184, + 255, + 41, + 18, + 161, + 0, + 175, + 6, + 96, + 255, + 160, + 172, + 52, + 254, + 24, + 176, + 183, + 255, + 198, + 193, + 85, + 1, + 124, + 121, + 137, + 255, + 151, + 50, + 114, + 255, + 220, + 203, + 60, + 255, + 207, + 239, + 5, + 1, + 0, + 38, + 107, + 255, + 55, + 238, + 94, + 254, + 70, + 152, + 94, + 0, + 213, + 220, + 77, + 1, + 120, + 17, + 69, + 255, + 85, + 164, + 190, + 255, + 203, + 234, + 81, + 0, + 38, + 49, + 37, + 254, + 61, + 144, + 124, + 0, + 137, + 78, + 49, + 254, + 168, + 247, + 48, + 0, + 95, + 164, + 252, + 0, + 105, + 169, + 135, + 0, + 253, + 228, + 134, + 0, + 64, + 166, + 75, + 0, + 81, + 73, + 20, + 255, + 207, + 210, + 10, + 0, + 234, + 106, + 150, + 255, + 94, + 34, + 90, + 255, + 254, + 159, + 57, + 254, + 220, + 133, + 99, + 0, + 139, + 147, + 180, + 254, + 24, + 23, + 185, + 0, + 41, + 57, + 30, + 255, + 189, + 97, + 76, + 0, + 65, + 187, + 223, + 255, + 224, + 172, + 37, + 255, + 34, + 62, + 95, + 1, + 231, + 144, + 240, + 0, + 77, + 106, + 126, + 254, + 64, + 152, + 91, + 0, + 29, + 98, + 155, + 0, + 226, + 251, + 53, + 255, + 234, + 211, + 5, + 255, + 144, + 203, + 222, + 255, + 164, + 176, + 221, + 254, + 5, + 231, + 24, + 0, + 179, + 122, + 205, + 0, + 36, + 1, + 134, + 255, + 125, + 70, + 151, + 254, + 97, + 228, + 252, + 0, + 172, + 129, + 23, + 254, + 48, + 90, + 209, + 255, + 150, + 224, + 82, + 1, + 84, + 134, + 30, + 0, + 241, + 196, + 46, + 0, + 103, + 113, + 234, + 255, + 46, + 101, + 121, + 254, + 40, + 124, + 250, + 255, + 135, + 45, + 242, + 254, + 9, + 249, + 168, + 255, + 140, + 108, + 131, + 255, + 143, + 163, + 171, + 0, + 50, + 173, + 199, + 255, + 88, + 222, + 142, + 255, + 200, + 95, + 158, + 0, + 142, + 192, + 163, + 255, + 7, + 117, + 135, + 0, + 111, + 124, + 22, + 0, + 236, + 12, + 65, + 254, + 68, + 38, + 65, + 255, + 227, + 174, + 254, + 0, + 244, + 245, + 38, + 0, + 240, + 50, + 208, + 255, + 161, + 63, + 250, + 0, + 60, + 209, + 239, + 0, + 122, + 35, + 19, + 0, + 14, + 33, + 230, + 254, + 2, + 159, + 113, + 0, + 106, + 20, + 127, + 255, + 228, + 205, + 96, + 0, + 137, + 210, + 174, + 254, + 180, + 212, + 144, + 255, + 89, + 98, + 154, + 1, + 34, + 88, + 139, + 0, + 167, + 162, + 112, + 1, + 65, + 110, + 197, + 0, + 241, + 37, + 169, + 0, + 66, + 56, + 131, + 255, + 10, + 201, + 83, + 254, + 133, + 253, + 187, + 255, + 177, + 112, + 45, + 254, + 196, + 251, + 0, + 0, + 196, + 250, + 151, + 255, + 238, + 232, + 214, + 255, + 150, + 209, + 205, + 0, + 28, + 240, + 118, + 0, + 71, + 76, + 83, + 1, + 236, + 99, + 91, + 0, + 42, + 250, + 131, + 1, + 96, + 18, + 64, + 255, + 118, + 222, + 35, + 0, + 113, + 214, + 203, + 255, + 122, + 119, + 184, + 255, + 66, + 19, + 36, + 0, + 204, + 64, + 249, + 0, + 146, + 89, + 139, + 0, + 134, + 62, + 135, + 1, + 104, + 233, + 101, + 0, + 188, + 84, + 26, + 0, + 49, + 249, + 129, + 0, + 208, + 214, + 75, + 255, + 207, + 130, + 77, + 255, + 115, + 175, + 235, + 0, + 171, + 2, + 137, + 255, + 175, + 145, + 186, + 1, + 55, + 245, + 135, + 255, + 154, + 86, + 181, + 1, + 100, + 58, + 246, + 255, + 109, + 199, + 60, + 255, + 82, + 204, + 134, + 255, + 215, + 49, + 230, + 1, + 140, + 229, + 192, + 255, + 222, + 193, + 251, + 255, + 81, + 136, + 15, + 255, + 179, + 149, + 162, + 255, + 23, + 39, + 29, + 255, + 7, + 95, + 75, + 254, + 191, + 81, + 222, + 0, + 241, + 81, + 90, + 255, + 107, + 49, + 201, + 255, + 244, + 211, + 157, + 0, + 222, + 140, + 149, + 255, + 65, + 219, + 56, + 254, + 189, + 246, + 90, + 255, + 178, + 59, + 157, + 1, + 48, + 219, + 52, + 0, + 98, + 34, + 215, + 0, + 28, + 17, + 187, + 255, + 175, + 169, + 24, + 0, + 92, + 79, + 161, + 255, + 236, + 200, + 194, + 1, + 147, + 143, + 234, + 0, + 229, + 225, + 7, + 1, + 197, + 168, + 14, + 0, + 235, + 51, + 53, + 1, + 253, + 120, + 174, + 0, + 197, + 6, + 168, + 255, + 202, + 117, + 171, + 0, + 163, + 21, + 206, + 0, + 114, + 85, + 90, + 255, + 15, + 41, + 10, + 255, + 194, + 19, + 99, + 0, + 65, + 55, + 216, + 254, + 162, + 146, + 116, + 0, + 50, + 206, + 212, + 255, + 64, + 146, + 29, + 255, + 158, + 158, + 131, + 1, + 100, + 165, + 130, + 255, + 172, + 23, + 129, + 255, + 125, + 53, + 9, + 255, + 15, + 193, + 18, + 1, + 26, + 49, + 11, + 255, + 181, + 174, + 201, + 1, + 135, + 201, + 14, + 255, + 100, + 19, + 149, + 0, + 219, + 98, + 79, + 0, + 42, + 99, + 143, + 254, + 96, + 0, + 48, + 255, + 197, + 249, + 83, + 254, + 104, + 149, + 79, + 255, + 235, + 110, + 136, + 254, + 82, + 128, + 44, + 255, + 65, + 41, + 36, + 254, + 88, + 211, + 10, + 0, + 187, + 121, + 187, + 0, + 98, + 134, + 199, + 0, + 171, + 188, + 179, + 254, + 210, + 11, + 238, + 255, + 66, + 123, + 130, + 254, + 52, + 234, + 61, + 0, + 48, + 113, + 23, + 254, + 6, + 86, + 120, + 255, + 119, + 178, + 245, + 0, + 87, + 129, + 201, + 0, + 242, + 141, + 209, + 0, + 202, + 114, + 85, + 0, + 148, + 22, + 161, + 0, + 103, + 195, + 48, + 0, + 25, + 49, + 171, + 255, + 138, + 67, + 130, + 0, + 182, + 73, + 122, + 254, + 148, + 24, + 130, + 0, + 211, + 229, + 154, + 0, + 32, + 155, + 158, + 0, + 84, + 105, + 61, + 0, + 177, + 194, + 9, + 255, + 166, + 89, + 86, + 1, + 54, + 83, + 187, + 0, + 249, + 40, + 117, + 255, + 109, + 3, + 215, + 255, + 53, + 146, + 44, + 1, + 63, + 47, + 179, + 0, + 194, + 216, + 3, + 254, + 14, + 84, + 136, + 0, + 136, + 177, + 13, + 255, + 72, + 243, + 186, + 255, + 117, + 17, + 125, + 255, + 211, + 58, + 211, + 255, + 93, + 79, + 223, + 0, + 90, + 88, + 245, + 255, + 139, + 209, + 111, + 255, + 70, + 222, + 47, + 0, + 10, + 246, + 79, + 255, + 198, + 217, + 178, + 0, + 227, + 225, + 11, + 1, + 78, + 126, + 179, + 255, + 62, + 43, + 126, + 0, + 103, + 148, + 35, + 0, + 129, + 8, + 165, + 254, + 245, + 240, + 148, + 0, + 61, + 51, + 142, + 0, + 81, + 208, + 134, + 0, + 15, + 137, + 115, + 255, + 211, + 119, + 236, + 255, + 159, + 245, + 248, + 255, + 2, + 134, + 136, + 255, + 230, + 139, + 58, + 1, + 160, + 164, + 254, + 0, + 114, + 85, + 141, + 255, + 49, + 166, + 182, + 255, + 144, + 70, + 84, + 1, + 85, + 182, + 7, + 0, + 46, + 53, + 93, + 0, + 9, + 166, + 161, + 255, + 55, + 162, + 178, + 255, + 45, + 184, + 188, + 0, + 146, + 28, + 44, + 254, + 169, + 90, + 49, + 0, + 120, + 178, + 241, + 1, + 14, + 123, + 127, + 255, + 7, + 241, + 199, + 1, + 189, + 66, + 50, + 255, + 198, + 143, + 101, + 254, + 189, + 243, + 135, + 255, + 141, + 24, + 24, + 254, + 75, + 97, + 87, + 0, + 118, + 251, + 154, + 1, + 237, + 54, + 156, + 0, + 171, + 146, + 207, + 255, + 131, + 196, + 246, + 255, + 136, + 64, + 113, + 1, + 151, + 232, + 57, + 0, + 240, + 218, + 115, + 0, + 49, + 61, + 27, + 255, + 64, + 129, + 73, + 1, + 252, + 169, + 27, + 255, + 40, + 132, + 10, + 1, + 90, + 201, + 193, + 255, + 252, + 121, + 240, + 1, + 186, + 206, + 41, + 0, + 43, + 198, + 97, + 0, + 145, + 100, + 183, + 0, + 204, + 216, + 80, + 254, + 172, + 150, + 65, + 0, + 249, + 229, + 196, + 254, + 104, + 123, + 73, + 255, + 77, + 104, + 96, + 254, + 130, + 180, + 8, + 0, + 104, + 123, + 57, + 0, + 220, + 202, + 229, + 255, + 102, + 249, + 211, + 0, + 86, + 14, + 232, + 255, + 182, + 78, + 209, + 0, + 239, + 225, + 164, + 0, + 106, + 13, + 32, + 255, + 120, + 73, + 17, + 255, + 134, + 67, + 233, + 0, + 83, + 254, + 181, + 0, + 183, + 236, + 112, + 1, + 48, + 64, + 131, + 255, + 241, + 216, + 243, + 255, + 65, + 193, + 226, + 0, + 206, + 241, + 100, + 254, + 100, + 134, + 166, + 255, + 237, + 202, + 197, + 0, + 55, + 13, + 81, + 0, + 32, + 124, + 102, + 255, + 40, + 228, + 177, + 0, + 118, + 181, + 31, + 1, + 231, + 160, + 134, + 255, + 119, + 187, + 202, + 0, + 0, + 142, + 60, + 255, + 128, + 38, + 189, + 255, + 166, + 201, + 150, + 0, + 207, + 120, + 26, + 1, + 54, + 184, + 172, + 0, + 12, + 242, + 204, + 254, + 133, + 66, + 230, + 0, + 34, + 38, + 31, + 1, + 184, + 112, + 80, + 0, + 32, + 51, + 165, + 254, + 191, + 243, + 55, + 0, + 58, + 73, + 146, + 254, + 155, + 167, + 205, + 255, + 100, + 104, + 152, + 255, + 197, + 254, + 207, + 255, + 173, + 19, + 247, + 0, + 238, + 10, + 202, + 0, + 239, + 151, + 242, + 0, + 94, + 59, + 39, + 255, + 240, + 29, + 102, + 255, + 10, + 92, + 154, + 255, + 229, + 84, + 219, + 255, + 161, + 129, + 80, + 0, + 208, + 90, + 204, + 1, + 240, + 219, + 174, + 255, + 158, + 102, + 145, + 1, + 53, + 178, + 76, + 255, + 52, + 108, + 168, + 1, + 83, + 222, + 107, + 0, + 211, + 36, + 109, + 0, + 118, + 58, + 56, + 0, + 8, + 29, + 22, + 0, + 237, + 160, + 199, + 0, + 170, + 209, + 157, + 0, + 137, + 71, + 47, + 0, + 143, + 86, + 32, + 0, + 198, + 242, + 2, + 0, + 212, + 48, + 136, + 1, + 92, + 172, + 186, + 0, + 230, + 151, + 105, + 1, + 96, + 191, + 229, + 0, + 138, + 80, + 191, + 254, + 240, + 216, + 130, + 255, + 98, + 43, + 6, + 254, + 168, + 196, + 49, + 0, + 253, + 18, + 91, + 1, + 144, + 73, + 121, + 0, + 61, + 146, + 39, + 1, + 63, + 104, + 24, + 255, + 184, + 165, + 112, + 254, + 126, + 235, + 98, + 0, + 80, + 213, + 98, + 255, + 123, + 60, + 87, + 255, + 82, + 140, + 245, + 1, + 223, + 120, + 173, + 255, + 15, + 198, + 134, + 1, + 206, + 60, + 239, + 0, + 231, + 234, + 92, + 255, + 33, + 238, + 19, + 255, + 165, + 113, + 142, + 1, + 176, + 119, + 38, + 0, + 160, + 43, + 166, + 254, + 239, + 91, + 105, + 0, + 107, + 61, + 194, + 1, + 25, + 4, + 68, + 0, + 15, + 139, + 51, + 0, + 164, + 132, + 106, + 255, + 34, + 116, + 46, + 254, + 168, + 95, + 197, + 0, + 137, + 212, + 23, + 0, + 72, + 156, + 58, + 0, + 137, + 112, + 69, + 254, + 150, + 105, + 154, + 255, + 236, + 201, + 157, + 0, + 23, + 212, + 154, + 255, + 136, + 82, + 227, + 254, + 226, + 59, + 221, + 255, + 95, + 149, + 192, + 0, + 81, + 118, + 52, + 255, + 33, + 43, + 215, + 1, + 14, + 147, + 75, + 255, + 89, + 156, + 121, + 254, + 14, + 18, + 79, + 0, + 147, + 208, + 139, + 1, + 151, + 218, + 62, + 255, + 156, + 88, + 8, + 1, + 210, + 184, + 98, + 255, + 20, + 175, + 123, + 255, + 102, + 83, + 229, + 0, + 220, + 65, + 116, + 1, + 150, + 250, + 4, + 255, + 92, + 142, + 220, + 255, + 34, + 247, + 66, + 255, + 204, + 225, + 179, + 254, + 151, + 81, + 151, + 0, + 71, + 40, + 236, + 255, + 138, + 63, + 62, + 0, + 6, + 79, + 240, + 255, + 183, + 185, + 181, + 0, + 118, + 50, + 27, + 0, + 63, + 227, + 192, + 0, + 123, + 99, + 58, + 1, + 50, + 224, + 155, + 255, + 17, + 225, + 223, + 254, + 220, + 224, + 77, + 255, + 14, + 44, + 123, + 1, + 141, + 128, + 175, + 0, + 248, + 212, + 200, + 0, + 150, + 59, + 183, + 255, + 147, + 97, + 29, + 0, + 150, + 204, + 181, + 0, + 253, + 37, + 71, + 0, + 145, + 85, + 119, + 0, + 154, + 200, + 186, + 0, + 2, + 128, + 249, + 255, + 83, + 24, + 124, + 0, + 14, + 87, + 143, + 0, + 168, + 51, + 245, + 1, + 124, + 151, + 231, + 255, + 208, + 240, + 197, + 1, + 124, + 190, + 185, + 0, + 48, + 58, + 246, + 0, + 20, + 233, + 232, + 0, + 125, + 18, + 98, + 255, + 13, + 254, + 31, + 255, + 245, + 177, + 130, + 255, + 108, + 142, + 35, + 0, + 171, + 125, + 242, + 254, + 140, + 12, + 34, + 255, + 165, + 161, + 162, + 0, + 206, + 205, + 101, + 0, + 247, + 25, + 34, + 1, + 100, + 145, + 57, + 0, + 39, + 70, + 57, + 0, + 118, + 204, + 203, + 255, + 242, + 0, + 162, + 0, + 165, + 244, + 30, + 0, + 198, + 116, + 226, + 0, + 128, + 111, + 153, + 255, + 140, + 54, + 182, + 1, + 60, + 122, + 15, + 255, + 155, + 58, + 57, + 1, + 54, + 50, + 198, + 0, + 171, + 211, + 29, + 255, + 107, + 138, + 167, + 255, + 173, + 107, + 199, + 255, + 109, + 161, + 193, + 0, + 89, + 72, + 242, + 255, + 206, + 115, + 89, + 255, + 250, + 254, + 142, + 254, + 177, + 202, + 94, + 255, + 81, + 89, + 50, + 0, + 7, + 105, + 66, + 255, + 25, + 254, + 255, + 254, + 203, + 64, + 23, + 255, + 79, + 222, + 108, + 255, + 39, + 249, + 75, + 0, + 241, + 124, + 50, + 0, + 239, + 152, + 133, + 0, + 221, + 241, + 105, + 0, + 147, + 151, + 98, + 0, + 213, + 161, + 121, + 254, + 242, + 49, + 137, + 0, + 233, + 37, + 249, + 254, + 42, + 183, + 27, + 0, + 184, + 119, + 230, + 255, + 217, + 32, + 163, + 255, + 208, + 251, + 228, + 1, + 137, + 62, + 131, + 255, + 79, + 64, + 9, + 254, + 94, + 48, + 113, + 0, + 17, + 138, + 50, + 254, + 193, + 255, + 22, + 0, + 247, + 18, + 197, + 1, + 67, + 55, + 104, + 0, + 16, + 205, + 95, + 255, + 48, + 37, + 66, + 0, + 55, + 156, + 63, + 1, + 64, + 82, + 74, + 255, + 200, + 53, + 71, + 254, + 239, + 67, + 125, + 0, + 26, + 224, + 222, + 0, + 223, + 137, + 93, + 255, + 30, + 224, + 202, + 255, + 9, + 220, + 132, + 0, + 198, + 38, + 235, + 1, + 102, + 141, + 86, + 0, + 60, + 43, + 81, + 1, + 136, + 28, + 26, + 0, + 233, + 36, + 8, + 254, + 207, + 242, + 148, + 0, + 164, + 162, + 63, + 0, + 51, + 46, + 224, + 255, + 114, + 48, + 79, + 255, + 9, + 175, + 226, + 0, + 222, + 3, + 193, + 255, + 47, + 160, + 232, + 255, + 255, + 93, + 105, + 254, + 14, + 42, + 230, + 0, + 26, + 138, + 82, + 1, + 208, + 43, + 244, + 0, + 27, + 39, + 38, + 255, + 98, + 208, + 127, + 255, + 64, + 149, + 182, + 255, + 5, + 250, + 209, + 0, + 187, + 60, + 28, + 254, + 49, + 25, + 218, + 255, + 169, + 116, + 205, + 255, + 119, + 18, + 120, + 0, + 156, + 116, + 147, + 255, + 132, + 53, + 109, + 255, + 13, + 10, + 202, + 0, + 110, + 83, + 167, + 0, + 157, + 219, + 137, + 255, + 6, + 3, + 130, + 255, + 50, + 167, + 30, + 255, + 60, + 159, + 47, + 255, + 129, + 128, + 157, + 254, + 94, + 3, + 189, + 0, + 3, + 166, + 68, + 0, + 83, + 223, + 215, + 0, + 150, + 90, + 194, + 1, + 15, + 168, + 65, + 0, + 227, + 83, + 51, + 255, + 205, + 171, + 66, + 255, + 54, + 187, + 60, + 1, + 152, + 102, + 45, + 255, + 119, + 154, + 225, + 0, + 240, + 247, + 136, + 0, + 100, + 197, + 178, + 255, + 139, + 71, + 223, + 255, + 204, + 82, + 16, + 1, + 41, + 206, + 42, + 255, + 156, + 192, + 221, + 255, + 216, + 123, + 244, + 255, + 218, + 218, + 185, + 255, + 187, + 186, + 239, + 255, + 252, + 172, + 160, + 255, + 195, + 52, + 22, + 0, + 144, + 174, + 181, + 254, + 187, + 100, + 115, + 255, + 211, + 78, + 176, + 255, + 27, + 7, + 193, + 0, + 147, + 213, + 104, + 255, + 90, + 201, + 10, + 255, + 80, + 123, + 66, + 1, + 22, + 33, + 186, + 0, + 1, + 7, + 99, + 254, + 30, + 206, + 10, + 0, + 229, + 234, + 5, + 0, + 53, + 30, + 210, + 0, + 138, + 8, + 220, + 254, + 71, + 55, + 167, + 0, + 72, + 225, + 86, + 1, + 118, + 190, + 188, + 0, + 254, + 193, + 101, + 1, + 171, + 249, + 172, + 255, + 94, + 158, + 183, + 254, + 93, + 2, + 108, + 255, + 176, + 93, + 76, + 255, + 73, + 99, + 79, + 255, + 74, + 64, + 129, + 254, + 246, + 46, + 65, + 0, + 99, + 241, + 127, + 254, + 246, + 151, + 102, + 255, + 44, + 53, + 208, + 254, + 59, + 102, + 234, + 0, + 154, + 175, + 164, + 255, + 88, + 242, + 32, + 0, + 111, + 38, + 1, + 0, + 255, + 182, + 190, + 255, + 115, + 176, + 15, + 254, + 169, + 60, + 129, + 0, + 122, + 237, + 241, + 0, + 90, + 76, + 63, + 0, + 62, + 74, + 120, + 255, + 122, + 195, + 110, + 0, + 119, + 4, + 178, + 0, + 222, + 242, + 210, + 0, + 130, + 33, + 46, + 254, + 156, + 40, + 41, + 0, + 167, + 146, + 112, + 1, + 49, + 163, + 111, + 255, + 121, + 176, + 235, + 0, + 76, + 207, + 14, + 255, + 3, + 25, + 198, + 1, + 41, + 235, + 213, + 0, + 85, + 36, + 214, + 1, + 49, + 92, + 109, + 255, + 200, + 24, + 30, + 254, + 168, + 236, + 195, + 0, + 145, + 39, + 124, + 1, + 236, + 195, + 149, + 0, + 90, + 36, + 184, + 255, + 67, + 85, + 170, + 255, + 38, + 35, + 26, + 254, + 131, + 124, + 68, + 255, + 239, + 155, + 35, + 255, + 54, + 201, + 164, + 0, + 196, + 22, + 117, + 255, + 49, + 15, + 205, + 0, + 24, + 224, + 29, + 1, + 126, + 113, + 144, + 0, + 117, + 21, + 182, + 0, + 203, + 159, + 141, + 0, + 223, + 135, + 77, + 0, + 176, + 230, + 176, + 255, + 190, + 229, + 215, + 255, + 99, + 37, + 181, + 255, + 51, + 21, + 138, + 255, + 25, + 189, + 89, + 255, + 49, + 48, + 165, + 254, + 152, + 45, + 247, + 0, + 170, + 108, + 222, + 0, + 80, + 202, + 5, + 0, + 27, + 69, + 103, + 254, + 204, + 22, + 129, + 255, + 180, + 252, + 62, + 254, + 210, + 1, + 91, + 255, + 146, + 110, + 254, + 255, + 219, + 162, + 28, + 0, + 223, + 252, + 213, + 1, + 59, + 8, + 33, + 0, + 206, + 16, + 244, + 0, + 129, + 211, + 48, + 0, + 107, + 160, + 208, + 0, + 112, + 59, + 209, + 0, + 109, + 77, + 216, + 254, + 34, + 21, + 185, + 255, + 246, + 99, + 56, + 255, + 179, + 139, + 19, + 255, + 185, + 29, + 50, + 255, + 84, + 89, + 19, + 0, + 74, + 250, + 98, + 255, + 225, + 42, + 200, + 255, + 192, + 217, + 205, + 255, + 210, + 16, + 167, + 0, + 99, + 132, + 95, + 1, + 43, + 230, + 57, + 0, + 254, + 11, + 203, + 255, + 99, + 188, + 63, + 255, + 119, + 193, + 251, + 254, + 80, + 105, + 54, + 0, + 232, + 181, + 189, + 1, + 183, + 69, + 112, + 255, + 208, + 171, + 165, + 255, + 47, + 109, + 180, + 255, + 123, + 83, + 165, + 0, + 146, + 162, + 52, + 255, + 154, + 11, + 4, + 255, + 151, + 227, + 90, + 255, + 146, + 137, + 97, + 254, + 61, + 233, + 41, + 255, + 94, + 42, + 55, + 255, + 108, + 164, + 236, + 0, + 152, + 68, + 254, + 0, + 10, + 140, + 131, + 255, + 10, + 106, + 79, + 254, + 243, + 158, + 137, + 0, + 67, + 178, + 66, + 254, + 177, + 123, + 198, + 255, + 15, + 62, + 34, + 0, + 197, + 88, + 42, + 255, + 149, + 95, + 177, + 255, + 152, + 0, + 198, + 255, + 149, + 254, + 113, + 255, + 225, + 90, + 163, + 255, + 125, + 217, + 247, + 0, + 18, + 17, + 224, + 0, + 128, + 66, + 120, + 254, + 192, + 25, + 9, + 255, + 50, + 221, + 205, + 0, + 49, + 212, + 70, + 0, + 233, + 255, + 164, + 0, + 2, + 209, + 9, + 0, + 221, + 52, + 219, + 254, + 172, + 224, + 244, + 255, + 94, + 56, + 206, + 1, + 242, + 179, + 2, + 255, + 31, + 91, + 164, + 1, + 230, + 46, + 138, + 255, + 189, + 230, + 220, + 0, + 57, + 47, + 61, + 255, + 111, + 11, + 157, + 0, + 177, + 91, + 152, + 0, + 28, + 230, + 98, + 0, + 97, + 87, + 126, + 0, + 198, + 89, + 145, + 255, + 167, + 79, + 107, + 0, + 249, + 77, + 160, + 1, + 29, + 233, + 230, + 255, + 150, + 21, + 86, + 254, + 60, + 11, + 193, + 0, + 151, + 37, + 36, + 254, + 185, + 150, + 243, + 255, + 228, + 212, + 83, + 1, + 172, + 151, + 180, + 0, + 201, + 169, + 155, + 0, + 244, + 60, + 234, + 0, + 142, + 235, + 4, + 1, + 67, + 218, + 60, + 0, + 192, + 113, + 75, + 1, + 116, + 243, + 207, + 255, + 65, + 172, + 155, + 0, + 81, + 30, + 156, + 255, + 80, + 72, + 33, + 254, + 18, + 231, + 109, + 255, + 142, + 107, + 21, + 254, + 125, + 26, + 132, + 255, + 176, + 16, + 59, + 255, + 150, + 201, + 58, + 0, + 206, + 169, + 201, + 0, + 208, + 121, + 226, + 0, + 40, + 172, + 14, + 255, + 150, + 61, + 94, + 255, + 56, + 57, + 156, + 255, + 141, + 60, + 145, + 255, + 45, + 108, + 149, + 255, + 238, + 145, + 155, + 255, + 209, + 85, + 31, + 254, + 192, + 12, + 210, + 0, + 99, + 98, + 93, + 254, + 152, + 16, + 151, + 0, + 225, + 185, + 220, + 0, + 141, + 235, + 44, + 255, + 160, + 172, + 21, + 254, + 71, + 26, + 31, + 255, + 13, + 64, + 93, + 254, + 28, + 56, + 198, + 0, + 177, + 62, + 248, + 1, + 182, + 8, + 241, + 0, + 166, + 101, + 148, + 255, + 78, + 81, + 133, + 255, + 129, + 222, + 215, + 1, + 188, + 169, + 129, + 255, + 232, + 7, + 97, + 0, + 49, + 112, + 60, + 255, + 217, + 229, + 251, + 0, + 119, + 108, + 138, + 0, + 39, + 19, + 123, + 254, + 131, + 49, + 235, + 0, + 132, + 84, + 145, + 0, + 130, + 230, + 148, + 255, + 25, + 74, + 187, + 0, + 5, + 245, + 54, + 255, + 185, + 219, + 241, + 1, + 18, + 194, + 228, + 255, + 241, + 202, + 102, + 0, + 105, + 113, + 202, + 0, + 155, + 235, + 79, + 0, + 21, + 9, + 178, + 255, + 156, + 1, + 239, + 0, + 200, + 148, + 61, + 0, + 115, + 247, + 210, + 255, + 49, + 221, + 135, + 0, + 58, + 189, + 8, + 1, + 35, + 46, + 9, + 0, + 81, + 65, + 5, + 255, + 52, + 158, + 185, + 255, + 125, + 116, + 46, + 255, + 74, + 140, + 13, + 255, + 210, + 92, + 172, + 254, + 147, + 23, + 71, + 0, + 217, + 224, + 253, + 254, + 115, + 108, + 180, + 255, + 145, + 58, + 48, + 254, + 219, + 177, + 24, + 255, + 156, + 255, + 60, + 1, + 154, + 147, + 242, + 0, + 253, + 134, + 87, + 0, + 53, + 75, + 229, + 0, + 48, + 195, + 222, + 255, + 31, + 175, + 50, + 255, + 156, + 210, + 120, + 255, + 208, + 35, + 222, + 255, + 18, + 248, + 179, + 1, + 2, + 10, + 101, + 255, + 157, + 194, + 248, + 255, + 158, + 204, + 101, + 255, + 104, + 254, + 197, + 255, + 79, + 62, + 4, + 0, + 178, + 172, + 101, + 1, + 96, + 146, + 251, + 255, + 65, + 10, + 156, + 0, + 2, + 137, + 165, + 255, + 116, + 4, + 231, + 0, + 242, + 215, + 1, + 0, + 19, + 35, + 29, + 255, + 43, + 161, + 79, + 0, + 59, + 149, + 246, + 1, + 251, + 66, + 176, + 0, + 200, + 33, + 3, + 255, + 80, + 110, + 142, + 255, + 195, + 161, + 17, + 1, + 228, + 56, + 66, + 255, + 123, + 47, + 145, + 254, + 132, + 4, + 164, + 0, + 67, + 174, + 172, + 0, + 25, + 253, + 114, + 0, + 87, + 97, + 87, + 1, + 250, + 220, + 84, + 0, + 96, + 91, + 200, + 255, + 37, + 125, + 59, + 0, + 19, + 65, + 118, + 0, + 161, + 52, + 241, + 255, + 237, + 172, + 6, + 255, + 176, + 191, + 255, + 255, + 1, + 65, + 130, + 254, + 223, + 190, + 230, + 0, + 101, + 253, + 231, + 255, + 146, + 35, + 109, + 0, + 250, + 29, + 77, + 1, + 49, + 0, + 19, + 0, + 123, + 90, + 155, + 1, + 22, + 86, + 32, + 255, + 218, + 213, + 65, + 0, + 111, + 93, + 127, + 0, + 60, + 93, + 169, + 255, + 8, + 127, + 182, + 0, + 17, + 186, + 14, + 254, + 253, + 137, + 246, + 255, + 213, + 25, + 48, + 254, + 76, + 238, + 0, + 255, + 248, + 92, + 70, + 255, + 99, + 224, + 139, + 0, + 184, + 9, + 255, + 1, + 7, + 164, + 208, + 0, + 205, + 131, + 198, + 1, + 87, + 214, + 199, + 0, + 130, + 214, + 95, + 0, + 221, + 149, + 222, + 0, + 23, + 38, + 171, + 254, + 197, + 110, + 213, + 0, + 43, + 115, + 140, + 254, + 215, + 177, + 118, + 0, + 96, + 52, + 66, + 1, + 117, + 158, + 237, + 0, + 14, + 64, + 182, + 255, + 46, + 63, + 174, + 255, + 158, + 95, + 190, + 255, + 225, + 205, + 177, + 255, + 43, + 5, + 142, + 255, + 172, + 99, + 212, + 255, + 244, + 187, + 147, + 0, + 29, + 51, + 153, + 255, + 228, + 116, + 24, + 254, + 30, + 101, + 207, + 0, + 19, + 246, + 150, + 255, + 134, + 231, + 5, + 0, + 125, + 134, + 226, + 1, + 77, + 65, + 98, + 0, + 236, + 130, + 33, + 255, + 5, + 110, + 62, + 0, + 69, + 108, + 127, + 255, + 7, + 113, + 22, + 0, + 145, + 20, + 83, + 254, + 194, + 161, + 231, + 255, + 131, + 181, + 60, + 0, + 217, + 209, + 177, + 255, + 229, + 148, + 212, + 254, + 3, + 131, + 184, + 0, + 117, + 177, + 187, + 1, + 28, + 14, + 31, + 255, + 176, + 102, + 80, + 0, + 50, + 84, + 151, + 255, + 125, + 31, + 54, + 255, + 21, + 157, + 133, + 255, + 19, + 179, + 139, + 1, + 224, + 232, + 26, + 0, + 34, + 117, + 170, + 255, + 167, + 252, + 171, + 255, + 73, + 141, + 206, + 254, + 129, + 250, + 35, + 0, + 72, + 79, + 236, + 1, + 220, + 229, + 20, + 255, + 41, + 202, + 173, + 255, + 99, + 76, + 238, + 255, + 198, + 22, + 224, + 255, + 108, + 198, + 195, + 255, + 36, + 141, + 96, + 1, + 236, + 158, + 59, + 255, + 106, + 100, + 87, + 0, + 110, + 226, + 2, + 0, + 227, + 234, + 222, + 0, + 154, + 93, + 119, + 255, + 74, + 112, + 164, + 255, + 67, + 91, + 2, + 255, + 21, + 145, + 33, + 255, + 102, + 214, + 137, + 255, + 175, + 230, + 103, + 254, + 163, + 246, + 166, + 0, + 93, + 247, + 116, + 254, + 167, + 224, + 28, + 255, + 220, + 2, + 57, + 1, + 171, + 206, + 84, + 0, + 123, + 228, + 17, + 255, + 27, + 120, + 119, + 0, + 119, + 11, + 147, + 1, + 180, + 47, + 225, + 255, + 104, + 200, + 185, + 254, + 165, + 2, + 114, + 0, + 77, + 78, + 212, + 0, + 45, + 154, + 177, + 255, + 24, + 196, + 121, + 254, + 82, + 157, + 182, + 0, + 90, + 16, + 190, + 1, + 12, + 147, + 197, + 0, + 95, + 239, + 152, + 255, + 11, + 235, + 71, + 0, + 86, + 146, + 119, + 255, + 172, + 134, + 214, + 0, + 60, + 131, + 196, + 0, + 161, + 225, + 129, + 0, + 31, + 130, + 120, + 254, + 95, + 200, + 51, + 0, + 105, + 231, + 210, + 255, + 58, + 9, + 148, + 255, + 43, + 168, + 221, + 255, + 124, + 237, + 142, + 0, + 198, + 211, + 50, + 254, + 46, + 245, + 103, + 0, + 164, + 248, + 84, + 0, + 152, + 70, + 208, + 255, + 180, + 117, + 177, + 0, + 70, + 79, + 185, + 0, + 243, + 74, + 32, + 0, + 149, + 156, + 207, + 0, + 197, + 196, + 161, + 1, + 245, + 53, + 239, + 0, + 15, + 93, + 246, + 254, + 139, + 240, + 49, + 255, + 196, + 88, + 36, + 255, + 162, + 38, + 123, + 0, + 128, + 200, + 157, + 1, + 174, + 76, + 103, + 255, + 173, + 169, + 34, + 254, + 216, + 1, + 171, + 255, + 114, + 51, + 17, + 0, + 136, + 228, + 194, + 0, + 110, + 150, + 56, + 254, + 106, + 246, + 159, + 0, + 19, + 184, + 79, + 255, + 150, + 77, + 240, + 255, + 155, + 80, + 162, + 0, + 0, + 53, + 169, + 255, + 29, + 151, + 86, + 0, + 68, + 94, + 16, + 0, + 92, + 7, + 110, + 254, + 98, + 117, + 149, + 255, + 249, + 77, + 230, + 255, + 253, + 10, + 140, + 0, + 214, + 124, + 92, + 254, + 35, + 118, + 235, + 0, + 89, + 48, + 57, + 1, + 22, + 53, + 166, + 0, + 184, + 144, + 61, + 255, + 179, + 255, + 194, + 0, + 214, + 248, + 61, + 254, + 59, + 110, + 246, + 0, + 121, + 21, + 81, + 254, + 166, + 3, + 228, + 0, + 106, + 64, + 26, + 255, + 69, + 232, + 134, + 255, + 242, + 220, + 53, + 254, + 46, + 220, + 85, + 0, + 113, + 149, + 247, + 255, + 97, + 179, + 103, + 255, + 190, + 127, + 11, + 0, + 135, + 209, + 182, + 0, + 95, + 52, + 129, + 1, + 170, + 144, + 206, + 255, + 122, + 200, + 204, + 255, + 168, + 100, + 146, + 0, + 60, + 144, + 149, + 254, + 70, + 60, + 40, + 0, + 122, + 52, + 177, + 255, + 246, + 211, + 101, + 255, + 174, + 237, + 8, + 0, + 7, + 51, + 120, + 0, + 19, + 31, + 173, + 0, + 126, + 239, + 156, + 255, + 143, + 189, + 203, + 0, + 196, + 128, + 88, + 255, + 233, + 133, + 226, + 255, + 30, + 125, + 173, + 255, + 201, + 108, + 50, + 0, + 123, + 100, + 59, + 255, + 254, + 163, + 3, + 1, + 221, + 148, + 181, + 255, + 214, + 136, + 57, + 254, + 222, + 180, + 137, + 255, + 207, + 88, + 54, + 255, + 28, + 33, + 251, + 255, + 67, + 214, + 52, + 1, + 210, + 208, + 100, + 0, + 81, + 170, + 94, + 0, + 145, + 40, + 53, + 0, + 224, + 111, + 231, + 254, + 35, + 28, + 244, + 255, + 226, + 199, + 195, + 254, + 238, + 17, + 230, + 0, + 217, + 217, + 164, + 254, + 169, + 157, + 221, + 0, + 218, + 46, + 162, + 1, + 199, + 207, + 163, + 255, + 108, + 115, + 162, + 1, + 14, + 96, + 187, + 255, + 118, + 60, + 76, + 0, + 184, + 159, + 152, + 0, + 209, + 231, + 71, + 254, + 42, + 164, + 186, + 255, + 186, + 153, + 51, + 254, + 221, + 171, + 182, + 255, + 162, + 142, + 173, + 0, + 235, + 47, + 193, + 0, + 7, + 139, + 16, + 1, + 95, + 164, + 64, + 255, + 16, + 221, + 166, + 0, + 219, + 197, + 16, + 0, + 132, + 29, + 44, + 255, + 100, + 69, + 117, + 255, + 60, + 235, + 88, + 254, + 40, + 81, + 173, + 0, + 71, + 190, + 61, + 255, + 187, + 88, + 157, + 0, + 231, + 11, + 23, + 0, + 237, + 117, + 164, + 0, + 225, + 168, + 223, + 255, + 154, + 114, + 116, + 255, + 163, + 152, + 242, + 1, + 24, + 32, + 170, + 0, + 125, + 98, + 113, + 254, + 168, + 19, + 76, + 0, + 17, + 157, + 220, + 254, + 155, + 52, + 5, + 0, + 19, + 111, + 161, + 255, + 71, + 90, + 252, + 255, + 173, + 110, + 240, + 0, + 10, + 198, + 121, + 255, + 253, + 255, + 240, + 255, + 66, + 123, + 210, + 0, + 221, + 194, + 215, + 254, + 121, + 163, + 17, + 255, + 225, + 7, + 99, + 0, + 190, + 49, + 182, + 0, + 115, + 9, + 133, + 1, + 232, + 26, + 138, + 255, + 213, + 68, + 132, + 0, + 44, + 119, + 122, + 255, + 179, + 98, + 51, + 0, + 149, + 90, + 106, + 0, + 71, + 50, + 230, + 255, + 10, + 153, + 118, + 255, + 177, + 70, + 25, + 0, + 165, + 87, + 205, + 0, + 55, + 138, + 234, + 0, + 238, + 30, + 97, + 0, + 113, + 155, + 207, + 0, + 98, + 153, + 127, + 0, + 34, + 107, + 219, + 254, + 117, + 114, + 172, + 255, + 76, + 180, + 255, + 254, + 242, + 57, + 179, + 255, + 221, + 34, + 172, + 254, + 56, + 162, + 49, + 255, + 83, + 3, + 255, + 255, + 113, + 221, + 189, + 255, + 188, + 25, + 228, + 254, + 16, + 88, + 89, + 255, + 71, + 28, + 198, + 254, + 22, + 17, + 149, + 255, + 243, + 121, + 254, + 255, + 107, + 202, + 99, + 255, + 9, + 206, + 14, + 1, + 220, + 47, + 153, + 0, + 107, + 137, + 39, + 1, + 97, + 49, + 194, + 255, + 149, + 51, + 197, + 254, + 186, + 58, + 11, + 255, + 107, + 43, + 232, + 1, + 200, + 6, + 14, + 255, + 181, + 133, + 65, + 254, + 221, + 228, + 171, + 255, + 123, + 62, + 231, + 1, + 227, + 234, + 179, + 255, + 34, + 189, + 212, + 254, + 244, + 187, + 249, + 0, + 190, + 13, + 80, + 1, + 130, + 89, + 1, + 0, + 223, + 133, + 173, + 0, + 9, + 222, + 198, + 255, + 66, + 127, + 74, + 0, + 167, + 216, + 93, + 255, + 155, + 168, + 198, + 1, + 66, + 145, + 0, + 0, + 68, + 102, + 46, + 1, + 172, + 90, + 154, + 0, + 216, + 128, + 75, + 255, + 160, + 40, + 51, + 0, + 158, + 17, + 27, + 1, + 124, + 240, + 49, + 0, + 236, + 202, + 176, + 255, + 151, + 124, + 192, + 255, + 38, + 193, + 190, + 0, + 95, + 182, + 61, + 0, + 163, + 147, + 124, + 255, + 255, + 165, + 51, + 255, + 28, + 40, + 17, + 254, + 215, + 96, + 78, + 0, + 86, + 145, + 218, + 254, + 31, + 36, + 202, + 255, + 86, + 9, + 5, + 0, + 111, + 41, + 200, + 255, + 237, + 108, + 97, + 0, + 57, + 62, + 44, + 0, + 117, + 184, + 15, + 1, + 45, + 241, + 116, + 0, + 152, + 1, + 220, + 255, + 157, + 165, + 188, + 0, + 250, + 15, + 131, + 1, + 60, + 44, + 125, + 255, + 65, + 220, + 251, + 255, + 75, + 50, + 184, + 0, + 53, + 90, + 128, + 255, + 231, + 80, + 194, + 255, + 136, + 129, + 127, + 1, + 21, + 18, + 187, + 255, + 45, + 58, + 161, + 255, + 71, + 147, + 34, + 0, + 174, + 249, + 11, + 254, + 35, + 141, + 29, + 0, + 239, + 68, + 177, + 255, + 115, + 110, + 58, + 0, + 238, + 190, + 177, + 1, + 87, + 245, + 166, + 255, + 190, + 49, + 247, + 255, + 146, + 83, + 184, + 255, + 173, + 14, + 39, + 255, + 146, + 215, + 104, + 0, + 142, + 223, + 120, + 0, + 149, + 200, + 155, + 255, + 212, + 207, + 145, + 1, + 16, + 181, + 217, + 0, + 173, + 32, + 87, + 255, + 255, + 35, + 181, + 0, + 119, + 223, + 161, + 1, + 200, + 223, + 94, + 255, + 70, + 6, + 186, + 255, + 192, + 67, + 85, + 255, + 50, + 169, + 152, + 0, + 144, + 26, + 123, + 255, + 56, + 243, + 179, + 254, + 20, + 68, + 136, + 0, + 39, + 140, + 188, + 254, + 253, + 208, + 5, + 255, + 200, + 115, + 135, + 1, + 43, + 172, + 229, + 255, + 156, + 104, + 187, + 0, + 151, + 251, + 167, + 0, + 52, + 135, + 23, + 0, + 151, + 153, + 72, + 0, + 147, + 197, + 107, + 254, + 148, + 158, + 5, + 255, + 238, + 143, + 206, + 0, + 126, + 153, + 137, + 255, + 88, + 152, + 197, + 254, + 7, + 68, + 167, + 0, + 252, + 159, + 165, + 255, + 239, + 78, + 54, + 255, + 24, + 63, + 55, + 255, + 38, + 222, + 94, + 0, + 237, + 183, + 12, + 255, + 206, + 204, + 210, + 0, + 19, + 39, + 246, + 254, + 30, + 74, + 231, + 0, + 135, + 108, + 29, + 1, + 179, + 115, + 0, + 0, + 117, + 118, + 116, + 1, + 132, + 6, + 252, + 255, + 145, + 129, + 161, + 1, + 105, + 67, + 141, + 0, + 82, + 37, + 226, + 255, + 238, + 226, + 228, + 255, + 204, + 214, + 129, + 254, + 162, + 123, + 100, + 255, + 185, + 121, + 234, + 0, + 45, + 108, + 231, + 0, + 66, + 8, + 56, + 255, + 132, + 136, + 128, + 0, + 172, + 224, + 66, + 254, + 175, + 157, + 188, + 0, + 230, + 223, + 226, + 254, + 242, + 219, + 69, + 0, + 184, + 14, + 119, + 1, + 82, + 162, + 56, + 0, + 114, + 123, + 20, + 0, + 162, + 103, + 85, + 255, + 49, + 239, + 99, + 254, + 156, + 135, + 215, + 0, + 111, + 255, + 167, + 254, + 39, + 196, + 214, + 0, + 144, + 38, + 79, + 1, + 249, + 168, + 125, + 0, + 155, + 97, + 156, + 255, + 23, + 52, + 219, + 255, + 150, + 22, + 144, + 0, + 44, + 149, + 165, + 255, + 40, + 127, + 183, + 0, + 196, + 77, + 233, + 255, + 118, + 129, + 210, + 255, + 170, + 135, + 230, + 255, + 214, + 119, + 198, + 0, + 233, + 240, + 35, + 0, + 253, + 52, + 7, + 255, + 117, + 102, + 48, + 255, + 21, + 204, + 154, + 255, + 179, + 136, + 177, + 255, + 23, + 2, + 3, + 1, + 149, + 130, + 89, + 255, + 252, + 17, + 159, + 1, + 70, + 60, + 26, + 0, + 144, + 107, + 17, + 0, + 180, + 190, + 60, + 255, + 56, + 182, + 59, + 255, + 110, + 71, + 54, + 255, + 198, + 18, + 129, + 255, + 149, + 224, + 87, + 255, + 223, + 21, + 152, + 255, + 138, + 22, + 182, + 255, + 250, + 156, + 205, + 0, + 236, + 45, + 208, + 255, + 79, + 148, + 242, + 1, + 101, + 70, + 209, + 0, + 103, + 78, + 174, + 0, + 101, + 144, + 172, + 255, + 152, + 136, + 237, + 1, + 191, + 194, + 136, + 0, + 113, + 80, + 125, + 1, + 152, + 4, + 141, + 0, + 155, + 150, + 53, + 255, + 196, + 116, + 245, + 0, + 239, + 114, + 73, + 254, + 19, + 82, + 17, + 255, + 124, + 125, + 234, + 255, + 40, + 52, + 191, + 0, + 42, + 210, + 158, + 255, + 155, + 132, + 165, + 0, + 178, + 5, + 42, + 1, + 64, + 92, + 40, + 255, + 36, + 85, + 77, + 255, + 178, + 228, + 118, + 0, + 137, + 66, + 96, + 254, + 115, + 226, + 66, + 0, + 110, + 240, + 69, + 254, + 151, + 111, + 80, + 0, + 167, + 174, + 236, + 255, + 227, + 108, + 107, + 255, + 188, + 242, + 65, + 255, + 183, + 81, + 255, + 0, + 57, + 206, + 181, + 255, + 47, + 34, + 181, + 255, + 213, + 240, + 158, + 1, + 71, + 75, + 95, + 0, + 156, + 40, + 24, + 255, + 102, + 210, + 81, + 0, + 171, + 199, + 228, + 255, + 154, + 34, + 41, + 0, + 227, + 175, + 75, + 0, + 21, + 239, + 195, + 0, + 138, + 229, + 95, + 1, + 76, + 192, + 49, + 0, + 117, + 123, + 87, + 1, + 227, + 225, + 130, + 0, + 125, + 62, + 63, + 255, + 2, + 198, + 171, + 0, + 254, + 36, + 13, + 254, + 145, + 186, + 206, + 0, + 148, + 255, + 244, + 255, + 35, + 0, + 166, + 0, + 30, + 150, + 219, + 1, + 92, + 228, + 212, + 0, + 92, + 198, + 60, + 254, + 62, + 133, + 200, + 255, + 201, + 41, + 59, + 0, + 125, + 238, + 109, + 255, + 180, + 163, + 238, + 1, + 140, + 122, + 82, + 0, + 9, + 22, + 88, + 255, + 197, + 157, + 47, + 255, + 153, + 94, + 57, + 0, + 88, + 30, + 182, + 0, + 84, + 161, + 85, + 0, + 178, + 146, + 124, + 0, + 166, + 166, + 7, + 255, + 21, + 208, + 223, + 0, + 156, + 182, + 242, + 0, + 155, + 121, + 185, + 0, + 83, + 156, + 174, + 254, + 154, + 16, + 118, + 255, + 186, + 83, + 232, + 1, + 223, + 58, + 121, + 255, + 29, + 23, + 88, + 0, + 35, + 125, + 127, + 255, + 170, + 5, + 149, + 254, + 164, + 12, + 130, + 255, + 155, + 196, + 29, + 0, + 161, + 96, + 136, + 0, + 7, + 35, + 29, + 1, + 162, + 37, + 251, + 0, + 3, + 46, + 242, + 255, + 0, + 217, + 188, + 0, + 57, + 174, + 226, + 1, + 206, + 233, + 2, + 0, + 57, + 187, + 136, + 254, + 123, + 189, + 9, + 255, + 201, + 117, + 127, + 255, + 186, + 36, + 204, + 0, + 231, + 25, + 216, + 0, + 80, + 78, + 105, + 0, + 19, + 134, + 129, + 255, + 148, + 203, + 68, + 0, + 141, + 81, + 125, + 254, + 248, + 165, + 200, + 255, + 214, + 144, + 135, + 0, + 151, + 55, + 166, + 255, + 38, + 235, + 91, + 0, + 21, + 46, + 154, + 0, + 223, + 254, + 150, + 255, + 35, + 153, + 180, + 255, + 125, + 176, + 29, + 1, + 43, + 98, + 30, + 255, + 216, + 122, + 230, + 255, + 233, + 160, + 12, + 0, + 57, + 185, + 12, + 254, + 240, + 113, + 7, + 255, + 5, + 9, + 16, + 254, + 26, + 91, + 108, + 0, + 109, + 198, + 203, + 0, + 8, + 147, + 40, + 0, + 129, + 134, + 228, + 255, + 124, + 186, + 40, + 255, + 114, + 98, + 132, + 254, + 166, + 132, + 23, + 0, + 99, + 69, + 44, + 0, + 9, + 242, + 238, + 255, + 184, + 53, + 59, + 0, + 132, + 129, + 102, + 255, + 52, + 32, + 243, + 254, + 147, + 223, + 200, + 255, + 123, + 83, + 179, + 254, + 135, + 144, + 201, + 255, + 141, + 37, + 56, + 1, + 151, + 60, + 227, + 255, + 90, + 73, + 156, + 1, + 203, + 172, + 187, + 0, + 80, + 151, + 47, + 255, + 94, + 137, + 231, + 255, + 36, + 191, + 59, + 255, + 225, + 209, + 181, + 255, + 74, + 215, + 213, + 254, + 6, + 118, + 179, + 255, + 153, + 54, + 193, + 1, + 50, + 0, + 231, + 0, + 104, + 157, + 72, + 1, + 140, + 227, + 154, + 255, + 182, + 226, + 16, + 254, + 96, + 225, + 92, + 255, + 115, + 20, + 170, + 254, + 6, + 250, + 78, + 0, + 248, + 75, + 173, + 255, + 53, + 89, + 6, + 255, + 0, + 180, + 118, + 0, + 72, + 173, + 1, + 0, + 64, + 8, + 206, + 1, + 174, + 133, + 223, + 0, + 185, + 62, + 133, + 255, + 214, + 11, + 98, + 0, + 197, + 31, + 208, + 0, + 171, + 167, + 244, + 255, + 22, + 231, + 181, + 1, + 150, + 218, + 185, + 0, + 247, + 169, + 97, + 1, + 165, + 139, + 247, + 255, + 47, + 120, + 149, + 1, + 103, + 248, + 51, + 0, + 60, + 69, + 28, + 254, + 25, + 179, + 196, + 0, + 124, + 7, + 218, + 254, + 58, + 107, + 81, + 0, + 184, + 233, + 156, + 255, + 252, + 74, + 36, + 0, + 118, + 188, + 67, + 0, + 141, + 95, + 53, + 255, + 222, + 94, + 165, + 254, + 46, + 61, + 53, + 0, + 206, + 59, + 115, + 255, + 47, + 236, + 250, + 255, + 74, + 5, + 32, + 1, + 129, + 154, + 238, + 255, + 106, + 32, + 226, + 0, + 121, + 187, + 61, + 255, + 3, + 166, + 241, + 254, + 67, + 170, + 172, + 255, + 29, + 216, + 178, + 255, + 23, + 201, + 252, + 0, + 253, + 110, + 243, + 0, + 200, + 125, + 57, + 0, + 109, + 192, + 96, + 255, + 52, + 115, + 238, + 0, + 38, + 121, + 243, + 255, + 201, + 56, + 33, + 0, + 194, + 118, + 130, + 0, + 75, + 96, + 25, + 255, + 170, + 30, + 230, + 254, + 39, + 63, + 253, + 0, + 36, + 45, + 250, + 255, + 251, + 1, + 239, + 0, + 160, + 212, + 92, + 1, + 45, + 209, + 237, + 0, + 243, + 33, + 87, + 254, + 237, + 84, + 201, + 255, + 212, + 18, + 157, + 254, + 212, + 99, + 127, + 255, + 217, + 98, + 16, + 254, + 139, + 172, + 239, + 0, + 168, + 201, + 130, + 255, + 143, + 193, + 169, + 255, + 238, + 151, + 193, + 1, + 215, + 104, + 41, + 0, + 239, + 61, + 165, + 254, + 2, + 3, + 242, + 0, + 22, + 203, + 177, + 254, + 177, + 204, + 22, + 0, + 149, + 129, + 213, + 254, + 31, + 11, + 41, + 255, + 0, + 159, + 121, + 254, + 160, + 25, + 114, + 255, + 162, + 80, + 200, + 0, + 157, + 151, + 11, + 0, + 154, + 134, + 78, + 1, + 216, + 54, + 252, + 0, + 48, + 103, + 133, + 0, + 105, + 220, + 197, + 0, + 253, + 168, + 77, + 254, + 53, + 179, + 23, + 0, + 24, + 121, + 240, + 1, + 255, + 46, + 96, + 255, + 107, + 60, + 135, + 254, + 98, + 205, + 249, + 255, + 63, + 249, + 119, + 255, + 120, + 59, + 211, + 255, + 114, + 180, + 55, + 254, + 91, + 85, + 237, + 0, + 149, + 212, + 77, + 1, + 56, + 73, + 49, + 0, + 86, + 198, + 150, + 0, + 93, + 209, + 160, + 0, + 69, + 205, + 182, + 255, + 244, + 90, + 43, + 0, + 20, + 36, + 176, + 0, + 122, + 116, + 221, + 0, + 51, + 167, + 39, + 1, + 231, + 1, + 63, + 255, + 13, + 197, + 134, + 0, + 3, + 209, + 34, + 255, + 135, + 59, + 202, + 0, + 167, + 100, + 78, + 0, + 47, + 223, + 76, + 0, + 185, + 60, + 62, + 0, + 178, + 166, + 123, + 1, + 132, + 12, + 161, + 255, + 61, + 174, + 43, + 0, + 195, + 69, + 144, + 0, + 127, + 47, + 191, + 1, + 34, + 44, + 78, + 0, + 57, + 234, + 52, + 1, + 255, + 22, + 40, + 255, + 246, + 94, + 146, + 0, + 83, + 228, + 128, + 0, + 60, + 78, + 224, + 255, + 0, + 96, + 210, + 255, + 153, + 175, + 236, + 0, + 159, + 21, + 73, + 0, + 180, + 115, + 196, + 254, + 131, + 225, + 106, + 0, + 255, + 167, + 134, + 0, + 159, + 8, + 112, + 255, + 120, + 68, + 194, + 255, + 176, + 196, + 198, + 255, + 118, + 48, + 168, + 255, + 93, + 169, + 1, + 0, + 112, + 200, + 102, + 1, + 74, + 24, + 254, + 0, + 19, + 141, + 4, + 254, + 142, + 62, + 63, + 0, + 131, + 179, + 187, + 255, + 77, + 156, + 155, + 255, + 119, + 86, + 164, + 0, + 170, + 208, + 146, + 255, + 208, + 133, + 154, + 255, + 148, + 155, + 58, + 255, + 162, + 120, + 232, + 254, + 252, + 213, + 155, + 0, + 241, + 13, + 42, + 0, + 94, + 50, + 131, + 0, + 179, + 170, + 112, + 0, + 140, + 83, + 151, + 255, + 55, + 119, + 84, + 1, + 140, + 35, + 239, + 255, + 153, + 45, + 67, + 1, + 236, + 175, + 39, + 0, + 54, + 151, + 103, + 255, + 158, + 42, + 65, + 255, + 196, + 239, + 135, + 254, + 86, + 53, + 203, + 0, + 149, + 97, + 47, + 254, + 216, + 35, + 17, + 255, + 70, + 3, + 70, + 1, + 103, + 36, + 90, + 255, + 40, + 26, + 173, + 0, + 184, + 48, + 13, + 0, + 163, + 219, + 217, + 255, + 81, + 6, + 1, + 255, + 221, + 170, + 108, + 254, + 233, + 208, + 93, + 0, + 100, + 201, + 249, + 254, + 86, + 36, + 35, + 255, + 209, + 154, + 30, + 1, + 227, + 201, + 251, + 255, + 2, + 189, + 167, + 254, + 100, + 57, + 3, + 0, + 13, + 128, + 41, + 0, + 197, + 100, + 75, + 0, + 150, + 204, + 235, + 255, + 145, + 174, + 59, + 0, + 120, + 248, + 149, + 255, + 85, + 55, + 225, + 0, + 114, + 210, + 53, + 254, + 199, + 204, + 119, + 0, + 14, + 247, + 74, + 1, + 63, + 251, + 129, + 0, + 67, + 104, + 151, + 1, + 135, + 130, + 80, + 0, + 79, + 89, + 55, + 255, + 117, + 230, + 157, + 255, + 25, + 96, + 143, + 0, + 213, + 145, + 5, + 0, + 69, + 241, + 120, + 1, + 149, + 243, + 95, + 255, + 114, + 42, + 20, + 0, + 131, + 72, + 2, + 0, + 154, + 53, + 20, + 255, + 73, + 62, + 109, + 0, + 196, + 102, + 152, + 0, + 41, + 12, + 204, + 255, + 122, + 38, + 11, + 1, + 250, + 10, + 145, + 0, + 207, + 125, + 148, + 0, + 246, + 244, + 222, + 255, + 41, + 32, + 85, + 1, + 112, + 213, + 126, + 0, + 162, + 249, + 86, + 1, + 71, + 198, + 127, + 255, + 81, + 9, + 21, + 1, + 98, + 39, + 4, + 255, + 204, + 71, + 45, + 1, + 75, + 111, + 137, + 0, + 234, + 59, + 231, + 0, + 32, + 48, + 95, + 255, + 204, + 31, + 114, + 1, + 29, + 196, + 181, + 255, + 51, + 241, + 167, + 254, + 93, + 109, + 142, + 0, + 104, + 144, + 45, + 0, + 235, + 12, + 181, + 255, + 52, + 112, + 164, + 0, + 76, + 254, + 202, + 255, + 174, + 14, + 162, + 0, + 61, + 235, + 147, + 255, + 43, + 64, + 185, + 254, + 233, + 125, + 217, + 0, + 243, + 88, + 167, + 254, + 74, + 49, + 8, + 0, + 156, + 204, + 66, + 0, + 124, + 214, + 123, + 0, + 38, + 221, + 118, + 1, + 146, + 112, + 236, + 0, + 114, + 98, + 177, + 0, + 151, + 89, + 199, + 0, + 87, + 197, + 112, + 0, + 185, + 149, + 161, + 0, + 44, + 96, + 165, + 0, + 248, + 179, + 20, + 255, + 188, + 219, + 216, + 254, + 40, + 62, + 13, + 0, + 243, + 142, + 141, + 0, + 229, + 227, + 206, + 255, + 172, + 202, + 35, + 255, + 117, + 176, + 225, + 255, + 82, + 110, + 38, + 1, + 42, + 245, + 14, + 255, + 20, + 83, + 97, + 0, + 49, + 171, + 10, + 0, + 242, + 119, + 120, + 0, + 25, + 232, + 61, + 0, + 212, + 240, + 147, + 255, + 4, + 115, + 56, + 255, + 145, + 17, + 239, + 254, + 202, + 17, + 251, + 255, + 249, + 18, + 245, + 255, + 99, + 117, + 239, + 0, + 184, + 4, + 179, + 255, + 246, + 237, + 51, + 255, + 37, + 239, + 137, + 255, + 166, + 112, + 166, + 255, + 81, + 188, + 33, + 255, + 185, + 250, + 142, + 255, + 54, + 187, + 173, + 0, + 208, + 112, + 201, + 0, + 246, + 43, + 228, + 1, + 104, + 184, + 88, + 255, + 212, + 52, + 196, + 255, + 51, + 117, + 108, + 255, + 254, + 117, + 155, + 0, + 46, + 91, + 15, + 255, + 87, + 14, + 144, + 255, + 87, + 227, + 204, + 0, + 83, + 26, + 83, + 1, + 159, + 76, + 227, + 0, + 159, + 27, + 213, + 1, + 24, + 151, + 108, + 0, + 117, + 144, + 179, + 254, + 137, + 209, + 82, + 0, + 38, + 159, + 10, + 0, + 115, + 133, + 201, + 0, + 223, + 182, + 156, + 1, + 110, + 196, + 93, + 255, + 57, + 60, + 233, + 0, + 5, + 167, + 105, + 255, + 154, + 197, + 164, + 0, + 96, + 34, + 186, + 255, + 147, + 133, + 37, + 1, + 220, + 99, + 190, + 0, + 1, + 167, + 84, + 255, + 20, + 145, + 171, + 0, + 194, + 197, + 251, + 254, + 95, + 78, + 133, + 255, + 252, + 248, + 243, + 255, + 225, + 93, + 131, + 255, + 187, + 134, + 196, + 255, + 216, + 153, + 170, + 0, + 20, + 118, + 158, + 254, + 140, + 1, + 118, + 0, + 86, + 158, + 15, + 1, + 45, + 211, + 41, + 255, + 147, + 1, + 100, + 254, + 113, + 116, + 76, + 255, + 211, + 127, + 108, + 1, + 103, + 15, + 48, + 0, + 193, + 16, + 102, + 1, + 69, + 51, + 95, + 255, + 107, + 128, + 157, + 0, + 137, + 171, + 233, + 0, + 90, + 124, + 144, + 1, + 106, + 161, + 182, + 0, + 175, + 76, + 236, + 1, + 200, + 141, + 172, + 255, + 163, + 58, + 104, + 0, + 233, + 180, + 52, + 255, + 240, + 253, + 14, + 255, + 162, + 113, + 254, + 255, + 38, + 239, + 138, + 254, + 52, + 46, + 166, + 0, + 241, + 101, + 33, + 254, + 131, + 186, + 156, + 0, + 111, + 208, + 62, + 255, + 124, + 94, + 160, + 255, + 31, + 172, + 254, + 0, + 112, + 174, + 56, + 255, + 188, + 99, + 27, + 255, + 67, + 138, + 251, + 0, + 125, + 58, + 128, + 1, + 156, + 152, + 174, + 255, + 178, + 12, + 247, + 255, + 252, + 84, + 158, + 0, + 82, + 197, + 14, + 254, + 172, + 200, + 83, + 255, + 37, + 39, + 46, + 1, + 106, + 207, + 167, + 0, + 24, + 189, + 34, + 0, + 131, + 178, + 144, + 0, + 206, + 213, + 4, + 0, + 161, + 226, + 210, + 0, + 72, + 51, + 105, + 255, + 97, + 45, + 187, + 255, + 78, + 184, + 223, + 255, + 176, + 29, + 251, + 0, + 79, + 160, + 86, + 255, + 116, + 37, + 178, + 0, + 82, + 77, + 213, + 1, + 82, + 84, + 141, + 255, + 226, + 101, + 212, + 1, + 175, + 88, + 199, + 255, + 245, + 94, + 247, + 1, + 172, + 118, + 109, + 255, + 166, + 185, + 190, + 0, + 131, + 181, + 120, + 0, + 87, + 254, + 93, + 255, + 134, + 240, + 73, + 255, + 32, + 245, + 143, + 255, + 139, + 162, + 103, + 255, + 179, + 98, + 18, + 254, + 217, + 204, + 112, + 0, + 147, + 223, + 120, + 255, + 53, + 10, + 243, + 0, + 166, + 140, + 150, + 0, + 125, + 80, + 200, + 255, + 14, + 109, + 219, + 255, + 91, + 218, + 1, + 255, + 252, + 252, + 47, + 254, + 109, + 156, + 116, + 255, + 115, + 49, + 127, + 1, + 204, + 87, + 211, + 255, + 148, + 202, + 217, + 255, + 26, + 85, + 249, + 255, + 14, + 245, + 134, + 1, + 76, + 89, + 169, + 255, + 242, + 45, + 230, + 0, + 59, + 98, + 172, + 255, + 114, + 73, + 132, + 254, + 78, + 155, + 49, + 255, + 158, + 126, + 84, + 0, + 49, + 175, + 43, + 255, + 16, + 182, + 84, + 255, + 157, + 103, + 35, + 0, + 104, + 193, + 109, + 255, + 67, + 221, + 154, + 0, + 201, + 172, + 1, + 254, + 8, + 162, + 88, + 0, + 165, + 1, + 29, + 255, + 125, + 155, + 229, + 255, + 30, + 154, + 220, + 1, + 103, + 239, + 92, + 0, + 220, + 1, + 109, + 255, + 202, + 198, + 1, + 0, + 94, + 2, + 142, + 1, + 36, + 54, + 44, + 0, + 235, + 226, + 158, + 255, + 170, + 251, + 214, + 255, + 185, + 77, + 9, + 0, + 97, + 74, + 242, + 0, + 219, + 163, + 149, + 255, + 240, + 35, + 118, + 255, + 223, + 114, + 88, + 254, + 192, + 199, + 3, + 0, + 106, + 37, + 24, + 255, + 201, + 161, + 118, + 255, + 97, + 89, + 99, + 1, + 224, + 58, + 103, + 255, + 101, + 199, + 147, + 254, + 222, + 60, + 99, + 0, + 234, + 25, + 59, + 1, + 52, + 135, + 27, + 0, + 102, + 3, + 91, + 254, + 168, + 216, + 235, + 0, + 229, + 232, + 136, + 0, + 104, + 60, + 129, + 0, + 46, + 168, + 238, + 0, + 39, + 191, + 67, + 0, + 75, + 163, + 47, + 0, + 143, + 97, + 98, + 255, + 56, + 216, + 168, + 1, + 168, + 233, + 252, + 255, + 35, + 111, + 22, + 255, + 92, + 84, + 43, + 0, + 26, + 200, + 87, + 1, + 91, + 253, + 152, + 0, + 202, + 56, + 70, + 0, + 142, + 8, + 77, + 0, + 80, + 10, + 175, + 1, + 252, + 199, + 76, + 0, + 22, + 110, + 82, + 255, + 129, + 1, + 194, + 0, + 11, + 128, + 61, + 1, + 87, + 14, + 145, + 255, + 253, + 222, + 190, + 1, + 15, + 72, + 174, + 0, + 85, + 163, + 86, + 254, + 58, + 99, + 44, + 255, + 45, + 24, + 188, + 254, + 26, + 205, + 15, + 0, + 19, + 229, + 210, + 254, + 248, + 67, + 195, + 0, + 99, + 71, + 184, + 0, + 154, + 199, + 37, + 255, + 151, + 243, + 121, + 255, + 38, + 51, + 75, + 255, + 201, + 85, + 130, + 254, + 44, + 65, + 250, + 0, + 57, + 147, + 243, + 254, + 146, + 43, + 59, + 255, + 89, + 28, + 53, + 0, + 33, + 84, + 24, + 255, + 179, + 51, + 18, + 254, + 189, + 70, + 83, + 0, + 11, + 156, + 179, + 1, + 98, + 134, + 119, + 0, + 158, + 111, + 111, + 0, + 119, + 154, + 73, + 255, + 200, + 63, + 140, + 254, + 45, + 13, + 13, + 255, + 154, + 192, + 2, + 254, + 81, + 72, + 42, + 0, + 46, + 160, + 185, + 254, + 44, + 112, + 6, + 0, + 146, + 215, + 149, + 1, + 26, + 176, + 104, + 0, + 68, + 28, + 87, + 1, + 236, + 50, + 153, + 255, + 179, + 128, + 250, + 254, + 206, + 193, + 191, + 255, + 166, + 92, + 137, + 254, + 53, + 40, + 239, + 0, + 210, + 1, + 204, + 254, + 168, + 173, + 35, + 0, + 141, + 243, + 45, + 1, + 36, + 50, + 109, + 255, + 15, + 242, + 194, + 255, + 227, + 159, + 122, + 255, + 176, + 175, + 202, + 254, + 70, + 57, + 72, + 0, + 40, + 223, + 56, + 0, + 208, + 162, + 58, + 255, + 183, + 98, + 93, + 0, + 15, + 111, + 12, + 0, + 30, + 8, + 76, + 255, + 132, + 127, + 246, + 255, + 45, + 242, + 103, + 0, + 69, + 181, + 15, + 255, + 10, + 209, + 30, + 0, + 3, + 179, + 121, + 0, + 241, + 232, + 218, + 1, + 123, + 199, + 88, + 255, + 2, + 210, + 202, + 1, + 188, + 130, + 81, + 255, + 94, + 101, + 208, + 1, + 103, + 36, + 45, + 0, + 76, + 193, + 24, + 1, + 95, + 26, + 241, + 255, + 165, + 162, + 187, + 0, + 36, + 114, + 140, + 0, + 202, + 66, + 5, + 255, + 37, + 56, + 147, + 0, + 152, + 11, + 243, + 1, + 127, + 85, + 232, + 255, + 250, + 135, + 212, + 1, + 185, + 177, + 113, + 0, + 90, + 220, + 75, + 255, + 69, + 248, + 146, + 0, + 50, + 111, + 50, + 0, + 92, + 22, + 80, + 0, + 244, + 36, + 115, + 254, + 163, + 100, + 82, + 255, + 25, + 193, + 6, + 1, + 127, + 61, + 36, + 0, + 253, + 67, + 30, + 254, + 65, + 236, + 170, + 255, + 161, + 17, + 215, + 254, + 63, + 175, + 140, + 0, + 55, + 127, + 4, + 0, + 79, + 112, + 233, + 0, + 109, + 160, + 40, + 0, + 143, + 83, + 7, + 255, + 65, + 26, + 238, + 255, + 217, + 169, + 140, + 255, + 78, + 94, + 189, + 255, + 0, + 147, + 190, + 255, + 147, + 71, + 186, + 254, + 106, + 77, + 127, + 255, + 233, + 157, + 233, + 1, + 135, + 87, + 237, + 255, + 208, + 13, + 236, + 1, + 155, + 109, + 36, + 255, + 180, + 100, + 218, + 0, + 180, + 163, + 18, + 0, + 190, + 110, + 9, + 1, + 17, + 63, + 123, + 255, + 179, + 136, + 180, + 255, + 165, + 123, + 123, + 255, + 144, + 188, + 81, + 254, + 71, + 240, + 108, + 255, + 25, + 112, + 11, + 255, + 227, + 218, + 51, + 255, + 167, + 50, + 234, + 255, + 114, + 79, + 108, + 255, + 31, + 19, + 115, + 255, + 183, + 240, + 99, + 0, + 227, + 87, + 143, + 255, + 72, + 217, + 248, + 255, + 102, + 169, + 95, + 1, + 129, + 149, + 149, + 0, + 238, + 133, + 12, + 1, + 227, + 204, + 35, + 0, + 208, + 115, + 26, + 1, + 102, + 8, + 234, + 0, + 112, + 88, + 143, + 1, + 144, + 249, + 14, + 0, + 240, + 158, + 172, + 254, + 100, + 112, + 119, + 0, + 194, + 141, + 153, + 254, + 40, + 56, + 83, + 255, + 121, + 176, + 46, + 0, + 42, + 53, + 76, + 255, + 158, + 191, + 154, + 0, + 91, + 209, + 92, + 0, + 173, + 13, + 16, + 1, + 5, + 72, + 226, + 255, + 204, + 254, + 149, + 0, + 80, + 184, + 207, + 0, + 100, + 9, + 122, + 254, + 118, + 101, + 171, + 255, + 252, + 203, + 0, + 254, + 160, + 207, + 54, + 0, + 56, + 72, + 249, + 1, + 56, + 140, + 13, + 255, + 10, + 64, + 107, + 254, + 91, + 101, + 52, + 255, + 225, + 181, + 248, + 1, + 139, + 255, + 132, + 0, + 230, + 145, + 17, + 0, + 233, + 56, + 23, + 0, + 119, + 1, + 241, + 255, + 213, + 169, + 151, + 255, + 99, + 99, + 9, + 254, + 185, + 15, + 191, + 255, + 173, + 103, + 109, + 1, + 174, + 13, + 251, + 255, + 178, + 88, + 7, + 254, + 27, + 59, + 68, + 255, + 10, + 33, + 2, + 255, + 248, + 97, + 59, + 0, + 26, + 30, + 146, + 1, + 176, + 147, + 10, + 0, + 95, + 121, + 207, + 1, + 188, + 88, + 24, + 0, + 185, + 94, + 254, + 254, + 115, + 55, + 201, + 0, + 24, + 50, + 70, + 0, + 120, + 53, + 6, + 0, + 142, + 66, + 146, + 0, + 228, + 226, + 249, + 255, + 104, + 192, + 222, + 1, + 173, + 68, + 219, + 0, + 162, + 184, + 36, + 255, + 143, + 102, + 137, + 255, + 157, + 11, + 23, + 0, + 125, + 45, + 98, + 0, + 235, + 93, + 225, + 254, + 56, + 112, + 160, + 255, + 70, + 116, + 243, + 1, + 153, + 249, + 55, + 255, + 129, + 39, + 17, + 1, + 241, + 80, + 244, + 0, + 87, + 69, + 21, + 1, + 94, + 228, + 73, + 255, + 78, + 66, + 65, + 255, + 194, + 227, + 231, + 0, + 61, + 146, + 87, + 255, + 173, + 155, + 23, + 255, + 112, + 116, + 219, + 254, + 216, + 38, + 11, + 255, + 131, + 186, + 133, + 0, + 94, + 212, + 187, + 0, + 100, + 47, + 91, + 0, + 204, + 254, + 175, + 255, + 222, + 18, + 215, + 254, + 173, + 68, + 108, + 255, + 227, + 228, + 79, + 255, + 38, + 221, + 213, + 0, + 163, + 227, + 150, + 254, + 31, + 190, + 18, + 0, + 160, + 179, + 11, + 1, + 10, + 90, + 94, + 255, + 220, + 174, + 88, + 0, + 163, + 211, + 229, + 255, + 199, + 136, + 52, + 0, + 130, + 95, + 221, + 255, + 140, + 188, + 231, + 254, + 139, + 113, + 128, + 255, + 117, + 171, + 236, + 254, + 49, + 220, + 20, + 255, + 59, + 20, + 171, + 255, + 228, + 109, + 188, + 0, + 20, + 225, + 32, + 254, + 195, + 16, + 174, + 0, + 227, + 254, + 136, + 1, + 135, + 39, + 105, + 0, + 150, + 77, + 206, + 255, + 210, + 238, + 226, + 0, + 55, + 212, + 132, + 254, + 239, + 57, + 124, + 0, + 170, + 194, + 93, + 255, + 249, + 16, + 247, + 255, + 24, + 151, + 62, + 255, + 10, + 151, + 10, + 0, + 79, + 139, + 178, + 255, + 120, + 242, + 202, + 0, + 26, + 219, + 213, + 0, + 62, + 125, + 35, + 255, + 144, + 2, + 108, + 255, + 230, + 33, + 83, + 255, + 81, + 45, + 216, + 1, + 224, + 62, + 17, + 0, + 214, + 217, + 125, + 0, + 98, + 153, + 153, + 255, + ], + 'i8', + ALLOC_NONE, + Runtime.GLOBAL_BASE + 10240 +); +/* memory initializer */ allocate( + [ + 179, + 176, + 106, + 254, + 131, + 93, + 138, + 255, + 109, + 62, + 36, + 255, + 178, + 121, + 32, + 255, + 120, + 252, + 70, + 0, + 220, + 248, + 37, + 0, + 204, + 88, + 103, + 1, + 128, + 220, + 251, + 255, + 236, + 227, + 7, + 1, + 106, + 49, + 198, + 255, + 60, + 56, + 107, + 0, + 99, + 114, + 238, + 0, + 220, + 204, + 94, + 1, + 73, + 187, + 1, + 0, + 89, + 154, + 34, + 0, + 78, + 217, + 165, + 255, + 14, + 195, + 249, + 255, + 9, + 230, + 253, + 255, + 205, + 135, + 245, + 0, + 26, + 252, + 7, + 255, + 84, + 205, + 27, + 1, + 134, + 2, + 112, + 0, + 37, + 158, + 32, + 0, + 231, + 91, + 237, + 255, + 191, + 170, + 204, + 255, + 152, + 7, + 222, + 0, + 109, + 192, + 49, + 0, + 193, + 166, + 146, + 255, + 232, + 19, + 181, + 255, + 105, + 142, + 52, + 255, + 103, + 16, + 27, + 1, + 253, + 200, + 165, + 0, + 195, + 217, + 4, + 255, + 52, + 189, + 144, + 255, + 123, + 155, + 160, + 254, + 87, + 130, + 54, + 255, + 78, + 120, + 61, + 255, + 14, + 56, + 41, + 0, + 25, + 41, + 125, + 255, + 87, + 168, + 245, + 0, + 214, + 165, + 70, + 0, + 212, + 169, + 6, + 255, + 219, + 211, + 194, + 254, + 72, + 93, + 164, + 255, + 197, + 33, + 103, + 255, + 43, + 142, + 141, + 0, + 131, + 225, + 172, + 0, + 244, + 105, + 28, + 0, + 68, + 68, + 225, + 0, + 136, + 84, + 13, + 255, + 130, + 57, + 40, + 254, + 139, + 77, + 56, + 0, + 84, + 150, + 53, + 0, + 54, + 95, + 157, + 0, + 144, + 13, + 177, + 254, + 95, + 115, + 186, + 0, + 117, + 23, + 118, + 255, + 244, + 166, + 241, + 255, + 11, + 186, + 135, + 0, + 178, + 106, + 203, + 255, + 97, + 218, + 93, + 0, + 43, + 253, + 45, + 0, + 164, + 152, + 4, + 0, + 139, + 118, + 239, + 0, + 96, + 1, + 24, + 254, + 235, + 153, + 211, + 255, + 168, + 110, + 20, + 255, + 50, + 239, + 176, + 0, + 114, + 41, + 232, + 0, + 193, + 250, + 53, + 0, + 254, + 160, + 111, + 254, + 136, + 122, + 41, + 255, + 97, + 108, + 67, + 0, + 215, + 152, + 23, + 255, + 140, + 209, + 212, + 0, + 42, + 189, + 163, + 0, + 202, + 42, + 50, + 255, + 106, + 106, + 189, + 255, + 190, + 68, + 217, + 255, + 233, + 58, + 117, + 0, + 229, + 220, + 243, + 1, + 197, + 3, + 4, + 0, + 37, + 120, + 54, + 254, + 4, + 156, + 134, + 255, + 36, + 61, + 171, + 254, + 165, + 136, + 100, + 255, + 212, + 232, + 14, + 0, + 90, + 174, + 10, + 0, + 216, + 198, + 65, + 255, + 12, + 3, + 64, + 0, + 116, + 113, + 115, + 255, + 248, + 103, + 8, + 0, + 231, + 125, + 18, + 255, + 160, + 28, + 197, + 0, + 30, + 184, + 35, + 1, + 223, + 73, + 249, + 255, + 123, + 20, + 46, + 254, + 135, + 56, + 37, + 255, + 173, + 13, + 229, + 1, + 119, + 161, + 34, + 255, + 245, + 61, + 73, + 0, + 205, + 125, + 112, + 0, + 137, + 104, + 134, + 0, + 217, + 246, + 30, + 255, + 237, + 142, + 143, + 0, + 65, + 159, + 102, + 255, + 108, + 164, + 190, + 0, + 219, + 117, + 173, + 255, + 34, + 37, + 120, + 254, + 200, + 69, + 80, + 0, + 31, + 124, + 218, + 254, + 74, + 27, + 160, + 255, + 186, + 154, + 199, + 255, + 71, + 199, + 252, + 0, + 104, + 81, + 159, + 1, + 17, + 200, + 39, + 0, + 211, + 61, + 192, + 1, + 26, + 238, + 91, + 0, + 148, + 217, + 12, + 0, + 59, + 91, + 213, + 255, + 11, + 81, + 183, + 255, + 129, + 230, + 122, + 255, + 114, + 203, + 145, + 1, + 119, + 180, + 66, + 255, + 72, + 138, + 180, + 0, + 224, + 149, + 106, + 0, + 119, + 82, + 104, + 255, + 208, + 140, + 43, + 0, + 98, + 9, + 182, + 255, + 205, + 101, + 134, + 255, + 18, + 101, + 38, + 0, + 95, + 197, + 166, + 255, + 203, + 241, + 147, + 0, + 62, + 208, + 145, + 255, + 133, + 246, + 251, + 0, + 2, + 169, + 14, + 0, + 13, + 247, + 184, + 0, + 142, + 7, + 254, + 0, + 36, + 200, + 23, + 255, + 88, + 205, + 223, + 0, + 91, + 129, + 52, + 255, + 21, + 186, + 30, + 0, + 143, + 228, + 210, + 1, + 247, + 234, + 248, + 255, + 230, + 69, + 31, + 254, + 176, + 186, + 135, + 255, + 238, + 205, + 52, + 1, + 139, + 79, + 43, + 0, + 17, + 176, + 217, + 254, + 32, + 243, + 67, + 0, + 242, + 111, + 233, + 0, + 44, + 35, + 9, + 255, + 227, + 114, + 81, + 1, + 4, + 71, + 12, + 255, + 38, + 105, + 191, + 0, + 7, + 117, + 50, + 255, + 81, + 79, + 16, + 0, + 63, + 68, + 65, + 255, + 157, + 36, + 110, + 255, + 77, + 241, + 3, + 255, + 226, + 45, + 251, + 1, + 142, + 25, + 206, + 0, + 120, + 123, + 209, + 1, + 28, + 254, + 238, + 255, + 5, + 128, + 126, + 255, + 91, + 222, + 215, + 255, + 162, + 15, + 191, + 0, + 86, + 240, + 73, + 0, + 135, + 185, + 81, + 254, + 44, + 241, + 163, + 0, + 212, + 219, + 210, + 255, + 112, + 162, + 155, + 0, + 207, + 101, + 118, + 0, + 168, + 72, + 56, + 255, + 196, + 5, + 52, + 0, + 72, + 172, + 242, + 255, + 126, + 22, + 157, + 255, + 146, + 96, + 59, + 255, + 162, + 121, + 152, + 254, + 140, + 16, + 95, + 0, + 195, + 254, + 200, + 254, + 82, + 150, + 162, + 0, + 119, + 43, + 145, + 254, + 204, + 172, + 78, + 255, + 166, + 224, + 159, + 0, + 104, + 19, + 237, + 255, + 245, + 126, + 208, + 255, + 226, + 59, + 213, + 0, + 117, + 217, + 197, + 0, + 152, + 72, + 237, + 0, + 220, + 31, + 23, + 254, + 14, + 90, + 231, + 255, + 188, + 212, + 64, + 1, + 60, + 101, + 246, + 255, + 85, + 24, + 86, + 0, + 1, + 177, + 109, + 0, + 146, + 83, + 32, + 1, + 75, + 182, + 192, + 0, + 119, + 241, + 224, + 0, + 185, + 237, + 27, + 255, + 184, + 101, + 82, + 1, + 235, + 37, + 77, + 255, + 253, + 134, + 19, + 0, + 232, + 246, + 122, + 0, + 60, + 106, + 179, + 0, + 195, + 11, + 12, + 0, + 109, + 66, + 235, + 1, + 125, + 113, + 59, + 0, + 61, + 40, + 164, + 0, + 175, + 104, + 240, + 0, + 2, + 47, + 187, + 255, + 50, + 12, + 141, + 0, + 194, + 139, + 181, + 255, + 135, + 250, + 104, + 0, + 97, + 92, + 222, + 255, + 217, + 149, + 201, + 255, + 203, + 241, + 118, + 255, + 79, + 151, + 67, + 0, + 122, + 142, + 218, + 255, + 149, + 245, + 239, + 0, + 138, + 42, + 200, + 254, + 80, + 37, + 97, + 255, + 124, + 112, + 167, + 255, + 36, + 138, + 87, + 255, + 130, + 29, + 147, + 255, + 241, + 87, + 78, + 255, + 204, + 97, + 19, + 1, + 177, + 209, + 22, + 255, + 247, + 227, + 127, + 254, + 99, + 119, + 83, + 255, + 212, + 25, + 198, + 1, + 16, + 179, + 179, + 0, + 145, + 77, + 172, + 254, + 89, + 153, + 14, + 255, + 218, + 189, + 167, + 0, + 107, + 233, + 59, + 255, + 35, + 33, + 243, + 254, + 44, + 112, + 112, + 255, + 161, + 127, + 79, + 1, + 204, + 175, + 10, + 0, + 40, + 21, + 138, + 254, + 104, + 116, + 228, + 0, + 199, + 95, + 137, + 255, + 133, + 190, + 168, + 255, + 146, + 165, + 234, + 1, + 183, + 99, + 39, + 0, + 183, + 220, + 54, + 254, + 255, + 222, + 133, + 0, + 162, + 219, + 121, + 254, + 63, + 239, + 6, + 0, + 225, + 102, + 54, + 255, + 251, + 18, + 246, + 0, + 4, + 34, + 129, + 1, + 135, + 36, + 131, + 0, + 206, + 50, + 59, + 1, + 15, + 97, + 183, + 0, + 171, + 216, + 135, + 255, + 101, + 152, + 43, + 255, + 150, + 251, + 91, + 0, + 38, + 145, + 95, + 0, + 34, + 204, + 38, + 254, + 178, + 140, + 83, + 255, + 25, + 129, + 243, + 255, + 76, + 144, + 37, + 0, + 106, + 36, + 26, + 254, + 118, + 144, + 172, + 255, + 68, + 186, + 229, + 255, + 107, + 161, + 213, + 255, + 46, + 163, + 68, + 255, + 149, + 170, + 253, + 0, + 187, + 17, + 15, + 0, + 218, + 160, + 165, + 255, + 171, + 35, + 246, + 1, + 96, + 13, + 19, + 0, + 165, + 203, + 117, + 0, + 214, + 107, + 192, + 255, + 244, + 123, + 177, + 1, + 100, + 3, + 104, + 0, + 178, + 242, + 97, + 255, + 251, + 76, + 130, + 255, + 211, + 77, + 42, + 1, + 250, + 79, + 70, + 255, + 63, + 244, + 80, + 1, + 105, + 101, + 246, + 0, + 61, + 136, + 58, + 1, + 238, + 91, + 213, + 0, + 14, + 59, + 98, + 255, + 167, + 84, + 77, + 0, + 17, + 132, + 46, + 254, + 57, + 175, + 197, + 255, + 185, + 62, + 184, + 0, + 76, + 64, + 207, + 0, + 172, + 175, + 208, + 254, + 175, + 74, + 37, + 0, + 138, + 27, + 211, + 254, + 148, + 125, + 194, + 0, + 10, + 89, + 81, + 0, + 168, + 203, + 101, + 255, + 43, + 213, + 209, + 1, + 235, + 245, + 54, + 0, + 30, + 35, + 226, + 255, + 9, + 126, + 70, + 0, + 226, + 125, + 94, + 254, + 156, + 117, + 20, + 255, + 57, + 248, + 112, + 1, + 230, + 48, + 64, + 255, + 164, + 92, + 166, + 1, + 224, + 214, + 230, + 255, + 36, + 120, + 143, + 0, + 55, + 8, + 43, + 255, + 251, + 1, + 245, + 1, + 106, + 98, + 165, + 0, + 74, + 107, + 106, + 254, + 53, + 4, + 54, + 255, + 90, + 178, + 150, + 1, + 3, + 120, + 123, + 255, + 244, + 5, + 89, + 1, + 114, + 250, + 61, + 255, + 254, + 153, + 82, + 1, + 77, + 15, + 17, + 0, + 57, + 238, + 90, + 1, + 95, + 223, + 230, + 0, + 236, + 52, + 47, + 254, + 103, + 148, + 164, + 255, + 121, + 207, + 36, + 1, + 18, + 16, + 185, + 255, + 75, + 20, + 74, + 0, + 187, + 11, + 101, + 0, + 46, + 48, + 129, + 255, + 22, + 239, + 210, + 255, + 77, + 236, + 129, + 255, + 111, + 77, + 204, + 255, + 61, + 72, + 97, + 255, + 199, + 217, + 251, + 255, + 42, + 215, + 204, + 0, + 133, + 145, + 201, + 255, + 57, + 230, + 146, + 1, + 235, + 100, + 198, + 0, + 146, + 73, + 35, + 254, + 108, + 198, + 20, + 255, + 182, + 79, + 210, + 255, + 82, + 103, + 136, + 0, + 246, + 108, + 176, + 0, + 34, + 17, + 60, + 255, + 19, + 74, + 114, + 254, + 168, + 170, + 78, + 255, + 157, + 239, + 20, + 255, + 149, + 41, + 168, + 0, + 58, + 121, + 28, + 0, + 79, + 179, + 134, + 255, + 231, + 121, + 135, + 255, + 174, + 209, + 98, + 255, + 243, + 122, + 190, + 0, + 171, + 166, + 205, + 0, + 212, + 116, + 48, + 0, + 29, + 108, + 66, + 255, + 162, + 222, + 182, + 1, + 14, + 119, + 21, + 0, + 213, + 39, + 249, + 255, + 254, + 223, + 228, + 255, + 183, + 165, + 198, + 0, + 133, + 190, + 48, + 0, + 124, + 208, + 109, + 255, + 119, + 175, + 85, + 255, + 9, + 209, + 121, + 1, + 48, + 171, + 189, + 255, + 195, + 71, + 134, + 1, + 136, + 219, + 51, + 255, + 182, + 91, + 141, + 254, + 49, + 159, + 72, + 0, + 35, + 118, + 245, + 255, + 112, + 186, + 227, + 255, + 59, + 137, + 31, + 0, + 137, + 44, + 163, + 0, + 114, + 103, + 60, + 254, + 8, + 213, + 150, + 0, + 162, + 10, + 113, + 255, + 194, + 104, + 72, + 0, + 220, + 131, + 116, + 255, + 178, + 79, + 92, + 0, + 203, + 250, + 213, + 254, + 93, + 193, + 189, + 255, + 130, + 255, + 34, + 254, + 212, + 188, + 151, + 0, + 136, + 17, + 20, + 255, + 20, + 101, + 83, + 255, + 212, + 206, + 166, + 0, + 229, + 238, + 73, + 255, + 151, + 74, + 3, + 255, + 168, + 87, + 215, + 0, + 155, + 188, + 133, + 255, + 166, + 129, + 73, + 0, + 240, + 79, + 133, + 255, + 178, + 211, + 81, + 255, + 203, + 72, + 163, + 254, + 193, + 168, + 165, + 0, + 14, + 164, + 199, + 254, + 30, + 255, + 204, + 0, + 65, + 72, + 91, + 1, + 166, + 74, + 102, + 255, + 200, + 42, + 0, + 255, + 194, + 113, + 227, + 255, + 66, + 23, + 208, + 0, + 229, + 216, + 100, + 255, + 24, + 239, + 26, + 0, + 10, + 233, + 62, + 255, + 123, + 10, + 178, + 1, + 26, + 36, + 174, + 255, + 119, + 219, + 199, + 1, + 45, + 163, + 190, + 0, + 16, + 168, + 42, + 0, + 166, + 57, + 198, + 255, + 28, + 26, + 26, + 0, + 126, + 165, + 231, + 0, + 251, + 108, + 100, + 255, + 61, + 229, + 121, + 255, + 58, + 118, + 138, + 0, + 76, + 207, + 17, + 0, + 13, + 34, + 112, + 254, + 89, + 16, + 168, + 0, + 37, + 208, + 105, + 255, + 35, + 201, + 215, + 255, + 40, + 106, + 101, + 254, + 6, + 239, + 114, + 0, + 40, + 103, + 226, + 254, + 246, + 127, + 110, + 255, + 63, + 167, + 58, + 0, + 132, + 240, + 142, + 0, + 5, + 158, + 88, + 255, + 129, + 73, + 158, + 255, + 94, + 89, + 146, + 0, + 230, + 54, + 146, + 0, + 8, + 45, + 173, + 0, + 79, + 169, + 1, + 0, + 115, + 186, + 247, + 0, + 84, + 64, + 131, + 0, + 67, + 224, + 253, + 255, + 207, + 189, + 64, + 0, + 154, + 28, + 81, + 1, + 45, + 184, + 54, + 255, + 87, + 212, + 224, + 255, + 0, + 96, + 73, + 255, + 129, + 33, + 235, + 1, + 52, + 66, + 80, + 255, + 251, + 174, + 155, + 255, + 4, + 179, + 37, + 0, + 234, + 164, + 93, + 254, + 93, + 175, + 253, + 0, + 198, + 69, + 87, + 255, + 224, + 106, + 46, + 0, + 99, + 29, + 210, + 0, + 62, + 188, + 114, + 255, + 44, + 234, + 8, + 0, + 169, + 175, + 247, + 255, + 23, + 109, + 137, + 255, + 229, + 182, + 39, + 0, + 192, + 165, + 94, + 254, + 245, + 101, + 217, + 0, + 191, + 88, + 96, + 0, + 196, + 94, + 99, + 255, + 106, + 238, + 11, + 254, + 53, + 126, + 243, + 0, + 94, + 1, + 101, + 255, + 46, + 147, + 2, + 0, + 201, + 124, + 124, + 255, + 141, + 12, + 218, + 0, + 13, + 166, + 157, + 1, + 48, + 251, + 237, + 255, + 155, + 250, + 124, + 255, + 106, + 148, + 146, + 255, + 182, + 13, + 202, + 0, + 28, + 61, + 167, + 0, + 217, + 152, + 8, + 254, + 220, + 130, + 45, + 255, + 200, + 230, + 255, + 1, + 55, + 65, + 87, + 255, + 93, + 191, + 97, + 254, + 114, + 251, + 14, + 0, + 32, + 105, + 92, + 1, + 26, + 207, + 141, + 0, + 24, + 207, + 13, + 254, + 21, + 50, + 48, + 255, + 186, + 148, + 116, + 255, + 211, + 43, + 225, + 0, + 37, + 34, + 162, + 254, + 164, + 210, + 42, + 255, + 68, + 23, + 96, + 255, + 182, + 214, + 8, + 255, + 245, + 117, + 137, + 255, + 66, + 195, + 50, + 0, + 75, + 12, + 83, + 254, + 80, + 140, + 164, + 0, + 9, + 165, + 36, + 1, + 228, + 110, + 227, + 0, + 241, + 17, + 90, + 1, + 25, + 52, + 212, + 0, + 6, + 223, + 12, + 255, + 139, + 243, + 57, + 0, + 12, + 113, + 75, + 1, + 246, + 183, + 191, + 255, + 213, + 191, + 69, + 255, + 230, + 15, + 142, + 0, + 1, + 195, + 196, + 255, + 138, + 171, + 47, + 255, + 64, + 63, + 106, + 1, + 16, + 169, + 214, + 255, + 207, + 174, + 56, + 1, + 88, + 73, + 133, + 255, + 182, + 133, + 140, + 0, + 177, + 14, + 25, + 255, + 147, + 184, + 53, + 255, + 10, + 227, + 161, + 255, + 120, + 216, + 244, + 255, + 73, + 77, + 233, + 0, + 157, + 238, + 139, + 1, + 59, + 65, + 233, + 0, + 70, + 251, + 216, + 1, + 41, + 184, + 153, + 255, + 32, + 203, + 112, + 0, + 146, + 147, + 253, + 0, + 87, + 101, + 109, + 1, + 44, + 82, + 133, + 255, + 244, + 150, + 53, + 255, + 94, + 152, + 232, + 255, + 59, + 93, + 39, + 255, + 88, + 147, + 220, + 255, + 78, + 81, + 13, + 1, + 32, + 47, + 252, + 255, + 160, + 19, + 114, + 255, + 93, + 107, + 39, + 255, + 118, + 16, + 211, + 1, + 185, + 119, + 209, + 255, + 227, + 219, + 127, + 254, + 88, + 105, + 236, + 255, + 162, + 110, + 23, + 255, + 36, + 166, + 110, + 255, + 91, + 236, + 221, + 255, + 66, + 234, + 116, + 0, + 111, + 19, + 244, + 254, + 10, + 233, + 26, + 0, + 32, + 183, + 6, + 254, + 2, + 191, + 242, + 0, + 218, + 156, + 53, + 254, + 41, + 60, + 70, + 255, + 168, + 236, + 111, + 0, + 121, + 185, + 126, + 255, + 238, + 142, + 207, + 255, + 55, + 126, + 52, + 0, + 220, + 129, + 208, + 254, + 80, + 204, + 164, + 255, + 67, + 23, + 144, + 254, + 218, + 40, + 108, + 255, + 127, + 202, + 164, + 0, + 203, + 33, + 3, + 255, + 2, + 158, + 0, + 0, + 37, + 96, + 188, + 255, + 192, + 49, + 74, + 0, + 109, + 4, + 0, + 0, + 111, + 167, + 10, + 254, + 91, + 218, + 135, + 255, + 203, + 66, + 173, + 255, + 150, + 194, + 226, + 0, + 201, + 253, + 6, + 255, + 174, + 102, + 121, + 0, + 205, + 191, + 110, + 0, + 53, + 194, + 4, + 0, + 81, + 40, + 45, + 254, + 35, + 102, + 143, + 255, + 12, + 108, + 198, + 255, + 16, + 27, + 232, + 255, + 252, + 71, + 186, + 1, + 176, + 110, + 114, + 0, + 142, + 3, + 117, + 1, + 113, + 77, + 142, + 0, + 19, + 156, + 197, + 1, + 92, + 47, + 252, + 0, + 53, + 232, + 22, + 1, + 54, + 18, + 235, + 0, + 46, + 35, + 189, + 255, + 236, + 212, + 129, + 0, + 2, + 96, + 208, + 254, + 200, + 238, + 199, + 255, + 59, + 175, + 164, + 255, + 146, + 43, + 231, + 0, + 194, + 217, + 52, + 255, + 3, + 223, + 12, + 0, + 138, + 54, + 178, + 254, + 85, + 235, + 207, + 0, + 232, + 207, + 34, + 0, + 49, + 52, + 50, + 255, + 166, + 113, + 89, + 255, + 10, + 45, + 216, + 255, + 62, + 173, + 28, + 0, + 111, + 165, + 246, + 0, + 118, + 115, + 91, + 255, + 128, + 84, + 60, + 0, + 167, + 144, + 203, + 0, + 87, + 13, + 243, + 0, + 22, + 30, + 228, + 1, + 177, + 113, + 146, + 255, + 129, + 170, + 230, + 254, + 252, + 153, + 129, + 255, + 145, + 225, + 43, + 0, + 70, + 231, + 5, + 255, + 122, + 105, + 126, + 254, + 86, + 246, + 148, + 255, + 110, + 37, + 154, + 254, + 209, + 3, + 91, + 0, + 68, + 145, + 62, + 0, + 228, + 16, + 165, + 255, + 55, + 221, + 249, + 254, + 178, + 210, + 91, + 0, + 83, + 146, + 226, + 254, + 69, + 146, + 186, + 0, + 93, + 210, + 104, + 254, + 16, + 25, + 173, + 0, + 231, + 186, + 38, + 0, + 189, + 122, + 140, + 255, + 251, + 13, + 112, + 255, + 105, + 110, + 93, + 0, + 251, + 72, + 170, + 0, + 192, + 23, + 223, + 255, + 24, + 3, + 202, + 1, + 225, + 93, + 228, + 0, + 153, + 147, + 199, + 254, + 109, + 170, + 22, + 0, + 248, + 101, + 246, + 255, + 178, + 124, + 12, + 255, + 178, + 254, + 102, + 254, + 55, + 4, + 65, + 0, + 125, + 214, + 180, + 0, + 183, + 96, + 147, + 0, + 45, + 117, + 23, + 254, + 132, + 191, + 249, + 0, + 143, + 176, + 203, + 254, + 136, + 183, + 54, + 255, + 146, + 234, + 177, + 0, + 146, + 101, + 86, + 255, + 44, + 123, + 143, + 1, + 33, + 209, + 152, + 0, + 192, + 90, + 41, + 254, + 83, + 15, + 125, + 255, + 213, + 172, + 82, + 0, + 215, + 169, + 144, + 0, + 16, + 13, + 34, + 0, + 32, + 209, + 100, + 255, + 84, + 18, + 249, + 1, + 197, + 17, + 236, + 255, + 217, + 186, + 230, + 0, + 49, + 160, + 176, + 255, + 111, + 118, + 97, + 255, + 237, + 104, + 235, + 0, + 79, + 59, + 92, + 254, + 69, + 249, + 11, + 255, + 35, + 172, + 74, + 1, + 19, + 118, + 68, + 0, + 222, + 124, + 165, + 255, + 180, + 66, + 35, + 255, + 86, + 174, + 246, + 0, + 43, + 74, + 111, + 255, + 126, + 144, + 86, + 255, + 228, + 234, + 91, + 0, + 242, + 213, + 24, + 254, + 69, + 44, + 235, + 255, + 220, + 180, + 35, + 0, + 8, + 248, + 7, + 255, + 102, + 47, + 92, + 255, + 240, + 205, + 102, + 255, + 113, + 230, + 171, + 1, + 31, + 185, + 201, + 255, + 194, + 246, + 70, + 255, + 122, + 17, + 187, + 0, + 134, + 70, + 199, + 255, + 149, + 3, + 150, + 255, + 117, + 63, + 103, + 0, + 65, + 104, + 123, + 255, + 212, + 54, + 19, + 1, + 6, + 141, + 88, + 0, + 83, + 134, + 243, + 255, + 136, + 53, + 103, + 0, + 169, + 27, + 180, + 0, + 177, + 49, + 24, + 0, + 111, + 54, + 167, + 0, + 195, + 61, + 215, + 255, + 31, + 1, + 108, + 1, + 60, + 42, + 70, + 0, + 185, + 3, + 162, + 255, + 194, + 149, + 40, + 255, + 246, + 127, + 38, + 254, + 190, + 119, + 38, + 255, + 61, + 119, + 8, + 1, + 96, + 161, + 219, + 255, + 42, + 203, + 221, + 1, + 177, + 242, + 164, + 255, + 245, + 159, + 10, + 0, + 116, + 196, + 0, + 0, + 5, + 93, + 205, + 254, + 128, + 127, + 179, + 0, + 125, + 237, + 246, + 255, + 149, + 162, + 217, + 255, + 87, + 37, + 20, + 254, + 140, + 238, + 192, + 0, + 9, + 9, + 193, + 0, + 97, + 1, + 226, + 0, + 29, + 38, + 10, + 0, + 0, + 136, + 63, + 255, + 229, + 72, + 210, + 254, + 38, + 134, + 92, + 255, + 78, + 218, + 208, + 1, + 104, + 36, + 84, + 255, + 12, + 5, + 193, + 255, + 242, + 175, + 61, + 255, + 191, + 169, + 46, + 1, + 179, + 147, + 147, + 255, + 113, + 190, + 139, + 254, + 125, + 172, + 31, + 0, + 3, + 75, + 252, + 254, + 215, + 36, + 15, + 0, + 193, + 27, + 24, + 1, + 255, + 69, + 149, + 255, + 110, + 129, + 118, + 0, + 203, + 93, + 249, + 0, + 138, + 137, + 64, + 254, + 38, + 70, + 6, + 0, + 153, + 116, + 222, + 0, + 161, + 74, + 123, + 0, + 193, + 99, + 79, + 255, + 118, + 59, + 94, + 255, + 61, + 12, + 43, + 1, + 146, + 177, + 157, + 0, + 46, + 147, + 191, + 0, + 16, + 255, + 38, + 0, + 11, + 51, + 31, + 1, + 60, + 58, + 98, + 255, + 111, + 194, + 77, + 1, + 154, + 91, + 244, + 0, + 140, + 40, + 144, + 1, + 173, + 10, + 251, + 0, + 203, + 209, + 50, + 254, + 108, + 130, + 78, + 0, + 228, + 180, + 90, + 0, + 174, + 7, + 250, + 0, + 31, + 174, + 60, + 0, + 41, + 171, + 30, + 0, + 116, + 99, + 82, + 255, + 118, + 193, + 139, + 255, + 187, + 173, + 198, + 254, + 218, + 111, + 56, + 0, + 185, + 123, + 216, + 0, + 249, + 158, + 52, + 0, + 52, + 180, + 93, + 255, + 201, + 9, + 91, + 255, + 56, + 45, + 166, + 254, + 132, + 155, + 203, + 255, + 58, + 232, + 110, + 0, + 52, + 211, + 89, + 255, + 253, + 0, + 162, + 1, + 9, + 87, + 183, + 0, + 145, + 136, + 44, + 1, + 94, + 122, + 245, + 0, + 85, + 188, + 171, + 1, + 147, + 92, + 198, + 0, + 0, + 8, + 104, + 0, + 30, + 95, + 174, + 0, + 221, + 230, + 52, + 1, + 247, + 247, + 235, + 255, + 137, + 174, + 53, + 255, + 35, + 21, + 204, + 255, + 71, + 227, + 214, + 1, + 232, + 82, + 194, + 0, + 11, + 48, + 227, + 255, + 170, + 73, + 184, + 255, + 198, + 251, + 252, + 254, + 44, + 112, + 34, + 0, + 131, + 101, + 131, + 255, + 72, + 168, + 187, + 0, + 132, + 135, + 125, + 255, + 138, + 104, + 97, + 255, + 238, + 184, + 168, + 255, + 243, + 104, + 84, + 255, + 135, + 216, + 226, + 255, + 139, + 144, + 237, + 0, + 188, + 137, + 150, + 1, + 80, + 56, + 140, + 255, + 86, + 169, + 167, + 255, + 194, + 78, + 25, + 255, + 220, + 17, + 180, + 255, + 17, + 13, + 193, + 0, + 117, + 137, + 212, + 255, + 141, + 224, + 151, + 0, + 49, + 244, + 175, + 0, + 193, + 99, + 175, + 255, + 19, + 99, + 154, + 1, + 255, + 65, + 62, + 255, + 156, + 210, + 55, + 255, + 242, + 244, + 3, + 255, + 250, + 14, + 149, + 0, + 158, + 88, + 217, + 255, + 157, + 207, + 134, + 254, + 251, + 232, + 28, + 0, + 46, + 156, + 251, + 255, + 171, + 56, + 184, + 255, + 239, + 51, + 234, + 0, + 142, + 138, + 131, + 255, + 25, + 254, + 243, + 1, + 10, + 201, + 194, + 0, + 63, + 97, + 75, + 0, + 210, + 239, + 162, + 0, + 192, + 200, + 31, + 1, + 117, + 214, + 243, + 0, + 24, + 71, + 222, + 254, + 54, + 40, + 232, + 255, + 76, + 183, + 111, + 254, + 144, + 14, + 87, + 255, + 214, + 79, + 136, + 255, + 216, + 196, + 212, + 0, + 132, + 27, + 140, + 254, + 131, + 5, + 253, + 0, + 124, + 108, + 19, + 255, + 28, + 215, + 75, + 0, + 76, + 222, + 55, + 254, + 233, + 182, + 63, + 0, + 68, + 171, + 191, + 254, + 52, + 111, + 222, + 255, + 10, + 105, + 77, + 255, + 80, + 170, + 235, + 0, + 143, + 24, + 88, + 255, + 45, + 231, + 121, + 0, + 148, + 129, + 224, + 1, + 61, + 246, + 84, + 0, + 253, + 46, + 219, + 255, + 239, + 76, + 33, + 0, + 49, + 148, + 18, + 254, + 230, + 37, + 69, + 0, + 67, + 134, + 22, + 254, + 142, + 155, + 94, + 0, + 31, + 157, + 211, + 254, + 213, + 42, + 30, + 255, + 4, + 228, + 247, + 254, + 252, + 176, + 13, + 255, + 39, + 0, + 31, + 254, + 241, + 244, + 255, + 255, + 170, + 45, + 10, + 254, + 253, + 222, + 249, + 0, + 222, + 114, + 132, + 0, + 255, + 47, + 6, + 255, + 180, + 163, + 179, + 1, + 84, + 94, + 151, + 255, + 89, + 209, + 82, + 254, + 229, + 52, + 169, + 255, + 213, + 236, + 0, + 1, + 214, + 56, + 228, + 255, + 135, + 119, + 151, + 255, + 112, + 201, + 193, + 0, + 83, + 160, + 53, + 254, + 6, + 151, + 66, + 0, + 18, + 162, + 17, + 0, + 233, + 97, + 91, + 0, + 131, + 5, + 78, + 1, + 181, + 120, + 53, + 255, + 117, + 95, + 63, + 255, + 237, + 117, + 185, + 0, + 191, + 126, + 136, + 255, + 144, + 119, + 233, + 0, + 183, + 57, + 97, + 1, + 47, + 201, + 187, + 255, + 167, + 165, + 119, + 1, + 45, + 100, + 126, + 0, + 21, + 98, + 6, + 254, + 145, + 150, + 95, + 255, + 120, + 54, + 152, + 0, + 209, + 98, + 104, + 0, + 143, + 111, + 30, + 254, + 184, + 148, + 249, + 0, + 235, + 216, + 46, + 0, + 248, + 202, + 148, + 255, + 57, + 95, + 22, + 0, + 242, + 225, + 163, + 0, + 233, + 247, + 232, + 255, + 71, + 171, + 19, + 255, + 103, + 244, + 49, + 255, + 84, + 103, + 93, + 255, + 68, + 121, + 244, + 1, + 82, + 224, + 13, + 0, + 41, + 79, + 43, + 255, + 249, + 206, + 167, + 255, + 215, + 52, + 21, + 254, + 192, + 32, + 22, + 255, + 247, + 111, + 60, + 0, + 101, + 74, + 38, + 255, + 22, + 91, + 84, + 254, + 29, + 28, + 13, + 255, + 198, + 231, + 215, + 254, + 244, + 154, + 200, + 0, + 223, + 137, + 237, + 0, + 211, + 132, + 14, + 0, + 95, + 64, + 206, + 255, + 17, + 62, + 247, + 255, + 233, + 131, + 121, + 1, + 93, + 23, + 77, + 0, + 205, + 204, + 52, + 254, + 81, + 189, + 136, + 0, + 180, + 219, + 138, + 1, + 143, + 18, + 94, + 0, + 204, + 43, + 140, + 254, + 188, + 175, + 219, + 0, + 111, + 98, + 143, + 255, + 151, + 63, + 162, + 255, + 211, + 50, + 71, + 254, + 19, + 146, + 53, + 0, + 146, + 45, + 83, + 254, + 178, + 82, + 238, + 255, + 16, + 133, + 84, + 255, + 226, + 198, + 93, + 255, + 201, + 97, + 20, + 255, + 120, + 118, + 35, + 255, + 114, + 50, + 231, + 255, + 162, + 229, + 156, + 255, + 211, + 26, + 12, + 0, + 114, + 39, + 115, + 255, + 206, + 212, + 134, + 0, + 197, + 217, + 160, + 255, + 116, + 129, + 94, + 254, + 199, + 215, + 219, + 255, + 75, + 223, + 249, + 1, + 253, + 116, + 181, + 255, + 232, + 215, + 104, + 255, + 228, + 130, + 246, + 255, + 185, + 117, + 86, + 0, + 14, + 5, + 8, + 0, + 239, + 29, + 61, + 1, + 237, + 87, + 133, + 255, + 125, + 146, + 137, + 254, + 204, + 168, + 223, + 0, + 46, + 168, + 245, + 0, + 154, + 105, + 22, + 0, + 220, + 212, + 161, + 255, + 107, + 69, + 24, + 255, + 137, + 218, + 181, + 255, + 241, + 84, + 198, + 255, + 130, + 122, + 211, + 255, + 141, + 8, + 153, + 255, + 190, + 177, + 118, + 0, + 96, + 89, + 178, + 0, + 255, + 16, + 48, + 254, + 122, + 96, + 105, + 255, + 117, + 54, + 232, + 255, + 34, + 126, + 105, + 255, + 204, + 67, + 166, + 0, + 232, + 52, + 138, + 255, + 211, + 147, + 12, + 0, + 25, + 54, + 7, + 0, + 44, + 15, + 215, + 254, + 51, + 236, + 45, + 0, + 190, + 68, + 129, + 1, + 106, + 147, + 225, + 0, + 28, + 93, + 45, + 254, + 236, + 141, + 15, + 255, + 17, + 61, + 161, + 0, + 220, + 115, + 192, + 0, + 236, + 145, + 24, + 254, + 111, + 168, + 169, + 0, + 224, + 58, + 63, + 255, + 127, + 164, + 188, + 0, + 82, + 234, + 75, + 1, + 224, + 158, + 134, + 0, + 209, + 68, + 110, + 1, + 217, + 166, + 217, + 0, + 70, + 225, + 166, + 1, + 187, + 193, + 143, + 255, + 16, + 7, + 88, + 255, + 10, + 205, + 140, + 0, + 117, + 192, + 156, + 1, + 17, + 56, + 38, + 0, + 27, + 124, + 108, + 1, + 171, + 215, + 55, + 255, + 95, + 253, + 212, + 0, + 155, + 135, + 168, + 255, + 246, + 178, + 153, + 254, + 154, + 68, + 74, + 0, + 232, + 61, + 96, + 254, + 105, + 132, + 59, + 0, + 33, + 76, + 199, + 1, + 189, + 176, + 130, + 255, + 9, + 104, + 25, + 254, + 75, + 198, + 102, + 255, + 233, + 1, + 112, + 0, + 108, + 220, + 20, + 255, + 114, + 230, + 70, + 0, + 140, + 194, + 133, + 255, + 57, + 158, + 164, + 254, + 146, + 6, + 80, + 255, + 169, + 196, + 97, + 1, + 85, + 183, + 130, + 0, + 70, + 158, + 222, + 1, + 59, + 237, + 234, + 255, + 96, + 25, + 26, + 255, + 232, + 175, + 97, + 255, + 11, + 121, + 248, + 254, + 88, + 35, + 194, + 0, + 219, + 180, + 252, + 254, + 74, + 8, + 227, + 0, + 195, + 227, + 73, + 1, + 184, + 110, + 161, + 255, + 49, + 233, + 164, + 1, + 128, + 53, + 47, + 0, + 82, + 14, + 121, + 255, + 193, + 190, + 58, + 0, + 48, + 174, + 117, + 255, + 132, + 23, + 32, + 0, + 40, + 10, + 134, + 1, + 22, + 51, + 25, + 255, + 240, + 11, + 176, + 255, + 110, + 57, + 146, + 0, + 117, + 143, + 239, + 1, + 157, + 101, + 118, + 255, + 54, + 84, + 76, + 0, + 205, + 184, + 18, + 255, + 47, + 4, + 72, + 255, + 78, + 112, + 85, + 255, + 193, + 50, + 66, + 1, + 93, + 16, + 52, + 255, + 8, + 105, + 134, + 0, + 12, + 109, + 72, + 255, + 58, + 156, + 251, + 0, + 144, + 35, + 204, + 0, + 44, + 160, + 117, + 254, + 50, + 107, + 194, + 0, + 1, + 68, + 165, + 255, + 111, + 110, + 162, + 0, + 158, + 83, + 40, + 254, + 76, + 214, + 234, + 0, + 58, + 216, + 205, + 255, + 171, + 96, + 147, + 255, + 40, + 227, + 114, + 1, + 176, + 227, + 241, + 0, + 70, + 249, + 183, + 1, + 136, + 84, + 139, + 255, + 60, + 122, + 247, + 254, + 143, + 9, + 117, + 255, + 177, + 174, + 137, + 254, + 73, + 247, + 143, + 0, + 236, + 185, + 126, + 255, + 62, + 25, + 247, + 255, + 45, + 64, + 56, + 255, + 161, + 244, + 6, + 0, + 34, + 57, + 56, + 1, + 105, + 202, + 83, + 0, + 128, + 147, + 208, + 0, + 6, + 103, + 10, + 255, + 74, + 138, + 65, + 255, + 97, + 80, + 100, + 255, + 214, + 174, + 33, + 255, + 50, + 134, + 74, + 255, + 110, + 151, + 130, + 254, + 111, + 84, + 172, + 0, + 84, + 199, + 75, + 254, + 248, + 59, + 112, + 255, + 8, + 216, + 178, + 1, + 9, + 183, + 95, + 0, + 238, + 27, + 8, + 254, + 170, + 205, + 220, + 0, + 195, + 229, + 135, + 0, + 98, + 76, + 237, + 255, + 226, + 91, + 26, + 1, + 82, + 219, + 39, + 255, + 225, + 190, + 199, + 1, + 217, + 200, + 121, + 255, + 81, + 179, + 8, + 255, + 140, + 65, + 206, + 0, + 178, + 207, + 87, + 254, + 250, + 252, + 46, + 255, + 104, + 89, + 110, + 1, + 253, + 189, + 158, + 255, + 144, + 214, + 158, + 255, + 160, + 245, + 54, + 255, + 53, + 183, + 92, + 1, + 21, + 200, + 194, + 255, + 146, + 33, + 113, + 1, + 209, + 1, + 255, + 0, + 235, + 106, + 43, + 255, + 167, + 52, + 232, + 0, + 157, + 229, + 221, + 0, + 51, + 30, + 25, + 0, + 250, + 221, + 27, + 1, + 65, + 147, + 87, + 255, + 79, + 123, + 196, + 0, + 65, + 196, + 223, + 255, + 76, + 44, + 17, + 1, + 85, + 241, + 68, + 0, + 202, + 183, + 249, + 255, + 65, + 212, + 212, + 255, + 9, + 33, + 154, + 1, + 71, + 59, + 80, + 0, + 175, + 194, + 59, + 255, + 141, + 72, + 9, + 0, + 100, + 160, + 244, + 0, + 230, + 208, + 56, + 0, + 59, + 25, + 75, + 254, + 80, + 194, + 194, + 0, + 18, + 3, + 200, + 254, + 160, + 159, + 115, + 0, + 132, + 143, + 247, + 1, + 111, + 93, + 57, + 255, + 58, + 237, + 11, + 1, + 134, + 222, + 135, + 255, + 122, + 163, + 108, + 1, + 123, + 43, + 190, + 255, + 251, + 189, + 206, + 254, + 80, + 182, + 72, + 255, + 208, + 246, + 224, + 1, + 17, + 60, + 9, + 0, + 161, + 207, + 38, + 0, + 141, + 109, + 91, + 0, + 216, + 15, + 211, + 255, + 136, + 78, + 110, + 0, + 98, + 163, + 104, + 255, + 21, + 80, + 121, + 255, + 173, + 178, + 183, + 1, + 127, + 143, + 4, + 0, + 104, + 60, + 82, + 254, + 214, + 16, + 13, + 255, + 96, + 238, + 33, + 1, + 158, + 148, + 230, + 255, + 127, + 129, + 62, + 255, + 51, + 255, + 210, + 255, + 62, + 141, + 236, + 254, + 157, + 55, + 224, + 255, + 114, + 39, + 244, + 0, + 192, + 188, + 250, + 255, + 228, + 76, + 53, + 0, + 98, + 84, + 81, + 255, + 173, + 203, + 61, + 254, + 147, + 50, + 55, + 255, + 204, + 235, + 191, + 0, + 52, + 197, + 244, + 0, + 88, + 43, + 211, + 254, + 27, + 191, + 119, + 0, + 188, + 231, + 154, + 0, + 66, + 81, + 161, + 0, + 92, + 193, + 160, + 1, + 250, + 227, + 120, + 0, + 123, + 55, + 226, + 0, + 184, + 17, + 72, + 0, + 133, + 168, + 10, + 254, + 22, + 135, + 156, + 255, + 41, + 25, + 103, + 255, + 48, + 202, + 58, + 0, + 186, + 149, + 81, + 255, + 188, + 134, + 239, + 0, + 235, + 181, + 189, + 254, + 217, + 139, + 188, + 255, + 74, + 48, + 82, + 0, + 46, + 218, + 229, + 0, + 189, + 253, + 251, + 0, + 50, + 229, + 12, + 255, + 211, + 141, + 191, + 1, + 128, + 244, + 25, + 255, + 169, + 231, + 122, + 254, + 86, + 47, + 189, + 255, + 132, + 183, + 23, + 255, + 37, + 178, + 150, + 255, + 51, + 137, + 253, + 0, + 200, + 78, + 31, + 0, + 22, + 105, + 50, + 0, + 130, + 60, + 0, + 0, + 132, + 163, + 91, + 254, + 23, + 231, + 187, + 0, + 192, + 79, + 239, + 0, + 157, + 102, + 164, + 255, + 192, + 82, + 20, + 1, + 24, + 181, + 103, + 255, + 240, + 9, + 234, + 0, + 1, + 123, + 164, + 255, + 133, + 233, + 0, + 255, + 202, + 242, + 242, + 0, + 60, + 186, + 245, + 0, + 241, + 16, + 199, + 255, + 224, + 116, + 158, + 254, + 191, + 125, + 91, + 255, + 224, + 86, + 207, + 0, + 121, + 37, + 231, + 255, + 227, + 9, + 198, + 255, + 15, + 153, + 239, + 255, + 121, + 232, + 217, + 254, + 75, + 112, + 82, + 0, + 95, + 12, + 57, + 254, + 51, + 214, + 105, + 255, + 148, + 220, + 97, + 1, + 199, + 98, + 36, + 0, + 156, + 209, + 12, + 254, + 10, + 212, + 52, + 0, + 217, + 180, + 55, + 254, + 212, + 170, + 232, + 255, + 216, + 20, + 84, + 255, + 157, + 250, + 135, + 0, + 157, + 99, + 127, + 254, + 1, + 206, + 41, + 0, + 149, + 36, + 70, + 1, + 54, + 196, + 201, + 255, + 87, + 116, + 0, + 254, + 235, + 171, + 150, + 0, + 27, + 163, + 234, + 0, + 202, + 135, + 180, + 0, + 208, + 95, + 0, + 254, + 123, + 156, + 93, + 0, + 183, + 62, + 75, + 0, + 137, + 235, + 182, + 0, + 204, + 225, + 255, + 255, + 214, + 139, + 210, + 255, + 2, + 115, + 8, + 255, + 29, + 12, + 111, + 0, + 52, + 156, + 1, + 0, + 253, + 21, + 251, + 255, + 37, + 165, + 31, + 254, + 12, + 130, + 211, + 0, + 106, + 18, + 53, + 254, + 42, + 99, + 154, + 0, + 14, + 217, + 61, + 254, + 216, + 11, + 92, + 255, + 200, + 197, + 112, + 254, + 147, + 38, + 199, + 0, + 36, + 252, + 120, + 254, + 107, + 169, + 77, + 0, + 1, + 123, + 159, + 255, + 207, + 75, + 102, + 0, + 163, + 175, + 196, + 0, + 44, + 1, + 240, + 0, + 120, + 186, + 176, + 254, + 13, + 98, + 76, + 255, + 237, + 124, + 241, + 255, + 232, + 146, + 188, + 255, + 200, + 96, + 224, + 0, + 204, + 31, + 41, + 0, + 208, + 200, + 13, + 0, + 21, + 225, + 96, + 255, + 175, + 156, + 196, + 0, + 247, + 208, + 126, + 0, + 62, + 184, + 244, + 254, + 2, + 171, + 81, + 0, + 85, + 115, + 158, + 0, + 54, + 64, + 45, + 255, + 19, + 138, + 114, + 0, + 135, + 71, + 205, + 0, + 227, + 47, + 147, + 1, + 218, + 231, + 66, + 0, + 253, + 209, + 28, + 0, + 244, + 15, + 173, + 255, + 6, + 15, + 118, + 254, + 16, + 150, + 208, + 255, + 185, + 22, + 50, + 255, + 86, + 112, + 207, + 255, + 75, + 113, + 215, + 1, + 63, + 146, + 43, + 255, + 4, + 225, + 19, + 254, + 227, + 23, + 62, + 255, + 14, + 255, + 214, + 254, + 45, + 8, + 205, + 255, + 87, + 197, + 151, + 254, + 210, + 82, + 215, + 255, + 245, + 248, + 247, + 255, + 128, + 248, + 70, + 0, + 225, + 247, + 87, + 0, + 90, + 120, + 70, + 0, + 213, + 245, + 92, + 0, + 13, + 133, + 226, + 0, + 47, + 181, + 5, + 1, + 92, + 163, + 105, + 255, + 6, + 30, + 133, + 254, + 232, + 178, + 61, + 255, + 230, + 149, + 24, + 255, + 18, + 49, + 158, + 0, + 228, + 100, + 61, + 254, + 116, + 243, + 251, + 255, + 77, + 75, + 92, + 1, + 81, + 219, + 147, + 255, + 76, + 163, + 254, + 254, + 141, + 213, + 246, + 0, + 232, + 37, + 152, + 254, + 97, + 44, + 100, + 0, + 201, + 37, + 50, + 1, + 212, + 244, + 57, + 0, + 174, + 171, + 183, + 255, + 249, + 74, + 112, + 0, + 166, + 156, + 30, + 0, + 222, + 221, + 97, + 255, + 243, + 93, + 73, + 254, + 251, + 101, + 100, + 255, + 216, + 217, + 93, + 255, + 254, + 138, + 187, + 255, + 142, + 190, + 52, + 255, + 59, + 203, + 177, + 255, + 200, + 94, + 52, + 0, + 115, + 114, + 158, + 255, + 165, + 152, + 104, + 1, + 126, + 99, + 226, + 255, + 118, + 157, + 244, + 1, + 107, + 200, + 16, + 0, + 193, + 90, + 229, + 0, + 121, + 6, + 88, + 0, + 156, + 32, + 93, + 254, + 125, + 241, + 211, + 255, + 14, + 237, + 157, + 255, + 165, + 154, + 21, + 255, + 184, + 224, + 22, + 255, + 250, + 24, + 152, + 255, + 113, + 77, + 31, + 0, + 247, + 171, + 23, + 255, + 237, + 177, + 204, + 255, + 52, + 137, + 145, + 255, + 194, + 182, + 114, + 0, + 224, + 234, + 149, + 0, + 10, + 111, + 103, + 1, + 201, + 129, + 4, + 0, + 238, + 142, + 78, + 0, + 52, + 6, + 40, + 255, + 110, + 213, + 165, + 254, + 60, + 207, + 253, + 0, + 62, + 215, + 69, + 0, + 96, + 97, + 0, + 255, + 49, + 45, + 202, + 0, + 120, + 121, + 22, + 255, + 235, + 139, + 48, + 1, + 198, + 45, + 34, + 255, + 182, + 50, + 27, + 1, + 131, + 210, + 91, + 255, + 46, + 54, + 128, + 0, + 175, + 123, + 105, + 255, + 198, + 141, + 78, + 254, + 67, + 244, + 239, + 255, + 245, + 54, + 103, + 254, + 78, + 38, + 242, + 255, + 2, + 92, + 249, + 254, + 251, + 174, + 87, + 255, + 139, + 63, + 144, + 0, + 24, + 108, + 27, + 255, + 34, + 102, + 18, + 1, + 34, + 22, + 152, + 0, + 66, + 229, + 118, + 254, + 50, + 143, + 99, + 0, + 144, + 169, + 149, + 1, + 118, + 30, + 152, + 0, + 178, + 8, + 121, + 1, + 8, + 159, + 18, + 0, + 90, + 101, + 230, + 255, + 129, + 29, + 119, + 0, + 68, + 36, + 11, + 1, + 232, + 183, + 55, + 0, + 23, + 255, + 96, + 255, + 161, + 41, + 193, + 255, + 63, + 139, + 222, + 0, + 15, + 179, + 243, + 0, + 255, + 100, + 15, + 255, + 82, + 53, + 135, + 0, + 137, + 57, + 149, + 1, + 99, + 240, + 170, + 255, + 22, + 230, + 228, + 254, + 49, + 180, + 82, + 255, + 61, + 82, + 43, + 0, + 110, + 245, + 217, + 0, + 199, + 125, + 61, + 0, + 46, + 253, + 52, + 0, + 141, + 197, + 219, + 0, + 211, + 159, + 193, + 0, + 55, + 121, + 105, + 254, + 183, + 20, + 129, + 0, + 169, + 119, + 170, + 255, + 203, + 178, + 139, + 255, + 135, + 40, + 182, + 255, + 172, + 13, + 202, + 255, + 65, + 178, + 148, + 0, + 8, + 207, + 43, + 0, + 122, + 53, + 127, + 1, + 74, + 161, + 48, + 0, + 227, + 214, + 128, + 254, + 86, + 11, + 243, + 255, + 100, + 86, + 7, + 1, + 245, + 68, + 134, + 255, + 61, + 43, + 21, + 1, + 152, + 84, + 94, + 255, + 190, + 60, + 250, + 254, + 239, + 118, + 232, + 255, + 214, + 136, + 37, + 1, + 113, + 76, + 107, + 255, + 93, + 104, + 100, + 1, + 144, + 206, + 23, + 255, + 110, + 150, + 154, + 1, + 228, + 103, + 185, + 0, + 218, + 49, + 50, + 254, + 135, + 77, + 139, + 255, + 185, + 1, + 78, + 0, + 0, + 161, + 148, + 255, + 97, + 29, + 233, + 255, + 207, + 148, + 149, + 255, + 160, + 168, + 0, + 0, + 91, + 128, + 171, + 255, + 6, + 28, + 19, + 254, + 11, + 111, + 247, + 0, + 39, + 187, + 150, + 255, + 138, + 232, + 149, + 0, + 117, + 62, + 68, + 255, + 63, + 216, + 188, + 255, + 235, + 234, + 32, + 254, + 29, + 57, + 160, + 255, + 25, + 12, + 241, + 1, + 169, + 60, + 191, + 0, + 32, + 131, + 141, + 255, + 237, + 159, + 123, + 255, + 94, + 197, + 94, + 254, + 116, + 254, + 3, + 255, + 92, + 179, + 97, + 254, + 121, + 97, + 92, + 255, + 170, + 112, + 14, + 0, + 21, + 149, + 248, + 0, + 248, + 227, + 3, + 0, + 80, + 96, + 109, + 0, + 75, + 192, + 74, + 1, + 12, + 90, + 226, + 255, + 161, + 106, + 68, + 1, + 208, + 114, + 127, + 255, + 114, + 42, + 255, + 254, + 74, + 26, + 74, + 255, + 247, + 179, + 150, + 254, + 121, + 140, + 60, + 0, + 147, + 70, + 200, + 255, + 214, + 40, + 161, + 255, + 161, + 188, + 201, + 255, + 141, + 65, + 135, + 255, + 242, + 115, + 252, + 0, + 62, + 47, + 202, + 0, + 180, + 149, + 255, + 254, + 130, + 55, + 237, + 0, + 165, + 17, + 186, + 255, + 10, + 169, + 194, + 0, + 156, + 109, + 218, + 255, + 112, + 140, + 123, + 255, + 104, + 128, + 223, + 254, + 177, + 142, + 108, + 255, + 121, + 37, + 219, + 255, + 128, + 77, + 18, + 255, + 111, + 108, + 23, + 1, + 91, + 192, + 75, + 0, + 174, + 245, + 22, + 255, + 4, + 236, + 62, + 255, + 43, + 64, + 153, + 1, + 227, + 173, + 254, + 0, + 237, + 122, + 132, + 1, + 127, + 89, + 186, + 255, + 142, + 82, + 128, + 254, + 252, + 84, + 174, + 0, + 90, + 179, + 177, + 1, + 243, + 214, + 87, + 255, + 103, + 60, + 162, + 255, + 208, + 130, + 14, + 255, + 11, + 130, + 139, + 0, + 206, + 129, + 219, + 255, + 94, + 217, + 157, + 255, + 239, + 230, + 230, + 255, + 116, + 115, + 159, + 254, + 164, + 107, + 95, + 0, + 51, + 218, + 2, + 1, + 216, + 125, + 198, + 255, + 140, + 202, + 128, + 254, + 11, + 95, + 68, + 255, + 55, + 9, + 93, + 254, + 174, + 153, + 6, + 255, + 204, + 172, + 96, + 0, + 69, + 160, + 110, + 0, + 213, + 38, + 49, + 254, + 27, + 80, + 213, + 0, + 118, + 125, + 114, + 0, + 70, + 70, + 67, + 255, + 15, + 142, + 73, + 255, + 131, + 122, + 185, + 255, + 243, + 20, + 50, + 254, + 130, + 237, + 40, + 0, + 210, + 159, + 140, + 1, + 197, + 151, + 65, + 255, + 84, + 153, + 66, + 0, + 195, + 126, + 90, + 0, + 16, + 238, + 236, + 1, + 118, + 187, + 102, + 255, + 3, + 24, + 133, + 255, + 187, + 69, + 230, + 0, + 56, + 197, + 92, + 1, + 213, + 69, + 94, + 255, + 80, + 138, + 229, + 1, + 206, + 7, + 230, + 0, + 222, + 111, + 230, + 1, + 91, + 233, + 119, + 255, + 9, + 89, + 7, + 1, + 2, + 98, + 1, + 0, + 148, + 74, + 133, + 255, + 51, + 246, + 180, + 255, + 228, + 177, + 112, + 1, + 58, + 189, + 108, + 255, + 194, + 203, + 237, + 254, + 21, + 209, + 195, + 0, + 147, + 10, + 35, + 1, + 86, + 157, + 226, + 0, + 31, + 163, + 139, + 254, + 56, + 7, + 75, + 255, + 62, + 90, + 116, + 0, + 181, + 60, + 169, + 0, + 138, + 162, + 212, + 254, + 81, + 167, + 31, + 0, + 205, + 90, + 112, + 255, + 33, + 112, + 227, + 0, + 83, + 151, + 117, + 1, + 177, + 224, + 73, + 255, + 174, + 144, + 217, + 255, + 230, + 204, + 79, + 255, + 22, + 77, + 232, + 255, + 114, + 78, + 234, + 0, + 224, + 57, + 126, + 254, + 9, + 49, + 141, + 0, + 242, + 147, + 165, + 1, + 104, + 182, + 140, + 255, + 167, + 132, + 12, + 1, + 123, + 68, + 127, + 0, + 225, + 87, + 39, + 1, + 251, + 108, + 8, + 0, + 198, + 193, + 143, + 1, + 121, + 135, + 207, + 255, + 172, + 22, + 70, + 0, + 50, + 68, + 116, + 255, + 101, + 175, + 40, + 255, + 248, + 105, + 233, + 0, + 166, + 203, + 7, + 0, + 110, + 197, + 218, + 0, + 215, + 254, + 26, + 254, + 168, + 226, + 253, + 0, + 31, + 143, + 96, + 0, + 11, + 103, + 41, + 0, + 183, + 129, + 203, + 254, + 100, + 247, + 74, + 255, + 213, + 126, + 132, + 0, + 210, + 147, + 44, + 0, + 199, + 234, + 27, + 1, + 148, + 47, + 181, + 0, + 155, + 91, + 158, + 1, + 54, + 105, + 175, + 255, + 2, + 78, + 145, + 254, + 102, + 154, + 95, + 0, + 128, + 207, + 127, + 254, + 52, + 124, + 236, + 255, + 130, + 84, + 71, + 0, + 221, + 243, + 211, + 0, + 152, + 170, + 207, + 0, + 222, + 106, + 199, + 0, + 183, + 84, + 94, + 254, + 92, + 200, + 56, + 255, + 138, + 182, + 115, + 1, + 142, + 96, + 146, + 0, + 133, + 136, + 228, + 0, + 97, + 18, + 150, + 0, + 55, + 251, + 66, + 0, + 140, + 102, + 4, + 0, + 202, + 103, + 151, + 0, + 30, + 19, + 248, + 255, + 51, + 184, + 207, + 0, + 202, + 198, + 89, + 0, + 55, + 197, + 225, + 254, + 169, + 95, + 249, + 255, + 66, + 65, + 68, + 255, + 188, + 234, + 126, + 0, + 166, + 223, + 100, + 1, + 112, + 239, + 244, + 0, + 144, + 23, + 194, + 0, + 58, + 39, + 182, + 0, + 244, + 44, + 24, + 254, + 175, + 68, + 179, + 255, + 152, + 118, + 154, + 1, + 176, + 162, + 130, + 0, + 217, + 114, + 204, + 254, + 173, + 126, + 78, + 255, + 33, + 222, + 30, + 255, + 36, + 2, + 91, + 255, + 2, + 143, + 243, + 0, + 9, + 235, + 215, + 0, + 3, + 171, + 151, + 1, + 24, + 215, + 245, + 255, + 168, + 47, + 164, + 254, + 241, + 146, + 207, + 0, + 69, + 129, + 180, + 0, + 68, + 243, + 113, + 0, + 144, + 53, + 72, + 254, + 251, + 45, + 14, + 0, + 23, + 110, + 168, + 0, + 68, + 68, + 79, + 255, + 110, + 70, + 95, + 254, + 174, + 91, + 144, + 255, + 33, + 206, + 95, + 255, + 137, + 41, + 7, + 255, + 19, + 187, + 153, + 254, + 35, + 255, + 112, + 255, + 9, + 145, + 185, + 254, + 50, + 157, + 37, + 0, + 11, + 112, + 49, + 1, + 102, + 8, + 190, + 255, + 234, + 243, + 169, + 1, + 60, + 85, + 23, + 0, + 74, + 39, + 189, + 0, + 116, + 49, + 239, + 0, + 173, + 213, + 210, + 0, + 46, + 161, + 108, + 255, + 159, + 150, + 37, + 0, + 196, + 120, + 185, + 255, + 34, + 98, + 6, + 255, + 153, + 195, + 62, + 255, + 97, + 230, + 71, + 255, + 102, + 61, + 76, + 0, + 26, + 212, + 236, + 255, + 164, + 97, + 16, + 0, + 198, + 59, + 146, + 0, + 163, + 23, + 196, + 0, + 56, + 24, + 61, + 0, + 181, + 98, + 193, + 0, + 251, + 147, + 229, + 255, + 98, + 189, + 24, + 255, + 46, + 54, + 206, + 255, + 234, + 82, + 246, + 0, + 183, + 103, + 38, + 1, + 109, + 62, + 204, + 0, + 10, + 240, + 224, + 0, + 146, + 22, + 117, + 255, + 142, + 154, + 120, + 0, + 69, + 212, + 35, + 0, + 208, + 99, + 118, + 1, + 121, + 255, + 3, + 255, + 72, + 6, + 194, + 0, + 117, + 17, + 197, + 255, + 125, + 15, + 23, + 0, + 154, + 79, + 153, + 0, + 214, + 94, + 197, + 255, + 185, + 55, + 147, + 255, + 62, + 254, + 78, + 254, + 127, + 82, + 153, + 0, + 110, + 102, + 63, + 255, + 108, + 82, + 161, + 255, + 105, + 187, + 212, + 1, + 80, + 138, + 39, + 0, + 60, + 255, + 93, + 255, + 72, + 12, + 186, + 0, + 210, + 251, + 31, + 1, + 190, + 167, + 144, + 255, + 228, + 44, + 19, + 254, + 128, + 67, + 232, + 0, + 214, + 249, + 107, + 254, + 136, + 145, + 86, + 255, + 132, + 46, + 176, + 0, + 189, + 187, + 227, + 255, + 208, + 22, + 140, + 0, + 217, + 211, + 116, + 0, + 50, + 81, + 186, + 254, + 139, + 250, + 31, + 0, + 30, + 64, + 198, + 1, + 135, + 155, + 100, + 0, + 160, + 206, + 23, + 254, + 187, + 162, + 211, + 255, + 16, + 188, + 63, + 0, + 254, + 208, + 49, + 0, + 85, + 84, + 191, + 0, + 241, + 192, + 242, + 255, + 153, + 126, + 145, + 1, + 234, + 162, + 162, + 255, + 230, + 97, + 216, + 1, + 64, + 135, + 126, + 0, + 190, + 148, + 223, + 1, + 52, + 0, + 43, + 255, + 28, + 39, + 189, + 1, + 64, + 136, + 238, + 0, + 175, + 196, + 185, + 0, + 98, + 226, + 213, + 255, + 127, + 159, + 244, + 1, + 226, + 175, + 60, + 0, + 160, + 233, + 142, + 1, + 180, + 243, + 207, + 255, + 69, + 152, + 89, + 1, + 31, + 101, + 21, + 0, + 144, + 25, + 164, + 254, + 139, + 191, + 209, + 0, + 91, + 25, + 121, + 0, + 32, + 147, + 5, + 0, + 39, + 186, + 123, + 255, + 63, + 115, + 230, + 255, + 93, + 167, + 198, + 255, + 143, + 213, + 220, + 255, + 179, + 156, + 19, + 255, + 25, + 66, + 122, + 0, + 214, + 160, + 217, + 255, + 2, + 45, + 62, + 255, + 106, + 79, + 146, + 254, + 51, + 137, + 99, + 255, + 87, + 100, + 231, + 255, + 175, + 145, + 232, + 255, + 101, + 184, + 1, + 255, + 174, + 9, + 125, + 0, + 82, + 37, + 161, + 1, + 36, + 114, + 141, + 255, + 48, + 222, + 142, + 255, + 245, + 186, + 154, + 0, + 5, + 174, + 221, + 254, + 63, + 114, + 155, + 255, + 135, + 55, + 160, + 1, + 80, + 31, + 135, + 0, + 126, + 250, + 179, + 1, + 236, + 218, + 45, + 0, + 20, + 28, + 145, + 1, + 16, + 147, + 73, + 0, + 249, + 189, + 132, + 1, + 17, + 189, + 192, + 255, + 223, + 142, + 198, + 255, + 72, + 20, + 15, + 255, + 250, + 53, + 237, + 254, + 15, + 11, + 18, + 0, + 27, + 211, + 113, + 254, + 213, + 107, + 56, + 255, + 174, + 147, + 146, + 255, + 96, + 126, + 48, + 0, + 23, + 193, + 109, + 1, + 37, + 162, + 94, + 0, + 199, + 157, + 249, + 254, + 24, + 128, + 187, + 255, + 205, + 49, + 178, + 254, + 93, + 164, + 42, + 255, + 43, + 119, + 235, + 1, + 88, + 183, + 237, + 255, + 218, + 210, + 1, + 255, + 107, + 254, + 42, + 0, + 230, + 10, + 99, + 255, + 162, + 0, + 226, + 0, + 219, + 237, + 91, + 0, + 129, + 178, + 203, + 0, + 208, + 50, + 95, + 254, + 206, + 208, + 95, + 255, + 247, + 191, + 89, + 254, + 110, + 234, + 79, + 255, + 165, + 61, + 243, + 0, + 20, + 122, + 112, + 255, + 246, + 246, + 185, + 254, + 103, + 4, + 123, + 0, + 233, + 99, + 230, + 1, + 219, + 91, + 252, + 255, + 199, + 222, + 22, + 255, + 179, + 245, + 233, + 255, + 211, + 241, + 234, + 0, + 111, + 250, + 192, + 255, + 85, + 84, + 136, + 0, + 101, + 58, + 50, + 255, + 131, + 173, + 156, + 254, + 119, + 45, + 51, + 255, + 118, + 233, + 16, + 254, + 242, + 90, + 214, + 0, + 94, + 159, + 219, + 1, + 3, + 3, + 234, + 255, + 98, + 76, + 92, + 254, + 80, + 54, + 230, + 0, + 5, + 228, + 231, + 254, + 53, + 24, + 223, + 255, + 113, + 56, + 118, + 1, + 20, + 132, + 1, + 255, + 171, + 210, + 236, + 0, + 56, + 241, + 158, + 255, + 186, + 115, + 19, + 255, + 8, + 229, + 174, + 0, + 48, + 44, + 0, + 1, + 114, + 114, + 166, + 255, + 6, + 73, + 226, + 255, + 205, + 89, + 244, + 0, + 137, + 227, + 75, + 1, + 248, + 173, + 56, + 0, + 74, + 120, + 246, + 254, + 119, + 3, + 11, + 255, + 81, + 120, + 198, + 255, + 136, + 122, + 98, + 255, + 146, + 241, + 221, + 1, + 109, + 194, + 78, + 255, + 223, + 241, + 70, + 1, + 214, + 200, + 169, + 255, + 97, + 190, + 47, + 255, + 47, + 103, + 174, + 255, + 99, + 92, + 72, + 254, + 118, + 233, + 180, + 255, + 193, + 35, + 233, + 254, + 26, + 229, + 32, + 255, + 222, + 252, + 198, + 0, + 204, + 43, + 71, + 255, + 199, + 84, + 172, + 0, + 134, + 102, + 190, + 0, + 111, + 238, + 97, + 254, + 230, + 40, + 230, + 0, + 227, + 205, + 64, + 254, + 200, + 12, + 225, + 0, + 166, + 25, + 222, + 0, + 113, + 69, + 51, + 255, + 143, + 159, + 24, + 0, + 167, + 184, + 74, + 0, + 29, + 224, + 116, + 254, + 158, + 208, + 233, + 0, + 193, + 116, + 126, + 255, + 212, + 11, + 133, + 255, + 22, + 58, + 140, + 1, + 204, + 36, + 51, + 255, + 232, + 30, + 43, + 0, + 235, + 70, + 181, + 255, + 64, + 56, + 146, + 254, + 169, + 18, + 84, + 255, + 226, + 1, + 13, + 255, + 200, + 50, + 176, + 255, + 52, + 213, + 245, + 254, + 168, + 209, + 97, + 0, + 191, + 71, + 55, + 0, + 34, + 78, + 156, + 0, + 232, + 144, + 58, + 1, + 185, + 74, + 189, + 0, + 186, + 142, + 149, + 254, + 64, + 69, + 127, + 255, + 161, + 203, + 147, + 255, + 176, + 151, + 191, + 0, + 136, + 231, + 203, + 254, + 163, + 182, + 137, + 0, + 161, + 126, + 251, + 254, + 233, + 32, + 66, + 0, + 68, + 207, + 66, + 0, + 30, + 28, + 37, + 0, + 93, + 114, + 96, + 1, + 254, + 92, + 247, + 255, + 44, + 171, + 69, + 0, + 202, + 119, + 11, + 255, + 188, + 118, + 50, + 1, + 255, + 83, + 136, + 255, + 71, + 82, + 26, + 0, + 70, + 227, + 2, + 0, + 32, + 235, + 121, + 1, + 181, + 41, + 154, + 0, + 71, + 134, + 229, + 254, + 202, + 255, + 36, + 0, + 41, + 152, + 5, + 0, + 154, + 63, + 73, + 255, + 34, + 182, + 124, + 0, + 121, + 221, + 150, + 255, + 26, + 204, + 213, + 1, + 41, + 172, + 87, + 0, + 90, + 157, + 146, + 255, + 109, + 130, + 20, + 0, + 71, + 107, + 200, + 255, + 243, + 102, + 189, + 0, + 1, + 195, + 145, + 254, + 46, + 88, + 117, + 0, + 8, + 206, + 227, + 0, + 191, + 110, + 253, + 255, + 109, + 128, + 20, + 254, + 134, + 85, + 51, + 255, + 137, + 177, + 112, + 1, + 216, + 34, + 22, + 255, + 131, + 16, + 208, + 255, + 121, + 149, + 170, + 0, + 114, + 19, + 23, + 1, + 166, + 80, + 31, + 255, + 113, + 240, + 122, + 0, + 232, + 179, + 250, + 0, + 68, + 110, + 180, + 254, + 210, + 170, + 119, + 0, + 223, + 108, + 164, + 255, + 207, + 79, + 233, + 255, + 27, + 229, + 226, + 254, + 209, + 98, + 81, + 255, + 79, + 68, + 7, + 0, + 131, + 185, + 100, + 0, + 170, + 29, + 162, + 255, + 17, + 162, + 107, + 255, + 57, + 21, + 11, + 1, + 100, + 200, + 181, + 255, + 127, + 65, + 166, + 1, + 165, + 134, + 204, + 0, + 104, + 167, + 168, + 0, + 1, + 164, + 79, + 0, + 146, + 135, + 59, + 1, + 70, + 50, + 128, + 255, + 102, + 119, + 13, + 254, + 227, + 6, + 135, + 0, + 162, + 142, + 179, + 255, + 160, + 100, + 222, + 0, + 27, + 224, + 219, + 1, + 158, + 93, + 195, + 255, + 234, + 141, + 137, + 0, + 16, + 24, + 125, + 255, + 238, + 206, + 47, + 255, + 97, + 17, + 98, + 255, + 116, + 110, + 12, + 255, + 96, + 115, + 77, + 0, + 91, + 227, + 232, + 255, + 248, + 254, + 79, + 255, + 92, + 229, + 6, + 254, + 88, + 198, + 139, + 0, + 206, + 75, + 129, + 0, + 250, + 77, + 206, + 255, + 141, + 244, + 123, + 1, + 138, + 69, + 220, + 0, + 32, + 151, + 6, + 1, + 131, + 167, + 22, + 255, + 237, + 68, + 167, + 254, + 199, + 189, + 150, + 0, + 163, + 171, + 138, + 255, + 51, + 188, + 6, + 255, + 95, + 29, + 137, + 254, + 148, + 226, + 179, + 0, + 181, + 107, + 208, + 255, + 134, + 31, + 82, + 255, + 151, + 101, + 45, + 255, + 129, + 202, + 225, + 0, + 224, + 72, + 147, + 0, + 48, + 138, + 151, + 255, + 195, + 64, + 206, + 254, + 237, + 218, + 158, + 0, + 106, + 29, + 137, + 254, + 253, + 189, + 233, + 255, + 103, + 15, + 17, + 255, + 194, + 97, + 255, + 0, + 178, + 45, + 169, + 254, + 198, + 225, + 155, + 0, + 39, + 48, + 117, + 255, + 135, + 106, + 115, + 0, + 97, + 38, + 181, + 0, + 150, + 47, + 65, + 255, + 83, + 130, + 229, + 254, + 246, + 38, + 129, + 0, + 92, + 239, + 154, + 254, + 91, + 99, + 127, + 0, + 161, + 111, + 33, + 255, + 238, + 217, + 242, + 255, + 131, + 185, + 195, + 255, + 213, + 191, + 158, + 255, + 41, + 150, + 218, + 0, + 132, + 169, + 131, + 0, + 89, + 84, + 252, + 1, + 171, + 70, + 128, + 255, + 163, + 248, + 203, + 254, + 1, + 50, + 180, + 255, + 124, + 76, + 85, + 1, + 251, + 111, + 80, + 0, + 99, + 66, + 239, + 255, + 154, + 237, + 182, + 255, + 221, + 126, + 133, + 254, + 74, + 204, + 99, + 255, + 65, + 147, + 119, + 255, + 99, + 56, + 167, + 255, + 79, + 248, + 149, + 255, + 116, + 155, + 228, + 255, + 237, + 43, + 14, + 254, + 69, + 137, + 11, + 255, + 22, + 250, + 241, + 1, + 91, + 122, + 143, + 255, + 205, + 249, + 243, + 0, + 212, + 26, + 60, + 255, + 48, + 182, + 176, + 1, + 48, + 23, + 191, + 255, + 203, + 121, + 152, + 254, + 45, + 74, + 213, + 255, + 62, + 90, + 18, + 254, + 245, + 163, + 230, + 255, + 185, + 106, + 116, + 255, + 83, + 35, + 159, + 0, + 12, + 33, + 2, + 255, + 80, + 34, + 62, + 0, + 16, + 87, + 174, + 255, + 173, + 101, + 85, + 0, + 202, + 36, + 81, + 254, + 160, + 69, + 204, + 255, + 64, + 225, + 187, + 0, + 58, + 206, + 94, + 0, + 86, + 144, + 47, + 0, + 229, + 86, + 245, + 0, + 63, + 145, + 190, + 1, + 37, + 5, + 39, + 0, + 109, + 251, + 26, + 0, + 137, + 147, + 234, + 0, + 162, + 121, + 145, + 255, + 144, + 116, + 206, + 255, + 197, + 232, + 185, + 255, + 183, + 190, + 140, + 255, + 73, + 12, + 254, + 255, + 139, + 20, + 242, + 255, + 170, + 90, + 239, + 255, + 97, + 66, + 187, + 255, + 245, + 181, + 135, + 254, + 222, + 136, + 52, + 0, + 245, + 5, + 51, + 254, + 203, + 47, + 78, + 0, + 152, + 101, + 216, + 0, + 73, + 23, + 125, + 0, + 254, + 96, + 33, + 1, + 235, + 210, + 73, + 255, + 43, + 209, + 88, + 1, + 7, + 129, + 109, + 0, + 122, + 104, + 228, + 254, + 170, + 242, + 203, + 0, + 242, + 204, + 135, + 255, + 202, + 28, + 233, + 255, + 65, + 6, + 127, + 0, + 159, + 144, + 71, + 0, + 100, + 140, + 95, + 0, + 78, + 150, + 13, + 0, + 251, + 107, + 118, + 1, + 182, + 58, + 125, + 255, + 1, + 38, + 108, + 255, + 141, + 189, + 209, + 255, + 8, + 155, + 125, + 1, + 113, + 163, + 91, + 255, + 121, + 79, + 190, + 255, + 134, + 239, + 108, + 255, + 76, + 47, + 248, + 0, + 163, + 228, + 239, + 0, + 17, + 111, + 10, + 0, + 88, + 149, + 75, + 255, + 215, + 235, + 239, + 0, + 167, + 159, + 24, + 255, + 47, + 151, + 108, + 255, + 107, + 209, + 188, + 0, + 233, + 231, + 99, + 254, + 28, + 202, + 148, + 255, + 174, + 35, + 138, + 255, + 110, + 24, + 68, + 255, + 2, + 69, + 181, + 0, + 107, + 102, + 82, + 0, + 102, + 237, + 7, + 0, + 92, + 36, + 237, + 255, + 221, + 162, + 83, + 1, + 55, + 202, + 6, + 255, + 135, + 234, + 135, + 255, + 24, + 250, + 222, + 0, + 65, + 94, + 168, + 254, + 245, + 248, + 210, + 255, + 167, + 108, + 201, + 254, + 255, + 161, + 111, + 0, + 205, + 8, + 254, + 0, + 136, + 13, + 116, + 0, + 100, + 176, + 132, + 255, + 43, + 215, + 126, + 255, + 177, + 133, + 130, + 255, + 158, + 79, + 148, + 0, + 67, + 224, + 37, + 1, + 12, + 206, + 21, + 255, + 62, + 34, + 110, + 1, + 237, + 104, + 175, + 255, + 80, + 132, + 111, + 255, + 142, + 174, + 72, + 0, + 84, + 229, + 180, + 254, + 105, + 179, + 140, + 0, + 64, + 248, + 15, + 255, + 233, + 138, + 16, + 0, + 245, + 67, + 123, + 254, + 218, + 121, + 212, + 255, + 63, + 95, + 218, + 1, + 213, + 133, + 137, + 255, + 143, + 182, + 82, + 255, + 48, + 28, + 11, + 0, + 244, + 114, + 141, + 1, + 209, + 175, + 76, + 255, + 157, + 181, + 150, + 255, + 186, + 229, + 3, + 255, + 164, + 157, + 111, + 1, + 231, + 189, + 139, + 0, + 119, + 202, + 190, + 255, + 218, + 106, + 64, + 255, + 68, + 235, + 63, + 254, + 96, + 26, + 172, + 255, + 187, + 47, + 11, + 1, + 215, + 18, + 251, + 255, + 81, + 84, + 89, + 0, + 68, + 58, + 128, + 0, + 94, + 113, + 5, + 1, + 92, + 129, + 208, + 255, + 97, + 15, + 83, + 254, + 9, + 28, + 188, + 0, + 239, + 9, + 164, + 0, + 60, + 205, + 152, + 0, + 192, + 163, + 98, + 255, + 184, + 18, + 60, + 0, + 217, + 182, + 139, + 0, + 109, + 59, + 120, + 255, + 4, + 192, + 251, + 0, + 169, + 210, + 240, + 255, + 37, + 172, + 92, + 254, + 148, + 211, + 245, + 255, + 179, + 65, + 52, + 0, + 253, + 13, + 115, + 0, + 185, + 174, + 206, + 1, + 114, + 188, + 149, + 255, + 237, + 90, + 173, + 0, + 43, + 199, + 192, + 255, + 88, + 108, + 113, + 0, + 52, + 35, + 76, + 0, + 66, + 25, + 148, + 255, + 221, + 4, + 7, + 255, + 151, + 241, + 114, + 255, + 190, + 209, + 232, + 0, + 98, + 50, + 199, + 0, + 151, + 150, + 213, + 255, + 18, + 74, + 36, + 1, + 53, + 40, + 7, + 0, + 19, + 135, + 65, + 255, + 26, + 172, + 69, + 0, + 174, + 237, + 85, + 0, + 99, + 95, + 41, + 0, + 3, + 56, + 16, + 0, + 39, + 160, + 177, + 255, + 200, + 106, + 218, + 254, + 185, + 68, + 84, + 255, + 91, + 186, + 61, + 254, + 67, + 143, + 141, + 255, + 13, + 244, + 166, + 255, + 99, + 114, + 198, + 0, + 199, + 110, + 163, + 255, + 193, + 18, + 186, + 0, + 124, + 239, + 246, + 1, + 110, + 68, + 22, + 0, + 2, + 235, + 46, + 1, + 212, + 60, + 107, + 0, + 105, + 42, + 105, + 1, + 14, + 230, + 152, + 0, + 7, + 5, + 131, + 0, + 141, + 104, + 154, + 255, + 213, + 3, + 6, + 0, + 131, + 228, + 162, + 255, + 179, + 100, + 28, + 1, + 231, + 123, + 85, + 255, + 206, + 14, + 223, + 1, + 253, + 96, + 230, + 0, + 38, + 152, + 149, + 1, + 98, + 137, + 122, + 0, + 214, + 205, + 3, + 255, + 226, + 152, + 179, + 255, + 6, + 133, + 137, + 0, + 158, + 69, + 140, + 255, + 113, + 162, + 154, + 255, + 180, + 243, + 172, + 255, + 27, + 189, + 115, + 255, + 143, + 46, + 220, + 255, + 213, + 134, + 225, + 255, + 126, + 29, + 69, + 0, + 188, + 43, + 137, + 1, + 242, + 70, + 9, + 0, + 90, + 204, + 255, + 255, + 231, + 170, + 147, + 0, + 23, + 56, + 19, + 254, + 56, + 125, + 157, + 255, + 48, + 179, + 218, + 255, + 79, + 182, + 253, + 255, + 38, + 212, + 191, + 1, + 41, + 235, + 124, + 0, + 96, + 151, + 28, + 0, + 135, + 148, + 190, + 0, + 205, + 249, + 39, + 254, + 52, + 96, + 136, + 255, + 212, + 44, + 136, + 255, + 67, + 209, + 131, + 255, + 252, + 130, + 23, + 255, + 219, + 128, + 20, + 255, + 198, + 129, + 118, + 0, + 108, + 101, + 11, + 0, + 178, + 5, + 146, + 1, + 62, + 7, + 100, + 255, + 181, + 236, + 94, + 254, + 28, + 26, + 164, + 0, + 76, + 22, + 112, + 255, + 120, + 102, + 79, + 0, + 202, + 192, + 229, + 1, + 200, + 176, + 215, + 0, + 41, + 64, + 244, + 255, + 206, + 184, + 78, + 0, + 167, + 45, + 63, + 1, + 160, + 35, + 0, + 255, + 59, + 12, + 142, + 255, + 204, + 9, + 144, + 255, + 219, + 94, + 229, + 1, + 122, + 27, + 112, + 0, + 189, + 105, + 109, + 255, + 64, + 208, + 74, + 255, + 251, + 127, + 55, + 1, + 2, + 226, + 198, + 0, + 44, + 76, + 209, + 0, + 151, + 152, + 77, + 255, + 210, + 23, + 46, + 1, + 201, + 171, + 69, + 255, + 44, + 211, + 231, + 0, + 190, + 37, + 224, + 255, + 245, + 196, + 62, + 255, + 169, + 181, + 222, + 255, + 34, + 211, + 17, + 0, + 119, + 241, + 197, + 255, + 229, + 35, + 152, + 1, + 21, + 69, + 40, + 255, + 178, + 226, + 161, + 0, + 148, + 179, + 193, + 0, + 219, + 194, + 254, + 1, + 40, + 206, + 51, + 255, + 231, + 92, + 250, + 1, + 67, + 153, + 170, + 0, + 21, + 148, + 241, + 0, + 170, + 69, + 82, + 255, + 121, + 18, + 231, + 255, + 92, + 114, + 3, + 0, + 184, + 62, + 230, + 0, + 225, + 201, + 87, + 255, + 146, + 96, + 162, + 255, + 181, + 242, + 220, + 0, + 173, + 187, + 221, + 1, + 226, + 62, + 170, + 255, + 56, + 126, + 217, + 1, + 117, + 13, + 227, + 255, + 179, + 44, + 239, + 0, + 157, + 141, + 155, + 255, + 144, + 221, + 83, + 0, + 235, + 209, + 208, + 0, + 42, + 17, + 165, + 1, + 251, + 81, + 133, + 0, + 124, + 245, + 201, + 254, + 97, + 211, + 24, + 255, + 83, + 214, + 166, + 0, + 154, + 36, + 9, + 255, + 248, + 47, + 127, + 0, + 90, + 219, + 140, + 255, + 161, + 217, + 38, + 254, + 212, + 147, + 63, + 255, + 66, + 84, + 148, + 1, + 207, + 3, + 1, + 0, + 230, + 134, + 89, + 1, + 127, + 78, + 122, + 255, + 224, + 155, + 1, + 255, + 82, + 136, + 74, + 0, + 178, + 156, + 208, + 255, + 186, + 25, + 49, + 255, + 222, + 3, + 210, + 1, + 229, + 150, + 190, + 255, + 85, + 162, + 52, + 255, + 41, + 84, + 141, + 255, + 73, + 123, + 84, + 254, + 93, + 17, + 150, + 0, + 119, + 19, + 28, + 1, + 32, + 22, + 215, + 255, + 28, + 23, + 204, + 255, + 142, + 241, + 52, + 255, + 228, + 52, + 125, + 0, + 29, + 76, + 207, + 0, + 215, + 167, + 250, + 254, + 175, + 164, + 230, + 0, + 55, + 207, + 105, + 1, + 109, + 187, + 245, + 255, + 161, + 44, + 220, + 1, + 41, + 101, + 128, + 255, + 167, + 16, + 94, + 0, + 93, + 214, + 107, + 255, + 118, + 72, + 0, + 254, + 80, + 61, + 234, + 255, + 121, + 175, + 125, + 0, + 139, + 169, + 251, + 0, + 97, + 39, + 147, + 254, + 250, + 196, + 49, + 255, + 165, + 179, + 110, + 254, + 223, + 70, + 187, + 255, + 22, + 142, + 125, + 1, + 154, + 179, + 138, + 255, + 118, + 176, + 42, + 1, + 10, + 174, + 153, + 0, + 156, + 92, + 102, + 0, + 168, + 13, + 161, + 255, + 143, + 16, + 32, + 0, + 250, + 197, + 180, + 255, + 203, + 163, + 44, + 1, + 87, + 32, + 36, + 0, + 161, + 153, + 20, + 255, + 123, + 252, + 15, + 0, + 25, + 227, + 80, + 0, + 60, + 88, + 142, + 0, + 17, + 22, + 201, + 1, + 154, + 205, + 77, + 255, + 39, + 63, + 47, + 0, + 8, + 122, + 141, + 0, + 128, + 23, + 182, + 254, + 204, + 39, + 19, + 255, + 4, + 112, + 29, + 255, + 23, + 36, + 140, + 255, + 210, + 234, + 116, + 254, + 53, + 50, + 63, + 255, + 121, + 171, + 104, + 255, + 160, + 219, + 94, + 0, + 87, + 82, + 14, + 254, + 231, + 42, + 5, + 0, + 165, + 139, + 127, + 254, + 86, + 78, + 38, + 0, + 130, + 60, + 66, + 254, + 203, + 30, + 45, + 255, + 46, + 196, + 122, + 1, + 249, + 53, + 162, + 255, + 136, + 143, + 103, + 254, + 215, + 210, + 114, + 0, + 231, + 7, + 160, + 254, + 169, + 152, + 42, + 255, + 111, + 45, + 246, + 0, + 142, + 131, + 135, + 255, + 131, + 71, + 204, + 255, + 36, + 226, + 11, + 0, + 0, + 28, + 242, + 255, + 225, + 138, + 213, + 255, + 247, + 46, + 216, + 254, + 245, + 3, + 183, + 0, + 108, + 252, + 74, + 1, + 206, + 26, + 48, + 255, + 205, + 54, + 246, + 255, + 211, + 198, + 36, + 255, + 121, + 35, + 50, + 0, + 52, + 216, + 202, + 255, + 38, + 139, + 129, + 254, + 242, + 73, + 148, + 0, + 67, + 231, + 141, + 255, + 42, + 47, + 204, + 0, + 78, + 116, + 25, + 1, + 4, + 225, + 191, + 255, + 6, + 147, + 228, + 0, + 58, + 88, + 177, + 0, + 122, + 165, + 229, + 255, + 252, + 83, + 201, + 255, + 224, + 167, + 96, + 1, + 177, + 184, + 158, + 255, + 242, + 105, + 179, + 1, + 248, + 198, + 240, + 0, + 133, + 66, + 203, + 1, + 254, + 36, + 47, + 0, + 45, + 24, + 115, + 255, + 119, + 62, + 254, + 0, + 196, + 225, + 186, + 254, + 123, + 141, + 172, + 0, + 26, + 85, + 41, + 255, + 226, + 111, + 183, + 0, + 213, + 231, + 151, + 0, + 4, + 59, + 7, + 255, + 238, + 138, + 148, + 0, + 66, + 147, + 33, + 255, + 31, + 246, + 141, + 255, + 209, + 141, + 116, + 255, + 104, + 112, + 31, + 0, + 88, + 161, + 172, + 0, + 83, + 215, + 230, + 254, + 47, + 111, + 151, + 0, + 45, + 38, + 52, + 1, + 132, + 45, + 204, + 0, + 138, + 128, + 109, + 254, + 233, + 117, + 134, + 255, + 243, + 190, + 173, + 254, + 241, + 236, + 240, + 0, + 82, + 127, + 236, + 254, + 40, + 223, + 161, + 255, + 110, + 182, + 225, + 255, + 123, + 174, + 239, + 0, + 135, + 242, + 145, + 1, + 51, + 209, + 154, + 0, + 150, + 3, + 115, + 254, + 217, + 164, + 252, + 255, + 55, + 156, + 69, + 1, + 84, + 94, + 255, + 255, + 232, + 73, + 45, + 1, + 20, + 19, + 212, + 255, + 96, + 197, + 59, + 254, + 96, + 251, + 33, + 0, + 38, + 199, + 73, + 1, + 64, + 172, + 247, + 255, + 117, + 116, + 56, + 255, + 228, + 17, + 18, + 0, + 62, + 138, + 103, + 1, + 246, + 229, + 164, + 255, + 244, + 118, + 201, + 254, + 86, + 32, + 159, + 255, + 109, + 34, + 137, + 1, + 85, + 211, + 186, + 0, + 10, + 193, + 193, + 254, + 122, + 194, + 177, + 0, + 122, + 238, + 102, + 255, + 162, + 218, + 171, + 0, + 108, + 217, + 161, + 1, + 158, + 170, + 34, + 0, + 176, + 47, + 155, + 1, + 181, + 228, + 11, + 255, + 8, + 156, + 0, + 0, + 16, + 75, + 93, + 0, + 206, + 98, + 255, + 1, + 58, + 154, + 35, + 0, + 12, + 243, + 184, + 254, + 67, + 117, + 66, + 255, + 230, + 229, + 123, + 0, + 201, + 42, + 110, + 0, + 134, + 228, + 178, + 254, + 186, + 108, + 118, + 255, + 58, + 19, + 154, + 255, + 82, + 169, + 62, + 255, + 114, + 143, + 115, + 1, + 239, + 196, + 50, + 255, + 173, + 48, + 193, + 255, + 147, + 2, + 84, + 255, + 150, + 134, + 147, + 254, + 95, + 232, + 73, + 0, + 109, + 227, + 52, + 254, + 191, + 137, + 10, + 0, + 40, + 204, + 30, + 254, + 76, + 52, + 97, + 255, + 164, + 235, + 126, + 0, + 254, + 124, + 188, + 0, + 74, + 182, + 21, + 1, + 121, + 29, + 35, + 255, + 241, + 30, + 7, + 254, + 85, + 218, + 214, + 255, + 7, + 84, + 150, + 254, + 81, + 27, + 117, + 255, + 160, + 159, + 152, + 254, + 66, + 24, + 221, + 255, + 227, + 10, + 60, + 1, + 141, + 135, + 102, + 0, + 208, + 189, + 150, + 1, + 117, + 179, + 92, + 0, + 132, + 22, + 136, + 255, + 120, + 199, + 28, + 0, + 21, + 129, + 79, + 254, + 182, + 9, + 65, + 0, + 218, + 163, + 169, + 0, + 246, + 147, + 198, + 255, + 107, + 38, + 144, + 1, + 78, + 175, + 205, + 255, + 214, + 5, + 250, + 254, + 47, + 88, + 29, + 255, + 164, + 47, + 204, + 255, + 43, + 55, + 6, + 255, + 131, + 134, + 207, + 254, + 116, + 100, + 214, + 0, + 96, + 140, + 75, + 1, + 106, + 220, + 144, + 0, + 195, + 32, + 28, + 1, + 172, + 81, + 5, + 255, + 199, + 179, + 52, + 255, + 37, + 84, + 203, + 0, + 170, + 112, + 174, + 0, + 11, + 4, + 91, + 0, + 69, + 244, + 27, + 1, + 117, + 131, + 92, + 0, + 33, + 152, + 175, + 255, + 140, + 153, + 107, + 255, + 251, + 135, + 43, + 254, + 87, + 138, + 4, + 255, + 198, + 234, + 147, + 254, + 121, + 152, + 84, + 255, + 205, + 101, + 155, + 1, + 157, + 9, + 25, + 0, + 72, + 106, + 17, + 254, + 108, + 153, + 0, + 255, + 189, + 229, + 186, + 0, + 193, + 8, + 176, + 255, + 174, + 149, + 209, + 0, + 238, + 130, + 29, + 0, + 233, + 214, + 126, + 1, + 61, + 226, + 102, + 0, + 57, + 163, + 4, + 1, + 198, + 111, + 51, + 255, + 45, + 79, + 78, + 1, + 115, + 210, + 10, + 255, + 218, + 9, + 25, + 255, + 158, + 139, + 198, + 255, + 211, + 82, + 187, + 254, + 80, + 133, + 83, + 0, + 157, + 129, + 230, + 1, + 243, + 133, + 134, + 255, + 40, + 136, + 16, + 0, + 77, + 107, + 79, + 255, + 183, + 85, + 92, + 1, + 177, + 204, + 202, + 0, + 163, + 71, + 147, + 255, + 152, + 69, + 190, + 0, + 172, + 51, + 188, + 1, + 250, + 210, + 172, + 255, + 211, + 242, + 113, + 1, + 89, + 89, + 26, + 255, + 64, + 66, + 111, + 254, + 116, + 152, + 42, + 0, + 161, + 39, + 27, + 255, + 54, + 80, + 254, + 0, + 106, + 209, + 115, + 1, + 103, + 124, + 97, + 0, + 221, + 230, + 98, + 255, + 31, + 231, + 6, + 0, + 178, + 192, + 120, + 254, + 15, + 217, + 203, + 255, + 124, + 158, + 79, + 0, + 112, + 145, + 247, + 0, + 92, + 250, + 48, + 1, + 163, + 181, + 193, + 255, + 37, + 47, + 142, + 254, + 144, + 189, + 165, + 255, + 46, + 146, + 240, + 0, + 6, + 75, + 128, + 0, + 41, + 157, + 200, + 254, + 87, + 121, + 213, + 0, + 1, + 113, + 236, + 0, + 5, + 45, + 250, + 0, + 144, + 12, + 82, + 0, + 31, + 108, + 231, + 0, + 225, + 239, + 119, + 255, + 167, + 7, + 189, + 255, + 187, + 228, + 132, + 255, + 110, + 189, + 34, + 0, + 94, + 44, + 204, + 1, + 162, + 52, + 197, + 0, + 78, + 188, + 241, + 254, + 57, + 20, + 141, + 0, + 244, + 146, + 47, + 1, + 206, + 100, + 51, + 0, + 125, + 107, + 148, + 254, + 27, + 195, + 77, + 0, + 152, + 253, + 90, + 1, + 7, + 143, + 144, + 255, + 51, + 37, + 31, + 0, + 34, + 119, + 38, + 255, + 7, + 197, + 118, + 0, + 153, + 188, + 211, + 0, + 151, + 20, + 116, + 254, + 245, + 65, + 52, + 255, + 180, + 253, + 110, + 1, + 47, + 177, + 209, + 0, + 161, + 99, + 17, + 255, + 118, + 222, + 202, + 0, + 125, + 179, + 252, + 1, + 123, + 54, + 126, + 255, + 145, + 57, + 191, + 0, + 55, + 186, + 121, + 0, + 10, + 243, + 138, + 0, + 205, + 211, + 229, + 255, + 125, + 156, + 241, + 254, + 148, + 156, + 185, + 255, + 227, + 19, + 188, + 255, + 124, + 41, + 32, + 255, + 31, + 34, + 206, + 254, + 17, + 57, + 83, + 0, + 204, + 22, + 37, + 255, + 42, + 96, + 98, + 0, + 119, + 102, + 184, + 1, + 3, + 190, + 28, + 0, + 110, + 82, + 218, + 255, + 200, + 204, + 192, + 255, + 201, + 145, + 118, + 0, + 117, + 204, + 146, + 0, + 132, + 32, + 98, + 1, + 192, + 194, + 121, + 0, + 106, + 161, + 248, + 1, + 237, + 88, + 124, + 0, + 23, + 212, + 26, + 0, + 205, + 171, + 90, + 255, + 248, + 48, + 216, + 1, + 141, + 37, + 230, + 255, + 124, + 203, + 0, + 254, + 158, + 168, + 30, + 255, + 214, + 248, + 21, + 0, + 112, + 187, + 7, + 255, + ], + 'i8', + ALLOC_NONE, + Runtime.GLOBAL_BASE + 20480 +); +/* memory initializer */ allocate( + [ + 75, + 133, + 239, + 255, + 74, + 227, + 243, + 255, + 250, + 147, + 70, + 0, + 214, + 120, + 162, + 0, + 167, + 9, + 179, + 255, + 22, + 158, + 18, + 0, + 218, + 77, + 209, + 1, + 97, + 109, + 81, + 255, + 244, + 33, + 179, + 255, + 57, + 52, + 57, + 255, + 65, + 172, + 210, + 255, + 249, + 71, + 209, + 255, + 142, + 169, + 238, + 0, + 158, + 189, + 153, + 255, + 174, + 254, + 103, + 254, + 98, + 33, + 14, + 0, + 141, + 76, + 230, + 255, + 113, + 139, + 52, + 255, + 15, + 58, + 212, + 0, + 168, + 215, + 201, + 255, + 248, + 204, + 215, + 1, + 223, + 68, + 160, + 255, + 57, + 154, + 183, + 254, + 47, + 231, + 121, + 0, + 106, + 166, + 137, + 0, + 81, + 136, + 138, + 0, + 165, + 43, + 51, + 0, + 231, + 139, + 61, + 0, + 57, + 95, + 59, + 254, + 118, + 98, + 25, + 255, + 151, + 63, + 236, + 1, + 94, + 190, + 250, + 255, + 169, + 185, + 114, + 1, + 5, + 250, + 58, + 255, + 75, + 105, + 97, + 1, + 215, + 223, + 134, + 0, + 113, + 99, + 163, + 1, + 128, + 62, + 112, + 0, + 99, + 106, + 147, + 0, + 163, + 195, + 10, + 0, + 33, + 205, + 182, + 0, + 214, + 14, + 174, + 255, + 129, + 38, + 231, + 255, + 53, + 182, + 223, + 0, + 98, + 42, + 159, + 255, + 247, + 13, + 40, + 0, + 188, + 210, + 177, + 1, + 6, + 21, + 0, + 255, + 255, + 61, + 148, + 254, + 137, + 45, + 129, + 255, + 89, + 26, + 116, + 254, + 126, + 38, + 114, + 0, + 251, + 50, + 242, + 254, + 121, + 134, + 128, + 255, + 204, + 249, + 167, + 254, + 165, + 235, + 215, + 0, + 202, + 177, + 243, + 0, + 133, + 141, + 62, + 0, + 240, + 130, + 190, + 1, + 110, + 175, + 255, + 0, + 0, + 20, + 146, + 1, + 37, + 210, + 121, + 255, + 7, + 39, + 130, + 0, + 142, + 250, + 84, + 255, + 141, + 200, + 207, + 0, + 9, + 95, + 104, + 255, + 11, + 244, + 174, + 0, + 134, + 232, + 126, + 0, + 167, + 1, + 123, + 254, + 16, + 193, + 149, + 255, + 232, + 233, + 239, + 1, + 213, + 70, + 112, + 255, + 252, + 116, + 160, + 254, + 242, + 222, + 220, + 255, + 205, + 85, + 227, + 0, + 7, + 185, + 58, + 0, + 118, + 247, + 63, + 1, + 116, + 77, + 177, + 255, + 62, + 245, + 200, + 254, + 63, + 18, + 37, + 255, + 107, + 53, + 232, + 254, + 50, + 221, + 211, + 0, + 162, + 219, + 7, + 254, + 2, + 94, + 43, + 0, + 182, + 62, + 182, + 254, + 160, + 78, + 200, + 255, + 135, + 140, + 170, + 0, + 235, + 184, + 228, + 0, + 175, + 53, + 138, + 254, + 80, + 58, + 77, + 255, + 152, + 201, + 2, + 1, + 63, + 196, + 34, + 0, + 5, + 30, + 184, + 0, + 171, + 176, + 154, + 0, + 121, + 59, + 206, + 0, + 38, + 99, + 39, + 0, + 172, + 80, + 77, + 254, + 0, + 134, + 151, + 0, + 186, + 33, + 241, + 254, + 94, + 253, + 223, + 255, + 44, + 114, + 252, + 0, + 108, + 126, + 57, + 255, + 201, + 40, + 13, + 255, + 39, + 229, + 27, + 255, + 39, + 239, + 23, + 1, + 151, + 121, + 51, + 255, + 153, + 150, + 248, + 0, + 10, + 234, + 174, + 255, + 118, + 246, + 4, + 254, + 200, + 245, + 38, + 0, + 69, + 161, + 242, + 1, + 16, + 178, + 150, + 0, + 113, + 56, + 130, + 0, + 171, + 31, + 105, + 0, + 26, + 88, + 108, + 255, + 49, + 42, + 106, + 0, + 251, + 169, + 66, + 0, + 69, + 93, + 149, + 0, + 20, + 57, + 254, + 0, + 164, + 25, + 111, + 0, + 90, + 188, + 90, + 255, + 204, + 4, + 197, + 0, + 40, + 213, + 50, + 1, + 212, + 96, + 132, + 255, + 88, + 138, + 180, + 254, + 228, + 146, + 124, + 255, + 184, + 246, + 247, + 0, + 65, + 117, + 86, + 255, + 253, + 102, + 210, + 254, + 254, + 121, + 36, + 0, + 137, + 115, + 3, + 255, + 60, + 24, + 216, + 0, + 134, + 18, + 29, + 0, + 59, + 226, + 97, + 0, + 176, + 142, + 71, + 0, + 7, + 209, + 161, + 0, + 189, + 84, + 51, + 254, + 155, + 250, + 72, + 0, + 213, + 84, + 235, + 255, + 45, + 222, + 224, + 0, + 238, + 148, + 143, + 255, + 170, + 42, + 53, + 255, + 78, + 167, + 117, + 0, + 186, + 0, + 40, + 255, + 125, + 177, + 103, + 255, + 69, + 225, + 66, + 0, + 227, + 7, + 88, + 1, + 75, + 172, + 6, + 0, + 169, + 45, + 227, + 1, + 16, + 36, + 70, + 255, + 50, + 2, + 9, + 255, + 139, + 193, + 22, + 0, + 143, + 183, + 231, + 254, + 218, + 69, + 50, + 0, + 236, + 56, + 161, + 1, + 213, + 131, + 42, + 0, + 138, + 145, + 44, + 254, + 136, + 229, + 40, + 255, + 49, + 63, + 35, + 255, + 61, + 145, + 245, + 255, + 101, + 192, + 2, + 254, + 232, + 167, + 113, + 0, + 152, + 104, + 38, + 1, + 121, + 185, + 218, + 0, + 121, + 139, + 211, + 254, + 119, + 240, + 35, + 0, + 65, + 189, + 217, + 254, + 187, + 179, + 162, + 255, + 160, + 187, + 230, + 0, + 62, + 248, + 14, + 255, + 60, + 78, + 97, + 0, + 255, + 247, + 163, + 255, + 225, + 59, + 91, + 255, + 107, + 71, + 58, + 255, + 241, + 47, + 33, + 1, + 50, + 117, + 236, + 0, + 219, + 177, + 63, + 254, + 244, + 90, + 179, + 0, + 35, + 194, + 215, + 255, + 189, + 67, + 50, + 255, + 23, + 135, + 129, + 0, + 104, + 189, + 37, + 255, + 185, + 57, + 194, + 0, + 35, + 62, + 231, + 255, + 220, + 248, + 108, + 0, + 12, + 231, + 178, + 0, + 143, + 80, + 91, + 1, + 131, + 93, + 101, + 255, + 144, + 39, + 2, + 1, + 255, + 250, + 178, + 0, + 5, + 17, + 236, + 254, + 139, + 32, + 46, + 0, + 204, + 188, + 38, + 254, + 245, + 115, + 52, + 255, + 191, + 113, + 73, + 254, + 191, + 108, + 69, + 255, + 22, + 69, + 245, + 1, + 23, + 203, + 178, + 0, + 170, + 99, + 170, + 0, + 65, + 248, + 111, + 0, + 37, + 108, + 153, + 255, + 64, + 37, + 69, + 0, + 0, + 88, + 62, + 254, + 89, + 148, + 144, + 255, + 191, + 68, + 224, + 1, + 241, + 39, + 53, + 0, + 41, + 203, + 237, + 255, + 145, + 126, + 194, + 255, + 221, + 42, + 253, + 255, + 25, + 99, + 151, + 0, + 97, + 253, + 223, + 1, + 74, + 115, + 49, + 255, + 6, + 175, + 72, + 255, + 59, + 176, + 203, + 0, + 124, + 183, + 249, + 1, + 228, + 228, + 99, + 0, + 129, + 12, + 207, + 254, + 168, + 192, + 195, + 255, + 204, + 176, + 16, + 254, + 152, + 234, + 171, + 0, + 77, + 37, + 85, + 255, + 33, + 120, + 135, + 255, + 142, + 194, + 227, + 1, + 31, + 214, + 58, + 0, + 213, + 187, + 125, + 255, + 232, + 46, + 60, + 255, + 190, + 116, + 42, + 254, + 151, + 178, + 19, + 255, + 51, + 62, + 237, + 254, + 204, + 236, + 193, + 0, + 194, + 232, + 60, + 0, + 172, + 34, + 157, + 255, + 189, + 16, + 184, + 254, + 103, + 3, + 95, + 255, + 141, + 233, + 36, + 254, + 41, + 25, + 11, + 255, + 21, + 195, + 166, + 0, + 118, + 245, + 45, + 0, + 67, + 213, + 149, + 255, + 159, + 12, + 18, + 255, + 187, + 164, + 227, + 1, + 160, + 25, + 5, + 0, + 12, + 78, + 195, + 1, + 43, + 197, + 225, + 0, + 48, + 142, + 41, + 254, + 196, + 155, + 60, + 255, + 223, + 199, + 18, + 1, + 145, + 136, + 156, + 0, + 252, + 117, + 169, + 254, + 145, + 226, + 238, + 0, + 239, + 23, + 107, + 0, + 109, + 181, + 188, + 255, + 230, + 112, + 49, + 254, + 73, + 170, + 237, + 255, + 231, + 183, + 227, + 255, + 80, + 220, + 20, + 0, + 194, + 107, + 127, + 1, + 127, + 205, + 101, + 0, + 46, + 52, + 197, + 1, + 210, + 171, + 36, + 255, + 88, + 3, + 90, + 255, + 56, + 151, + 141, + 0, + 96, + 187, + 255, + 255, + 42, + 78, + 200, + 0, + 254, + 70, + 70, + 1, + 244, + 125, + 168, + 0, + 204, + 68, + 138, + 1, + 124, + 215, + 70, + 0, + 102, + 66, + 200, + 254, + 17, + 52, + 228, + 0, + 117, + 220, + 143, + 254, + 203, + 248, + 123, + 0, + 56, + 18, + 174, + 255, + 186, + 151, + 164, + 255, + 51, + 232, + 208, + 1, + 160, + 228, + 43, + 255, + 249, + 29, + 25, + 1, + 68, + 190, + 63, + 0, + 8, + 201, + 188, + 243, + 103, + 230, + 9, + 106, + 59, + 167, + 202, + 132, + 133, + 174, + 103, + 187, + 43, + 248, + 148, + 254, + 114, + 243, + 110, + 60, + 241, + 54, + 29, + 95, + 58, + 245, + 79, + 165, + 209, + 130, + 230, + 173, + 127, + 82, + 14, + 81, + 31, + 108, + 62, + 43, + 140, + 104, + 5, + 155, + 107, + 189, + 65, + 251, + 171, + 217, + 131, + 31, + 121, + 33, + 126, + 19, + 25, + 205, + 224, + 91, + 34, + 174, + 40, + 215, + 152, + 47, + 138, + 66, + 205, + 101, + 239, + 35, + 145, + 68, + 55, + 113, + 47, + 59, + 77, + 236, + 207, + 251, + 192, + 181, + 188, + 219, + 137, + 129, + 165, + 219, + 181, + 233, + 56, + 181, + 72, + 243, + 91, + 194, + 86, + 57, + 25, + 208, + 5, + 182, + 241, + 17, + 241, + 89, + 155, + 79, + 25, + 175, + 164, + 130, + 63, + 146, + 24, + 129, + 109, + 218, + 213, + 94, + 28, + 171, + 66, + 2, + 3, + 163, + 152, + 170, + 7, + 216, + 190, + 111, + 112, + 69, + 1, + 91, + 131, + 18, + 140, + 178, + 228, + 78, + 190, + 133, + 49, + 36, + 226, + 180, + 255, + 213, + 195, + 125, + 12, + 85, + 111, + 137, + 123, + 242, + 116, + 93, + 190, + 114, + 177, + 150, + 22, + 59, + 254, + 177, + 222, + 128, + 53, + 18, + 199, + 37, + 167, + 6, + 220, + 155, + 148, + 38, + 105, + 207, + 116, + 241, + 155, + 193, + 210, + 74, + 241, + 158, + 193, + 105, + 155, + 228, + 227, + 37, + 79, + 56, + 134, + 71, + 190, + 239, + 181, + 213, + 140, + 139, + 198, + 157, + 193, + 15, + 101, + 156, + 172, + 119, + 204, + 161, + 12, + 36, + 117, + 2, + 43, + 89, + 111, + 44, + 233, + 45, + 131, + 228, + 166, + 110, + 170, + 132, + 116, + 74, + 212, + 251, + 65, + 189, + 220, + 169, + 176, + 92, + 181, + 83, + 17, + 131, + 218, + 136, + 249, + 118, + 171, + 223, + 102, + 238, + 82, + 81, + 62, + 152, + 16, + 50, + 180, + 45, + 109, + 198, + 49, + 168, + 63, + 33, + 251, + 152, + 200, + 39, + 3, + 176, + 228, + 14, + 239, + 190, + 199, + 127, + 89, + 191, + 194, + 143, + 168, + 61, + 243, + 11, + 224, + 198, + 37, + 167, + 10, + 147, + 71, + 145, + 167, + 213, + 111, + 130, + 3, + 224, + 81, + 99, + 202, + 6, + 112, + 110, + 14, + 10, + 103, + 41, + 41, + 20, + 252, + 47, + 210, + 70, + 133, + 10, + 183, + 39, + 38, + 201, + 38, + 92, + 56, + 33, + 27, + 46, + 237, + 42, + 196, + 90, + 252, + 109, + 44, + 77, + 223, + 179, + 149, + 157, + 19, + 13, + 56, + 83, + 222, + 99, + 175, + 139, + 84, + 115, + 10, + 101, + 168, + 178, + 119, + 60, + 187, + 10, + 106, + 118, + 230, + 174, + 237, + 71, + 46, + 201, + 194, + 129, + 59, + 53, + 130, + 20, + 133, + 44, + 114, + 146, + 100, + 3, + 241, + 76, + 161, + 232, + 191, + 162, + 1, + 48, + 66, + 188, + 75, + 102, + 26, + 168, + 145, + 151, + 248, + 208, + 112, + 139, + 75, + 194, + 48, + 190, + 84, + 6, + 163, + 81, + 108, + 199, + 24, + 82, + 239, + 214, + 25, + 232, + 146, + 209, + 16, + 169, + 101, + 85, + 36, + 6, + 153, + 214, + 42, + 32, + 113, + 87, + 133, + 53, + 14, + 244, + 184, + 209, + 187, + 50, + 112, + 160, + 106, + 16, + 200, + 208, + 210, + 184, + 22, + 193, + 164, + 25, + 83, + 171, + 65, + 81, + 8, + 108, + 55, + 30, + 153, + 235, + 142, + 223, + 76, + 119, + 72, + 39, + 168, + 72, + 155, + 225, + 181, + 188, + 176, + 52, + 99, + 90, + 201, + 197, + 179, + 12, + 28, + 57, + 203, + 138, + 65, + 227, + 74, + 170, + 216, + 78, + 115, + 227, + 99, + 119, + 79, + 202, + 156, + 91, + 163, + 184, + 178, + 214, + 243, + 111, + 46, + 104, + 252, + 178, + 239, + 93, + 238, + 130, + 143, + 116, + 96, + 47, + 23, + 67, + 111, + 99, + 165, + 120, + 114, + 171, + 240, + 161, + 20, + 120, + 200, + 132, + 236, + 57, + 100, + 26, + 8, + 2, + 199, + 140, + 40, + 30, + 99, + 35, + 250, + 255, + 190, + 144, + 233, + 189, + 130, + 222, + 235, + 108, + 80, + 164, + 21, + 121, + 198, + 178, + 247, + 163, + 249, + 190, + 43, + 83, + 114, + 227, + 242, + 120, + 113, + 198, + 156, + 97, + 38, + 234, + 206, + 62, + 39, + 202, + 7, + 194, + 192, + 33, + 199, + 184, + 134, + 209, + 30, + 235, + 224, + 205, + 214, + 125, + 218, + 234, + 120, + 209, + 110, + 238, + 127, + 79, + 125, + 245, + 186, + 111, + 23, + 114, + 170, + 103, + 240, + 6, + 166, + 152, + 200, + 162, + 197, + 125, + 99, + 10, + 174, + 13, + 249, + 190, + 4, + 152, + 63, + 17, + 27, + 71, + 28, + 19, + 53, + 11, + 113, + 27, + 132, + 125, + 4, + 35, + 245, + 119, + 219, + 40, + 147, + 36, + 199, + 64, + 123, + 171, + 202, + 50, + 188, + 190, + 201, + 21, + 10, + 190, + 158, + 60, + 76, + 13, + 16, + 156, + 196, + 103, + 29, + 67, + 182, + 66, + 62, + 203, + 190, + 212, + 197, + 76, + 42, + 126, + 101, + 252, + 156, + 41, + 127, + 89, + 236, + 250, + 214, + 58, + 171, + 111, + 203, + 95, + 23, + 88, + 71, + 74, + 140, + 25, + 68, + 108, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + 'i8', + ALLOC_NONE, + Runtime.GLOBAL_BASE + 30720 +); + +var tempDoublePtr = Runtime.alignMemory(allocate(12, 'i8', ALLOC_STATIC), 8); + +assert(tempDoublePtr % 8 == 0); + +function copyTempFloat(ptr) { + // functions, because inlining this code increases code size too much + + HEAP8[tempDoublePtr] = HEAP8[ptr]; + + HEAP8[tempDoublePtr + 1] = HEAP8[ptr + 1]; + + HEAP8[tempDoublePtr + 2] = HEAP8[ptr + 2]; + + HEAP8[tempDoublePtr + 3] = HEAP8[ptr + 3]; +} + +function copyTempDouble(ptr) { + HEAP8[tempDoublePtr] = HEAP8[ptr]; + + HEAP8[tempDoublePtr + 1] = HEAP8[ptr + 1]; + + HEAP8[tempDoublePtr + 2] = HEAP8[ptr + 2]; + + HEAP8[tempDoublePtr + 3] = HEAP8[ptr + 3]; + + HEAP8[tempDoublePtr + 4] = HEAP8[ptr + 4]; + + HEAP8[tempDoublePtr + 5] = HEAP8[ptr + 5]; + + HEAP8[tempDoublePtr + 6] = HEAP8[ptr + 6]; + + HEAP8[tempDoublePtr + 7] = HEAP8[ptr + 7]; +} + +Module['_bitshift64Ashr'] = _bitshift64Ashr; + +Module['_i64Subtract'] = _i64Subtract; + +Module['_i64Add'] = _i64Add; + +Module['_memset'] = _memset; + +Module['_bitshift64Lshr'] = _bitshift64Lshr; + +Module['_bitshift64Shl'] = _bitshift64Shl; + +function _abort() { + Module['abort'](); +} + +Module['_strlen'] = _strlen; + +function _emscripten_memcpy_big(dest, src, num) { + HEAPU8.set(HEAPU8.subarray(src, src + num), dest); + return dest; +} +Module['_memcpy'] = _memcpy; + +var ___errno_state = 0; +function ___setErrNo(value) { + // For convenient setting and returning of errno. + HEAP32[___errno_state >> 2] = value; + return value; +} + +var ERRNO_CODES = { + EPERM: 1, + ENOENT: 2, + ESRCH: 3, + EINTR: 4, + EIO: 5, + ENXIO: 6, + E2BIG: 7, + ENOEXEC: 8, + EBADF: 9, + ECHILD: 10, + EAGAIN: 11, + EWOULDBLOCK: 11, + ENOMEM: 12, + EACCES: 13, + EFAULT: 14, + ENOTBLK: 15, + EBUSY: 16, + EEXIST: 17, + EXDEV: 18, + ENODEV: 19, + ENOTDIR: 20, + EISDIR: 21, + EINVAL: 22, + ENFILE: 23, + EMFILE: 24, + ENOTTY: 25, + ETXTBSY: 26, + EFBIG: 27, + ENOSPC: 28, + ESPIPE: 29, + EROFS: 30, + EMLINK: 31, + EPIPE: 32, + EDOM: 33, + ERANGE: 34, + ENOMSG: 42, + EIDRM: 43, + ECHRNG: 44, + EL2NSYNC: 45, + EL3HLT: 46, + EL3RST: 47, + ELNRNG: 48, + EUNATCH: 49, + ENOCSI: 50, + EL2HLT: 51, + EDEADLK: 35, + ENOLCK: 37, + EBADE: 52, + EBADR: 53, + EXFULL: 54, + ENOANO: 55, + EBADRQC: 56, + EBADSLT: 57, + EDEADLOCK: 35, + EBFONT: 59, + ENOSTR: 60, + ENODATA: 61, + ETIME: 62, + ENOSR: 63, + ENONET: 64, + ENOPKG: 65, + EREMOTE: 66, + ENOLINK: 67, + EADV: 68, + ESRMNT: 69, + ECOMM: 70, + EPROTO: 71, + EMULTIHOP: 72, + EDOTDOT: 73, + EBADMSG: 74, + ENOTUNIQ: 76, + EBADFD: 77, + EREMCHG: 78, + ELIBACC: 79, + ELIBBAD: 80, + ELIBSCN: 81, + ELIBMAX: 82, + ELIBEXEC: 83, + ENOSYS: 38, + ENOTEMPTY: 39, + ENAMETOOLONG: 36, + ELOOP: 40, + EOPNOTSUPP: 95, + EPFNOSUPPORT: 96, + ECONNRESET: 104, + ENOBUFS: 105, + EAFNOSUPPORT: 97, + EPROTOTYPE: 91, + ENOTSOCK: 88, + ENOPROTOOPT: 92, + ESHUTDOWN: 108, + ECONNREFUSED: 111, + EADDRINUSE: 98, + ECONNABORTED: 103, + ENETUNREACH: 101, + ENETDOWN: 100, + ETIMEDOUT: 110, + EHOSTDOWN: 112, + EHOSTUNREACH: 113, + EINPROGRESS: 115, + EALREADY: 114, + EDESTADDRREQ: 89, + EMSGSIZE: 90, + EPROTONOSUPPORT: 93, + ESOCKTNOSUPPORT: 94, + EADDRNOTAVAIL: 99, + ENETRESET: 102, + EISCONN: 106, + ENOTCONN: 107, + ETOOMANYREFS: 109, + EUSERS: 87, + EDQUOT: 122, + ESTALE: 116, + ENOTSUP: 95, + ENOMEDIUM: 123, + EILSEQ: 84, + EOVERFLOW: 75, + ECANCELED: 125, + ENOTRECOVERABLE: 131, + EOWNERDEAD: 130, + ESTRPIPE: 86, +}; +function _sysconf(name) { + // long sysconf(int name); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/sysconf.html + switch (name) { + case 30: + return PAGE_SIZE; + case 132: + case 133: + case 12: + case 137: + case 138: + case 15: + case 235: + case 16: + case 17: + case 18: + case 19: + case 20: + case 149: + case 13: + case 10: + case 236: + case 153: + case 9: + case 21: + case 22: + case 159: + case 154: + case 14: + case 77: + case 78: + case 139: + case 80: + case 81: + case 79: + case 82: + case 68: + case 67: + case 164: + case 11: + case 29: + case 47: + case 48: + case 95: + case 52: + case 51: + case 46: + return 200809; + case 27: + case 246: + case 127: + case 128: + case 23: + case 24: + case 160: + case 161: + case 181: + case 182: + case 242: + case 183: + case 184: + case 243: + case 244: + case 245: + case 165: + case 178: + case 179: + case 49: + case 50: + case 168: + case 169: + case 175: + case 170: + case 171: + case 172: + case 97: + case 76: + case 32: + case 173: + case 35: + return -1; + case 176: + case 177: + case 7: + case 155: + case 8: + case 157: + case 125: + case 126: + case 92: + case 93: + case 129: + case 130: + case 131: + case 94: + case 91: + return 1; + case 74: + case 60: + case 69: + case 70: + case 4: + return 1024; + case 31: + case 42: + case 72: + return 32; + case 87: + case 26: + case 33: + return 2147483647; + case 34: + case 1: + return 47839; + case 38: + case 36: + return 99; + case 43: + case 37: + return 2048; + case 0: + return 2097152; + case 3: + return 65536; + case 28: + return 32768; + case 44: + return 32767; + case 75: + return 16384; + case 39: + return 1000; + case 89: + return 700; + case 71: + return 256; + case 40: + return 255; + case 2: + return 100; + case 180: + return 64; + case 25: + return 20; + case 5: + return 16; + case 6: + return 6; + case 73: + return 4; + case 84: { + if (typeof navigator === 'object') + return navigator['hardwareConcurrency'] || 1; + return 1; + } + } + ___setErrNo(ERRNO_CODES.EINVAL); + return -1; +} + +function _sbrk(bytes) { + // Implement a Linux-like 'memory area' for our 'process'. + // Changes the size of the memory area by |bytes|; returns the + // address of the previous top ('break') of the memory area + // We control the "dynamic" memory - DYNAMIC_BASE to DYNAMICTOP + var self = _sbrk; + if (!self.called) { + DYNAMICTOP = alignMemoryPage(DYNAMICTOP); // make sure we start out aligned + self.called = true; + assert(Runtime.dynamicAlloc); + self.alloc = Runtime.dynamicAlloc; + Runtime.dynamicAlloc = function() { + abort('cannot dynamically allocate, sbrk now has control'); + }; + } + var ret = DYNAMICTOP; + if (bytes != 0) self.alloc(bytes); + return ret; // Previous break location. +} + +Module['_memmove'] = _memmove; + +function ___errno_location() { + return ___errno_state; +} + +var ERRNO_MESSAGES = { + 0: 'Success', + 1: 'Not super-user', + 2: 'No such file or directory', + 3: 'No such process', + 4: 'Interrupted system call', + 5: 'I/O error', + 6: 'No such device or address', + 7: 'Arg list too long', + 8: 'Exec format error', + 9: 'Bad file number', + 10: 'No children', + 11: 'No more processes', + 12: 'Not enough core', + 13: 'Permission denied', + 14: 'Bad address', + 15: 'Block device required', + 16: 'Mount device busy', + 17: 'File exists', + 18: 'Cross-device link', + 19: 'No such device', + 20: 'Not a directory', + 21: 'Is a directory', + 22: 'Invalid argument', + 23: 'Too many open files in system', + 24: 'Too many open files', + 25: 'Not a typewriter', + 26: 'Text file busy', + 27: 'File too large', + 28: 'No space left on device', + 29: 'Illegal seek', + 30: 'Read only file system', + 31: 'Too many links', + 32: 'Broken pipe', + 33: 'Math arg out of domain of func', + 34: 'Math result not representable', + 35: 'File locking deadlock error', + 36: 'File or path name too long', + 37: 'No record locks available', + 38: 'Function not implemented', + 39: 'Directory not empty', + 40: 'Too many symbolic links', + 42: 'No message of desired type', + 43: 'Identifier removed', + 44: 'Channel number out of range', + 45: 'Level 2 not synchronized', + 46: 'Level 3 halted', + 47: 'Level 3 reset', + 48: 'Link number out of range', + 49: 'Protocol driver not attached', + 50: 'No CSI structure available', + 51: 'Level 2 halted', + 52: 'Invalid exchange', + 53: 'Invalid request descriptor', + 54: 'Exchange full', + 55: 'No anode', + 56: 'Invalid request code', + 57: 'Invalid slot', + 59: 'Bad font file fmt', + 60: 'Device not a stream', + 61: 'No data (for no delay io)', + 62: 'Timer expired', + 63: 'Out of streams resources', + 64: 'Machine is not on the network', + 65: 'Package not installed', + 66: 'The object is remote', + 67: 'The link has been severed', + 68: 'Advertise error', + 69: 'Srmount error', + 70: 'Communication error on send', + 71: 'Protocol error', + 72: 'Multihop attempted', + 73: 'Cross mount point (not really error)', + 74: 'Trying to read unreadable message', + 75: 'Value too large for defined data type', + 76: 'Given log. name not unique', + 77: 'f.d. invalid for this operation', + 78: 'Remote address changed', + 79: 'Can access a needed shared lib', + 80: 'Accessing a corrupted shared lib', + 81: '.lib section in a.out corrupted', + 82: 'Attempting to link in too many libs', + 83: 'Attempting to exec a shared library', + 84: 'Illegal byte sequence', + 86: 'Streams pipe error', + 87: 'Too many users', + 88: 'Socket operation on non-socket', + 89: 'Destination address required', + 90: 'Message too long', + 91: 'Protocol wrong type for socket', + 92: 'Protocol not available', + 93: 'Unknown protocol', + 94: 'Socket type not supported', + 95: 'Not supported', + 96: 'Protocol family not supported', + 97: 'Address family not supported by protocol family', + 98: 'Address already in use', + 99: 'Address not available', + 100: 'Network interface is not configured', + 101: 'Network is unreachable', + 102: 'Connection reset by network', + 103: 'Connection aborted', + 104: 'Connection reset by peer', + 105: 'No buffer space available', + 106: 'Socket is already connected', + 107: 'Socket is not connected', + 108: "Can't send after socket shutdown", + 109: 'Too many references', + 110: 'Connection timed out', + 111: 'Connection refused', + 112: 'Host is down', + 113: 'Host is unreachable', + 114: 'Socket already connected', + 115: 'Connection already in progress', + 116: 'Stale file handle', + 122: 'Quota exceeded', + 123: 'No medium (in tape drive)', + 125: 'Operation canceled', + 130: 'Previous owner died', + 131: 'State not recoverable', +}; + +var TTY = { + ttys: [], + init: function() { + // https://github.com/kripken/emscripten/pull/1555 + // if (ENVIRONMENT_IS_NODE) { + // // currently, FS.init does not distinguish if process.stdin is a file or TTY + // // device, it always assumes it's a TTY device. because of this, we're forcing + // // process.stdin to UTF8 encoding to at least make stdin reading compatible + // // with text files until FS.init can be refactored. + // process['stdin']['setEncoding']('utf8'); + // } + }, + shutdown: function() { + // https://github.com/kripken/emscripten/pull/1555 + // if (ENVIRONMENT_IS_NODE) { + // // inolen: any idea as to why node -e 'process.stdin.read()' wouldn't exit immediately (with process.stdin being a tty)? + // // isaacs: because now it's reading from the stream, you've expressed interest in it, so that read() kicks off a _read() which creates a ReadReq operation + // // inolen: I thought read() in that case was a synchronous operation that just grabbed some amount of buffered data if it exists? + // // isaacs: it is. but it also triggers a _read() call, which calls readStart() on the handle + // // isaacs: do process.stdin.pause() and i'd think it'd probably close the pending call + // process['stdin']['pause'](); + // } + }, + register: function(dev, ops) { + TTY.ttys[dev] = { input: [], output: [], ops: ops }; + FS.registerDevice(dev, TTY.stream_ops); + }, + stream_ops: { + open: function(stream) { + var tty = TTY.ttys[stream.node.rdev]; + if (!tty) { + throw new FS.ErrnoError(ERRNO_CODES.ENODEV); + } + stream.tty = tty; + stream.seekable = false; + }, + close: function(stream) { + // flush any pending line data + stream.tty.ops.flush(stream.tty); + }, + flush: function(stream) { + stream.tty.ops.flush(stream.tty); + }, + read: function(stream, buffer, offset, length, pos /* ignored */) { + if (!stream.tty || !stream.tty.ops.get_char) { + throw new FS.ErrnoError(ERRNO_CODES.ENXIO); + } + var bytesRead = 0; + for (var i = 0; i < length; i++) { + var result; + try { + result = stream.tty.ops.get_char(stream.tty); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + if (result === undefined && bytesRead === 0) { + throw new FS.ErrnoError(ERRNO_CODES.EAGAIN); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[offset + i] = result; + } + if (bytesRead) { + stream.node.timestamp = Date.now(); + } + return bytesRead; + }, + write: function(stream, buffer, offset, length, pos) { + if (!stream.tty || !stream.tty.ops.put_char) { + throw new FS.ErrnoError(ERRNO_CODES.ENXIO); + } + for (var i = 0; i < length; i++) { + try { + stream.tty.ops.put_char(stream.tty, buffer[offset + i]); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + } + if (length) { + stream.node.timestamp = Date.now(); + } + return i; + }, + }, + default_tty_ops: { + get_char: function(tty) { + if (!tty.input.length) { + var result = null; + if (ENVIRONMENT_IS_NODE) { + result = process['stdin']['read'](); + if (!result) { + if ( + process['stdin']['_readableState'] && + process['stdin']['_readableState']['ended'] + ) { + return null; // EOF + } + return undefined; // no data available + } + } else if ( + typeof window != 'undefined' && + typeof window.prompt == 'function' + ) { + // Browser. + result = window.prompt('Input: '); // returns null on cancel + if (result !== null) { + result += '\n'; + } + } else if (typeof readline == 'function') { + // Command line. + result = readline(); + if (result !== null) { + result += '\n'; + } + } + if (!result) { + return null; + } + tty.input = intArrayFromString(result, true); + } + return tty.input.shift(); + }, + flush: function(tty) { + if (tty.output && tty.output.length > 0) { + Module['print'](tty.output.join('')); + tty.output = []; + } + }, + put_char: function(tty, val) { + if (val === null || val === 10) { + Module['print'](tty.output.join('')); + tty.output = []; + } else { + tty.output.push(TTY.utf8.processCChar(val)); + } + }, + }, + default_tty1_ops: { + put_char: function(tty, val) { + if (val === null || val === 10) { + Module['printErr'](tty.output.join('')); + tty.output = []; + } else { + tty.output.push(TTY.utf8.processCChar(val)); + } + }, + flush: function(tty) { + if (tty.output && tty.output.length > 0) { + Module['printErr'](tty.output.join('')); + tty.output = []; + } + }, + }, +}; + +var MEMFS = { + ops_table: null, + mount: function(mount) { + return MEMFS.createNode(null, '/', 16384 | 511 /* 0777 */, 0); + }, + createNode: function(parent, name, mode, dev) { + if (FS.isBlkdev(mode) || FS.isFIFO(mode)) { + // no supported + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + if (!MEMFS.ops_table) { + MEMFS.ops_table = { + dir: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + lookup: MEMFS.node_ops.lookup, + mknod: MEMFS.node_ops.mknod, + rename: MEMFS.node_ops.rename, + unlink: MEMFS.node_ops.unlink, + rmdir: MEMFS.node_ops.rmdir, + readdir: MEMFS.node_ops.readdir, + symlink: MEMFS.node_ops.symlink, + }, + stream: { + llseek: MEMFS.stream_ops.llseek, + }, + }, + file: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + }, + stream: { + llseek: MEMFS.stream_ops.llseek, + read: MEMFS.stream_ops.read, + write: MEMFS.stream_ops.write, + allocate: MEMFS.stream_ops.allocate, + mmap: MEMFS.stream_ops.mmap, + }, + }, + link: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + readlink: MEMFS.node_ops.readlink, + }, + stream: {}, + }, + chrdev: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + }, + stream: FS.chrdev_stream_ops, + }, + }; + } + var node = FS.createNode(parent, name, mode, dev); + if (FS.isDir(node.mode)) { + node.node_ops = MEMFS.ops_table.dir.node; + node.stream_ops = MEMFS.ops_table.dir.stream; + node.contents = {}; + } else if (FS.isFile(node.mode)) { + node.node_ops = MEMFS.ops_table.file.node; + node.stream_ops = MEMFS.ops_table.file.stream; + node.usedBytes = 0; // The actual number of bytes used in the typed array, as opposed to contents.buffer.byteLength which gives the whole capacity. + // When the byte data of the file is populated, this will point to either a typed array, or a normal JS array. Typed arrays are preferred + // for performance, and used by default. However, typed arrays are not resizable like normal JS arrays are, so there is a small disk size + // penalty involved for appending file writes that continuously grow a file similar to std::vector capacity vs used -scheme. + node.contents = null; + } else if (FS.isLink(node.mode)) { + node.node_ops = MEMFS.ops_table.link.node; + node.stream_ops = MEMFS.ops_table.link.stream; + } else if (FS.isChrdev(node.mode)) { + node.node_ops = MEMFS.ops_table.chrdev.node; + node.stream_ops = MEMFS.ops_table.chrdev.stream; + } + node.timestamp = Date.now(); + // add the new node to the parent + if (parent) { + parent.contents[name] = node; + } + return node; + }, + getFileDataAsRegularArray: function(node) { + if (node.contents && node.contents.subarray) { + var arr = []; + for (var i = 0; i < node.usedBytes; ++i) arr.push(node.contents[i]); + return arr; // Returns a copy of the original data. + } + return node.contents; // No-op, the file contents are already in a JS array. Return as-is. + }, + getFileDataAsTypedArray: function(node) { + if (!node.contents) return new Uint8Array(); + if (node.contents.subarray) + return node.contents.subarray(0, node.usedBytes); // Make sure to not return excess unused bytes. + return new Uint8Array(node.contents); + }, + expandFileStorage: function(node, newCapacity) { + // If we are asked to expand the size of a file that already exists, revert to using a standard JS array to store the file + // instead of a typed array. This makes resizing the array more flexible because we can just .push() elements at the back to + // increase the size. + if ( + node.contents && + node.contents.subarray && + newCapacity > node.contents.length + ) { + node.contents = MEMFS.getFileDataAsRegularArray(node); + node.usedBytes = node.contents.length; // We might be writing to a lazy-loaded file which had overridden this property, so force-reset it. + } + + if (!node.contents || node.contents.subarray) { + // Keep using a typed array if creating a new storage, or if old one was a typed array as well. + var prevCapacity = node.contents ? node.contents.buffer.byteLength : 0; + if (prevCapacity >= newCapacity) return; // No need to expand, the storage was already large enough. + // Don't expand strictly to the given requested limit if it's only a very small increase, but instead geometrically grow capacity. + // For small filesizes (<1MB), perform size*2 geometric increase, but for large sizes, do a much more conservative size*1.125 increase to + // avoid overshooting the allocation cap by a very large margin. + var CAPACITY_DOUBLING_MAX = 1024 * 1024; + newCapacity = Math.max( + newCapacity, + (prevCapacity * (prevCapacity < CAPACITY_DOUBLING_MAX ? 2.0 : 1.125)) | + 0 + ); + if (prevCapacity != 0) newCapacity = Math.max(newCapacity, 256); // At minimum allocate 256b for each file when expanding. + var oldContents = node.contents; + node.contents = new Uint8Array(newCapacity); // Allocate new storage. + if (node.usedBytes > 0) + node.contents.set(oldContents.subarray(0, node.usedBytes), 0); // Copy old data over to the new storage. + return; + } + // Not using a typed array to back the file storage. Use a standard JS array instead. + if (!node.contents && newCapacity > 0) node.contents = []; + while (node.contents.length < newCapacity) node.contents.push(0); + }, + resizeFileStorage: function(node, newSize) { + if (node.usedBytes == newSize) return; + if (newSize == 0) { + node.contents = null; // Fully decommit when requesting a resize to zero. + node.usedBytes = 0; + return; + } + + if (!node.contents || node.contents.subarray) { + // Resize a typed array if that is being used as the backing store. + var oldContents = node.contents; + node.contents = new Uint8Array(new ArrayBuffer(newSize)); // Allocate new storage. + if (oldContents) { + node.contents.set( + oldContents.subarray(0, Math.min(newSize, node.usedBytes)) + ); // Copy old data over to the new storage. + } + node.usedBytes = newSize; + return; + } + // Backing with a JS array. + if (!node.contents) node.contents = []; + if (node.contents.length > newSize) node.contents.length = newSize; + else while (node.contents.length < newSize) node.contents.push(0); + node.usedBytes = newSize; + }, + node_ops: { + getattr: function(node) { + var attr = {}; + // device numbers reuse inode numbers. + attr.dev = FS.isChrdev(node.mode) ? node.id : 1; + attr.ino = node.id; + attr.mode = node.mode; + attr.nlink = 1; + attr.uid = 0; + attr.gid = 0; + attr.rdev = node.rdev; + if (FS.isDir(node.mode)) { + attr.size = 4096; + } else if (FS.isFile(node.mode)) { + attr.size = node.usedBytes; + } else if (FS.isLink(node.mode)) { + attr.size = node.link.length; + } else { + attr.size = 0; + } + attr.atime = new Date(node.timestamp); + attr.mtime = new Date(node.timestamp); + attr.ctime = new Date(node.timestamp); + // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize), + // but this is not required by the standard. + attr.blksize = 4096; + attr.blocks = Math.ceil(attr.size / attr.blksize); + return attr; + }, + setattr: function(node, attr) { + if (attr.mode !== undefined) { + node.mode = attr.mode; + } + if (attr.timestamp !== undefined) { + node.timestamp = attr.timestamp; + } + if (attr.size !== undefined) { + MEMFS.resizeFileStorage(node, attr.size); + } + }, + lookup: function(parent, name) { + throw FS.genericErrors[ERRNO_CODES.ENOENT]; + }, + mknod: function(parent, name, mode, dev) { + return MEMFS.createNode(parent, name, mode, dev); + }, + rename: function(old_node, new_dir, new_name) { + // if we're overwriting a directory at new_name, make sure it's empty. + if (FS.isDir(old_node.mode)) { + var new_node; + try { + new_node = FS.lookupNode(new_dir, new_name); + } catch (e) {} + if (new_node) { + for (var i in new_node.contents) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTEMPTY); + } + } + } + // do the internal rewiring + delete old_node.parent.contents[old_node.name]; + old_node.name = new_name; + new_dir.contents[new_name] = old_node; + old_node.parent = new_dir; + }, + unlink: function(parent, name) { + delete parent.contents[name]; + }, + rmdir: function(parent, name) { + var node = FS.lookupNode(parent, name); + for (var i in node.contents) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTEMPTY); + } + delete parent.contents[name]; + }, + readdir: function(node) { + var entries = ['.', '..']; + for (var key in node.contents) { + if (!node.contents.hasOwnProperty(key)) { + continue; + } + entries.push(key); + } + return entries; + }, + symlink: function(parent, newname, oldpath) { + var node = MEMFS.createNode(parent, newname, 511 /* 0777 */ | 40960, 0); + node.link = oldpath; + return node; + }, + readlink: function(node) { + if (!FS.isLink(node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + return node.link; + }, + }, + stream_ops: { + read: function(stream, buffer, offset, length, position) { + var contents = stream.node.contents; + if (position >= stream.node.usedBytes) return 0; + var size = Math.min(stream.node.usedBytes - position, length); + assert(size >= 0); + if (size > 8 && contents.subarray) { + // non-trivial, and typed array + buffer.set(contents.subarray(position, position + size), offset); + } else { + for (var i = 0; i < size; i++) + buffer[offset + i] = contents[position + i]; + } + return size; + }, + write: function(stream, buffer, offset, length, position, canOwn) { + if (!length) return 0; + var node = stream.node; + node.timestamp = Date.now(); + + if (buffer.subarray && (!node.contents || node.contents.subarray)) { + // This write is from a typed array to a typed array? + if (canOwn) { + // Can we just reuse the buffer we are given? + node.contents = buffer.subarray(offset, offset + length); + node.usedBytes = length; + return length; + } else if (node.usedBytes === 0 && position === 0) { + // If this is a simple first write to an empty file, do a fast set since we don't need to care about old data. + node.contents = new Uint8Array( + buffer.subarray(offset, offset + length) + ); + node.usedBytes = length; + return length; + } else if (position + length <= node.usedBytes) { + // Writing to an already allocated and used subrange of the file? + node.contents.set(buffer.subarray(offset, offset + length), position); + return length; + } + } + // Appending to an existing file and we need to reallocate, or source data did not come as a typed array. + MEMFS.expandFileStorage(node, position + length); + if (node.contents.subarray && buffer.subarray) + node.contents.set(buffer.subarray(offset, offset + length), position); + // Use typed array write if available. + else + for (var i = 0; i < length; i++) { + node.contents[position + i] = buffer[offset + i]; // Or fall back to manual write if not. + } + node.usedBytes = Math.max(node.usedBytes, position + length); + return length; + }, + llseek: function(stream, offset, whence) { + var position = offset; + if (whence === 1) { + // SEEK_CUR. + position += stream.position; + } else if (whence === 2) { + // SEEK_END. + if (FS.isFile(stream.node.mode)) { + position += stream.node.usedBytes; + } + } + if (position < 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + return position; + }, + allocate: function(stream, offset, length) { + MEMFS.expandFileStorage(stream.node, offset + length); + stream.node.usedBytes = Math.max(stream.node.usedBytes, offset + length); + }, + mmap: function(stream, buffer, offset, length, position, prot, flags) { + if (!FS.isFile(stream.node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.ENODEV); + } + var ptr; + var allocated; + var contents = stream.node.contents; + // Only make a new copy when MAP_PRIVATE is specified. + if ( + !(flags & 2) && + (contents.buffer === buffer || contents.buffer === buffer.buffer) + ) { + // We can't emulate MAP_SHARED when the file is not backed by the buffer + // we're mapping to (e.g. the HEAP buffer). + allocated = false; + ptr = contents.byteOffset; + } else { + // Try to avoid unnecessary slices. + if (position > 0 || position + length < stream.node.usedBytes) { + if (contents.subarray) { + contents = contents.subarray(position, position + length); + } else { + contents = Array.prototype.slice.call( + contents, + position, + position + length + ); + } + } + allocated = true; + ptr = _malloc(length); + if (!ptr) { + throw new FS.ErrnoError(ERRNO_CODES.ENOMEM); + } + buffer.set(contents, ptr); + } + return { ptr: ptr, allocated: allocated }; + }, + }, +}; + +var IDBFS = { + dbs: {}, + indexedDB: function() { + if (typeof indexedDB !== 'undefined') return indexedDB; + var ret = null; + if (typeof window === 'object') + ret = + window.indexedDB || + window.mozIndexedDB || + window.webkitIndexedDB || + window.msIndexedDB; + assert(ret, 'IDBFS used, but indexedDB not supported'); + return ret; + }, + DB_VERSION: 21, + DB_STORE_NAME: 'FILE_DATA', + mount: function(mount) { + // reuse all of the core MEMFS functionality + return MEMFS.mount.apply(null, arguments); + }, + syncfs: function(mount, populate, callback) { + IDBFS.getLocalSet(mount, function(err, local) { + if (err) return callback(err); + + IDBFS.getRemoteSet(mount, function(err, remote) { + if (err) return callback(err); + + var src = populate ? remote : local; + var dst = populate ? local : remote; + + IDBFS.reconcile(src, dst, callback); + }); + }); + }, + getDB: function(name, callback) { + // check the cache first + var db = IDBFS.dbs[name]; + if (db) { + return callback(null, db); + } + + var req; + try { + req = IDBFS.indexedDB().open(name, IDBFS.DB_VERSION); + } catch (e) { + return callback(e); + } + req.onupgradeneeded = function(e) { + var db = e.target.result; + var transaction = e.target.transaction; + + var fileStore; + + if (db.objectStoreNames.contains(IDBFS.DB_STORE_NAME)) { + fileStore = transaction.objectStore(IDBFS.DB_STORE_NAME); + } else { + fileStore = db.createObjectStore(IDBFS.DB_STORE_NAME); + } + + fileStore.createIndex('timestamp', 'timestamp', { unique: false }); + }; + req.onsuccess = function() { + db = req.result; + + // add to the cache + IDBFS.dbs[name] = db; + callback(null, db); + }; + req.onerror = function() { + callback(this.error); + }; + }, + getLocalSet: function(mount, callback) { + var entries = {}; + + function isRealDir(p) { + return p !== '.' && p !== '..'; + } + function toAbsolute(root) { + return function(p) { + return PATH.join2(root, p); + }; + } + + var check = FS.readdir(mount.mountpoint) + .filter(isRealDir) + .map(toAbsolute(mount.mountpoint)); + + while (check.length) { + var path = check.pop(); + var stat; + + try { + stat = FS.stat(path); + } catch (e) { + return callback(e); + } + + if (FS.isDir(stat.mode)) { + check.push.apply( + check, + FS.readdir(path) + .filter(isRealDir) + .map(toAbsolute(path)) + ); + } + + entries[path] = { timestamp: stat.mtime }; + } + + return callback(null, { type: 'local', entries: entries }); + }, + getRemoteSet: function(mount, callback) { + var entries = {}; + + IDBFS.getDB(mount.mountpoint, function(err, db) { + if (err) return callback(err); + + var transaction = db.transaction([IDBFS.DB_STORE_NAME], 'readonly'); + transaction.onerror = function() { + callback(this.error); + }; + + var store = transaction.objectStore(IDBFS.DB_STORE_NAME); + var index = store.index('timestamp'); + + index.openKeyCursor().onsuccess = function(event) { + var cursor = event.target.result; + + if (!cursor) { + return callback(null, { type: 'remote', db: db, entries: entries }); + } + + entries[cursor.primaryKey] = { timestamp: cursor.key }; + + cursor.continue(); + }; + }); + }, + loadLocalEntry: function(path, callback) { + var stat, node; + + try { + var lookup = FS.lookupPath(path); + node = lookup.node; + stat = FS.stat(path); + } catch (e) { + return callback(e); + } + + if (FS.isDir(stat.mode)) { + return callback(null, { timestamp: stat.mtime, mode: stat.mode }); + } else if (FS.isFile(stat.mode)) { + // Performance consideration: storing a normal JavaScript array to a IndexedDB is much slower than storing a typed array. + // Therefore always convert the file contents to a typed array first before writing the data to IndexedDB. + node.contents = MEMFS.getFileDataAsTypedArray(node); + return callback(null, { + timestamp: stat.mtime, + mode: stat.mode, + contents: node.contents, + }); + } else { + return callback(new Error('node type not supported')); + } + }, + storeLocalEntry: function(path, entry, callback) { + try { + if (FS.isDir(entry.mode)) { + FS.mkdir(path, entry.mode); + } else if (FS.isFile(entry.mode)) { + FS.writeFile(path, entry.contents, { + encoding: 'binary', + canOwn: true, + }); + } else { + return callback(new Error('node type not supported')); + } + + FS.chmod(path, entry.mode); + FS.utime(path, entry.timestamp, entry.timestamp); + } catch (e) { + return callback(e); + } + + callback(null); + }, + removeLocalEntry: function(path, callback) { + try { + var lookup = FS.lookupPath(path); + var stat = FS.stat(path); + + if (FS.isDir(stat.mode)) { + FS.rmdir(path); + } else if (FS.isFile(stat.mode)) { + FS.unlink(path); + } + } catch (e) { + return callback(e); + } + + callback(null); + }, + loadRemoteEntry: function(store, path, callback) { + var req = store.get(path); + req.onsuccess = function(event) { + callback(null, event.target.result); + }; + req.onerror = function() { + callback(this.error); + }; + }, + storeRemoteEntry: function(store, path, entry, callback) { + var req = store.put(entry, path); + req.onsuccess = function() { + callback(null); + }; + req.onerror = function() { + callback(this.error); + }; + }, + removeRemoteEntry: function(store, path, callback) { + var req = store.delete(path); + req.onsuccess = function() { + callback(null); + }; + req.onerror = function() { + callback(this.error); + }; + }, + reconcile: function(src, dst, callback) { + var total = 0; + + var create = []; + Object.keys(src.entries).forEach(function(key) { + var e = src.entries[key]; + var e2 = dst.entries[key]; + if (!e2 || e.timestamp > e2.timestamp) { + create.push(key); + total++; + } + }); + + var remove = []; + Object.keys(dst.entries).forEach(function(key) { + var e = dst.entries[key]; + var e2 = src.entries[key]; + if (!e2) { + remove.push(key); + total++; + } + }); + + if (!total) { + return callback(null); + } + + var errored = false; + var completed = 0; + var db = src.type === 'remote' ? src.db : dst.db; + var transaction = db.transaction([IDBFS.DB_STORE_NAME], 'readwrite'); + var store = transaction.objectStore(IDBFS.DB_STORE_NAME); + + function done(err) { + if (err) { + if (!done.errored) { + done.errored = true; + return callback(err); + } + return; + } + if (++completed >= total) { + return callback(null); + } + } + + transaction.onerror = function() { + done(this.error); + }; + + // sort paths in ascending order so directory entries are created + // before the files inside them + create.sort().forEach(function(path) { + if (dst.type === 'local') { + IDBFS.loadRemoteEntry(store, path, function(err, entry) { + if (err) return done(err); + IDBFS.storeLocalEntry(path, entry, done); + }); + } else { + IDBFS.loadLocalEntry(path, function(err, entry) { + if (err) return done(err); + IDBFS.storeRemoteEntry(store, path, entry, done); + }); + } + }); + + // sort paths in descending order so files are deleted before their + // parent directories + remove + .sort() + .reverse() + .forEach(function(path) { + if (dst.type === 'local') { + IDBFS.removeLocalEntry(path, done); + } else { + IDBFS.removeRemoteEntry(store, path, done); + } + }); + }, +}; + +var NODEFS = { + isWindows: false, + staticInit: function() { + NODEFS.isWindows = !!process.platform.match(/^win/); + }, + mount: function(mount) { + assert(ENVIRONMENT_IS_NODE); + return NODEFS.createNode(null, '/', NODEFS.getMode(mount.opts.root), 0); + }, + createNode: function(parent, name, mode, dev) { + if (!FS.isDir(mode) && !FS.isFile(mode) && !FS.isLink(mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var node = FS.createNode(parent, name, mode); + node.node_ops = NODEFS.node_ops; + node.stream_ops = NODEFS.stream_ops; + return node; + }, + getMode: function(path) { + var stat; + try { + stat = fs.lstatSync(path); + if (NODEFS.isWindows) { + // On Windows, directories return permission bits 'rw-rw-rw-', even though they have 'rwxrwxrwx', so + // propagate write bits to execute bits. + stat.mode = stat.mode | ((stat.mode & 146) >> 1); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + return stat.mode; + }, + realPath: function(node) { + var parts = []; + while (node.parent !== node) { + parts.push(node.name); + node = node.parent; + } + parts.push(node.mount.opts.root); + parts.reverse(); + return PATH.join.apply(null, parts); + }, + flagsToPermissionStringMap: { + 0: 'r', + 1: 'r+', + 2: 'r+', + 64: 'r', + 65: 'r+', + 66: 'r+', + 129: 'rx+', + 193: 'rx+', + 514: 'w+', + 577: 'w', + 578: 'w+', + 705: 'wx', + 706: 'wx+', + 1024: 'a', + 1025: 'a', + 1026: 'a+', + 1089: 'a', + 1090: 'a+', + 1153: 'ax', + 1154: 'ax+', + 1217: 'ax', + 1218: 'ax+', + 4096: 'rs', + 4098: 'rs+', + }, + flagsToPermissionString: function(flags) { + if (flags in NODEFS.flagsToPermissionStringMap) { + return NODEFS.flagsToPermissionStringMap[flags]; + } else { + return flags; + } + }, + node_ops: { + getattr: function(node) { + var path = NODEFS.realPath(node); + var stat; + try { + stat = fs.lstatSync(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + // node.js v0.10.20 doesn't report blksize and blocks on Windows. Fake them with default blksize of 4096. + // See http://support.microsoft.com/kb/140365 + if (NODEFS.isWindows && !stat.blksize) { + stat.blksize = 4096; + } + if (NODEFS.isWindows && !stat.blocks) { + stat.blocks = ((stat.size + stat.blksize - 1) / stat.blksize) | 0; + } + return { + dev: stat.dev, + ino: stat.ino, + mode: stat.mode, + nlink: stat.nlink, + uid: stat.uid, + gid: stat.gid, + rdev: stat.rdev, + size: stat.size, + atime: stat.atime, + mtime: stat.mtime, + ctime: stat.ctime, + blksize: stat.blksize, + blocks: stat.blocks, + }; + }, + setattr: function(node, attr) { + var path = NODEFS.realPath(node); + try { + if (attr.mode !== undefined) { + fs.chmodSync(path, attr.mode); + // update the common node structure mode as well + node.mode = attr.mode; + } + if (attr.timestamp !== undefined) { + var date = new Date(attr.timestamp); + fs.utimesSync(path, date, date); + } + if (attr.size !== undefined) { + fs.truncateSync(path, attr.size); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + lookup: function(parent, name) { + var path = PATH.join2(NODEFS.realPath(parent), name); + var mode = NODEFS.getMode(path); + return NODEFS.createNode(parent, name, mode); + }, + mknod: function(parent, name, mode, dev) { + var node = NODEFS.createNode(parent, name, mode, dev); + // create the backing node for this in the fs root as well + var path = NODEFS.realPath(node); + try { + if (FS.isDir(node.mode)) { + fs.mkdirSync(path, node.mode); + } else { + fs.writeFileSync(path, '', { mode: node.mode }); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + return node; + }, + rename: function(oldNode, newDir, newName) { + var oldPath = NODEFS.realPath(oldNode); + var newPath = PATH.join2(NODEFS.realPath(newDir), newName); + try { + fs.renameSync(oldPath, newPath); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + unlink: function(parent, name) { + var path = PATH.join2(NODEFS.realPath(parent), name); + try { + fs.unlinkSync(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + rmdir: function(parent, name) { + var path = PATH.join2(NODEFS.realPath(parent), name); + try { + fs.rmdirSync(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + readdir: function(node) { + var path = NODEFS.realPath(node); + try { + return fs.readdirSync(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + symlink: function(parent, newName, oldPath) { + var newPath = PATH.join2(NODEFS.realPath(parent), newName); + try { + fs.symlinkSync(oldPath, newPath); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + readlink: function(node) { + var path = NODEFS.realPath(node); + try { + return fs.readlinkSync(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + }, + stream_ops: { + open: function(stream) { + var path = NODEFS.realPath(stream.node); + try { + if (FS.isFile(stream.node.mode)) { + stream.nfd = fs.openSync( + path, + NODEFS.flagsToPermissionString(stream.flags) + ); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + close: function(stream) { + try { + if (FS.isFile(stream.node.mode) && stream.nfd) { + fs.closeSync(stream.nfd); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + read: function(stream, buffer, offset, length, position) { + if (length === 0) return 0; // node errors on 0 length reads + // FIXME this is terrible. + var nbuffer = new Buffer(length); + var res; + try { + res = fs.readSync(stream.nfd, nbuffer, 0, length, position); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + if (res > 0) { + for (var i = 0; i < res; i++) { + buffer[offset + i] = nbuffer[i]; + } + } + return res; + }, + write: function(stream, buffer, offset, length, position) { + // FIXME this is terrible. + var nbuffer = new Buffer(buffer.subarray(offset, offset + length)); + var res; + try { + res = fs.writeSync(stream.nfd, nbuffer, 0, length, position); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + return res; + }, + llseek: function(stream, offset, whence) { + var position = offset; + if (whence === 1) { + // SEEK_CUR. + position += stream.position; + } else if (whence === 2) { + // SEEK_END. + if (FS.isFile(stream.node.mode)) { + try { + var stat = fs.fstatSync(stream.nfd); + position += stat.size; + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + } + } + + if (position < 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + + return position; + }, + }, +}; + +var _stdin = allocate(1, 'i32*', ALLOC_STATIC); + +var _stdout = allocate(1, 'i32*', ALLOC_STATIC); + +var _stderr = allocate(1, 'i32*', ALLOC_STATIC); + +function _fflush(stream) { + // int fflush(FILE *stream); + // http://pubs.opengroup.org/onlinepubs/000095399/functions/fflush.html + /* + // Disabled, see https://github.com/kripken/emscripten/issues/2770 + stream = FS.getStreamFromPtr(stream); + if (stream.stream_ops.flush) { + stream.stream_ops.flush(stream); + } + */ +} +var FS = { + root: null, + mounts: [], + devices: [null], + streams: [], + nextInode: 1, + nameTable: null, + currentPath: '/', + initialized: false, + ignorePermissions: true, + trackingDelegate: {}, + tracking: { openFlags: { READ: 1, WRITE: 2 } }, + ErrnoError: null, + genericErrors: {}, + handleFSError: function(e) { + if (!(e instanceof FS.ErrnoError)) throw e + ' : ' + stackTrace(); + return ___setErrNo(e.errno); + }, + lookupPath: function(path, opts) { + path = PATH.resolve(FS.cwd(), path); + opts = opts || {}; + + if (!path) return { path: '', node: null }; + + var defaults = { + follow_mount: true, + recurse_count: 0, + }; + for (var key in defaults) { + if (opts[key] === undefined) { + opts[key] = defaults[key]; + } + } + + if (opts.recurse_count > 8) { + // max recursive lookup of 8 + throw new FS.ErrnoError(ERRNO_CODES.ELOOP); + } + + // split the path + var parts = PATH.normalizeArray( + path.split('/').filter(function(p) { + return !!p; + }), + false + ); + + // start at the root + var current = FS.root; + var current_path = '/'; + + for (var i = 0; i < parts.length; i++) { + var islast = i === parts.length - 1; + if (islast && opts.parent) { + // stop resolving + break; + } + + current = FS.lookupNode(current, parts[i]); + current_path = PATH.join2(current_path, parts[i]); + + // jump to the mount's root node if this is a mountpoint + if (FS.isMountpoint(current)) { + if (!islast || (islast && opts.follow_mount)) { + current = current.mounted.root; + } + } + + // by default, lookupPath will not follow a symlink if it is the final path component. + // setting opts.follow = true will override this behavior. + if (!islast || opts.follow) { + var count = 0; + while (FS.isLink(current.mode)) { + var link = FS.readlink(current_path); + current_path = PATH.resolve(PATH.dirname(current_path), link); + + var lookup = FS.lookupPath(current_path, { + recurse_count: opts.recurse_count, + }); + current = lookup.node; + + if (count++ > 40) { + // limit max consecutive symlinks to 40 (SYMLOOP_MAX). + throw new FS.ErrnoError(ERRNO_CODES.ELOOP); + } + } + } + } + + return { path: current_path, node: current }; + }, + getPath: function(node) { + var path; + while (true) { + if (FS.isRoot(node)) { + var mount = node.mount.mountpoint; + if (!path) return mount; + return mount[mount.length - 1] !== '/' + ? mount + '/' + path + : mount + path; + } + path = path ? node.name + '/' + path : node.name; + node = node.parent; + } + }, + hashName: function(parentid, name) { + var hash = 0; + + for (var i = 0; i < name.length; i++) { + hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0; + } + return ((parentid + hash) >>> 0) % FS.nameTable.length; + }, + hashAddNode: function(node) { + var hash = FS.hashName(node.parent.id, node.name); + node.name_next = FS.nameTable[hash]; + FS.nameTable[hash] = node; + }, + hashRemoveNode: function(node) { + var hash = FS.hashName(node.parent.id, node.name); + if (FS.nameTable[hash] === node) { + FS.nameTable[hash] = node.name_next; + } else { + var current = FS.nameTable[hash]; + while (current) { + if (current.name_next === node) { + current.name_next = node.name_next; + break; + } + current = current.name_next; + } + } + }, + lookupNode: function(parent, name) { + var err = FS.mayLookup(parent); + if (err) { + throw new FS.ErrnoError(err, parent); + } + var hash = FS.hashName(parent.id, name); + for (var node = FS.nameTable[hash]; node; node = node.name_next) { + var nodeName = node.name; + if (node.parent.id === parent.id && nodeName === name) { + return node; + } + } + // if we failed to find it in the cache, call into the VFS + return FS.lookup(parent, name); + }, + createNode: function(parent, name, mode, rdev) { + if (!FS.FSNode) { + FS.FSNode = function(parent, name, mode, rdev) { + if (!parent) { + parent = this; // root node sets parent to itself + } + this.parent = parent; + this.mount = parent.mount; + this.mounted = null; + this.id = FS.nextInode++; + this.name = name; + this.mode = mode; + this.node_ops = {}; + this.stream_ops = {}; + this.rdev = rdev; + }; + + FS.FSNode.prototype = {}; + + // compatibility + var readMode = 292 | 73; + var writeMode = 146; + + // NOTE we must use Object.defineProperties instead of individual calls to + // Object.defineProperty in order to make closure compiler happy + Object.defineProperties(FS.FSNode.prototype, { + read: { + get: function() { + return (this.mode & readMode) === readMode; + }, + set: function(val) { + val ? (this.mode |= readMode) : (this.mode &= ~readMode); + }, + }, + write: { + get: function() { + return (this.mode & writeMode) === writeMode; + }, + set: function(val) { + val ? (this.mode |= writeMode) : (this.mode &= ~writeMode); + }, + }, + isFolder: { + get: function() { + return FS.isDir(this.mode); + }, + }, + isDevice: { + get: function() { + return FS.isChrdev(this.mode); + }, + }, + }); + } + + var node = new FS.FSNode(parent, name, mode, rdev); + + FS.hashAddNode(node); + + return node; + }, + destroyNode: function(node) { + FS.hashRemoveNode(node); + }, + isRoot: function(node) { + return node === node.parent; + }, + isMountpoint: function(node) { + return !!node.mounted; + }, + isFile: function(mode) { + return (mode & 61440) === 32768; + }, + isDir: function(mode) { + return (mode & 61440) === 16384; + }, + isLink: function(mode) { + return (mode & 61440) === 40960; + }, + isChrdev: function(mode) { + return (mode & 61440) === 8192; + }, + isBlkdev: function(mode) { + return (mode & 61440) === 24576; + }, + isFIFO: function(mode) { + return (mode & 61440) === 4096; + }, + isSocket: function(mode) { + return (mode & 49152) === 49152; + }, + flagModes: { + r: 0, + rs: 1052672, + 'r+': 2, + w: 577, + wx: 705, + xw: 705, + 'w+': 578, + 'wx+': 706, + 'xw+': 706, + a: 1089, + ax: 1217, + xa: 1217, + 'a+': 1090, + 'ax+': 1218, + 'xa+': 1218, + }, + modeStringToFlags: function(str) { + var flags = FS.flagModes[str]; + if (typeof flags === 'undefined') { + throw new Error('Unknown file open mode: ' + str); + } + return flags; + }, + flagsToPermissionString: function(flag) { + var accmode = flag & 2097155; + var perms = ['r', 'w', 'rw'][accmode]; + if (flag & 512) { + perms += 'w'; + } + return perms; + }, + nodePermissions: function(node, perms) { + if (FS.ignorePermissions) { + return 0; + } + // return 0 if any user, group or owner bits are set. + if (perms.indexOf('r') !== -1 && !(node.mode & 292)) { + return ERRNO_CODES.EACCES; + } else if (perms.indexOf('w') !== -1 && !(node.mode & 146)) { + return ERRNO_CODES.EACCES; + } else if (perms.indexOf('x') !== -1 && !(node.mode & 73)) { + return ERRNO_CODES.EACCES; + } + return 0; + }, + mayLookup: function(dir) { + var err = FS.nodePermissions(dir, 'x'); + if (err) return err; + if (!dir.node_ops.lookup) return ERRNO_CODES.EACCES; + return 0; + }, + mayCreate: function(dir, name) { + try { + var node = FS.lookupNode(dir, name); + return ERRNO_CODES.EEXIST; + } catch (e) {} + return FS.nodePermissions(dir, 'wx'); + }, + mayDelete: function(dir, name, isdir) { + var node; + try { + node = FS.lookupNode(dir, name); + } catch (e) { + return e.errno; + } + var err = FS.nodePermissions(dir, 'wx'); + if (err) { + return err; + } + if (isdir) { + if (!FS.isDir(node.mode)) { + return ERRNO_CODES.ENOTDIR; + } + if (FS.isRoot(node) || FS.getPath(node) === FS.cwd()) { + return ERRNO_CODES.EBUSY; + } + } else { + if (FS.isDir(node.mode)) { + return ERRNO_CODES.EISDIR; + } + } + return 0; + }, + mayOpen: function(node, flags) { + if (!node) { + return ERRNO_CODES.ENOENT; + } + if (FS.isLink(node.mode)) { + return ERRNO_CODES.ELOOP; + } else if (FS.isDir(node.mode)) { + if ( + (flags & 2097155) !== 0 || // opening for write + flags & 512 + ) { + return ERRNO_CODES.EISDIR; + } + } + return FS.nodePermissions(node, FS.flagsToPermissionString(flags)); + }, + MAX_OPEN_FDS: 4096, + nextfd: function(fd_start, fd_end) { + fd_start = fd_start || 0; + fd_end = fd_end || FS.MAX_OPEN_FDS; + for (var fd = fd_start; fd <= fd_end; fd++) { + if (!FS.streams[fd]) { + return fd; + } + } + throw new FS.ErrnoError(ERRNO_CODES.EMFILE); + }, + getStream: function(fd) { + return FS.streams[fd]; + }, + createStream: function(stream, fd_start, fd_end) { + if (!FS.FSStream) { + FS.FSStream = function() {}; + FS.FSStream.prototype = {}; + // compatibility + Object.defineProperties(FS.FSStream.prototype, { + object: { + get: function() { + return this.node; + }, + set: function(val) { + this.node = val; + }, + }, + isRead: { + get: function() { + return (this.flags & 2097155) !== 1; + }, + }, + isWrite: { + get: function() { + return (this.flags & 2097155) !== 0; + }, + }, + isAppend: { + get: function() { + return this.flags & 1024; + }, + }, + }); + } + // clone it, so we can return an instance of FSStream + var newStream = new FS.FSStream(); + for (var p in stream) { + newStream[p] = stream[p]; + } + stream = newStream; + var fd = FS.nextfd(fd_start, fd_end); + stream.fd = fd; + FS.streams[fd] = stream; + return stream; + }, + closeStream: function(fd) { + FS.streams[fd] = null; + }, + getStreamFromPtr: function(ptr) { + return FS.streams[ptr - 1]; + }, + getPtrForStream: function(stream) { + return stream ? stream.fd + 1 : 0; + }, + chrdev_stream_ops: { + open: function(stream) { + var device = FS.getDevice(stream.node.rdev); + // override node's stream ops with the device's + stream.stream_ops = device.stream_ops; + // forward the open call + if (stream.stream_ops.open) { + stream.stream_ops.open(stream); + } + }, + llseek: function() { + throw new FS.ErrnoError(ERRNO_CODES.ESPIPE); + }, + }, + major: function(dev) { + return dev >> 8; + }, + minor: function(dev) { + return dev & 0xff; + }, + makedev: function(ma, mi) { + return (ma << 8) | mi; + }, + registerDevice: function(dev, ops) { + FS.devices[dev] = { stream_ops: ops }; + }, + getDevice: function(dev) { + return FS.devices[dev]; + }, + getMounts: function(mount) { + var mounts = []; + var check = [mount]; + + while (check.length) { + var m = check.pop(); + + mounts.push(m); + + check.push.apply(check, m.mounts); + } + + return mounts; + }, + syncfs: function(populate, callback) { + if (typeof populate === 'function') { + callback = populate; + populate = false; + } + + var mounts = FS.getMounts(FS.root.mount); + var completed = 0; + + function done(err) { + if (err) { + if (!done.errored) { + done.errored = true; + return callback(err); + } + return; + } + if (++completed >= mounts.length) { + callback(null); + } + } + + // sync all mounts + mounts.forEach(function(mount) { + if (!mount.type.syncfs) { + return done(null); + } + mount.type.syncfs(mount, populate, done); + }); + }, + mount: function(type, opts, mountpoint) { + var root = mountpoint === '/'; + var pseudo = !mountpoint; + var node; + + if (root && FS.root) { + throw new FS.ErrnoError(ERRNO_CODES.EBUSY); + } else if (!root && !pseudo) { + var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); + + mountpoint = lookup.path; // use the absolute path + node = lookup.node; + + if (FS.isMountpoint(node)) { + throw new FS.ErrnoError(ERRNO_CODES.EBUSY); + } + + if (!FS.isDir(node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTDIR); + } + } + + var mount = { + type: type, + opts: opts, + mountpoint: mountpoint, + mounts: [], + }; + + // create a root node for the fs + var mountRoot = type.mount(mount); + mountRoot.mount = mount; + mount.root = mountRoot; + + if (root) { + FS.root = mountRoot; + } else if (node) { + // set as a mountpoint + node.mounted = mount; + + // add the new mount to the current mount's children + if (node.mount) { + node.mount.mounts.push(mount); + } + } + + return mountRoot; + }, + unmount: function(mountpoint) { + var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); + + if (!FS.isMountpoint(lookup.node)) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + + // destroy the nodes for this mount, and all its child mounts + var node = lookup.node; + var mount = node.mounted; + var mounts = FS.getMounts(mount); + + Object.keys(FS.nameTable).forEach(function(hash) { + var current = FS.nameTable[hash]; + + while (current) { + var next = current.name_next; + + if (mounts.indexOf(current.mount) !== -1) { + FS.destroyNode(current); + } + + current = next; + } + }); + + // no longer a mountpoint + node.mounted = null; + + // remove this mount from the child mounts + var idx = node.mount.mounts.indexOf(mount); + assert(idx !== -1); + node.mount.mounts.splice(idx, 1); + }, + lookup: function(parent, name) { + return parent.node_ops.lookup(parent, name); + }, + mknod: function(path, mode, dev) { + var lookup = FS.lookupPath(path, { parent: true }); + var parent = lookup.node; + var name = PATH.basename(path); + if (!name || name === '.' || name === '..') { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var err = FS.mayCreate(parent, name); + if (err) { + throw new FS.ErrnoError(err); + } + if (!parent.node_ops.mknod) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + return parent.node_ops.mknod(parent, name, mode, dev); + }, + create: function(path, mode) { + mode = mode !== undefined ? mode : 438 /* 0666 */; + mode &= 4095; + mode |= 32768; + return FS.mknod(path, mode, 0); + }, + mkdir: function(path, mode) { + mode = mode !== undefined ? mode : 511 /* 0777 */; + mode &= 511 | 512; + mode |= 16384; + return FS.mknod(path, mode, 0); + }, + mkdev: function(path, mode, dev) { + if (typeof dev === 'undefined') { + dev = mode; + mode = 438 /* 0666 */; + } + mode |= 8192; + return FS.mknod(path, mode, dev); + }, + symlink: function(oldpath, newpath) { + if (!PATH.resolve(oldpath)) { + throw new FS.ErrnoError(ERRNO_CODES.ENOENT); + } + var lookup = FS.lookupPath(newpath, { parent: true }); + var parent = lookup.node; + if (!parent) { + throw new FS.ErrnoError(ERRNO_CODES.ENOENT); + } + var newname = PATH.basename(newpath); + var err = FS.mayCreate(parent, newname); + if (err) { + throw new FS.ErrnoError(err); + } + if (!parent.node_ops.symlink) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + return parent.node_ops.symlink(parent, newname, oldpath); + }, + rename: function(old_path, new_path) { + var old_dirname = PATH.dirname(old_path); + var new_dirname = PATH.dirname(new_path); + var old_name = PATH.basename(old_path); + var new_name = PATH.basename(new_path); + // parents must exist + var lookup, old_dir, new_dir; + try { + lookup = FS.lookupPath(old_path, { parent: true }); + old_dir = lookup.node; + lookup = FS.lookupPath(new_path, { parent: true }); + new_dir = lookup.node; + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EBUSY); + } + if (!old_dir || !new_dir) throw new FS.ErrnoError(ERRNO_CODES.ENOENT); + // need to be part of the same mount + if (old_dir.mount !== new_dir.mount) { + throw new FS.ErrnoError(ERRNO_CODES.EXDEV); + } + // source must exist + var old_node = FS.lookupNode(old_dir, old_name); + // old path should not be an ancestor of the new path + var relative = PATH.relative(old_path, new_dirname); + if (relative.charAt(0) !== '.') { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + // new path should not be an ancestor of the old path + relative = PATH.relative(new_path, old_dirname); + if (relative.charAt(0) !== '.') { + throw new FS.ErrnoError(ERRNO_CODES.ENOTEMPTY); + } + // see if the new path already exists + var new_node; + try { + new_node = FS.lookupNode(new_dir, new_name); + } catch (e) { + // not fatal + } + // early out if nothing needs to change + if (old_node === new_node) { + return; + } + // we'll need to delete the old entry + var isdir = FS.isDir(old_node.mode); + var err = FS.mayDelete(old_dir, old_name, isdir); + if (err) { + throw new FS.ErrnoError(err); + } + // need delete permissions if we'll be overwriting. + // need create permissions if new doesn't already exist. + err = new_node + ? FS.mayDelete(new_dir, new_name, isdir) + : FS.mayCreate(new_dir, new_name); + if (err) { + throw new FS.ErrnoError(err); + } + if (!old_dir.node_ops.rename) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + if (FS.isMountpoint(old_node) || (new_node && FS.isMountpoint(new_node))) { + throw new FS.ErrnoError(ERRNO_CODES.EBUSY); + } + // if we are going to change the parent, check write permissions + if (new_dir !== old_dir) { + err = FS.nodePermissions(old_dir, 'w'); + if (err) { + throw new FS.ErrnoError(err); + } + } + try { + if (FS.trackingDelegate['willMovePath']) { + FS.trackingDelegate['willMovePath'](old_path, new_path); + } + } catch (e) { + console.log( + "FS.trackingDelegate['willMovePath']('" + + old_path + + "', '" + + new_path + + "') threw an exception: " + + e.message + ); + } + // remove the node from the lookup hash + FS.hashRemoveNode(old_node); + // do the underlying fs rename + try { + old_dir.node_ops.rename(old_node, new_dir, new_name); + } catch (e) { + throw e; + } finally { + // add the node back to the hash (in case node_ops.rename + // changed its name) + FS.hashAddNode(old_node); + } + try { + if (FS.trackingDelegate['onMovePath']) + FS.trackingDelegate['onMovePath'](old_path, new_path); + } catch (e) { + console.log( + "FS.trackingDelegate['onMovePath']('" + + old_path + + "', '" + + new_path + + "') threw an exception: " + + e.message + ); + } + }, + rmdir: function(path) { + var lookup = FS.lookupPath(path, { parent: true }); + var parent = lookup.node; + var name = PATH.basename(path); + var node = FS.lookupNode(parent, name); + var err = FS.mayDelete(parent, name, true); + if (err) { + throw new FS.ErrnoError(err); + } + if (!parent.node_ops.rmdir) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + if (FS.isMountpoint(node)) { + throw new FS.ErrnoError(ERRNO_CODES.EBUSY); + } + try { + if (FS.trackingDelegate['willDeletePath']) { + FS.trackingDelegate['willDeletePath'](path); + } + } catch (e) { + console.log( + "FS.trackingDelegate['willDeletePath']('" + + path + + "') threw an exception: " + + e.message + ); + } + parent.node_ops.rmdir(parent, name); + FS.destroyNode(node); + try { + if (FS.trackingDelegate['onDeletePath']) + FS.trackingDelegate['onDeletePath'](path); + } catch (e) { + console.log( + "FS.trackingDelegate['onDeletePath']('" + + path + + "') threw an exception: " + + e.message + ); + } + }, + readdir: function(path) { + var lookup = FS.lookupPath(path, { follow: true }); + var node = lookup.node; + if (!node.node_ops.readdir) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTDIR); + } + return node.node_ops.readdir(node); + }, + unlink: function(path) { + var lookup = FS.lookupPath(path, { parent: true }); + var parent = lookup.node; + var name = PATH.basename(path); + var node = FS.lookupNode(parent, name); + var err = FS.mayDelete(parent, name, false); + if (err) { + // POSIX says unlink should set EPERM, not EISDIR + if (err === ERRNO_CODES.EISDIR) err = ERRNO_CODES.EPERM; + throw new FS.ErrnoError(err); + } + if (!parent.node_ops.unlink) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + if (FS.isMountpoint(node)) { + throw new FS.ErrnoError(ERRNO_CODES.EBUSY); + } + try { + if (FS.trackingDelegate['willDeletePath']) { + FS.trackingDelegate['willDeletePath'](path); + } + } catch (e) { + console.log( + "FS.trackingDelegate['willDeletePath']('" + + path + + "') threw an exception: " + + e.message + ); + } + parent.node_ops.unlink(parent, name); + FS.destroyNode(node); + try { + if (FS.trackingDelegate['onDeletePath']) + FS.trackingDelegate['onDeletePath'](path); + } catch (e) { + console.log( + "FS.trackingDelegate['onDeletePath']('" + + path + + "') threw an exception: " + + e.message + ); + } + }, + readlink: function(path) { + var lookup = FS.lookupPath(path); + var link = lookup.node; + if (!link) { + throw new FS.ErrnoError(ERRNO_CODES.ENOENT); + } + if (!link.node_ops.readlink) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + return link.node_ops.readlink(link); + }, + stat: function(path, dontFollow) { + var lookup = FS.lookupPath(path, { follow: !dontFollow }); + var node = lookup.node; + if (!node) { + throw new FS.ErrnoError(ERRNO_CODES.ENOENT); + } + if (!node.node_ops.getattr) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + return node.node_ops.getattr(node); + }, + lstat: function(path) { + return FS.stat(path, true); + }, + chmod: function(path, mode, dontFollow) { + var node; + if (typeof path === 'string') { + var lookup = FS.lookupPath(path, { follow: !dontFollow }); + node = lookup.node; + } else { + node = path; + } + if (!node.node_ops.setattr) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + node.node_ops.setattr(node, { + mode: (mode & 4095) | (node.mode & ~4095), + timestamp: Date.now(), + }); + }, + lchmod: function(path, mode) { + FS.chmod(path, mode, true); + }, + fchmod: function(fd, mode) { + var stream = FS.getStream(fd); + if (!stream) { + throw new FS.ErrnoError(ERRNO_CODES.EBADF); + } + FS.chmod(stream.node, mode); + }, + chown: function(path, uid, gid, dontFollow) { + var node; + if (typeof path === 'string') { + var lookup = FS.lookupPath(path, { follow: !dontFollow }); + node = lookup.node; + } else { + node = path; + } + if (!node.node_ops.setattr) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + node.node_ops.setattr(node, { + timestamp: Date.now(), + // we ignore the uid / gid for now + }); + }, + lchown: function(path, uid, gid) { + FS.chown(path, uid, gid, true); + }, + fchown: function(fd, uid, gid) { + var stream = FS.getStream(fd); + if (!stream) { + throw new FS.ErrnoError(ERRNO_CODES.EBADF); + } + FS.chown(stream.node, uid, gid); + }, + truncate: function(path, len) { + if (len < 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var node; + if (typeof path === 'string') { + var lookup = FS.lookupPath(path, { follow: true }); + node = lookup.node; + } else { + node = path; + } + if (!node.node_ops.setattr) { + throw new FS.ErrnoError(ERRNO_CODES.EPERM); + } + if (FS.isDir(node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EISDIR); + } + if (!FS.isFile(node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var err = FS.nodePermissions(node, 'w'); + if (err) { + throw new FS.ErrnoError(err); + } + node.node_ops.setattr(node, { + size: len, + timestamp: Date.now(), + }); + }, + ftruncate: function(fd, len) { + var stream = FS.getStream(fd); + if (!stream) { + throw new FS.ErrnoError(ERRNO_CODES.EBADF); + } + if ((stream.flags & 2097155) === 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + FS.truncate(stream.node, len); + }, + utime: function(path, atime, mtime) { + var lookup = FS.lookupPath(path, { follow: true }); + var node = lookup.node; + node.node_ops.setattr(node, { + timestamp: Math.max(atime, mtime), + }); + }, + open: function(path, flags, mode, fd_start, fd_end) { + if (path === '') { + throw new FS.ErrnoError(ERRNO_CODES.ENOENT); + } + flags = typeof flags === 'string' ? FS.modeStringToFlags(flags) : flags; + mode = typeof mode === 'undefined' ? 438 /* 0666 */ : mode; + if (flags & 64) { + mode = (mode & 4095) | 32768; + } else { + mode = 0; + } + var node; + if (typeof path === 'object') { + node = path; + } else { + path = PATH.normalize(path); + try { + var lookup = FS.lookupPath(path, { + follow: !(flags & 131072), + }); + node = lookup.node; + } catch (e) { + // ignore + } + } + // perhaps we need to create the node + var created = false; + if (flags & 64) { + if (node) { + // if O_CREAT and O_EXCL are set, error out if the node already exists + if (flags & 128) { + throw new FS.ErrnoError(ERRNO_CODES.EEXIST); + } + } else { + // node doesn't exist, try to create it + node = FS.mknod(path, mode, 0); + created = true; + } + } + if (!node) { + throw new FS.ErrnoError(ERRNO_CODES.ENOENT); + } + // can't truncate a device + if (FS.isChrdev(node.mode)) { + flags &= ~512; + } + // check permissions, if this is not a file we just created now (it is ok to + // create and write to a file with read-only permissions; it is read-only + // for later use) + if (!created) { + var err = FS.mayOpen(node, flags); + if (err) { + throw new FS.ErrnoError(err); + } + } + // do truncation if necessary + if (flags & 512) { + FS.truncate(node, 0); + } + // we've already handled these, don't pass down to the underlying vfs + flags &= ~(128 | 512); + + // register the stream with the filesystem + var stream = FS.createStream( + { + node: node, + path: FS.getPath(node), // we want the absolute path to the node + flags: flags, + seekable: true, + position: 0, + stream_ops: node.stream_ops, + // used by the file family libc calls (fopen, fwrite, ferror, etc.) + ungotten: [], + error: false, + }, + fd_start, + fd_end + ); + // call the new stream's open function + if (stream.stream_ops.open) { + stream.stream_ops.open(stream); + } + if (Module['logReadFiles'] && !(flags & 1)) { + if (!FS.readFiles) FS.readFiles = {}; + if (!(path in FS.readFiles)) { + FS.readFiles[path] = 1; + Module['printErr']('read file: ' + path); + } + } + try { + if (FS.trackingDelegate['onOpenFile']) { + var trackingFlags = 0; + if ((flags & 2097155) !== 1) { + trackingFlags |= FS.tracking.openFlags.READ; + } + if ((flags & 2097155) !== 0) { + trackingFlags |= FS.tracking.openFlags.WRITE; + } + FS.trackingDelegate['onOpenFile'](path, trackingFlags); + } + } catch (e) { + console.log( + "FS.trackingDelegate['onOpenFile']('" + + path + + "', flags) threw an exception: " + + e.message + ); + } + return stream; + }, + close: function(stream) { + try { + if (stream.stream_ops.close) { + stream.stream_ops.close(stream); + } + } catch (e) { + throw e; + } finally { + FS.closeStream(stream.fd); + } + }, + llseek: function(stream, offset, whence) { + if (!stream.seekable || !stream.stream_ops.llseek) { + throw new FS.ErrnoError(ERRNO_CODES.ESPIPE); + } + stream.position = stream.stream_ops.llseek(stream, offset, whence); + stream.ungotten = []; + return stream.position; + }, + read: function(stream, buffer, offset, length, position) { + if (length < 0 || position < 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + if ((stream.flags & 2097155) === 1) { + throw new FS.ErrnoError(ERRNO_CODES.EBADF); + } + if (FS.isDir(stream.node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EISDIR); + } + if (!stream.stream_ops.read) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var seeking = true; + if (typeof position === 'undefined') { + position = stream.position; + seeking = false; + } else if (!stream.seekable) { + throw new FS.ErrnoError(ERRNO_CODES.ESPIPE); + } + var bytesRead = stream.stream_ops.read( + stream, + buffer, + offset, + length, + position + ); + if (!seeking) stream.position += bytesRead; + return bytesRead; + }, + write: function(stream, buffer, offset, length, position, canOwn) { + if (length < 0 || position < 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + if ((stream.flags & 2097155) === 0) { + throw new FS.ErrnoError(ERRNO_CODES.EBADF); + } + if (FS.isDir(stream.node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EISDIR); + } + if (!stream.stream_ops.write) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + if (stream.flags & 1024) { + // seek to the end before writing in append mode + FS.llseek(stream, 0, 2); + } + var seeking = true; + if (typeof position === 'undefined') { + position = stream.position; + seeking = false; + } else if (!stream.seekable) { + throw new FS.ErrnoError(ERRNO_CODES.ESPIPE); + } + var bytesWritten = stream.stream_ops.write( + stream, + buffer, + offset, + length, + position, + canOwn + ); + if (!seeking) stream.position += bytesWritten; + try { + if (stream.path && FS.trackingDelegate['onWriteToFile']) + FS.trackingDelegate['onWriteToFile'](stream.path); + } catch (e) { + console.log( + "FS.trackingDelegate['onWriteToFile']('" + + path + + "') threw an exception: " + + e.message + ); + } + return bytesWritten; + }, + allocate: function(stream, offset, length) { + if (offset < 0 || length <= 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + if ((stream.flags & 2097155) === 0) { + throw new FS.ErrnoError(ERRNO_CODES.EBADF); + } + if (!FS.isFile(stream.node.mode) && !FS.isDir(node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.ENODEV); + } + if (!stream.stream_ops.allocate) { + throw new FS.ErrnoError(ERRNO_CODES.EOPNOTSUPP); + } + stream.stream_ops.allocate(stream, offset, length); + }, + mmap: function(stream, buffer, offset, length, position, prot, flags) { + // TODO if PROT is PROT_WRITE, make sure we have write access + if ((stream.flags & 2097155) === 1) { + throw new FS.ErrnoError(ERRNO_CODES.EACCES); + } + if (!stream.stream_ops.mmap) { + throw new FS.ErrnoError(ERRNO_CODES.ENODEV); + } + return stream.stream_ops.mmap( + stream, + buffer, + offset, + length, + position, + prot, + flags + ); + }, + ioctl: function(stream, cmd, arg) { + if (!stream.stream_ops.ioctl) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTTY); + } + return stream.stream_ops.ioctl(stream, cmd, arg); + }, + readFile: function(path, opts) { + opts = opts || {}; + opts.flags = opts.flags || 'r'; + opts.encoding = opts.encoding || 'binary'; + if (opts.encoding !== 'utf8' && opts.encoding !== 'binary') { + throw new Error('Invalid encoding type "' + opts.encoding + '"'); + } + var ret; + var stream = FS.open(path, opts.flags); + var stat = FS.stat(path); + var length = stat.size; + var buf = new Uint8Array(length); + FS.read(stream, buf, 0, length, 0); + if (opts.encoding === 'utf8') { + ret = ''; + var utf8 = new Runtime.UTF8Processor(); + for (var i = 0; i < length; i++) { + ret += utf8.processCChar(buf[i]); + } + } else if (opts.encoding === 'binary') { + ret = buf; + } + FS.close(stream); + return ret; + }, + writeFile: function(path, data, opts) { + opts = opts || {}; + opts.flags = opts.flags || 'w'; + opts.encoding = opts.encoding || 'utf8'; + if (opts.encoding !== 'utf8' && opts.encoding !== 'binary') { + throw new Error('Invalid encoding type "' + opts.encoding + '"'); + } + var stream = FS.open(path, opts.flags, opts.mode); + if (opts.encoding === 'utf8') { + var utf8 = new Runtime.UTF8Processor(); + var buf = new Uint8Array(utf8.processJSString(data)); + FS.write(stream, buf, 0, buf.length, 0, opts.canOwn); + } else if (opts.encoding === 'binary') { + FS.write(stream, data, 0, data.length, 0, opts.canOwn); + } + FS.close(stream); + }, + cwd: function() { + return FS.currentPath; + }, + chdir: function(path) { + var lookup = FS.lookupPath(path, { follow: true }); + if (!FS.isDir(lookup.node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTDIR); + } + var err = FS.nodePermissions(lookup.node, 'x'); + if (err) { + throw new FS.ErrnoError(err); + } + FS.currentPath = lookup.path; + }, + createDefaultDirectories: function() { + FS.mkdir('/tmp'); + FS.mkdir('/home'); + FS.mkdir('/home/web_user'); + }, + createDefaultDevices: function() { + // create /dev + FS.mkdir('/dev'); + // setup /dev/null + FS.registerDevice(FS.makedev(1, 3), { + read: function() { + return 0; + }, + write: function() { + return 0; + }, + }); + FS.mkdev('/dev/null', FS.makedev(1, 3)); + // setup /dev/tty and /dev/tty1 + // stderr needs to print output using Module['printErr'] + // so we register a second tty just for it. + TTY.register(FS.makedev(5, 0), TTY.default_tty_ops); + TTY.register(FS.makedev(6, 0), TTY.default_tty1_ops); + FS.mkdev('/dev/tty', FS.makedev(5, 0)); + FS.mkdev('/dev/tty1', FS.makedev(6, 0)); + // setup /dev/[u]random + var random_device; + if (typeof crypto !== 'undefined') { + // for modern web browsers + var randomBuffer = new Uint8Array(1); + random_device = function() { + crypto.getRandomValues(randomBuffer); + return randomBuffer[0]; + }; + } else if (ENVIRONMENT_IS_NODE) { + // for nodejs + random_device = function() { + return require('crypto').randomBytes(1)[0]; + }; + } else { + // default for ES5 platforms + random_device = function() { + return (Math.random() * 256) | 0; + }; + } + FS.createDevice('/dev', 'random', random_device); + FS.createDevice('/dev', 'urandom', random_device); + // we're not going to emulate the actual shm device, + // just create the tmp dirs that reside in it commonly + FS.mkdir('/dev/shm'); + FS.mkdir('/dev/shm/tmp'); + }, + createStandardStreams: function() { + // TODO deprecate the old functionality of a single + // input / output callback and that utilizes FS.createDevice + // and instead require a unique set of stream ops + + // by default, we symlink the standard streams to the + // default tty devices. however, if the standard streams + // have been overwritten we create a unique device for + // them instead. + if (Module['stdin']) { + FS.createDevice('/dev', 'stdin', Module['stdin']); + } else { + FS.symlink('/dev/tty', '/dev/stdin'); + } + if (Module['stdout']) { + FS.createDevice('/dev', 'stdout', null, Module['stdout']); + } else { + FS.symlink('/dev/tty', '/dev/stdout'); + } + if (Module['stderr']) { + FS.createDevice('/dev', 'stderr', null, Module['stderr']); + } else { + FS.symlink('/dev/tty1', '/dev/stderr'); + } + + // open default streams for the stdin, stdout and stderr devices + var stdin = FS.open('/dev/stdin', 'r'); + HEAP32[_stdin >> 2] = FS.getPtrForStream(stdin); + assert(stdin.fd === 0, 'invalid handle for stdin (' + stdin.fd + ')'); + + var stdout = FS.open('/dev/stdout', 'w'); + HEAP32[_stdout >> 2] = FS.getPtrForStream(stdout); + assert(stdout.fd === 1, 'invalid handle for stdout (' + stdout.fd + ')'); + + var stderr = FS.open('/dev/stderr', 'w'); + HEAP32[_stderr >> 2] = FS.getPtrForStream(stderr); + assert(stderr.fd === 2, 'invalid handle for stderr (' + stderr.fd + ')'); + }, + ensureErrnoError: function() { + if (FS.ErrnoError) return; + FS.ErrnoError = function ErrnoError(errno, node) { + this.node = node; + this.setErrno = function(errno) { + this.errno = errno; + for (var key in ERRNO_CODES) { + if (ERRNO_CODES[key] === errno) { + this.code = key; + break; + } + } + }; + this.setErrno(errno); + this.message = ERRNO_MESSAGES[errno]; + }; + FS.ErrnoError.prototype = new Error(); + FS.ErrnoError.prototype.constructor = FS.ErrnoError; + // Some errors may happen quite a bit, to avoid overhead we reuse them (and suffer a lack of stack info) + [ERRNO_CODES.ENOENT].forEach(function(code) { + FS.genericErrors[code] = new FS.ErrnoError(code); + FS.genericErrors[code].stack = ''; + }); + }, + staticInit: function() { + FS.ensureErrnoError(); + + FS.nameTable = new Array(4096); + + FS.mount(MEMFS, {}, '/'); + + FS.createDefaultDirectories(); + FS.createDefaultDevices(); + }, + init: function(input, output, error) { + assert( + !FS.init.initialized, + 'FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)' + ); + FS.init.initialized = true; + + FS.ensureErrnoError(); + + // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here + Module['stdin'] = input || Module['stdin']; + Module['stdout'] = output || Module['stdout']; + Module['stderr'] = error || Module['stderr']; + + FS.createStandardStreams(); + }, + quit: function() { + FS.init.initialized = false; + for (var i = 0; i < FS.streams.length; i++) { + var stream = FS.streams[i]; + if (!stream) { + continue; + } + FS.close(stream); + } + }, + getMode: function(canRead, canWrite) { + var mode = 0; + if (canRead) mode |= 292 | 73; + if (canWrite) mode |= 146; + return mode; + }, + joinPath: function(parts, forceRelative) { + var path = PATH.join.apply(null, parts); + if (forceRelative && path[0] == '/') path = path.substr(1); + return path; + }, + absolutePath: function(relative, base) { + return PATH.resolve(base, relative); + }, + standardizePath: function(path) { + return PATH.normalize(path); + }, + findObject: function(path, dontResolveLastLink) { + var ret = FS.analyzePath(path, dontResolveLastLink); + if (ret.exists) { + return ret.object; + } else { + ___setErrNo(ret.error); + return null; + } + }, + analyzePath: function(path, dontResolveLastLink) { + // operate from within the context of the symlink's target + try { + var lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); + path = lookup.path; + } catch (e) {} + var ret = { + isRoot: false, + exists: false, + error: 0, + name: null, + path: null, + object: null, + parentExists: false, + parentPath: null, + parentObject: null, + }; + try { + var lookup = FS.lookupPath(path, { parent: true }); + ret.parentExists = true; + ret.parentPath = lookup.path; + ret.parentObject = lookup.node; + ret.name = PATH.basename(path); + lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); + ret.exists = true; + ret.path = lookup.path; + ret.object = lookup.node; + ret.name = lookup.node.name; + ret.isRoot = lookup.path === '/'; + } catch (e) { + ret.error = e.errno; + } + return ret; + }, + createFolder: function(parent, name, canRead, canWrite) { + var path = PATH.join2( + typeof parent === 'string' ? parent : FS.getPath(parent), + name + ); + var mode = FS.getMode(canRead, canWrite); + return FS.mkdir(path, mode); + }, + createPath: function(parent, path, canRead, canWrite) { + parent = typeof parent === 'string' ? parent : FS.getPath(parent); + var parts = path.split('/').reverse(); + while (parts.length) { + var part = parts.pop(); + if (!part) continue; + var current = PATH.join2(parent, part); + try { + FS.mkdir(current); + } catch (e) { + // ignore EEXIST + } + parent = current; + } + return current; + }, + createFile: function(parent, name, properties, canRead, canWrite) { + var path = PATH.join2( + typeof parent === 'string' ? parent : FS.getPath(parent), + name + ); + var mode = FS.getMode(canRead, canWrite); + return FS.create(path, mode); + }, + createDataFile: function(parent, name, data, canRead, canWrite, canOwn) { + var path = name + ? PATH.join2( + typeof parent === 'string' ? parent : FS.getPath(parent), + name + ) + : parent; + var mode = FS.getMode(canRead, canWrite); + var node = FS.create(path, mode); + if (data) { + if (typeof data === 'string') { + var arr = new Array(data.length); + for (var i = 0, len = data.length; i < len; ++i) + arr[i] = data.charCodeAt(i); + data = arr; + } + // make sure we can write to the file + FS.chmod(node, mode | 146); + var stream = FS.open(node, 'w'); + FS.write(stream, data, 0, data.length, 0, canOwn); + FS.close(stream); + FS.chmod(node, mode); + } + return node; + }, + createDevice: function(parent, name, input, output) { + var path = PATH.join2( + typeof parent === 'string' ? parent : FS.getPath(parent), + name + ); + var mode = FS.getMode(!!input, !!output); + if (!FS.createDevice.major) FS.createDevice.major = 64; + var dev = FS.makedev(FS.createDevice.major++, 0); + // Create a fake device that a set of stream ops to emulate + // the old behavior. + FS.registerDevice(dev, { + open: function(stream) { + stream.seekable = false; + }, + close: function(stream) { + // flush any pending line data + if (output && output.buffer && output.buffer.length) { + output(10); + } + }, + read: function(stream, buffer, offset, length, pos /* ignored */) { + var bytesRead = 0; + for (var i = 0; i < length; i++) { + var result; + try { + result = input(); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + if (result === undefined && bytesRead === 0) { + throw new FS.ErrnoError(ERRNO_CODES.EAGAIN); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[offset + i] = result; + } + if (bytesRead) { + stream.node.timestamp = Date.now(); + } + return bytesRead; + }, + write: function(stream, buffer, offset, length, pos) { + for (var i = 0; i < length; i++) { + try { + output(buffer[offset + i]); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + } + if (length) { + stream.node.timestamp = Date.now(); + } + return i; + }, + }); + return FS.mkdev(path, mode, dev); + }, + createLink: function(parent, name, target, canRead, canWrite) { + var path = PATH.join2( + typeof parent === 'string' ? parent : FS.getPath(parent), + name + ); + return FS.symlink(target, path); + }, + forceLoadFile: function(obj) { + if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true; + var success = true; + if (typeof XMLHttpRequest !== 'undefined') { + throw new Error( + 'Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.' + ); + } else if (Module['read']) { + // Command-line. + try { + // WARNING: Can't read binary files in V8's d8 or tracemonkey's js, as + // read() will try to parse UTF8. + obj.contents = intArrayFromString(Module['read'](obj.url), true); + obj.usedBytes = obj.contents.length; + } catch (e) { + success = false; + } + } else { + throw new Error('Cannot load without read() or XMLHttpRequest.'); + } + if (!success) ___setErrNo(ERRNO_CODES.EIO); + return success; + }, + createLazyFile: function(parent, name, url, canRead, canWrite) { + // Lazy chunked Uint8Array (implements get and length from Uint8Array). Actual getting is abstracted away for eventual reuse. + function LazyUint8Array() { + this.lengthKnown = false; + this.chunks = []; // Loaded chunks. Index is the chunk number + } + LazyUint8Array.prototype.get = function LazyUint8Array_get(idx) { + if (idx > this.length - 1 || idx < 0) { + return undefined; + } + var chunkOffset = idx % this.chunkSize; + var chunkNum = (idx / this.chunkSize) | 0; + return this.getter(chunkNum)[chunkOffset]; + }; + LazyUint8Array.prototype.setDataGetter = function LazyUint8Array_setDataGetter( + getter + ) { + this.getter = getter; + }; + LazyUint8Array.prototype.cacheLength = function LazyUint8Array_cacheLength() { + // Find length + var xhr = new XMLHttpRequest(); + xhr.open('HEAD', url, false); + xhr.send(null); + if (!((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304)) + throw new Error("Couldn't load " + url + '. Status: ' + xhr.status); + var datalength = Number(xhr.getResponseHeader('Content-length')); + var header; + var hasByteServing = + (header = xhr.getResponseHeader('Accept-Ranges')) && header === 'bytes'; + var chunkSize = 1024 * 1024; // Chunk size in bytes + + if (!hasByteServing) chunkSize = datalength; + + // Function to get a range from the remote URL. + var doXHR = function(from, to) { + if (from > to) + throw new Error( + 'invalid range (' + from + ', ' + to + ') or no bytes requested!' + ); + if (to > datalength - 1) + throw new Error( + 'only ' + datalength + ' bytes available! programmer error!' + ); + + // TODO: Use mozResponseArrayBuffer, responseStream, etc. if available. + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, false); + if (datalength !== chunkSize) + xhr.setRequestHeader('Range', 'bytes=' + from + '-' + to); + + // Some hints to the browser that we want binary data. + if (typeof Uint8Array != 'undefined') xhr.responseType = 'arraybuffer'; + if (xhr.overrideMimeType) { + xhr.overrideMimeType('text/plain; charset=x-user-defined'); + } + + xhr.send(null); + if (!((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304)) + throw new Error("Couldn't load " + url + '. Status: ' + xhr.status); + if (xhr.response !== undefined) { + return new Uint8Array(xhr.response || []); + } else { + return intArrayFromString(xhr.responseText || '', true); + } + }; + var lazyArray = this; + lazyArray.setDataGetter(function(chunkNum) { + var start = chunkNum * chunkSize; + var end = (chunkNum + 1) * chunkSize - 1; // including this byte + end = Math.min(end, datalength - 1); // if datalength-1 is selected, this is the last block + if (typeof lazyArray.chunks[chunkNum] === 'undefined') { + lazyArray.chunks[chunkNum] = doXHR(start, end); + } + if (typeof lazyArray.chunks[chunkNum] === 'undefined') + throw new Error('doXHR failed!'); + return lazyArray.chunks[chunkNum]; + }); + + this._length = datalength; + this._chunkSize = chunkSize; + this.lengthKnown = true; + }; + if (typeof XMLHttpRequest !== 'undefined') { + if (!ENVIRONMENT_IS_WORKER) + throw 'Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc'; + var lazyArray = new LazyUint8Array(); + Object.defineProperty(lazyArray, 'length', { + get: function() { + if (!this.lengthKnown) { + this.cacheLength(); + } + return this._length; + }, + }); + Object.defineProperty(lazyArray, 'chunkSize', { + get: function() { + if (!this.lengthKnown) { + this.cacheLength(); + } + return this._chunkSize; + }, + }); + + var properties = { isDevice: false, contents: lazyArray }; + } else { + var properties = { isDevice: false, url: url }; + } + + var node = FS.createFile(parent, name, properties, canRead, canWrite); + // This is a total hack, but I want to get this lazy file code out of the + // core of MEMFS. If we want to keep this lazy file concept I feel it should + // be its own thin LAZYFS proxying calls to MEMFS. + if (properties.contents) { + node.contents = properties.contents; + } else if (properties.url) { + node.contents = null; + node.url = properties.url; + } + // Add a function that defers querying the file size until it is asked the first time. + Object.defineProperty(node, 'usedBytes', { + get: function() { + return this.contents.length; + }, + }); + // override each stream op with one that tries to force load the lazy file first + var stream_ops = {}; + var keys = Object.keys(node.stream_ops); + keys.forEach(function(key) { + var fn = node.stream_ops[key]; + stream_ops[key] = function forceLoadLazyFile() { + if (!FS.forceLoadFile(node)) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + return fn.apply(null, arguments); + }; + }); + // use a custom read function + stream_ops.read = function stream_ops_read( + stream, + buffer, + offset, + length, + position + ) { + if (!FS.forceLoadFile(node)) { + throw new FS.ErrnoError(ERRNO_CODES.EIO); + } + var contents = stream.node.contents; + if (position >= contents.length) return 0; + var size = Math.min(contents.length - position, length); + assert(size >= 0); + if (contents.slice) { + // normal array + for (var i = 0; i < size; i++) { + buffer[offset + i] = contents[position + i]; + } + } else { + for (var i = 0; i < size; i++) { + // LazyUint8Array from sync binary XHR + buffer[offset + i] = contents.get(position + i); + } + } + return size; + }; + node.stream_ops = stream_ops; + return node; + }, + createPreloadedFile: function( + parent, + name, + url, + canRead, + canWrite, + onload, + onerror, + dontCreateFile, + canOwn + ) { + Browser.init(); + // TODO we should allow people to just pass in a complete filename instead + // of parent and name being that we just join them anyways + var fullname = name ? PATH.resolve(PATH.join2(parent, name)) : parent; + function processData(byteArray) { + function finish(byteArray) { + if (!dontCreateFile) { + FS.createDataFile(parent, name, byteArray, canRead, canWrite, canOwn); + } + if (onload) onload(); + removeRunDependency('cp ' + fullname); + } + var handled = false; + Module['preloadPlugins'].forEach(function(plugin) { + if (handled) return; + if (plugin['canHandle'](fullname)) { + plugin['handle'](byteArray, fullname, finish, function() { + if (onerror) onerror(); + removeRunDependency('cp ' + fullname); + }); + handled = true; + } + }); + if (!handled) finish(byteArray); + } + addRunDependency('cp ' + fullname); + if (typeof url == 'string') { + Browser.asyncLoad( + url, + function(byteArray) { + processData(byteArray); + }, + onerror + ); + } else { + processData(url); + } + }, + indexedDB: function() { + return ( + window.indexedDB || + window.mozIndexedDB || + window.webkitIndexedDB || + window.msIndexedDB + ); + }, + DB_NAME: function() { + return 'EM_FS_' + window.location.pathname; + }, + DB_VERSION: 20, + DB_STORE_NAME: 'FILE_DATA', + saveFilesToDB: function(paths, onload, onerror) { + onload = onload || function() {}; + onerror = onerror || function() {}; + var indexedDB = FS.indexedDB(); + try { + var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION); + } catch (e) { + return onerror(e); + } + openRequest.onupgradeneeded = function openRequest_onupgradeneeded() { + console.log('creating db'); + var db = openRequest.result; + db.createObjectStore(FS.DB_STORE_NAME); + }; + openRequest.onsuccess = function openRequest_onsuccess() { + var db = openRequest.result; + var transaction = db.transaction([FS.DB_STORE_NAME], 'readwrite'); + var files = transaction.objectStore(FS.DB_STORE_NAME); + var ok = 0, + fail = 0, + total = paths.length; + function finish() { + if (fail == 0) onload(); + else onerror(); + } + paths.forEach(function(path) { + var putRequest = files.put(FS.analyzePath(path).object.contents, path); + putRequest.onsuccess = function putRequest_onsuccess() { + ok++; + if (ok + fail == total) finish(); + }; + putRequest.onerror = function putRequest_onerror() { + fail++; + if (ok + fail == total) finish(); + }; + }); + transaction.onerror = onerror; + }; + openRequest.onerror = onerror; + }, + loadFilesFromDB: function(paths, onload, onerror) { + onload = onload || function() {}; + onerror = onerror || function() {}; + var indexedDB = FS.indexedDB(); + try { + var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION); + } catch (e) { + return onerror(e); + } + openRequest.onupgradeneeded = onerror; // no database to load from + openRequest.onsuccess = function openRequest_onsuccess() { + var db = openRequest.result; + try { + var transaction = db.transaction([FS.DB_STORE_NAME], 'readonly'); + } catch (e) { + onerror(e); + return; + } + var files = transaction.objectStore(FS.DB_STORE_NAME); + var ok = 0, + fail = 0, + total = paths.length; + function finish() { + if (fail == 0) onload(); + else onerror(); + } + paths.forEach(function(path) { + var getRequest = files.get(path); + getRequest.onsuccess = function getRequest_onsuccess() { + if (FS.analyzePath(path).exists) { + FS.unlink(path); + } + FS.createDataFile( + PATH.dirname(path), + PATH.basename(path), + getRequest.result, + true, + true, + true + ); + ok++; + if (ok + fail == total) finish(); + }; + getRequest.onerror = function getRequest_onerror() { + fail++; + if (ok + fail == total) finish(); + }; + }); + transaction.onerror = onerror; + }; + openRequest.onerror = onerror; + }, +}; +var PATH = { + splitPath: function(filename) { + var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; + return splitPathRe.exec(filename).slice(1); + }, + normalizeArray: function(parts, allowAboveRoot) { + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = parts.length - 1; i >= 0; i--) { + var last = parts[i]; + if (last === '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } + } + // if the path is allowed to go above the root, restore leading ..s + if (allowAboveRoot) { + for (; up--; up) { + parts.unshift('..'); + } + } + return parts; + }, + normalize: function(path) { + var isAbsolute = path.charAt(0) === '/', + trailingSlash = path.substr(-1) === '/'; + // Normalize the path + path = PATH.normalizeArray( + path.split('/').filter(function(p) { + return !!p; + }), + !isAbsolute + ).join('/'); + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; + } + return (isAbsolute ? '/' : '') + path; + }, + dirname: function(path) { + var result = PATH.splitPath(path), + root = result[0], + dir = result[1]; + if (!root && !dir) { + // No dirname whatsoever + return '.'; + } + if (dir) { + // It has a dirname, strip trailing slash + dir = dir.substr(0, dir.length - 1); + } + return root + dir; + }, + basename: function(path) { + // EMSCRIPTEN return '/'' for '/', not an empty string + if (path === '/') return '/'; + var lastSlash = path.lastIndexOf('/'); + if (lastSlash === -1) return path; + return path.substr(lastSlash + 1); + }, + extname: function(path) { + return PATH.splitPath(path)[3]; + }, + join: function() { + var paths = Array.prototype.slice.call(arguments, 0); + return PATH.normalize(paths.join('/')); + }, + join2: function(l, r) { + return PATH.normalize(l + '/' + r); + }, + resolve: function() { + var resolvedPath = '', + resolvedAbsolute = false; + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = i >= 0 ? arguments[i] : FS.cwd(); + // Skip empty and invalid entries + if (typeof path !== 'string') { + throw new TypeError('Arguments to path.resolve must be strings'); + } else if (!path) { + return ''; // an invalid portion invalidates the whole thing + } + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charAt(0) === '/'; + } + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + resolvedPath = PATH.normalizeArray( + resolvedPath.split('/').filter(function(p) { + return !!p; + }), + !resolvedAbsolute + ).join('/'); + return (resolvedAbsolute ? '/' : '') + resolvedPath || '.'; + }, + relative: function(from, to) { + from = PATH.resolve(from).substr(1); + to = PATH.resolve(to).substr(1); + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; + } + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; + } + if (start > end) return []; + return arr.slice(start, end - start + 1); + } + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } + } + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + return outputParts.join('/'); + }, +}; + +function _emscripten_set_main_loop_timing(mode, value) { + Browser.mainLoop.timingMode = mode; + Browser.mainLoop.timingValue = value; + + if (!Browser.mainLoop.func) { + return 1; // Return non-zero on failure, can't set timing mode when there is no main loop. + } + + if (mode == 0 /*EM_TIMING_SETTIMEOUT*/) { + Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler() { + setTimeout(Browser.mainLoop.runner, value); // doing this each time means that on exception, we stop + }; + Browser.mainLoop.method = 'timeout'; + } else if (mode == 1 /*EM_TIMING_RAF*/) { + Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler() { + Browser.requestAnimationFrame(Browser.mainLoop.runner); + }; + Browser.mainLoop.method = 'rAF'; + } + return 0; +} +function _emscripten_set_main_loop(func, fps, simulateInfiniteLoop, arg) { + Module['noExitRuntime'] = true; + + assert( + !Browser.mainLoop.func, + 'emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters.' + ); + + Browser.mainLoop.func = func; + Browser.mainLoop.arg = arg; + + var thisMainLoopId = Browser.mainLoop.currentlyRunningMainloop; + + Browser.mainLoop.runner = function Browser_mainLoop_runner() { + if (ABORT) return; + if (Browser.mainLoop.queue.length > 0) { + var start = Date.now(); + var blocker = Browser.mainLoop.queue.shift(); + blocker.func(blocker.arg); + if (Browser.mainLoop.remainingBlockers) { + var remaining = Browser.mainLoop.remainingBlockers; + var next = remaining % 1 == 0 ? remaining - 1 : Math.floor(remaining); + if (blocker.counted) { + Browser.mainLoop.remainingBlockers = next; + } else { + // not counted, but move the progress along a tiny bit + next = next + 0.5; // do not steal all the next one's progress + Browser.mainLoop.remainingBlockers = (8 * remaining + next) / 9; + } + } + console.log( + 'main loop blocker "' + + blocker.name + + '" took ' + + (Date.now() - start) + + ' ms' + ); //, left: ' + Browser.mainLoop.remainingBlockers); + Browser.mainLoop.updateStatus(); + setTimeout(Browser.mainLoop.runner, 0); + return; + } + + // catch pauses from non-main loop sources + if (thisMainLoopId < Browser.mainLoop.currentlyRunningMainloop) return; + + // Implement very basic swap interval control + Browser.mainLoop.currentFrameNumber = + (Browser.mainLoop.currentFrameNumber + 1) | 0; + if ( + Browser.mainLoop.timingMode == 1 /*EM_TIMING_RAF*/ && + Browser.mainLoop.timingValue > 1 && + Browser.mainLoop.currentFrameNumber % Browser.mainLoop.timingValue != 0 + ) { + // Not the scheduled time to render this frame - skip. + Browser.mainLoop.scheduler(); + return; + } + + // Signal GL rendering layer that processing of a new frame is about to start. This helps it optimize + // VBO double-buffering and reduce GPU stalls. + + if (Browser.mainLoop.method === 'timeout' && Module.ctx) { + Module.printErr( + 'Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!' + ); + Browser.mainLoop.method = ''; // just warn once per call to set main loop + } + + Browser.mainLoop.runIter(function() { + if (typeof arg !== 'undefined') { + Runtime.dynCall('vi', func, [arg]); + } else { + Runtime.dynCall('v', func); + } + }); + + // catch pauses from the main loop itself + if (thisMainLoopId < Browser.mainLoop.currentlyRunningMainloop) return; + + // Queue new audio data. This is important to be right after the main loop invocation, so that we will immediately be able + // to queue the newest produced audio samples. + // TODO: Consider adding pre- and post- rAF callbacks so that GL.newRenderingFrameStarted() and SDL.audio.queueNewAudioData() + // do not need to be hardcoded into this function, but can be more generic. + if (typeof SDL === 'object' && SDL.audio && SDL.audio.queueNewAudioData) + SDL.audio.queueNewAudioData(); + + Browser.mainLoop.scheduler(); + }; + + if (fps && fps > 0) + _emscripten_set_main_loop_timing(0 /*EM_TIMING_SETTIMEOUT*/, 1000.0 / fps); + else _emscripten_set_main_loop_timing(1 /*EM_TIMING_RAF*/, 1); // Do rAF by rendering each frame (no decimating) + + Browser.mainLoop.scheduler(); + + if (simulateInfiniteLoop) { + throw 'SimulateInfiniteLoop'; + } +} +var Browser = { + mainLoop: { + scheduler: null, + method: '', + currentlyRunningMainloop: 0, + func: null, + arg: 0, + timingMode: 0, + timingValue: 0, + currentFrameNumber: 0, + queue: [], + pause: function() { + Browser.mainLoop.scheduler = null; + Browser.mainLoop.currentlyRunningMainloop++; // Incrementing this signals the previous main loop that it's now become old, and it must return. + }, + resume: function() { + Browser.mainLoop.currentlyRunningMainloop++; + var timingMode = Browser.mainLoop.timingMode; + var timingValue = Browser.mainLoop.timingValue; + var func = Browser.mainLoop.func; + Browser.mainLoop.func = null; + _emscripten_set_main_loop(func, 0, false, Browser.mainLoop.arg); + _emscripten_set_main_loop_timing(timingMode, timingValue); + }, + updateStatus: function() { + if (Module['setStatus']) { + var message = Module['statusMessage'] || 'Please wait...'; + var remaining = Browser.mainLoop.remainingBlockers; + var expected = Browser.mainLoop.expectedBlockers; + if (remaining) { + if (remaining < expected) { + Module['setStatus']( + message + ' (' + (expected - remaining) + '/' + expected + ')' + ); + } else { + Module['setStatus'](message); + } + } else { + Module['setStatus'](''); + } + } + }, + runIter: function(func) { + if (ABORT) return; + if (Module['preMainLoop']) { + var preRet = Module['preMainLoop'](); + if (preRet === false) { + return; // |return false| skips a frame + } + } + try { + func(); + } catch (e) { + if (e instanceof ExitStatus) { + return; + } else { + if (e && typeof e === 'object' && e.stack) + Module.printErr('exception thrown: ' + [e, e.stack]); + throw e; + } + } + if (Module['postMainLoop']) Module['postMainLoop'](); + }, + }, + isFullScreen: false, + pointerLock: false, + moduleContextCreatedCallbacks: [], + workers: [], + init: function() { + if (!Module['preloadPlugins']) Module['preloadPlugins'] = []; // needs to exist even in workers + + if (Browser.initted) return; + Browser.initted = true; + + try { + new Blob(); + Browser.hasBlobConstructor = true; + } catch (e) { + Browser.hasBlobConstructor = false; + console.log( + 'warning: no blob constructor, cannot create blobs with mimetypes' + ); + } + Browser.BlobBuilder = + typeof MozBlobBuilder != 'undefined' + ? MozBlobBuilder + : typeof WebKitBlobBuilder != 'undefined' + ? WebKitBlobBuilder + : !Browser.hasBlobConstructor + ? console.log('warning: no BlobBuilder') + : null; + Browser.URLObject = + typeof window != 'undefined' + ? window.URL + ? window.URL + : window.webkitURL + : undefined; + if (!Module.noImageDecoding && typeof Browser.URLObject === 'undefined') { + console.log( + 'warning: Browser does not support creating object URLs. Built-in browser image decoding will not be available.' + ); + Module.noImageDecoding = true; + } + + // Support for plugins that can process preloaded files. You can add more of these to + // your app by creating and appending to Module.preloadPlugins. + // + // Each plugin is asked if it can handle a file based on the file's name. If it can, + // it is given the file's raw data. When it is done, it calls a callback with the file's + // (possibly modified) data. For example, a plugin might decompress a file, or it + // might create some side data structure for use later (like an Image element, etc.). + + var imagePlugin = {}; + imagePlugin['canHandle'] = function imagePlugin_canHandle(name) { + return !Module.noImageDecoding && /\.(jpg|jpeg|png|bmp)$/i.test(name); + }; + imagePlugin['handle'] = function imagePlugin_handle( + byteArray, + name, + onload, + onerror + ) { + var b = null; + if (Browser.hasBlobConstructor) { + try { + b = new Blob([byteArray], { type: Browser.getMimetype(name) }); + if (b.size !== byteArray.length) { + // Safari bug #118630 + // Safari's Blob can only take an ArrayBuffer + b = new Blob([new Uint8Array(byteArray).buffer], { + type: Browser.getMimetype(name), + }); + } + } catch (e) { + Runtime.warnOnce( + 'Blob constructor present but fails: ' + + e + + '; falling back to blob builder' + ); + } + } + if (!b) { + var bb = new Browser.BlobBuilder(); + bb.append(new Uint8Array(byteArray).buffer); // we need to pass a buffer, and must copy the array to get the right data range + b = bb.getBlob(); + } + var url = Browser.URLObject.createObjectURL(b); + var img = new Image(); + img.onload = function img_onload() { + assert(img.complete, 'Image ' + name + ' could not be decoded'); + var canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + var ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); + Module['preloadedImages'][name] = canvas; + Browser.URLObject.revokeObjectURL(url); + if (onload) onload(byteArray); + }; + img.onerror = function img_onerror(event) { + console.log('Image ' + url + ' could not be decoded'); + if (onerror) onerror(); + }; + img.src = url; + }; + Module['preloadPlugins'].push(imagePlugin); + + var audioPlugin = {}; + audioPlugin['canHandle'] = function audioPlugin_canHandle(name) { + return ( + !Module.noAudioDecoding && + name.substr(-4) in { '.ogg': 1, '.wav': 1, '.mp3': 1 } + ); + }; + audioPlugin['handle'] = function audioPlugin_handle( + byteArray, + name, + onload, + onerror + ) { + var done = false; + function finish(audio) { + if (done) return; + done = true; + Module['preloadedAudios'][name] = audio; + if (onload) onload(byteArray); + } + function fail() { + if (done) return; + done = true; + Module['preloadedAudios'][name] = new Audio(); // empty shim + if (onerror) onerror(); + } + if (Browser.hasBlobConstructor) { + try { + var b = new Blob([byteArray], { type: Browser.getMimetype(name) }); + } catch (e) { + return fail(); + } + var url = Browser.URLObject.createObjectURL(b); // XXX we never revoke this! + var audio = new Audio(); + audio.addEventListener( + 'canplaythrough', + function() { + finish(audio); + }, + false + ); // use addEventListener due to chromium bug 124926 + audio.onerror = function audio_onerror(event) { + if (done) return; + console.log( + 'warning: browser could not fully decode audio ' + + name + + ', trying slower base64 approach' + ); + function encode64(data) { + var BASE = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + var PAD = '='; + var ret = ''; + var leftchar = 0; + var leftbits = 0; + for (var i = 0; i < data.length; i++) { + leftchar = (leftchar << 8) | data[i]; + leftbits += 8; + while (leftbits >= 6) { + var curr = (leftchar >> (leftbits - 6)) & 0x3f; + leftbits -= 6; + ret += BASE[curr]; + } + } + if (leftbits == 2) { + ret += BASE[(leftchar & 3) << 4]; + ret += PAD + PAD; + } else if (leftbits == 4) { + ret += BASE[(leftchar & 0xf) << 2]; + ret += PAD; + } + return ret; + } + audio.src = + 'data:audio/x-' + + name.substr(-3) + + ';base64,' + + encode64(byteArray); + finish(audio); // we don't wait for confirmation this worked - but it's worth trying + }; + audio.src = url; + // workaround for chrome bug 124926 - we do not always get oncanplaythrough or onerror + Browser.safeSetTimeout(function() { + finish(audio); // try to use it even though it is not necessarily ready to play + }, 10000); + } else { + return fail(); + } + }; + Module['preloadPlugins'].push(audioPlugin); + + // Canvas event setup + + var canvas = Module['canvas']; + function pointerLockChange() { + Browser.pointerLock = + document['pointerLockElement'] === canvas || + document['mozPointerLockElement'] === canvas || + document['webkitPointerLockElement'] === canvas || + document['msPointerLockElement'] === canvas; + } + if (canvas) { + // forced aspect ratio can be enabled by defining 'forcedAspectRatio' on Module + // Module['forcedAspectRatio'] = 4 / 3; + + canvas.requestPointerLock = + canvas['requestPointerLock'] || + canvas['mozRequestPointerLock'] || + canvas['webkitRequestPointerLock'] || + canvas['msRequestPointerLock'] || + function() {}; + canvas.exitPointerLock = + document['exitPointerLock'] || + document['mozExitPointerLock'] || + document['webkitExitPointerLock'] || + document['msExitPointerLock'] || + function() {}; // no-op if function does not exist + canvas.exitPointerLock = canvas.exitPointerLock.bind(document); + + document.addEventListener('pointerlockchange', pointerLockChange, false); + document.addEventListener( + 'mozpointerlockchange', + pointerLockChange, + false + ); + document.addEventListener( + 'webkitpointerlockchange', + pointerLockChange, + false + ); + document.addEventListener( + 'mspointerlockchange', + pointerLockChange, + false + ); + + if (Module['elementPointerLock']) { + canvas.addEventListener( + 'click', + function(ev) { + if (!Browser.pointerLock && canvas.requestPointerLock) { + canvas.requestPointerLock(); + ev.preventDefault(); + } + }, + false + ); + } + } + }, + createContext: function( + canvas, + useWebGL, + setInModule, + webGLContextAttributes + ) { + if (useWebGL && Module.ctx && canvas == Module.canvas) return Module.ctx; // no need to recreate GL context if it's already been created for this canvas. + + var ctx; + var contextHandle; + if (useWebGL) { + // For GLES2/desktop GL compatibility, adjust a few defaults to be different to WebGL defaults, so that they align better with the desktop defaults. + var contextAttributes = { + antialias: false, + alpha: false, + }; + + if (webGLContextAttributes) { + for (var attribute in webGLContextAttributes) { + contextAttributes[attribute] = webGLContextAttributes[attribute]; + } + } + + contextHandle = GL.createContext(canvas, contextAttributes); + if (contextHandle) { + ctx = GL.getContext(contextHandle).GLctx; + } + // Set the background of the WebGL canvas to black + canvas.style.backgroundColor = 'black'; + } else { + ctx = canvas.getContext('2d'); + } + + if (!ctx) return null; + + if (setInModule) { + if (!useWebGL) + assert( + typeof GLctx === 'undefined', + 'cannot set in module if GLctx is used, but we are a non-GL context that would replace it' + ); + + Module.ctx = ctx; + if (useWebGL) GL.makeContextCurrent(contextHandle); + Module.useWebGL = useWebGL; + Browser.moduleContextCreatedCallbacks.forEach(function(callback) { + callback(); + }); + Browser.init(); + } + return ctx; + }, + destroyContext: function(canvas, useWebGL, setInModule) {}, + fullScreenHandlersInstalled: false, + lockPointer: undefined, + resizeCanvas: undefined, + requestFullScreen: function(lockPointer, resizeCanvas) { + Browser.lockPointer = lockPointer; + Browser.resizeCanvas = resizeCanvas; + if (typeof Browser.lockPointer === 'undefined') Browser.lockPointer = true; + if (typeof Browser.resizeCanvas === 'undefined') + Browser.resizeCanvas = false; + + var canvas = Module['canvas']; + function fullScreenChange() { + Browser.isFullScreen = false; + var canvasContainer = canvas.parentNode; + if ( + (document['webkitFullScreenElement'] || + document['webkitFullscreenElement'] || + document['mozFullScreenElement'] || + document['mozFullscreenElement'] || + document['fullScreenElement'] || + document['fullscreenElement'] || + document['msFullScreenElement'] || + document['msFullscreenElement'] || + document['webkitCurrentFullScreenElement']) === canvasContainer + ) { + canvas.cancelFullScreen = + document['cancelFullScreen'] || + document['mozCancelFullScreen'] || + document['webkitCancelFullScreen'] || + document['msExitFullscreen'] || + document['exitFullscreen'] || + function() {}; + canvas.cancelFullScreen = canvas.cancelFullScreen.bind(document); + if (Browser.lockPointer) canvas.requestPointerLock(); + Browser.isFullScreen = true; + if (Browser.resizeCanvas) Browser.setFullScreenCanvasSize(); + } else { + // remove the full screen specific parent of the canvas again to restore the HTML structure from before going full screen + canvasContainer.parentNode.insertBefore(canvas, canvasContainer); + canvasContainer.parentNode.removeChild(canvasContainer); + + if (Browser.resizeCanvas) Browser.setWindowedCanvasSize(); + } + if (Module['onFullScreen']) Module['onFullScreen'](Browser.isFullScreen); + Browser.updateCanvasDimensions(canvas); + } + + if (!Browser.fullScreenHandlersInstalled) { + Browser.fullScreenHandlersInstalled = true; + document.addEventListener('fullscreenchange', fullScreenChange, false); + document.addEventListener('mozfullscreenchange', fullScreenChange, false); + document.addEventListener( + 'webkitfullscreenchange', + fullScreenChange, + false + ); + document.addEventListener('MSFullscreenChange', fullScreenChange, false); + } + + // create a new parent to ensure the canvas has no siblings. this allows browsers to optimize full screen performance when its parent is the full screen root + var canvasContainer = document.createElement('div'); + canvas.parentNode.insertBefore(canvasContainer, canvas); + canvasContainer.appendChild(canvas); + + // use parent of canvas as full screen root to allow aspect ratio correction (Firefox stretches the root to screen size) + canvasContainer.requestFullScreen = + canvasContainer['requestFullScreen'] || + canvasContainer['mozRequestFullScreen'] || + canvasContainer['msRequestFullscreen'] || + (canvasContainer['webkitRequestFullScreen'] + ? function() { + canvasContainer['webkitRequestFullScreen']( + Element['ALLOW_KEYBOARD_INPUT'] + ); + } + : null); + canvasContainer.requestFullScreen(); + }, + nextRAF: 0, + fakeRequestAnimationFrame: function(func) { + // try to keep 60fps between calls to here + var now = Date.now(); + if (Browser.nextRAF === 0) { + Browser.nextRAF = now + 1000 / 60; + } else { + while (now + 2 >= Browser.nextRAF) { + // fudge a little, to avoid timer jitter causing us to do lots of delay:0 + Browser.nextRAF += 1000 / 60; + } + } + var delay = Math.max(Browser.nextRAF - now, 0); + setTimeout(func, delay); + }, + requestAnimationFrame: function requestAnimationFrame(func) { + if (typeof window === 'undefined') { + // Provide fallback to setTimeout if window is undefined (e.g. in Node.js) + Browser.fakeRequestAnimationFrame(func); + } else { + if (!window.requestAnimationFrame) { + window.requestAnimationFrame = + window['requestAnimationFrame'] || + window['mozRequestAnimationFrame'] || + window['webkitRequestAnimationFrame'] || + window['msRequestAnimationFrame'] || + window['oRequestAnimationFrame'] || + Browser.fakeRequestAnimationFrame; + } + window.requestAnimationFrame(func); + } + }, + safeCallback: function(func) { + return function() { + if (!ABORT) return func.apply(null, arguments); + }; + }, + safeRequestAnimationFrame: function(func) { + return Browser.requestAnimationFrame(function() { + if (!ABORT) func(); + }); + }, + safeSetTimeout: function(func, timeout) { + Module['noExitRuntime'] = true; + return setTimeout(function() { + if (!ABORT) func(); + }, timeout); + }, + safeSetInterval: function(func, timeout) { + Module['noExitRuntime'] = true; + return setInterval(function() { + if (!ABORT) func(); + }, timeout); + }, + getMimetype: function(name) { + return { + jpg: 'image/jpeg', + jpeg: 'image/jpeg', + png: 'image/png', + bmp: 'image/bmp', + ogg: 'audio/ogg', + wav: 'audio/wav', + mp3: 'audio/mpeg', + }[name.substr(name.lastIndexOf('.') + 1)]; + }, + getUserMedia: function(func) { + if (!window.getUserMedia) { + window.getUserMedia = + navigator['getUserMedia'] || navigator['mozGetUserMedia']; + } + window.getUserMedia(func); + }, + getMovementX: function(event) { + return ( + event['movementX'] || + event['mozMovementX'] || + event['webkitMovementX'] || + 0 + ); + }, + getMovementY: function(event) { + return ( + event['movementY'] || + event['mozMovementY'] || + event['webkitMovementY'] || + 0 + ); + }, + getMouseWheelDelta: function(event) { + var delta = 0; + switch (event.type) { + case 'DOMMouseScroll': + delta = event.detail; + break; + case 'mousewheel': + delta = event.wheelDelta; + break; + case 'wheel': + delta = event['deltaY']; + break; + default: + throw 'unrecognized mouse wheel event: ' + event.type; + } + return delta; + }, + mouseX: 0, + mouseY: 0, + mouseMovementX: 0, + mouseMovementY: 0, + touches: {}, + lastTouches: {}, + calculateMouseEvent: function(event) { + // event should be mousemove, mousedown or mouseup + if (Browser.pointerLock) { + // When the pointer is locked, calculate the coordinates + // based on the movement of the mouse. + // Workaround for Firefox bug 764498 + if (event.type != 'mousemove' && 'mozMovementX' in event) { + Browser.mouseMovementX = Browser.mouseMovementY = 0; + } else { + Browser.mouseMovementX = Browser.getMovementX(event); + Browser.mouseMovementY = Browser.getMovementY(event); + } + + // check if SDL is available + if (typeof SDL != 'undefined') { + Browser.mouseX = SDL.mouseX + Browser.mouseMovementX; + Browser.mouseY = SDL.mouseY + Browser.mouseMovementY; + } else { + // just add the mouse delta to the current absolut mouse position + // FIXME: ideally this should be clamped against the canvas size and zero + Browser.mouseX += Browser.mouseMovementX; + Browser.mouseY += Browser.mouseMovementY; + } + } else { + // Otherwise, calculate the movement based on the changes + // in the coordinates. + var rect = Module['canvas'].getBoundingClientRect(); + var cw = Module['canvas'].width; + var ch = Module['canvas'].height; + + // Neither .scrollX or .pageXOffset are defined in a spec, but + // we prefer .scrollX because it is currently in a spec draft. + // (see: http://www.w3.org/TR/2013/WD-cssom-view-20131217/) + var scrollX = + typeof window.scrollX !== 'undefined' + ? window.scrollX + : window.pageXOffset; + var scrollY = + typeof window.scrollY !== 'undefined' + ? window.scrollY + : window.pageYOffset; + + if ( + event.type === 'touchstart' || + event.type === 'touchend' || + event.type === 'touchmove' + ) { + var touch = event.touch; + if (touch === undefined) { + return; // the "touch" property is only defined in SDL + } + var adjustedX = touch.pageX - (scrollX + rect.left); + var adjustedY = touch.pageY - (scrollY + rect.top); + + adjustedX = adjustedX * (cw / rect.width); + adjustedY = adjustedY * (ch / rect.height); + + var coords = { x: adjustedX, y: adjustedY }; + + if (event.type === 'touchstart') { + Browser.lastTouches[touch.identifier] = coords; + Browser.touches[touch.identifier] = coords; + } else if (event.type === 'touchend' || event.type === 'touchmove') { + Browser.lastTouches[touch.identifier] = + Browser.touches[touch.identifier]; + Browser.touches[touch.identifier] = { x: adjustedX, y: adjustedY }; + } + return; + } + + var x = event.pageX - (scrollX + rect.left); + var y = event.pageY - (scrollY + rect.top); + + // the canvas might be CSS-scaled compared to its backbuffer; + // SDL-using content will want mouse coordinates in terms + // of backbuffer units. + x = x * (cw / rect.width); + y = y * (ch / rect.height); + + Browser.mouseMovementX = x - Browser.mouseX; + Browser.mouseMovementY = y - Browser.mouseY; + Browser.mouseX = x; + Browser.mouseY = y; + } + }, + xhrLoad: function(url, onload, onerror) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.responseType = 'arraybuffer'; + xhr.onload = function xhr_onload() { + if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) { + // file URLs can return 0 + onload(xhr.response); + } else { + onerror(); + } + }; + xhr.onerror = onerror; + xhr.send(null); + }, + asyncLoad: function(url, onload, onerror, noRunDep) { + Browser.xhrLoad( + url, + function(arrayBuffer) { + assert( + arrayBuffer, + 'Loading data file "' + url + '" failed (no arrayBuffer).' + ); + onload(new Uint8Array(arrayBuffer)); + if (!noRunDep) removeRunDependency('al ' + url); + }, + function(event) { + if (onerror) { + onerror(); + } else { + throw 'Loading data file "' + url + '" failed.'; + } + } + ); + if (!noRunDep) addRunDependency('al ' + url); + }, + resizeListeners: [], + updateResizeListeners: function() { + var canvas = Module['canvas']; + Browser.resizeListeners.forEach(function(listener) { + listener(canvas.width, canvas.height); + }); + }, + setCanvasSize: function(width, height, noUpdates) { + var canvas = Module['canvas']; + Browser.updateCanvasDimensions(canvas, width, height); + if (!noUpdates) Browser.updateResizeListeners(); + }, + windowedWidth: 0, + windowedHeight: 0, + setFullScreenCanvasSize: function() { + // check if SDL is available + if (typeof SDL != 'undefined') { + var flags = HEAPU32[(SDL.screen + Runtime.QUANTUM_SIZE * 0) >> 2]; + flags = flags | 0x00800000; // set SDL_FULLSCREEN flag + HEAP32[(SDL.screen + Runtime.QUANTUM_SIZE * 0) >> 2] = flags; + } + Browser.updateResizeListeners(); + }, + setWindowedCanvasSize: function() { + // check if SDL is available + if (typeof SDL != 'undefined') { + var flags = HEAPU32[(SDL.screen + Runtime.QUANTUM_SIZE * 0) >> 2]; + flags = flags & ~0x00800000; // clear SDL_FULLSCREEN flag + HEAP32[(SDL.screen + Runtime.QUANTUM_SIZE * 0) >> 2] = flags; + } + Browser.updateResizeListeners(); + }, + updateCanvasDimensions: function(canvas, wNative, hNative) { + if (wNative && hNative) { + canvas.widthNative = wNative; + canvas.heightNative = hNative; + } else { + wNative = canvas.widthNative; + hNative = canvas.heightNative; + } + var w = wNative; + var h = hNative; + if (Module['forcedAspectRatio'] && Module['forcedAspectRatio'] > 0) { + if (w / h < Module['forcedAspectRatio']) { + w = Math.round(h * Module['forcedAspectRatio']); + } else { + h = Math.round(w / Module['forcedAspectRatio']); + } + } + if ( + (document['webkitFullScreenElement'] || + document['webkitFullscreenElement'] || + document['mozFullScreenElement'] || + document['mozFullscreenElement'] || + document['fullScreenElement'] || + document['fullscreenElement'] || + document['msFullScreenElement'] || + document['msFullscreenElement'] || + document['webkitCurrentFullScreenElement']) === canvas.parentNode && + typeof screen != 'undefined' + ) { + var factor = Math.min(screen.width / w, screen.height / h); + w = Math.round(w * factor); + h = Math.round(h * factor); + } + if (Browser.resizeCanvas) { + if (canvas.width != w) canvas.width = w; + if (canvas.height != h) canvas.height = h; + if (typeof canvas.style != 'undefined') { + canvas.style.removeProperty('width'); + canvas.style.removeProperty('height'); + } + } else { + if (canvas.width != wNative) canvas.width = wNative; + if (canvas.height != hNative) canvas.height = hNative; + if (typeof canvas.style != 'undefined') { + if (w != wNative || h != hNative) { + canvas.style.setProperty('width', w + 'px', 'important'); + canvas.style.setProperty('height', h + 'px', 'important'); + } else { + canvas.style.removeProperty('width'); + canvas.style.removeProperty('height'); + } + } + } + }, + wgetRequests: {}, + nextWgetRequestHandle: 0, + getNextWgetRequestHandle: function() { + var handle = Browser.nextWgetRequestHandle; + Browser.nextWgetRequestHandle++; + return handle; + }, +}; + +function _time(ptr) { + var ret = (Date.now() / 1000) | 0; + if (ptr) { + HEAP32[ptr >> 2] = ret; + } + return ret; +} +___errno_state = Runtime.staticAlloc(4); +HEAP32[___errno_state >> 2] = 0; +Module['requestFullScreen'] = function Module_requestFullScreen( + lockPointer, + resizeCanvas +) { + Browser.requestFullScreen(lockPointer, resizeCanvas); +}; +Module['requestAnimationFrame'] = function Module_requestAnimationFrame(func) { + Browser.requestAnimationFrame(func); +}; +Module['setCanvasSize'] = function Module_setCanvasSize( + width, + height, + noUpdates +) { + Browser.setCanvasSize(width, height, noUpdates); +}; +Module['pauseMainLoop'] = function Module_pauseMainLoop() { + Browser.mainLoop.pause(); +}; +Module['resumeMainLoop'] = function Module_resumeMainLoop() { + Browser.mainLoop.resume(); +}; +Module['getUserMedia'] = function Module_getUserMedia() { + Browser.getUserMedia(); +}; +FS.staticInit(); +__ATINIT__.unshift({ + func: function() { + if (!Module['noFSInit'] && !FS.init.initialized) FS.init(); + }, +}); +__ATMAIN__.push({ + func: function() { + FS.ignorePermissions = false; + }, +}); +__ATEXIT__.push({ + func: function() { + FS.quit(); + }, +}); +Module['FS_createFolder'] = FS.createFolder; +Module['FS_createPath'] = FS.createPath; +Module['FS_createDataFile'] = FS.createDataFile; +Module['FS_createPreloadedFile'] = FS.createPreloadedFile; +Module['FS_createLazyFile'] = FS.createLazyFile; +Module['FS_createLink'] = FS.createLink; +Module['FS_createDevice'] = FS.createDevice; +__ATINIT__.unshift({ + func: function() { + TTY.init(); + }, +}); +__ATEXIT__.push({ + func: function() { + TTY.shutdown(); + }, +}); +TTY.utf8 = new Runtime.UTF8Processor(); +if (ENVIRONMENT_IS_NODE) { + var fs = require('fs'); + NODEFS.staticInit(); +} +STACK_BASE = STACKTOP = Runtime.alignMemory(STATICTOP); + +staticSealed = true; // seal the static portion of memory + +STACK_MAX = STACK_BASE + TOTAL_STACK; + +DYNAMIC_BASE = DYNAMICTOP = Runtime.alignMemory(STACK_MAX); + +assert(DYNAMIC_BASE < TOTAL_MEMORY, 'TOTAL_MEMORY not big enough for stack'); + +var ctlz_i8 = allocate( + [ + 8, + 7, + 6, + 6, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + 'i8', + ALLOC_DYNAMIC +); +var cttz_i8 = allocate( + [ + 8, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 4, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 5, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 4, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 6, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 4, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 5, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 4, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 7, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 4, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 5, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 4, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 6, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 4, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 5, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 4, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + 3, + 0, + 1, + 0, + 2, + 0, + 1, + 0, + ], + 'i8', + ALLOC_DYNAMIC +); + +Module.asmGlobalArg = { + Math: Math, + Int8Array: Int8Array, + Int16Array: Int16Array, + Int32Array: Int32Array, + Uint8Array: Uint8Array, + Uint16Array: Uint16Array, + Uint32Array: Uint32Array, + Float32Array: Float32Array, + Float64Array: Float64Array, +}; +Module.asmLibraryArg = { + abort: abort, + assert: assert, + min: Math_min, + _fflush: _fflush, + _sysconf: _sysconf, + _abort: _abort, + ___setErrNo: ___setErrNo, + _sbrk: _sbrk, + _time: _time, + _emscripten_set_main_loop_timing: _emscripten_set_main_loop_timing, + _emscripten_memcpy_big: _emscripten_memcpy_big, + _emscripten_set_main_loop: _emscripten_set_main_loop, + ___errno_location: ___errno_location, + STACKTOP: STACKTOP, + STACK_MAX: STACK_MAX, + tempDoublePtr: tempDoublePtr, + ABORT: ABORT, + cttz_i8: cttz_i8, + ctlz_i8: ctlz_i8, + NaN: NaN, + Infinity: Infinity, +}; +// EMSCRIPTEN_START_ASM +var asm = (function(global, env, buffer) { + 'use asm'; + + var HEAP8 = new global.Int8Array(buffer); + var HEAP16 = new global.Int16Array(buffer); + var HEAP32 = new global.Int32Array(buffer); + var HEAPU8 = new global.Uint8Array(buffer); + var HEAPU16 = new global.Uint16Array(buffer); + var HEAPU32 = new global.Uint32Array(buffer); + var HEAPF32 = new global.Float32Array(buffer); + var HEAPF64 = new global.Float64Array(buffer); + + var STACKTOP = env.STACKTOP | 0; + var STACK_MAX = env.STACK_MAX | 0; + var tempDoublePtr = env.tempDoublePtr | 0; + var ABORT = env.ABORT | 0; + var cttz_i8 = env.cttz_i8 | 0; + var ctlz_i8 = env.ctlz_i8 | 0; + + var __THREW__ = 0; + var threwValue = 0; + var setjmpId = 0; + var undef = 0; + var nan = +env.NaN, + inf = +env.Infinity; + var tempInt = 0, + tempBigInt = 0, + tempBigIntP = 0, + tempBigIntS = 0, + tempBigIntR = 0.0, + tempBigIntI = 0, + tempBigIntD = 0, + tempValue = 0, + tempDouble = 0.0; + + var tempRet0 = 0; + var tempRet1 = 0; + var tempRet2 = 0; + var tempRet3 = 0; + var tempRet4 = 0; + var tempRet5 = 0; + var tempRet6 = 0; + var tempRet7 = 0; + var tempRet8 = 0; + var tempRet9 = 0; + var Math_floor = global.Math.floor; + var Math_abs = global.Math.abs; + var Math_sqrt = global.Math.sqrt; + var Math_pow = global.Math.pow; + var Math_cos = global.Math.cos; + var Math_sin = global.Math.sin; + var Math_tan = global.Math.tan; + var Math_acos = global.Math.acos; + var Math_asin = global.Math.asin; + var Math_atan = global.Math.atan; + var Math_atan2 = global.Math.atan2; + var Math_exp = global.Math.exp; + var Math_log = global.Math.log; + var Math_ceil = global.Math.ceil; + var Math_imul = global.Math.imul; + var abort = env.abort; + var assert = env.assert; + var Math_min = env.min; + var _fflush = env._fflush; + var _sysconf = env._sysconf; + var _abort = env._abort; + var ___setErrNo = env.___setErrNo; + var _sbrk = env._sbrk; + var _time = env._time; + var _emscripten_set_main_loop_timing = env._emscripten_set_main_loop_timing; + var _emscripten_memcpy_big = env._emscripten_memcpy_big; + var _emscripten_set_main_loop = env._emscripten_set_main_loop; + var ___errno_location = env.___errno_location; + var tempFloat = 0.0; + + // EMSCRIPTEN_START_FUNCS + function stackAlloc(size) { + size = size | 0; + var ret = 0; + ret = STACKTOP; + STACKTOP = (STACKTOP + size) | 0; + STACKTOP = (STACKTOP + 15) & -16; + + return ret | 0; + } + function stackSave() { + return STACKTOP | 0; + } + function stackRestore(top) { + top = top | 0; + STACKTOP = top; + } + + function setThrew(threw, value) { + threw = threw | 0; + value = value | 0; + if ((__THREW__ | 0) == 0) { + __THREW__ = threw; + threwValue = value; + } + } + function copyTempFloat(ptr) { + ptr = ptr | 0; + HEAP8[tempDoublePtr >> 0] = HEAP8[ptr >> 0]; + HEAP8[(tempDoublePtr + 1) >> 0] = HEAP8[(ptr + 1) >> 0]; + HEAP8[(tempDoublePtr + 2) >> 0] = HEAP8[(ptr + 2) >> 0]; + HEAP8[(tempDoublePtr + 3) >> 0] = HEAP8[(ptr + 3) >> 0]; + } + function copyTempDouble(ptr) { + ptr = ptr | 0; + HEAP8[tempDoublePtr >> 0] = HEAP8[ptr >> 0]; + HEAP8[(tempDoublePtr + 1) >> 0] = HEAP8[(ptr + 1) >> 0]; + HEAP8[(tempDoublePtr + 2) >> 0] = HEAP8[(ptr + 2) >> 0]; + HEAP8[(tempDoublePtr + 3) >> 0] = HEAP8[(ptr + 3) >> 0]; + HEAP8[(tempDoublePtr + 4) >> 0] = HEAP8[(ptr + 4) >> 0]; + HEAP8[(tempDoublePtr + 5) >> 0] = HEAP8[(ptr + 5) >> 0]; + HEAP8[(tempDoublePtr + 6) >> 0] = HEAP8[(ptr + 6) >> 0]; + HEAP8[(tempDoublePtr + 7) >> 0] = HEAP8[(ptr + 7) >> 0]; + } + function setTempRet0(value) { + value = value | 0; + tempRet0 = value; + } + function getTempRet0() { + return tempRet0 | 0; + } + + function _crypto_verify_32_ref($x, $y) { + $x = $x | 0; + $y = $y | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $101 = 0, + $102 = 0, + $103 = 0, + $104 = 0, + $105 = 0, + $106 = 0, + $107 = 0, + $108 = 0, + $109 = 0, + $11 = 0, + $110 = 0, + $111 = 0, + $112 = 0, + $113 = 0, + $114 = 0, + $115 = 0; + var $116 = 0, + $117 = 0, + $118 = 0, + $119 = 0, + $12 = 0, + $120 = 0, + $121 = 0, + $122 = 0, + $123 = 0, + $124 = 0, + $125 = 0, + $126 = 0, + $127 = 0, + $128 = 0, + $129 = 0, + $13 = 0, + $130 = 0, + $131 = 0, + $132 = 0, + $133 = 0; + var $134 = 0, + $135 = 0, + $136 = 0, + $137 = 0, + $138 = 0, + $139 = 0, + $14 = 0, + $140 = 0, + $141 = 0, + $142 = 0, + $143 = 0, + $144 = 0, + $145 = 0, + $146 = 0, + $147 = 0, + $148 = 0, + $149 = 0, + $15 = 0, + $150 = 0, + $151 = 0; + var $152 = 0, + $153 = 0, + $154 = 0, + $155 = 0, + $156 = 0, + $157 = 0, + $158 = 0, + $159 = 0, + $16 = 0, + $160 = 0, + $161 = 0, + $162 = 0, + $163 = 0, + $164 = 0, + $165 = 0, + $166 = 0, + $167 = 0, + $168 = 0, + $169 = 0, + $17 = 0; + var $170 = 0, + $171 = 0, + $172 = 0, + $173 = 0, + $174 = 0, + $175 = 0, + $176 = 0, + $177 = 0, + $178 = 0, + $179 = 0, + $18 = 0, + $180 = 0, + $181 = 0, + $182 = 0, + $183 = 0, + $184 = 0, + $185 = 0, + $186 = 0, + $187 = 0, + $188 = 0; + var $189 = 0, + $19 = 0, + $190 = 0, + $191 = 0, + $192 = 0, + $193 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0, + $27 = 0, + $28 = 0, + $29 = 0, + $3 = 0, + $30 = 0, + $31 = 0; + var $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $37 = 0, + $38 = 0, + $39 = 0, + $4 = 0, + $40 = 0, + $41 = 0, + $42 = 0, + $43 = 0, + $44 = 0, + $45 = 0, + $46 = 0, + $47 = 0, + $48 = 0, + $49 = 0, + $5 = 0; + var $50 = 0, + $51 = 0, + $52 = 0, + $53 = 0, + $54 = 0, + $55 = 0, + $56 = 0, + $57 = 0, + $58 = 0, + $59 = 0, + $6 = 0, + $60 = 0, + $61 = 0, + $62 = 0, + $63 = 0, + $64 = 0, + $65 = 0, + $66 = 0, + $67 = 0, + $68 = 0; + var $69 = 0, + $7 = 0, + $70 = 0, + $71 = 0, + $72 = 0, + $73 = 0, + $74 = 0, + $75 = 0, + $76 = 0, + $77 = 0, + $78 = 0, + $79 = 0, + $8 = 0, + $80 = 0, + $81 = 0, + $82 = 0, + $83 = 0, + $84 = 0, + $85 = 0, + $86 = 0; + var $87 = 0, + $88 = 0, + $89 = 0, + $9 = 0, + $90 = 0, + $91 = 0, + $92 = 0, + $93 = 0, + $94 = 0, + $95 = 0, + $96 = 0, + $97 = 0, + $98 = 0, + $99 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP8[$x >> 0] | 0; + $1 = HEAP8[$y >> 0] | 0; + $2 = $1 ^ $0; + $3 = ($x + 1) | 0; + $4 = HEAP8[$3 >> 0] | 0; + $5 = ($y + 1) | 0; + $6 = HEAP8[$5 >> 0] | 0; + $7 = $6 ^ $4; + $8 = $7 | $2; + $9 = ($x + 2) | 0; + $10 = HEAP8[$9 >> 0] | 0; + $11 = ($y + 2) | 0; + $12 = HEAP8[$11 >> 0] | 0; + $13 = $12 ^ $10; + $14 = $8 | $13; + $15 = ($x + 3) | 0; + $16 = HEAP8[$15 >> 0] | 0; + $17 = ($y + 3) | 0; + $18 = HEAP8[$17 >> 0] | 0; + $19 = $18 ^ $16; + $20 = $14 | $19; + $21 = ($x + 4) | 0; + $22 = HEAP8[$21 >> 0] | 0; + $23 = ($y + 4) | 0; + $24 = HEAP8[$23 >> 0] | 0; + $25 = $24 ^ $22; + $26 = $20 | $25; + $27 = ($x + 5) | 0; + $28 = HEAP8[$27 >> 0] | 0; + $29 = ($y + 5) | 0; + $30 = HEAP8[$29 >> 0] | 0; + $31 = $30 ^ $28; + $32 = $26 | $31; + $33 = ($x + 6) | 0; + $34 = HEAP8[$33 >> 0] | 0; + $35 = ($y + 6) | 0; + $36 = HEAP8[$35 >> 0] | 0; + $37 = $36 ^ $34; + $38 = $32 | $37; + $39 = ($x + 7) | 0; + $40 = HEAP8[$39 >> 0] | 0; + $41 = ($y + 7) | 0; + $42 = HEAP8[$41 >> 0] | 0; + $43 = $42 ^ $40; + $44 = $38 | $43; + $45 = ($x + 8) | 0; + $46 = HEAP8[$45 >> 0] | 0; + $47 = ($y + 8) | 0; + $48 = HEAP8[$47 >> 0] | 0; + $49 = $48 ^ $46; + $50 = $44 | $49; + $51 = ($x + 9) | 0; + $52 = HEAP8[$51 >> 0] | 0; + $53 = ($y + 9) | 0; + $54 = HEAP8[$53 >> 0] | 0; + $55 = $54 ^ $52; + $56 = $50 | $55; + $57 = ($x + 10) | 0; + $58 = HEAP8[$57 >> 0] | 0; + $59 = ($y + 10) | 0; + $60 = HEAP8[$59 >> 0] | 0; + $61 = $60 ^ $58; + $62 = $56 | $61; + $63 = ($x + 11) | 0; + $64 = HEAP8[$63 >> 0] | 0; + $65 = ($y + 11) | 0; + $66 = HEAP8[$65 >> 0] | 0; + $67 = $66 ^ $64; + $68 = $62 | $67; + $69 = ($x + 12) | 0; + $70 = HEAP8[$69 >> 0] | 0; + $71 = ($y + 12) | 0; + $72 = HEAP8[$71 >> 0] | 0; + $73 = $72 ^ $70; + $74 = $68 | $73; + $75 = ($x + 13) | 0; + $76 = HEAP8[$75 >> 0] | 0; + $77 = ($y + 13) | 0; + $78 = HEAP8[$77 >> 0] | 0; + $79 = $78 ^ $76; + $80 = $74 | $79; + $81 = ($x + 14) | 0; + $82 = HEAP8[$81 >> 0] | 0; + $83 = ($y + 14) | 0; + $84 = HEAP8[$83 >> 0] | 0; + $85 = $84 ^ $82; + $86 = $80 | $85; + $87 = ($x + 15) | 0; + $88 = HEAP8[$87 >> 0] | 0; + $89 = ($y + 15) | 0; + $90 = HEAP8[$89 >> 0] | 0; + $91 = $90 ^ $88; + $92 = $86 | $91; + $93 = ($x + 16) | 0; + $94 = HEAP8[$93 >> 0] | 0; + $95 = ($y + 16) | 0; + $96 = HEAP8[$95 >> 0] | 0; + $97 = $96 ^ $94; + $98 = $92 | $97; + $99 = ($x + 17) | 0; + $100 = HEAP8[$99 >> 0] | 0; + $101 = ($y + 17) | 0; + $102 = HEAP8[$101 >> 0] | 0; + $103 = $102 ^ $100; + $104 = $98 | $103; + $105 = ($x + 18) | 0; + $106 = HEAP8[$105 >> 0] | 0; + $107 = ($y + 18) | 0; + $108 = HEAP8[$107 >> 0] | 0; + $109 = $108 ^ $106; + $110 = $104 | $109; + $111 = ($x + 19) | 0; + $112 = HEAP8[$111 >> 0] | 0; + $113 = ($y + 19) | 0; + $114 = HEAP8[$113 >> 0] | 0; + $115 = $114 ^ $112; + $116 = $110 | $115; + $117 = ($x + 20) | 0; + $118 = HEAP8[$117 >> 0] | 0; + $119 = ($y + 20) | 0; + $120 = HEAP8[$119 >> 0] | 0; + $121 = $120 ^ $118; + $122 = $116 | $121; + $123 = ($x + 21) | 0; + $124 = HEAP8[$123 >> 0] | 0; + $125 = ($y + 21) | 0; + $126 = HEAP8[$125 >> 0] | 0; + $127 = $126 ^ $124; + $128 = $122 | $127; + $129 = ($x + 22) | 0; + $130 = HEAP8[$129 >> 0] | 0; + $131 = ($y + 22) | 0; + $132 = HEAP8[$131 >> 0] | 0; + $133 = $132 ^ $130; + $134 = $128 | $133; + $135 = ($x + 23) | 0; + $136 = HEAP8[$135 >> 0] | 0; + $137 = ($y + 23) | 0; + $138 = HEAP8[$137 >> 0] | 0; + $139 = $138 ^ $136; + $140 = $134 | $139; + $141 = ($x + 24) | 0; + $142 = HEAP8[$141 >> 0] | 0; + $143 = ($y + 24) | 0; + $144 = HEAP8[$143 >> 0] | 0; + $145 = $144 ^ $142; + $146 = $140 | $145; + $147 = ($x + 25) | 0; + $148 = HEAP8[$147 >> 0] | 0; + $149 = ($y + 25) | 0; + $150 = HEAP8[$149 >> 0] | 0; + $151 = $150 ^ $148; + $152 = $146 | $151; + $153 = ($x + 26) | 0; + $154 = HEAP8[$153 >> 0] | 0; + $155 = ($y + 26) | 0; + $156 = HEAP8[$155 >> 0] | 0; + $157 = $156 ^ $154; + $158 = $152 | $157; + $159 = ($x + 27) | 0; + $160 = HEAP8[$159 >> 0] | 0; + $161 = ($y + 27) | 0; + $162 = HEAP8[$161 >> 0] | 0; + $163 = $162 ^ $160; + $164 = $158 | $163; + $165 = ($x + 28) | 0; + $166 = HEAP8[$165 >> 0] | 0; + $167 = ($y + 28) | 0; + $168 = HEAP8[$167 >> 0] | 0; + $169 = $168 ^ $166; + $170 = $164 | $169; + $171 = ($x + 29) | 0; + $172 = HEAP8[$171 >> 0] | 0; + $173 = ($y + 29) | 0; + $174 = HEAP8[$173 >> 0] | 0; + $175 = $174 ^ $172; + $176 = $170 | $175; + $177 = ($x + 30) | 0; + $178 = HEAP8[$177 >> 0] | 0; + $179 = ($y + 30) | 0; + $180 = HEAP8[$179 >> 0] | 0; + $181 = $180 ^ $178; + $182 = $176 | $181; + $183 = ($x + 31) | 0; + $184 = HEAP8[$183 >> 0] | 0; + $185 = ($y + 31) | 0; + $186 = HEAP8[$185 >> 0] | 0; + $187 = $186 ^ $184; + $188 = $182 | $187; + $189 = $188 & 255; + $190 = ($189 + 511) | 0; + $191 = $190 >>> 8; + $192 = $191 & 1; + $193 = ($192 + -1) | 0; + STACKTOP = sp; + return $193 | 0; + } + function _curve25519_sign( + $signature_out, + $curve25519_privkey, + $msg, + $msg_len + ) { + $signature_out = $signature_out | 0; + $curve25519_privkey = $curve25519_privkey | 0; + $msg = $msg | 0; + $msg_len = $msg_len | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + $ed_keypair = 0, + $ed_pubkey_point = 0, + $sigbuf_out_len = 0, + dest = 0; + var label = 0, + sp = 0, + src = 0, + stop = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 240) | 0; + $ed_pubkey_point = (sp + 8) | 0; + $ed_keypair = (sp + 168) | 0; + $sigbuf_out_len = sp; + $0 = ($msg_len + 64) | 0; + $1 = STACKTOP; + STACKTOP = (STACKTOP + ((((1 * $0) | 0) + 15) & -16)) | 0; + $2 = $sigbuf_out_len; + $3 = $2; + HEAP32[$3 >> 2] = 0; + $4 = ($2 + 4) | 0; + $5 = $4; + HEAP32[$5 >> 2] = 0; + dest = ($ed_keypair + 0) | 0; + src = ($curve25519_privkey + 0) | 0; + stop = (dest + 32) | 0; + do { + HEAP8[dest >> 0] = HEAP8[src >> 0] | 0; + dest = (dest + 1) | 0; + src = (src + 1) | 0; + } while ((dest | 0) < (stop | 0)); + _crypto_sign_ed25519_ref10_ge_scalarmult_base( + $ed_pubkey_point, + $curve25519_privkey + ); + $6 = ($ed_keypair + 32) | 0; + _crypto_sign_ed25519_ref10_ge_p3_tobytes($6, $ed_pubkey_point); + $7 = ($ed_keypair + 63) | 0; + $8 = HEAP8[$7 >> 0] | 0; + $9 = $8 & 255; + $10 = $9 & 128; + _crypto_sign_modified($1, $sigbuf_out_len, $msg, $msg_len, 0, $ed_keypair) | + 0; + dest = ($signature_out + 0) | 0; + src = ($1 + 0) | 0; + stop = (dest + 64) | 0; + do { + HEAP8[dest >> 0] = HEAP8[src >> 0] | 0; + dest = (dest + 1) | 0; + src = (src + 1) | 0; + } while ((dest | 0) < (stop | 0)); + $11 = ($signature_out + 63) | 0; + $12 = HEAP8[$11 >> 0] | 0; + $13 = $12 & 255; + $14 = $13 | $10; + $15 = $14 & 255; + HEAP8[$11 >> 0] = $15; + STACKTOP = sp; + return; + } + function _curve25519_verify($signature, $curve25519_pubkey, $msg, $msg_len) { + $signature = $signature | 0; + $curve25519_pubkey = $curve25519_pubkey | 0; + $msg = $msg | 0; + $msg_len = $msg_len | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + $ed_pubkey = 0, + $ed_y = 0; + var $inv_mont_x_plus_one = 0, + $mont_x = 0, + $mont_x_minus_one = 0, + $mont_x_plus_one = 0, + $one = 0, + $some_retval = 0, + dest = 0, + label = 0, + sp = 0, + src = 0, + stop = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 288) | 0; + $mont_x = (sp + 208) | 0; + $mont_x_minus_one = (sp + 168) | 0; + $mont_x_plus_one = (sp + 128) | 0; + $inv_mont_x_plus_one = (sp + 88) | 0; + $one = (sp + 48) | 0; + $ed_y = (sp + 8) | 0; + $ed_pubkey = (sp + 248) | 0; + $some_retval = sp; + $0 = ($msg_len + 64) | 0; + $1 = STACKTOP; + STACKTOP = (STACKTOP + ((((1 * $0) | 0) + 15) & -16)) | 0; + $2 = STACKTOP; + STACKTOP = (STACKTOP + ((((1 * $0) | 0) + 15) & -16)) | 0; + _crypto_sign_ed25519_ref10_fe_frombytes($mont_x, $curve25519_pubkey); + _crypto_sign_ed25519_ref10_fe_1($one); + _crypto_sign_ed25519_ref10_fe_sub($mont_x_minus_one, $mont_x, $one); + _crypto_sign_ed25519_ref10_fe_add($mont_x_plus_one, $mont_x, $one); + _crypto_sign_ed25519_ref10_fe_invert( + $inv_mont_x_plus_one, + $mont_x_plus_one + ); + _crypto_sign_ed25519_ref10_fe_mul( + $ed_y, + $mont_x_minus_one, + $inv_mont_x_plus_one + ); + _crypto_sign_ed25519_ref10_fe_tobytes($ed_pubkey, $ed_y); + $3 = ($signature + 63) | 0; + $4 = HEAP8[$3 >> 0] | 0; + $5 = $4 & 255; + $6 = $5 & 128; + $7 = ($ed_pubkey + 31) | 0; + $8 = HEAP8[$7 >> 0] | 0; + $9 = $8 & 255; + $10 = $9 | $6; + $11 = $10 & 255; + HEAP8[$7 >> 0] = $11; + $12 = HEAP8[$3 >> 0] | 0; + $13 = $12 & 255; + $14 = $13 & 127; + $15 = $14 & 255; + HEAP8[$3 >> 0] = $15; + dest = ($1 + 0) | 0; + src = ($signature + 0) | 0; + stop = (dest + 64) | 0; + do { + HEAP8[dest >> 0] = HEAP8[src >> 0] | 0; + dest = (dest + 1) | 0; + src = (src + 1) | 0; + } while ((dest | 0) < (stop | 0)); + $16 = ($1 + 64) | 0; + _memcpy($16 | 0, $msg | 0, $msg_len | 0) | 0; + $17 = + _crypto_sign_edwards25519sha512batch_ref10_open( + $2, + $some_retval, + $1, + $0, + 0, + $ed_pubkey + ) | 0; + STACKTOP = sp; + return $17 | 0; + } + function _crypto_hash_sha512_ref($output, $input, $0, $1) { + $output = $output | 0; + $input = $input | 0; + $0 = $0 | 0; + $1 = $1 | 0; + var $ctx = 0, + label = 0, + sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 208) | 0; + $ctx = sp; + _sph_sha512_init($ctx); + _sph_sha384($ctx, $input, $0); + _sph_sha512_close($ctx, $output); + STACKTOP = sp; + return 0; + } + function _crypto_sign_modified($sm, $smlen, $m, $0, $1, $sk) { + $sm = $sm | 0; + $smlen = $smlen | 0; + $m = $m | 0; + $0 = $0 | 0; + $1 = $1 | 0; + $sk = $sk | 0; + var $10 = 0, + $11 = 0, + $12 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + $R = 0, + $hram = 0, + $nonce = 0, + $pk1 = 0, + dest = 0, + label = 0, + sp = 0, + src = 0, + stop = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 320) | 0; + $pk1 = (sp + 288) | 0; + $nonce = (sp + 224) | 0; + $hram = (sp + 160) | 0; + $R = sp; + $2 = ($sk + 32) | 0; + dest = ($pk1 + 0) | 0; + src = ($2 + 0) | 0; + stop = (dest + 32) | 0; + do { + HEAP8[dest >> 0] = HEAP8[src >> 0] | 0; + dest = (dest + 1) | 0; + src = (src + 1) | 0; + } while ((dest | 0) < (stop | 0)); + $3 = _i64Add($0 | 0, $1 | 0, 64, 0) | 0; + $4 = tempRet0; + $5 = $smlen; + $6 = $5; + HEAP32[$6 >> 2] = $3; + $7 = ($5 + 4) | 0; + $8 = $7; + HEAP32[$8 >> 2] = $4; + $9 = ($sm + 64) | 0; + _memmove($9 | 0, $m | 0, $0 | 0) | 0; + $10 = ($sm + 32) | 0; + _memmove($10 | 0, $sk | 0, 32) | 0; + $11 = _i64Add($0 | 0, $1 | 0, 32, 0) | 0; + $12 = tempRet0; + _crypto_hash_sha512_ref($nonce, $10, $11, $12) | 0; + dest = ($10 + 0) | 0; + src = ($pk1 + 0) | 0; + stop = (dest + 32) | 0; + do { + HEAP8[dest >> 0] = HEAP8[src >> 0] | 0; + dest = (dest + 1) | 0; + src = (src + 1) | 0; + } while ((dest | 0) < (stop | 0)); + _crypto_sign_ed25519_ref10_sc_reduce($nonce); + _crypto_sign_ed25519_ref10_ge_scalarmult_base($R, $nonce); + _crypto_sign_ed25519_ref10_ge_p3_tobytes($sm, $R); + _crypto_hash_sha512_ref($hram, $sm, $3, $4) | 0; + _crypto_sign_ed25519_ref10_sc_reduce($hram); + _crypto_sign_ed25519_ref10_sc_muladd($10, $hram, $sk, $nonce); + STACKTOP = sp; + return 0; + } + function _curve25519_donna($mypublic, $secret, $basepoint) { + $mypublic = $mypublic | 0; + $secret = $secret | 0; + $basepoint = $basepoint | 0; + var $bp = 0, + $e = 0, + $x = 0, + $z = 0, + $zmone = 0, + dest = 0, + label = 0, + sp = 0, + src = 0, + stop = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 368) | 0; + $bp = (sp + 248) | 0; + $x = (sp + 168) | 0; + $z = (sp + 80) | 0; + $zmone = sp; + $e = (sp + 328) | 0; + dest = ($e + 0) | 0; + src = ($secret + 0) | 0; + stop = (dest + 32) | 0; + do { + HEAP8[dest >> 0] = HEAP8[src >> 0] | 0; + dest = (dest + 1) | 0; + src = (src + 1) | 0; + } while ((dest | 0) < (stop | 0)); + _fexpand($bp, $basepoint); + _cmult($x, $z, $e, $bp); + _crecip($zmone, $z); + _fmul($z, $x, $zmone); + _fcontract($mypublic, $z); + STACKTOP = sp; + return 0; + } + function _fexpand($output, $input) { + $output = $output | 0; + $input = $input | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $101 = 0, + $102 = 0, + $103 = 0, + $104 = 0, + $105 = 0, + $106 = 0, + $107 = 0, + $108 = 0, + $109 = 0, + $11 = 0, + $110 = 0, + $111 = 0, + $112 = 0, + $113 = 0, + $114 = 0, + $115 = 0; + var $116 = 0, + $117 = 0, + $118 = 0, + $119 = 0, + $12 = 0, + $120 = 0, + $121 = 0, + $122 = 0, + $123 = 0, + $124 = 0, + $125 = 0, + $126 = 0, + $127 = 0, + $128 = 0, + $129 = 0, + $13 = 0, + $130 = 0, + $131 = 0, + $132 = 0, + $133 = 0; + var $134 = 0, + $135 = 0, + $136 = 0, + $137 = 0, + $138 = 0, + $139 = 0, + $14 = 0, + $140 = 0, + $141 = 0, + $142 = 0, + $143 = 0, + $144 = 0, + $145 = 0, + $146 = 0, + $147 = 0, + $148 = 0, + $149 = 0, + $15 = 0, + $150 = 0, + $151 = 0; + var $152 = 0, + $153 = 0, + $154 = 0, + $155 = 0, + $156 = 0, + $157 = 0, + $158 = 0, + $159 = 0, + $16 = 0, + $160 = 0, + $161 = 0, + $162 = 0, + $163 = 0, + $164 = 0, + $165 = 0, + $166 = 0, + $167 = 0, + $168 = 0, + $169 = 0, + $17 = 0; + var $170 = 0, + $171 = 0, + $172 = 0, + $173 = 0, + $174 = 0, + $175 = 0, + $176 = 0, + $177 = 0, + $178 = 0, + $179 = 0, + $18 = 0, + $180 = 0, + $181 = 0, + $182 = 0, + $183 = 0, + $184 = 0, + $185 = 0, + $186 = 0, + $187 = 0, + $188 = 0; + var $189 = 0, + $19 = 0, + $190 = 0, + $191 = 0, + $192 = 0, + $193 = 0, + $194 = 0, + $195 = 0, + $196 = 0, + $197 = 0, + $198 = 0, + $199 = 0, + $2 = 0, + $20 = 0, + $200 = 0, + $201 = 0, + $202 = 0, + $203 = 0, + $204 = 0, + $205 = 0; + var $206 = 0, + $207 = 0, + $208 = 0, + $209 = 0, + $21 = 0, + $210 = 0, + $211 = 0, + $212 = 0, + $213 = 0, + $214 = 0, + $215 = 0, + $216 = 0, + $217 = 0, + $218 = 0, + $219 = 0, + $22 = 0, + $220 = 0, + $221 = 0, + $222 = 0, + $223 = 0; + var $224 = 0, + $225 = 0, + $226 = 0, + $227 = 0, + $228 = 0, + $229 = 0, + $23 = 0, + $230 = 0, + $231 = 0, + $232 = 0, + $233 = 0, + $234 = 0, + $235 = 0, + $236 = 0, + $237 = 0, + $238 = 0, + $239 = 0, + $24 = 0, + $240 = 0, + $241 = 0; + var $242 = 0, + $243 = 0, + $244 = 0, + $245 = 0, + $246 = 0, + $247 = 0, + $248 = 0, + $249 = 0, + $25 = 0, + $250 = 0, + $251 = 0, + $252 = 0, + $253 = 0, + $254 = 0, + $255 = 0, + $256 = 0, + $257 = 0, + $258 = 0, + $259 = 0, + $26 = 0; + var $260 = 0, + $261 = 0, + $262 = 0, + $263 = 0, + $264 = 0, + $265 = 0, + $266 = 0, + $267 = 0, + $268 = 0, + $269 = 0, + $27 = 0, + $270 = 0, + $271 = 0, + $272 = 0, + $273 = 0, + $274 = 0, + $275 = 0, + $276 = 0, + $277 = 0, + $278 = 0; + var $279 = 0, + $28 = 0, + $280 = 0, + $281 = 0, + $282 = 0, + $283 = 0, + $284 = 0, + $285 = 0, + $286 = 0, + $287 = 0, + $288 = 0, + $289 = 0, + $29 = 0, + $290 = 0, + $291 = 0, + $292 = 0, + $293 = 0, + $3 = 0, + $30 = 0, + $31 = 0; + var $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $37 = 0, + $38 = 0, + $39 = 0, + $4 = 0, + $40 = 0, + $41 = 0, + $42 = 0, + $43 = 0, + $44 = 0, + $45 = 0, + $46 = 0, + $47 = 0, + $48 = 0, + $49 = 0, + $5 = 0; + var $50 = 0, + $51 = 0, + $52 = 0, + $53 = 0, + $54 = 0, + $55 = 0, + $56 = 0, + $57 = 0, + $58 = 0, + $59 = 0, + $6 = 0, + $60 = 0, + $61 = 0, + $62 = 0, + $63 = 0, + $64 = 0, + $65 = 0, + $66 = 0, + $67 = 0, + $68 = 0; + var $69 = 0, + $7 = 0, + $70 = 0, + $71 = 0, + $72 = 0, + $73 = 0, + $74 = 0, + $75 = 0, + $76 = 0, + $77 = 0, + $78 = 0, + $79 = 0, + $8 = 0, + $80 = 0, + $81 = 0, + $82 = 0, + $83 = 0, + $84 = 0, + $85 = 0, + $86 = 0; + var $87 = 0, + $88 = 0, + $89 = 0, + $9 = 0, + $90 = 0, + $91 = 0, + $92 = 0, + $93 = 0, + $94 = 0, + $95 = 0, + $96 = 0, + $97 = 0, + $98 = 0, + $99 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP8[$input >> 0] | 0; + $1 = $0 & 255; + $2 = ($input + 1) | 0; + $3 = HEAP8[$2 >> 0] | 0; + $4 = $3 & 255; + $5 = _bitshift64Shl($4 | 0, 0, 8) | 0; + $6 = tempRet0; + $7 = $5 | $1; + $8 = ($input + 2) | 0; + $9 = HEAP8[$8 >> 0] | 0; + $10 = $9 & 255; + $11 = _bitshift64Shl($10 | 0, 0, 16) | 0; + $12 = tempRet0; + $13 = $7 | $11; + $14 = $6 | $12; + $15 = ($input + 3) | 0; + $16 = HEAP8[$15 >> 0] | 0; + $17 = $16 & 255; + $18 = _bitshift64Shl($17 | 0, 0, 24) | 0; + $19 = tempRet0; + $20 = $18 & 50331648; + $21 = $13 | $20; + $22 = $output; + $23 = $22; + HEAP32[$23 >> 2] = $21; + $24 = ($22 + 4) | 0; + $25 = $24; + HEAP32[$25 >> 2] = $14; + $26 = HEAP8[$15 >> 0] | 0; + $27 = $26 & 255; + $28 = ($input + 4) | 0; + $29 = HEAP8[$28 >> 0] | 0; + $30 = $29 & 255; + $31 = _bitshift64Shl($30 | 0, 0, 8) | 0; + $32 = tempRet0; + $33 = $31 | $27; + $34 = ($input + 5) | 0; + $35 = HEAP8[$34 >> 0] | 0; + $36 = $35 & 255; + $37 = _bitshift64Shl($36 | 0, 0, 16) | 0; + $38 = tempRet0; + $39 = $33 | $37; + $40 = $32 | $38; + $41 = ($input + 6) | 0; + $42 = HEAP8[$41 >> 0] | 0; + $43 = $42 & 255; + $44 = _bitshift64Shl($43 | 0, 0, 24) | 0; + $45 = tempRet0; + $46 = $39 | $44; + $47 = $40 | $45; + $48 = _bitshift64Lshr($46 | 0, $47 | 0, 2) | 0; + $49 = tempRet0; + $50 = $48 & 33554431; + $51 = ($output + 8) | 0; + $52 = $51; + $53 = $52; + HEAP32[$53 >> 2] = $50; + $54 = ($52 + 4) | 0; + $55 = $54; + HEAP32[$55 >> 2] = 0; + $56 = HEAP8[$41 >> 0] | 0; + $57 = $56 & 255; + $58 = ($input + 7) | 0; + $59 = HEAP8[$58 >> 0] | 0; + $60 = $59 & 255; + $61 = _bitshift64Shl($60 | 0, 0, 8) | 0; + $62 = tempRet0; + $63 = $61 | $57; + $64 = ($input + 8) | 0; + $65 = HEAP8[$64 >> 0] | 0; + $66 = $65 & 255; + $67 = _bitshift64Shl($66 | 0, 0, 16) | 0; + $68 = tempRet0; + $69 = $63 | $67; + $70 = $62 | $68; + $71 = ($input + 9) | 0; + $72 = HEAP8[$71 >> 0] | 0; + $73 = $72 & 255; + $74 = _bitshift64Shl($73 | 0, 0, 24) | 0; + $75 = tempRet0; + $76 = $69 | $74; + $77 = $70 | $75; + $78 = _bitshift64Lshr($76 | 0, $77 | 0, 3) | 0; + $79 = tempRet0; + $80 = $78 & 67108863; + $81 = ($output + 16) | 0; + $82 = $81; + $83 = $82; + HEAP32[$83 >> 2] = $80; + $84 = ($82 + 4) | 0; + $85 = $84; + HEAP32[$85 >> 2] = 0; + $86 = HEAP8[$71 >> 0] | 0; + $87 = $86 & 255; + $88 = ($input + 10) | 0; + $89 = HEAP8[$88 >> 0] | 0; + $90 = $89 & 255; + $91 = _bitshift64Shl($90 | 0, 0, 8) | 0; + $92 = tempRet0; + $93 = $91 | $87; + $94 = ($input + 11) | 0; + $95 = HEAP8[$94 >> 0] | 0; + $96 = $95 & 255; + $97 = _bitshift64Shl($96 | 0, 0, 16) | 0; + $98 = tempRet0; + $99 = $93 | $97; + $100 = $92 | $98; + $101 = ($input + 12) | 0; + $102 = HEAP8[$101 >> 0] | 0; + $103 = $102 & 255; + $104 = _bitshift64Shl($103 | 0, 0, 24) | 0; + $105 = tempRet0; + $106 = $99 | $104; + $107 = $100 | $105; + $108 = _bitshift64Lshr($106 | 0, $107 | 0, 5) | 0; + $109 = tempRet0; + $110 = $108 & 33554431; + $111 = ($output + 24) | 0; + $112 = $111; + $113 = $112; + HEAP32[$113 >> 2] = $110; + $114 = ($112 + 4) | 0; + $115 = $114; + HEAP32[$115 >> 2] = 0; + $116 = HEAP8[$101 >> 0] | 0; + $117 = $116 & 255; + $118 = ($input + 13) | 0; + $119 = HEAP8[$118 >> 0] | 0; + $120 = $119 & 255; + $121 = _bitshift64Shl($120 | 0, 0, 8) | 0; + $122 = tempRet0; + $123 = $121 | $117; + $124 = ($input + 14) | 0; + $125 = HEAP8[$124 >> 0] | 0; + $126 = $125 & 255; + $127 = _bitshift64Shl($126 | 0, 0, 16) | 0; + $128 = tempRet0; + $129 = $123 | $127; + $130 = $122 | $128; + $131 = ($input + 15) | 0; + $132 = HEAP8[$131 >> 0] | 0; + $133 = $132 & 255; + $134 = _bitshift64Shl($133 | 0, 0, 24) | 0; + $135 = tempRet0; + $136 = $129 | $134; + $137 = $130 | $135; + $138 = _bitshift64Lshr($136 | 0, $137 | 0, 6) | 0; + $139 = tempRet0; + $140 = $138 & 67108863; + $141 = ($output + 32) | 0; + $142 = $141; + $143 = $142; + HEAP32[$143 >> 2] = $140; + $144 = ($142 + 4) | 0; + $145 = $144; + HEAP32[$145 >> 2] = 0; + $146 = ($input + 16) | 0; + $147 = HEAP8[$146 >> 0] | 0; + $148 = $147 & 255; + $149 = ($input + 17) | 0; + $150 = HEAP8[$149 >> 0] | 0; + $151 = $150 & 255; + $152 = _bitshift64Shl($151 | 0, 0, 8) | 0; + $153 = tempRet0; + $154 = $152 | $148; + $155 = ($input + 18) | 0; + $156 = HEAP8[$155 >> 0] | 0; + $157 = $156 & 255; + $158 = _bitshift64Shl($157 | 0, 0, 16) | 0; + $159 = tempRet0; + $160 = $154 | $158; + $161 = $153 | $159; + $162 = ($input + 19) | 0; + $163 = HEAP8[$162 >> 0] | 0; + $164 = $163 & 255; + $165 = _bitshift64Shl($164 | 0, 0, 24) | 0; + $166 = tempRet0; + $167 = $165 & 16777216; + $168 = $160 | $167; + $169 = ($output + 40) | 0; + $170 = $169; + $171 = $170; + HEAP32[$171 >> 2] = $168; + $172 = ($170 + 4) | 0; + $173 = $172; + HEAP32[$173 >> 2] = $161; + $174 = HEAP8[$162 >> 0] | 0; + $175 = $174 & 255; + $176 = ($input + 20) | 0; + $177 = HEAP8[$176 >> 0] | 0; + $178 = $177 & 255; + $179 = _bitshift64Shl($178 | 0, 0, 8) | 0; + $180 = tempRet0; + $181 = $179 | $175; + $182 = ($input + 21) | 0; + $183 = HEAP8[$182 >> 0] | 0; + $184 = $183 & 255; + $185 = _bitshift64Shl($184 | 0, 0, 16) | 0; + $186 = tempRet0; + $187 = $181 | $185; + $188 = $180 | $186; + $189 = ($input + 22) | 0; + $190 = HEAP8[$189 >> 0] | 0; + $191 = $190 & 255; + $192 = _bitshift64Shl($191 | 0, 0, 24) | 0; + $193 = tempRet0; + $194 = $187 | $192; + $195 = $188 | $193; + $196 = _bitshift64Lshr($194 | 0, $195 | 0, 1) | 0; + $197 = tempRet0; + $198 = $196 & 67108863; + $199 = ($output + 48) | 0; + $200 = $199; + $201 = $200; + HEAP32[$201 >> 2] = $198; + $202 = ($200 + 4) | 0; + $203 = $202; + HEAP32[$203 >> 2] = 0; + $204 = HEAP8[$189 >> 0] | 0; + $205 = $204 & 255; + $206 = ($input + 23) | 0; + $207 = HEAP8[$206 >> 0] | 0; + $208 = $207 & 255; + $209 = _bitshift64Shl($208 | 0, 0, 8) | 0; + $210 = tempRet0; + $211 = $209 | $205; + $212 = ($input + 24) | 0; + $213 = HEAP8[$212 >> 0] | 0; + $214 = $213 & 255; + $215 = _bitshift64Shl($214 | 0, 0, 16) | 0; + $216 = tempRet0; + $217 = $211 | $215; + $218 = $210 | $216; + $219 = ($input + 25) | 0; + $220 = HEAP8[$219 >> 0] | 0; + $221 = $220 & 255; + $222 = _bitshift64Shl($221 | 0, 0, 24) | 0; + $223 = tempRet0; + $224 = $217 | $222; + $225 = $218 | $223; + $226 = _bitshift64Lshr($224 | 0, $225 | 0, 3) | 0; + $227 = tempRet0; + $228 = $226 & 33554431; + $229 = ($output + 56) | 0; + $230 = $229; + $231 = $230; + HEAP32[$231 >> 2] = $228; + $232 = ($230 + 4) | 0; + $233 = $232; + HEAP32[$233 >> 2] = 0; + $234 = HEAP8[$219 >> 0] | 0; + $235 = $234 & 255; + $236 = ($input + 26) | 0; + $237 = HEAP8[$236 >> 0] | 0; + $238 = $237 & 255; + $239 = _bitshift64Shl($238 | 0, 0, 8) | 0; + $240 = tempRet0; + $241 = $239 | $235; + $242 = ($input + 27) | 0; + $243 = HEAP8[$242 >> 0] | 0; + $244 = $243 & 255; + $245 = _bitshift64Shl($244 | 0, 0, 16) | 0; + $246 = tempRet0; + $247 = $241 | $245; + $248 = $240 | $246; + $249 = ($input + 28) | 0; + $250 = HEAP8[$249 >> 0] | 0; + $251 = $250 & 255; + $252 = _bitshift64Shl($251 | 0, 0, 24) | 0; + $253 = tempRet0; + $254 = $247 | $252; + $255 = $248 | $253; + $256 = _bitshift64Lshr($254 | 0, $255 | 0, 4) | 0; + $257 = tempRet0; + $258 = $256 & 67108863; + $259 = ($output + 64) | 0; + $260 = $259; + $261 = $260; + HEAP32[$261 >> 2] = $258; + $262 = ($260 + 4) | 0; + $263 = $262; + HEAP32[$263 >> 2] = 0; + $264 = HEAP8[$249 >> 0] | 0; + $265 = $264 & 255; + $266 = ($input + 29) | 0; + $267 = HEAP8[$266 >> 0] | 0; + $268 = $267 & 255; + $269 = _bitshift64Shl($268 | 0, 0, 8) | 0; + $270 = tempRet0; + $271 = $269 | $265; + $272 = ($input + 30) | 0; + $273 = HEAP8[$272 >> 0] | 0; + $274 = $273 & 255; + $275 = _bitshift64Shl($274 | 0, 0, 16) | 0; + $276 = tempRet0; + $277 = $271 | $275; + $278 = $270 | $276; + $279 = ($input + 31) | 0; + $280 = HEAP8[$279 >> 0] | 0; + $281 = $280 & 255; + $282 = _bitshift64Shl($281 | 0, 0, 24) | 0; + $283 = tempRet0; + $284 = $277 | $282; + $285 = $278 | $283; + $286 = _bitshift64Lshr($284 | 0, $285 | 0, 6) | 0; + $287 = tempRet0; + $288 = $286 & 33554431; + $289 = ($output + 72) | 0; + $290 = $289; + $291 = $290; + HEAP32[$291 >> 2] = $288; + $292 = ($290 + 4) | 0; + $293 = $292; + HEAP32[$293 >> 2] = 0; + STACKTOP = sp; + return; + } + function _cmult($resultx, $resultz, $n, $q) { + $resultx = $resultx | 0; + $resultz = $resultz | 0; + $n = $n | 0; + $q = $q | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $3 = 0, + $4 = 0; + var $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + $a = 0, + $b = 0, + $byte$09 = 0, + $c = 0, + $d = 0, + $e = 0, + $exitcond = 0, + $exitcond20 = 0, + $f = 0, + $g = 0, + $h = 0, + $i$018 = 0, + $j$08 = 0, + $nqpqx$019 = 0, + $nqpqx$110 = 0; + var $nqpqx$110$phi = 0, + $nqpqx2$014 = 0, + $nqpqx2$14 = 0, + $nqpqx2$14$phi = 0, + $nqpqz$013 = 0, + $nqpqz$13 = 0, + $nqpqz$13$phi = 0, + $nqpqz2$015 = 0, + $nqpqz2$15 = 0, + $nqpqz2$15$phi = 0, + $nqx$011 = 0, + $nqx$11 = 0, + $nqx$11$phi = 0, + $nqx2$016 = 0, + $nqx2$16 = 0, + $nqx2$16$phi = 0, + $nqz$012 = 0, + $nqz$12 = 0, + $nqz$12$phi = 0, + $nqz2$017 = 0; + var $nqz2$17 = 0, + $nqz2$17$phi = 0, + dest = 0, + label = 0, + sp = 0, + src = 0, + stop = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 1216) | 0; + $a = (sp + 1064) | 0; + $b = (sp + 912) | 0; + $c = (sp + 760) | 0; + $d = (sp + 608) | 0; + $e = (sp + 456) | 0; + $f = (sp + 304) | 0; + $g = (sp + 152) | 0; + $h = sp; + _memset($a | 0, 0, 152) | 0; + _memset($b | 0, 0, 152) | 0; + $0 = $b; + $1 = $0; + HEAP32[$1 >> 2] = 1; + $2 = ($0 + 4) | 0; + $3 = $2; + HEAP32[$3 >> 2] = 0; + _memset($c | 0, 0, 152) | 0; + $4 = $c; + $5 = $4; + HEAP32[$5 >> 2] = 1; + $6 = ($4 + 4) | 0; + $7 = $6; + HEAP32[$7 >> 2] = 0; + _memset($d | 0, 0, 152) | 0; + _memset($e | 0, 0, 152) | 0; + _memset($f | 0, 0, 152) | 0; + $8 = $f; + $9 = $8; + HEAP32[$9 >> 2] = 1; + $10 = ($8 + 4) | 0; + $11 = $10; + HEAP32[$11 >> 2] = 0; + _memset($g | 0, 0, 152) | 0; + _memset($h | 0, 0, 152) | 0; + $12 = $h; + $13 = $12; + HEAP32[$13 >> 2] = 1; + $14 = ($12 + 4) | 0; + $15 = $14; + HEAP32[$15 >> 2] = 0; + dest = ($a + 0) | 0; + src = ($q + 0) | 0; + stop = (dest + 80) | 0; + do { + HEAP32[dest >> 2] = HEAP32[src >> 2] | 0; + dest = (dest + 4) | 0; + src = (src + 4) | 0; + } while ((dest | 0) < (stop | 0)); + $i$018 = 0; + $nqpqx$019 = $a; + $nqpqx2$014 = $e; + $nqpqz$013 = $b; + $nqpqz2$015 = $f; + $nqx$011 = $c; + $nqx2$016 = $g; + $nqz$012 = $d; + $nqz2$017 = $h; + while (1) { + $16 = (31 - $i$018) | 0; + $17 = ($n + $16) | 0; + $18 = HEAP8[$17 >> 0] | 0; + $byte$09 = $18; + $j$08 = 0; + $nqpqx$110 = $nqpqx$019; + $nqpqx2$14 = $nqpqx2$014; + $nqpqz$13 = $nqpqz$013; + $nqpqz2$15 = $nqpqz2$015; + $nqx$11 = $nqx$011; + $nqx2$16 = $nqx2$016; + $nqz$12 = $nqz$012; + $nqz2$17 = $nqz2$017; + while (1) { + $19 = $byte$09 & 255; + $20 = $19 >>> 7; + _swap_conditional($nqx$11, $nqpqx$110, $20, 0); + _swap_conditional($nqz$12, $nqpqz$13, $20, 0); + _fmonty( + $nqx2$16, + $nqz2$17, + $nqpqx2$14, + $nqpqz2$15, + $nqx$11, + $nqz$12, + $nqpqx$110, + $nqpqz$13, + $q + ); + _swap_conditional($nqx2$16, $nqpqx2$14, $20, 0); + _swap_conditional($nqz2$17, $nqpqz2$15, $20, 0); + $21 = $19 << 1; + $22 = $21 & 255; + $23 = ($j$08 + 1) | 0; + $exitcond = ($23 | 0) == 8; + if ($exitcond) { + break; + } else { + $nqz2$17$phi = $nqz$12; + $nqz$12$phi = $nqz2$17; + $nqx2$16$phi = $nqx$11; + $nqx$11$phi = $nqx2$16; + $nqpqz2$15$phi = $nqpqz$13; + $nqpqz$13$phi = $nqpqz2$15; + $nqpqx2$14$phi = $nqpqx$110; + $nqpqx$110$phi = $nqpqx2$14; + $byte$09 = $22; + $j$08 = $23; + $nqz2$17 = $nqz2$17$phi; + $nqz$12 = $nqz$12$phi; + $nqx2$16 = $nqx2$16$phi; + $nqx$11 = $nqx$11$phi; + $nqpqz2$15 = $nqpqz2$15$phi; + $nqpqz$13 = $nqpqz$13$phi; + $nqpqx2$14 = $nqpqx2$14$phi; + $nqpqx$110 = $nqpqx$110$phi; + } + } + $24 = ($i$018 + 1) | 0; + $exitcond20 = ($24 | 0) == 32; + if ($exitcond20) { + break; + } else { + $i$018 = $24; + $nqpqx$019 = $nqpqx2$14; + $nqpqx2$014 = $nqpqx$110; + $nqpqz$013 = $nqpqz2$15; + $nqpqz2$015 = $nqpqz$13; + $nqx$011 = $nqx2$16; + $nqx2$016 = $nqx$11; + $nqz$012 = $nqz2$17; + $nqz2$017 = $nqz$12; + } + } + dest = ($resultx + 0) | 0; + src = ($nqx2$16 + 0) | 0; + stop = (dest + 80) | 0; + do { + HEAP32[dest >> 2] = HEAP32[src >> 2] | 0; + dest = (dest + 4) | 0; + src = (src + 4) | 0; + } while ((dest | 0) < (stop | 0)); + dest = ($resultz + 0) | 0; + src = ($nqz2$17 + 0) | 0; + stop = (dest + 80) | 0; + do { + HEAP32[dest >> 2] = HEAP32[src >> 2] | 0; + dest = (dest + 4) | 0; + src = (src + 4) | 0; + } while ((dest | 0) < (stop | 0)); + STACKTOP = sp; + return; + } + function _crecip($out, $z) { + $out = $out | 0; + $z = $z | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $i$33 = 0, + $i$42 = 0, + $i$51 = 0, + $t0 = 0, + $t1 = 0, + $z11 = 0, + $z2 = 0, + $z2_100_0 = 0, + $z2_10_0 = 0, + $z2_20_0 = 0, + $z2_50_0 = 0, + $z2_5_0 = 0, + $z9 = 0, + label = 0; + var sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 800) | 0; + $z2 = (sp + 720) | 0; + $z9 = (sp + 640) | 0; + $z11 = (sp + 560) | 0; + $z2_5_0 = (sp + 480) | 0; + $z2_10_0 = (sp + 400) | 0; + $z2_20_0 = (sp + 320) | 0; + $z2_50_0 = (sp + 240) | 0; + $z2_100_0 = (sp + 160) | 0; + $t0 = (sp + 80) | 0; + $t1 = sp; + _fsquare($z2, $z); + _fsquare($t1, $z2); + _fsquare($t0, $t1); + _fmul($z9, $t0, $z); + _fmul($z11, $z9, $z2); + _fsquare($t0, $z11); + _fmul($z2_5_0, $t0, $z9); + _fsquare($t0, $z2_5_0); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fmul($z2_10_0, $t0, $z2_5_0); + _fsquare($t0, $z2_10_0); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fmul($z2_20_0, $t1, $z2_10_0); + _fsquare($t0, $z2_20_0); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fmul($t0, $t1, $z2_20_0); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fmul($z2_50_0, $t0, $z2_10_0); + _fsquare($t0, $z2_50_0); + _fsquare($t1, $t0); + $i$33 = 2; + while (1) { + _fsquare($t0, $t1); + _fsquare($t1, $t0); + $0 = ($i$33 + 2) | 0; + $1 = ($0 | 0) < 50; + if ($1) { + $i$33 = $0; + } else { + break; + } + } + _fmul($z2_100_0, $t1, $z2_50_0); + _fsquare($t1, $z2_100_0); + _fsquare($t0, $t1); + $i$42 = 2; + while (1) { + _fsquare($t1, $t0); + _fsquare($t0, $t1); + $2 = ($i$42 + 2) | 0; + $3 = ($2 | 0) < 100; + if ($3) { + $i$42 = $2; + } else { + break; + } + } + _fmul($t1, $t0, $z2_100_0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + $i$51 = 2; + while (1) { + _fsquare($t0, $t1); + _fsquare($t1, $t0); + $4 = ($i$51 + 2) | 0; + $5 = ($4 | 0) < 50; + if ($5) { + $i$51 = $4; + } else { + break; + } + } + _fmul($t0, $t1, $z2_50_0); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fsquare($t0, $t1); + _fsquare($t1, $t0); + _fmul($out, $t1, $z11); + STACKTOP = sp; + return; + } + function _fmul($output, $in, $in2) { + $output = $output | 0; + $in = $in | 0; + $in2 = $in2 | 0; + var $t = 0, + dest = 0, + label = 0, + sp = 0, + src = 0, + stop = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 160) | 0; + $t = sp; + _fproduct($t, $in, $in2); + _freduce_degree($t); + _freduce_coefficients($t); + dest = ($output + 0) | 0; + src = ($t + 0) | 0; + stop = (dest + 80) | 0; + do { + HEAP32[dest >> 2] = HEAP32[src >> 2] | 0; + dest = (dest + 4) | 0; + src = (src + 4) | 0; + } while ((dest | 0) < (stop | 0)); + STACKTOP = sp; + return; + } + function _fcontract($output, $input_limbs) { + $output = $output | 0; + $input_limbs = $input_limbs | 0; + var $$pn = 0, + $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $101 = 0, + $102 = 0, + $103 = 0, + $104 = 0, + $105 = 0, + $106 = 0, + $107 = 0, + $108 = 0, + $109 = 0, + $11 = 0, + $110 = 0, + $111 = 0, + $112 = 0, + $113 = 0, + $114 = 0; + var $115 = 0, + $116 = 0, + $117 = 0, + $118 = 0, + $119 = 0, + $12 = 0, + $120 = 0, + $121 = 0, + $122 = 0, + $123 = 0, + $124 = 0, + $125 = 0, + $126 = 0, + $127 = 0, + $128 = 0, + $129 = 0, + $13 = 0, + $130 = 0, + $131 = 0, + $132 = 0; + var $133 = 0, + $134 = 0, + $135 = 0, + $136 = 0, + $137 = 0, + $138 = 0, + $139 = 0, + $14 = 0, + $140 = 0, + $141 = 0, + $142 = 0, + $143 = 0, + $144 = 0, + $145 = 0, + $146 = 0, + $147 = 0, + $148 = 0, + $149 = 0, + $15 = 0, + $150 = 0; + var $151 = 0, + $152 = 0, + $153 = 0, + $154 = 0, + $155 = 0, + $156 = 0, + $157 = 0, + $158 = 0, + $159 = 0, + $16 = 0, + $160 = 0, + $161 = 0, + $162 = 0, + $163 = 0, + $164 = 0, + $165 = 0, + $166 = 0, + $167 = 0, + $168 = 0, + $169 = 0; + var $17 = 0, + $170 = 0, + $171 = 0, + $172 = 0, + $173 = 0, + $174 = 0, + $175 = 0, + $176 = 0, + $177 = 0, + $178 = 0, + $179 = 0, + $18 = 0, + $180 = 0, + $181 = 0, + $182 = 0, + $183 = 0, + $184 = 0, + $185 = 0, + $186 = 0, + $187 = 0; + var $188 = 0, + $189 = 0, + $19 = 0, + $190 = 0, + $191 = 0, + $192 = 0, + $193 = 0, + $194 = 0, + $195 = 0, + $196 = 0, + $197 = 0, + $198 = 0, + $199 = 0, + $2 = 0, + $20 = 0, + $200 = 0, + $201 = 0, + $202 = 0, + $203 = 0, + $204 = 0; + var $205 = 0, + $206 = 0, + $207 = 0, + $208 = 0, + $209 = 0, + $21 = 0, + $210 = 0, + $211 = 0, + $212 = 0, + $213 = 0, + $214 = 0, + $215 = 0, + $216 = 0, + $217 = 0, + $218 = 0, + $219 = 0, + $22 = 0, + $220 = 0, + $221 = 0, + $222 = 0; + var $223 = 0, + $224 = 0, + $225 = 0, + $226 = 0, + $227 = 0, + $228 = 0, + $229 = 0, + $23 = 0, + $230 = 0, + $231 = 0, + $232 = 0, + $233 = 0, + $234 = 0, + $235 = 0, + $236 = 0, + $237 = 0, + $238 = 0, + $239 = 0, + $24 = 0, + $240 = 0; + var $241 = 0, + $242 = 0, + $243 = 0, + $244 = 0, + $245 = 0, + $246 = 0, + $247 = 0, + $248 = 0, + $249 = 0, + $25 = 0, + $250 = 0, + $251 = 0, + $252 = 0, + $253 = 0, + $254 = 0, + $255 = 0, + $256 = 0, + $257 = 0, + $258 = 0, + $259 = 0; + var $26 = 0, + $260 = 0, + $261 = 0, + $262 = 0, + $263 = 0, + $264 = 0, + $265 = 0, + $266 = 0, + $267 = 0, + $268 = 0, + $269 = 0, + $27 = 0, + $270 = 0, + $271 = 0, + $272 = 0, + $273 = 0, + $274 = 0, + $275 = 0, + $276 = 0, + $277 = 0; + var $278 = 0, + $279 = 0, + $28 = 0, + $280 = 0, + $281 = 0, + $282 = 0, + $283 = 0, + $284 = 0, + $285 = 0, + $286 = 0, + $287 = 0, + $288 = 0, + $289 = 0, + $29 = 0, + $290 = 0, + $291 = 0, + $292 = 0, + $293 = 0, + $294 = 0, + $295 = 0; + var $296 = 0, + $297 = 0, + $298 = 0, + $299 = 0, + $3 = 0, + $30 = 0, + $300 = 0, + $301 = 0, + $302 = 0, + $303 = 0, + $304 = 0, + $305 = 0, + $306 = 0, + $307 = 0, + $308 = 0, + $309 = 0, + $31 = 0, + $310 = 0, + $311 = 0, + $312 = 0; + var $313 = 0, + $314 = 0, + $315 = 0, + $316 = 0, + $317 = 0, + $318 = 0, + $319 = 0, + $32 = 0, + $320 = 0, + $321 = 0, + $322 = 0, + $323 = 0, + $324 = 0, + $325 = 0, + $326 = 0, + $327 = 0, + $328 = 0, + $329 = 0, + $33 = 0, + $330 = 0; + var $331 = 0, + $332 = 0, + $333 = 0, + $334 = 0, + $335 = 0, + $336 = 0, + $337 = 0, + $338 = 0, + $339 = 0, + $34 = 0, + $340 = 0, + $341 = 0, + $342 = 0, + $343 = 0, + $344 = 0, + $345 = 0, + $346 = 0, + $347 = 0, + $348 = 0, + $349 = 0; + var $35 = 0, + $350 = 0, + $351 = 0, + $352 = 0, + $353 = 0, + $354 = 0, + $355 = 0, + $356 = 0, + $357 = 0, + $358 = 0, + $359 = 0, + $36 = 0, + $360 = 0, + $361 = 0, + $362 = 0, + $363 = 0, + $364 = 0, + $365 = 0, + $366 = 0, + $367 = 0; + var $368 = 0, + $369 = 0, + $37 = 0, + $370 = 0, + $371 = 0, + $372 = 0, + $373 = 0, + $374 = 0, + $375 = 0, + $376 = 0, + $377 = 0, + $378 = 0, + $379 = 0, + $38 = 0, + $380 = 0, + $381 = 0, + $382 = 0, + $383 = 0, + $384 = 0, + $385 = 0; + var $386 = 0, + $387 = 0, + $388 = 0, + $389 = 0, + $39 = 0, + $390 = 0, + $391 = 0, + $392 = 0, + $393 = 0, + $394 = 0, + $395 = 0, + $396 = 0, + $397 = 0, + $398 = 0, + $399 = 0, + $4 = 0, + $40 = 0, + $400 = 0, + $401 = 0, + $402 = 0; + var $403 = 0, + $404 = 0, + $41 = 0, + $42 = 0, + $43 = 0, + $44 = 0, + $45 = 0, + $46 = 0, + $47 = 0, + $48 = 0, + $49 = 0, + $5 = 0, + $50 = 0, + $51 = 0, + $52 = 0, + $53 = 0, + $54 = 0, + $55 = 0, + $56 = 0, + $57 = 0; + var $58 = 0, + $59 = 0, + $6 = 0, + $60 = 0, + $61 = 0, + $62 = 0, + $63 = 0, + $64 = 0, + $65 = 0, + $66 = 0, + $67 = 0, + $68 = 0, + $69 = 0, + $7 = 0, + $70 = 0, + $71 = 0, + $72 = 0, + $73 = 0, + $74 = 0, + $75 = 0; + var $76 = 0, + $77 = 0, + $78 = 0, + $79 = 0, + $8 = 0, + $80 = 0, + $81 = 0, + $82 = 0, + $83 = 0, + $84 = 0, + $85 = 0, + $86 = 0, + $87 = 0, + $88 = 0, + $89 = 0, + $9 = 0, + $90 = 0, + $91 = 0, + $92 = 0, + $93 = 0; + var $94 = 0, + $95 = 0, + $96 = 0, + $97 = 0, + $98 = 0, + $99 = 0, + $exitcond = 0, + $exitcond11 = 0, + $exitcond11$1 = 0, + $exitcond14 = 0, + $exitcond14$1 = 0, + $i$17 = 0, + $i$17$1 = 0, + $i$24 = 0, + $i$24$1 = 0, + $i$33 = 0, + $input = 0, + $mask$02 = 0, + $mask$1 = 0, + label = 0; + var sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 48) | 0; + $input = sp; + $0 = $input_limbs; + $1 = $0; + $2 = HEAP32[$1 >> 2] | 0; + $3 = ($0 + 4) | 0; + $4 = $3; + $5 = HEAP32[$4 >> 2] | 0; + HEAP32[$input >> 2] = $2; + $6 = ($input_limbs + 8) | 0; + $7 = $6; + $8 = $7; + $9 = HEAP32[$8 >> 2] | 0; + $10 = ($7 + 4) | 0; + $11 = $10; + $12 = HEAP32[$11 >> 2] | 0; + $13 = ($input + 4) | 0; + HEAP32[$13 >> 2] = $9; + $14 = ($input_limbs + 16) | 0; + $15 = $14; + $16 = $15; + $17 = HEAP32[$16 >> 2] | 0; + $18 = ($15 + 4) | 0; + $19 = $18; + $20 = HEAP32[$19 >> 2] | 0; + $21 = ($input + 8) | 0; + HEAP32[$21 >> 2] = $17; + $22 = ($input_limbs + 24) | 0; + $23 = $22; + $24 = $23; + $25 = HEAP32[$24 >> 2] | 0; + $26 = ($23 + 4) | 0; + $27 = $26; + $28 = HEAP32[$27 >> 2] | 0; + $29 = ($input + 12) | 0; + HEAP32[$29 >> 2] = $25; + $30 = ($input_limbs + 32) | 0; + $31 = $30; + $32 = $31; + $33 = HEAP32[$32 >> 2] | 0; + $34 = ($31 + 4) | 0; + $35 = $34; + $36 = HEAP32[$35 >> 2] | 0; + $37 = ($input + 16) | 0; + HEAP32[$37 >> 2] = $33; + $38 = ($input_limbs + 40) | 0; + $39 = $38; + $40 = $39; + $41 = HEAP32[$40 >> 2] | 0; + $42 = ($39 + 4) | 0; + $43 = $42; + $44 = HEAP32[$43 >> 2] | 0; + $45 = ($input + 20) | 0; + HEAP32[$45 >> 2] = $41; + $46 = ($input_limbs + 48) | 0; + $47 = $46; + $48 = $47; + $49 = HEAP32[$48 >> 2] | 0; + $50 = ($47 + 4) | 0; + $51 = $50; + $52 = HEAP32[$51 >> 2] | 0; + $53 = ($input + 24) | 0; + HEAP32[$53 >> 2] = $49; + $54 = ($input_limbs + 56) | 0; + $55 = $54; + $56 = $55; + $57 = HEAP32[$56 >> 2] | 0; + $58 = ($55 + 4) | 0; + $59 = $58; + $60 = HEAP32[$59 >> 2] | 0; + $61 = ($input + 28) | 0; + HEAP32[$61 >> 2] = $57; + $62 = ($input_limbs + 64) | 0; + $63 = $62; + $64 = $63; + $65 = HEAP32[$64 >> 2] | 0; + $66 = ($63 + 4) | 0; + $67 = $66; + $68 = HEAP32[$67 >> 2] | 0; + $69 = ($input + 32) | 0; + HEAP32[$69 >> 2] = $65; + $70 = ($input_limbs + 72) | 0; + $71 = $70; + $72 = $71; + $73 = HEAP32[$72 >> 2] | 0; + $74 = ($71 + 4) | 0; + $75 = $74; + $76 = HEAP32[$75 >> 2] | 0; + $77 = ($input + 36) | 0; + HEAP32[$77 >> 2] = $73; + $78 = ($input + 36) | 0; + $i$17 = 0; + while (1) { + $79 = $i$17 & 1; + $80 = ($79 | 0) == 0; + $81 = ($input + ($i$17 << 2)) | 0; + $82 = HEAP32[$81 >> 2] | 0; + $83 = $82 >> 31; + $84 = $83 & $82; + if ($80) { + $92 = $84 >> 26; + $93 = Math_imul($92, -67108864) | 0; + $94 = ($93 + $82) | 0; + HEAP32[$81 >> 2] = $94; + $95 = ($i$17 + 1) | 0; + $96 = ($input + ($95 << 2)) | 0; + $97 = HEAP32[$96 >> 2] | 0; + $98 = ($97 + $92) | 0; + HEAP32[$96 >> 2] = $98; + } else { + $85 = $84 >> 25; + $86 = Math_imul($85, -33554432) | 0; + $87 = ($86 + $82) | 0; + HEAP32[$81 >> 2] = $87; + $88 = ($i$17 + 1) | 0; + $89 = ($input + ($88 << 2)) | 0; + $90 = HEAP32[$89 >> 2] | 0; + $91 = ($90 + $85) | 0; + HEAP32[$89 >> 2] = $91; + } + $99 = ($i$17 + 1) | 0; + $exitcond14 = ($99 | 0) == 9; + if ($exitcond14) { + break; + } else { + $i$17 = $99; + } + } + $100 = HEAP32[$78 >> 2] | 0; + $101 = $100 >> 31; + $102 = $101 & $100; + $103 = $102 >> 25; + $104 = Math_imul($103, -33554432) | 0; + $105 = ($104 + $100) | 0; + HEAP32[$78 >> 2] = $105; + $106 = HEAP32[$input >> 2] | 0; + $107 = ($103 * 19) | 0; + $108 = ($107 + $106) | 0; + HEAP32[$input >> 2] = $108; + $i$17$1 = 0; + while (1) { + $384 = $i$17$1 & 1; + $385 = ($384 | 0) == 0; + $386 = ($input + ($i$17$1 << 2)) | 0; + $387 = HEAP32[$386 >> 2] | 0; + $388 = $387 >> 31; + $389 = $388 & $387; + if ($385) { + $397 = $389 >> 26; + $398 = Math_imul($397, -67108864) | 0; + $399 = ($398 + $387) | 0; + HEAP32[$386 >> 2] = $399; + $400 = ($i$17$1 + 1) | 0; + $401 = ($input + ($400 << 2)) | 0; + $402 = HEAP32[$401 >> 2] | 0; + $403 = ($402 + $397) | 0; + HEAP32[$401 >> 2] = $403; + } else { + $390 = $389 >> 25; + $391 = Math_imul($390, -33554432) | 0; + $392 = ($391 + $387) | 0; + HEAP32[$386 >> 2] = $392; + $393 = ($i$17$1 + 1) | 0; + $394 = ($input + ($393 << 2)) | 0; + $395 = HEAP32[$394 >> 2] | 0; + $396 = ($395 + $390) | 0; + HEAP32[$394 >> 2] = $396; + } + $404 = ($i$17$1 + 1) | 0; + $exitcond14$1 = ($404 | 0) == 9; + if ($exitcond14$1) { + break; + } else { + $i$17$1 = $404; + } + } + $109 = HEAP32[$78 >> 2] | 0; + $110 = $109 >> 31; + $111 = $110 & $109; + $112 = $111 >> 25; + $113 = Math_imul($112, -33554432) | 0; + $114 = ($113 + $109) | 0; + HEAP32[$78 >> 2] = $114; + $115 = HEAP32[$input >> 2] | 0; + $116 = ($112 * 19) | 0; + $117 = ($116 + $115) | 0; + $118 = $117 >> 31; + $119 = $118 & $117; + $120 = $119 >> 26; + $121 = Math_imul($120, -67108864) | 0; + $122 = ($121 + $117) | 0; + HEAP32[$input >> 2] = $122; + $123 = ($input + 4) | 0; + $124 = HEAP32[$123 >> 2] | 0; + $125 = ($120 + $124) | 0; + HEAP32[$123 >> 2] = $125; + $126 = ($input + 36) | 0; + $i$24 = 0; + while (1) { + $127 = $i$24 & 1; + $128 = ($127 | 0) == 0; + $129 = ($input + ($i$24 << 2)) | 0; + $130 = HEAP32[$129 >> 2] | 0; + if ($128) { + $137 = $130 >> 26; + $138 = $130 & 67108863; + HEAP32[$129 >> 2] = $138; + $139 = ($i$24 + 1) | 0; + $140 = ($input + ($139 << 2)) | 0; + $141 = HEAP32[$140 >> 2] | 0; + $142 = ($141 + $137) | 0; + HEAP32[$140 >> 2] = $142; + } else { + $131 = $130 >> 25; + $132 = $130 & 33554431; + HEAP32[$129 >> 2] = $132; + $133 = ($i$24 + 1) | 0; + $134 = ($input + ($133 << 2)) | 0; + $135 = HEAP32[$134 >> 2] | 0; + $136 = ($135 + $131) | 0; + HEAP32[$134 >> 2] = $136; + } + $143 = ($i$24 + 1) | 0; + $exitcond11 = ($143 | 0) == 9; + if ($exitcond11) { + break; + } else { + $i$24 = $143; + } + } + $144 = HEAP32[$126 >> 2] | 0; + $145 = $144 >> 25; + $146 = $144 & 33554431; + HEAP32[$126 >> 2] = $146; + $147 = ($145 * 19) | 0; + $148 = HEAP32[$input >> 2] | 0; + $149 = ($147 + $148) | 0; + HEAP32[$input >> 2] = $149; + $i$24$1 = 0; + while (1) { + $360 = $i$24$1 & 1; + $361 = ($360 | 0) == 0; + $362 = ($input + ($i$24$1 << 2)) | 0; + $363 = HEAP32[$362 >> 2] | 0; + if ($361) { + $370 = $363 >> 26; + $371 = $363 & 67108863; + HEAP32[$362 >> 2] = $371; + $372 = ($i$24$1 + 1) | 0; + $373 = ($input + ($372 << 2)) | 0; + $374 = HEAP32[$373 >> 2] | 0; + $375 = ($374 + $370) | 0; + HEAP32[$373 >> 2] = $375; + } else { + $364 = $363 >> 25; + $365 = $363 & 33554431; + HEAP32[$362 >> 2] = $365; + $366 = ($i$24$1 + 1) | 0; + $367 = ($input + ($366 << 2)) | 0; + $368 = HEAP32[$367 >> 2] | 0; + $369 = ($368 + $364) | 0; + HEAP32[$367 >> 2] = $369; + } + $376 = ($i$24$1 + 1) | 0; + $exitcond11$1 = ($376 | 0) == 9; + if ($exitcond11$1) { + break; + } else { + $i$24$1 = $376; + } + } + $377 = HEAP32[$126 >> 2] | 0; + $378 = $377 >> 25; + $379 = $377 & 33554431; + HEAP32[$126 >> 2] = $379; + $380 = ($378 * 19) | 0; + $381 = HEAP32[$input >> 2] | 0; + $382 = ($380 + $381) | 0; + HEAP32[$input >> 2] = $382; + $383 = _s32_gte($382) | 0; + $i$33 = 1; + $mask$02 = $383; + while (1) { + $150 = $i$33 & 1; + $151 = ($150 | 0) == 0; + $152 = ($input + ($i$33 << 2)) | 0; + $153 = HEAP32[$152 >> 2] | 0; + if ($151) { + $155 = _s32_eq($153, 67108863) | 0; + $$pn = $155; + } else { + $154 = _s32_eq($153, 33554431) | 0; + $$pn = $154; + } + $mask$1 = $$pn & $mask$02; + $156 = ($i$33 + 1) | 0; + $exitcond = ($156 | 0) == 10; + if ($exitcond) { + break; + } else { + $i$33 = $156; + $mask$02 = $mask$1; + } + } + $157 = $mask$1 & 67108845; + $158 = HEAP32[$input >> 2] | 0; + $159 = ($158 - $157) | 0; + HEAP32[$input >> 2] = $159; + $160 = $mask$1 & 67108863; + $161 = $mask$1 & 33554431; + $162 = ($input + 4) | 0; + $163 = HEAP32[$162 >> 2] | 0; + $164 = ($163 - $161) | 0; + HEAP32[$162 >> 2] = $164; + $165 = ($input + 8) | 0; + $166 = HEAP32[$165 >> 2] | 0; + $167 = ($166 - $160) | 0; + HEAP32[$165 >> 2] = $167; + $168 = ($input + 12) | 0; + $169 = HEAP32[$168 >> 2] | 0; + $170 = ($169 - $161) | 0; + HEAP32[$168 >> 2] = $170; + $171 = ($input + 16) | 0; + $172 = HEAP32[$171 >> 2] | 0; + $173 = ($172 - $160) | 0; + HEAP32[$171 >> 2] = $173; + $174 = ($input + 20) | 0; + $175 = HEAP32[$174 >> 2] | 0; + $176 = ($175 - $161) | 0; + HEAP32[$174 >> 2] = $176; + $177 = ($input + 24) | 0; + $178 = HEAP32[$177 >> 2] | 0; + $179 = ($178 - $160) | 0; + HEAP32[$177 >> 2] = $179; + $180 = ($input + 28) | 0; + $181 = HEAP32[$180 >> 2] | 0; + $182 = ($181 - $161) | 0; + HEAP32[$180 >> 2] = $182; + $183 = ($input + 32) | 0; + $184 = HEAP32[$183 >> 2] | 0; + $185 = ($184 - $160) | 0; + HEAP32[$183 >> 2] = $185; + $186 = ($input + 36) | 0; + $187 = HEAP32[$186 >> 2] | 0; + $188 = ($187 - $161) | 0; + HEAP32[$186 >> 2] = $188; + $189 = HEAP32[$123 >> 2] | 0; + $190 = $189 << 2; + HEAP32[$123 >> 2] = $190; + $191 = ($input + 8) | 0; + $192 = HEAP32[$191 >> 2] | 0; + $193 = $192 << 3; + HEAP32[$191 >> 2] = $193; + $194 = ($input + 12) | 0; + $195 = HEAP32[$194 >> 2] | 0; + $196 = $195 << 5; + HEAP32[$194 >> 2] = $196; + $197 = ($input + 16) | 0; + $198 = HEAP32[$197 >> 2] | 0; + $199 = $198 << 6; + HEAP32[$197 >> 2] = $199; + $200 = ($input + 24) | 0; + $201 = HEAP32[$200 >> 2] | 0; + $202 = $201 << 1; + HEAP32[$200 >> 2] = $202; + $203 = ($input + 28) | 0; + $204 = HEAP32[$203 >> 2] | 0; + $205 = $204 << 3; + HEAP32[$203 >> 2] = $205; + $206 = ($input + 32) | 0; + $207 = HEAP32[$206 >> 2] | 0; + $208 = $207 << 4; + HEAP32[$206 >> 2] = $208; + $209 = ($input + 36) | 0; + $210 = HEAP32[$209 >> 2] | 0; + $211 = $210 << 6; + HEAP32[$209 >> 2] = $211; + HEAP8[$output >> 0] = 0; + $212 = ($output + 16) | 0; + HEAP8[$212 >> 0] = 0; + $213 = HEAP32[$input >> 2] | 0; + $214 = HEAP8[$output >> 0] | 0; + $215 = $214 & 255; + $216 = $215 | $213; + $217 = $216 & 255; + HEAP8[$output >> 0] = $217; + $218 = HEAP32[$input >> 2] | 0; + $219 = $218 >>> 8; + $220 = $219 & 255; + $221 = ($output + 1) | 0; + HEAP8[$221 >> 0] = $220; + $222 = HEAP32[$input >> 2] | 0; + $223 = $222 >>> 16; + $224 = $223 & 255; + $225 = ($output + 2) | 0; + HEAP8[$225 >> 0] = $224; + $226 = HEAP32[$input >> 2] | 0; + $227 = $226 >>> 24; + $228 = ($output + 3) | 0; + $229 = HEAP32[$123 >> 2] | 0; + $230 = $227 | $229; + $231 = $230 & 255; + HEAP8[$228 >> 0] = $231; + $232 = HEAP32[$123 >> 2] | 0; + $233 = $232 >>> 8; + $234 = $233 & 255; + $235 = ($output + 4) | 0; + HEAP8[$235 >> 0] = $234; + $236 = HEAP32[$123 >> 2] | 0; + $237 = $236 >>> 16; + $238 = $237 & 255; + $239 = ($output + 5) | 0; + HEAP8[$239 >> 0] = $238; + $240 = HEAP32[$123 >> 2] | 0; + $241 = $240 >>> 24; + $242 = ($output + 6) | 0; + $243 = HEAP32[$191 >> 2] | 0; + $244 = $241 | $243; + $245 = $244 & 255; + HEAP8[$242 >> 0] = $245; + $246 = HEAP32[$191 >> 2] | 0; + $247 = $246 >>> 8; + $248 = $247 & 255; + $249 = ($output + 7) | 0; + HEAP8[$249 >> 0] = $248; + $250 = HEAP32[$191 >> 2] | 0; + $251 = $250 >>> 16; + $252 = $251 & 255; + $253 = ($output + 8) | 0; + HEAP8[$253 >> 0] = $252; + $254 = HEAP32[$191 >> 2] | 0; + $255 = $254 >>> 24; + $256 = ($output + 9) | 0; + $257 = HEAP32[$194 >> 2] | 0; + $258 = $255 | $257; + $259 = $258 & 255; + HEAP8[$256 >> 0] = $259; + $260 = HEAP32[$194 >> 2] | 0; + $261 = $260 >>> 8; + $262 = $261 & 255; + $263 = ($output + 10) | 0; + HEAP8[$263 >> 0] = $262; + $264 = HEAP32[$194 >> 2] | 0; + $265 = $264 >>> 16; + $266 = $265 & 255; + $267 = ($output + 11) | 0; + HEAP8[$267 >> 0] = $266; + $268 = HEAP32[$194 >> 2] | 0; + $269 = $268 >>> 24; + $270 = ($output + 12) | 0; + $271 = HEAP32[$197 >> 2] | 0; + $272 = $269 | $271; + $273 = $272 & 255; + HEAP8[$270 >> 0] = $273; + $274 = HEAP32[$197 >> 2] | 0; + $275 = $274 >>> 8; + $276 = $275 & 255; + $277 = ($output + 13) | 0; + HEAP8[$277 >> 0] = $276; + $278 = HEAP32[$197 >> 2] | 0; + $279 = $278 >>> 16; + $280 = $279 & 255; + $281 = ($output + 14) | 0; + HEAP8[$281 >> 0] = $280; + $282 = HEAP32[$197 >> 2] | 0; + $283 = $282 >>> 24; + $284 = $283 & 255; + $285 = ($output + 15) | 0; + HEAP8[$285 >> 0] = $284; + $286 = ($input + 20) | 0; + $287 = HEAP32[$286 >> 2] | 0; + $288 = HEAP8[$212 >> 0] | 0; + $289 = $288 & 255; + $290 = $289 | $287; + $291 = $290 & 255; + HEAP8[$212 >> 0] = $291; + $292 = HEAP32[$286 >> 2] | 0; + $293 = $292 >>> 8; + $294 = $293 & 255; + $295 = ($output + 17) | 0; + HEAP8[$295 >> 0] = $294; + $296 = HEAP32[$286 >> 2] | 0; + $297 = $296 >>> 16; + $298 = $297 & 255; + $299 = ($output + 18) | 0; + HEAP8[$299 >> 0] = $298; + $300 = HEAP32[$286 >> 2] | 0; + $301 = $300 >>> 24; + $302 = ($output + 19) | 0; + $303 = HEAP32[$200 >> 2] | 0; + $304 = $301 | $303; + $305 = $304 & 255; + HEAP8[$302 >> 0] = $305; + $306 = HEAP32[$200 >> 2] | 0; + $307 = $306 >>> 8; + $308 = $307 & 255; + $309 = ($output + 20) | 0; + HEAP8[$309 >> 0] = $308; + $310 = HEAP32[$200 >> 2] | 0; + $311 = $310 >>> 16; + $312 = $311 & 255; + $313 = ($output + 21) | 0; + HEAP8[$313 >> 0] = $312; + $314 = HEAP32[$200 >> 2] | 0; + $315 = $314 >>> 24; + $316 = ($output + 22) | 0; + $317 = HEAP32[$203 >> 2] | 0; + $318 = $315 | $317; + $319 = $318 & 255; + HEAP8[$316 >> 0] = $319; + $320 = HEAP32[$203 >> 2] | 0; + $321 = $320 >>> 8; + $322 = $321 & 255; + $323 = ($output + 23) | 0; + HEAP8[$323 >> 0] = $322; + $324 = HEAP32[$203 >> 2] | 0; + $325 = $324 >>> 16; + $326 = $325 & 255; + $327 = ($output + 24) | 0; + HEAP8[$327 >> 0] = $326; + $328 = HEAP32[$203 >> 2] | 0; + $329 = $328 >>> 24; + $330 = ($output + 25) | 0; + $331 = HEAP32[$206 >> 2] | 0; + $332 = $329 | $331; + $333 = $332 & 255; + HEAP8[$330 >> 0] = $333; + $334 = HEAP32[$206 >> 2] | 0; + $335 = $334 >>> 8; + $336 = $335 & 255; + $337 = ($output + 26) | 0; + HEAP8[$337 >> 0] = $336; + $338 = HEAP32[$206 >> 2] | 0; + $339 = $338 >>> 16; + $340 = $339 & 255; + $341 = ($output + 27) | 0; + HEAP8[$341 >> 0] = $340; + $342 = HEAP32[$206 >> 2] | 0; + $343 = $342 >>> 24; + $344 = ($output + 28) | 0; + $345 = HEAP32[$209 >> 2] | 0; + $346 = $343 | $345; + $347 = $346 & 255; + HEAP8[$344 >> 0] = $347; + $348 = HEAP32[$209 >> 2] | 0; + $349 = $348 >>> 8; + $350 = $349 & 255; + $351 = ($output + 29) | 0; + HEAP8[$351 >> 0] = $350; + $352 = HEAP32[$209 >> 2] | 0; + $353 = $352 >>> 16; + $354 = $353 & 255; + $355 = ($output + 30) | 0; + HEAP8[$355 >> 0] = $354; + $356 = HEAP32[$209 >> 2] | 0; + $357 = $356 >>> 24; + $358 = $357 & 255; + $359 = ($output + 31) | 0; + HEAP8[$359 >> 0] = $358; + STACKTOP = sp; + return; + } + function _swap_conditional($a, $b, $0, $1) { + $a = $a | 0; + $b = $b | 0; + $0 = $0 | 0; + $1 = $1 | 0; + var $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0, + $27 = 0, + $28 = 0; + var $29 = 0, + $3 = 0, + $30 = 0, + $31 = 0, + $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $37 = 0, + $38 = 0, + $39 = 0, + $4 = 0, + $40 = 0, + $41 = 0, + $42 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0; + var $9 = 0, + $exitcond = 0, + $i$02 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $2 = _i64Subtract(0, 0, $0 | 0, $1 | 0) | 0; + $3 = tempRet0; + $i$02 = 0; + while (1) { + $4 = ($a + ($i$02 << 3)) | 0; + $5 = $4; + $6 = $5; + $7 = HEAP32[$6 >> 2] | 0; + $8 = ($5 + 4) | 0; + $9 = $8; + $10 = HEAP32[$9 >> 2] | 0; + $11 = ($b + ($i$02 << 3)) | 0; + $12 = $11; + $13 = $12; + $14 = HEAP32[$13 >> 2] | 0; + $15 = ($12 + 4) | 0; + $16 = $15; + $17 = HEAP32[$16 >> 2] | 0; + $18 = $14 ^ $7; + $19 = $17 ^ $10; + $20 = $18 & $2; + $21 = $19 & $3; + $22 = $20 ^ $7; + $21 ^ $10; + $23 = _bitshift64Ashr(0, $22 | 0, 32) | 0; + $24 = tempRet0; + $25 = $4; + $26 = $25; + HEAP32[$26 >> 2] = $23; + $27 = ($25 + 4) | 0; + $28 = $27; + HEAP32[$28 >> 2] = $24; + $29 = $11; + $30 = $29; + $31 = HEAP32[$30 >> 2] | 0; + $32 = ($29 + 4) | 0; + $33 = $32; + $34 = HEAP32[$33 >> 2] | 0; + $35 = $20 ^ $31; + $21 ^ $34; + $36 = _bitshift64Ashr(0, $35 | 0, 32) | 0; + $37 = tempRet0; + $38 = $11; + $39 = $38; + HEAP32[$39 >> 2] = $36; + $40 = ($38 + 4) | 0; + $41 = $40; + HEAP32[$41 >> 2] = $37; + $42 = ($i$02 + 1) | 0; + $exitcond = ($42 | 0) == 10; + if ($exitcond) { + break; + } else { + $i$02 = $42; + } + } + STACKTOP = sp; + return; + } + function _fmonty($x2, $z2, $x3, $z3, $x, $z, $xprime, $zprime, $qmqp) { + $x2 = $x2 | 0; + $z2 = $z2 | 0; + $x3 = $x3 | 0; + $z3 = $z3 | 0; + $x = $x | 0; + $z = $z | 0; + $xprime = $xprime | 0; + $zprime = $zprime | 0; + $qmqp = $qmqp | 0; + var $0 = 0, + $origx = 0, + $origxprime = 0, + $xx = 0, + $xxprime = 0, + $xxxprime = 0, + $zz = 0, + $zzprime = 0, + $zzz = 0, + $zzzprime = 0, + dest = 0, + label = 0, + sp = 0, + src = 0, + stop = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 1232) | 0; + $origx = (sp + 1144) | 0; + $origxprime = (sp + 1064) | 0; + $zzz = (sp + 912) | 0; + $xx = (sp + 760) | 0; + $zz = (sp + 608) | 0; + $xxprime = (sp + 456) | 0; + $zzprime = (sp + 304) | 0; + $zzzprime = (sp + 152) | 0; + $xxxprime = sp; + dest = ($origx + 0) | 0; + src = ($x + 0) | 0; + stop = (dest + 80) | 0; + do { + HEAP32[dest >> 2] = HEAP32[src >> 2] | 0; + dest = (dest + 4) | 0; + src = (src + 4) | 0; + } while ((dest | 0) < (stop | 0)); + _fsum($x, $z); + _fdifference($z, $origx); + dest = ($origxprime + 0) | 0; + src = ($xprime + 0) | 0; + stop = (dest + 80) | 0; + do { + HEAP32[dest >> 2] = HEAP32[src >> 2] | 0; + dest = (dest + 4) | 0; + src = (src + 4) | 0; + } while ((dest | 0) < (stop | 0)); + _fsum($xprime, $zprime); + _fdifference($zprime, $origxprime); + _fproduct($xxprime, $xprime, $z); + _fproduct($zzprime, $x, $zprime); + _freduce_degree($xxprime); + _freduce_coefficients($xxprime); + _freduce_degree($zzprime); + _freduce_coefficients($zzprime); + dest = ($origxprime + 0) | 0; + src = ($xxprime + 0) | 0; + stop = (dest + 80) | 0; + do { + HEAP32[dest >> 2] = HEAP32[src >> 2] | 0; + dest = (dest + 4) | 0; + src = (src + 4) | 0; + } while ((dest | 0) < (stop | 0)); + _fsum($xxprime, $zzprime); + _fdifference($zzprime, $origxprime); + _fsquare($xxxprime, $xxprime); + _fsquare($zzzprime, $zzprime); + _fproduct($zzprime, $zzzprime, $qmqp); + _freduce_degree($zzprime); + _freduce_coefficients($zzprime); + dest = ($x3 + 0) | 0; + src = ($xxxprime + 0) | 0; + stop = (dest + 80) | 0; + do { + HEAP32[dest >> 2] = HEAP32[src >> 2] | 0; + dest = (dest + 4) | 0; + src = (src + 4) | 0; + } while ((dest | 0) < (stop | 0)); + dest = ($z3 + 0) | 0; + src = ($zzprime + 0) | 0; + stop = (dest + 80) | 0; + do { + HEAP32[dest >> 2] = HEAP32[src >> 2] | 0; + dest = (dest + 4) | 0; + src = (src + 4) | 0; + } while ((dest | 0) < (stop | 0)); + _fsquare($xx, $x); + _fsquare($zz, $z); + _fproduct($x2, $xx, $zz); + _freduce_degree($x2); + _freduce_coefficients($x2); + _fdifference($zz, $xx); + $0 = ($zzz + 80) | 0; + dest = ($0 + 0) | 0; + stop = (dest + 72) | 0; + do { + HEAP32[dest >> 2] = 0 | 0; + dest = (dest + 4) | 0; + } while ((dest | 0) < (stop | 0)); + _fscalar_product($zzz, $zz); + _freduce_coefficients($zzz); + _fsum($zzz, $xx); + _fproduct($z2, $zz, $zzz); + _freduce_degree($z2); + _freduce_coefficients($z2); + STACKTOP = sp; + return; + } + function _fsquare($output, $in) { + $output = $output | 0; + $in = $in | 0; + var $t = 0, + dest = 0, + label = 0, + sp = 0, + src = 0, + stop = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 160) | 0; + $t = sp; + _fsquare_inner($t, $in); + _freduce_degree($t); + _freduce_coefficients($t); + dest = ($output + 0) | 0; + src = ($t + 0) | 0; + stop = (dest + 80) | 0; + do { + HEAP32[dest >> 2] = HEAP32[src >> 2] | 0; + dest = (dest + 4) | 0; + src = (src + 4) | 0; + } while ((dest | 0) < (stop | 0)); + STACKTOP = sp; + return; + } + function _fproduct($output, $in2, $in) { + $output = $output | 0; + $in2 = $in2 | 0; + $in = $in | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $1000 = 0, + $1001 = 0, + $1002 = 0, + $1003 = 0, + $1004 = 0, + $1005 = 0, + $1006 = 0, + $1007 = 0, + $1008 = 0, + $1009 = 0, + $101 = 0, + $1010 = 0, + $1011 = 0, + $1012 = 0, + $1013 = 0, + $1014 = 0; + var $1015 = 0, + $1016 = 0, + $1017 = 0, + $1018 = 0, + $1019 = 0, + $102 = 0, + $1020 = 0, + $1021 = 0, + $1022 = 0, + $1023 = 0, + $1024 = 0, + $1025 = 0, + $1026 = 0, + $1027 = 0, + $1028 = 0, + $1029 = 0, + $103 = 0, + $1030 = 0, + $1031 = 0, + $1032 = 0; + var $1033 = 0, + $1034 = 0, + $1035 = 0, + $1036 = 0, + $1037 = 0, + $1038 = 0, + $1039 = 0, + $104 = 0, + $1040 = 0, + $1041 = 0, + $1042 = 0, + $1043 = 0, + $1044 = 0, + $1045 = 0, + $1046 = 0, + $1047 = 0, + $1048 = 0, + $1049 = 0, + $105 = 0, + $1050 = 0; + var $1051 = 0, + $1052 = 0, + $1053 = 0, + $1054 = 0, + $1055 = 0, + $1056 = 0, + $1057 = 0, + $1058 = 0, + $1059 = 0, + $106 = 0, + $1060 = 0, + $1061 = 0, + $1062 = 0, + $1063 = 0, + $1064 = 0, + $1065 = 0, + $1066 = 0, + $1067 = 0, + $1068 = 0, + $1069 = 0; + var $107 = 0, + $1070 = 0, + $1071 = 0, + $1072 = 0, + $1073 = 0, + $1074 = 0, + $1075 = 0, + $1076 = 0, + $1077 = 0, + $1078 = 0, + $1079 = 0, + $108 = 0, + $1080 = 0, + $1081 = 0, + $1082 = 0, + $1083 = 0, + $1084 = 0, + $1085 = 0, + $1086 = 0, + $1087 = 0; + var $1088 = 0, + $1089 = 0, + $109 = 0, + $1090 = 0, + $1091 = 0, + $1092 = 0, + $1093 = 0, + $1094 = 0, + $1095 = 0, + $1096 = 0, + $1097 = 0, + $1098 = 0, + $1099 = 0, + $11 = 0, + $110 = 0, + $1100 = 0, + $1101 = 0, + $1102 = 0, + $1103 = 0, + $1104 = 0; + var $1105 = 0, + $1106 = 0, + $1107 = 0, + $1108 = 0, + $1109 = 0, + $111 = 0, + $1110 = 0, + $1111 = 0, + $1112 = 0, + $1113 = 0, + $1114 = 0, + $1115 = 0, + $1116 = 0, + $1117 = 0, + $1118 = 0, + $1119 = 0, + $112 = 0, + $1120 = 0, + $1121 = 0, + $1122 = 0; + var $1123 = 0, + $1124 = 0, + $1125 = 0, + $1126 = 0, + $1127 = 0, + $1128 = 0, + $1129 = 0, + $113 = 0, + $1130 = 0, + $1131 = 0, + $1132 = 0, + $1133 = 0, + $1134 = 0, + $1135 = 0, + $1136 = 0, + $1137 = 0, + $1138 = 0, + $1139 = 0, + $114 = 0, + $1140 = 0; + var $1141 = 0, + $1142 = 0, + $1143 = 0, + $1144 = 0, + $1145 = 0, + $1146 = 0, + $1147 = 0, + $1148 = 0, + $1149 = 0, + $115 = 0, + $1150 = 0, + $1151 = 0, + $1152 = 0, + $1153 = 0, + $1154 = 0, + $1155 = 0, + $1156 = 0, + $1157 = 0, + $1158 = 0, + $1159 = 0; + var $116 = 0, + $1160 = 0, + $1161 = 0, + $1162 = 0, + $1163 = 0, + $1164 = 0, + $1165 = 0, + $1166 = 0, + $1167 = 0, + $1168 = 0, + $1169 = 0, + $117 = 0, + $1170 = 0, + $1171 = 0, + $1172 = 0, + $1173 = 0, + $1174 = 0, + $1175 = 0, + $1176 = 0, + $1177 = 0; + var $1178 = 0, + $1179 = 0, + $118 = 0, + $1180 = 0, + $1181 = 0, + $1182 = 0, + $1183 = 0, + $1184 = 0, + $1185 = 0, + $1186 = 0, + $1187 = 0, + $1188 = 0, + $1189 = 0, + $119 = 0, + $1190 = 0, + $1191 = 0, + $1192 = 0, + $1193 = 0, + $1194 = 0, + $1195 = 0; + var $1196 = 0, + $1197 = 0, + $1198 = 0, + $1199 = 0, + $12 = 0, + $120 = 0, + $1200 = 0, + $1201 = 0, + $1202 = 0, + $1203 = 0, + $1204 = 0, + $1205 = 0, + $1206 = 0, + $1207 = 0, + $1208 = 0, + $1209 = 0, + $121 = 0, + $1210 = 0, + $1211 = 0, + $1212 = 0; + var $1213 = 0, + $1214 = 0, + $1215 = 0, + $1216 = 0, + $1217 = 0, + $1218 = 0, + $1219 = 0, + $122 = 0, + $1220 = 0, + $1221 = 0, + $1222 = 0, + $1223 = 0, + $1224 = 0, + $1225 = 0, + $1226 = 0, + $1227 = 0, + $1228 = 0, + $1229 = 0, + $123 = 0, + $1230 = 0; + var $1231 = 0, + $1232 = 0, + $1233 = 0, + $1234 = 0, + $1235 = 0, + $1236 = 0, + $1237 = 0, + $1238 = 0, + $1239 = 0, + $124 = 0, + $1240 = 0, + $1241 = 0, + $1242 = 0, + $1243 = 0, + $1244 = 0, + $1245 = 0, + $1246 = 0, + $1247 = 0, + $1248 = 0, + $1249 = 0; + var $125 = 0, + $1250 = 0, + $1251 = 0, + $1252 = 0, + $1253 = 0, + $1254 = 0, + $1255 = 0, + $1256 = 0, + $1257 = 0, + $1258 = 0, + $1259 = 0, + $126 = 0, + $1260 = 0, + $1261 = 0, + $1262 = 0, + $1263 = 0, + $1264 = 0, + $1265 = 0, + $1266 = 0, + $1267 = 0; + var $1268 = 0, + $1269 = 0, + $127 = 0, + $1270 = 0, + $1271 = 0, + $1272 = 0, + $1273 = 0, + $1274 = 0, + $1275 = 0, + $1276 = 0, + $1277 = 0, + $1278 = 0, + $1279 = 0, + $128 = 0, + $1280 = 0, + $1281 = 0, + $1282 = 0, + $1283 = 0, + $1284 = 0, + $1285 = 0; + var $1286 = 0, + $1287 = 0, + $1288 = 0, + $1289 = 0, + $129 = 0, + $1290 = 0, + $1291 = 0, + $1292 = 0, + $1293 = 0, + $1294 = 0, + $1295 = 0, + $1296 = 0, + $1297 = 0, + $1298 = 0, + $1299 = 0, + $13 = 0, + $130 = 0, + $1300 = 0, + $1301 = 0, + $1302 = 0; + var $1303 = 0, + $1304 = 0, + $1305 = 0, + $1306 = 0, + $1307 = 0, + $1308 = 0, + $1309 = 0, + $131 = 0, + $1310 = 0, + $1311 = 0, + $1312 = 0, + $1313 = 0, + $1314 = 0, + $1315 = 0, + $1316 = 0, + $1317 = 0, + $1318 = 0, + $1319 = 0, + $132 = 0, + $1320 = 0; + var $1321 = 0, + $1322 = 0, + $1323 = 0, + $1324 = 0, + $1325 = 0, + $1326 = 0, + $1327 = 0, + $1328 = 0, + $1329 = 0, + $133 = 0, + $1330 = 0, + $1331 = 0, + $1332 = 0, + $1333 = 0, + $1334 = 0, + $1335 = 0, + $1336 = 0, + $1337 = 0, + $1338 = 0, + $1339 = 0; + var $134 = 0, + $1340 = 0, + $1341 = 0, + $1342 = 0, + $1343 = 0, + $1344 = 0, + $1345 = 0, + $1346 = 0, + $1347 = 0, + $1348 = 0, + $1349 = 0, + $135 = 0, + $1350 = 0, + $1351 = 0, + $1352 = 0, + $1353 = 0, + $1354 = 0, + $1355 = 0, + $1356 = 0, + $1357 = 0; + var $1358 = 0, + $1359 = 0, + $136 = 0, + $1360 = 0, + $1361 = 0, + $1362 = 0, + $1363 = 0, + $1364 = 0, + $1365 = 0, + $1366 = 0, + $1367 = 0, + $1368 = 0, + $1369 = 0, + $137 = 0, + $1370 = 0, + $1371 = 0, + $1372 = 0, + $1373 = 0, + $1374 = 0, + $1375 = 0; + var $1376 = 0, + $1377 = 0, + $1378 = 0, + $1379 = 0, + $138 = 0, + $1380 = 0, + $1381 = 0, + $1382 = 0, + $1383 = 0, + $1384 = 0, + $1385 = 0, + $1386 = 0, + $1387 = 0, + $1388 = 0, + $1389 = 0, + $139 = 0, + $1390 = 0, + $1391 = 0, + $1392 = 0, + $1393 = 0; + var $1394 = 0, + $1395 = 0, + $1396 = 0, + $1397 = 0, + $1398 = 0, + $1399 = 0, + $14 = 0, + $140 = 0, + $1400 = 0, + $1401 = 0, + $1402 = 0, + $1403 = 0, + $1404 = 0, + $1405 = 0, + $1406 = 0, + $1407 = 0, + $1408 = 0, + $1409 = 0, + $141 = 0, + $1410 = 0; + var $1411 = 0, + $1412 = 0, + $1413 = 0, + $1414 = 0, + $1415 = 0, + $1416 = 0, + $1417 = 0, + $1418 = 0, + $1419 = 0, + $142 = 0, + $1420 = 0, + $1421 = 0, + $1422 = 0, + $1423 = 0, + $1424 = 0, + $1425 = 0, + $1426 = 0, + $1427 = 0, + $1428 = 0, + $1429 = 0; + var $143 = 0, + $1430 = 0, + $1431 = 0, + $1432 = 0, + $1433 = 0, + $1434 = 0, + $1435 = 0, + $1436 = 0, + $1437 = 0, + $1438 = 0, + $1439 = 0, + $144 = 0, + $1440 = 0, + $1441 = 0, + $1442 = 0, + $1443 = 0, + $1444 = 0, + $1445 = 0, + $1446 = 0, + $1447 = 0; + var $1448 = 0, + $1449 = 0, + $145 = 0, + $1450 = 0, + $1451 = 0, + $1452 = 0, + $1453 = 0, + $1454 = 0, + $1455 = 0, + $1456 = 0, + $1457 = 0, + $1458 = 0, + $1459 = 0, + $146 = 0, + $1460 = 0, + $1461 = 0, + $1462 = 0, + $1463 = 0, + $1464 = 0, + $1465 = 0; + var $1466 = 0, + $1467 = 0, + $1468 = 0, + $1469 = 0, + $147 = 0, + $1470 = 0, + $1471 = 0, + $1472 = 0, + $1473 = 0, + $1474 = 0, + $1475 = 0, + $1476 = 0, + $1477 = 0, + $1478 = 0, + $1479 = 0, + $148 = 0, + $1480 = 0, + $1481 = 0, + $1482 = 0, + $1483 = 0; + var $1484 = 0, + $1485 = 0, + $1486 = 0, + $1487 = 0, + $1488 = 0, + $1489 = 0, + $149 = 0, + $1490 = 0, + $1491 = 0, + $1492 = 0, + $1493 = 0, + $1494 = 0, + $1495 = 0, + $1496 = 0, + $1497 = 0, + $1498 = 0, + $1499 = 0, + $15 = 0, + $150 = 0, + $1500 = 0; + var $1501 = 0, + $1502 = 0, + $1503 = 0, + $1504 = 0, + $1505 = 0, + $1506 = 0, + $1507 = 0, + $1508 = 0, + $1509 = 0, + $151 = 0, + $1510 = 0, + $1511 = 0, + $1512 = 0, + $1513 = 0, + $1514 = 0, + $1515 = 0, + $1516 = 0, + $1517 = 0, + $1518 = 0, + $1519 = 0; + var $152 = 0, + $1520 = 0, + $1521 = 0, + $1522 = 0, + $1523 = 0, + $1524 = 0, + $1525 = 0, + $1526 = 0, + $1527 = 0, + $1528 = 0, + $1529 = 0, + $153 = 0, + $1530 = 0, + $1531 = 0, + $1532 = 0, + $1533 = 0, + $1534 = 0, + $1535 = 0, + $1536 = 0, + $1537 = 0; + var $1538 = 0, + $1539 = 0, + $154 = 0, + $1540 = 0, + $1541 = 0, + $1542 = 0, + $1543 = 0, + $1544 = 0, + $1545 = 0, + $1546 = 0, + $1547 = 0, + $1548 = 0, + $1549 = 0, + $155 = 0, + $1550 = 0, + $1551 = 0, + $1552 = 0, + $1553 = 0, + $1554 = 0, + $1555 = 0; + var $1556 = 0, + $1557 = 0, + $1558 = 0, + $1559 = 0, + $156 = 0, + $1560 = 0, + $1561 = 0, + $1562 = 0, + $1563 = 0, + $1564 = 0, + $1565 = 0, + $1566 = 0, + $1567 = 0, + $1568 = 0, + $1569 = 0, + $157 = 0, + $1570 = 0, + $1571 = 0, + $1572 = 0, + $1573 = 0; + var $1574 = 0, + $1575 = 0, + $1576 = 0, + $1577 = 0, + $1578 = 0, + $1579 = 0, + $158 = 0, + $1580 = 0, + $1581 = 0, + $1582 = 0, + $1583 = 0, + $1584 = 0, + $1585 = 0, + $1586 = 0, + $1587 = 0, + $1588 = 0, + $1589 = 0, + $159 = 0, + $1590 = 0, + $1591 = 0; + var $1592 = 0, + $1593 = 0, + $1594 = 0, + $1595 = 0, + $1596 = 0, + $1597 = 0, + $1598 = 0, + $1599 = 0, + $16 = 0, + $160 = 0, + $1600 = 0, + $1601 = 0, + $1602 = 0, + $1603 = 0, + $1604 = 0, + $1605 = 0, + $1606 = 0, + $1607 = 0, + $1608 = 0, + $1609 = 0; + var $161 = 0, + $1610 = 0, + $1611 = 0, + $1612 = 0, + $1613 = 0, + $1614 = 0, + $1615 = 0, + $1616 = 0, + $1617 = 0, + $1618 = 0, + $1619 = 0, + $162 = 0, + $1620 = 0, + $1621 = 0, + $1622 = 0, + $1623 = 0, + $1624 = 0, + $1625 = 0, + $1626 = 0, + $1627 = 0; + var $1628 = 0, + $1629 = 0, + $163 = 0, + $1630 = 0, + $1631 = 0, + $1632 = 0, + $1633 = 0, + $1634 = 0, + $1635 = 0, + $1636 = 0, + $1637 = 0, + $1638 = 0, + $1639 = 0, + $164 = 0, + $1640 = 0, + $1641 = 0, + $1642 = 0, + $1643 = 0, + $1644 = 0, + $1645 = 0; + var $1646 = 0, + $1647 = 0, + $1648 = 0, + $1649 = 0, + $165 = 0, + $1650 = 0, + $1651 = 0, + $1652 = 0, + $1653 = 0, + $1654 = 0, + $1655 = 0, + $1656 = 0, + $1657 = 0, + $1658 = 0, + $1659 = 0, + $166 = 0, + $1660 = 0, + $1661 = 0, + $1662 = 0, + $1663 = 0; + var $1664 = 0, + $1665 = 0, + $1666 = 0, + $1667 = 0, + $1668 = 0, + $1669 = 0, + $167 = 0, + $1670 = 0, + $1671 = 0, + $1672 = 0, + $1673 = 0, + $1674 = 0, + $1675 = 0, + $1676 = 0, + $1677 = 0, + $1678 = 0, + $1679 = 0, + $168 = 0, + $1680 = 0, + $1681 = 0; + var $1682 = 0, + $1683 = 0, + $1684 = 0, + $1685 = 0, + $1686 = 0, + $1687 = 0, + $1688 = 0, + $1689 = 0, + $169 = 0, + $1690 = 0, + $1691 = 0, + $1692 = 0, + $1693 = 0, + $1694 = 0, + $1695 = 0, + $1696 = 0, + $1697 = 0, + $1698 = 0, + $1699 = 0, + $17 = 0; + var $170 = 0, + $1700 = 0, + $1701 = 0, + $1702 = 0, + $1703 = 0, + $1704 = 0, + $1705 = 0, + $1706 = 0, + $1707 = 0, + $1708 = 0, + $1709 = 0, + $171 = 0, + $1710 = 0, + $1711 = 0, + $1712 = 0, + $1713 = 0, + $1714 = 0, + $1715 = 0, + $1716 = 0, + $1717 = 0; + var $1718 = 0, + $1719 = 0, + $172 = 0, + $1720 = 0, + $1721 = 0, + $1722 = 0, + $1723 = 0, + $1724 = 0, + $1725 = 0, + $1726 = 0, + $1727 = 0, + $1728 = 0, + $1729 = 0, + $173 = 0, + $1730 = 0, + $1731 = 0, + $1732 = 0, + $1733 = 0, + $1734 = 0, + $1735 = 0; + var $1736 = 0, + $1737 = 0, + $1738 = 0, + $1739 = 0, + $174 = 0, + $1740 = 0, + $1741 = 0, + $1742 = 0, + $1743 = 0, + $1744 = 0, + $1745 = 0, + $1746 = 0, + $1747 = 0, + $1748 = 0, + $1749 = 0, + $175 = 0, + $1750 = 0, + $1751 = 0, + $1752 = 0, + $1753 = 0; + var $1754 = 0, + $1755 = 0, + $1756 = 0, + $1757 = 0, + $1758 = 0, + $1759 = 0, + $176 = 0, + $1760 = 0, + $1761 = 0, + $1762 = 0, + $1763 = 0, + $1764 = 0, + $1765 = 0, + $1766 = 0, + $1767 = 0, + $1768 = 0, + $1769 = 0, + $177 = 0, + $1770 = 0, + $1771 = 0; + var $1772 = 0, + $1773 = 0, + $1774 = 0, + $1775 = 0, + $1776 = 0, + $1777 = 0, + $1778 = 0, + $1779 = 0, + $178 = 0, + $1780 = 0, + $1781 = 0, + $1782 = 0, + $1783 = 0, + $1784 = 0, + $1785 = 0, + $1786 = 0, + $1787 = 0, + $1788 = 0, + $1789 = 0, + $179 = 0; + var $1790 = 0, + $1791 = 0, + $1792 = 0, + $1793 = 0, + $1794 = 0, + $1795 = 0, + $1796 = 0, + $1797 = 0, + $1798 = 0, + $1799 = 0, + $18 = 0, + $180 = 0, + $1800 = 0, + $1801 = 0, + $1802 = 0, + $1803 = 0, + $1804 = 0, + $1805 = 0, + $1806 = 0, + $1807 = 0; + var $1808 = 0, + $1809 = 0, + $181 = 0, + $1810 = 0, + $1811 = 0, + $1812 = 0, + $1813 = 0, + $1814 = 0, + $1815 = 0, + $1816 = 0, + $1817 = 0, + $1818 = 0, + $1819 = 0, + $182 = 0, + $1820 = 0, + $1821 = 0, + $1822 = 0, + $1823 = 0, + $1824 = 0, + $1825 = 0; + var $1826 = 0, + $1827 = 0, + $1828 = 0, + $1829 = 0, + $183 = 0, + $1830 = 0, + $1831 = 0, + $1832 = 0, + $1833 = 0, + $1834 = 0, + $1835 = 0, + $1836 = 0, + $1837 = 0, + $1838 = 0, + $1839 = 0, + $184 = 0, + $1840 = 0, + $1841 = 0, + $1842 = 0, + $1843 = 0; + var $1844 = 0, + $1845 = 0, + $1846 = 0, + $1847 = 0, + $1848 = 0, + $1849 = 0, + $185 = 0, + $1850 = 0, + $1851 = 0, + $1852 = 0, + $1853 = 0, + $1854 = 0, + $1855 = 0, + $1856 = 0, + $1857 = 0, + $1858 = 0, + $1859 = 0, + $186 = 0, + $1860 = 0, + $1861 = 0; + var $1862 = 0, + $1863 = 0, + $1864 = 0, + $1865 = 0, + $1866 = 0, + $1867 = 0, + $1868 = 0, + $1869 = 0, + $187 = 0, + $1870 = 0, + $1871 = 0, + $1872 = 0, + $1873 = 0, + $1874 = 0, + $1875 = 0, + $1876 = 0, + $1877 = 0, + $1878 = 0, + $1879 = 0, + $188 = 0; + var $1880 = 0, + $1881 = 0, + $1882 = 0, + $1883 = 0, + $1884 = 0, + $1885 = 0, + $1886 = 0, + $1887 = 0, + $1888 = 0, + $1889 = 0, + $189 = 0, + $1890 = 0, + $1891 = 0, + $1892 = 0, + $1893 = 0, + $1894 = 0, + $1895 = 0, + $1896 = 0, + $1897 = 0, + $1898 = 0; + var $1899 = 0, + $19 = 0, + $190 = 0, + $1900 = 0, + $1901 = 0, + $1902 = 0, + $1903 = 0, + $1904 = 0, + $1905 = 0, + $1906 = 0, + $1907 = 0, + $1908 = 0, + $1909 = 0, + $191 = 0, + $1910 = 0, + $1911 = 0, + $1912 = 0, + $1913 = 0, + $1914 = 0, + $1915 = 0; + var $1916 = 0, + $1917 = 0, + $1918 = 0, + $1919 = 0, + $192 = 0, + $1920 = 0, + $1921 = 0, + $1922 = 0, + $1923 = 0, + $1924 = 0, + $1925 = 0, + $1926 = 0, + $1927 = 0, + $1928 = 0, + $1929 = 0, + $193 = 0, + $1930 = 0, + $1931 = 0, + $1932 = 0, + $1933 = 0; + var $1934 = 0, + $1935 = 0, + $1936 = 0, + $1937 = 0, + $1938 = 0, + $1939 = 0, + $194 = 0, + $1940 = 0, + $1941 = 0, + $1942 = 0, + $1943 = 0, + $1944 = 0, + $1945 = 0, + $1946 = 0, + $1947 = 0, + $1948 = 0, + $1949 = 0, + $195 = 0, + $1950 = 0, + $1951 = 0; + var $1952 = 0, + $1953 = 0, + $1954 = 0, + $1955 = 0, + $1956 = 0, + $1957 = 0, + $1958 = 0, + $1959 = 0, + $196 = 0, + $1960 = 0, + $1961 = 0, + $1962 = 0, + $1963 = 0, + $1964 = 0, + $1965 = 0, + $1966 = 0, + $1967 = 0, + $1968 = 0, + $1969 = 0, + $197 = 0; + var $1970 = 0, + $1971 = 0, + $1972 = 0, + $1973 = 0, + $1974 = 0, + $1975 = 0, + $1976 = 0, + $1977 = 0, + $1978 = 0, + $1979 = 0, + $198 = 0, + $1980 = 0, + $1981 = 0, + $1982 = 0, + $1983 = 0, + $1984 = 0, + $1985 = 0, + $1986 = 0, + $1987 = 0, + $1988 = 0; + var $1989 = 0, + $199 = 0, + $1990 = 0, + $1991 = 0, + $1992 = 0, + $1993 = 0, + $1994 = 0, + $1995 = 0, + $1996 = 0, + $1997 = 0, + $1998 = 0, + $1999 = 0, + $2 = 0, + $20 = 0, + $200 = 0, + $2000 = 0, + $2001 = 0, + $2002 = 0, + $2003 = 0, + $2004 = 0; + var $2005 = 0, + $2006 = 0, + $2007 = 0, + $2008 = 0, + $2009 = 0, + $201 = 0, + $2010 = 0, + $2011 = 0, + $2012 = 0, + $2013 = 0, + $2014 = 0, + $2015 = 0, + $2016 = 0, + $2017 = 0, + $2018 = 0, + $2019 = 0, + $202 = 0, + $2020 = 0, + $2021 = 0, + $2022 = 0; + var $2023 = 0, + $2024 = 0, + $2025 = 0, + $2026 = 0, + $2027 = 0, + $2028 = 0, + $2029 = 0, + $203 = 0, + $2030 = 0, + $2031 = 0, + $2032 = 0, + $2033 = 0, + $2034 = 0, + $2035 = 0, + $2036 = 0, + $2037 = 0, + $2038 = 0, + $2039 = 0, + $204 = 0, + $2040 = 0; + var $2041 = 0, + $2042 = 0, + $2043 = 0, + $2044 = 0, + $2045 = 0, + $2046 = 0, + $2047 = 0, + $2048 = 0, + $2049 = 0, + $205 = 0, + $2050 = 0, + $2051 = 0, + $2052 = 0, + $2053 = 0, + $2054 = 0, + $2055 = 0, + $2056 = 0, + $2057 = 0, + $2058 = 0, + $2059 = 0; + var $206 = 0, + $2060 = 0, + $2061 = 0, + $2062 = 0, + $2063 = 0, + $2064 = 0, + $2065 = 0, + $2066 = 0, + $2067 = 0, + $2068 = 0, + $2069 = 0, + $207 = 0, + $2070 = 0, + $2071 = 0, + $2072 = 0, + $2073 = 0, + $2074 = 0, + $2075 = 0, + $2076 = 0, + $2077 = 0; + var $2078 = 0, + $2079 = 0, + $208 = 0, + $2080 = 0, + $2081 = 0, + $2082 = 0, + $2083 = 0, + $2084 = 0, + $2085 = 0, + $2086 = 0, + $2087 = 0, + $209 = 0, + $21 = 0, + $210 = 0, + $211 = 0, + $212 = 0, + $213 = 0, + $214 = 0, + $215 = 0, + $216 = 0; + var $217 = 0, + $218 = 0, + $219 = 0, + $22 = 0, + $220 = 0, + $221 = 0, + $222 = 0, + $223 = 0, + $224 = 0, + $225 = 0, + $226 = 0, + $227 = 0, + $228 = 0, + $229 = 0, + $23 = 0, + $230 = 0, + $231 = 0, + $232 = 0, + $233 = 0, + $234 = 0; + var $235 = 0, + $236 = 0, + $237 = 0, + $238 = 0, + $239 = 0, + $24 = 0, + $240 = 0, + $241 = 0, + $242 = 0, + $243 = 0, + $244 = 0, + $245 = 0, + $246 = 0, + $247 = 0, + $248 = 0, + $249 = 0, + $25 = 0, + $250 = 0, + $251 = 0, + $252 = 0; + var $253 = 0, + $254 = 0, + $255 = 0, + $256 = 0, + $257 = 0, + $258 = 0, + $259 = 0, + $26 = 0, + $260 = 0, + $261 = 0, + $262 = 0, + $263 = 0, + $264 = 0, + $265 = 0, + $266 = 0, + $267 = 0, + $268 = 0, + $269 = 0, + $27 = 0, + $270 = 0; + var $271 = 0, + $272 = 0, + $273 = 0, + $274 = 0, + $275 = 0, + $276 = 0, + $277 = 0, + $278 = 0, + $279 = 0, + $28 = 0, + $280 = 0, + $281 = 0, + $282 = 0, + $283 = 0, + $284 = 0, + $285 = 0, + $286 = 0, + $287 = 0, + $288 = 0, + $289 = 0; + var $29 = 0, + $290 = 0, + $291 = 0, + $292 = 0, + $293 = 0, + $294 = 0, + $295 = 0, + $296 = 0, + $297 = 0, + $298 = 0, + $299 = 0, + $3 = 0, + $30 = 0, + $300 = 0, + $301 = 0, + $302 = 0, + $303 = 0, + $304 = 0, + $305 = 0, + $306 = 0; + var $307 = 0, + $308 = 0, + $309 = 0, + $31 = 0, + $310 = 0, + $311 = 0, + $312 = 0, + $313 = 0, + $314 = 0, + $315 = 0, + $316 = 0, + $317 = 0, + $318 = 0, + $319 = 0, + $32 = 0, + $320 = 0, + $321 = 0, + $322 = 0, + $323 = 0, + $324 = 0; + var $325 = 0, + $326 = 0, + $327 = 0, + $328 = 0, + $329 = 0, + $33 = 0, + $330 = 0, + $331 = 0, + $332 = 0, + $333 = 0, + $334 = 0, + $335 = 0, + $336 = 0, + $337 = 0, + $338 = 0, + $339 = 0, + $34 = 0, + $340 = 0, + $341 = 0, + $342 = 0; + var $343 = 0, + $344 = 0, + $345 = 0, + $346 = 0, + $347 = 0, + $348 = 0, + $349 = 0, + $35 = 0, + $350 = 0, + $351 = 0, + $352 = 0, + $353 = 0, + $354 = 0, + $355 = 0, + $356 = 0, + $357 = 0, + $358 = 0, + $359 = 0, + $36 = 0, + $360 = 0; + var $361 = 0, + $362 = 0, + $363 = 0, + $364 = 0, + $365 = 0, + $366 = 0, + $367 = 0, + $368 = 0, + $369 = 0, + $37 = 0, + $370 = 0, + $371 = 0, + $372 = 0, + $373 = 0, + $374 = 0, + $375 = 0, + $376 = 0, + $377 = 0, + $378 = 0, + $379 = 0; + var $38 = 0, + $380 = 0, + $381 = 0, + $382 = 0, + $383 = 0, + $384 = 0, + $385 = 0, + $386 = 0, + $387 = 0, + $388 = 0, + $389 = 0, + $39 = 0, + $390 = 0, + $391 = 0, + $392 = 0, + $393 = 0, + $394 = 0, + $395 = 0, + $396 = 0, + $397 = 0; + var $398 = 0, + $399 = 0, + $4 = 0, + $40 = 0, + $400 = 0, + $401 = 0, + $402 = 0, + $403 = 0, + $404 = 0, + $405 = 0, + $406 = 0, + $407 = 0, + $408 = 0, + $409 = 0, + $41 = 0, + $410 = 0, + $411 = 0, + $412 = 0, + $413 = 0, + $414 = 0; + var $415 = 0, + $416 = 0, + $417 = 0, + $418 = 0, + $419 = 0, + $42 = 0, + $420 = 0, + $421 = 0, + $422 = 0, + $423 = 0, + $424 = 0, + $425 = 0, + $426 = 0, + $427 = 0, + $428 = 0, + $429 = 0, + $43 = 0, + $430 = 0, + $431 = 0, + $432 = 0; + var $433 = 0, + $434 = 0, + $435 = 0, + $436 = 0, + $437 = 0, + $438 = 0, + $439 = 0, + $44 = 0, + $440 = 0, + $441 = 0, + $442 = 0, + $443 = 0, + $444 = 0, + $445 = 0, + $446 = 0, + $447 = 0, + $448 = 0, + $449 = 0, + $45 = 0, + $450 = 0; + var $451 = 0, + $452 = 0, + $453 = 0, + $454 = 0, + $455 = 0, + $456 = 0, + $457 = 0, + $458 = 0, + $459 = 0, + $46 = 0, + $460 = 0, + $461 = 0, + $462 = 0, + $463 = 0, + $464 = 0, + $465 = 0, + $466 = 0, + $467 = 0, + $468 = 0, + $469 = 0; + var $47 = 0, + $470 = 0, + $471 = 0, + $472 = 0, + $473 = 0, + $474 = 0, + $475 = 0, + $476 = 0, + $477 = 0, + $478 = 0, + $479 = 0, + $48 = 0, + $480 = 0, + $481 = 0, + $482 = 0, + $483 = 0, + $484 = 0, + $485 = 0, + $486 = 0, + $487 = 0; + var $488 = 0, + $489 = 0, + $49 = 0, + $490 = 0, + $491 = 0, + $492 = 0, + $493 = 0, + $494 = 0, + $495 = 0, + $496 = 0, + $497 = 0, + $498 = 0, + $499 = 0, + $5 = 0, + $50 = 0, + $500 = 0, + $501 = 0, + $502 = 0, + $503 = 0, + $504 = 0; + var $505 = 0, + $506 = 0, + $507 = 0, + $508 = 0, + $509 = 0, + $51 = 0, + $510 = 0, + $511 = 0, + $512 = 0, + $513 = 0, + $514 = 0, + $515 = 0, + $516 = 0, + $517 = 0, + $518 = 0, + $519 = 0, + $52 = 0, + $520 = 0, + $521 = 0, + $522 = 0; + var $523 = 0, + $524 = 0, + $525 = 0, + $526 = 0, + $527 = 0, + $528 = 0, + $529 = 0, + $53 = 0, + $530 = 0, + $531 = 0, + $532 = 0, + $533 = 0, + $534 = 0, + $535 = 0, + $536 = 0, + $537 = 0, + $538 = 0, + $539 = 0, + $54 = 0, + $540 = 0; + var $541 = 0, + $542 = 0, + $543 = 0, + $544 = 0, + $545 = 0, + $546 = 0, + $547 = 0, + $548 = 0, + $549 = 0, + $55 = 0, + $550 = 0, + $551 = 0, + $552 = 0, + $553 = 0, + $554 = 0, + $555 = 0, + $556 = 0, + $557 = 0, + $558 = 0, + $559 = 0; + var $56 = 0, + $560 = 0, + $561 = 0, + $562 = 0, + $563 = 0, + $564 = 0, + $565 = 0, + $566 = 0, + $567 = 0, + $568 = 0, + $569 = 0, + $57 = 0, + $570 = 0, + $571 = 0, + $572 = 0, + $573 = 0, + $574 = 0, + $575 = 0, + $576 = 0, + $577 = 0; + var $578 = 0, + $579 = 0, + $58 = 0, + $580 = 0, + $581 = 0, + $582 = 0, + $583 = 0, + $584 = 0, + $585 = 0, + $586 = 0, + $587 = 0, + $588 = 0, + $589 = 0, + $59 = 0, + $590 = 0, + $591 = 0, + $592 = 0, + $593 = 0, + $594 = 0, + $595 = 0; + var $596 = 0, + $597 = 0, + $598 = 0, + $599 = 0, + $6 = 0, + $60 = 0, + $600 = 0, + $601 = 0, + $602 = 0, + $603 = 0, + $604 = 0, + $605 = 0, + $606 = 0, + $607 = 0, + $608 = 0, + $609 = 0, + $61 = 0, + $610 = 0, + $611 = 0, + $612 = 0; + var $613 = 0, + $614 = 0, + $615 = 0, + $616 = 0, + $617 = 0, + $618 = 0, + $619 = 0, + $62 = 0, + $620 = 0, + $621 = 0, + $622 = 0, + $623 = 0, + $624 = 0, + $625 = 0, + $626 = 0, + $627 = 0, + $628 = 0, + $629 = 0, + $63 = 0, + $630 = 0; + var $631 = 0, + $632 = 0, + $633 = 0, + $634 = 0, + $635 = 0, + $636 = 0, + $637 = 0, + $638 = 0, + $639 = 0, + $64 = 0, + $640 = 0, + $641 = 0, + $642 = 0, + $643 = 0, + $644 = 0, + $645 = 0, + $646 = 0, + $647 = 0, + $648 = 0, + $649 = 0; + var $65 = 0, + $650 = 0, + $651 = 0, + $652 = 0, + $653 = 0, + $654 = 0, + $655 = 0, + $656 = 0, + $657 = 0, + $658 = 0, + $659 = 0, + $66 = 0, + $660 = 0, + $661 = 0, + $662 = 0, + $663 = 0, + $664 = 0, + $665 = 0, + $666 = 0, + $667 = 0; + var $668 = 0, + $669 = 0, + $67 = 0, + $670 = 0, + $671 = 0, + $672 = 0, + $673 = 0, + $674 = 0, + $675 = 0, + $676 = 0, + $677 = 0, + $678 = 0, + $679 = 0, + $68 = 0, + $680 = 0, + $681 = 0, + $682 = 0, + $683 = 0, + $684 = 0, + $685 = 0; + var $686 = 0, + $687 = 0, + $688 = 0, + $689 = 0, + $69 = 0, + $690 = 0, + $691 = 0, + $692 = 0, + $693 = 0, + $694 = 0, + $695 = 0, + $696 = 0, + $697 = 0, + $698 = 0, + $699 = 0, + $7 = 0, + $70 = 0, + $700 = 0, + $701 = 0, + $702 = 0; + var $703 = 0, + $704 = 0, + $705 = 0, + $706 = 0, + $707 = 0, + $708 = 0, + $709 = 0, + $71 = 0, + $710 = 0, + $711 = 0, + $712 = 0, + $713 = 0, + $714 = 0, + $715 = 0, + $716 = 0, + $717 = 0, + $718 = 0, + $719 = 0, + $72 = 0, + $720 = 0; + var $721 = 0, + $722 = 0, + $723 = 0, + $724 = 0, + $725 = 0, + $726 = 0, + $727 = 0, + $728 = 0, + $729 = 0, + $73 = 0, + $730 = 0, + $731 = 0, + $732 = 0, + $733 = 0, + $734 = 0, + $735 = 0, + $736 = 0, + $737 = 0, + $738 = 0, + $739 = 0; + var $74 = 0, + $740 = 0, + $741 = 0, + $742 = 0, + $743 = 0, + $744 = 0, + $745 = 0, + $746 = 0, + $747 = 0, + $748 = 0, + $749 = 0, + $75 = 0, + $750 = 0, + $751 = 0, + $752 = 0, + $753 = 0, + $754 = 0, + $755 = 0, + $756 = 0, + $757 = 0; + var $758 = 0, + $759 = 0, + $76 = 0, + $760 = 0, + $761 = 0, + $762 = 0, + $763 = 0, + $764 = 0, + $765 = 0, + $766 = 0, + $767 = 0, + $768 = 0, + $769 = 0, + $77 = 0, + $770 = 0, + $771 = 0, + $772 = 0, + $773 = 0, + $774 = 0, + $775 = 0; + var $776 = 0, + $777 = 0, + $778 = 0, + $779 = 0, + $78 = 0, + $780 = 0, + $781 = 0, + $782 = 0, + $783 = 0, + $784 = 0, + $785 = 0, + $786 = 0, + $787 = 0, + $788 = 0, + $789 = 0, + $79 = 0, + $790 = 0, + $791 = 0, + $792 = 0, + $793 = 0; + var $794 = 0, + $795 = 0, + $796 = 0, + $797 = 0, + $798 = 0, + $799 = 0, + $8 = 0, + $80 = 0, + $800 = 0, + $801 = 0, + $802 = 0, + $803 = 0, + $804 = 0, + $805 = 0, + $806 = 0, + $807 = 0, + $808 = 0, + $809 = 0, + $81 = 0, + $810 = 0; + var $811 = 0, + $812 = 0, + $813 = 0, + $814 = 0, + $815 = 0, + $816 = 0, + $817 = 0, + $818 = 0, + $819 = 0, + $82 = 0, + $820 = 0, + $821 = 0, + $822 = 0, + $823 = 0, + $824 = 0, + $825 = 0, + $826 = 0, + $827 = 0, + $828 = 0, + $829 = 0; + var $83 = 0, + $830 = 0, + $831 = 0, + $832 = 0, + $833 = 0, + $834 = 0, + $835 = 0, + $836 = 0, + $837 = 0, + $838 = 0, + $839 = 0, + $84 = 0, + $840 = 0, + $841 = 0, + $842 = 0, + $843 = 0, + $844 = 0, + $845 = 0, + $846 = 0, + $847 = 0; + var $848 = 0, + $849 = 0, + $85 = 0, + $850 = 0, + $851 = 0, + $852 = 0, + $853 = 0, + $854 = 0, + $855 = 0, + $856 = 0, + $857 = 0, + $858 = 0, + $859 = 0, + $86 = 0, + $860 = 0, + $861 = 0, + $862 = 0, + $863 = 0, + $864 = 0, + $865 = 0; + var $866 = 0, + $867 = 0, + $868 = 0, + $869 = 0, + $87 = 0, + $870 = 0, + $871 = 0, + $872 = 0, + $873 = 0, + $874 = 0, + $875 = 0, + $876 = 0, + $877 = 0, + $878 = 0, + $879 = 0, + $88 = 0, + $880 = 0, + $881 = 0, + $882 = 0, + $883 = 0; + var $884 = 0, + $885 = 0, + $886 = 0, + $887 = 0, + $888 = 0, + $889 = 0, + $89 = 0, + $890 = 0, + $891 = 0, + $892 = 0, + $893 = 0, + $894 = 0, + $895 = 0, + $896 = 0, + $897 = 0, + $898 = 0, + $899 = 0, + $9 = 0, + $90 = 0, + $900 = 0; + var $901 = 0, + $902 = 0, + $903 = 0, + $904 = 0, + $905 = 0, + $906 = 0, + $907 = 0, + $908 = 0, + $909 = 0, + $91 = 0, + $910 = 0, + $911 = 0, + $912 = 0, + $913 = 0, + $914 = 0, + $915 = 0, + $916 = 0, + $917 = 0, + $918 = 0, + $919 = 0; + var $92 = 0, + $920 = 0, + $921 = 0, + $922 = 0, + $923 = 0, + $924 = 0, + $925 = 0, + $926 = 0, + $927 = 0, + $928 = 0, + $929 = 0, + $93 = 0, + $930 = 0, + $931 = 0, + $932 = 0, + $933 = 0, + $934 = 0, + $935 = 0, + $936 = 0, + $937 = 0; + var $938 = 0, + $939 = 0, + $94 = 0, + $940 = 0, + $941 = 0, + $942 = 0, + $943 = 0, + $944 = 0, + $945 = 0, + $946 = 0, + $947 = 0, + $948 = 0, + $949 = 0, + $95 = 0, + $950 = 0, + $951 = 0, + $952 = 0, + $953 = 0, + $954 = 0, + $955 = 0; + var $956 = 0, + $957 = 0, + $958 = 0, + $959 = 0, + $96 = 0, + $960 = 0, + $961 = 0, + $962 = 0, + $963 = 0, + $964 = 0, + $965 = 0, + $966 = 0, + $967 = 0, + $968 = 0, + $969 = 0, + $97 = 0, + $970 = 0, + $971 = 0, + $972 = 0, + $973 = 0; + var $974 = 0, + $975 = 0, + $976 = 0, + $977 = 0, + $978 = 0, + $979 = 0, + $98 = 0, + $980 = 0, + $981 = 0, + $982 = 0, + $983 = 0, + $984 = 0, + $985 = 0, + $986 = 0, + $987 = 0, + $988 = 0, + $989 = 0, + $99 = 0, + $990 = 0, + $991 = 0; + var $992 = 0, + $993 = 0, + $994 = 0, + $995 = 0, + $996 = 0, + $997 = 0, + $998 = 0, + $999 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = $in2; + $1 = $0; + $2 = HEAP32[$1 >> 2] | 0; + $3 = ($0 + 4) | 0; + $4 = $3; + $5 = HEAP32[$4 >> 2] | 0; + $6 = _bitshift64Ashr(0, $2 | 0, 32) | 0; + $7 = tempRet0; + $8 = $in; + $9 = $8; + $10 = HEAP32[$9 >> 2] | 0; + $11 = ($8 + 4) | 0; + $12 = $11; + $13 = HEAP32[$12 >> 2] | 0; + $14 = _bitshift64Ashr(0, $10 | 0, 32) | 0; + $15 = tempRet0; + $16 = ___muldi3($14 | 0, $15 | 0, $6 | 0, $7 | 0) | 0; + $17 = tempRet0; + $18 = $output; + $19 = $18; + HEAP32[$19 >> 2] = $16; + $20 = ($18 + 4) | 0; + $21 = $20; + HEAP32[$21 >> 2] = $17; + $22 = $in2; + $23 = $22; + $24 = HEAP32[$23 >> 2] | 0; + $25 = ($22 + 4) | 0; + $26 = $25; + $27 = HEAP32[$26 >> 2] | 0; + $28 = _bitshift64Ashr(0, $24 | 0, 32) | 0; + $29 = tempRet0; + $30 = ($in + 8) | 0; + $31 = $30; + $32 = $31; + $33 = HEAP32[$32 >> 2] | 0; + $34 = ($31 + 4) | 0; + $35 = $34; + $36 = HEAP32[$35 >> 2] | 0; + $37 = _bitshift64Ashr(0, $33 | 0, 32) | 0; + $38 = tempRet0; + $39 = ___muldi3($37 | 0, $38 | 0, $28 | 0, $29 | 0) | 0; + $40 = tempRet0; + $41 = ($in2 + 8) | 0; + $42 = $41; + $43 = $42; + $44 = HEAP32[$43 >> 2] | 0; + $45 = ($42 + 4) | 0; + $46 = $45; + $47 = HEAP32[$46 >> 2] | 0; + $48 = _bitshift64Ashr(0, $44 | 0, 32) | 0; + $49 = tempRet0; + $50 = $in; + $51 = $50; + $52 = HEAP32[$51 >> 2] | 0; + $53 = ($50 + 4) | 0; + $54 = $53; + $55 = HEAP32[$54 >> 2] | 0; + $56 = _bitshift64Ashr(0, $52 | 0, 32) | 0; + $57 = tempRet0; + $58 = ___muldi3($56 | 0, $57 | 0, $48 | 0, $49 | 0) | 0; + $59 = tempRet0; + $60 = _i64Add($58 | 0, $59 | 0, $39 | 0, $40 | 0) | 0; + $61 = tempRet0; + $62 = ($output + 8) | 0; + $63 = $62; + $64 = $63; + HEAP32[$64 >> 2] = $60; + $65 = ($63 + 4) | 0; + $66 = $65; + HEAP32[$66 >> 2] = $61; + $67 = $41; + $68 = $67; + $69 = HEAP32[$68 >> 2] | 0; + $70 = ($67 + 4) | 0; + $71 = $70; + $72 = HEAP32[$71 >> 2] | 0; + $73 = _bitshift64Ashr(0, $69 | 0, 31) | 0; + $74 = tempRet0; + $75 = $30; + $76 = $75; + $77 = HEAP32[$76 >> 2] | 0; + $78 = ($75 + 4) | 0; + $79 = $78; + $80 = HEAP32[$79 >> 2] | 0; + $81 = _bitshift64Ashr(0, $77 | 0, 32) | 0; + $82 = tempRet0; + $83 = ___muldi3($81 | 0, $82 | 0, $73 | 0, $74 | 0) | 0; + $84 = tempRet0; + $85 = $in2; + $86 = $85; + $87 = HEAP32[$86 >> 2] | 0; + $88 = ($85 + 4) | 0; + $89 = $88; + $90 = HEAP32[$89 >> 2] | 0; + $91 = _bitshift64Ashr(0, $87 | 0, 32) | 0; + $92 = tempRet0; + $93 = ($in + 16) | 0; + $94 = $93; + $95 = $94; + $96 = HEAP32[$95 >> 2] | 0; + $97 = ($94 + 4) | 0; + $98 = $97; + $99 = HEAP32[$98 >> 2] | 0; + $100 = _bitshift64Ashr(0, $96 | 0, 32) | 0; + $101 = tempRet0; + $102 = ___muldi3($100 | 0, $101 | 0, $91 | 0, $92 | 0) | 0; + $103 = tempRet0; + $104 = _i64Add($102 | 0, $103 | 0, $83 | 0, $84 | 0) | 0; + $105 = tempRet0; + $106 = ($in2 + 16) | 0; + $107 = $106; + $108 = $107; + $109 = HEAP32[$108 >> 2] | 0; + $110 = ($107 + 4) | 0; + $111 = $110; + $112 = HEAP32[$111 >> 2] | 0; + $113 = _bitshift64Ashr(0, $109 | 0, 32) | 0; + $114 = tempRet0; + $115 = $in; + $116 = $115; + $117 = HEAP32[$116 >> 2] | 0; + $118 = ($115 + 4) | 0; + $119 = $118; + $120 = HEAP32[$119 >> 2] | 0; + $121 = _bitshift64Ashr(0, $117 | 0, 32) | 0; + $122 = tempRet0; + $123 = ___muldi3($121 | 0, $122 | 0, $113 | 0, $114 | 0) | 0; + $124 = tempRet0; + $125 = _i64Add($104 | 0, $105 | 0, $123 | 0, $124 | 0) | 0; + $126 = tempRet0; + $127 = ($output + 16) | 0; + $128 = $127; + $129 = $128; + HEAP32[$129 >> 2] = $125; + $130 = ($128 + 4) | 0; + $131 = $130; + HEAP32[$131 >> 2] = $126; + $132 = $41; + $133 = $132; + $134 = HEAP32[$133 >> 2] | 0; + $135 = ($132 + 4) | 0; + $136 = $135; + $137 = HEAP32[$136 >> 2] | 0; + $138 = _bitshift64Ashr(0, $134 | 0, 32) | 0; + $139 = tempRet0; + $140 = $93; + $141 = $140; + $142 = HEAP32[$141 >> 2] | 0; + $143 = ($140 + 4) | 0; + $144 = $143; + $145 = HEAP32[$144 >> 2] | 0; + $146 = _bitshift64Ashr(0, $142 | 0, 32) | 0; + $147 = tempRet0; + $148 = ___muldi3($146 | 0, $147 | 0, $138 | 0, $139 | 0) | 0; + $149 = tempRet0; + $150 = $106; + $151 = $150; + $152 = HEAP32[$151 >> 2] | 0; + $153 = ($150 + 4) | 0; + $154 = $153; + $155 = HEAP32[$154 >> 2] | 0; + $156 = _bitshift64Ashr(0, $152 | 0, 32) | 0; + $157 = tempRet0; + $158 = $30; + $159 = $158; + $160 = HEAP32[$159 >> 2] | 0; + $161 = ($158 + 4) | 0; + $162 = $161; + $163 = HEAP32[$162 >> 2] | 0; + $164 = _bitshift64Ashr(0, $160 | 0, 32) | 0; + $165 = tempRet0; + $166 = ___muldi3($164 | 0, $165 | 0, $156 | 0, $157 | 0) | 0; + $167 = tempRet0; + $168 = _i64Add($166 | 0, $167 | 0, $148 | 0, $149 | 0) | 0; + $169 = tempRet0; + $170 = $in2; + $171 = $170; + $172 = HEAP32[$171 >> 2] | 0; + $173 = ($170 + 4) | 0; + $174 = $173; + $175 = HEAP32[$174 >> 2] | 0; + $176 = _bitshift64Ashr(0, $172 | 0, 32) | 0; + $177 = tempRet0; + $178 = ($in + 24) | 0; + $179 = $178; + $180 = $179; + $181 = HEAP32[$180 >> 2] | 0; + $182 = ($179 + 4) | 0; + $183 = $182; + $184 = HEAP32[$183 >> 2] | 0; + $185 = _bitshift64Ashr(0, $181 | 0, 32) | 0; + $186 = tempRet0; + $187 = ___muldi3($185 | 0, $186 | 0, $176 | 0, $177 | 0) | 0; + $188 = tempRet0; + $189 = _i64Add($168 | 0, $169 | 0, $187 | 0, $188 | 0) | 0; + $190 = tempRet0; + $191 = ($in2 + 24) | 0; + $192 = $191; + $193 = $192; + $194 = HEAP32[$193 >> 2] | 0; + $195 = ($192 + 4) | 0; + $196 = $195; + $197 = HEAP32[$196 >> 2] | 0; + $198 = _bitshift64Ashr(0, $194 | 0, 32) | 0; + $199 = tempRet0; + $200 = $in; + $201 = $200; + $202 = HEAP32[$201 >> 2] | 0; + $203 = ($200 + 4) | 0; + $204 = $203; + $205 = HEAP32[$204 >> 2] | 0; + $206 = _bitshift64Ashr(0, $202 | 0, 32) | 0; + $207 = tempRet0; + $208 = ___muldi3($206 | 0, $207 | 0, $198 | 0, $199 | 0) | 0; + $209 = tempRet0; + $210 = _i64Add($189 | 0, $190 | 0, $208 | 0, $209 | 0) | 0; + $211 = tempRet0; + $212 = ($output + 24) | 0; + $213 = $212; + $214 = $213; + HEAP32[$214 >> 2] = $210; + $215 = ($213 + 4) | 0; + $216 = $215; + HEAP32[$216 >> 2] = $211; + $217 = $106; + $218 = $217; + $219 = HEAP32[$218 >> 2] | 0; + $220 = ($217 + 4) | 0; + $221 = $220; + $222 = HEAP32[$221 >> 2] | 0; + $223 = _bitshift64Ashr(0, $219 | 0, 32) | 0; + $224 = tempRet0; + $225 = $93; + $226 = $225; + $227 = HEAP32[$226 >> 2] | 0; + $228 = ($225 + 4) | 0; + $229 = $228; + $230 = HEAP32[$229 >> 2] | 0; + $231 = _bitshift64Ashr(0, $227 | 0, 32) | 0; + $232 = tempRet0; + $233 = ___muldi3($231 | 0, $232 | 0, $223 | 0, $224 | 0) | 0; + $234 = tempRet0; + $235 = $41; + $236 = $235; + $237 = HEAP32[$236 >> 2] | 0; + $238 = ($235 + 4) | 0; + $239 = $238; + $240 = HEAP32[$239 >> 2] | 0; + $241 = _bitshift64Ashr(0, $237 | 0, 32) | 0; + $242 = tempRet0; + $243 = $178; + $244 = $243; + $245 = HEAP32[$244 >> 2] | 0; + $246 = ($243 + 4) | 0; + $247 = $246; + $248 = HEAP32[$247 >> 2] | 0; + $249 = _bitshift64Ashr(0, $245 | 0, 32) | 0; + $250 = tempRet0; + $251 = ___muldi3($249 | 0, $250 | 0, $241 | 0, $242 | 0) | 0; + $252 = tempRet0; + $253 = $191; + $254 = $253; + $255 = HEAP32[$254 >> 2] | 0; + $256 = ($253 + 4) | 0; + $257 = $256; + $258 = HEAP32[$257 >> 2] | 0; + $259 = _bitshift64Ashr(0, $255 | 0, 32) | 0; + $260 = tempRet0; + $261 = $30; + $262 = $261; + $263 = HEAP32[$262 >> 2] | 0; + $264 = ($261 + 4) | 0; + $265 = $264; + $266 = HEAP32[$265 >> 2] | 0; + $267 = _bitshift64Ashr(0, $263 | 0, 32) | 0; + $268 = tempRet0; + $269 = ___muldi3($267 | 0, $268 | 0, $259 | 0, $260 | 0) | 0; + $270 = tempRet0; + $271 = _i64Add($269 | 0, $270 | 0, $251 | 0, $252 | 0) | 0; + $272 = tempRet0; + $273 = _bitshift64Shl($271 | 0, $272 | 0, 1) | 0; + $274 = tempRet0; + $275 = _i64Add($273 | 0, $274 | 0, $233 | 0, $234 | 0) | 0; + $276 = tempRet0; + $277 = $in2; + $278 = $277; + $279 = HEAP32[$278 >> 2] | 0; + $280 = ($277 + 4) | 0; + $281 = $280; + $282 = HEAP32[$281 >> 2] | 0; + $283 = _bitshift64Ashr(0, $279 | 0, 32) | 0; + $284 = tempRet0; + $285 = ($in + 32) | 0; + $286 = $285; + $287 = $286; + $288 = HEAP32[$287 >> 2] | 0; + $289 = ($286 + 4) | 0; + $290 = $289; + $291 = HEAP32[$290 >> 2] | 0; + $292 = _bitshift64Ashr(0, $288 | 0, 32) | 0; + $293 = tempRet0; + $294 = ___muldi3($292 | 0, $293 | 0, $283 | 0, $284 | 0) | 0; + $295 = tempRet0; + $296 = _i64Add($275 | 0, $276 | 0, $294 | 0, $295 | 0) | 0; + $297 = tempRet0; + $298 = ($in2 + 32) | 0; + $299 = $298; + $300 = $299; + $301 = HEAP32[$300 >> 2] | 0; + $302 = ($299 + 4) | 0; + $303 = $302; + $304 = HEAP32[$303 >> 2] | 0; + $305 = _bitshift64Ashr(0, $301 | 0, 32) | 0; + $306 = tempRet0; + $307 = $in; + $308 = $307; + $309 = HEAP32[$308 >> 2] | 0; + $310 = ($307 + 4) | 0; + $311 = $310; + $312 = HEAP32[$311 >> 2] | 0; + $313 = _bitshift64Ashr(0, $309 | 0, 32) | 0; + $314 = tempRet0; + $315 = ___muldi3($313 | 0, $314 | 0, $305 | 0, $306 | 0) | 0; + $316 = tempRet0; + $317 = _i64Add($296 | 0, $297 | 0, $315 | 0, $316 | 0) | 0; + $318 = tempRet0; + $319 = ($output + 32) | 0; + $320 = $319; + $321 = $320; + HEAP32[$321 >> 2] = $317; + $322 = ($320 + 4) | 0; + $323 = $322; + HEAP32[$323 >> 2] = $318; + $324 = $106; + $325 = $324; + $326 = HEAP32[$325 >> 2] | 0; + $327 = ($324 + 4) | 0; + $328 = $327; + $329 = HEAP32[$328 >> 2] | 0; + $330 = _bitshift64Ashr(0, $326 | 0, 32) | 0; + $331 = tempRet0; + $332 = $178; + $333 = $332; + $334 = HEAP32[$333 >> 2] | 0; + $335 = ($332 + 4) | 0; + $336 = $335; + $337 = HEAP32[$336 >> 2] | 0; + $338 = _bitshift64Ashr(0, $334 | 0, 32) | 0; + $339 = tempRet0; + $340 = ___muldi3($338 | 0, $339 | 0, $330 | 0, $331 | 0) | 0; + $341 = tempRet0; + $342 = $191; + $343 = $342; + $344 = HEAP32[$343 >> 2] | 0; + $345 = ($342 + 4) | 0; + $346 = $345; + $347 = HEAP32[$346 >> 2] | 0; + $348 = _bitshift64Ashr(0, $344 | 0, 32) | 0; + $349 = tempRet0; + $350 = $93; + $351 = $350; + $352 = HEAP32[$351 >> 2] | 0; + $353 = ($350 + 4) | 0; + $354 = $353; + $355 = HEAP32[$354 >> 2] | 0; + $356 = _bitshift64Ashr(0, $352 | 0, 32) | 0; + $357 = tempRet0; + $358 = ___muldi3($356 | 0, $357 | 0, $348 | 0, $349 | 0) | 0; + $359 = tempRet0; + $360 = _i64Add($358 | 0, $359 | 0, $340 | 0, $341 | 0) | 0; + $361 = tempRet0; + $362 = $41; + $363 = $362; + $364 = HEAP32[$363 >> 2] | 0; + $365 = ($362 + 4) | 0; + $366 = $365; + $367 = HEAP32[$366 >> 2] | 0; + $368 = _bitshift64Ashr(0, $364 | 0, 32) | 0; + $369 = tempRet0; + $370 = $285; + $371 = $370; + $372 = HEAP32[$371 >> 2] | 0; + $373 = ($370 + 4) | 0; + $374 = $373; + $375 = HEAP32[$374 >> 2] | 0; + $376 = _bitshift64Ashr(0, $372 | 0, 32) | 0; + $377 = tempRet0; + $378 = ___muldi3($376 | 0, $377 | 0, $368 | 0, $369 | 0) | 0; + $379 = tempRet0; + $380 = _i64Add($360 | 0, $361 | 0, $378 | 0, $379 | 0) | 0; + $381 = tempRet0; + $382 = $298; + $383 = $382; + $384 = HEAP32[$383 >> 2] | 0; + $385 = ($382 + 4) | 0; + $386 = $385; + $387 = HEAP32[$386 >> 2] | 0; + $388 = _bitshift64Ashr(0, $384 | 0, 32) | 0; + $389 = tempRet0; + $390 = $30; + $391 = $390; + $392 = HEAP32[$391 >> 2] | 0; + $393 = ($390 + 4) | 0; + $394 = $393; + $395 = HEAP32[$394 >> 2] | 0; + $396 = _bitshift64Ashr(0, $392 | 0, 32) | 0; + $397 = tempRet0; + $398 = ___muldi3($396 | 0, $397 | 0, $388 | 0, $389 | 0) | 0; + $399 = tempRet0; + $400 = _i64Add($380 | 0, $381 | 0, $398 | 0, $399 | 0) | 0; + $401 = tempRet0; + $402 = $in2; + $403 = $402; + $404 = HEAP32[$403 >> 2] | 0; + $405 = ($402 + 4) | 0; + $406 = $405; + $407 = HEAP32[$406 >> 2] | 0; + $408 = _bitshift64Ashr(0, $404 | 0, 32) | 0; + $409 = tempRet0; + $410 = ($in + 40) | 0; + $411 = $410; + $412 = $411; + $413 = HEAP32[$412 >> 2] | 0; + $414 = ($411 + 4) | 0; + $415 = $414; + $416 = HEAP32[$415 >> 2] | 0; + $417 = _bitshift64Ashr(0, $413 | 0, 32) | 0; + $418 = tempRet0; + $419 = ___muldi3($417 | 0, $418 | 0, $408 | 0, $409 | 0) | 0; + $420 = tempRet0; + $421 = _i64Add($400 | 0, $401 | 0, $419 | 0, $420 | 0) | 0; + $422 = tempRet0; + $423 = ($in2 + 40) | 0; + $424 = $423; + $425 = $424; + $426 = HEAP32[$425 >> 2] | 0; + $427 = ($424 + 4) | 0; + $428 = $427; + $429 = HEAP32[$428 >> 2] | 0; + $430 = _bitshift64Ashr(0, $426 | 0, 32) | 0; + $431 = tempRet0; + $432 = $in; + $433 = $432; + $434 = HEAP32[$433 >> 2] | 0; + $435 = ($432 + 4) | 0; + $436 = $435; + $437 = HEAP32[$436 >> 2] | 0; + $438 = _bitshift64Ashr(0, $434 | 0, 32) | 0; + $439 = tempRet0; + $440 = ___muldi3($438 | 0, $439 | 0, $430 | 0, $431 | 0) | 0; + $441 = tempRet0; + $442 = _i64Add($421 | 0, $422 | 0, $440 | 0, $441 | 0) | 0; + $443 = tempRet0; + $444 = ($output + 40) | 0; + $445 = $444; + $446 = $445; + HEAP32[$446 >> 2] = $442; + $447 = ($445 + 4) | 0; + $448 = $447; + HEAP32[$448 >> 2] = $443; + $449 = $191; + $450 = $449; + $451 = HEAP32[$450 >> 2] | 0; + $452 = ($449 + 4) | 0; + $453 = $452; + $454 = HEAP32[$453 >> 2] | 0; + $455 = _bitshift64Ashr(0, $451 | 0, 32) | 0; + $456 = tempRet0; + $457 = $178; + $458 = $457; + $459 = HEAP32[$458 >> 2] | 0; + $460 = ($457 + 4) | 0; + $461 = $460; + $462 = HEAP32[$461 >> 2] | 0; + $463 = _bitshift64Ashr(0, $459 | 0, 32) | 0; + $464 = tempRet0; + $465 = ___muldi3($463 | 0, $464 | 0, $455 | 0, $456 | 0) | 0; + $466 = tempRet0; + $467 = $41; + $468 = $467; + $469 = HEAP32[$468 >> 2] | 0; + $470 = ($467 + 4) | 0; + $471 = $470; + $472 = HEAP32[$471 >> 2] | 0; + $473 = _bitshift64Ashr(0, $469 | 0, 32) | 0; + $474 = tempRet0; + $475 = $410; + $476 = $475; + $477 = HEAP32[$476 >> 2] | 0; + $478 = ($475 + 4) | 0; + $479 = $478; + $480 = HEAP32[$479 >> 2] | 0; + $481 = _bitshift64Ashr(0, $477 | 0, 32) | 0; + $482 = tempRet0; + $483 = ___muldi3($481 | 0, $482 | 0, $473 | 0, $474 | 0) | 0; + $484 = tempRet0; + $485 = _i64Add($483 | 0, $484 | 0, $465 | 0, $466 | 0) | 0; + $486 = tempRet0; + $487 = $423; + $488 = $487; + $489 = HEAP32[$488 >> 2] | 0; + $490 = ($487 + 4) | 0; + $491 = $490; + $492 = HEAP32[$491 >> 2] | 0; + $493 = _bitshift64Ashr(0, $489 | 0, 32) | 0; + $494 = tempRet0; + $495 = $30; + $496 = $495; + $497 = HEAP32[$496 >> 2] | 0; + $498 = ($495 + 4) | 0; + $499 = $498; + $500 = HEAP32[$499 >> 2] | 0; + $501 = _bitshift64Ashr(0, $497 | 0, 32) | 0; + $502 = tempRet0; + $503 = ___muldi3($501 | 0, $502 | 0, $493 | 0, $494 | 0) | 0; + $504 = tempRet0; + $505 = _i64Add($485 | 0, $486 | 0, $503 | 0, $504 | 0) | 0; + $506 = tempRet0; + $507 = _bitshift64Shl($505 | 0, $506 | 0, 1) | 0; + $508 = tempRet0; + $509 = $106; + $510 = $509; + $511 = HEAP32[$510 >> 2] | 0; + $512 = ($509 + 4) | 0; + $513 = $512; + $514 = HEAP32[$513 >> 2] | 0; + $515 = _bitshift64Ashr(0, $511 | 0, 32) | 0; + $516 = tempRet0; + $517 = $285; + $518 = $517; + $519 = HEAP32[$518 >> 2] | 0; + $520 = ($517 + 4) | 0; + $521 = $520; + $522 = HEAP32[$521 >> 2] | 0; + $523 = _bitshift64Ashr(0, $519 | 0, 32) | 0; + $524 = tempRet0; + $525 = ___muldi3($523 | 0, $524 | 0, $515 | 0, $516 | 0) | 0; + $526 = tempRet0; + $527 = _i64Add($507 | 0, $508 | 0, $525 | 0, $526 | 0) | 0; + $528 = tempRet0; + $529 = $298; + $530 = $529; + $531 = HEAP32[$530 >> 2] | 0; + $532 = ($529 + 4) | 0; + $533 = $532; + $534 = HEAP32[$533 >> 2] | 0; + $535 = _bitshift64Ashr(0, $531 | 0, 32) | 0; + $536 = tempRet0; + $537 = $93; + $538 = $537; + $539 = HEAP32[$538 >> 2] | 0; + $540 = ($537 + 4) | 0; + $541 = $540; + $542 = HEAP32[$541 >> 2] | 0; + $543 = _bitshift64Ashr(0, $539 | 0, 32) | 0; + $544 = tempRet0; + $545 = ___muldi3($543 | 0, $544 | 0, $535 | 0, $536 | 0) | 0; + $546 = tempRet0; + $547 = _i64Add($527 | 0, $528 | 0, $545 | 0, $546 | 0) | 0; + $548 = tempRet0; + $549 = $in2; + $550 = $549; + $551 = HEAP32[$550 >> 2] | 0; + $552 = ($549 + 4) | 0; + $553 = $552; + $554 = HEAP32[$553 >> 2] | 0; + $555 = _bitshift64Ashr(0, $551 | 0, 32) | 0; + $556 = tempRet0; + $557 = ($in + 48) | 0; + $558 = $557; + $559 = $558; + $560 = HEAP32[$559 >> 2] | 0; + $561 = ($558 + 4) | 0; + $562 = $561; + $563 = HEAP32[$562 >> 2] | 0; + $564 = _bitshift64Ashr(0, $560 | 0, 32) | 0; + $565 = tempRet0; + $566 = ___muldi3($564 | 0, $565 | 0, $555 | 0, $556 | 0) | 0; + $567 = tempRet0; + $568 = _i64Add($547 | 0, $548 | 0, $566 | 0, $567 | 0) | 0; + $569 = tempRet0; + $570 = ($in2 + 48) | 0; + $571 = $570; + $572 = $571; + $573 = HEAP32[$572 >> 2] | 0; + $574 = ($571 + 4) | 0; + $575 = $574; + $576 = HEAP32[$575 >> 2] | 0; + $577 = _bitshift64Ashr(0, $573 | 0, 32) | 0; + $578 = tempRet0; + $579 = $in; + $580 = $579; + $581 = HEAP32[$580 >> 2] | 0; + $582 = ($579 + 4) | 0; + $583 = $582; + $584 = HEAP32[$583 >> 2] | 0; + $585 = _bitshift64Ashr(0, $581 | 0, 32) | 0; + $586 = tempRet0; + $587 = ___muldi3($585 | 0, $586 | 0, $577 | 0, $578 | 0) | 0; + $588 = tempRet0; + $589 = _i64Add($568 | 0, $569 | 0, $587 | 0, $588 | 0) | 0; + $590 = tempRet0; + $591 = ($output + 48) | 0; + $592 = $591; + $593 = $592; + HEAP32[$593 >> 2] = $589; + $594 = ($592 + 4) | 0; + $595 = $594; + HEAP32[$595 >> 2] = $590; + $596 = $191; + $597 = $596; + $598 = HEAP32[$597 >> 2] | 0; + $599 = ($596 + 4) | 0; + $600 = $599; + $601 = HEAP32[$600 >> 2] | 0; + $602 = _bitshift64Ashr(0, $598 | 0, 32) | 0; + $603 = tempRet0; + $604 = $285; + $605 = $604; + $606 = HEAP32[$605 >> 2] | 0; + $607 = ($604 + 4) | 0; + $608 = $607; + $609 = HEAP32[$608 >> 2] | 0; + $610 = _bitshift64Ashr(0, $606 | 0, 32) | 0; + $611 = tempRet0; + $612 = ___muldi3($610 | 0, $611 | 0, $602 | 0, $603 | 0) | 0; + $613 = tempRet0; + $614 = $298; + $615 = $614; + $616 = HEAP32[$615 >> 2] | 0; + $617 = ($614 + 4) | 0; + $618 = $617; + $619 = HEAP32[$618 >> 2] | 0; + $620 = _bitshift64Ashr(0, $616 | 0, 32) | 0; + $621 = tempRet0; + $622 = $178; + $623 = $622; + $624 = HEAP32[$623 >> 2] | 0; + $625 = ($622 + 4) | 0; + $626 = $625; + $627 = HEAP32[$626 >> 2] | 0; + $628 = _bitshift64Ashr(0, $624 | 0, 32) | 0; + $629 = tempRet0; + $630 = ___muldi3($628 | 0, $629 | 0, $620 | 0, $621 | 0) | 0; + $631 = tempRet0; + $632 = _i64Add($630 | 0, $631 | 0, $612 | 0, $613 | 0) | 0; + $633 = tempRet0; + $634 = $106; + $635 = $634; + $636 = HEAP32[$635 >> 2] | 0; + $637 = ($634 + 4) | 0; + $638 = $637; + $639 = HEAP32[$638 >> 2] | 0; + $640 = _bitshift64Ashr(0, $636 | 0, 32) | 0; + $641 = tempRet0; + $642 = $410; + $643 = $642; + $644 = HEAP32[$643 >> 2] | 0; + $645 = ($642 + 4) | 0; + $646 = $645; + $647 = HEAP32[$646 >> 2] | 0; + $648 = _bitshift64Ashr(0, $644 | 0, 32) | 0; + $649 = tempRet0; + $650 = ___muldi3($648 | 0, $649 | 0, $640 | 0, $641 | 0) | 0; + $651 = tempRet0; + $652 = _i64Add($632 | 0, $633 | 0, $650 | 0, $651 | 0) | 0; + $653 = tempRet0; + $654 = $423; + $655 = $654; + $656 = HEAP32[$655 >> 2] | 0; + $657 = ($654 + 4) | 0; + $658 = $657; + $659 = HEAP32[$658 >> 2] | 0; + $660 = _bitshift64Ashr(0, $656 | 0, 32) | 0; + $661 = tempRet0; + $662 = $93; + $663 = $662; + $664 = HEAP32[$663 >> 2] | 0; + $665 = ($662 + 4) | 0; + $666 = $665; + $667 = HEAP32[$666 >> 2] | 0; + $668 = _bitshift64Ashr(0, $664 | 0, 32) | 0; + $669 = tempRet0; + $670 = ___muldi3($668 | 0, $669 | 0, $660 | 0, $661 | 0) | 0; + $671 = tempRet0; + $672 = _i64Add($652 | 0, $653 | 0, $670 | 0, $671 | 0) | 0; + $673 = tempRet0; + $674 = $41; + $675 = $674; + $676 = HEAP32[$675 >> 2] | 0; + $677 = ($674 + 4) | 0; + $678 = $677; + $679 = HEAP32[$678 >> 2] | 0; + $680 = _bitshift64Ashr(0, $676 | 0, 32) | 0; + $681 = tempRet0; + $682 = $557; + $683 = $682; + $684 = HEAP32[$683 >> 2] | 0; + $685 = ($682 + 4) | 0; + $686 = $685; + $687 = HEAP32[$686 >> 2] | 0; + $688 = _bitshift64Ashr(0, $684 | 0, 32) | 0; + $689 = tempRet0; + $690 = ___muldi3($688 | 0, $689 | 0, $680 | 0, $681 | 0) | 0; + $691 = tempRet0; + $692 = _i64Add($672 | 0, $673 | 0, $690 | 0, $691 | 0) | 0; + $693 = tempRet0; + $694 = $570; + $695 = $694; + $696 = HEAP32[$695 >> 2] | 0; + $697 = ($694 + 4) | 0; + $698 = $697; + $699 = HEAP32[$698 >> 2] | 0; + $700 = _bitshift64Ashr(0, $696 | 0, 32) | 0; + $701 = tempRet0; + $702 = $30; + $703 = $702; + $704 = HEAP32[$703 >> 2] | 0; + $705 = ($702 + 4) | 0; + $706 = $705; + $707 = HEAP32[$706 >> 2] | 0; + $708 = _bitshift64Ashr(0, $704 | 0, 32) | 0; + $709 = tempRet0; + $710 = ___muldi3($708 | 0, $709 | 0, $700 | 0, $701 | 0) | 0; + $711 = tempRet0; + $712 = _i64Add($692 | 0, $693 | 0, $710 | 0, $711 | 0) | 0; + $713 = tempRet0; + $714 = $in2; + $715 = $714; + $716 = HEAP32[$715 >> 2] | 0; + $717 = ($714 + 4) | 0; + $718 = $717; + $719 = HEAP32[$718 >> 2] | 0; + $720 = _bitshift64Ashr(0, $716 | 0, 32) | 0; + $721 = tempRet0; + $722 = ($in + 56) | 0; + $723 = $722; + $724 = $723; + $725 = HEAP32[$724 >> 2] | 0; + $726 = ($723 + 4) | 0; + $727 = $726; + $728 = HEAP32[$727 >> 2] | 0; + $729 = _bitshift64Ashr(0, $725 | 0, 32) | 0; + $730 = tempRet0; + $731 = ___muldi3($729 | 0, $730 | 0, $720 | 0, $721 | 0) | 0; + $732 = tempRet0; + $733 = _i64Add($712 | 0, $713 | 0, $731 | 0, $732 | 0) | 0; + $734 = tempRet0; + $735 = ($in2 + 56) | 0; + $736 = $735; + $737 = $736; + $738 = HEAP32[$737 >> 2] | 0; + $739 = ($736 + 4) | 0; + $740 = $739; + $741 = HEAP32[$740 >> 2] | 0; + $742 = _bitshift64Ashr(0, $738 | 0, 32) | 0; + $743 = tempRet0; + $744 = $in; + $745 = $744; + $746 = HEAP32[$745 >> 2] | 0; + $747 = ($744 + 4) | 0; + $748 = $747; + $749 = HEAP32[$748 >> 2] | 0; + $750 = _bitshift64Ashr(0, $746 | 0, 32) | 0; + $751 = tempRet0; + $752 = ___muldi3($750 | 0, $751 | 0, $742 | 0, $743 | 0) | 0; + $753 = tempRet0; + $754 = _i64Add($733 | 0, $734 | 0, $752 | 0, $753 | 0) | 0; + $755 = tempRet0; + $756 = ($output + 56) | 0; + $757 = $756; + $758 = $757; + HEAP32[$758 >> 2] = $754; + $759 = ($757 + 4) | 0; + $760 = $759; + HEAP32[$760 >> 2] = $755; + $761 = $298; + $762 = $761; + $763 = HEAP32[$762 >> 2] | 0; + $764 = ($761 + 4) | 0; + $765 = $764; + $766 = HEAP32[$765 >> 2] | 0; + $767 = _bitshift64Ashr(0, $763 | 0, 32) | 0; + $768 = tempRet0; + $769 = $285; + $770 = $769; + $771 = HEAP32[$770 >> 2] | 0; + $772 = ($769 + 4) | 0; + $773 = $772; + $774 = HEAP32[$773 >> 2] | 0; + $775 = _bitshift64Ashr(0, $771 | 0, 32) | 0; + $776 = tempRet0; + $777 = ___muldi3($775 | 0, $776 | 0, $767 | 0, $768 | 0) | 0; + $778 = tempRet0; + $779 = $191; + $780 = $779; + $781 = HEAP32[$780 >> 2] | 0; + $782 = ($779 + 4) | 0; + $783 = $782; + $784 = HEAP32[$783 >> 2] | 0; + $785 = _bitshift64Ashr(0, $781 | 0, 32) | 0; + $786 = tempRet0; + $787 = $410; + $788 = $787; + $789 = HEAP32[$788 >> 2] | 0; + $790 = ($787 + 4) | 0; + $791 = $790; + $792 = HEAP32[$791 >> 2] | 0; + $793 = _bitshift64Ashr(0, $789 | 0, 32) | 0; + $794 = tempRet0; + $795 = ___muldi3($793 | 0, $794 | 0, $785 | 0, $786 | 0) | 0; + $796 = tempRet0; + $797 = $423; + $798 = $797; + $799 = HEAP32[$798 >> 2] | 0; + $800 = ($797 + 4) | 0; + $801 = $800; + $802 = HEAP32[$801 >> 2] | 0; + $803 = _bitshift64Ashr(0, $799 | 0, 32) | 0; + $804 = tempRet0; + $805 = $178; + $806 = $805; + $807 = HEAP32[$806 >> 2] | 0; + $808 = ($805 + 4) | 0; + $809 = $808; + $810 = HEAP32[$809 >> 2] | 0; + $811 = _bitshift64Ashr(0, $807 | 0, 32) | 0; + $812 = tempRet0; + $813 = ___muldi3($811 | 0, $812 | 0, $803 | 0, $804 | 0) | 0; + $814 = tempRet0; + $815 = _i64Add($813 | 0, $814 | 0, $795 | 0, $796 | 0) | 0; + $816 = tempRet0; + $817 = $41; + $818 = $817; + $819 = HEAP32[$818 >> 2] | 0; + $820 = ($817 + 4) | 0; + $821 = $820; + $822 = HEAP32[$821 >> 2] | 0; + $823 = _bitshift64Ashr(0, $819 | 0, 32) | 0; + $824 = tempRet0; + $825 = $722; + $826 = $825; + $827 = HEAP32[$826 >> 2] | 0; + $828 = ($825 + 4) | 0; + $829 = $828; + $830 = HEAP32[$829 >> 2] | 0; + $831 = _bitshift64Ashr(0, $827 | 0, 32) | 0; + $832 = tempRet0; + $833 = ___muldi3($831 | 0, $832 | 0, $823 | 0, $824 | 0) | 0; + $834 = tempRet0; + $835 = _i64Add($815 | 0, $816 | 0, $833 | 0, $834 | 0) | 0; + $836 = tempRet0; + $837 = $735; + $838 = $837; + $839 = HEAP32[$838 >> 2] | 0; + $840 = ($837 + 4) | 0; + $841 = $840; + $842 = HEAP32[$841 >> 2] | 0; + $843 = _bitshift64Ashr(0, $839 | 0, 32) | 0; + $844 = tempRet0; + $845 = $30; + $846 = $845; + $847 = HEAP32[$846 >> 2] | 0; + $848 = ($845 + 4) | 0; + $849 = $848; + $850 = HEAP32[$849 >> 2] | 0; + $851 = _bitshift64Ashr(0, $847 | 0, 32) | 0; + $852 = tempRet0; + $853 = ___muldi3($851 | 0, $852 | 0, $843 | 0, $844 | 0) | 0; + $854 = tempRet0; + $855 = _i64Add($835 | 0, $836 | 0, $853 | 0, $854 | 0) | 0; + $856 = tempRet0; + $857 = _bitshift64Shl($855 | 0, $856 | 0, 1) | 0; + $858 = tempRet0; + $859 = _i64Add($857 | 0, $858 | 0, $777 | 0, $778 | 0) | 0; + $860 = tempRet0; + $861 = $106; + $862 = $861; + $863 = HEAP32[$862 >> 2] | 0; + $864 = ($861 + 4) | 0; + $865 = $864; + $866 = HEAP32[$865 >> 2] | 0; + $867 = _bitshift64Ashr(0, $863 | 0, 32) | 0; + $868 = tempRet0; + $869 = $557; + $870 = $869; + $871 = HEAP32[$870 >> 2] | 0; + $872 = ($869 + 4) | 0; + $873 = $872; + $874 = HEAP32[$873 >> 2] | 0; + $875 = _bitshift64Ashr(0, $871 | 0, 32) | 0; + $876 = tempRet0; + $877 = ___muldi3($875 | 0, $876 | 0, $867 | 0, $868 | 0) | 0; + $878 = tempRet0; + $879 = _i64Add($859 | 0, $860 | 0, $877 | 0, $878 | 0) | 0; + $880 = tempRet0; + $881 = $570; + $882 = $881; + $883 = HEAP32[$882 >> 2] | 0; + $884 = ($881 + 4) | 0; + $885 = $884; + $886 = HEAP32[$885 >> 2] | 0; + $887 = _bitshift64Ashr(0, $883 | 0, 32) | 0; + $888 = tempRet0; + $889 = $93; + $890 = $889; + $891 = HEAP32[$890 >> 2] | 0; + $892 = ($889 + 4) | 0; + $893 = $892; + $894 = HEAP32[$893 >> 2] | 0; + $895 = _bitshift64Ashr(0, $891 | 0, 32) | 0; + $896 = tempRet0; + $897 = ___muldi3($895 | 0, $896 | 0, $887 | 0, $888 | 0) | 0; + $898 = tempRet0; + $899 = _i64Add($879 | 0, $880 | 0, $897 | 0, $898 | 0) | 0; + $900 = tempRet0; + $901 = $in2; + $902 = $901; + $903 = HEAP32[$902 >> 2] | 0; + $904 = ($901 + 4) | 0; + $905 = $904; + $906 = HEAP32[$905 >> 2] | 0; + $907 = _bitshift64Ashr(0, $903 | 0, 32) | 0; + $908 = tempRet0; + $909 = ($in + 64) | 0; + $910 = $909; + $911 = $910; + $912 = HEAP32[$911 >> 2] | 0; + $913 = ($910 + 4) | 0; + $914 = $913; + $915 = HEAP32[$914 >> 2] | 0; + $916 = _bitshift64Ashr(0, $912 | 0, 32) | 0; + $917 = tempRet0; + $918 = ___muldi3($916 | 0, $917 | 0, $907 | 0, $908 | 0) | 0; + $919 = tempRet0; + $920 = _i64Add($899 | 0, $900 | 0, $918 | 0, $919 | 0) | 0; + $921 = tempRet0; + $922 = ($in2 + 64) | 0; + $923 = $922; + $924 = $923; + $925 = HEAP32[$924 >> 2] | 0; + $926 = ($923 + 4) | 0; + $927 = $926; + $928 = HEAP32[$927 >> 2] | 0; + $929 = _bitshift64Ashr(0, $925 | 0, 32) | 0; + $930 = tempRet0; + $931 = $in; + $932 = $931; + $933 = HEAP32[$932 >> 2] | 0; + $934 = ($931 + 4) | 0; + $935 = $934; + $936 = HEAP32[$935 >> 2] | 0; + $937 = _bitshift64Ashr(0, $933 | 0, 32) | 0; + $938 = tempRet0; + $939 = ___muldi3($937 | 0, $938 | 0, $929 | 0, $930 | 0) | 0; + $940 = tempRet0; + $941 = _i64Add($920 | 0, $921 | 0, $939 | 0, $940 | 0) | 0; + $942 = tempRet0; + $943 = ($output + 64) | 0; + $944 = $943; + $945 = $944; + HEAP32[$945 >> 2] = $941; + $946 = ($944 + 4) | 0; + $947 = $946; + HEAP32[$947 >> 2] = $942; + $948 = $298; + $949 = $948; + $950 = HEAP32[$949 >> 2] | 0; + $951 = ($948 + 4) | 0; + $952 = $951; + $953 = HEAP32[$952 >> 2] | 0; + $954 = _bitshift64Ashr(0, $950 | 0, 32) | 0; + $955 = tempRet0; + $956 = $410; + $957 = $956; + $958 = HEAP32[$957 >> 2] | 0; + $959 = ($956 + 4) | 0; + $960 = $959; + $961 = HEAP32[$960 >> 2] | 0; + $962 = _bitshift64Ashr(0, $958 | 0, 32) | 0; + $963 = tempRet0; + $964 = ___muldi3($962 | 0, $963 | 0, $954 | 0, $955 | 0) | 0; + $965 = tempRet0; + $966 = $423; + $967 = $966; + $968 = HEAP32[$967 >> 2] | 0; + $969 = ($966 + 4) | 0; + $970 = $969; + $971 = HEAP32[$970 >> 2] | 0; + $972 = _bitshift64Ashr(0, $968 | 0, 32) | 0; + $973 = tempRet0; + $974 = $285; + $975 = $974; + $976 = HEAP32[$975 >> 2] | 0; + $977 = ($974 + 4) | 0; + $978 = $977; + $979 = HEAP32[$978 >> 2] | 0; + $980 = _bitshift64Ashr(0, $976 | 0, 32) | 0; + $981 = tempRet0; + $982 = ___muldi3($980 | 0, $981 | 0, $972 | 0, $973 | 0) | 0; + $983 = tempRet0; + $984 = _i64Add($982 | 0, $983 | 0, $964 | 0, $965 | 0) | 0; + $985 = tempRet0; + $986 = $191; + $987 = $986; + $988 = HEAP32[$987 >> 2] | 0; + $989 = ($986 + 4) | 0; + $990 = $989; + $991 = HEAP32[$990 >> 2] | 0; + $992 = _bitshift64Ashr(0, $988 | 0, 32) | 0; + $993 = tempRet0; + $994 = $557; + $995 = $994; + $996 = HEAP32[$995 >> 2] | 0; + $997 = ($994 + 4) | 0; + $998 = $997; + $999 = HEAP32[$998 >> 2] | 0; + $1000 = _bitshift64Ashr(0, $996 | 0, 32) | 0; + $1001 = tempRet0; + $1002 = ___muldi3($1000 | 0, $1001 | 0, $992 | 0, $993 | 0) | 0; + $1003 = tempRet0; + $1004 = _i64Add($984 | 0, $985 | 0, $1002 | 0, $1003 | 0) | 0; + $1005 = tempRet0; + $1006 = $570; + $1007 = $1006; + $1008 = HEAP32[$1007 >> 2] | 0; + $1009 = ($1006 + 4) | 0; + $1010 = $1009; + $1011 = HEAP32[$1010 >> 2] | 0; + $1012 = _bitshift64Ashr(0, $1008 | 0, 32) | 0; + $1013 = tempRet0; + $1014 = $178; + $1015 = $1014; + $1016 = HEAP32[$1015 >> 2] | 0; + $1017 = ($1014 + 4) | 0; + $1018 = $1017; + $1019 = HEAP32[$1018 >> 2] | 0; + $1020 = _bitshift64Ashr(0, $1016 | 0, 32) | 0; + $1021 = tempRet0; + $1022 = ___muldi3($1020 | 0, $1021 | 0, $1012 | 0, $1013 | 0) | 0; + $1023 = tempRet0; + $1024 = _i64Add($1004 | 0, $1005 | 0, $1022 | 0, $1023 | 0) | 0; + $1025 = tempRet0; + $1026 = $106; + $1027 = $1026; + $1028 = HEAP32[$1027 >> 2] | 0; + $1029 = ($1026 + 4) | 0; + $1030 = $1029; + $1031 = HEAP32[$1030 >> 2] | 0; + $1032 = _bitshift64Ashr(0, $1028 | 0, 32) | 0; + $1033 = tempRet0; + $1034 = $722; + $1035 = $1034; + $1036 = HEAP32[$1035 >> 2] | 0; + $1037 = ($1034 + 4) | 0; + $1038 = $1037; + $1039 = HEAP32[$1038 >> 2] | 0; + $1040 = _bitshift64Ashr(0, $1036 | 0, 32) | 0; + $1041 = tempRet0; + $1042 = ___muldi3($1040 | 0, $1041 | 0, $1032 | 0, $1033 | 0) | 0; + $1043 = tempRet0; + $1044 = _i64Add($1024 | 0, $1025 | 0, $1042 | 0, $1043 | 0) | 0; + $1045 = tempRet0; + $1046 = $735; + $1047 = $1046; + $1048 = HEAP32[$1047 >> 2] | 0; + $1049 = ($1046 + 4) | 0; + $1050 = $1049; + $1051 = HEAP32[$1050 >> 2] | 0; + $1052 = _bitshift64Ashr(0, $1048 | 0, 32) | 0; + $1053 = tempRet0; + $1054 = $93; + $1055 = $1054; + $1056 = HEAP32[$1055 >> 2] | 0; + $1057 = ($1054 + 4) | 0; + $1058 = $1057; + $1059 = HEAP32[$1058 >> 2] | 0; + $1060 = _bitshift64Ashr(0, $1056 | 0, 32) | 0; + $1061 = tempRet0; + $1062 = ___muldi3($1060 | 0, $1061 | 0, $1052 | 0, $1053 | 0) | 0; + $1063 = tempRet0; + $1064 = _i64Add($1044 | 0, $1045 | 0, $1062 | 0, $1063 | 0) | 0; + $1065 = tempRet0; + $1066 = $41; + $1067 = $1066; + $1068 = HEAP32[$1067 >> 2] | 0; + $1069 = ($1066 + 4) | 0; + $1070 = $1069; + $1071 = HEAP32[$1070 >> 2] | 0; + $1072 = _bitshift64Ashr(0, $1068 | 0, 32) | 0; + $1073 = tempRet0; + $1074 = $909; + $1075 = $1074; + $1076 = HEAP32[$1075 >> 2] | 0; + $1077 = ($1074 + 4) | 0; + $1078 = $1077; + $1079 = HEAP32[$1078 >> 2] | 0; + $1080 = _bitshift64Ashr(0, $1076 | 0, 32) | 0; + $1081 = tempRet0; + $1082 = ___muldi3($1080 | 0, $1081 | 0, $1072 | 0, $1073 | 0) | 0; + $1083 = tempRet0; + $1084 = _i64Add($1064 | 0, $1065 | 0, $1082 | 0, $1083 | 0) | 0; + $1085 = tempRet0; + $1086 = $922; + $1087 = $1086; + $1088 = HEAP32[$1087 >> 2] | 0; + $1089 = ($1086 + 4) | 0; + $1090 = $1089; + $1091 = HEAP32[$1090 >> 2] | 0; + $1092 = _bitshift64Ashr(0, $1088 | 0, 32) | 0; + $1093 = tempRet0; + $1094 = $30; + $1095 = $1094; + $1096 = HEAP32[$1095 >> 2] | 0; + $1097 = ($1094 + 4) | 0; + $1098 = $1097; + $1099 = HEAP32[$1098 >> 2] | 0; + $1100 = _bitshift64Ashr(0, $1096 | 0, 32) | 0; + $1101 = tempRet0; + $1102 = ___muldi3($1100 | 0, $1101 | 0, $1092 | 0, $1093 | 0) | 0; + $1103 = tempRet0; + $1104 = _i64Add($1084 | 0, $1085 | 0, $1102 | 0, $1103 | 0) | 0; + $1105 = tempRet0; + $1106 = $in2; + $1107 = $1106; + $1108 = HEAP32[$1107 >> 2] | 0; + $1109 = ($1106 + 4) | 0; + $1110 = $1109; + $1111 = HEAP32[$1110 >> 2] | 0; + $1112 = _bitshift64Ashr(0, $1108 | 0, 32) | 0; + $1113 = tempRet0; + $1114 = ($in + 72) | 0; + $1115 = $1114; + $1116 = $1115; + $1117 = HEAP32[$1116 >> 2] | 0; + $1118 = ($1115 + 4) | 0; + $1119 = $1118; + $1120 = HEAP32[$1119 >> 2] | 0; + $1121 = _bitshift64Ashr(0, $1117 | 0, 32) | 0; + $1122 = tempRet0; + $1123 = ___muldi3($1121 | 0, $1122 | 0, $1112 | 0, $1113 | 0) | 0; + $1124 = tempRet0; + $1125 = _i64Add($1104 | 0, $1105 | 0, $1123 | 0, $1124 | 0) | 0; + $1126 = tempRet0; + $1127 = ($in2 + 72) | 0; + $1128 = $1127; + $1129 = $1128; + $1130 = HEAP32[$1129 >> 2] | 0; + $1131 = ($1128 + 4) | 0; + $1132 = $1131; + $1133 = HEAP32[$1132 >> 2] | 0; + $1134 = _bitshift64Ashr(0, $1130 | 0, 32) | 0; + $1135 = tempRet0; + $1136 = $in; + $1137 = $1136; + $1138 = HEAP32[$1137 >> 2] | 0; + $1139 = ($1136 + 4) | 0; + $1140 = $1139; + $1141 = HEAP32[$1140 >> 2] | 0; + $1142 = _bitshift64Ashr(0, $1138 | 0, 32) | 0; + $1143 = tempRet0; + $1144 = ___muldi3($1142 | 0, $1143 | 0, $1134 | 0, $1135 | 0) | 0; + $1145 = tempRet0; + $1146 = _i64Add($1125 | 0, $1126 | 0, $1144 | 0, $1145 | 0) | 0; + $1147 = tempRet0; + $1148 = ($output + 72) | 0; + $1149 = $1148; + $1150 = $1149; + HEAP32[$1150 >> 2] = $1146; + $1151 = ($1149 + 4) | 0; + $1152 = $1151; + HEAP32[$1152 >> 2] = $1147; + $1153 = $423; + $1154 = $1153; + $1155 = HEAP32[$1154 >> 2] | 0; + $1156 = ($1153 + 4) | 0; + $1157 = $1156; + $1158 = HEAP32[$1157 >> 2] | 0; + $1159 = _bitshift64Ashr(0, $1155 | 0, 32) | 0; + $1160 = tempRet0; + $1161 = $410; + $1162 = $1161; + $1163 = HEAP32[$1162 >> 2] | 0; + $1164 = ($1161 + 4) | 0; + $1165 = $1164; + $1166 = HEAP32[$1165 >> 2] | 0; + $1167 = _bitshift64Ashr(0, $1163 | 0, 32) | 0; + $1168 = tempRet0; + $1169 = ___muldi3($1167 | 0, $1168 | 0, $1159 | 0, $1160 | 0) | 0; + $1170 = tempRet0; + $1171 = $191; + $1172 = $1171; + $1173 = HEAP32[$1172 >> 2] | 0; + $1174 = ($1171 + 4) | 0; + $1175 = $1174; + $1176 = HEAP32[$1175 >> 2] | 0; + $1177 = _bitshift64Ashr(0, $1173 | 0, 32) | 0; + $1178 = tempRet0; + $1179 = $722; + $1180 = $1179; + $1181 = HEAP32[$1180 >> 2] | 0; + $1182 = ($1179 + 4) | 0; + $1183 = $1182; + $1184 = HEAP32[$1183 >> 2] | 0; + $1185 = _bitshift64Ashr(0, $1181 | 0, 32) | 0; + $1186 = tempRet0; + $1187 = ___muldi3($1185 | 0, $1186 | 0, $1177 | 0, $1178 | 0) | 0; + $1188 = tempRet0; + $1189 = _i64Add($1187 | 0, $1188 | 0, $1169 | 0, $1170 | 0) | 0; + $1190 = tempRet0; + $1191 = $735; + $1192 = $1191; + $1193 = HEAP32[$1192 >> 2] | 0; + $1194 = ($1191 + 4) | 0; + $1195 = $1194; + $1196 = HEAP32[$1195 >> 2] | 0; + $1197 = _bitshift64Ashr(0, $1193 | 0, 32) | 0; + $1198 = tempRet0; + $1199 = $178; + $1200 = $1199; + $1201 = HEAP32[$1200 >> 2] | 0; + $1202 = ($1199 + 4) | 0; + $1203 = $1202; + $1204 = HEAP32[$1203 >> 2] | 0; + $1205 = _bitshift64Ashr(0, $1201 | 0, 32) | 0; + $1206 = tempRet0; + $1207 = ___muldi3($1205 | 0, $1206 | 0, $1197 | 0, $1198 | 0) | 0; + $1208 = tempRet0; + $1209 = _i64Add($1189 | 0, $1190 | 0, $1207 | 0, $1208 | 0) | 0; + $1210 = tempRet0; + $1211 = $41; + $1212 = $1211; + $1213 = HEAP32[$1212 >> 2] | 0; + $1214 = ($1211 + 4) | 0; + $1215 = $1214; + $1216 = HEAP32[$1215 >> 2] | 0; + $1217 = _bitshift64Ashr(0, $1213 | 0, 32) | 0; + $1218 = tempRet0; + $1219 = $1114; + $1220 = $1219; + $1221 = HEAP32[$1220 >> 2] | 0; + $1222 = ($1219 + 4) | 0; + $1223 = $1222; + $1224 = HEAP32[$1223 >> 2] | 0; + $1225 = _bitshift64Ashr(0, $1221 | 0, 32) | 0; + $1226 = tempRet0; + $1227 = ___muldi3($1225 | 0, $1226 | 0, $1217 | 0, $1218 | 0) | 0; + $1228 = tempRet0; + $1229 = _i64Add($1209 | 0, $1210 | 0, $1227 | 0, $1228 | 0) | 0; + $1230 = tempRet0; + $1231 = $1127; + $1232 = $1231; + $1233 = HEAP32[$1232 >> 2] | 0; + $1234 = ($1231 + 4) | 0; + $1235 = $1234; + $1236 = HEAP32[$1235 >> 2] | 0; + $1237 = _bitshift64Ashr(0, $1233 | 0, 32) | 0; + $1238 = tempRet0; + $1239 = $30; + $1240 = $1239; + $1241 = HEAP32[$1240 >> 2] | 0; + $1242 = ($1239 + 4) | 0; + $1243 = $1242; + $1244 = HEAP32[$1243 >> 2] | 0; + $1245 = _bitshift64Ashr(0, $1241 | 0, 32) | 0; + $1246 = tempRet0; + $1247 = ___muldi3($1245 | 0, $1246 | 0, $1237 | 0, $1238 | 0) | 0; + $1248 = tempRet0; + $1249 = _i64Add($1229 | 0, $1230 | 0, $1247 | 0, $1248 | 0) | 0; + $1250 = tempRet0; + $1251 = _bitshift64Shl($1249 | 0, $1250 | 0, 1) | 0; + $1252 = tempRet0; + $1253 = $298; + $1254 = $1253; + $1255 = HEAP32[$1254 >> 2] | 0; + $1256 = ($1253 + 4) | 0; + $1257 = $1256; + $1258 = HEAP32[$1257 >> 2] | 0; + $1259 = _bitshift64Ashr(0, $1255 | 0, 32) | 0; + $1260 = tempRet0; + $1261 = $557; + $1262 = $1261; + $1263 = HEAP32[$1262 >> 2] | 0; + $1264 = ($1261 + 4) | 0; + $1265 = $1264; + $1266 = HEAP32[$1265 >> 2] | 0; + $1267 = _bitshift64Ashr(0, $1263 | 0, 32) | 0; + $1268 = tempRet0; + $1269 = ___muldi3($1267 | 0, $1268 | 0, $1259 | 0, $1260 | 0) | 0; + $1270 = tempRet0; + $1271 = _i64Add($1251 | 0, $1252 | 0, $1269 | 0, $1270 | 0) | 0; + $1272 = tempRet0; + $1273 = $570; + $1274 = $1273; + $1275 = HEAP32[$1274 >> 2] | 0; + $1276 = ($1273 + 4) | 0; + $1277 = $1276; + $1278 = HEAP32[$1277 >> 2] | 0; + $1279 = _bitshift64Ashr(0, $1275 | 0, 32) | 0; + $1280 = tempRet0; + $1281 = $285; + $1282 = $1281; + $1283 = HEAP32[$1282 >> 2] | 0; + $1284 = ($1281 + 4) | 0; + $1285 = $1284; + $1286 = HEAP32[$1285 >> 2] | 0; + $1287 = _bitshift64Ashr(0, $1283 | 0, 32) | 0; + $1288 = tempRet0; + $1289 = ___muldi3($1287 | 0, $1288 | 0, $1279 | 0, $1280 | 0) | 0; + $1290 = tempRet0; + $1291 = _i64Add($1271 | 0, $1272 | 0, $1289 | 0, $1290 | 0) | 0; + $1292 = tempRet0; + $1293 = $106; + $1294 = $1293; + $1295 = HEAP32[$1294 >> 2] | 0; + $1296 = ($1293 + 4) | 0; + $1297 = $1296; + $1298 = HEAP32[$1297 >> 2] | 0; + $1299 = _bitshift64Ashr(0, $1295 | 0, 32) | 0; + $1300 = tempRet0; + $1301 = $909; + $1302 = $1301; + $1303 = HEAP32[$1302 >> 2] | 0; + $1304 = ($1301 + 4) | 0; + $1305 = $1304; + $1306 = HEAP32[$1305 >> 2] | 0; + $1307 = _bitshift64Ashr(0, $1303 | 0, 32) | 0; + $1308 = tempRet0; + $1309 = ___muldi3($1307 | 0, $1308 | 0, $1299 | 0, $1300 | 0) | 0; + $1310 = tempRet0; + $1311 = _i64Add($1291 | 0, $1292 | 0, $1309 | 0, $1310 | 0) | 0; + $1312 = tempRet0; + $1313 = $922; + $1314 = $1313; + $1315 = HEAP32[$1314 >> 2] | 0; + $1316 = ($1313 + 4) | 0; + $1317 = $1316; + $1318 = HEAP32[$1317 >> 2] | 0; + $1319 = _bitshift64Ashr(0, $1315 | 0, 32) | 0; + $1320 = tempRet0; + $1321 = $93; + $1322 = $1321; + $1323 = HEAP32[$1322 >> 2] | 0; + $1324 = ($1321 + 4) | 0; + $1325 = $1324; + $1326 = HEAP32[$1325 >> 2] | 0; + $1327 = _bitshift64Ashr(0, $1323 | 0, 32) | 0; + $1328 = tempRet0; + $1329 = ___muldi3($1327 | 0, $1328 | 0, $1319 | 0, $1320 | 0) | 0; + $1330 = tempRet0; + $1331 = _i64Add($1311 | 0, $1312 | 0, $1329 | 0, $1330 | 0) | 0; + $1332 = tempRet0; + $1333 = ($output + 80) | 0; + $1334 = $1333; + $1335 = $1334; + HEAP32[$1335 >> 2] = $1331; + $1336 = ($1334 + 4) | 0; + $1337 = $1336; + HEAP32[$1337 >> 2] = $1332; + $1338 = $423; + $1339 = $1338; + $1340 = HEAP32[$1339 >> 2] | 0; + $1341 = ($1338 + 4) | 0; + $1342 = $1341; + $1343 = HEAP32[$1342 >> 2] | 0; + $1344 = _bitshift64Ashr(0, $1340 | 0, 32) | 0; + $1345 = tempRet0; + $1346 = $557; + $1347 = $1346; + $1348 = HEAP32[$1347 >> 2] | 0; + $1349 = ($1346 + 4) | 0; + $1350 = $1349; + $1351 = HEAP32[$1350 >> 2] | 0; + $1352 = _bitshift64Ashr(0, $1348 | 0, 32) | 0; + $1353 = tempRet0; + $1354 = ___muldi3($1352 | 0, $1353 | 0, $1344 | 0, $1345 | 0) | 0; + $1355 = tempRet0; + $1356 = $570; + $1357 = $1356; + $1358 = HEAP32[$1357 >> 2] | 0; + $1359 = ($1356 + 4) | 0; + $1360 = $1359; + $1361 = HEAP32[$1360 >> 2] | 0; + $1362 = _bitshift64Ashr(0, $1358 | 0, 32) | 0; + $1363 = tempRet0; + $1364 = $410; + $1365 = $1364; + $1366 = HEAP32[$1365 >> 2] | 0; + $1367 = ($1364 + 4) | 0; + $1368 = $1367; + $1369 = HEAP32[$1368 >> 2] | 0; + $1370 = _bitshift64Ashr(0, $1366 | 0, 32) | 0; + $1371 = tempRet0; + $1372 = ___muldi3($1370 | 0, $1371 | 0, $1362 | 0, $1363 | 0) | 0; + $1373 = tempRet0; + $1374 = _i64Add($1372 | 0, $1373 | 0, $1354 | 0, $1355 | 0) | 0; + $1375 = tempRet0; + $1376 = $298; + $1377 = $1376; + $1378 = HEAP32[$1377 >> 2] | 0; + $1379 = ($1376 + 4) | 0; + $1380 = $1379; + $1381 = HEAP32[$1380 >> 2] | 0; + $1382 = _bitshift64Ashr(0, $1378 | 0, 32) | 0; + $1383 = tempRet0; + $1384 = $722; + $1385 = $1384; + $1386 = HEAP32[$1385 >> 2] | 0; + $1387 = ($1384 + 4) | 0; + $1388 = $1387; + $1389 = HEAP32[$1388 >> 2] | 0; + $1390 = _bitshift64Ashr(0, $1386 | 0, 32) | 0; + $1391 = tempRet0; + $1392 = ___muldi3($1390 | 0, $1391 | 0, $1382 | 0, $1383 | 0) | 0; + $1393 = tempRet0; + $1394 = _i64Add($1374 | 0, $1375 | 0, $1392 | 0, $1393 | 0) | 0; + $1395 = tempRet0; + $1396 = $735; + $1397 = $1396; + $1398 = HEAP32[$1397 >> 2] | 0; + $1399 = ($1396 + 4) | 0; + $1400 = $1399; + $1401 = HEAP32[$1400 >> 2] | 0; + $1402 = _bitshift64Ashr(0, $1398 | 0, 32) | 0; + $1403 = tempRet0; + $1404 = $285; + $1405 = $1404; + $1406 = HEAP32[$1405 >> 2] | 0; + $1407 = ($1404 + 4) | 0; + $1408 = $1407; + $1409 = HEAP32[$1408 >> 2] | 0; + $1410 = _bitshift64Ashr(0, $1406 | 0, 32) | 0; + $1411 = tempRet0; + $1412 = ___muldi3($1410 | 0, $1411 | 0, $1402 | 0, $1403 | 0) | 0; + $1413 = tempRet0; + $1414 = _i64Add($1394 | 0, $1395 | 0, $1412 | 0, $1413 | 0) | 0; + $1415 = tempRet0; + $1416 = $191; + $1417 = $1416; + $1418 = HEAP32[$1417 >> 2] | 0; + $1419 = ($1416 + 4) | 0; + $1420 = $1419; + $1421 = HEAP32[$1420 >> 2] | 0; + $1422 = _bitshift64Ashr(0, $1418 | 0, 32) | 0; + $1423 = tempRet0; + $1424 = $909; + $1425 = $1424; + $1426 = HEAP32[$1425 >> 2] | 0; + $1427 = ($1424 + 4) | 0; + $1428 = $1427; + $1429 = HEAP32[$1428 >> 2] | 0; + $1430 = _bitshift64Ashr(0, $1426 | 0, 32) | 0; + $1431 = tempRet0; + $1432 = ___muldi3($1430 | 0, $1431 | 0, $1422 | 0, $1423 | 0) | 0; + $1433 = tempRet0; + $1434 = _i64Add($1414 | 0, $1415 | 0, $1432 | 0, $1433 | 0) | 0; + $1435 = tempRet0; + $1436 = $922; + $1437 = $1436; + $1438 = HEAP32[$1437 >> 2] | 0; + $1439 = ($1436 + 4) | 0; + $1440 = $1439; + $1441 = HEAP32[$1440 >> 2] | 0; + $1442 = _bitshift64Ashr(0, $1438 | 0, 32) | 0; + $1443 = tempRet0; + $1444 = $178; + $1445 = $1444; + $1446 = HEAP32[$1445 >> 2] | 0; + $1447 = ($1444 + 4) | 0; + $1448 = $1447; + $1449 = HEAP32[$1448 >> 2] | 0; + $1450 = _bitshift64Ashr(0, $1446 | 0, 32) | 0; + $1451 = tempRet0; + $1452 = ___muldi3($1450 | 0, $1451 | 0, $1442 | 0, $1443 | 0) | 0; + $1453 = tempRet0; + $1454 = _i64Add($1434 | 0, $1435 | 0, $1452 | 0, $1453 | 0) | 0; + $1455 = tempRet0; + $1456 = $106; + $1457 = $1456; + $1458 = HEAP32[$1457 >> 2] | 0; + $1459 = ($1456 + 4) | 0; + $1460 = $1459; + $1461 = HEAP32[$1460 >> 2] | 0; + $1462 = _bitshift64Ashr(0, $1458 | 0, 32) | 0; + $1463 = tempRet0; + $1464 = $1114; + $1465 = $1464; + $1466 = HEAP32[$1465 >> 2] | 0; + $1467 = ($1464 + 4) | 0; + $1468 = $1467; + $1469 = HEAP32[$1468 >> 2] | 0; + $1470 = _bitshift64Ashr(0, $1466 | 0, 32) | 0; + $1471 = tempRet0; + $1472 = ___muldi3($1470 | 0, $1471 | 0, $1462 | 0, $1463 | 0) | 0; + $1473 = tempRet0; + $1474 = _i64Add($1454 | 0, $1455 | 0, $1472 | 0, $1473 | 0) | 0; + $1475 = tempRet0; + $1476 = $1127; + $1477 = $1476; + $1478 = HEAP32[$1477 >> 2] | 0; + $1479 = ($1476 + 4) | 0; + $1480 = $1479; + $1481 = HEAP32[$1480 >> 2] | 0; + $1482 = _bitshift64Ashr(0, $1478 | 0, 32) | 0; + $1483 = tempRet0; + $1484 = $93; + $1485 = $1484; + $1486 = HEAP32[$1485 >> 2] | 0; + $1487 = ($1484 + 4) | 0; + $1488 = $1487; + $1489 = HEAP32[$1488 >> 2] | 0; + $1490 = _bitshift64Ashr(0, $1486 | 0, 32) | 0; + $1491 = tempRet0; + $1492 = ___muldi3($1490 | 0, $1491 | 0, $1482 | 0, $1483 | 0) | 0; + $1493 = tempRet0; + $1494 = _i64Add($1474 | 0, $1475 | 0, $1492 | 0, $1493 | 0) | 0; + $1495 = tempRet0; + $1496 = ($output + 88) | 0; + $1497 = $1496; + $1498 = $1497; + HEAP32[$1498 >> 2] = $1494; + $1499 = ($1497 + 4) | 0; + $1500 = $1499; + HEAP32[$1500 >> 2] = $1495; + $1501 = $570; + $1502 = $1501; + $1503 = HEAP32[$1502 >> 2] | 0; + $1504 = ($1501 + 4) | 0; + $1505 = $1504; + $1506 = HEAP32[$1505 >> 2] | 0; + $1507 = _bitshift64Ashr(0, $1503 | 0, 32) | 0; + $1508 = tempRet0; + $1509 = $557; + $1510 = $1509; + $1511 = HEAP32[$1510 >> 2] | 0; + $1512 = ($1509 + 4) | 0; + $1513 = $1512; + $1514 = HEAP32[$1513 >> 2] | 0; + $1515 = _bitshift64Ashr(0, $1511 | 0, 32) | 0; + $1516 = tempRet0; + $1517 = ___muldi3($1515 | 0, $1516 | 0, $1507 | 0, $1508 | 0) | 0; + $1518 = tempRet0; + $1519 = $423; + $1520 = $1519; + $1521 = HEAP32[$1520 >> 2] | 0; + $1522 = ($1519 + 4) | 0; + $1523 = $1522; + $1524 = HEAP32[$1523 >> 2] | 0; + $1525 = _bitshift64Ashr(0, $1521 | 0, 32) | 0; + $1526 = tempRet0; + $1527 = $722; + $1528 = $1527; + $1529 = HEAP32[$1528 >> 2] | 0; + $1530 = ($1527 + 4) | 0; + $1531 = $1530; + $1532 = HEAP32[$1531 >> 2] | 0; + $1533 = _bitshift64Ashr(0, $1529 | 0, 32) | 0; + $1534 = tempRet0; + $1535 = ___muldi3($1533 | 0, $1534 | 0, $1525 | 0, $1526 | 0) | 0; + $1536 = tempRet0; + $1537 = $735; + $1538 = $1537; + $1539 = HEAP32[$1538 >> 2] | 0; + $1540 = ($1537 + 4) | 0; + $1541 = $1540; + $1542 = HEAP32[$1541 >> 2] | 0; + $1543 = _bitshift64Ashr(0, $1539 | 0, 32) | 0; + $1544 = tempRet0; + $1545 = $410; + $1546 = $1545; + $1547 = HEAP32[$1546 >> 2] | 0; + $1548 = ($1545 + 4) | 0; + $1549 = $1548; + $1550 = HEAP32[$1549 >> 2] | 0; + $1551 = _bitshift64Ashr(0, $1547 | 0, 32) | 0; + $1552 = tempRet0; + $1553 = ___muldi3($1551 | 0, $1552 | 0, $1543 | 0, $1544 | 0) | 0; + $1554 = tempRet0; + $1555 = _i64Add($1553 | 0, $1554 | 0, $1535 | 0, $1536 | 0) | 0; + $1556 = tempRet0; + $1557 = $191; + $1558 = $1557; + $1559 = HEAP32[$1558 >> 2] | 0; + $1560 = ($1557 + 4) | 0; + $1561 = $1560; + $1562 = HEAP32[$1561 >> 2] | 0; + $1563 = _bitshift64Ashr(0, $1559 | 0, 32) | 0; + $1564 = tempRet0; + $1565 = $1114; + $1566 = $1565; + $1567 = HEAP32[$1566 >> 2] | 0; + $1568 = ($1565 + 4) | 0; + $1569 = $1568; + $1570 = HEAP32[$1569 >> 2] | 0; + $1571 = _bitshift64Ashr(0, $1567 | 0, 32) | 0; + $1572 = tempRet0; + $1573 = ___muldi3($1571 | 0, $1572 | 0, $1563 | 0, $1564 | 0) | 0; + $1574 = tempRet0; + $1575 = _i64Add($1555 | 0, $1556 | 0, $1573 | 0, $1574 | 0) | 0; + $1576 = tempRet0; + $1577 = $1127; + $1578 = $1577; + $1579 = HEAP32[$1578 >> 2] | 0; + $1580 = ($1577 + 4) | 0; + $1581 = $1580; + $1582 = HEAP32[$1581 >> 2] | 0; + $1583 = _bitshift64Ashr(0, $1579 | 0, 32) | 0; + $1584 = tempRet0; + $1585 = $178; + $1586 = $1585; + $1587 = HEAP32[$1586 >> 2] | 0; + $1588 = ($1585 + 4) | 0; + $1589 = $1588; + $1590 = HEAP32[$1589 >> 2] | 0; + $1591 = _bitshift64Ashr(0, $1587 | 0, 32) | 0; + $1592 = tempRet0; + $1593 = ___muldi3($1591 | 0, $1592 | 0, $1583 | 0, $1584 | 0) | 0; + $1594 = tempRet0; + $1595 = _i64Add($1575 | 0, $1576 | 0, $1593 | 0, $1594 | 0) | 0; + $1596 = tempRet0; + $1597 = _bitshift64Shl($1595 | 0, $1596 | 0, 1) | 0; + $1598 = tempRet0; + $1599 = _i64Add($1597 | 0, $1598 | 0, $1517 | 0, $1518 | 0) | 0; + $1600 = tempRet0; + $1601 = $298; + $1602 = $1601; + $1603 = HEAP32[$1602 >> 2] | 0; + $1604 = ($1601 + 4) | 0; + $1605 = $1604; + $1606 = HEAP32[$1605 >> 2] | 0; + $1607 = _bitshift64Ashr(0, $1603 | 0, 32) | 0; + $1608 = tempRet0; + $1609 = $909; + $1610 = $1609; + $1611 = HEAP32[$1610 >> 2] | 0; + $1612 = ($1609 + 4) | 0; + $1613 = $1612; + $1614 = HEAP32[$1613 >> 2] | 0; + $1615 = _bitshift64Ashr(0, $1611 | 0, 32) | 0; + $1616 = tempRet0; + $1617 = ___muldi3($1615 | 0, $1616 | 0, $1607 | 0, $1608 | 0) | 0; + $1618 = tempRet0; + $1619 = _i64Add($1599 | 0, $1600 | 0, $1617 | 0, $1618 | 0) | 0; + $1620 = tempRet0; + $1621 = $922; + $1622 = $1621; + $1623 = HEAP32[$1622 >> 2] | 0; + $1624 = ($1621 + 4) | 0; + $1625 = $1624; + $1626 = HEAP32[$1625 >> 2] | 0; + $1627 = _bitshift64Ashr(0, $1623 | 0, 32) | 0; + $1628 = tempRet0; + $1629 = $285; + $1630 = $1629; + $1631 = HEAP32[$1630 >> 2] | 0; + $1632 = ($1629 + 4) | 0; + $1633 = $1632; + $1634 = HEAP32[$1633 >> 2] | 0; + $1635 = _bitshift64Ashr(0, $1631 | 0, 32) | 0; + $1636 = tempRet0; + $1637 = ___muldi3($1635 | 0, $1636 | 0, $1627 | 0, $1628 | 0) | 0; + $1638 = tempRet0; + $1639 = _i64Add($1619 | 0, $1620 | 0, $1637 | 0, $1638 | 0) | 0; + $1640 = tempRet0; + $1641 = ($output + 96) | 0; + $1642 = $1641; + $1643 = $1642; + HEAP32[$1643 >> 2] = $1639; + $1644 = ($1642 + 4) | 0; + $1645 = $1644; + HEAP32[$1645 >> 2] = $1640; + $1646 = $570; + $1647 = $1646; + $1648 = HEAP32[$1647 >> 2] | 0; + $1649 = ($1646 + 4) | 0; + $1650 = $1649; + $1651 = HEAP32[$1650 >> 2] | 0; + $1652 = _bitshift64Ashr(0, $1648 | 0, 32) | 0; + $1653 = tempRet0; + $1654 = $722; + $1655 = $1654; + $1656 = HEAP32[$1655 >> 2] | 0; + $1657 = ($1654 + 4) | 0; + $1658 = $1657; + $1659 = HEAP32[$1658 >> 2] | 0; + $1660 = _bitshift64Ashr(0, $1656 | 0, 32) | 0; + $1661 = tempRet0; + $1662 = ___muldi3($1660 | 0, $1661 | 0, $1652 | 0, $1653 | 0) | 0; + $1663 = tempRet0; + $1664 = $735; + $1665 = $1664; + $1666 = HEAP32[$1665 >> 2] | 0; + $1667 = ($1664 + 4) | 0; + $1668 = $1667; + $1669 = HEAP32[$1668 >> 2] | 0; + $1670 = _bitshift64Ashr(0, $1666 | 0, 32) | 0; + $1671 = tempRet0; + $1672 = $557; + $1673 = $1672; + $1674 = HEAP32[$1673 >> 2] | 0; + $1675 = ($1672 + 4) | 0; + $1676 = $1675; + $1677 = HEAP32[$1676 >> 2] | 0; + $1678 = _bitshift64Ashr(0, $1674 | 0, 32) | 0; + $1679 = tempRet0; + $1680 = ___muldi3($1678 | 0, $1679 | 0, $1670 | 0, $1671 | 0) | 0; + $1681 = tempRet0; + $1682 = _i64Add($1680 | 0, $1681 | 0, $1662 | 0, $1663 | 0) | 0; + $1683 = tempRet0; + $1684 = $423; + $1685 = $1684; + $1686 = HEAP32[$1685 >> 2] | 0; + $1687 = ($1684 + 4) | 0; + $1688 = $1687; + $1689 = HEAP32[$1688 >> 2] | 0; + $1690 = _bitshift64Ashr(0, $1686 | 0, 32) | 0; + $1691 = tempRet0; + $1692 = $909; + $1693 = $1692; + $1694 = HEAP32[$1693 >> 2] | 0; + $1695 = ($1692 + 4) | 0; + $1696 = $1695; + $1697 = HEAP32[$1696 >> 2] | 0; + $1698 = _bitshift64Ashr(0, $1694 | 0, 32) | 0; + $1699 = tempRet0; + $1700 = ___muldi3($1698 | 0, $1699 | 0, $1690 | 0, $1691 | 0) | 0; + $1701 = tempRet0; + $1702 = _i64Add($1682 | 0, $1683 | 0, $1700 | 0, $1701 | 0) | 0; + $1703 = tempRet0; + $1704 = $922; + $1705 = $1704; + $1706 = HEAP32[$1705 >> 2] | 0; + $1707 = ($1704 + 4) | 0; + $1708 = $1707; + $1709 = HEAP32[$1708 >> 2] | 0; + $1710 = _bitshift64Ashr(0, $1706 | 0, 32) | 0; + $1711 = tempRet0; + $1712 = $410; + $1713 = $1712; + $1714 = HEAP32[$1713 >> 2] | 0; + $1715 = ($1712 + 4) | 0; + $1716 = $1715; + $1717 = HEAP32[$1716 >> 2] | 0; + $1718 = _bitshift64Ashr(0, $1714 | 0, 32) | 0; + $1719 = tempRet0; + $1720 = ___muldi3($1718 | 0, $1719 | 0, $1710 | 0, $1711 | 0) | 0; + $1721 = tempRet0; + $1722 = _i64Add($1702 | 0, $1703 | 0, $1720 | 0, $1721 | 0) | 0; + $1723 = tempRet0; + $1724 = $298; + $1725 = $1724; + $1726 = HEAP32[$1725 >> 2] | 0; + $1727 = ($1724 + 4) | 0; + $1728 = $1727; + $1729 = HEAP32[$1728 >> 2] | 0; + $1730 = _bitshift64Ashr(0, $1726 | 0, 32) | 0; + $1731 = tempRet0; + $1732 = $1114; + $1733 = $1732; + $1734 = HEAP32[$1733 >> 2] | 0; + $1735 = ($1732 + 4) | 0; + $1736 = $1735; + $1737 = HEAP32[$1736 >> 2] | 0; + $1738 = _bitshift64Ashr(0, $1734 | 0, 32) | 0; + $1739 = tempRet0; + $1740 = ___muldi3($1738 | 0, $1739 | 0, $1730 | 0, $1731 | 0) | 0; + $1741 = tempRet0; + $1742 = _i64Add($1722 | 0, $1723 | 0, $1740 | 0, $1741 | 0) | 0; + $1743 = tempRet0; + $1744 = $1127; + $1745 = $1744; + $1746 = HEAP32[$1745 >> 2] | 0; + $1747 = ($1744 + 4) | 0; + $1748 = $1747; + $1749 = HEAP32[$1748 >> 2] | 0; + $1750 = _bitshift64Ashr(0, $1746 | 0, 32) | 0; + $1751 = tempRet0; + $1752 = $285; + $1753 = $1752; + $1754 = HEAP32[$1753 >> 2] | 0; + $1755 = ($1752 + 4) | 0; + $1756 = $1755; + $1757 = HEAP32[$1756 >> 2] | 0; + $1758 = _bitshift64Ashr(0, $1754 | 0, 32) | 0; + $1759 = tempRet0; + $1760 = ___muldi3($1758 | 0, $1759 | 0, $1750 | 0, $1751 | 0) | 0; + $1761 = tempRet0; + $1762 = _i64Add($1742 | 0, $1743 | 0, $1760 | 0, $1761 | 0) | 0; + $1763 = tempRet0; + $1764 = ($output + 104) | 0; + $1765 = $1764; + $1766 = $1765; + HEAP32[$1766 >> 2] = $1762; + $1767 = ($1765 + 4) | 0; + $1768 = $1767; + HEAP32[$1768 >> 2] = $1763; + $1769 = $735; + $1770 = $1769; + $1771 = HEAP32[$1770 >> 2] | 0; + $1772 = ($1769 + 4) | 0; + $1773 = $1772; + $1774 = HEAP32[$1773 >> 2] | 0; + $1775 = _bitshift64Ashr(0, $1771 | 0, 32) | 0; + $1776 = tempRet0; + $1777 = $722; + $1778 = $1777; + $1779 = HEAP32[$1778 >> 2] | 0; + $1780 = ($1777 + 4) | 0; + $1781 = $1780; + $1782 = HEAP32[$1781 >> 2] | 0; + $1783 = _bitshift64Ashr(0, $1779 | 0, 32) | 0; + $1784 = tempRet0; + $1785 = ___muldi3($1783 | 0, $1784 | 0, $1775 | 0, $1776 | 0) | 0; + $1786 = tempRet0; + $1787 = $423; + $1788 = $1787; + $1789 = HEAP32[$1788 >> 2] | 0; + $1790 = ($1787 + 4) | 0; + $1791 = $1790; + $1792 = HEAP32[$1791 >> 2] | 0; + $1793 = _bitshift64Ashr(0, $1789 | 0, 32) | 0; + $1794 = tempRet0; + $1795 = $1114; + $1796 = $1795; + $1797 = HEAP32[$1796 >> 2] | 0; + $1798 = ($1795 + 4) | 0; + $1799 = $1798; + $1800 = HEAP32[$1799 >> 2] | 0; + $1801 = _bitshift64Ashr(0, $1797 | 0, 32) | 0; + $1802 = tempRet0; + $1803 = ___muldi3($1801 | 0, $1802 | 0, $1793 | 0, $1794 | 0) | 0; + $1804 = tempRet0; + $1805 = _i64Add($1803 | 0, $1804 | 0, $1785 | 0, $1786 | 0) | 0; + $1806 = tempRet0; + $1807 = $1127; + $1808 = $1807; + $1809 = HEAP32[$1808 >> 2] | 0; + $1810 = ($1807 + 4) | 0; + $1811 = $1810; + $1812 = HEAP32[$1811 >> 2] | 0; + $1813 = _bitshift64Ashr(0, $1809 | 0, 32) | 0; + $1814 = tempRet0; + $1815 = $410; + $1816 = $1815; + $1817 = HEAP32[$1816 >> 2] | 0; + $1818 = ($1815 + 4) | 0; + $1819 = $1818; + $1820 = HEAP32[$1819 >> 2] | 0; + $1821 = _bitshift64Ashr(0, $1817 | 0, 32) | 0; + $1822 = tempRet0; + $1823 = ___muldi3($1821 | 0, $1822 | 0, $1813 | 0, $1814 | 0) | 0; + $1824 = tempRet0; + $1825 = _i64Add($1805 | 0, $1806 | 0, $1823 | 0, $1824 | 0) | 0; + $1826 = tempRet0; + $1827 = _bitshift64Shl($1825 | 0, $1826 | 0, 1) | 0; + $1828 = tempRet0; + $1829 = $570; + $1830 = $1829; + $1831 = HEAP32[$1830 >> 2] | 0; + $1832 = ($1829 + 4) | 0; + $1833 = $1832; + $1834 = HEAP32[$1833 >> 2] | 0; + $1835 = _bitshift64Ashr(0, $1831 | 0, 32) | 0; + $1836 = tempRet0; + $1837 = $909; + $1838 = $1837; + $1839 = HEAP32[$1838 >> 2] | 0; + $1840 = ($1837 + 4) | 0; + $1841 = $1840; + $1842 = HEAP32[$1841 >> 2] | 0; + $1843 = _bitshift64Ashr(0, $1839 | 0, 32) | 0; + $1844 = tempRet0; + $1845 = ___muldi3($1843 | 0, $1844 | 0, $1835 | 0, $1836 | 0) | 0; + $1846 = tempRet0; + $1847 = _i64Add($1827 | 0, $1828 | 0, $1845 | 0, $1846 | 0) | 0; + $1848 = tempRet0; + $1849 = $922; + $1850 = $1849; + $1851 = HEAP32[$1850 >> 2] | 0; + $1852 = ($1849 + 4) | 0; + $1853 = $1852; + $1854 = HEAP32[$1853 >> 2] | 0; + $1855 = _bitshift64Ashr(0, $1851 | 0, 32) | 0; + $1856 = tempRet0; + $1857 = $557; + $1858 = $1857; + $1859 = HEAP32[$1858 >> 2] | 0; + $1860 = ($1857 + 4) | 0; + $1861 = $1860; + $1862 = HEAP32[$1861 >> 2] | 0; + $1863 = _bitshift64Ashr(0, $1859 | 0, 32) | 0; + $1864 = tempRet0; + $1865 = ___muldi3($1863 | 0, $1864 | 0, $1855 | 0, $1856 | 0) | 0; + $1866 = tempRet0; + $1867 = _i64Add($1847 | 0, $1848 | 0, $1865 | 0, $1866 | 0) | 0; + $1868 = tempRet0; + $1869 = ($output + 112) | 0; + $1870 = $1869; + $1871 = $1870; + HEAP32[$1871 >> 2] = $1867; + $1872 = ($1870 + 4) | 0; + $1873 = $1872; + HEAP32[$1873 >> 2] = $1868; + $1874 = $735; + $1875 = $1874; + $1876 = HEAP32[$1875 >> 2] | 0; + $1877 = ($1874 + 4) | 0; + $1878 = $1877; + $1879 = HEAP32[$1878 >> 2] | 0; + $1880 = _bitshift64Ashr(0, $1876 | 0, 32) | 0; + $1881 = tempRet0; + $1882 = $909; + $1883 = $1882; + $1884 = HEAP32[$1883 >> 2] | 0; + $1885 = ($1882 + 4) | 0; + $1886 = $1885; + $1887 = HEAP32[$1886 >> 2] | 0; + $1888 = _bitshift64Ashr(0, $1884 | 0, 32) | 0; + $1889 = tempRet0; + $1890 = ___muldi3($1888 | 0, $1889 | 0, $1880 | 0, $1881 | 0) | 0; + $1891 = tempRet0; + $1892 = $922; + $1893 = $1892; + $1894 = HEAP32[$1893 >> 2] | 0; + $1895 = ($1892 + 4) | 0; + $1896 = $1895; + $1897 = HEAP32[$1896 >> 2] | 0; + $1898 = _bitshift64Ashr(0, $1894 | 0, 32) | 0; + $1899 = tempRet0; + $1900 = $722; + $1901 = $1900; + $1902 = HEAP32[$1901 >> 2] | 0; + $1903 = ($1900 + 4) | 0; + $1904 = $1903; + $1905 = HEAP32[$1904 >> 2] | 0; + $1906 = _bitshift64Ashr(0, $1902 | 0, 32) | 0; + $1907 = tempRet0; + $1908 = ___muldi3($1906 | 0, $1907 | 0, $1898 | 0, $1899 | 0) | 0; + $1909 = tempRet0; + $1910 = _i64Add($1908 | 0, $1909 | 0, $1890 | 0, $1891 | 0) | 0; + $1911 = tempRet0; + $1912 = $570; + $1913 = $1912; + $1914 = HEAP32[$1913 >> 2] | 0; + $1915 = ($1912 + 4) | 0; + $1916 = $1915; + $1917 = HEAP32[$1916 >> 2] | 0; + $1918 = _bitshift64Ashr(0, $1914 | 0, 32) | 0; + $1919 = tempRet0; + $1920 = $1114; + $1921 = $1920; + $1922 = HEAP32[$1921 >> 2] | 0; + $1923 = ($1920 + 4) | 0; + $1924 = $1923; + $1925 = HEAP32[$1924 >> 2] | 0; + $1926 = _bitshift64Ashr(0, $1922 | 0, 32) | 0; + $1927 = tempRet0; + $1928 = ___muldi3($1926 | 0, $1927 | 0, $1918 | 0, $1919 | 0) | 0; + $1929 = tempRet0; + $1930 = _i64Add($1910 | 0, $1911 | 0, $1928 | 0, $1929 | 0) | 0; + $1931 = tempRet0; + $1932 = $1127; + $1933 = $1932; + $1934 = HEAP32[$1933 >> 2] | 0; + $1935 = ($1932 + 4) | 0; + $1936 = $1935; + $1937 = HEAP32[$1936 >> 2] | 0; + $1938 = _bitshift64Ashr(0, $1934 | 0, 32) | 0; + $1939 = tempRet0; + $1940 = $557; + $1941 = $1940; + $1942 = HEAP32[$1941 >> 2] | 0; + $1943 = ($1940 + 4) | 0; + $1944 = $1943; + $1945 = HEAP32[$1944 >> 2] | 0; + $1946 = _bitshift64Ashr(0, $1942 | 0, 32) | 0; + $1947 = tempRet0; + $1948 = ___muldi3($1946 | 0, $1947 | 0, $1938 | 0, $1939 | 0) | 0; + $1949 = tempRet0; + $1950 = _i64Add($1930 | 0, $1931 | 0, $1948 | 0, $1949 | 0) | 0; + $1951 = tempRet0; + $1952 = ($output + 120) | 0; + $1953 = $1952; + $1954 = $1953; + HEAP32[$1954 >> 2] = $1950; + $1955 = ($1953 + 4) | 0; + $1956 = $1955; + HEAP32[$1956 >> 2] = $1951; + $1957 = $922; + $1958 = $1957; + $1959 = HEAP32[$1958 >> 2] | 0; + $1960 = ($1957 + 4) | 0; + $1961 = $1960; + $1962 = HEAP32[$1961 >> 2] | 0; + $1963 = _bitshift64Ashr(0, $1959 | 0, 32) | 0; + $1964 = tempRet0; + $1965 = $909; + $1966 = $1965; + $1967 = HEAP32[$1966 >> 2] | 0; + $1968 = ($1965 + 4) | 0; + $1969 = $1968; + $1970 = HEAP32[$1969 >> 2] | 0; + $1971 = _bitshift64Ashr(0, $1967 | 0, 32) | 0; + $1972 = tempRet0; + $1973 = ___muldi3($1971 | 0, $1972 | 0, $1963 | 0, $1964 | 0) | 0; + $1974 = tempRet0; + $1975 = $735; + $1976 = $1975; + $1977 = HEAP32[$1976 >> 2] | 0; + $1978 = ($1975 + 4) | 0; + $1979 = $1978; + $1980 = HEAP32[$1979 >> 2] | 0; + $1981 = _bitshift64Ashr(0, $1977 | 0, 32) | 0; + $1982 = tempRet0; + $1983 = $1114; + $1984 = $1983; + $1985 = HEAP32[$1984 >> 2] | 0; + $1986 = ($1983 + 4) | 0; + $1987 = $1986; + $1988 = HEAP32[$1987 >> 2] | 0; + $1989 = _bitshift64Ashr(0, $1985 | 0, 32) | 0; + $1990 = tempRet0; + $1991 = ___muldi3($1989 | 0, $1990 | 0, $1981 | 0, $1982 | 0) | 0; + $1992 = tempRet0; + $1993 = $1127; + $1994 = $1993; + $1995 = HEAP32[$1994 >> 2] | 0; + $1996 = ($1993 + 4) | 0; + $1997 = $1996; + $1998 = HEAP32[$1997 >> 2] | 0; + $1999 = _bitshift64Ashr(0, $1995 | 0, 32) | 0; + $2000 = tempRet0; + $2001 = $722; + $2002 = $2001; + $2003 = HEAP32[$2002 >> 2] | 0; + $2004 = ($2001 + 4) | 0; + $2005 = $2004; + $2006 = HEAP32[$2005 >> 2] | 0; + $2007 = _bitshift64Ashr(0, $2003 | 0, 32) | 0; + $2008 = tempRet0; + $2009 = ___muldi3($2007 | 0, $2008 | 0, $1999 | 0, $2000 | 0) | 0; + $2010 = tempRet0; + $2011 = _i64Add($2009 | 0, $2010 | 0, $1991 | 0, $1992 | 0) | 0; + $2012 = tempRet0; + $2013 = _bitshift64Shl($2011 | 0, $2012 | 0, 1) | 0; + $2014 = tempRet0; + $2015 = _i64Add($2013 | 0, $2014 | 0, $1973 | 0, $1974 | 0) | 0; + $2016 = tempRet0; + $2017 = ($output + 128) | 0; + $2018 = $2017; + $2019 = $2018; + HEAP32[$2019 >> 2] = $2015; + $2020 = ($2018 + 4) | 0; + $2021 = $2020; + HEAP32[$2021 >> 2] = $2016; + $2022 = $922; + $2023 = $2022; + $2024 = HEAP32[$2023 >> 2] | 0; + $2025 = ($2022 + 4) | 0; + $2026 = $2025; + $2027 = HEAP32[$2026 >> 2] | 0; + $2028 = _bitshift64Ashr(0, $2024 | 0, 32) | 0; + $2029 = tempRet0; + $2030 = $1114; + $2031 = $2030; + $2032 = HEAP32[$2031 >> 2] | 0; + $2033 = ($2030 + 4) | 0; + $2034 = $2033; + $2035 = HEAP32[$2034 >> 2] | 0; + $2036 = _bitshift64Ashr(0, $2032 | 0, 32) | 0; + $2037 = tempRet0; + $2038 = ___muldi3($2036 | 0, $2037 | 0, $2028 | 0, $2029 | 0) | 0; + $2039 = tempRet0; + $2040 = $1127; + $2041 = $2040; + $2042 = HEAP32[$2041 >> 2] | 0; + $2043 = ($2040 + 4) | 0; + $2044 = $2043; + $2045 = HEAP32[$2044 >> 2] | 0; + $2046 = _bitshift64Ashr(0, $2042 | 0, 32) | 0; + $2047 = tempRet0; + $2048 = $909; + $2049 = $2048; + $2050 = HEAP32[$2049 >> 2] | 0; + $2051 = ($2048 + 4) | 0; + $2052 = $2051; + $2053 = HEAP32[$2052 >> 2] | 0; + $2054 = _bitshift64Ashr(0, $2050 | 0, 32) | 0; + $2055 = tempRet0; + $2056 = ___muldi3($2054 | 0, $2055 | 0, $2046 | 0, $2047 | 0) | 0; + $2057 = tempRet0; + $2058 = _i64Add($2056 | 0, $2057 | 0, $2038 | 0, $2039 | 0) | 0; + $2059 = tempRet0; + $2060 = ($output + 136) | 0; + $2061 = $2060; + $2062 = $2061; + HEAP32[$2062 >> 2] = $2058; + $2063 = ($2061 + 4) | 0; + $2064 = $2063; + HEAP32[$2064 >> 2] = $2059; + $2065 = $1127; + $2066 = $2065; + $2067 = HEAP32[$2066 >> 2] | 0; + $2068 = ($2065 + 4) | 0; + $2069 = $2068; + $2070 = HEAP32[$2069 >> 2] | 0; + $2071 = _bitshift64Ashr(0, $2067 | 0, 31) | 0; + $2072 = tempRet0; + $2073 = $1114; + $2074 = $2073; + $2075 = HEAP32[$2074 >> 2] | 0; + $2076 = ($2073 + 4) | 0; + $2077 = $2076; + $2078 = HEAP32[$2077 >> 2] | 0; + $2079 = _bitshift64Ashr(0, $2075 | 0, 32) | 0; + $2080 = tempRet0; + $2081 = ___muldi3($2079 | 0, $2080 | 0, $2071 | 0, $2072 | 0) | 0; + $2082 = tempRet0; + $2083 = ($output + 144) | 0; + $2084 = $2083; + $2085 = $2084; + HEAP32[$2085 >> 2] = $2081; + $2086 = ($2084 + 4) | 0; + $2087 = $2086; + HEAP32[$2087 >> 2] = $2082; + STACKTOP = sp; + return; + } + function _freduce_degree($output) { + $output = $output | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $101 = 0, + $102 = 0, + $103 = 0, + $104 = 0, + $105 = 0, + $106 = 0, + $107 = 0, + $108 = 0, + $109 = 0, + $11 = 0, + $110 = 0, + $111 = 0, + $112 = 0, + $113 = 0, + $114 = 0, + $115 = 0; + var $116 = 0, + $117 = 0, + $118 = 0, + $119 = 0, + $12 = 0, + $120 = 0, + $121 = 0, + $122 = 0, + $123 = 0, + $124 = 0, + $125 = 0, + $126 = 0, + $127 = 0, + $128 = 0, + $129 = 0, + $13 = 0, + $130 = 0, + $131 = 0, + $132 = 0, + $133 = 0; + var $134 = 0, + $135 = 0, + $136 = 0, + $137 = 0, + $138 = 0, + $139 = 0, + $14 = 0, + $140 = 0, + $141 = 0, + $142 = 0, + $143 = 0, + $144 = 0, + $145 = 0, + $146 = 0, + $147 = 0, + $148 = 0, + $149 = 0, + $15 = 0, + $150 = 0, + $151 = 0; + var $152 = 0, + $153 = 0, + $154 = 0, + $155 = 0, + $156 = 0, + $157 = 0, + $158 = 0, + $159 = 0, + $16 = 0, + $160 = 0, + $161 = 0, + $162 = 0, + $163 = 0, + $164 = 0, + $165 = 0, + $166 = 0, + $167 = 0, + $168 = 0, + $169 = 0, + $17 = 0; + var $170 = 0, + $171 = 0, + $172 = 0, + $173 = 0, + $174 = 0, + $175 = 0, + $176 = 0, + $177 = 0, + $178 = 0, + $179 = 0, + $18 = 0, + $180 = 0, + $181 = 0, + $182 = 0, + $183 = 0, + $184 = 0, + $185 = 0, + $186 = 0, + $187 = 0, + $188 = 0; + var $189 = 0, + $19 = 0, + $190 = 0, + $191 = 0, + $192 = 0, + $193 = 0, + $194 = 0, + $195 = 0, + $196 = 0, + $197 = 0, + $198 = 0, + $199 = 0, + $2 = 0, + $20 = 0, + $200 = 0, + $201 = 0, + $202 = 0, + $203 = 0, + $204 = 0, + $205 = 0; + var $206 = 0, + $207 = 0, + $208 = 0, + $209 = 0, + $21 = 0, + $210 = 0, + $211 = 0, + $212 = 0, + $213 = 0, + $214 = 0, + $215 = 0, + $216 = 0, + $217 = 0, + $218 = 0, + $219 = 0, + $22 = 0, + $220 = 0, + $221 = 0, + $222 = 0, + $223 = 0; + var $224 = 0, + $225 = 0, + $226 = 0, + $227 = 0, + $228 = 0, + $229 = 0, + $23 = 0, + $230 = 0, + $231 = 0, + $232 = 0, + $233 = 0, + $234 = 0, + $235 = 0, + $236 = 0, + $237 = 0, + $238 = 0, + $239 = 0, + $24 = 0, + $240 = 0, + $241 = 0; + var $242 = 0, + $243 = 0, + $244 = 0, + $245 = 0, + $246 = 0, + $247 = 0, + $248 = 0, + $249 = 0, + $25 = 0, + $250 = 0, + $251 = 0, + $252 = 0, + $253 = 0, + $254 = 0, + $255 = 0, + $256 = 0, + $257 = 0, + $258 = 0, + $259 = 0, + $26 = 0; + var $260 = 0, + $261 = 0, + $262 = 0, + $263 = 0, + $264 = 0, + $265 = 0, + $266 = 0, + $267 = 0, + $268 = 0, + $269 = 0, + $27 = 0, + $270 = 0, + $271 = 0, + $272 = 0, + $273 = 0, + $274 = 0, + $275 = 0, + $276 = 0, + $277 = 0, + $278 = 0; + var $279 = 0, + $28 = 0, + $280 = 0, + $281 = 0, + $282 = 0, + $283 = 0, + $284 = 0, + $285 = 0, + $286 = 0, + $287 = 0, + $288 = 0, + $289 = 0, + $29 = 0, + $290 = 0, + $291 = 0, + $292 = 0, + $293 = 0, + $294 = 0, + $295 = 0, + $296 = 0; + var $297 = 0, + $298 = 0, + $3 = 0, + $30 = 0, + $31 = 0, + $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $37 = 0, + $38 = 0, + $39 = 0, + $4 = 0, + $40 = 0, + $41 = 0, + $42 = 0, + $43 = 0, + $44 = 0, + $45 = 0; + var $46 = 0, + $47 = 0, + $48 = 0, + $49 = 0, + $5 = 0, + $50 = 0, + $51 = 0, + $52 = 0, + $53 = 0, + $54 = 0, + $55 = 0, + $56 = 0, + $57 = 0, + $58 = 0, + $59 = 0, + $6 = 0, + $60 = 0, + $61 = 0, + $62 = 0, + $63 = 0; + var $64 = 0, + $65 = 0, + $66 = 0, + $67 = 0, + $68 = 0, + $69 = 0, + $7 = 0, + $70 = 0, + $71 = 0, + $72 = 0, + $73 = 0, + $74 = 0, + $75 = 0, + $76 = 0, + $77 = 0, + $78 = 0, + $79 = 0, + $8 = 0, + $80 = 0, + $81 = 0; + var $82 = 0, + $83 = 0, + $84 = 0, + $85 = 0, + $86 = 0, + $87 = 0, + $88 = 0, + $89 = 0, + $9 = 0, + $90 = 0, + $91 = 0, + $92 = 0, + $93 = 0, + $94 = 0, + $95 = 0, + $96 = 0, + $97 = 0, + $98 = 0, + $99 = 0, + label = 0; + var sp = 0; + sp = STACKTOP; + $0 = ($output + 144) | 0; + $1 = $0; + $2 = $1; + $3 = HEAP32[$2 >> 2] | 0; + $4 = ($1 + 4) | 0; + $5 = $4; + $6 = HEAP32[$5 >> 2] | 0; + $7 = _bitshift64Shl($3 | 0, $6 | 0, 4) | 0; + $8 = tempRet0; + $9 = ($output + 64) | 0; + $10 = $9; + $11 = $10; + $12 = HEAP32[$11 >> 2] | 0; + $13 = ($10 + 4) | 0; + $14 = $13; + $15 = HEAP32[$14 >> 2] | 0; + $16 = _i64Add($12 | 0, $15 | 0, $7 | 0, $8 | 0) | 0; + $17 = tempRet0; + $18 = _bitshift64Shl($3 | 0, $6 | 0, 1) | 0; + $19 = tempRet0; + $20 = _i64Add($18 | 0, $19 | 0, $16 | 0, $17 | 0) | 0; + $21 = tempRet0; + $22 = $0; + $23 = $22; + $24 = HEAP32[$23 >> 2] | 0; + $25 = ($22 + 4) | 0; + $26 = $25; + $27 = HEAP32[$26 >> 2] | 0; + $28 = _i64Add($20 | 0, $21 | 0, $24 | 0, $27 | 0) | 0; + $29 = tempRet0; + $30 = $9; + $31 = $30; + HEAP32[$31 >> 2] = $28; + $32 = ($30 + 4) | 0; + $33 = $32; + HEAP32[$33 >> 2] = $29; + $34 = ($output + 136) | 0; + $35 = $34; + $36 = $35; + $37 = HEAP32[$36 >> 2] | 0; + $38 = ($35 + 4) | 0; + $39 = $38; + $40 = HEAP32[$39 >> 2] | 0; + $41 = _bitshift64Shl($37 | 0, $40 | 0, 4) | 0; + $42 = tempRet0; + $43 = ($output + 56) | 0; + $44 = $43; + $45 = $44; + $46 = HEAP32[$45 >> 2] | 0; + $47 = ($44 + 4) | 0; + $48 = $47; + $49 = HEAP32[$48 >> 2] | 0; + $50 = _i64Add($46 | 0, $49 | 0, $41 | 0, $42 | 0) | 0; + $51 = tempRet0; + $52 = _bitshift64Shl($37 | 0, $40 | 0, 1) | 0; + $53 = tempRet0; + $54 = _i64Add($52 | 0, $53 | 0, $50 | 0, $51 | 0) | 0; + $55 = tempRet0; + $56 = $34; + $57 = $56; + $58 = HEAP32[$57 >> 2] | 0; + $59 = ($56 + 4) | 0; + $60 = $59; + $61 = HEAP32[$60 >> 2] | 0; + $62 = _i64Add($54 | 0, $55 | 0, $58 | 0, $61 | 0) | 0; + $63 = tempRet0; + $64 = $43; + $65 = $64; + HEAP32[$65 >> 2] = $62; + $66 = ($64 + 4) | 0; + $67 = $66; + HEAP32[$67 >> 2] = $63; + $68 = ($output + 128) | 0; + $69 = $68; + $70 = $69; + $71 = HEAP32[$70 >> 2] | 0; + $72 = ($69 + 4) | 0; + $73 = $72; + $74 = HEAP32[$73 >> 2] | 0; + $75 = _bitshift64Shl($71 | 0, $74 | 0, 4) | 0; + $76 = tempRet0; + $77 = ($output + 48) | 0; + $78 = $77; + $79 = $78; + $80 = HEAP32[$79 >> 2] | 0; + $81 = ($78 + 4) | 0; + $82 = $81; + $83 = HEAP32[$82 >> 2] | 0; + $84 = _i64Add($80 | 0, $83 | 0, $75 | 0, $76 | 0) | 0; + $85 = tempRet0; + $86 = _bitshift64Shl($71 | 0, $74 | 0, 1) | 0; + $87 = tempRet0; + $88 = _i64Add($86 | 0, $87 | 0, $84 | 0, $85 | 0) | 0; + $89 = tempRet0; + $90 = $68; + $91 = $90; + $92 = HEAP32[$91 >> 2] | 0; + $93 = ($90 + 4) | 0; + $94 = $93; + $95 = HEAP32[$94 >> 2] | 0; + $96 = _i64Add($88 | 0, $89 | 0, $92 | 0, $95 | 0) | 0; + $97 = tempRet0; + $98 = $77; + $99 = $98; + HEAP32[$99 >> 2] = $96; + $100 = ($98 + 4) | 0; + $101 = $100; + HEAP32[$101 >> 2] = $97; + $102 = ($output + 120) | 0; + $103 = $102; + $104 = $103; + $105 = HEAP32[$104 >> 2] | 0; + $106 = ($103 + 4) | 0; + $107 = $106; + $108 = HEAP32[$107 >> 2] | 0; + $109 = _bitshift64Shl($105 | 0, $108 | 0, 4) | 0; + $110 = tempRet0; + $111 = ($output + 40) | 0; + $112 = $111; + $113 = $112; + $114 = HEAP32[$113 >> 2] | 0; + $115 = ($112 + 4) | 0; + $116 = $115; + $117 = HEAP32[$116 >> 2] | 0; + $118 = _i64Add($114 | 0, $117 | 0, $109 | 0, $110 | 0) | 0; + $119 = tempRet0; + $120 = _bitshift64Shl($105 | 0, $108 | 0, 1) | 0; + $121 = tempRet0; + $122 = _i64Add($120 | 0, $121 | 0, $118 | 0, $119 | 0) | 0; + $123 = tempRet0; + $124 = $102; + $125 = $124; + $126 = HEAP32[$125 >> 2] | 0; + $127 = ($124 + 4) | 0; + $128 = $127; + $129 = HEAP32[$128 >> 2] | 0; + $130 = _i64Add($122 | 0, $123 | 0, $126 | 0, $129 | 0) | 0; + $131 = tempRet0; + $132 = $111; + $133 = $132; + HEAP32[$133 >> 2] = $130; + $134 = ($132 + 4) | 0; + $135 = $134; + HEAP32[$135 >> 2] = $131; + $136 = ($output + 112) | 0; + $137 = $136; + $138 = $137; + $139 = HEAP32[$138 >> 2] | 0; + $140 = ($137 + 4) | 0; + $141 = $140; + $142 = HEAP32[$141 >> 2] | 0; + $143 = _bitshift64Shl($139 | 0, $142 | 0, 4) | 0; + $144 = tempRet0; + $145 = ($output + 32) | 0; + $146 = $145; + $147 = $146; + $148 = HEAP32[$147 >> 2] | 0; + $149 = ($146 + 4) | 0; + $150 = $149; + $151 = HEAP32[$150 >> 2] | 0; + $152 = _i64Add($148 | 0, $151 | 0, $143 | 0, $144 | 0) | 0; + $153 = tempRet0; + $154 = _bitshift64Shl($139 | 0, $142 | 0, 1) | 0; + $155 = tempRet0; + $156 = _i64Add($154 | 0, $155 | 0, $152 | 0, $153 | 0) | 0; + $157 = tempRet0; + $158 = $136; + $159 = $158; + $160 = HEAP32[$159 >> 2] | 0; + $161 = ($158 + 4) | 0; + $162 = $161; + $163 = HEAP32[$162 >> 2] | 0; + $164 = _i64Add($156 | 0, $157 | 0, $160 | 0, $163 | 0) | 0; + $165 = tempRet0; + $166 = $145; + $167 = $166; + HEAP32[$167 >> 2] = $164; + $168 = ($166 + 4) | 0; + $169 = $168; + HEAP32[$169 >> 2] = $165; + $170 = ($output + 104) | 0; + $171 = $170; + $172 = $171; + $173 = HEAP32[$172 >> 2] | 0; + $174 = ($171 + 4) | 0; + $175 = $174; + $176 = HEAP32[$175 >> 2] | 0; + $177 = _bitshift64Shl($173 | 0, $176 | 0, 4) | 0; + $178 = tempRet0; + $179 = ($output + 24) | 0; + $180 = $179; + $181 = $180; + $182 = HEAP32[$181 >> 2] | 0; + $183 = ($180 + 4) | 0; + $184 = $183; + $185 = HEAP32[$184 >> 2] | 0; + $186 = _i64Add($182 | 0, $185 | 0, $177 | 0, $178 | 0) | 0; + $187 = tempRet0; + $188 = _bitshift64Shl($173 | 0, $176 | 0, 1) | 0; + $189 = tempRet0; + $190 = _i64Add($188 | 0, $189 | 0, $186 | 0, $187 | 0) | 0; + $191 = tempRet0; + $192 = $170; + $193 = $192; + $194 = HEAP32[$193 >> 2] | 0; + $195 = ($192 + 4) | 0; + $196 = $195; + $197 = HEAP32[$196 >> 2] | 0; + $198 = _i64Add($190 | 0, $191 | 0, $194 | 0, $197 | 0) | 0; + $199 = tempRet0; + $200 = $179; + $201 = $200; + HEAP32[$201 >> 2] = $198; + $202 = ($200 + 4) | 0; + $203 = $202; + HEAP32[$203 >> 2] = $199; + $204 = ($output + 96) | 0; + $205 = $204; + $206 = $205; + $207 = HEAP32[$206 >> 2] | 0; + $208 = ($205 + 4) | 0; + $209 = $208; + $210 = HEAP32[$209 >> 2] | 0; + $211 = _bitshift64Shl($207 | 0, $210 | 0, 4) | 0; + $212 = tempRet0; + $213 = ($output + 16) | 0; + $214 = $213; + $215 = $214; + $216 = HEAP32[$215 >> 2] | 0; + $217 = ($214 + 4) | 0; + $218 = $217; + $219 = HEAP32[$218 >> 2] | 0; + $220 = _i64Add($216 | 0, $219 | 0, $211 | 0, $212 | 0) | 0; + $221 = tempRet0; + $222 = _bitshift64Shl($207 | 0, $210 | 0, 1) | 0; + $223 = tempRet0; + $224 = _i64Add($222 | 0, $223 | 0, $220 | 0, $221 | 0) | 0; + $225 = tempRet0; + $226 = $204; + $227 = $226; + $228 = HEAP32[$227 >> 2] | 0; + $229 = ($226 + 4) | 0; + $230 = $229; + $231 = HEAP32[$230 >> 2] | 0; + $232 = _i64Add($224 | 0, $225 | 0, $228 | 0, $231 | 0) | 0; + $233 = tempRet0; + $234 = $213; + $235 = $234; + HEAP32[$235 >> 2] = $232; + $236 = ($234 + 4) | 0; + $237 = $236; + HEAP32[$237 >> 2] = $233; + $238 = ($output + 88) | 0; + $239 = $238; + $240 = $239; + $241 = HEAP32[$240 >> 2] | 0; + $242 = ($239 + 4) | 0; + $243 = $242; + $244 = HEAP32[$243 >> 2] | 0; + $245 = _bitshift64Shl($241 | 0, $244 | 0, 4) | 0; + $246 = tempRet0; + $247 = ($output + 8) | 0; + $248 = $247; + $249 = $248; + $250 = HEAP32[$249 >> 2] | 0; + $251 = ($248 + 4) | 0; + $252 = $251; + $253 = HEAP32[$252 >> 2] | 0; + $254 = _i64Add($250 | 0, $253 | 0, $245 | 0, $246 | 0) | 0; + $255 = tempRet0; + $256 = _bitshift64Shl($241 | 0, $244 | 0, 1) | 0; + $257 = tempRet0; + $258 = _i64Add($256 | 0, $257 | 0, $254 | 0, $255 | 0) | 0; + $259 = tempRet0; + $260 = $238; + $261 = $260; + $262 = HEAP32[$261 >> 2] | 0; + $263 = ($260 + 4) | 0; + $264 = $263; + $265 = HEAP32[$264 >> 2] | 0; + $266 = _i64Add($258 | 0, $259 | 0, $262 | 0, $265 | 0) | 0; + $267 = tempRet0; + $268 = $247; + $269 = $268; + HEAP32[$269 >> 2] = $266; + $270 = ($268 + 4) | 0; + $271 = $270; + HEAP32[$271 >> 2] = $267; + $272 = ($output + 80) | 0; + $273 = $272; + $274 = $273; + $275 = HEAP32[$274 >> 2] | 0; + $276 = ($273 + 4) | 0; + $277 = $276; + $278 = HEAP32[$277 >> 2] | 0; + $279 = _bitshift64Shl($275 | 0, $278 | 0, 4) | 0; + $280 = tempRet0; + $281 = $output; + $282 = $281; + $283 = HEAP32[$282 >> 2] | 0; + $284 = ($281 + 4) | 0; + $285 = $284; + $286 = HEAP32[$285 >> 2] | 0; + $287 = _i64Add($283 | 0, $286 | 0, $279 | 0, $280 | 0) | 0; + $288 = tempRet0; + $289 = _bitshift64Shl($275 | 0, $278 | 0, 1) | 0; + $290 = tempRet0; + $291 = _i64Add($289 | 0, $290 | 0, $287 | 0, $288 | 0) | 0; + $292 = tempRet0; + $293 = _i64Add($291 | 0, $292 | 0, $275 | 0, $278 | 0) | 0; + $294 = tempRet0; + $295 = $output; + $296 = $295; + HEAP32[$296 >> 2] = $293; + $297 = ($295 + 4) | 0; + $298 = $297; + HEAP32[$298 >> 2] = $294; + STACKTOP = sp; + return; + } + function _freduce_coefficients($output) { + $output = $output | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $101 = 0, + $102 = 0, + $103 = 0, + $104 = 0, + $105 = 0, + $106 = 0, + $107 = 0, + $108 = 0, + $109 = 0, + $11 = 0, + $110 = 0, + $111 = 0, + $112 = 0, + $113 = 0, + $114 = 0, + $115 = 0; + var $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0, + $27 = 0, + $28 = 0, + $29 = 0, + $3 = 0; + var $30 = 0, + $31 = 0, + $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $37 = 0, + $38 = 0, + $39 = 0, + $4 = 0, + $40 = 0, + $41 = 0, + $42 = 0, + $43 = 0, + $44 = 0, + $45 = 0, + $46 = 0, + $47 = 0, + $48 = 0; + var $49 = 0, + $5 = 0, + $50 = 0, + $51 = 0, + $52 = 0, + $53 = 0, + $54 = 0, + $55 = 0, + $56 = 0, + $57 = 0, + $58 = 0, + $59 = 0, + $6 = 0, + $60 = 0, + $61 = 0, + $62 = 0, + $63 = 0, + $64 = 0, + $65 = 0, + $66 = 0; + var $67 = 0, + $68 = 0, + $69 = 0, + $7 = 0, + $70 = 0, + $71 = 0, + $72 = 0, + $73 = 0, + $74 = 0, + $75 = 0, + $76 = 0, + $77 = 0, + $78 = 0, + $79 = 0, + $8 = 0, + $80 = 0, + $81 = 0, + $82 = 0, + $83 = 0, + $84 = 0; + var $85 = 0, + $86 = 0, + $87 = 0, + $88 = 0, + $89 = 0, + $9 = 0, + $90 = 0, + $91 = 0, + $92 = 0, + $93 = 0, + $94 = 0, + $95 = 0, + $96 = 0, + $97 = 0, + $98 = 0, + $99 = 0, + $i$01 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = ($output + 80) | 0; + $1 = $0; + $2 = $1; + HEAP32[$2 >> 2] = 0; + $3 = ($1 + 4) | 0; + $4 = $3; + HEAP32[$4 >> 2] = 0; + $i$01 = 0; + while (1) { + $5 = ($output + ($i$01 << 3)) | 0; + $6 = $5; + $7 = $6; + $8 = HEAP32[$7 >> 2] | 0; + $9 = ($6 + 4) | 0; + $10 = $9; + $11 = HEAP32[$10 >> 2] | 0; + $12 = _div_by_2_26($8, $11) | 0; + $13 = tempRet0; + $14 = _bitshift64Shl($12 | 0, $13 | 0, 26) | 0; + $15 = tempRet0; + $16 = _i64Subtract($8 | 0, $11 | 0, $14 | 0, $15 | 0) | 0; + $17 = tempRet0; + $18 = $5; + $19 = $18; + HEAP32[$19 >> 2] = $16; + $20 = ($18 + 4) | 0; + $21 = $20; + HEAP32[$21 >> 2] = $17; + $22 = $i$01 | 1; + $23 = ($output + ($22 << 3)) | 0; + $24 = $23; + $25 = $24; + $26 = HEAP32[$25 >> 2] | 0; + $27 = ($24 + 4) | 0; + $28 = $27; + $29 = HEAP32[$28 >> 2] | 0; + $30 = _i64Add($26 | 0, $29 | 0, $12 | 0, $13 | 0) | 0; + $31 = tempRet0; + $32 = _div_by_2_25($30, $31) | 0; + $33 = tempRet0; + $34 = _bitshift64Shl($32 | 0, $33 | 0, 25) | 0; + $35 = tempRet0; + $36 = _i64Subtract($30 | 0, $31 | 0, $34 | 0, $35 | 0) | 0; + $37 = tempRet0; + $38 = $23; + $39 = $38; + HEAP32[$39 >> 2] = $36; + $40 = ($38 + 4) | 0; + $41 = $40; + HEAP32[$41 >> 2] = $37; + $42 = ($i$01 + 2) | 0; + $43 = ($output + ($42 << 3)) | 0; + $44 = $43; + $45 = $44; + $46 = HEAP32[$45 >> 2] | 0; + $47 = ($44 + 4) | 0; + $48 = $47; + $49 = HEAP32[$48 >> 2] | 0; + $50 = _i64Add($46 | 0, $49 | 0, $32 | 0, $33 | 0) | 0; + $51 = tempRet0; + $52 = $43; + $53 = $52; + HEAP32[$53 >> 2] = $50; + $54 = ($52 + 4) | 0; + $55 = $54; + HEAP32[$55 >> 2] = $51; + $56 = $42 >>> 0 < 10; + if ($56) { + $i$01 = $42; + } else { + break; + } + } + $57 = $0; + $58 = $57; + $59 = HEAP32[$58 >> 2] | 0; + $60 = ($57 + 4) | 0; + $61 = $60; + $62 = HEAP32[$61 >> 2] | 0; + $63 = _bitshift64Shl($59 | 0, $62 | 0, 4) | 0; + $64 = tempRet0; + $65 = $output; + $66 = $65; + $67 = HEAP32[$66 >> 2] | 0; + $68 = ($65 + 4) | 0; + $69 = $68; + $70 = HEAP32[$69 >> 2] | 0; + $71 = _i64Add($67 | 0, $70 | 0, $63 | 0, $64 | 0) | 0; + $72 = tempRet0; + $73 = _bitshift64Shl($59 | 0, $62 | 0, 1) | 0; + $74 = tempRet0; + $75 = _i64Add($73 | 0, $74 | 0, $71 | 0, $72 | 0) | 0; + $76 = tempRet0; + $77 = _i64Add($75 | 0, $76 | 0, $59 | 0, $62 | 0) | 0; + $78 = tempRet0; + $79 = $output; + $80 = $79; + HEAP32[$80 >> 2] = $77; + $81 = ($79 + 4) | 0; + $82 = $81; + HEAP32[$82 >> 2] = $78; + $83 = $0; + $84 = $83; + HEAP32[$84 >> 2] = 0; + $85 = ($83 + 4) | 0; + $86 = $85; + HEAP32[$86 >> 2] = 0; + $87 = $output; + $88 = $87; + $89 = HEAP32[$88 >> 2] | 0; + $90 = ($87 + 4) | 0; + $91 = $90; + $92 = HEAP32[$91 >> 2] | 0; + $93 = _div_by_2_26($89, $92) | 0; + $94 = tempRet0; + $95 = _bitshift64Shl($93 | 0, $94 | 0, 26) | 0; + $96 = tempRet0; + $97 = _i64Subtract($89 | 0, $92 | 0, $95 | 0, $96 | 0) | 0; + $98 = tempRet0; + $99 = $output; + $100 = $99; + HEAP32[$100 >> 2] = $97; + $101 = ($99 + 4) | 0; + $102 = $101; + HEAP32[$102 >> 2] = $98; + $103 = ($output + 8) | 0; + $104 = $103; + $105 = $104; + $106 = HEAP32[$105 >> 2] | 0; + $107 = ($104 + 4) | 0; + $108 = $107; + $109 = HEAP32[$108 >> 2] | 0; + $110 = _i64Add($106 | 0, $109 | 0, $93 | 0, $94 | 0) | 0; + $111 = tempRet0; + $112 = $103; + $113 = $112; + HEAP32[$113 >> 2] = $110; + $114 = ($112 + 4) | 0; + $115 = $114; + HEAP32[$115 >> 2] = $111; + STACKTOP = sp; + return; + } + function _s32_eq($a, $b) { + $a = $a | 0; + $b = $b | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = $a ^ -1; + $1 = $0 ^ $b; + $2 = $1 << 16; + $3 = $2 & $1; + $4 = $3 << 8; + $5 = $4 & $3; + $6 = $5 << 4; + $7 = $6 & $5; + $8 = $7 << 2; + $9 = $8 & $7; + $10 = $9 << 1; + $11 = $10 & $9; + $12 = $11 >> 31; + STACKTOP = sp; + return $12 | 0; + } + function _s32_gte($a) { + $a = $a | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = ($a + -67108845) | 0; + $1 = $0 >> 31; + $2 = $1 ^ -1; + STACKTOP = sp; + return $2 | 0; + } + function _fsum($output, $in) { + $output = $output | 0; + $in = $in | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $101 = 0, + $102 = 0, + $103 = 0, + $104 = 0, + $105 = 0, + $106 = 0, + $107 = 0, + $108 = 0, + $109 = 0, + $11 = 0, + $110 = 0, + $111 = 0, + $112 = 0, + $113 = 0, + $114 = 0, + $115 = 0; + var $116 = 0, + $117 = 0, + $118 = 0, + $119 = 0, + $12 = 0, + $120 = 0, + $121 = 0, + $122 = 0, + $123 = 0, + $124 = 0, + $125 = 0, + $126 = 0, + $127 = 0, + $128 = 0, + $129 = 0, + $13 = 0, + $130 = 0, + $131 = 0, + $132 = 0, + $133 = 0; + var $134 = 0, + $135 = 0, + $136 = 0, + $137 = 0, + $138 = 0, + $139 = 0, + $14 = 0, + $140 = 0, + $141 = 0, + $142 = 0, + $143 = 0, + $144 = 0, + $145 = 0, + $146 = 0, + $147 = 0, + $148 = 0, + $149 = 0, + $15 = 0, + $150 = 0, + $151 = 0; + var $152 = 0, + $153 = 0, + $154 = 0, + $155 = 0, + $156 = 0, + $157 = 0, + $158 = 0, + $159 = 0, + $16 = 0, + $160 = 0, + $161 = 0, + $162 = 0, + $163 = 0, + $164 = 0, + $165 = 0, + $166 = 0, + $167 = 0, + $168 = 0, + $169 = 0, + $17 = 0; + var $170 = 0, + $171 = 0, + $172 = 0, + $173 = 0, + $174 = 0, + $175 = 0, + $176 = 0, + $177 = 0, + $178 = 0, + $179 = 0, + $18 = 0, + $180 = 0, + $181 = 0, + $182 = 0, + $183 = 0, + $184 = 0, + $185 = 0, + $186 = 0, + $187 = 0, + $188 = 0; + var $189 = 0, + $19 = 0, + $190 = 0, + $191 = 0, + $192 = 0, + $193 = 0, + $194 = 0, + $195 = 0, + $196 = 0, + $197 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0, + $27 = 0, + $28 = 0; + var $29 = 0, + $3 = 0, + $30 = 0, + $31 = 0, + $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $37 = 0, + $38 = 0, + $39 = 0, + $4 = 0, + $40 = 0, + $41 = 0, + $42 = 0, + $43 = 0, + $44 = 0, + $45 = 0, + $46 = 0; + var $47 = 0, + $48 = 0, + $49 = 0, + $5 = 0, + $50 = 0, + $51 = 0, + $52 = 0, + $53 = 0, + $54 = 0, + $55 = 0, + $56 = 0, + $57 = 0, + $58 = 0, + $59 = 0, + $6 = 0, + $60 = 0, + $61 = 0, + $62 = 0, + $63 = 0, + $64 = 0; + var $65 = 0, + $66 = 0, + $67 = 0, + $68 = 0, + $69 = 0, + $7 = 0, + $70 = 0, + $71 = 0, + $72 = 0, + $73 = 0, + $74 = 0, + $75 = 0, + $76 = 0, + $77 = 0, + $78 = 0, + $79 = 0, + $8 = 0, + $80 = 0, + $81 = 0, + $82 = 0; + var $83 = 0, + $84 = 0, + $85 = 0, + $86 = 0, + $87 = 0, + $88 = 0, + $89 = 0, + $9 = 0, + $90 = 0, + $91 = 0, + $92 = 0, + $93 = 0, + $94 = 0, + $95 = 0, + $96 = 0, + $97 = 0, + $98 = 0, + $99 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = $output; + $1 = $0; + $2 = HEAP32[$1 >> 2] | 0; + $3 = ($0 + 4) | 0; + $4 = $3; + $5 = HEAP32[$4 >> 2] | 0; + $6 = $in; + $7 = $6; + $8 = HEAP32[$7 >> 2] | 0; + $9 = ($6 + 4) | 0; + $10 = $9; + $11 = HEAP32[$10 >> 2] | 0; + $12 = _i64Add($8 | 0, $11 | 0, $2 | 0, $5 | 0) | 0; + $13 = tempRet0; + $14 = $output; + $15 = $14; + HEAP32[$15 >> 2] = $12; + $16 = ($14 + 4) | 0; + $17 = $16; + HEAP32[$17 >> 2] = $13; + $18 = ($output + 8) | 0; + $19 = $18; + $20 = $19; + $21 = HEAP32[$20 >> 2] | 0; + $22 = ($19 + 4) | 0; + $23 = $22; + $24 = HEAP32[$23 >> 2] | 0; + $25 = ($in + 8) | 0; + $26 = $25; + $27 = $26; + $28 = HEAP32[$27 >> 2] | 0; + $29 = ($26 + 4) | 0; + $30 = $29; + $31 = HEAP32[$30 >> 2] | 0; + $32 = _i64Add($28 | 0, $31 | 0, $21 | 0, $24 | 0) | 0; + $33 = tempRet0; + $34 = $18; + $35 = $34; + HEAP32[$35 >> 2] = $32; + $36 = ($34 + 4) | 0; + $37 = $36; + HEAP32[$37 >> 2] = $33; + $38 = ($output + 16) | 0; + $39 = $38; + $40 = $39; + $41 = HEAP32[$40 >> 2] | 0; + $42 = ($39 + 4) | 0; + $43 = $42; + $44 = HEAP32[$43 >> 2] | 0; + $45 = ($in + 16) | 0; + $46 = $45; + $47 = $46; + $48 = HEAP32[$47 >> 2] | 0; + $49 = ($46 + 4) | 0; + $50 = $49; + $51 = HEAP32[$50 >> 2] | 0; + $52 = _i64Add($48 | 0, $51 | 0, $41 | 0, $44 | 0) | 0; + $53 = tempRet0; + $54 = $38; + $55 = $54; + HEAP32[$55 >> 2] = $52; + $56 = ($54 + 4) | 0; + $57 = $56; + HEAP32[$57 >> 2] = $53; + $58 = ($output + 24) | 0; + $59 = $58; + $60 = $59; + $61 = HEAP32[$60 >> 2] | 0; + $62 = ($59 + 4) | 0; + $63 = $62; + $64 = HEAP32[$63 >> 2] | 0; + $65 = ($in + 24) | 0; + $66 = $65; + $67 = $66; + $68 = HEAP32[$67 >> 2] | 0; + $69 = ($66 + 4) | 0; + $70 = $69; + $71 = HEAP32[$70 >> 2] | 0; + $72 = _i64Add($68 | 0, $71 | 0, $61 | 0, $64 | 0) | 0; + $73 = tempRet0; + $74 = $58; + $75 = $74; + HEAP32[$75 >> 2] = $72; + $76 = ($74 + 4) | 0; + $77 = $76; + HEAP32[$77 >> 2] = $73; + $78 = ($output + 32) | 0; + $79 = $78; + $80 = $79; + $81 = HEAP32[$80 >> 2] | 0; + $82 = ($79 + 4) | 0; + $83 = $82; + $84 = HEAP32[$83 >> 2] | 0; + $85 = ($in + 32) | 0; + $86 = $85; + $87 = $86; + $88 = HEAP32[$87 >> 2] | 0; + $89 = ($86 + 4) | 0; + $90 = $89; + $91 = HEAP32[$90 >> 2] | 0; + $92 = _i64Add($88 | 0, $91 | 0, $81 | 0, $84 | 0) | 0; + $93 = tempRet0; + $94 = $78; + $95 = $94; + HEAP32[$95 >> 2] = $92; + $96 = ($94 + 4) | 0; + $97 = $96; + HEAP32[$97 >> 2] = $93; + $98 = ($output + 40) | 0; + $99 = $98; + $100 = $99; + $101 = HEAP32[$100 >> 2] | 0; + $102 = ($99 + 4) | 0; + $103 = $102; + $104 = HEAP32[$103 >> 2] | 0; + $105 = ($in + 40) | 0; + $106 = $105; + $107 = $106; + $108 = HEAP32[$107 >> 2] | 0; + $109 = ($106 + 4) | 0; + $110 = $109; + $111 = HEAP32[$110 >> 2] | 0; + $112 = _i64Add($108 | 0, $111 | 0, $101 | 0, $104 | 0) | 0; + $113 = tempRet0; + $114 = $98; + $115 = $114; + HEAP32[$115 >> 2] = $112; + $116 = ($114 + 4) | 0; + $117 = $116; + HEAP32[$117 >> 2] = $113; + $118 = ($output + 48) | 0; + $119 = $118; + $120 = $119; + $121 = HEAP32[$120 >> 2] | 0; + $122 = ($119 + 4) | 0; + $123 = $122; + $124 = HEAP32[$123 >> 2] | 0; + $125 = ($in + 48) | 0; + $126 = $125; + $127 = $126; + $128 = HEAP32[$127 >> 2] | 0; + $129 = ($126 + 4) | 0; + $130 = $129; + $131 = HEAP32[$130 >> 2] | 0; + $132 = _i64Add($128 | 0, $131 | 0, $121 | 0, $124 | 0) | 0; + $133 = tempRet0; + $134 = $118; + $135 = $134; + HEAP32[$135 >> 2] = $132; + $136 = ($134 + 4) | 0; + $137 = $136; + HEAP32[$137 >> 2] = $133; + $138 = ($output + 56) | 0; + $139 = $138; + $140 = $139; + $141 = HEAP32[$140 >> 2] | 0; + $142 = ($139 + 4) | 0; + $143 = $142; + $144 = HEAP32[$143 >> 2] | 0; + $145 = ($in + 56) | 0; + $146 = $145; + $147 = $146; + $148 = HEAP32[$147 >> 2] | 0; + $149 = ($146 + 4) | 0; + $150 = $149; + $151 = HEAP32[$150 >> 2] | 0; + $152 = _i64Add($148 | 0, $151 | 0, $141 | 0, $144 | 0) | 0; + $153 = tempRet0; + $154 = $138; + $155 = $154; + HEAP32[$155 >> 2] = $152; + $156 = ($154 + 4) | 0; + $157 = $156; + HEAP32[$157 >> 2] = $153; + $158 = ($output + 64) | 0; + $159 = $158; + $160 = $159; + $161 = HEAP32[$160 >> 2] | 0; + $162 = ($159 + 4) | 0; + $163 = $162; + $164 = HEAP32[$163 >> 2] | 0; + $165 = ($in + 64) | 0; + $166 = $165; + $167 = $166; + $168 = HEAP32[$167 >> 2] | 0; + $169 = ($166 + 4) | 0; + $170 = $169; + $171 = HEAP32[$170 >> 2] | 0; + $172 = _i64Add($168 | 0, $171 | 0, $161 | 0, $164 | 0) | 0; + $173 = tempRet0; + $174 = $158; + $175 = $174; + HEAP32[$175 >> 2] = $172; + $176 = ($174 + 4) | 0; + $177 = $176; + HEAP32[$177 >> 2] = $173; + $178 = ($output + 72) | 0; + $179 = $178; + $180 = $179; + $181 = HEAP32[$180 >> 2] | 0; + $182 = ($179 + 4) | 0; + $183 = $182; + $184 = HEAP32[$183 >> 2] | 0; + $185 = ($in + 72) | 0; + $186 = $185; + $187 = $186; + $188 = HEAP32[$187 >> 2] | 0; + $189 = ($186 + 4) | 0; + $190 = $189; + $191 = HEAP32[$190 >> 2] | 0; + $192 = _i64Add($188 | 0, $191 | 0, $181 | 0, $184 | 0) | 0; + $193 = tempRet0; + $194 = $178; + $195 = $194; + HEAP32[$195 >> 2] = $192; + $196 = ($194 + 4) | 0; + $197 = $196; + HEAP32[$197 >> 2] = $193; + STACKTOP = sp; + return; + } + function _fdifference($output, $in) { + $output = $output | 0; + $in = $in | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $101 = 0, + $102 = 0, + $103 = 0, + $104 = 0, + $105 = 0, + $106 = 0, + $107 = 0, + $108 = 0, + $109 = 0, + $11 = 0, + $110 = 0, + $111 = 0, + $112 = 0, + $113 = 0, + $114 = 0, + $115 = 0; + var $116 = 0, + $117 = 0, + $118 = 0, + $119 = 0, + $12 = 0, + $120 = 0, + $121 = 0, + $122 = 0, + $123 = 0, + $124 = 0, + $125 = 0, + $126 = 0, + $127 = 0, + $128 = 0, + $129 = 0, + $13 = 0, + $130 = 0, + $131 = 0, + $132 = 0, + $133 = 0; + var $134 = 0, + $135 = 0, + $136 = 0, + $137 = 0, + $138 = 0, + $139 = 0, + $14 = 0, + $140 = 0, + $141 = 0, + $142 = 0, + $143 = 0, + $144 = 0, + $145 = 0, + $146 = 0, + $147 = 0, + $148 = 0, + $149 = 0, + $15 = 0, + $150 = 0, + $151 = 0; + var $152 = 0, + $153 = 0, + $154 = 0, + $155 = 0, + $156 = 0, + $157 = 0, + $158 = 0, + $159 = 0, + $16 = 0, + $160 = 0, + $161 = 0, + $162 = 0, + $163 = 0, + $164 = 0, + $165 = 0, + $166 = 0, + $167 = 0, + $168 = 0, + $169 = 0, + $17 = 0; + var $170 = 0, + $171 = 0, + $172 = 0, + $173 = 0, + $174 = 0, + $175 = 0, + $176 = 0, + $177 = 0, + $178 = 0, + $179 = 0, + $18 = 0, + $180 = 0, + $181 = 0, + $182 = 0, + $183 = 0, + $184 = 0, + $185 = 0, + $186 = 0, + $187 = 0, + $188 = 0; + var $189 = 0, + $19 = 0, + $190 = 0, + $191 = 0, + $192 = 0, + $193 = 0, + $194 = 0, + $195 = 0, + $196 = 0, + $197 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0, + $27 = 0, + $28 = 0; + var $29 = 0, + $3 = 0, + $30 = 0, + $31 = 0, + $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $37 = 0, + $38 = 0, + $39 = 0, + $4 = 0, + $40 = 0, + $41 = 0, + $42 = 0, + $43 = 0, + $44 = 0, + $45 = 0, + $46 = 0; + var $47 = 0, + $48 = 0, + $49 = 0, + $5 = 0, + $50 = 0, + $51 = 0, + $52 = 0, + $53 = 0, + $54 = 0, + $55 = 0, + $56 = 0, + $57 = 0, + $58 = 0, + $59 = 0, + $6 = 0, + $60 = 0, + $61 = 0, + $62 = 0, + $63 = 0, + $64 = 0; + var $65 = 0, + $66 = 0, + $67 = 0, + $68 = 0, + $69 = 0, + $7 = 0, + $70 = 0, + $71 = 0, + $72 = 0, + $73 = 0, + $74 = 0, + $75 = 0, + $76 = 0, + $77 = 0, + $78 = 0, + $79 = 0, + $8 = 0, + $80 = 0, + $81 = 0, + $82 = 0; + var $83 = 0, + $84 = 0, + $85 = 0, + $86 = 0, + $87 = 0, + $88 = 0, + $89 = 0, + $9 = 0, + $90 = 0, + $91 = 0, + $92 = 0, + $93 = 0, + $94 = 0, + $95 = 0, + $96 = 0, + $97 = 0, + $98 = 0, + $99 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = $in; + $1 = $0; + $2 = HEAP32[$1 >> 2] | 0; + $3 = ($0 + 4) | 0; + $4 = $3; + $5 = HEAP32[$4 >> 2] | 0; + $6 = $output; + $7 = $6; + $8 = HEAP32[$7 >> 2] | 0; + $9 = ($6 + 4) | 0; + $10 = $9; + $11 = HEAP32[$10 >> 2] | 0; + $12 = _i64Subtract($2 | 0, $5 | 0, $8 | 0, $11 | 0) | 0; + $13 = tempRet0; + $14 = $output; + $15 = $14; + HEAP32[$15 >> 2] = $12; + $16 = ($14 + 4) | 0; + $17 = $16; + HEAP32[$17 >> 2] = $13; + $18 = ($in + 8) | 0; + $19 = $18; + $20 = $19; + $21 = HEAP32[$20 >> 2] | 0; + $22 = ($19 + 4) | 0; + $23 = $22; + $24 = HEAP32[$23 >> 2] | 0; + $25 = ($output + 8) | 0; + $26 = $25; + $27 = $26; + $28 = HEAP32[$27 >> 2] | 0; + $29 = ($26 + 4) | 0; + $30 = $29; + $31 = HEAP32[$30 >> 2] | 0; + $32 = _i64Subtract($21 | 0, $24 | 0, $28 | 0, $31 | 0) | 0; + $33 = tempRet0; + $34 = $25; + $35 = $34; + HEAP32[$35 >> 2] = $32; + $36 = ($34 + 4) | 0; + $37 = $36; + HEAP32[$37 >> 2] = $33; + $38 = ($in + 16) | 0; + $39 = $38; + $40 = $39; + $41 = HEAP32[$40 >> 2] | 0; + $42 = ($39 + 4) | 0; + $43 = $42; + $44 = HEAP32[$43 >> 2] | 0; + $45 = ($output + 16) | 0; + $46 = $45; + $47 = $46; + $48 = HEAP32[$47 >> 2] | 0; + $49 = ($46 + 4) | 0; + $50 = $49; + $51 = HEAP32[$50 >> 2] | 0; + $52 = _i64Subtract($41 | 0, $44 | 0, $48 | 0, $51 | 0) | 0; + $53 = tempRet0; + $54 = $45; + $55 = $54; + HEAP32[$55 >> 2] = $52; + $56 = ($54 + 4) | 0; + $57 = $56; + HEAP32[$57 >> 2] = $53; + $58 = ($in + 24) | 0; + $59 = $58; + $60 = $59; + $61 = HEAP32[$60 >> 2] | 0; + $62 = ($59 + 4) | 0; + $63 = $62; + $64 = HEAP32[$63 >> 2] | 0; + $65 = ($output + 24) | 0; + $66 = $65; + $67 = $66; + $68 = HEAP32[$67 >> 2] | 0; + $69 = ($66 + 4) | 0; + $70 = $69; + $71 = HEAP32[$70 >> 2] | 0; + $72 = _i64Subtract($61 | 0, $64 | 0, $68 | 0, $71 | 0) | 0; + $73 = tempRet0; + $74 = $65; + $75 = $74; + HEAP32[$75 >> 2] = $72; + $76 = ($74 + 4) | 0; + $77 = $76; + HEAP32[$77 >> 2] = $73; + $78 = ($in + 32) | 0; + $79 = $78; + $80 = $79; + $81 = HEAP32[$80 >> 2] | 0; + $82 = ($79 + 4) | 0; + $83 = $82; + $84 = HEAP32[$83 >> 2] | 0; + $85 = ($output + 32) | 0; + $86 = $85; + $87 = $86; + $88 = HEAP32[$87 >> 2] | 0; + $89 = ($86 + 4) | 0; + $90 = $89; + $91 = HEAP32[$90 >> 2] | 0; + $92 = _i64Subtract($81 | 0, $84 | 0, $88 | 0, $91 | 0) | 0; + $93 = tempRet0; + $94 = $85; + $95 = $94; + HEAP32[$95 >> 2] = $92; + $96 = ($94 + 4) | 0; + $97 = $96; + HEAP32[$97 >> 2] = $93; + $98 = ($in + 40) | 0; + $99 = $98; + $100 = $99; + $101 = HEAP32[$100 >> 2] | 0; + $102 = ($99 + 4) | 0; + $103 = $102; + $104 = HEAP32[$103 >> 2] | 0; + $105 = ($output + 40) | 0; + $106 = $105; + $107 = $106; + $108 = HEAP32[$107 >> 2] | 0; + $109 = ($106 + 4) | 0; + $110 = $109; + $111 = HEAP32[$110 >> 2] | 0; + $112 = _i64Subtract($101 | 0, $104 | 0, $108 | 0, $111 | 0) | 0; + $113 = tempRet0; + $114 = $105; + $115 = $114; + HEAP32[$115 >> 2] = $112; + $116 = ($114 + 4) | 0; + $117 = $116; + HEAP32[$117 >> 2] = $113; + $118 = ($in + 48) | 0; + $119 = $118; + $120 = $119; + $121 = HEAP32[$120 >> 2] | 0; + $122 = ($119 + 4) | 0; + $123 = $122; + $124 = HEAP32[$123 >> 2] | 0; + $125 = ($output + 48) | 0; + $126 = $125; + $127 = $126; + $128 = HEAP32[$127 >> 2] | 0; + $129 = ($126 + 4) | 0; + $130 = $129; + $131 = HEAP32[$130 >> 2] | 0; + $132 = _i64Subtract($121 | 0, $124 | 0, $128 | 0, $131 | 0) | 0; + $133 = tempRet0; + $134 = $125; + $135 = $134; + HEAP32[$135 >> 2] = $132; + $136 = ($134 + 4) | 0; + $137 = $136; + HEAP32[$137 >> 2] = $133; + $138 = ($in + 56) | 0; + $139 = $138; + $140 = $139; + $141 = HEAP32[$140 >> 2] | 0; + $142 = ($139 + 4) | 0; + $143 = $142; + $144 = HEAP32[$143 >> 2] | 0; + $145 = ($output + 56) | 0; + $146 = $145; + $147 = $146; + $148 = HEAP32[$147 >> 2] | 0; + $149 = ($146 + 4) | 0; + $150 = $149; + $151 = HEAP32[$150 >> 2] | 0; + $152 = _i64Subtract($141 | 0, $144 | 0, $148 | 0, $151 | 0) | 0; + $153 = tempRet0; + $154 = $145; + $155 = $154; + HEAP32[$155 >> 2] = $152; + $156 = ($154 + 4) | 0; + $157 = $156; + HEAP32[$157 >> 2] = $153; + $158 = ($in + 64) | 0; + $159 = $158; + $160 = $159; + $161 = HEAP32[$160 >> 2] | 0; + $162 = ($159 + 4) | 0; + $163 = $162; + $164 = HEAP32[$163 >> 2] | 0; + $165 = ($output + 64) | 0; + $166 = $165; + $167 = $166; + $168 = HEAP32[$167 >> 2] | 0; + $169 = ($166 + 4) | 0; + $170 = $169; + $171 = HEAP32[$170 >> 2] | 0; + $172 = _i64Subtract($161 | 0, $164 | 0, $168 | 0, $171 | 0) | 0; + $173 = tempRet0; + $174 = $165; + $175 = $174; + HEAP32[$175 >> 2] = $172; + $176 = ($174 + 4) | 0; + $177 = $176; + HEAP32[$177 >> 2] = $173; + $178 = ($in + 72) | 0; + $179 = $178; + $180 = $179; + $181 = HEAP32[$180 >> 2] | 0; + $182 = ($179 + 4) | 0; + $183 = $182; + $184 = HEAP32[$183 >> 2] | 0; + $185 = ($output + 72) | 0; + $186 = $185; + $187 = $186; + $188 = HEAP32[$187 >> 2] | 0; + $189 = ($186 + 4) | 0; + $190 = $189; + $191 = HEAP32[$190 >> 2] | 0; + $192 = _i64Subtract($181 | 0, $184 | 0, $188 | 0, $191 | 0) | 0; + $193 = tempRet0; + $194 = $185; + $195 = $194; + HEAP32[$195 >> 2] = $192; + $196 = ($194 + 4) | 0; + $197 = $196; + HEAP32[$197 >> 2] = $193; + STACKTOP = sp; + return; + } + function _fscalar_product($output, $in) { + $output = $output | 0; + $in = $in | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $101 = 0, + $102 = 0, + $103 = 0, + $104 = 0, + $105 = 0, + $106 = 0, + $107 = 0, + $108 = 0, + $109 = 0, + $11 = 0, + $110 = 0, + $111 = 0, + $112 = 0, + $113 = 0, + $114 = 0, + $115 = 0; + var $116 = 0, + $117 = 0, + $118 = 0, + $119 = 0, + $12 = 0, + $120 = 0, + $121 = 0, + $122 = 0, + $123 = 0, + $124 = 0, + $125 = 0, + $126 = 0, + $127 = 0, + $128 = 0, + $129 = 0, + $13 = 0, + $130 = 0, + $131 = 0, + $132 = 0, + $133 = 0; + var $134 = 0, + $135 = 0, + $136 = 0, + $137 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0, + $27 = 0, + $28 = 0; + var $29 = 0, + $3 = 0, + $30 = 0, + $31 = 0, + $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $37 = 0, + $38 = 0, + $39 = 0, + $4 = 0, + $40 = 0, + $41 = 0, + $42 = 0, + $43 = 0, + $44 = 0, + $45 = 0, + $46 = 0; + var $47 = 0, + $48 = 0, + $49 = 0, + $5 = 0, + $50 = 0, + $51 = 0, + $52 = 0, + $53 = 0, + $54 = 0, + $55 = 0, + $56 = 0, + $57 = 0, + $58 = 0, + $59 = 0, + $6 = 0, + $60 = 0, + $61 = 0, + $62 = 0, + $63 = 0, + $64 = 0; + var $65 = 0, + $66 = 0, + $67 = 0, + $68 = 0, + $69 = 0, + $7 = 0, + $70 = 0, + $71 = 0, + $72 = 0, + $73 = 0, + $74 = 0, + $75 = 0, + $76 = 0, + $77 = 0, + $78 = 0, + $79 = 0, + $8 = 0, + $80 = 0, + $81 = 0, + $82 = 0; + var $83 = 0, + $84 = 0, + $85 = 0, + $86 = 0, + $87 = 0, + $88 = 0, + $89 = 0, + $9 = 0, + $90 = 0, + $91 = 0, + $92 = 0, + $93 = 0, + $94 = 0, + $95 = 0, + $96 = 0, + $97 = 0, + $98 = 0, + $99 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = $in; + $1 = $0; + $2 = HEAP32[$1 >> 2] | 0; + $3 = ($0 + 4) | 0; + $4 = $3; + $5 = HEAP32[$4 >> 2] | 0; + $6 = ___muldi3($2 | 0, $5 | 0, 121665, 0) | 0; + $7 = tempRet0; + $8 = $output; + $9 = $8; + HEAP32[$9 >> 2] = $6; + $10 = ($8 + 4) | 0; + $11 = $10; + HEAP32[$11 >> 2] = $7; + $12 = ($in + 8) | 0; + $13 = $12; + $14 = $13; + $15 = HEAP32[$14 >> 2] | 0; + $16 = ($13 + 4) | 0; + $17 = $16; + $18 = HEAP32[$17 >> 2] | 0; + $19 = ___muldi3($15 | 0, $18 | 0, 121665, 0) | 0; + $20 = tempRet0; + $21 = ($output + 8) | 0; + $22 = $21; + $23 = $22; + HEAP32[$23 >> 2] = $19; + $24 = ($22 + 4) | 0; + $25 = $24; + HEAP32[$25 >> 2] = $20; + $26 = ($in + 16) | 0; + $27 = $26; + $28 = $27; + $29 = HEAP32[$28 >> 2] | 0; + $30 = ($27 + 4) | 0; + $31 = $30; + $32 = HEAP32[$31 >> 2] | 0; + $33 = ___muldi3($29 | 0, $32 | 0, 121665, 0) | 0; + $34 = tempRet0; + $35 = ($output + 16) | 0; + $36 = $35; + $37 = $36; + HEAP32[$37 >> 2] = $33; + $38 = ($36 + 4) | 0; + $39 = $38; + HEAP32[$39 >> 2] = $34; + $40 = ($in + 24) | 0; + $41 = $40; + $42 = $41; + $43 = HEAP32[$42 >> 2] | 0; + $44 = ($41 + 4) | 0; + $45 = $44; + $46 = HEAP32[$45 >> 2] | 0; + $47 = ___muldi3($43 | 0, $46 | 0, 121665, 0) | 0; + $48 = tempRet0; + $49 = ($output + 24) | 0; + $50 = $49; + $51 = $50; + HEAP32[$51 >> 2] = $47; + $52 = ($50 + 4) | 0; + $53 = $52; + HEAP32[$53 >> 2] = $48; + $54 = ($in + 32) | 0; + $55 = $54; + $56 = $55; + $57 = HEAP32[$56 >> 2] | 0; + $58 = ($55 + 4) | 0; + $59 = $58; + $60 = HEAP32[$59 >> 2] | 0; + $61 = ___muldi3($57 | 0, $60 | 0, 121665, 0) | 0; + $62 = tempRet0; + $63 = ($output + 32) | 0; + $64 = $63; + $65 = $64; + HEAP32[$65 >> 2] = $61; + $66 = ($64 + 4) | 0; + $67 = $66; + HEAP32[$67 >> 2] = $62; + $68 = ($in + 40) | 0; + $69 = $68; + $70 = $69; + $71 = HEAP32[$70 >> 2] | 0; + $72 = ($69 + 4) | 0; + $73 = $72; + $74 = HEAP32[$73 >> 2] | 0; + $75 = ___muldi3($71 | 0, $74 | 0, 121665, 0) | 0; + $76 = tempRet0; + $77 = ($output + 40) | 0; + $78 = $77; + $79 = $78; + HEAP32[$79 >> 2] = $75; + $80 = ($78 + 4) | 0; + $81 = $80; + HEAP32[$81 >> 2] = $76; + $82 = ($in + 48) | 0; + $83 = $82; + $84 = $83; + $85 = HEAP32[$84 >> 2] | 0; + $86 = ($83 + 4) | 0; + $87 = $86; + $88 = HEAP32[$87 >> 2] | 0; + $89 = ___muldi3($85 | 0, $88 | 0, 121665, 0) | 0; + $90 = tempRet0; + $91 = ($output + 48) | 0; + $92 = $91; + $93 = $92; + HEAP32[$93 >> 2] = $89; + $94 = ($92 + 4) | 0; + $95 = $94; + HEAP32[$95 >> 2] = $90; + $96 = ($in + 56) | 0; + $97 = $96; + $98 = $97; + $99 = HEAP32[$98 >> 2] | 0; + $100 = ($97 + 4) | 0; + $101 = $100; + $102 = HEAP32[$101 >> 2] | 0; + $103 = ___muldi3($99 | 0, $102 | 0, 121665, 0) | 0; + $104 = tempRet0; + $105 = ($output + 56) | 0; + $106 = $105; + $107 = $106; + HEAP32[$107 >> 2] = $103; + $108 = ($106 + 4) | 0; + $109 = $108; + HEAP32[$109 >> 2] = $104; + $110 = ($in + 64) | 0; + $111 = $110; + $112 = $111; + $113 = HEAP32[$112 >> 2] | 0; + $114 = ($111 + 4) | 0; + $115 = $114; + $116 = HEAP32[$115 >> 2] | 0; + $117 = ___muldi3($113 | 0, $116 | 0, 121665, 0) | 0; + $118 = tempRet0; + $119 = ($output + 64) | 0; + $120 = $119; + $121 = $120; + HEAP32[$121 >> 2] = $117; + $122 = ($120 + 4) | 0; + $123 = $122; + HEAP32[$123 >> 2] = $118; + $124 = ($in + 72) | 0; + $125 = $124; + $126 = $125; + $127 = HEAP32[$126 >> 2] | 0; + $128 = ($125 + 4) | 0; + $129 = $128; + $130 = HEAP32[$129 >> 2] | 0; + $131 = ___muldi3($127 | 0, $130 | 0, 121665, 0) | 0; + $132 = tempRet0; + $133 = ($output + 72) | 0; + $134 = $133; + $135 = $134; + HEAP32[$135 >> 2] = $131; + $136 = ($134 + 4) | 0; + $137 = $136; + HEAP32[$137 >> 2] = $132; + STACKTOP = sp; + return; + } + function _fsquare_inner($output, $in) { + $output = $output | 0; + $in = $in | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $1000 = 0, + $1001 = 0, + $1002 = 0, + $1003 = 0, + $1004 = 0, + $1005 = 0, + $1006 = 0, + $1007 = 0, + $1008 = 0, + $1009 = 0, + $101 = 0, + $1010 = 0, + $1011 = 0, + $1012 = 0, + $1013 = 0, + $1014 = 0; + var $1015 = 0, + $1016 = 0, + $1017 = 0, + $1018 = 0, + $1019 = 0, + $102 = 0, + $1020 = 0, + $1021 = 0, + $1022 = 0, + $1023 = 0, + $1024 = 0, + $1025 = 0, + $1026 = 0, + $1027 = 0, + $1028 = 0, + $1029 = 0, + $103 = 0, + $1030 = 0, + $1031 = 0, + $1032 = 0; + var $1033 = 0, + $1034 = 0, + $1035 = 0, + $1036 = 0, + $1037 = 0, + $1038 = 0, + $1039 = 0, + $104 = 0, + $1040 = 0, + $1041 = 0, + $1042 = 0, + $1043 = 0, + $1044 = 0, + $1045 = 0, + $1046 = 0, + $1047 = 0, + $1048 = 0, + $1049 = 0, + $105 = 0, + $1050 = 0; + var $1051 = 0, + $1052 = 0, + $1053 = 0, + $1054 = 0, + $1055 = 0, + $1056 = 0, + $1057 = 0, + $1058 = 0, + $1059 = 0, + $106 = 0, + $1060 = 0, + $1061 = 0, + $1062 = 0, + $1063 = 0, + $1064 = 0, + $1065 = 0, + $1066 = 0, + $1067 = 0, + $1068 = 0, + $1069 = 0; + var $107 = 0, + $1070 = 0, + $1071 = 0, + $1072 = 0, + $1073 = 0, + $1074 = 0, + $1075 = 0, + $1076 = 0, + $1077 = 0, + $1078 = 0, + $1079 = 0, + $108 = 0, + $1080 = 0, + $1081 = 0, + $1082 = 0, + $1083 = 0, + $1084 = 0, + $1085 = 0, + $1086 = 0, + $1087 = 0; + var $1088 = 0, + $1089 = 0, + $109 = 0, + $1090 = 0, + $1091 = 0, + $1092 = 0, + $1093 = 0, + $1094 = 0, + $1095 = 0, + $1096 = 0, + $1097 = 0, + $1098 = 0, + $1099 = 0, + $11 = 0, + $110 = 0, + $1100 = 0, + $1101 = 0, + $1102 = 0, + $1103 = 0, + $1104 = 0; + var $1105 = 0, + $1106 = 0, + $1107 = 0, + $1108 = 0, + $1109 = 0, + $111 = 0, + $1110 = 0, + $1111 = 0, + $1112 = 0, + $1113 = 0, + $1114 = 0, + $1115 = 0, + $1116 = 0, + $1117 = 0, + $1118 = 0, + $112 = 0, + $113 = 0, + $114 = 0, + $115 = 0, + $116 = 0; + var $117 = 0, + $118 = 0, + $119 = 0, + $12 = 0, + $120 = 0, + $121 = 0, + $122 = 0, + $123 = 0, + $124 = 0, + $125 = 0, + $126 = 0, + $127 = 0, + $128 = 0, + $129 = 0, + $13 = 0, + $130 = 0, + $131 = 0, + $132 = 0, + $133 = 0, + $134 = 0; + var $135 = 0, + $136 = 0, + $137 = 0, + $138 = 0, + $139 = 0, + $14 = 0, + $140 = 0, + $141 = 0, + $142 = 0, + $143 = 0, + $144 = 0, + $145 = 0, + $146 = 0, + $147 = 0, + $148 = 0, + $149 = 0, + $15 = 0, + $150 = 0, + $151 = 0, + $152 = 0; + var $153 = 0, + $154 = 0, + $155 = 0, + $156 = 0, + $157 = 0, + $158 = 0, + $159 = 0, + $16 = 0, + $160 = 0, + $161 = 0, + $162 = 0, + $163 = 0, + $164 = 0, + $165 = 0, + $166 = 0, + $167 = 0, + $168 = 0, + $169 = 0, + $17 = 0, + $170 = 0; + var $171 = 0, + $172 = 0, + $173 = 0, + $174 = 0, + $175 = 0, + $176 = 0, + $177 = 0, + $178 = 0, + $179 = 0, + $18 = 0, + $180 = 0, + $181 = 0, + $182 = 0, + $183 = 0, + $184 = 0, + $185 = 0, + $186 = 0, + $187 = 0, + $188 = 0, + $189 = 0; + var $19 = 0, + $190 = 0, + $191 = 0, + $192 = 0, + $193 = 0, + $194 = 0, + $195 = 0, + $196 = 0, + $197 = 0, + $198 = 0, + $199 = 0, + $2 = 0, + $20 = 0, + $200 = 0, + $201 = 0, + $202 = 0, + $203 = 0, + $204 = 0, + $205 = 0, + $206 = 0; + var $207 = 0, + $208 = 0, + $209 = 0, + $21 = 0, + $210 = 0, + $211 = 0, + $212 = 0, + $213 = 0, + $214 = 0, + $215 = 0, + $216 = 0, + $217 = 0, + $218 = 0, + $219 = 0, + $22 = 0, + $220 = 0, + $221 = 0, + $222 = 0, + $223 = 0, + $224 = 0; + var $225 = 0, + $226 = 0, + $227 = 0, + $228 = 0, + $229 = 0, + $23 = 0, + $230 = 0, + $231 = 0, + $232 = 0, + $233 = 0, + $234 = 0, + $235 = 0, + $236 = 0, + $237 = 0, + $238 = 0, + $239 = 0, + $24 = 0, + $240 = 0, + $241 = 0, + $242 = 0; + var $243 = 0, + $244 = 0, + $245 = 0, + $246 = 0, + $247 = 0, + $248 = 0, + $249 = 0, + $25 = 0, + $250 = 0, + $251 = 0, + $252 = 0, + $253 = 0, + $254 = 0, + $255 = 0, + $256 = 0, + $257 = 0, + $258 = 0, + $259 = 0, + $26 = 0, + $260 = 0; + var $261 = 0, + $262 = 0, + $263 = 0, + $264 = 0, + $265 = 0, + $266 = 0, + $267 = 0, + $268 = 0, + $269 = 0, + $27 = 0, + $270 = 0, + $271 = 0, + $272 = 0, + $273 = 0, + $274 = 0, + $275 = 0, + $276 = 0, + $277 = 0, + $278 = 0, + $279 = 0; + var $28 = 0, + $280 = 0, + $281 = 0, + $282 = 0, + $283 = 0, + $284 = 0, + $285 = 0, + $286 = 0, + $287 = 0, + $288 = 0, + $289 = 0, + $29 = 0, + $290 = 0, + $291 = 0, + $292 = 0, + $293 = 0, + $294 = 0, + $295 = 0, + $296 = 0, + $297 = 0; + var $298 = 0, + $299 = 0, + $3 = 0, + $30 = 0, + $300 = 0, + $301 = 0, + $302 = 0, + $303 = 0, + $304 = 0, + $305 = 0, + $306 = 0, + $307 = 0, + $308 = 0, + $309 = 0, + $31 = 0, + $310 = 0, + $311 = 0, + $312 = 0, + $313 = 0, + $314 = 0; + var $315 = 0, + $316 = 0, + $317 = 0, + $318 = 0, + $319 = 0, + $32 = 0, + $320 = 0, + $321 = 0, + $322 = 0, + $323 = 0, + $324 = 0, + $325 = 0, + $326 = 0, + $327 = 0, + $328 = 0, + $329 = 0, + $33 = 0, + $330 = 0, + $331 = 0, + $332 = 0; + var $333 = 0, + $334 = 0, + $335 = 0, + $336 = 0, + $337 = 0, + $338 = 0, + $339 = 0, + $34 = 0, + $340 = 0, + $341 = 0, + $342 = 0, + $343 = 0, + $344 = 0, + $345 = 0, + $346 = 0, + $347 = 0, + $348 = 0, + $349 = 0, + $35 = 0, + $350 = 0; + var $351 = 0, + $352 = 0, + $353 = 0, + $354 = 0, + $355 = 0, + $356 = 0, + $357 = 0, + $358 = 0, + $359 = 0, + $36 = 0, + $360 = 0, + $361 = 0, + $362 = 0, + $363 = 0, + $364 = 0, + $365 = 0, + $366 = 0, + $367 = 0, + $368 = 0, + $369 = 0; + var $37 = 0, + $370 = 0, + $371 = 0, + $372 = 0, + $373 = 0, + $374 = 0, + $375 = 0, + $376 = 0, + $377 = 0, + $378 = 0, + $379 = 0, + $38 = 0, + $380 = 0, + $381 = 0, + $382 = 0, + $383 = 0, + $384 = 0, + $385 = 0, + $386 = 0, + $387 = 0; + var $388 = 0, + $389 = 0, + $39 = 0, + $390 = 0, + $391 = 0, + $392 = 0, + $393 = 0, + $394 = 0, + $395 = 0, + $396 = 0, + $397 = 0, + $398 = 0, + $399 = 0, + $4 = 0, + $40 = 0, + $400 = 0, + $401 = 0, + $402 = 0, + $403 = 0, + $404 = 0; + var $405 = 0, + $406 = 0, + $407 = 0, + $408 = 0, + $409 = 0, + $41 = 0, + $410 = 0, + $411 = 0, + $412 = 0, + $413 = 0, + $414 = 0, + $415 = 0, + $416 = 0, + $417 = 0, + $418 = 0, + $419 = 0, + $42 = 0, + $420 = 0, + $421 = 0, + $422 = 0; + var $423 = 0, + $424 = 0, + $425 = 0, + $426 = 0, + $427 = 0, + $428 = 0, + $429 = 0, + $43 = 0, + $430 = 0, + $431 = 0, + $432 = 0, + $433 = 0, + $434 = 0, + $435 = 0, + $436 = 0, + $437 = 0, + $438 = 0, + $439 = 0, + $44 = 0, + $440 = 0; + var $441 = 0, + $442 = 0, + $443 = 0, + $444 = 0, + $445 = 0, + $446 = 0, + $447 = 0, + $448 = 0, + $449 = 0, + $45 = 0, + $450 = 0, + $451 = 0, + $452 = 0, + $453 = 0, + $454 = 0, + $455 = 0, + $456 = 0, + $457 = 0, + $458 = 0, + $459 = 0; + var $46 = 0, + $460 = 0, + $461 = 0, + $462 = 0, + $463 = 0, + $464 = 0, + $465 = 0, + $466 = 0, + $467 = 0, + $468 = 0, + $469 = 0, + $47 = 0, + $470 = 0, + $471 = 0, + $472 = 0, + $473 = 0, + $474 = 0, + $475 = 0, + $476 = 0, + $477 = 0; + var $478 = 0, + $479 = 0, + $48 = 0, + $480 = 0, + $481 = 0, + $482 = 0, + $483 = 0, + $484 = 0, + $485 = 0, + $486 = 0, + $487 = 0, + $488 = 0, + $489 = 0, + $49 = 0, + $490 = 0, + $491 = 0, + $492 = 0, + $493 = 0, + $494 = 0, + $495 = 0; + var $496 = 0, + $497 = 0, + $498 = 0, + $499 = 0, + $5 = 0, + $50 = 0, + $500 = 0, + $501 = 0, + $502 = 0, + $503 = 0, + $504 = 0, + $505 = 0, + $506 = 0, + $507 = 0, + $508 = 0, + $509 = 0, + $51 = 0, + $510 = 0, + $511 = 0, + $512 = 0; + var $513 = 0, + $514 = 0, + $515 = 0, + $516 = 0, + $517 = 0, + $518 = 0, + $519 = 0, + $52 = 0, + $520 = 0, + $521 = 0, + $522 = 0, + $523 = 0, + $524 = 0, + $525 = 0, + $526 = 0, + $527 = 0, + $528 = 0, + $529 = 0, + $53 = 0, + $530 = 0; + var $531 = 0, + $532 = 0, + $533 = 0, + $534 = 0, + $535 = 0, + $536 = 0, + $537 = 0, + $538 = 0, + $539 = 0, + $54 = 0, + $540 = 0, + $541 = 0, + $542 = 0, + $543 = 0, + $544 = 0, + $545 = 0, + $546 = 0, + $547 = 0, + $548 = 0, + $549 = 0; + var $55 = 0, + $550 = 0, + $551 = 0, + $552 = 0, + $553 = 0, + $554 = 0, + $555 = 0, + $556 = 0, + $557 = 0, + $558 = 0, + $559 = 0, + $56 = 0, + $560 = 0, + $561 = 0, + $562 = 0, + $563 = 0, + $564 = 0, + $565 = 0, + $566 = 0, + $567 = 0; + var $568 = 0, + $569 = 0, + $57 = 0, + $570 = 0, + $571 = 0, + $572 = 0, + $573 = 0, + $574 = 0, + $575 = 0, + $576 = 0, + $577 = 0, + $578 = 0, + $579 = 0, + $58 = 0, + $580 = 0, + $581 = 0, + $582 = 0, + $583 = 0, + $584 = 0, + $585 = 0; + var $586 = 0, + $587 = 0, + $588 = 0, + $589 = 0, + $59 = 0, + $590 = 0, + $591 = 0, + $592 = 0, + $593 = 0, + $594 = 0, + $595 = 0, + $596 = 0, + $597 = 0, + $598 = 0, + $599 = 0, + $6 = 0, + $60 = 0, + $600 = 0, + $601 = 0, + $602 = 0; + var $603 = 0, + $604 = 0, + $605 = 0, + $606 = 0, + $607 = 0, + $608 = 0, + $609 = 0, + $61 = 0, + $610 = 0, + $611 = 0, + $612 = 0, + $613 = 0, + $614 = 0, + $615 = 0, + $616 = 0, + $617 = 0, + $618 = 0, + $619 = 0, + $62 = 0, + $620 = 0; + var $621 = 0, + $622 = 0, + $623 = 0, + $624 = 0, + $625 = 0, + $626 = 0, + $627 = 0, + $628 = 0, + $629 = 0, + $63 = 0, + $630 = 0, + $631 = 0, + $632 = 0, + $633 = 0, + $634 = 0, + $635 = 0, + $636 = 0, + $637 = 0, + $638 = 0, + $639 = 0; + var $64 = 0, + $640 = 0, + $641 = 0, + $642 = 0, + $643 = 0, + $644 = 0, + $645 = 0, + $646 = 0, + $647 = 0, + $648 = 0, + $649 = 0, + $65 = 0, + $650 = 0, + $651 = 0, + $652 = 0, + $653 = 0, + $654 = 0, + $655 = 0, + $656 = 0, + $657 = 0; + var $658 = 0, + $659 = 0, + $66 = 0, + $660 = 0, + $661 = 0, + $662 = 0, + $663 = 0, + $664 = 0, + $665 = 0, + $666 = 0, + $667 = 0, + $668 = 0, + $669 = 0, + $67 = 0, + $670 = 0, + $671 = 0, + $672 = 0, + $673 = 0, + $674 = 0, + $675 = 0; + var $676 = 0, + $677 = 0, + $678 = 0, + $679 = 0, + $68 = 0, + $680 = 0, + $681 = 0, + $682 = 0, + $683 = 0, + $684 = 0, + $685 = 0, + $686 = 0, + $687 = 0, + $688 = 0, + $689 = 0, + $69 = 0, + $690 = 0, + $691 = 0, + $692 = 0, + $693 = 0; + var $694 = 0, + $695 = 0, + $696 = 0, + $697 = 0, + $698 = 0, + $699 = 0, + $7 = 0, + $70 = 0, + $700 = 0, + $701 = 0, + $702 = 0, + $703 = 0, + $704 = 0, + $705 = 0, + $706 = 0, + $707 = 0, + $708 = 0, + $709 = 0, + $71 = 0, + $710 = 0; + var $711 = 0, + $712 = 0, + $713 = 0, + $714 = 0, + $715 = 0, + $716 = 0, + $717 = 0, + $718 = 0, + $719 = 0, + $72 = 0, + $720 = 0, + $721 = 0, + $722 = 0, + $723 = 0, + $724 = 0, + $725 = 0, + $726 = 0, + $727 = 0, + $728 = 0, + $729 = 0; + var $73 = 0, + $730 = 0, + $731 = 0, + $732 = 0, + $733 = 0, + $734 = 0, + $735 = 0, + $736 = 0, + $737 = 0, + $738 = 0, + $739 = 0, + $74 = 0, + $740 = 0, + $741 = 0, + $742 = 0, + $743 = 0, + $744 = 0, + $745 = 0, + $746 = 0, + $747 = 0; + var $748 = 0, + $749 = 0, + $75 = 0, + $750 = 0, + $751 = 0, + $752 = 0, + $753 = 0, + $754 = 0, + $755 = 0, + $756 = 0, + $757 = 0, + $758 = 0, + $759 = 0, + $76 = 0, + $760 = 0, + $761 = 0, + $762 = 0, + $763 = 0, + $764 = 0, + $765 = 0; + var $766 = 0, + $767 = 0, + $768 = 0, + $769 = 0, + $77 = 0, + $770 = 0, + $771 = 0, + $772 = 0, + $773 = 0, + $774 = 0, + $775 = 0, + $776 = 0, + $777 = 0, + $778 = 0, + $779 = 0, + $78 = 0, + $780 = 0, + $781 = 0, + $782 = 0, + $783 = 0; + var $784 = 0, + $785 = 0, + $786 = 0, + $787 = 0, + $788 = 0, + $789 = 0, + $79 = 0, + $790 = 0, + $791 = 0, + $792 = 0, + $793 = 0, + $794 = 0, + $795 = 0, + $796 = 0, + $797 = 0, + $798 = 0, + $799 = 0, + $8 = 0, + $80 = 0, + $800 = 0; + var $801 = 0, + $802 = 0, + $803 = 0, + $804 = 0, + $805 = 0, + $806 = 0, + $807 = 0, + $808 = 0, + $809 = 0, + $81 = 0, + $810 = 0, + $811 = 0, + $812 = 0, + $813 = 0, + $814 = 0, + $815 = 0, + $816 = 0, + $817 = 0, + $818 = 0, + $819 = 0; + var $82 = 0, + $820 = 0, + $821 = 0, + $822 = 0, + $823 = 0, + $824 = 0, + $825 = 0, + $826 = 0, + $827 = 0, + $828 = 0, + $829 = 0, + $83 = 0, + $830 = 0, + $831 = 0, + $832 = 0, + $833 = 0, + $834 = 0, + $835 = 0, + $836 = 0, + $837 = 0; + var $838 = 0, + $839 = 0, + $84 = 0, + $840 = 0, + $841 = 0, + $842 = 0, + $843 = 0, + $844 = 0, + $845 = 0, + $846 = 0, + $847 = 0, + $848 = 0, + $849 = 0, + $85 = 0, + $850 = 0, + $851 = 0, + $852 = 0, + $853 = 0, + $854 = 0, + $855 = 0; + var $856 = 0, + $857 = 0, + $858 = 0, + $859 = 0, + $86 = 0, + $860 = 0, + $861 = 0, + $862 = 0, + $863 = 0, + $864 = 0, + $865 = 0, + $866 = 0, + $867 = 0, + $868 = 0, + $869 = 0, + $87 = 0, + $870 = 0, + $871 = 0, + $872 = 0, + $873 = 0; + var $874 = 0, + $875 = 0, + $876 = 0, + $877 = 0, + $878 = 0, + $879 = 0, + $88 = 0, + $880 = 0, + $881 = 0, + $882 = 0, + $883 = 0, + $884 = 0, + $885 = 0, + $886 = 0, + $887 = 0, + $888 = 0, + $889 = 0, + $89 = 0, + $890 = 0, + $891 = 0; + var $892 = 0, + $893 = 0, + $894 = 0, + $895 = 0, + $896 = 0, + $897 = 0, + $898 = 0, + $899 = 0, + $9 = 0, + $90 = 0, + $900 = 0, + $901 = 0, + $902 = 0, + $903 = 0, + $904 = 0, + $905 = 0, + $906 = 0, + $907 = 0, + $908 = 0, + $909 = 0; + var $91 = 0, + $910 = 0, + $911 = 0, + $912 = 0, + $913 = 0, + $914 = 0, + $915 = 0, + $916 = 0, + $917 = 0, + $918 = 0, + $919 = 0, + $92 = 0, + $920 = 0, + $921 = 0, + $922 = 0, + $923 = 0, + $924 = 0, + $925 = 0, + $926 = 0, + $927 = 0; + var $928 = 0, + $929 = 0, + $93 = 0, + $930 = 0, + $931 = 0, + $932 = 0, + $933 = 0, + $934 = 0, + $935 = 0, + $936 = 0, + $937 = 0, + $938 = 0, + $939 = 0, + $94 = 0, + $940 = 0, + $941 = 0, + $942 = 0, + $943 = 0, + $944 = 0, + $945 = 0; + var $946 = 0, + $947 = 0, + $948 = 0, + $949 = 0, + $95 = 0, + $950 = 0, + $951 = 0, + $952 = 0, + $953 = 0, + $954 = 0, + $955 = 0, + $956 = 0, + $957 = 0, + $958 = 0, + $959 = 0, + $96 = 0, + $960 = 0, + $961 = 0, + $962 = 0, + $963 = 0; + var $964 = 0, + $965 = 0, + $966 = 0, + $967 = 0, + $968 = 0, + $969 = 0, + $97 = 0, + $970 = 0, + $971 = 0, + $972 = 0, + $973 = 0, + $974 = 0, + $975 = 0, + $976 = 0, + $977 = 0, + $978 = 0, + $979 = 0, + $98 = 0, + $980 = 0, + $981 = 0; + var $982 = 0, + $983 = 0, + $984 = 0, + $985 = 0, + $986 = 0, + $987 = 0, + $988 = 0, + $989 = 0, + $99 = 0, + $990 = 0, + $991 = 0, + $992 = 0, + $993 = 0, + $994 = 0, + $995 = 0, + $996 = 0, + $997 = 0, + $998 = 0, + $999 = 0, + label = 0; + var sp = 0; + sp = STACKTOP; + $0 = $in; + $1 = $0; + $2 = HEAP32[$1 >> 2] | 0; + $3 = ($0 + 4) | 0; + $4 = $3; + $5 = HEAP32[$4 >> 2] | 0; + $6 = _bitshift64Ashr(0, $2 | 0, 32) | 0; + $7 = tempRet0; + $8 = ___muldi3($6 | 0, $7 | 0, $6 | 0, $7 | 0) | 0; + $9 = tempRet0; + $10 = $output; + $11 = $10; + HEAP32[$11 >> 2] = $8; + $12 = ($10 + 4) | 0; + $13 = $12; + HEAP32[$13 >> 2] = $9; + $14 = $in; + $15 = $14; + $16 = HEAP32[$15 >> 2] | 0; + $17 = ($14 + 4) | 0; + $18 = $17; + $19 = HEAP32[$18 >> 2] | 0; + $20 = _bitshift64Ashr(0, $16 | 0, 31) | 0; + $21 = tempRet0; + $22 = ($in + 8) | 0; + $23 = $22; + $24 = $23; + $25 = HEAP32[$24 >> 2] | 0; + $26 = ($23 + 4) | 0; + $27 = $26; + $28 = HEAP32[$27 >> 2] | 0; + $29 = _bitshift64Ashr(0, $25 | 0, 32) | 0; + $30 = tempRet0; + $31 = ___muldi3($29 | 0, $30 | 0, $20 | 0, $21 | 0) | 0; + $32 = tempRet0; + $33 = ($output + 8) | 0; + $34 = $33; + $35 = $34; + HEAP32[$35 >> 2] = $31; + $36 = ($34 + 4) | 0; + $37 = $36; + HEAP32[$37 >> 2] = $32; + $38 = $22; + $39 = $38; + $40 = HEAP32[$39 >> 2] | 0; + $41 = ($38 + 4) | 0; + $42 = $41; + $43 = HEAP32[$42 >> 2] | 0; + $44 = _bitshift64Ashr(0, $40 | 0, 32) | 0; + $45 = tempRet0; + $46 = ___muldi3($44 | 0, $45 | 0, $44 | 0, $45 | 0) | 0; + $47 = tempRet0; + $48 = $in; + $49 = $48; + $50 = HEAP32[$49 >> 2] | 0; + $51 = ($48 + 4) | 0; + $52 = $51; + $53 = HEAP32[$52 >> 2] | 0; + $54 = _bitshift64Ashr(0, $50 | 0, 32) | 0; + $55 = tempRet0; + $56 = ($in + 16) | 0; + $57 = $56; + $58 = $57; + $59 = HEAP32[$58 >> 2] | 0; + $60 = ($57 + 4) | 0; + $61 = $60; + $62 = HEAP32[$61 >> 2] | 0; + $63 = _bitshift64Ashr(0, $59 | 0, 32) | 0; + $64 = tempRet0; + $65 = ___muldi3($63 | 0, $64 | 0, $54 | 0, $55 | 0) | 0; + $66 = tempRet0; + $67 = _i64Add($65 | 0, $66 | 0, $46 | 0, $47 | 0) | 0; + $68 = tempRet0; + $69 = _bitshift64Shl($67 | 0, $68 | 0, 1) | 0; + $70 = tempRet0; + $71 = ($output + 16) | 0; + $72 = $71; + $73 = $72; + HEAP32[$73 >> 2] = $69; + $74 = ($72 + 4) | 0; + $75 = $74; + HEAP32[$75 >> 2] = $70; + $76 = $22; + $77 = $76; + $78 = HEAP32[$77 >> 2] | 0; + $79 = ($76 + 4) | 0; + $80 = $79; + $81 = HEAP32[$80 >> 2] | 0; + $82 = _bitshift64Ashr(0, $78 | 0, 32) | 0; + $83 = tempRet0; + $84 = $56; + $85 = $84; + $86 = HEAP32[$85 >> 2] | 0; + $87 = ($84 + 4) | 0; + $88 = $87; + $89 = HEAP32[$88 >> 2] | 0; + $90 = _bitshift64Ashr(0, $86 | 0, 32) | 0; + $91 = tempRet0; + $92 = ___muldi3($90 | 0, $91 | 0, $82 | 0, $83 | 0) | 0; + $93 = tempRet0; + $94 = $in; + $95 = $94; + $96 = HEAP32[$95 >> 2] | 0; + $97 = ($94 + 4) | 0; + $98 = $97; + $99 = HEAP32[$98 >> 2] | 0; + $100 = _bitshift64Ashr(0, $96 | 0, 32) | 0; + $101 = tempRet0; + $102 = ($in + 24) | 0; + $103 = $102; + $104 = $103; + $105 = HEAP32[$104 >> 2] | 0; + $106 = ($103 + 4) | 0; + $107 = $106; + $108 = HEAP32[$107 >> 2] | 0; + $109 = _bitshift64Ashr(0, $105 | 0, 32) | 0; + $110 = tempRet0; + $111 = ___muldi3($109 | 0, $110 | 0, $100 | 0, $101 | 0) | 0; + $112 = tempRet0; + $113 = _i64Add($111 | 0, $112 | 0, $92 | 0, $93 | 0) | 0; + $114 = tempRet0; + $115 = _bitshift64Shl($113 | 0, $114 | 0, 1) | 0; + $116 = tempRet0; + $117 = ($output + 24) | 0; + $118 = $117; + $119 = $118; + HEAP32[$119 >> 2] = $115; + $120 = ($118 + 4) | 0; + $121 = $120; + HEAP32[$121 >> 2] = $116; + $122 = $56; + $123 = $122; + $124 = HEAP32[$123 >> 2] | 0; + $125 = ($122 + 4) | 0; + $126 = $125; + $127 = HEAP32[$126 >> 2] | 0; + $128 = _bitshift64Ashr(0, $124 | 0, 32) | 0; + $129 = tempRet0; + $130 = ___muldi3($128 | 0, $129 | 0, $128 | 0, $129 | 0) | 0; + $131 = tempRet0; + $132 = $22; + $133 = $132; + $134 = HEAP32[$133 >> 2] | 0; + $135 = ($132 + 4) | 0; + $136 = $135; + $137 = HEAP32[$136 >> 2] | 0; + $138 = _bitshift64Ashr(0, $134 | 0, 30) | 0; + $139 = tempRet0; + $140 = $102; + $141 = $140; + $142 = HEAP32[$141 >> 2] | 0; + $143 = ($140 + 4) | 0; + $144 = $143; + $145 = HEAP32[$144 >> 2] | 0; + $146 = _bitshift64Ashr(0, $142 | 0, 32) | 0; + $147 = tempRet0; + $148 = ___muldi3($146 | 0, $147 | 0, $138 | 0, $139 | 0) | 0; + $149 = tempRet0; + $150 = _i64Add($148 | 0, $149 | 0, $130 | 0, $131 | 0) | 0; + $151 = tempRet0; + $152 = $in; + $153 = $152; + $154 = HEAP32[$153 >> 2] | 0; + $155 = ($152 + 4) | 0; + $156 = $155; + $157 = HEAP32[$156 >> 2] | 0; + $158 = _bitshift64Ashr(0, $154 | 0, 31) | 0; + $159 = tempRet0; + $160 = ($in + 32) | 0; + $161 = $160; + $162 = $161; + $163 = HEAP32[$162 >> 2] | 0; + $164 = ($161 + 4) | 0; + $165 = $164; + $166 = HEAP32[$165 >> 2] | 0; + $167 = _bitshift64Ashr(0, $163 | 0, 32) | 0; + $168 = tempRet0; + $169 = ___muldi3($167 | 0, $168 | 0, $158 | 0, $159 | 0) | 0; + $170 = tempRet0; + $171 = _i64Add($150 | 0, $151 | 0, $169 | 0, $170 | 0) | 0; + $172 = tempRet0; + $173 = ($output + 32) | 0; + $174 = $173; + $175 = $174; + HEAP32[$175 >> 2] = $171; + $176 = ($174 + 4) | 0; + $177 = $176; + HEAP32[$177 >> 2] = $172; + $178 = $56; + $179 = $178; + $180 = HEAP32[$179 >> 2] | 0; + $181 = ($178 + 4) | 0; + $182 = $181; + $183 = HEAP32[$182 >> 2] | 0; + $184 = _bitshift64Ashr(0, $180 | 0, 32) | 0; + $185 = tempRet0; + $186 = $102; + $187 = $186; + $188 = HEAP32[$187 >> 2] | 0; + $189 = ($186 + 4) | 0; + $190 = $189; + $191 = HEAP32[$190 >> 2] | 0; + $192 = _bitshift64Ashr(0, $188 | 0, 32) | 0; + $193 = tempRet0; + $194 = ___muldi3($192 | 0, $193 | 0, $184 | 0, $185 | 0) | 0; + $195 = tempRet0; + $196 = $22; + $197 = $196; + $198 = HEAP32[$197 >> 2] | 0; + $199 = ($196 + 4) | 0; + $200 = $199; + $201 = HEAP32[$200 >> 2] | 0; + $202 = _bitshift64Ashr(0, $198 | 0, 32) | 0; + $203 = tempRet0; + $204 = $160; + $205 = $204; + $206 = HEAP32[$205 >> 2] | 0; + $207 = ($204 + 4) | 0; + $208 = $207; + $209 = HEAP32[$208 >> 2] | 0; + $210 = _bitshift64Ashr(0, $206 | 0, 32) | 0; + $211 = tempRet0; + $212 = ___muldi3($210 | 0, $211 | 0, $202 | 0, $203 | 0) | 0; + $213 = tempRet0; + $214 = _i64Add($212 | 0, $213 | 0, $194 | 0, $195 | 0) | 0; + $215 = tempRet0; + $216 = $in; + $217 = $216; + $218 = HEAP32[$217 >> 2] | 0; + $219 = ($216 + 4) | 0; + $220 = $219; + $221 = HEAP32[$220 >> 2] | 0; + $222 = _bitshift64Ashr(0, $218 | 0, 32) | 0; + $223 = tempRet0; + $224 = ($in + 40) | 0; + $225 = $224; + $226 = $225; + $227 = HEAP32[$226 >> 2] | 0; + $228 = ($225 + 4) | 0; + $229 = $228; + $230 = HEAP32[$229 >> 2] | 0; + $231 = _bitshift64Ashr(0, $227 | 0, 32) | 0; + $232 = tempRet0; + $233 = ___muldi3($231 | 0, $232 | 0, $222 | 0, $223 | 0) | 0; + $234 = tempRet0; + $235 = _i64Add($214 | 0, $215 | 0, $233 | 0, $234 | 0) | 0; + $236 = tempRet0; + $237 = _bitshift64Shl($235 | 0, $236 | 0, 1) | 0; + $238 = tempRet0; + $239 = ($output + 40) | 0; + $240 = $239; + $241 = $240; + HEAP32[$241 >> 2] = $237; + $242 = ($240 + 4) | 0; + $243 = $242; + HEAP32[$243 >> 2] = $238; + $244 = $102; + $245 = $244; + $246 = HEAP32[$245 >> 2] | 0; + $247 = ($244 + 4) | 0; + $248 = $247; + $249 = HEAP32[$248 >> 2] | 0; + $250 = _bitshift64Ashr(0, $246 | 0, 32) | 0; + $251 = tempRet0; + $252 = ___muldi3($250 | 0, $251 | 0, $250 | 0, $251 | 0) | 0; + $253 = tempRet0; + $254 = $56; + $255 = $254; + $256 = HEAP32[$255 >> 2] | 0; + $257 = ($254 + 4) | 0; + $258 = $257; + $259 = HEAP32[$258 >> 2] | 0; + $260 = _bitshift64Ashr(0, $256 | 0, 32) | 0; + $261 = tempRet0; + $262 = $160; + $263 = $262; + $264 = HEAP32[$263 >> 2] | 0; + $265 = ($262 + 4) | 0; + $266 = $265; + $267 = HEAP32[$266 >> 2] | 0; + $268 = _bitshift64Ashr(0, $264 | 0, 32) | 0; + $269 = tempRet0; + $270 = ___muldi3($268 | 0, $269 | 0, $260 | 0, $261 | 0) | 0; + $271 = tempRet0; + $272 = _i64Add($270 | 0, $271 | 0, $252 | 0, $253 | 0) | 0; + $273 = tempRet0; + $274 = $in; + $275 = $274; + $276 = HEAP32[$275 >> 2] | 0; + $277 = ($274 + 4) | 0; + $278 = $277; + $279 = HEAP32[$278 >> 2] | 0; + $280 = _bitshift64Ashr(0, $276 | 0, 32) | 0; + $281 = tempRet0; + $282 = ($in + 48) | 0; + $283 = $282; + $284 = $283; + $285 = HEAP32[$284 >> 2] | 0; + $286 = ($283 + 4) | 0; + $287 = $286; + $288 = HEAP32[$287 >> 2] | 0; + $289 = _bitshift64Ashr(0, $285 | 0, 32) | 0; + $290 = tempRet0; + $291 = ___muldi3($289 | 0, $290 | 0, $280 | 0, $281 | 0) | 0; + $292 = tempRet0; + $293 = _i64Add($272 | 0, $273 | 0, $291 | 0, $292 | 0) | 0; + $294 = tempRet0; + $295 = $22; + $296 = $295; + $297 = HEAP32[$296 >> 2] | 0; + $298 = ($295 + 4) | 0; + $299 = $298; + $300 = HEAP32[$299 >> 2] | 0; + $301 = _bitshift64Ashr(0, $297 | 0, 31) | 0; + $302 = tempRet0; + $303 = $224; + $304 = $303; + $305 = HEAP32[$304 >> 2] | 0; + $306 = ($303 + 4) | 0; + $307 = $306; + $308 = HEAP32[$307 >> 2] | 0; + $309 = _bitshift64Ashr(0, $305 | 0, 32) | 0; + $310 = tempRet0; + $311 = ___muldi3($309 | 0, $310 | 0, $301 | 0, $302 | 0) | 0; + $312 = tempRet0; + $313 = _i64Add($293 | 0, $294 | 0, $311 | 0, $312 | 0) | 0; + $314 = tempRet0; + $315 = _bitshift64Shl($313 | 0, $314 | 0, 1) | 0; + $316 = tempRet0; + $317 = ($output + 48) | 0; + $318 = $317; + $319 = $318; + HEAP32[$319 >> 2] = $315; + $320 = ($318 + 4) | 0; + $321 = $320; + HEAP32[$321 >> 2] = $316; + $322 = $102; + $323 = $322; + $324 = HEAP32[$323 >> 2] | 0; + $325 = ($322 + 4) | 0; + $326 = $325; + $327 = HEAP32[$326 >> 2] | 0; + $328 = _bitshift64Ashr(0, $324 | 0, 32) | 0; + $329 = tempRet0; + $330 = $160; + $331 = $330; + $332 = HEAP32[$331 >> 2] | 0; + $333 = ($330 + 4) | 0; + $334 = $333; + $335 = HEAP32[$334 >> 2] | 0; + $336 = _bitshift64Ashr(0, $332 | 0, 32) | 0; + $337 = tempRet0; + $338 = ___muldi3($336 | 0, $337 | 0, $328 | 0, $329 | 0) | 0; + $339 = tempRet0; + $340 = $56; + $341 = $340; + $342 = HEAP32[$341 >> 2] | 0; + $343 = ($340 + 4) | 0; + $344 = $343; + $345 = HEAP32[$344 >> 2] | 0; + $346 = _bitshift64Ashr(0, $342 | 0, 32) | 0; + $347 = tempRet0; + $348 = $224; + $349 = $348; + $350 = HEAP32[$349 >> 2] | 0; + $351 = ($348 + 4) | 0; + $352 = $351; + $353 = HEAP32[$352 >> 2] | 0; + $354 = _bitshift64Ashr(0, $350 | 0, 32) | 0; + $355 = tempRet0; + $356 = ___muldi3($354 | 0, $355 | 0, $346 | 0, $347 | 0) | 0; + $357 = tempRet0; + $358 = _i64Add($356 | 0, $357 | 0, $338 | 0, $339 | 0) | 0; + $359 = tempRet0; + $360 = $22; + $361 = $360; + $362 = HEAP32[$361 >> 2] | 0; + $363 = ($360 + 4) | 0; + $364 = $363; + $365 = HEAP32[$364 >> 2] | 0; + $366 = _bitshift64Ashr(0, $362 | 0, 32) | 0; + $367 = tempRet0; + $368 = $282; + $369 = $368; + $370 = HEAP32[$369 >> 2] | 0; + $371 = ($368 + 4) | 0; + $372 = $371; + $373 = HEAP32[$372 >> 2] | 0; + $374 = _bitshift64Ashr(0, $370 | 0, 32) | 0; + $375 = tempRet0; + $376 = ___muldi3($374 | 0, $375 | 0, $366 | 0, $367 | 0) | 0; + $377 = tempRet0; + $378 = _i64Add($358 | 0, $359 | 0, $376 | 0, $377 | 0) | 0; + $379 = tempRet0; + $380 = $in; + $381 = $380; + $382 = HEAP32[$381 >> 2] | 0; + $383 = ($380 + 4) | 0; + $384 = $383; + $385 = HEAP32[$384 >> 2] | 0; + $386 = _bitshift64Ashr(0, $382 | 0, 32) | 0; + $387 = tempRet0; + $388 = ($in + 56) | 0; + $389 = $388; + $390 = $389; + $391 = HEAP32[$390 >> 2] | 0; + $392 = ($389 + 4) | 0; + $393 = $392; + $394 = HEAP32[$393 >> 2] | 0; + $395 = _bitshift64Ashr(0, $391 | 0, 32) | 0; + $396 = tempRet0; + $397 = ___muldi3($395 | 0, $396 | 0, $386 | 0, $387 | 0) | 0; + $398 = tempRet0; + $399 = _i64Add($378 | 0, $379 | 0, $397 | 0, $398 | 0) | 0; + $400 = tempRet0; + $401 = _bitshift64Shl($399 | 0, $400 | 0, 1) | 0; + $402 = tempRet0; + $403 = ($output + 56) | 0; + $404 = $403; + $405 = $404; + HEAP32[$405 >> 2] = $401; + $406 = ($404 + 4) | 0; + $407 = $406; + HEAP32[$407 >> 2] = $402; + $408 = $160; + $409 = $408; + $410 = HEAP32[$409 >> 2] | 0; + $411 = ($408 + 4) | 0; + $412 = $411; + $413 = HEAP32[$412 >> 2] | 0; + $414 = _bitshift64Ashr(0, $410 | 0, 32) | 0; + $415 = tempRet0; + $416 = ___muldi3($414 | 0, $415 | 0, $414 | 0, $415 | 0) | 0; + $417 = tempRet0; + $418 = $56; + $419 = $418; + $420 = HEAP32[$419 >> 2] | 0; + $421 = ($418 + 4) | 0; + $422 = $421; + $423 = HEAP32[$422 >> 2] | 0; + $424 = _bitshift64Ashr(0, $420 | 0, 32) | 0; + $425 = tempRet0; + $426 = $282; + $427 = $426; + $428 = HEAP32[$427 >> 2] | 0; + $429 = ($426 + 4) | 0; + $430 = $429; + $431 = HEAP32[$430 >> 2] | 0; + $432 = _bitshift64Ashr(0, $428 | 0, 32) | 0; + $433 = tempRet0; + $434 = ___muldi3($432 | 0, $433 | 0, $424 | 0, $425 | 0) | 0; + $435 = tempRet0; + $436 = $in; + $437 = $436; + $438 = HEAP32[$437 >> 2] | 0; + $439 = ($436 + 4) | 0; + $440 = $439; + $441 = HEAP32[$440 >> 2] | 0; + $442 = _bitshift64Ashr(0, $438 | 0, 32) | 0; + $443 = tempRet0; + $444 = ($in + 64) | 0; + $445 = $444; + $446 = $445; + $447 = HEAP32[$446 >> 2] | 0; + $448 = ($445 + 4) | 0; + $449 = $448; + $450 = HEAP32[$449 >> 2] | 0; + $451 = _bitshift64Ashr(0, $447 | 0, 32) | 0; + $452 = tempRet0; + $453 = ___muldi3($451 | 0, $452 | 0, $442 | 0, $443 | 0) | 0; + $454 = tempRet0; + $455 = _i64Add($453 | 0, $454 | 0, $434 | 0, $435 | 0) | 0; + $456 = tempRet0; + $457 = $22; + $458 = $457; + $459 = HEAP32[$458 >> 2] | 0; + $460 = ($457 + 4) | 0; + $461 = $460; + $462 = HEAP32[$461 >> 2] | 0; + $463 = _bitshift64Ashr(0, $459 | 0, 32) | 0; + $464 = tempRet0; + $465 = $388; + $466 = $465; + $467 = HEAP32[$466 >> 2] | 0; + $468 = ($465 + 4) | 0; + $469 = $468; + $470 = HEAP32[$469 >> 2] | 0; + $471 = _bitshift64Ashr(0, $467 | 0, 32) | 0; + $472 = tempRet0; + $473 = ___muldi3($471 | 0, $472 | 0, $463 | 0, $464 | 0) | 0; + $474 = tempRet0; + $475 = $102; + $476 = $475; + $477 = HEAP32[$476 >> 2] | 0; + $478 = ($475 + 4) | 0; + $479 = $478; + $480 = HEAP32[$479 >> 2] | 0; + $481 = _bitshift64Ashr(0, $477 | 0, 32) | 0; + $482 = tempRet0; + $483 = $224; + $484 = $483; + $485 = HEAP32[$484 >> 2] | 0; + $486 = ($483 + 4) | 0; + $487 = $486; + $488 = HEAP32[$487 >> 2] | 0; + $489 = _bitshift64Ashr(0, $485 | 0, 32) | 0; + $490 = tempRet0; + $491 = ___muldi3($489 | 0, $490 | 0, $481 | 0, $482 | 0) | 0; + $492 = tempRet0; + $493 = _i64Add($491 | 0, $492 | 0, $473 | 0, $474 | 0) | 0; + $494 = tempRet0; + $495 = _bitshift64Shl($493 | 0, $494 | 0, 1) | 0; + $496 = tempRet0; + $497 = _i64Add($455 | 0, $456 | 0, $495 | 0, $496 | 0) | 0; + $498 = tempRet0; + $499 = _bitshift64Shl($497 | 0, $498 | 0, 1) | 0; + $500 = tempRet0; + $501 = _i64Add($499 | 0, $500 | 0, $416 | 0, $417 | 0) | 0; + $502 = tempRet0; + $503 = ($output + 64) | 0; + $504 = $503; + $505 = $504; + HEAP32[$505 >> 2] = $501; + $506 = ($504 + 4) | 0; + $507 = $506; + HEAP32[$507 >> 2] = $502; + $508 = $160; + $509 = $508; + $510 = HEAP32[$509 >> 2] | 0; + $511 = ($508 + 4) | 0; + $512 = $511; + $513 = HEAP32[$512 >> 2] | 0; + $514 = _bitshift64Ashr(0, $510 | 0, 32) | 0; + $515 = tempRet0; + $516 = $224; + $517 = $516; + $518 = HEAP32[$517 >> 2] | 0; + $519 = ($516 + 4) | 0; + $520 = $519; + $521 = HEAP32[$520 >> 2] | 0; + $522 = _bitshift64Ashr(0, $518 | 0, 32) | 0; + $523 = tempRet0; + $524 = ___muldi3($522 | 0, $523 | 0, $514 | 0, $515 | 0) | 0; + $525 = tempRet0; + $526 = $102; + $527 = $526; + $528 = HEAP32[$527 >> 2] | 0; + $529 = ($526 + 4) | 0; + $530 = $529; + $531 = HEAP32[$530 >> 2] | 0; + $532 = _bitshift64Ashr(0, $528 | 0, 32) | 0; + $533 = tempRet0; + $534 = $282; + $535 = $534; + $536 = HEAP32[$535 >> 2] | 0; + $537 = ($534 + 4) | 0; + $538 = $537; + $539 = HEAP32[$538 >> 2] | 0; + $540 = _bitshift64Ashr(0, $536 | 0, 32) | 0; + $541 = tempRet0; + $542 = ___muldi3($540 | 0, $541 | 0, $532 | 0, $533 | 0) | 0; + $543 = tempRet0; + $544 = _i64Add($542 | 0, $543 | 0, $524 | 0, $525 | 0) | 0; + $545 = tempRet0; + $546 = $56; + $547 = $546; + $548 = HEAP32[$547 >> 2] | 0; + $549 = ($546 + 4) | 0; + $550 = $549; + $551 = HEAP32[$550 >> 2] | 0; + $552 = _bitshift64Ashr(0, $548 | 0, 32) | 0; + $553 = tempRet0; + $554 = $388; + $555 = $554; + $556 = HEAP32[$555 >> 2] | 0; + $557 = ($554 + 4) | 0; + $558 = $557; + $559 = HEAP32[$558 >> 2] | 0; + $560 = _bitshift64Ashr(0, $556 | 0, 32) | 0; + $561 = tempRet0; + $562 = ___muldi3($560 | 0, $561 | 0, $552 | 0, $553 | 0) | 0; + $563 = tempRet0; + $564 = _i64Add($544 | 0, $545 | 0, $562 | 0, $563 | 0) | 0; + $565 = tempRet0; + $566 = $22; + $567 = $566; + $568 = HEAP32[$567 >> 2] | 0; + $569 = ($566 + 4) | 0; + $570 = $569; + $571 = HEAP32[$570 >> 2] | 0; + $572 = _bitshift64Ashr(0, $568 | 0, 32) | 0; + $573 = tempRet0; + $574 = $444; + $575 = $574; + $576 = HEAP32[$575 >> 2] | 0; + $577 = ($574 + 4) | 0; + $578 = $577; + $579 = HEAP32[$578 >> 2] | 0; + $580 = _bitshift64Ashr(0, $576 | 0, 32) | 0; + $581 = tempRet0; + $582 = ___muldi3($580 | 0, $581 | 0, $572 | 0, $573 | 0) | 0; + $583 = tempRet0; + $584 = _i64Add($564 | 0, $565 | 0, $582 | 0, $583 | 0) | 0; + $585 = tempRet0; + $586 = $in; + $587 = $586; + $588 = HEAP32[$587 >> 2] | 0; + $589 = ($586 + 4) | 0; + $590 = $589; + $591 = HEAP32[$590 >> 2] | 0; + $592 = _bitshift64Ashr(0, $588 | 0, 32) | 0; + $593 = tempRet0; + $594 = ($in + 72) | 0; + $595 = $594; + $596 = $595; + $597 = HEAP32[$596 >> 2] | 0; + $598 = ($595 + 4) | 0; + $599 = $598; + $600 = HEAP32[$599 >> 2] | 0; + $601 = _bitshift64Ashr(0, $597 | 0, 32) | 0; + $602 = tempRet0; + $603 = ___muldi3($601 | 0, $602 | 0, $592 | 0, $593 | 0) | 0; + $604 = tempRet0; + $605 = _i64Add($584 | 0, $585 | 0, $603 | 0, $604 | 0) | 0; + $606 = tempRet0; + $607 = _bitshift64Shl($605 | 0, $606 | 0, 1) | 0; + $608 = tempRet0; + $609 = ($output + 72) | 0; + $610 = $609; + $611 = $610; + HEAP32[$611 >> 2] = $607; + $612 = ($610 + 4) | 0; + $613 = $612; + HEAP32[$613 >> 2] = $608; + $614 = $224; + $615 = $614; + $616 = HEAP32[$615 >> 2] | 0; + $617 = ($614 + 4) | 0; + $618 = $617; + $619 = HEAP32[$618 >> 2] | 0; + $620 = _bitshift64Ashr(0, $616 | 0, 32) | 0; + $621 = tempRet0; + $622 = ___muldi3($620 | 0, $621 | 0, $620 | 0, $621 | 0) | 0; + $623 = tempRet0; + $624 = $160; + $625 = $624; + $626 = HEAP32[$625 >> 2] | 0; + $627 = ($624 + 4) | 0; + $628 = $627; + $629 = HEAP32[$628 >> 2] | 0; + $630 = _bitshift64Ashr(0, $626 | 0, 32) | 0; + $631 = tempRet0; + $632 = $282; + $633 = $632; + $634 = HEAP32[$633 >> 2] | 0; + $635 = ($632 + 4) | 0; + $636 = $635; + $637 = HEAP32[$636 >> 2] | 0; + $638 = _bitshift64Ashr(0, $634 | 0, 32) | 0; + $639 = tempRet0; + $640 = ___muldi3($638 | 0, $639 | 0, $630 | 0, $631 | 0) | 0; + $641 = tempRet0; + $642 = _i64Add($640 | 0, $641 | 0, $622 | 0, $623 | 0) | 0; + $643 = tempRet0; + $644 = $56; + $645 = $644; + $646 = HEAP32[$645 >> 2] | 0; + $647 = ($644 + 4) | 0; + $648 = $647; + $649 = HEAP32[$648 >> 2] | 0; + $650 = _bitshift64Ashr(0, $646 | 0, 32) | 0; + $651 = tempRet0; + $652 = $444; + $653 = $652; + $654 = HEAP32[$653 >> 2] | 0; + $655 = ($652 + 4) | 0; + $656 = $655; + $657 = HEAP32[$656 >> 2] | 0; + $658 = _bitshift64Ashr(0, $654 | 0, 32) | 0; + $659 = tempRet0; + $660 = ___muldi3($658 | 0, $659 | 0, $650 | 0, $651 | 0) | 0; + $661 = tempRet0; + $662 = _i64Add($642 | 0, $643 | 0, $660 | 0, $661 | 0) | 0; + $663 = tempRet0; + $664 = $102; + $665 = $664; + $666 = HEAP32[$665 >> 2] | 0; + $667 = ($664 + 4) | 0; + $668 = $667; + $669 = HEAP32[$668 >> 2] | 0; + $670 = _bitshift64Ashr(0, $666 | 0, 32) | 0; + $671 = tempRet0; + $672 = $388; + $673 = $672; + $674 = HEAP32[$673 >> 2] | 0; + $675 = ($672 + 4) | 0; + $676 = $675; + $677 = HEAP32[$676 >> 2] | 0; + $678 = _bitshift64Ashr(0, $674 | 0, 32) | 0; + $679 = tempRet0; + $680 = ___muldi3($678 | 0, $679 | 0, $670 | 0, $671 | 0) | 0; + $681 = tempRet0; + $682 = $22; + $683 = $682; + $684 = HEAP32[$683 >> 2] | 0; + $685 = ($682 + 4) | 0; + $686 = $685; + $687 = HEAP32[$686 >> 2] | 0; + $688 = _bitshift64Ashr(0, $684 | 0, 32) | 0; + $689 = tempRet0; + $690 = $594; + $691 = $690; + $692 = HEAP32[$691 >> 2] | 0; + $693 = ($690 + 4) | 0; + $694 = $693; + $695 = HEAP32[$694 >> 2] | 0; + $696 = _bitshift64Ashr(0, $692 | 0, 32) | 0; + $697 = tempRet0; + $698 = ___muldi3($696 | 0, $697 | 0, $688 | 0, $689 | 0) | 0; + $699 = tempRet0; + $700 = _i64Add($698 | 0, $699 | 0, $680 | 0, $681 | 0) | 0; + $701 = tempRet0; + $702 = _bitshift64Shl($700 | 0, $701 | 0, 1) | 0; + $703 = tempRet0; + $704 = _i64Add($662 | 0, $663 | 0, $702 | 0, $703 | 0) | 0; + $705 = tempRet0; + $706 = _bitshift64Shl($704 | 0, $705 | 0, 1) | 0; + $707 = tempRet0; + $708 = ($output + 80) | 0; + $709 = $708; + $710 = $709; + HEAP32[$710 >> 2] = $706; + $711 = ($709 + 4) | 0; + $712 = $711; + HEAP32[$712 >> 2] = $707; + $713 = $224; + $714 = $713; + $715 = HEAP32[$714 >> 2] | 0; + $716 = ($713 + 4) | 0; + $717 = $716; + $718 = HEAP32[$717 >> 2] | 0; + $719 = _bitshift64Ashr(0, $715 | 0, 32) | 0; + $720 = tempRet0; + $721 = $282; + $722 = $721; + $723 = HEAP32[$722 >> 2] | 0; + $724 = ($721 + 4) | 0; + $725 = $724; + $726 = HEAP32[$725 >> 2] | 0; + $727 = _bitshift64Ashr(0, $723 | 0, 32) | 0; + $728 = tempRet0; + $729 = ___muldi3($727 | 0, $728 | 0, $719 | 0, $720 | 0) | 0; + $730 = tempRet0; + $731 = $160; + $732 = $731; + $733 = HEAP32[$732 >> 2] | 0; + $734 = ($731 + 4) | 0; + $735 = $734; + $736 = HEAP32[$735 >> 2] | 0; + $737 = _bitshift64Ashr(0, $733 | 0, 32) | 0; + $738 = tempRet0; + $739 = $388; + $740 = $739; + $741 = HEAP32[$740 >> 2] | 0; + $742 = ($739 + 4) | 0; + $743 = $742; + $744 = HEAP32[$743 >> 2] | 0; + $745 = _bitshift64Ashr(0, $741 | 0, 32) | 0; + $746 = tempRet0; + $747 = ___muldi3($745 | 0, $746 | 0, $737 | 0, $738 | 0) | 0; + $748 = tempRet0; + $749 = _i64Add($747 | 0, $748 | 0, $729 | 0, $730 | 0) | 0; + $750 = tempRet0; + $751 = $102; + $752 = $751; + $753 = HEAP32[$752 >> 2] | 0; + $754 = ($751 + 4) | 0; + $755 = $754; + $756 = HEAP32[$755 >> 2] | 0; + $757 = _bitshift64Ashr(0, $753 | 0, 32) | 0; + $758 = tempRet0; + $759 = $444; + $760 = $759; + $761 = HEAP32[$760 >> 2] | 0; + $762 = ($759 + 4) | 0; + $763 = $762; + $764 = HEAP32[$763 >> 2] | 0; + $765 = _bitshift64Ashr(0, $761 | 0, 32) | 0; + $766 = tempRet0; + $767 = ___muldi3($765 | 0, $766 | 0, $757 | 0, $758 | 0) | 0; + $768 = tempRet0; + $769 = _i64Add($749 | 0, $750 | 0, $767 | 0, $768 | 0) | 0; + $770 = tempRet0; + $771 = $56; + $772 = $771; + $773 = HEAP32[$772 >> 2] | 0; + $774 = ($771 + 4) | 0; + $775 = $774; + $776 = HEAP32[$775 >> 2] | 0; + $777 = _bitshift64Ashr(0, $773 | 0, 32) | 0; + $778 = tempRet0; + $779 = $594; + $780 = $779; + $781 = HEAP32[$780 >> 2] | 0; + $782 = ($779 + 4) | 0; + $783 = $782; + $784 = HEAP32[$783 >> 2] | 0; + $785 = _bitshift64Ashr(0, $781 | 0, 32) | 0; + $786 = tempRet0; + $787 = ___muldi3($785 | 0, $786 | 0, $777 | 0, $778 | 0) | 0; + $788 = tempRet0; + $789 = _i64Add($769 | 0, $770 | 0, $787 | 0, $788 | 0) | 0; + $790 = tempRet0; + $791 = _bitshift64Shl($789 | 0, $790 | 0, 1) | 0; + $792 = tempRet0; + $793 = ($output + 88) | 0; + $794 = $793; + $795 = $794; + HEAP32[$795 >> 2] = $791; + $796 = ($794 + 4) | 0; + $797 = $796; + HEAP32[$797 >> 2] = $792; + $798 = $282; + $799 = $798; + $800 = HEAP32[$799 >> 2] | 0; + $801 = ($798 + 4) | 0; + $802 = $801; + $803 = HEAP32[$802 >> 2] | 0; + $804 = _bitshift64Ashr(0, $800 | 0, 32) | 0; + $805 = tempRet0; + $806 = ___muldi3($804 | 0, $805 | 0, $804 | 0, $805 | 0) | 0; + $807 = tempRet0; + $808 = $160; + $809 = $808; + $810 = HEAP32[$809 >> 2] | 0; + $811 = ($808 + 4) | 0; + $812 = $811; + $813 = HEAP32[$812 >> 2] | 0; + $814 = _bitshift64Ashr(0, $810 | 0, 32) | 0; + $815 = tempRet0; + $816 = $444; + $817 = $816; + $818 = HEAP32[$817 >> 2] | 0; + $819 = ($816 + 4) | 0; + $820 = $819; + $821 = HEAP32[$820 >> 2] | 0; + $822 = _bitshift64Ashr(0, $818 | 0, 32) | 0; + $823 = tempRet0; + $824 = ___muldi3($822 | 0, $823 | 0, $814 | 0, $815 | 0) | 0; + $825 = tempRet0; + $826 = $224; + $827 = $826; + $828 = HEAP32[$827 >> 2] | 0; + $829 = ($826 + 4) | 0; + $830 = $829; + $831 = HEAP32[$830 >> 2] | 0; + $832 = _bitshift64Ashr(0, $828 | 0, 32) | 0; + $833 = tempRet0; + $834 = $388; + $835 = $834; + $836 = HEAP32[$835 >> 2] | 0; + $837 = ($834 + 4) | 0; + $838 = $837; + $839 = HEAP32[$838 >> 2] | 0; + $840 = _bitshift64Ashr(0, $836 | 0, 32) | 0; + $841 = tempRet0; + $842 = ___muldi3($840 | 0, $841 | 0, $832 | 0, $833 | 0) | 0; + $843 = tempRet0; + $844 = $102; + $845 = $844; + $846 = HEAP32[$845 >> 2] | 0; + $847 = ($844 + 4) | 0; + $848 = $847; + $849 = HEAP32[$848 >> 2] | 0; + $850 = _bitshift64Ashr(0, $846 | 0, 32) | 0; + $851 = tempRet0; + $852 = $594; + $853 = $852; + $854 = HEAP32[$853 >> 2] | 0; + $855 = ($852 + 4) | 0; + $856 = $855; + $857 = HEAP32[$856 >> 2] | 0; + $858 = _bitshift64Ashr(0, $854 | 0, 32) | 0; + $859 = tempRet0; + $860 = ___muldi3($858 | 0, $859 | 0, $850 | 0, $851 | 0) | 0; + $861 = tempRet0; + $862 = _i64Add($860 | 0, $861 | 0, $842 | 0, $843 | 0) | 0; + $863 = tempRet0; + $864 = _bitshift64Shl($862 | 0, $863 | 0, 1) | 0; + $865 = tempRet0; + $866 = _i64Add($864 | 0, $865 | 0, $824 | 0, $825 | 0) | 0; + $867 = tempRet0; + $868 = _bitshift64Shl($866 | 0, $867 | 0, 1) | 0; + $869 = tempRet0; + $870 = _i64Add($868 | 0, $869 | 0, $806 | 0, $807 | 0) | 0; + $871 = tempRet0; + $872 = ($output + 96) | 0; + $873 = $872; + $874 = $873; + HEAP32[$874 >> 2] = $870; + $875 = ($873 + 4) | 0; + $876 = $875; + HEAP32[$876 >> 2] = $871; + $877 = $282; + $878 = $877; + $879 = HEAP32[$878 >> 2] | 0; + $880 = ($877 + 4) | 0; + $881 = $880; + $882 = HEAP32[$881 >> 2] | 0; + $883 = _bitshift64Ashr(0, $879 | 0, 32) | 0; + $884 = tempRet0; + $885 = $388; + $886 = $885; + $887 = HEAP32[$886 >> 2] | 0; + $888 = ($885 + 4) | 0; + $889 = $888; + $890 = HEAP32[$889 >> 2] | 0; + $891 = _bitshift64Ashr(0, $887 | 0, 32) | 0; + $892 = tempRet0; + $893 = ___muldi3($891 | 0, $892 | 0, $883 | 0, $884 | 0) | 0; + $894 = tempRet0; + $895 = $224; + $896 = $895; + $897 = HEAP32[$896 >> 2] | 0; + $898 = ($895 + 4) | 0; + $899 = $898; + $900 = HEAP32[$899 >> 2] | 0; + $901 = _bitshift64Ashr(0, $897 | 0, 32) | 0; + $902 = tempRet0; + $903 = $444; + $904 = $903; + $905 = HEAP32[$904 >> 2] | 0; + $906 = ($903 + 4) | 0; + $907 = $906; + $908 = HEAP32[$907 >> 2] | 0; + $909 = _bitshift64Ashr(0, $905 | 0, 32) | 0; + $910 = tempRet0; + $911 = ___muldi3($909 | 0, $910 | 0, $901 | 0, $902 | 0) | 0; + $912 = tempRet0; + $913 = _i64Add($911 | 0, $912 | 0, $893 | 0, $894 | 0) | 0; + $914 = tempRet0; + $915 = $160; + $916 = $915; + $917 = HEAP32[$916 >> 2] | 0; + $918 = ($915 + 4) | 0; + $919 = $918; + $920 = HEAP32[$919 >> 2] | 0; + $921 = _bitshift64Ashr(0, $917 | 0, 32) | 0; + $922 = tempRet0; + $923 = $594; + $924 = $923; + $925 = HEAP32[$924 >> 2] | 0; + $926 = ($923 + 4) | 0; + $927 = $926; + $928 = HEAP32[$927 >> 2] | 0; + $929 = _bitshift64Ashr(0, $925 | 0, 32) | 0; + $930 = tempRet0; + $931 = ___muldi3($929 | 0, $930 | 0, $921 | 0, $922 | 0) | 0; + $932 = tempRet0; + $933 = _i64Add($913 | 0, $914 | 0, $931 | 0, $932 | 0) | 0; + $934 = tempRet0; + $935 = _bitshift64Shl($933 | 0, $934 | 0, 1) | 0; + $936 = tempRet0; + $937 = ($output + 104) | 0; + $938 = $937; + $939 = $938; + HEAP32[$939 >> 2] = $935; + $940 = ($938 + 4) | 0; + $941 = $940; + HEAP32[$941 >> 2] = $936; + $942 = $388; + $943 = $942; + $944 = HEAP32[$943 >> 2] | 0; + $945 = ($942 + 4) | 0; + $946 = $945; + $947 = HEAP32[$946 >> 2] | 0; + $948 = _bitshift64Ashr(0, $944 | 0, 32) | 0; + $949 = tempRet0; + $950 = ___muldi3($948 | 0, $949 | 0, $948 | 0, $949 | 0) | 0; + $951 = tempRet0; + $952 = $282; + $953 = $952; + $954 = HEAP32[$953 >> 2] | 0; + $955 = ($952 + 4) | 0; + $956 = $955; + $957 = HEAP32[$956 >> 2] | 0; + $958 = _bitshift64Ashr(0, $954 | 0, 32) | 0; + $959 = tempRet0; + $960 = $444; + $961 = $960; + $962 = HEAP32[$961 >> 2] | 0; + $963 = ($960 + 4) | 0; + $964 = $963; + $965 = HEAP32[$964 >> 2] | 0; + $966 = _bitshift64Ashr(0, $962 | 0, 32) | 0; + $967 = tempRet0; + $968 = ___muldi3($966 | 0, $967 | 0, $958 | 0, $959 | 0) | 0; + $969 = tempRet0; + $970 = _i64Add($968 | 0, $969 | 0, $950 | 0, $951 | 0) | 0; + $971 = tempRet0; + $972 = $224; + $973 = $972; + $974 = HEAP32[$973 >> 2] | 0; + $975 = ($972 + 4) | 0; + $976 = $975; + $977 = HEAP32[$976 >> 2] | 0; + $978 = _bitshift64Ashr(0, $974 | 0, 31) | 0; + $979 = tempRet0; + $980 = $594; + $981 = $980; + $982 = HEAP32[$981 >> 2] | 0; + $983 = ($980 + 4) | 0; + $984 = $983; + $985 = HEAP32[$984 >> 2] | 0; + $986 = _bitshift64Ashr(0, $982 | 0, 32) | 0; + $987 = tempRet0; + $988 = ___muldi3($986 | 0, $987 | 0, $978 | 0, $979 | 0) | 0; + $989 = tempRet0; + $990 = _i64Add($970 | 0, $971 | 0, $988 | 0, $989 | 0) | 0; + $991 = tempRet0; + $992 = _bitshift64Shl($990 | 0, $991 | 0, 1) | 0; + $993 = tempRet0; + $994 = ($output + 112) | 0; + $995 = $994; + $996 = $995; + HEAP32[$996 >> 2] = $992; + $997 = ($995 + 4) | 0; + $998 = $997; + HEAP32[$998 >> 2] = $993; + $999 = $388; + $1000 = $999; + $1001 = HEAP32[$1000 >> 2] | 0; + $1002 = ($999 + 4) | 0; + $1003 = $1002; + $1004 = HEAP32[$1003 >> 2] | 0; + $1005 = _bitshift64Ashr(0, $1001 | 0, 32) | 0; + $1006 = tempRet0; + $1007 = $444; + $1008 = $1007; + $1009 = HEAP32[$1008 >> 2] | 0; + $1010 = ($1007 + 4) | 0; + $1011 = $1010; + $1012 = HEAP32[$1011 >> 2] | 0; + $1013 = _bitshift64Ashr(0, $1009 | 0, 32) | 0; + $1014 = tempRet0; + $1015 = ___muldi3($1013 | 0, $1014 | 0, $1005 | 0, $1006 | 0) | 0; + $1016 = tempRet0; + $1017 = $282; + $1018 = $1017; + $1019 = HEAP32[$1018 >> 2] | 0; + $1020 = ($1017 + 4) | 0; + $1021 = $1020; + $1022 = HEAP32[$1021 >> 2] | 0; + $1023 = _bitshift64Ashr(0, $1019 | 0, 32) | 0; + $1024 = tempRet0; + $1025 = $594; + $1026 = $1025; + $1027 = HEAP32[$1026 >> 2] | 0; + $1028 = ($1025 + 4) | 0; + $1029 = $1028; + $1030 = HEAP32[$1029 >> 2] | 0; + $1031 = _bitshift64Ashr(0, $1027 | 0, 32) | 0; + $1032 = tempRet0; + $1033 = ___muldi3($1031 | 0, $1032 | 0, $1023 | 0, $1024 | 0) | 0; + $1034 = tempRet0; + $1035 = _i64Add($1033 | 0, $1034 | 0, $1015 | 0, $1016 | 0) | 0; + $1036 = tempRet0; + $1037 = _bitshift64Shl($1035 | 0, $1036 | 0, 1) | 0; + $1038 = tempRet0; + $1039 = ($output + 120) | 0; + $1040 = $1039; + $1041 = $1040; + HEAP32[$1041 >> 2] = $1037; + $1042 = ($1040 + 4) | 0; + $1043 = $1042; + HEAP32[$1043 >> 2] = $1038; + $1044 = $444; + $1045 = $1044; + $1046 = HEAP32[$1045 >> 2] | 0; + $1047 = ($1044 + 4) | 0; + $1048 = $1047; + $1049 = HEAP32[$1048 >> 2] | 0; + $1050 = _bitshift64Ashr(0, $1046 | 0, 32) | 0; + $1051 = tempRet0; + $1052 = ___muldi3($1050 | 0, $1051 | 0, $1050 | 0, $1051 | 0) | 0; + $1053 = tempRet0; + $1054 = $388; + $1055 = $1054; + $1056 = HEAP32[$1055 >> 2] | 0; + $1057 = ($1054 + 4) | 0; + $1058 = $1057; + $1059 = HEAP32[$1058 >> 2] | 0; + $1060 = _bitshift64Ashr(0, $1056 | 0, 30) | 0; + $1061 = tempRet0; + $1062 = $594; + $1063 = $1062; + $1064 = HEAP32[$1063 >> 2] | 0; + $1065 = ($1062 + 4) | 0; + $1066 = $1065; + $1067 = HEAP32[$1066 >> 2] | 0; + $1068 = _bitshift64Ashr(0, $1064 | 0, 32) | 0; + $1069 = tempRet0; + $1070 = ___muldi3($1068 | 0, $1069 | 0, $1060 | 0, $1061 | 0) | 0; + $1071 = tempRet0; + $1072 = _i64Add($1070 | 0, $1071 | 0, $1052 | 0, $1053 | 0) | 0; + $1073 = tempRet0; + $1074 = ($output + 128) | 0; + $1075 = $1074; + $1076 = $1075; + HEAP32[$1076 >> 2] = $1072; + $1077 = ($1075 + 4) | 0; + $1078 = $1077; + HEAP32[$1078 >> 2] = $1073; + $1079 = $444; + $1080 = $1079; + $1081 = HEAP32[$1080 >> 2] | 0; + $1082 = ($1079 + 4) | 0; + $1083 = $1082; + $1084 = HEAP32[$1083 >> 2] | 0; + $1085 = _bitshift64Ashr(0, $1081 | 0, 31) | 0; + $1086 = tempRet0; + $1087 = $594; + $1088 = $1087; + $1089 = HEAP32[$1088 >> 2] | 0; + $1090 = ($1087 + 4) | 0; + $1091 = $1090; + $1092 = HEAP32[$1091 >> 2] | 0; + $1093 = _bitshift64Ashr(0, $1089 | 0, 32) | 0; + $1094 = tempRet0; + $1095 = ___muldi3($1093 | 0, $1094 | 0, $1085 | 0, $1086 | 0) | 0; + $1096 = tempRet0; + $1097 = ($output + 136) | 0; + $1098 = $1097; + $1099 = $1098; + HEAP32[$1099 >> 2] = $1095; + $1100 = ($1098 + 4) | 0; + $1101 = $1100; + HEAP32[$1101 >> 2] = $1096; + $1102 = $594; + $1103 = $1102; + $1104 = HEAP32[$1103 >> 2] | 0; + $1105 = ($1102 + 4) | 0; + $1106 = $1105; + $1107 = HEAP32[$1106 >> 2] | 0; + $1108 = _bitshift64Ashr(0, $1104 | 0, 32) | 0; + $1109 = tempRet0; + $1110 = _bitshift64Ashr(0, $1104 | 0, 31) | 0; + $1111 = tempRet0; + $1112 = ___muldi3($1110 | 0, $1111 | 0, $1108 | 0, $1109 | 0) | 0; + $1113 = tempRet0; + $1114 = ($output + 144) | 0; + $1115 = $1114; + $1116 = $1115; + HEAP32[$1116 >> 2] = $1112; + $1117 = ($1115 + 4) | 0; + $1118 = $1117; + HEAP32[$1118 >> 2] = $1113; + STACKTOP = sp; + return; + } + function _div_by_2_26($0, $1) { + $0 = $0 | 0; + $1 = $1 | 0; + var $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $2 = $1 >> 31; + $3 = $2 >>> 6; + $4 = _i64Add($3 | 0, 0, $0 | 0, $1 | 0) | 0; + $5 = tempRet0; + $6 = _bitshift64Ashr($4 | 0, $5 | 0, 26) | 0; + $7 = tempRet0; + tempRet0 = $7; + STACKTOP = sp; + return $6 | 0; + } + function _div_by_2_25($0, $1) { + $0 = $0 | 0; + $1 = $1 | 0; + var $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $2 = $1 >> 31; + $3 = $2 >>> 7; + $4 = _i64Add($3 | 0, 0, $0 | 0, $1 | 0) | 0; + $5 = tempRet0; + $6 = _bitshift64Ashr($4 | 0, $5 | 0, 25) | 0; + $7 = tempRet0; + tempRet0 = $7; + STACKTOP = sp; + return $6 | 0; + } + function _crypto_sign_ed25519_ref10_fe_0($h) { + $h = $h | 0; + var dest = 0, + label = 0, + sp = 0, + stop = 0; + sp = STACKTOP; + dest = ($h + 0) | 0; + stop = (dest + 40) | 0; + do { + HEAP32[dest >> 2] = 0 | 0; + dest = (dest + 4) | 0; + } while ((dest | 0) < (stop | 0)); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_fe_1($h) { + $h = $h | 0; + var $0 = 0, + dest = 0, + label = 0, + sp = 0, + stop = 0; + sp = STACKTOP; + HEAP32[$h >> 2] = 1; + $0 = ($h + 4) | 0; + dest = ($0 + 0) | 0; + stop = (dest + 36) | 0; + do { + HEAP32[dest >> 2] = 0 | 0; + dest = (dest + 4) | 0; + } while ((dest | 0) < (stop | 0)); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_fe_add($h, $f, $g) { + $h = $h | 0; + $f = $f | 0; + $g = $g | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0; + var $27 = 0, + $28 = 0, + $29 = 0, + $3 = 0, + $30 = 0, + $31 = 0, + $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $37 = 0, + $38 = 0, + $39 = 0, + $4 = 0, + $40 = 0, + $41 = 0, + $42 = 0, + $43 = 0, + $44 = 0; + var $45 = 0, + $46 = 0, + $47 = 0, + $48 = 0, + $49 = 0, + $5 = 0, + $50 = 0, + $51 = 0, + $52 = 0, + $53 = 0, + $54 = 0, + $55 = 0, + $56 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP32[$f >> 2] | 0; + $1 = ($f + 4) | 0; + $2 = HEAP32[$1 >> 2] | 0; + $3 = ($f + 8) | 0; + $4 = HEAP32[$3 >> 2] | 0; + $5 = ($f + 12) | 0; + $6 = HEAP32[$5 >> 2] | 0; + $7 = ($f + 16) | 0; + $8 = HEAP32[$7 >> 2] | 0; + $9 = ($f + 20) | 0; + $10 = HEAP32[$9 >> 2] | 0; + $11 = ($f + 24) | 0; + $12 = HEAP32[$11 >> 2] | 0; + $13 = ($f + 28) | 0; + $14 = HEAP32[$13 >> 2] | 0; + $15 = ($f + 32) | 0; + $16 = HEAP32[$15 >> 2] | 0; + $17 = ($f + 36) | 0; + $18 = HEAP32[$17 >> 2] | 0; + $19 = HEAP32[$g >> 2] | 0; + $20 = ($g + 4) | 0; + $21 = HEAP32[$20 >> 2] | 0; + $22 = ($g + 8) | 0; + $23 = HEAP32[$22 >> 2] | 0; + $24 = ($g + 12) | 0; + $25 = HEAP32[$24 >> 2] | 0; + $26 = ($g + 16) | 0; + $27 = HEAP32[$26 >> 2] | 0; + $28 = ($g + 20) | 0; + $29 = HEAP32[$28 >> 2] | 0; + $30 = ($g + 24) | 0; + $31 = HEAP32[$30 >> 2] | 0; + $32 = ($g + 28) | 0; + $33 = HEAP32[$32 >> 2] | 0; + $34 = ($g + 32) | 0; + $35 = HEAP32[$34 >> 2] | 0; + $36 = ($g + 36) | 0; + $37 = HEAP32[$36 >> 2] | 0; + $38 = ($19 + $0) | 0; + $39 = ($21 + $2) | 0; + $40 = ($23 + $4) | 0; + $41 = ($25 + $6) | 0; + $42 = ($27 + $8) | 0; + $43 = ($29 + $10) | 0; + $44 = ($31 + $12) | 0; + $45 = ($33 + $14) | 0; + $46 = ($35 + $16) | 0; + $47 = ($37 + $18) | 0; + HEAP32[$h >> 2] = $38; + $48 = ($h + 4) | 0; + HEAP32[$48 >> 2] = $39; + $49 = ($h + 8) | 0; + HEAP32[$49 >> 2] = $40; + $50 = ($h + 12) | 0; + HEAP32[$50 >> 2] = $41; + $51 = ($h + 16) | 0; + HEAP32[$51 >> 2] = $42; + $52 = ($h + 20) | 0; + HEAP32[$52 >> 2] = $43; + $53 = ($h + 24) | 0; + HEAP32[$53 >> 2] = $44; + $54 = ($h + 28) | 0; + HEAP32[$54 >> 2] = $45; + $55 = ($h + 32) | 0; + HEAP32[$55 >> 2] = $46; + $56 = ($h + 36) | 0; + HEAP32[$56 >> 2] = $47; + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_fe_cmov($f, $g, $b) { + $f = $f | 0; + $g = $g | 0; + $b = $b | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0; + var $27 = 0, + $28 = 0, + $29 = 0, + $3 = 0, + $30 = 0, + $31 = 0, + $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $37 = 0, + $38 = 0, + $39 = 0, + $4 = 0, + $40 = 0, + $41 = 0, + $42 = 0, + $43 = 0, + $44 = 0; + var $45 = 0, + $46 = 0, + $47 = 0, + $48 = 0, + $49 = 0, + $5 = 0, + $50 = 0, + $51 = 0, + $52 = 0, + $53 = 0, + $54 = 0, + $55 = 0, + $56 = 0, + $57 = 0, + $58 = 0, + $59 = 0, + $6 = 0, + $60 = 0, + $61 = 0, + $62 = 0; + var $63 = 0, + $64 = 0, + $65 = 0, + $66 = 0, + $67 = 0, + $68 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP32[$f >> 2] | 0; + $1 = ($f + 4) | 0; + $2 = HEAP32[$1 >> 2] | 0; + $3 = ($f + 8) | 0; + $4 = HEAP32[$3 >> 2] | 0; + $5 = ($f + 12) | 0; + $6 = HEAP32[$5 >> 2] | 0; + $7 = ($f + 16) | 0; + $8 = HEAP32[$7 >> 2] | 0; + $9 = ($f + 20) | 0; + $10 = HEAP32[$9 >> 2] | 0; + $11 = ($f + 24) | 0; + $12 = HEAP32[$11 >> 2] | 0; + $13 = ($f + 28) | 0; + $14 = HEAP32[$13 >> 2] | 0; + $15 = ($f + 32) | 0; + $16 = HEAP32[$15 >> 2] | 0; + $17 = ($f + 36) | 0; + $18 = HEAP32[$17 >> 2] | 0; + $19 = HEAP32[$g >> 2] | 0; + $20 = ($g + 4) | 0; + $21 = HEAP32[$20 >> 2] | 0; + $22 = ($g + 8) | 0; + $23 = HEAP32[$22 >> 2] | 0; + $24 = ($g + 12) | 0; + $25 = HEAP32[$24 >> 2] | 0; + $26 = ($g + 16) | 0; + $27 = HEAP32[$26 >> 2] | 0; + $28 = ($g + 20) | 0; + $29 = HEAP32[$28 >> 2] | 0; + $30 = ($g + 24) | 0; + $31 = HEAP32[$30 >> 2] | 0; + $32 = ($g + 28) | 0; + $33 = HEAP32[$32 >> 2] | 0; + $34 = ($g + 32) | 0; + $35 = HEAP32[$34 >> 2] | 0; + $36 = ($g + 36) | 0; + $37 = HEAP32[$36 >> 2] | 0; + $38 = $19 ^ $0; + $39 = $21 ^ $2; + $40 = $23 ^ $4; + $41 = $25 ^ $6; + $42 = $27 ^ $8; + $43 = $29 ^ $10; + $44 = $31 ^ $12; + $45 = $33 ^ $14; + $46 = $35 ^ $16; + $47 = $37 ^ $18; + $48 = (0 - $b) | 0; + $49 = $38 & $48; + $50 = $39 & $48; + $51 = $40 & $48; + $52 = $41 & $48; + $53 = $42 & $48; + $54 = $43 & $48; + $55 = $44 & $48; + $56 = $45 & $48; + $57 = $46 & $48; + $58 = $47 & $48; + $59 = $49 ^ $0; + HEAP32[$f >> 2] = $59; + $60 = $50 ^ $2; + HEAP32[$1 >> 2] = $60; + $61 = $51 ^ $4; + HEAP32[$3 >> 2] = $61; + $62 = $52 ^ $6; + HEAP32[$5 >> 2] = $62; + $63 = $53 ^ $8; + HEAP32[$7 >> 2] = $63; + $64 = $54 ^ $10; + HEAP32[$9 >> 2] = $64; + $65 = $55 ^ $12; + HEAP32[$11 >> 2] = $65; + $66 = $56 ^ $14; + HEAP32[$13 >> 2] = $66; + $67 = $57 ^ $16; + HEAP32[$15 >> 2] = $67; + $68 = $58 ^ $18; + HEAP32[$17 >> 2] = $68; + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_fe_copy($h, $f) { + $h = $h | 0; + $f = $f | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0; + var $27 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP32[$f >> 2] | 0; + $1 = ($f + 4) | 0; + $2 = HEAP32[$1 >> 2] | 0; + $3 = ($f + 8) | 0; + $4 = HEAP32[$3 >> 2] | 0; + $5 = ($f + 12) | 0; + $6 = HEAP32[$5 >> 2] | 0; + $7 = ($f + 16) | 0; + $8 = HEAP32[$7 >> 2] | 0; + $9 = ($f + 20) | 0; + $10 = HEAP32[$9 >> 2] | 0; + $11 = ($f + 24) | 0; + $12 = HEAP32[$11 >> 2] | 0; + $13 = ($f + 28) | 0; + $14 = HEAP32[$13 >> 2] | 0; + $15 = ($f + 32) | 0; + $16 = HEAP32[$15 >> 2] | 0; + $17 = ($f + 36) | 0; + $18 = HEAP32[$17 >> 2] | 0; + HEAP32[$h >> 2] = $0; + $19 = ($h + 4) | 0; + HEAP32[$19 >> 2] = $2; + $20 = ($h + 8) | 0; + HEAP32[$20 >> 2] = $4; + $21 = ($h + 12) | 0; + HEAP32[$21 >> 2] = $6; + $22 = ($h + 16) | 0; + HEAP32[$22 >> 2] = $8; + $23 = ($h + 20) | 0; + HEAP32[$23 >> 2] = $10; + $24 = ($h + 24) | 0; + HEAP32[$24 >> 2] = $12; + $25 = ($h + 28) | 0; + HEAP32[$25 >> 2] = $14; + $26 = ($h + 32) | 0; + HEAP32[$26 >> 2] = $16; + $27 = ($h + 36) | 0; + HEAP32[$27 >> 2] = $18; + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_fe_frombytes($h, $s) { + $h = $h | 0; + $s = $s | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $101 = 0, + $102 = 0, + $103 = 0, + $104 = 0, + $105 = 0, + $106 = 0, + $107 = 0, + $108 = 0, + $109 = 0, + $11 = 0, + $110 = 0, + $111 = 0, + $112 = 0, + $113 = 0, + $114 = 0, + $115 = 0; + var $116 = 0, + $117 = 0, + $118 = 0, + $119 = 0, + $12 = 0, + $120 = 0, + $121 = 0, + $122 = 0, + $123 = 0, + $124 = 0, + $125 = 0, + $126 = 0, + $127 = 0, + $128 = 0, + $129 = 0, + $13 = 0, + $130 = 0, + $131 = 0, + $132 = 0, + $133 = 0; + var $134 = 0, + $135 = 0, + $136 = 0, + $137 = 0, + $138 = 0, + $139 = 0, + $14 = 0, + $140 = 0, + $141 = 0, + $142 = 0, + $143 = 0, + $144 = 0, + $145 = 0, + $146 = 0, + $147 = 0, + $148 = 0, + $149 = 0, + $15 = 0, + $150 = 0, + $151 = 0; + var $152 = 0, + $153 = 0, + $154 = 0, + $155 = 0, + $156 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0, + $27 = 0, + $28 = 0, + $29 = 0; + var $3 = 0, + $30 = 0, + $31 = 0, + $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $37 = 0, + $38 = 0, + $39 = 0, + $4 = 0, + $40 = 0, + $41 = 0, + $42 = 0, + $43 = 0, + $44 = 0, + $45 = 0, + $46 = 0, + $47 = 0; + var $48 = 0, + $49 = 0, + $5 = 0, + $50 = 0, + $51 = 0, + $52 = 0, + $53 = 0, + $54 = 0, + $55 = 0, + $56 = 0, + $57 = 0, + $58 = 0, + $59 = 0, + $6 = 0, + $60 = 0, + $61 = 0, + $62 = 0, + $63 = 0, + $64 = 0, + $65 = 0; + var $66 = 0, + $67 = 0, + $68 = 0, + $69 = 0, + $7 = 0, + $70 = 0, + $71 = 0, + $72 = 0, + $73 = 0, + $74 = 0, + $75 = 0, + $76 = 0, + $77 = 0, + $78 = 0, + $79 = 0, + $8 = 0, + $80 = 0, + $81 = 0, + $82 = 0, + $83 = 0; + var $84 = 0, + $85 = 0, + $86 = 0, + $87 = 0, + $88 = 0, + $89 = 0, + $9 = 0, + $90 = 0, + $91 = 0, + $92 = 0, + $93 = 0, + $94 = 0, + $95 = 0, + $96 = 0, + $97 = 0, + $98 = 0, + $99 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = _load_4($s) | 0; + $1 = tempRet0; + $2 = ($s + 4) | 0; + $3 = _load_3($2) | 0; + $4 = tempRet0; + $5 = _bitshift64Shl($3 | 0, $4 | 0, 6) | 0; + $6 = tempRet0; + $7 = ($s + 7) | 0; + $8 = _load_3($7) | 0; + $9 = tempRet0; + $10 = _bitshift64Shl($8 | 0, $9 | 0, 5) | 0; + $11 = tempRet0; + $12 = ($s + 10) | 0; + $13 = _load_3($12) | 0; + $14 = tempRet0; + $15 = _bitshift64Shl($13 | 0, $14 | 0, 3) | 0; + $16 = tempRet0; + $17 = ($s + 13) | 0; + $18 = _load_3($17) | 0; + $19 = tempRet0; + $20 = _bitshift64Shl($18 | 0, $19 | 0, 2) | 0; + $21 = tempRet0; + $22 = ($s + 16) | 0; + $23 = _load_4($22) | 0; + $24 = tempRet0; + $25 = ($s + 20) | 0; + $26 = _load_3($25) | 0; + $27 = tempRet0; + $28 = _bitshift64Shl($26 | 0, $27 | 0, 7) | 0; + $29 = tempRet0; + $30 = ($s + 23) | 0; + $31 = _load_3($30) | 0; + $32 = tempRet0; + $33 = _bitshift64Shl($31 | 0, $32 | 0, 5) | 0; + $34 = tempRet0; + $35 = ($s + 26) | 0; + $36 = _load_3($35) | 0; + $37 = tempRet0; + $38 = _bitshift64Shl($36 | 0, $37 | 0, 4) | 0; + $39 = tempRet0; + $40 = ($s + 29) | 0; + $41 = _load_3($40) | 0; + $42 = tempRet0; + $43 = _bitshift64Shl($41 | 0, $42 | 0, 2) | 0; + $44 = tempRet0; + $45 = $43 & 33554428; + $46 = _i64Add($45 | 0, 0, 16777216, 0) | 0; + $47 = tempRet0; + $48 = _bitshift64Lshr($46 | 0, $47 | 0, 25) | 0; + $49 = tempRet0; + $50 = ___muldi3($48 | 0, $49 | 0, 19, 0) | 0; + $51 = tempRet0; + $52 = _i64Add($50 | 0, $51 | 0, $0 | 0, $1 | 0) | 0; + $53 = tempRet0; + $54 = _bitshift64Shl($48 | 0, $49 | 0, 25) | 0; + $55 = tempRet0; + $56 = _i64Add($5 | 0, $6 | 0, 16777216, 0) | 0; + $57 = tempRet0; + $58 = _bitshift64Ashr($56 | 0, $57 | 0, 25) | 0; + $59 = tempRet0; + $60 = _i64Add($58 | 0, $59 | 0, $10 | 0, $11 | 0) | 0; + $61 = tempRet0; + $62 = _bitshift64Shl($58 | 0, $59 | 0, 25) | 0; + $63 = tempRet0; + $64 = _i64Subtract($5 | 0, $6 | 0, $62 | 0, $63 | 0) | 0; + $65 = tempRet0; + $66 = _i64Add($15 | 0, $16 | 0, 16777216, 0) | 0; + $67 = tempRet0; + $68 = _bitshift64Ashr($66 | 0, $67 | 0, 25) | 0; + $69 = tempRet0; + $70 = _i64Add($68 | 0, $69 | 0, $20 | 0, $21 | 0) | 0; + $71 = tempRet0; + $72 = _bitshift64Shl($68 | 0, $69 | 0, 25) | 0; + $73 = tempRet0; + $74 = _i64Subtract($15 | 0, $16 | 0, $72 | 0, $73 | 0) | 0; + $75 = tempRet0; + $76 = _i64Add($23 | 0, $24 | 0, 16777216, 0) | 0; + $77 = tempRet0; + $78 = _bitshift64Ashr($76 | 0, $77 | 0, 25) | 0; + $79 = tempRet0; + $80 = _i64Add($28 | 0, $29 | 0, $78 | 0, $79 | 0) | 0; + $81 = tempRet0; + $82 = _bitshift64Shl($78 | 0, $79 | 0, 25) | 0; + $83 = tempRet0; + $84 = _i64Subtract($23 | 0, $24 | 0, $82 | 0, $83 | 0) | 0; + $85 = tempRet0; + $86 = _i64Add($33 | 0, $34 | 0, 16777216, 0) | 0; + $87 = tempRet0; + $88 = _bitshift64Ashr($86 | 0, $87 | 0, 25) | 0; + $89 = tempRet0; + $90 = _i64Add($88 | 0, $89 | 0, $38 | 0, $39 | 0) | 0; + $91 = tempRet0; + $92 = _bitshift64Shl($88 | 0, $89 | 0, 25) | 0; + $93 = tempRet0; + $94 = _i64Add($52 | 0, $53 | 0, 33554432, 0) | 0; + $95 = tempRet0; + $96 = _bitshift64Ashr($94 | 0, $95 | 0, 26) | 0; + $97 = tempRet0; + $98 = _i64Add($64 | 0, $65 | 0, $96 | 0, $97 | 0) | 0; + $99 = tempRet0; + $100 = _bitshift64Shl($96 | 0, $97 | 0, 26) | 0; + $101 = tempRet0; + $102 = _i64Subtract($52 | 0, $53 | 0, $100 | 0, $101 | 0) | 0; + $103 = tempRet0; + $104 = _i64Add($60 | 0, $61 | 0, 33554432, 0) | 0; + $105 = tempRet0; + $106 = _bitshift64Ashr($104 | 0, $105 | 0, 26) | 0; + $107 = tempRet0; + $108 = _i64Add($74 | 0, $75 | 0, $106 | 0, $107 | 0) | 0; + $109 = tempRet0; + $110 = _bitshift64Shl($106 | 0, $107 | 0, 26) | 0; + $111 = tempRet0; + $112 = _i64Subtract($60 | 0, $61 | 0, $110 | 0, $111 | 0) | 0; + $113 = tempRet0; + $114 = _i64Add($70 | 0, $71 | 0, 33554432, 0) | 0; + $115 = tempRet0; + $116 = _bitshift64Ashr($114 | 0, $115 | 0, 26) | 0; + $117 = tempRet0; + $118 = _i64Add($84 | 0, $85 | 0, $116 | 0, $117 | 0) | 0; + $119 = tempRet0; + $120 = _bitshift64Shl($116 | 0, $117 | 0, 26) | 0; + $121 = tempRet0; + $122 = _i64Subtract($70 | 0, $71 | 0, $120 | 0, $121 | 0) | 0; + $123 = tempRet0; + $124 = _i64Add($80 | 0, $81 | 0, 33554432, 0) | 0; + $125 = tempRet0; + $126 = _bitshift64Ashr($124 | 0, $125 | 0, 26) | 0; + $127 = tempRet0; + $128 = _i64Add($126 | 0, $127 | 0, $33 | 0, $34 | 0) | 0; + $129 = tempRet0; + $130 = _i64Subtract($128 | 0, $129 | 0, $92 | 0, $93 | 0) | 0; + $131 = tempRet0; + $132 = _bitshift64Shl($126 | 0, $127 | 0, 26) | 0; + $133 = tempRet0; + $134 = _i64Subtract($80 | 0, $81 | 0, $132 | 0, $133 | 0) | 0; + $135 = tempRet0; + $136 = _i64Add($90 | 0, $91 | 0, 33554432, 0) | 0; + $137 = tempRet0; + $138 = _bitshift64Ashr($136 | 0, $137 | 0, 26) | 0; + $139 = tempRet0; + $140 = _i64Add($138 | 0, $139 | 0, $45 | 0, 0) | 0; + $141 = tempRet0; + $142 = _i64Subtract($140 | 0, $141 | 0, $54 | 0, $55 | 0) | 0; + $143 = tempRet0; + $144 = _bitshift64Shl($138 | 0, $139 | 0, 26) | 0; + $145 = tempRet0; + $146 = _i64Subtract($90 | 0, $91 | 0, $144 | 0, $145 | 0) | 0; + $147 = tempRet0; + HEAP32[$h >> 2] = $102; + $148 = ($h + 4) | 0; + HEAP32[$148 >> 2] = $98; + $149 = ($h + 8) | 0; + HEAP32[$149 >> 2] = $112; + $150 = ($h + 12) | 0; + HEAP32[$150 >> 2] = $108; + $151 = ($h + 16) | 0; + HEAP32[$151 >> 2] = $122; + $152 = ($h + 20) | 0; + HEAP32[$152 >> 2] = $118; + $153 = ($h + 24) | 0; + HEAP32[$153 >> 2] = $134; + $154 = ($h + 28) | 0; + HEAP32[$154 >> 2] = $130; + $155 = ($h + 32) | 0; + HEAP32[$155 >> 2] = $146; + $156 = ($h + 36) | 0; + HEAP32[$156 >> 2] = $142; + STACKTOP = sp; + return; + } + function _load_4($in) { + $in = $in | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0; + var $8 = 0, + $9 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP8[$in >> 0] | 0; + $1 = $0 & 255; + $2 = ($in + 1) | 0; + $3 = HEAP8[$2 >> 0] | 0; + $4 = $3 & 255; + $5 = _bitshift64Shl($4 | 0, 0, 8) | 0; + $6 = tempRet0; + $7 = $5 | $1; + $8 = ($in + 2) | 0; + $9 = HEAP8[$8 >> 0] | 0; + $10 = $9 & 255; + $11 = _bitshift64Shl($10 | 0, 0, 16) | 0; + $12 = tempRet0; + $13 = $7 | $11; + $14 = $6 | $12; + $15 = ($in + 3) | 0; + $16 = HEAP8[$15 >> 0] | 0; + $17 = $16 & 255; + $18 = _bitshift64Shl($17 | 0, 0, 24) | 0; + $19 = tempRet0; + $20 = $13 | $18; + $21 = $14 | $19; + tempRet0 = $21; + STACKTOP = sp; + return $20 | 0; + } + function _load_3($in) { + $in = $in | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP8[$in >> 0] | 0; + $1 = $0 & 255; + $2 = ($in + 1) | 0; + $3 = HEAP8[$2 >> 0] | 0; + $4 = $3 & 255; + $5 = _bitshift64Shl($4 | 0, 0, 8) | 0; + $6 = tempRet0; + $7 = $5 | $1; + $8 = ($in + 2) | 0; + $9 = HEAP8[$8 >> 0] | 0; + $10 = $9 & 255; + $11 = _bitshift64Shl($10 | 0, 0, 16) | 0; + $12 = tempRet0; + $13 = $7 | $11; + $14 = $6 | $12; + tempRet0 = $14; + STACKTOP = sp; + return $13 | 0; + } + function _crypto_sign_ed25519_ref10_fe_invert($out, $z) { + $out = $out | 0; + $z = $z | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $exitcond = 0, + $exitcond10 = 0, + $exitcond11 = 0, + $i$74 = 0, + $i$83 = 0, + $i$92 = 0, + $t0 = 0, + $t1 = 0, + $t2 = 0, + $t3 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 160) | 0; + $t0 = (sp + 120) | 0; + $t1 = (sp + 80) | 0; + $t2 = (sp + 40) | 0; + $t3 = sp; + _crypto_sign_ed25519_ref10_fe_sq($t0, $z); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t0); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_mul($t1, $z, $t1); + _crypto_sign_ed25519_ref10_fe_mul($t0, $t0, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t0); + _crypto_sign_ed25519_ref10_fe_mul($t1, $t1, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_mul($t1, $t2, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_mul($t2, $t2, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + _crypto_sign_ed25519_ref10_fe_mul($t2, $t3, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_mul($t1, $t2, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t1); + $i$74 = 1; + while (1) { + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + $0 = ($i$74 + 1) | 0; + $exitcond11 = ($0 | 0) == 50; + if ($exitcond11) { + break; + } else { + $i$74 = $0; + } + } + _crypto_sign_ed25519_ref10_fe_mul($t2, $t2, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t3, $t2); + $i$83 = 1; + while (1) { + _crypto_sign_ed25519_ref10_fe_sq($t3, $t3); + $1 = ($i$83 + 1) | 0; + $exitcond10 = ($1 | 0) == 100; + if ($exitcond10) { + break; + } else { + $i$83 = $1; + } + } + _crypto_sign_ed25519_ref10_fe_mul($t2, $t3, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + $i$92 = 1; + while (1) { + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + $2 = ($i$92 + 1) | 0; + $exitcond = ($2 | 0) == 50; + if ($exitcond) { + break; + } else { + $i$92 = $2; + } + } + _crypto_sign_ed25519_ref10_fe_mul($t1, $t2, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_mul($out, $t1, $t0); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_fe_isnegative($f) { + $f = $f | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $s = 0, + label = 0, + sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 32) | 0; + $s = sp; + _crypto_sign_ed25519_ref10_fe_tobytes($s, $f); + $0 = HEAP8[$s >> 0] | 0; + $1 = $0 & 255; + $2 = $1 & 1; + STACKTOP = sp; + return $2 | 0; + } + function _crypto_sign_ed25519_ref10_fe_isnonzero($f) { + $f = $f | 0; + var $0 = 0, + $s = 0, + label = 0, + sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 32) | 0; + $s = sp; + _crypto_sign_ed25519_ref10_fe_tobytes($s, $f); + $0 = _crypto_verify_32_ref($s, 8) | 0; + STACKTOP = sp; + return $0 | 0; + } + function _crypto_sign_ed25519_ref10_fe_mul($h, $f, $g) { + $h = $h | 0; + $f = $f | 0; + $g = $g | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $101 = 0, + $102 = 0, + $103 = 0, + $104 = 0, + $105 = 0, + $106 = 0, + $107 = 0, + $108 = 0, + $109 = 0, + $11 = 0, + $110 = 0, + $111 = 0, + $112 = 0, + $113 = 0, + $114 = 0, + $115 = 0; + var $116 = 0, + $117 = 0, + $118 = 0, + $119 = 0, + $12 = 0, + $120 = 0, + $121 = 0, + $122 = 0, + $123 = 0, + $124 = 0, + $125 = 0, + $126 = 0, + $127 = 0, + $128 = 0, + $129 = 0, + $13 = 0, + $130 = 0, + $131 = 0, + $132 = 0, + $133 = 0; + var $134 = 0, + $135 = 0, + $136 = 0, + $137 = 0, + $138 = 0, + $139 = 0, + $14 = 0, + $140 = 0, + $141 = 0, + $142 = 0, + $143 = 0, + $144 = 0, + $145 = 0, + $146 = 0, + $147 = 0, + $148 = 0, + $149 = 0, + $15 = 0, + $150 = 0, + $151 = 0; + var $152 = 0, + $153 = 0, + $154 = 0, + $155 = 0, + $156 = 0, + $157 = 0, + $158 = 0, + $159 = 0, + $16 = 0, + $160 = 0, + $161 = 0, + $162 = 0, + $163 = 0, + $164 = 0, + $165 = 0, + $166 = 0, + $167 = 0, + $168 = 0, + $169 = 0, + $17 = 0; + var $170 = 0, + $171 = 0, + $172 = 0, + $173 = 0, + $174 = 0, + $175 = 0, + $176 = 0, + $177 = 0, + $178 = 0, + $179 = 0, + $18 = 0, + $180 = 0, + $181 = 0, + $182 = 0, + $183 = 0, + $184 = 0, + $185 = 0, + $186 = 0, + $187 = 0, + $188 = 0; + var $189 = 0, + $19 = 0, + $190 = 0, + $191 = 0, + $192 = 0, + $193 = 0, + $194 = 0, + $195 = 0, + $196 = 0, + $197 = 0, + $198 = 0, + $199 = 0, + $2 = 0, + $20 = 0, + $200 = 0, + $201 = 0, + $202 = 0, + $203 = 0, + $204 = 0, + $205 = 0; + var $206 = 0, + $207 = 0, + $208 = 0, + $209 = 0, + $21 = 0, + $210 = 0, + $211 = 0, + $212 = 0, + $213 = 0, + $214 = 0, + $215 = 0, + $216 = 0, + $217 = 0, + $218 = 0, + $219 = 0, + $22 = 0, + $220 = 0, + $221 = 0, + $222 = 0, + $223 = 0; + var $224 = 0, + $225 = 0, + $226 = 0, + $227 = 0, + $228 = 0, + $229 = 0, + $23 = 0, + $230 = 0, + $231 = 0, + $232 = 0, + $233 = 0, + $234 = 0, + $235 = 0, + $236 = 0, + $237 = 0, + $238 = 0, + $239 = 0, + $24 = 0, + $240 = 0, + $241 = 0; + var $242 = 0, + $243 = 0, + $244 = 0, + $245 = 0, + $246 = 0, + $247 = 0, + $248 = 0, + $249 = 0, + $25 = 0, + $250 = 0, + $251 = 0, + $252 = 0, + $253 = 0, + $254 = 0, + $255 = 0, + $256 = 0, + $257 = 0, + $258 = 0, + $259 = 0, + $26 = 0; + var $260 = 0, + $261 = 0, + $262 = 0, + $263 = 0, + $264 = 0, + $265 = 0, + $266 = 0, + $267 = 0, + $268 = 0, + $269 = 0, + $27 = 0, + $270 = 0, + $271 = 0, + $272 = 0, + $273 = 0, + $274 = 0, + $275 = 0, + $276 = 0, + $277 = 0, + $278 = 0; + var $279 = 0, + $28 = 0, + $280 = 0, + $281 = 0, + $282 = 0, + $283 = 0, + $284 = 0, + $285 = 0, + $286 = 0, + $287 = 0, + $288 = 0, + $289 = 0, + $29 = 0, + $290 = 0, + $291 = 0, + $292 = 0, + $293 = 0, + $294 = 0, + $295 = 0, + $296 = 0; + var $297 = 0, + $298 = 0, + $299 = 0, + $3 = 0, + $30 = 0, + $300 = 0, + $301 = 0, + $302 = 0, + $303 = 0, + $304 = 0, + $305 = 0, + $306 = 0, + $307 = 0, + $308 = 0, + $309 = 0, + $31 = 0, + $310 = 0, + $311 = 0, + $312 = 0, + $313 = 0; + var $314 = 0, + $315 = 0, + $316 = 0, + $317 = 0, + $318 = 0, + $319 = 0, + $32 = 0, + $320 = 0, + $321 = 0, + $322 = 0, + $323 = 0, + $324 = 0, + $325 = 0, + $326 = 0, + $327 = 0, + $328 = 0, + $329 = 0, + $33 = 0, + $330 = 0, + $331 = 0; + var $332 = 0, + $333 = 0, + $334 = 0, + $335 = 0, + $336 = 0, + $337 = 0, + $338 = 0, + $339 = 0, + $34 = 0, + $340 = 0, + $341 = 0, + $342 = 0, + $343 = 0, + $344 = 0, + $345 = 0, + $346 = 0, + $347 = 0, + $348 = 0, + $349 = 0, + $35 = 0; + var $350 = 0, + $351 = 0, + $352 = 0, + $353 = 0, + $354 = 0, + $355 = 0, + $356 = 0, + $357 = 0, + $358 = 0, + $359 = 0, + $36 = 0, + $360 = 0, + $361 = 0, + $362 = 0, + $363 = 0, + $364 = 0, + $365 = 0, + $366 = 0, + $367 = 0, + $368 = 0; + var $369 = 0, + $37 = 0, + $370 = 0, + $371 = 0, + $372 = 0, + $373 = 0, + $374 = 0, + $375 = 0, + $376 = 0, + $377 = 0, + $378 = 0, + $379 = 0, + $38 = 0, + $380 = 0, + $381 = 0, + $382 = 0, + $383 = 0, + $384 = 0, + $385 = 0, + $386 = 0; + var $387 = 0, + $388 = 0, + $389 = 0, + $39 = 0, + $390 = 0, + $391 = 0, + $392 = 0, + $393 = 0, + $394 = 0, + $395 = 0, + $396 = 0, + $397 = 0, + $398 = 0, + $399 = 0, + $4 = 0, + $40 = 0, + $400 = 0, + $401 = 0, + $402 = 0, + $403 = 0; + var $404 = 0, + $405 = 0, + $406 = 0, + $407 = 0, + $408 = 0, + $409 = 0, + $41 = 0, + $410 = 0, + $411 = 0, + $412 = 0, + $413 = 0, + $414 = 0, + $415 = 0, + $416 = 0, + $417 = 0, + $418 = 0, + $419 = 0, + $42 = 0, + $420 = 0, + $421 = 0; + var $422 = 0, + $423 = 0, + $424 = 0, + $425 = 0, + $426 = 0, + $427 = 0, + $428 = 0, + $429 = 0, + $43 = 0, + $430 = 0, + $431 = 0, + $432 = 0, + $433 = 0, + $434 = 0, + $435 = 0, + $436 = 0, + $437 = 0, + $438 = 0, + $439 = 0, + $44 = 0; + var $440 = 0, + $441 = 0, + $442 = 0, + $443 = 0, + $444 = 0, + $445 = 0, + $446 = 0, + $447 = 0, + $448 = 0, + $449 = 0, + $45 = 0, + $450 = 0, + $451 = 0, + $452 = 0, + $453 = 0, + $454 = 0, + $455 = 0, + $456 = 0, + $457 = 0, + $458 = 0; + var $459 = 0, + $46 = 0, + $460 = 0, + $461 = 0, + $462 = 0, + $463 = 0, + $464 = 0, + $465 = 0, + $466 = 0, + $467 = 0, + $468 = 0, + $469 = 0, + $47 = 0, + $470 = 0, + $471 = 0, + $472 = 0, + $473 = 0, + $474 = 0, + $475 = 0, + $476 = 0; + var $477 = 0, + $478 = 0, + $479 = 0, + $48 = 0, + $480 = 0, + $481 = 0, + $482 = 0, + $483 = 0, + $484 = 0, + $485 = 0, + $486 = 0, + $487 = 0, + $488 = 0, + $489 = 0, + $49 = 0, + $490 = 0, + $491 = 0, + $492 = 0, + $493 = 0, + $494 = 0; + var $495 = 0, + $496 = 0, + $497 = 0, + $498 = 0, + $499 = 0, + $5 = 0, + $50 = 0, + $500 = 0, + $501 = 0, + $502 = 0, + $503 = 0, + $504 = 0, + $505 = 0, + $506 = 0, + $507 = 0, + $508 = 0, + $509 = 0, + $51 = 0, + $510 = 0, + $511 = 0; + var $512 = 0, + $513 = 0, + $514 = 0, + $515 = 0, + $516 = 0, + $517 = 0, + $518 = 0, + $519 = 0, + $52 = 0, + $520 = 0, + $521 = 0, + $522 = 0, + $523 = 0, + $524 = 0, + $525 = 0, + $526 = 0, + $527 = 0, + $528 = 0, + $529 = 0, + $53 = 0; + var $530 = 0, + $531 = 0, + $532 = 0, + $533 = 0, + $534 = 0, + $535 = 0, + $536 = 0, + $537 = 0, + $538 = 0, + $539 = 0, + $54 = 0, + $540 = 0, + $541 = 0, + $542 = 0, + $543 = 0, + $544 = 0, + $545 = 0, + $546 = 0, + $547 = 0, + $548 = 0; + var $549 = 0, + $55 = 0, + $550 = 0, + $551 = 0, + $552 = 0, + $553 = 0, + $554 = 0, + $555 = 0, + $556 = 0, + $557 = 0, + $558 = 0, + $559 = 0, + $56 = 0, + $560 = 0, + $561 = 0, + $562 = 0, + $563 = 0, + $564 = 0, + $565 = 0, + $566 = 0; + var $567 = 0, + $568 = 0, + $569 = 0, + $57 = 0, + $570 = 0, + $571 = 0, + $572 = 0, + $573 = 0, + $574 = 0, + $575 = 0, + $576 = 0, + $577 = 0, + $578 = 0, + $579 = 0, + $58 = 0, + $580 = 0, + $581 = 0, + $582 = 0, + $583 = 0, + $584 = 0; + var $585 = 0, + $586 = 0, + $587 = 0, + $588 = 0, + $589 = 0, + $59 = 0, + $590 = 0, + $591 = 0, + $592 = 0, + $593 = 0, + $594 = 0, + $595 = 0, + $596 = 0, + $597 = 0, + $598 = 0, + $599 = 0, + $6 = 0, + $60 = 0, + $600 = 0, + $601 = 0; + var $602 = 0, + $603 = 0, + $604 = 0, + $605 = 0, + $606 = 0, + $607 = 0, + $608 = 0, + $609 = 0, + $61 = 0, + $610 = 0, + $611 = 0, + $612 = 0, + $613 = 0, + $614 = 0, + $615 = 0, + $616 = 0, + $617 = 0, + $618 = 0, + $619 = 0, + $62 = 0; + var $620 = 0, + $621 = 0, + $622 = 0, + $623 = 0, + $624 = 0, + $625 = 0, + $626 = 0, + $627 = 0, + $628 = 0, + $629 = 0, + $63 = 0, + $630 = 0, + $64 = 0, + $65 = 0, + $66 = 0, + $67 = 0, + $68 = 0, + $69 = 0, + $7 = 0, + $70 = 0; + var $71 = 0, + $72 = 0, + $73 = 0, + $74 = 0, + $75 = 0, + $76 = 0, + $77 = 0, + $78 = 0, + $79 = 0, + $8 = 0, + $80 = 0, + $81 = 0, + $82 = 0, + $83 = 0, + $84 = 0, + $85 = 0, + $86 = 0, + $87 = 0, + $88 = 0, + $89 = 0; + var $9 = 0, + $90 = 0, + $91 = 0, + $92 = 0, + $93 = 0, + $94 = 0, + $95 = 0, + $96 = 0, + $97 = 0, + $98 = 0, + $99 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP32[$f >> 2] | 0; + $1 = ($f + 4) | 0; + $2 = HEAP32[$1 >> 2] | 0; + $3 = ($f + 8) | 0; + $4 = HEAP32[$3 >> 2] | 0; + $5 = ($f + 12) | 0; + $6 = HEAP32[$5 >> 2] | 0; + $7 = ($f + 16) | 0; + $8 = HEAP32[$7 >> 2] | 0; + $9 = ($f + 20) | 0; + $10 = HEAP32[$9 >> 2] | 0; + $11 = ($f + 24) | 0; + $12 = HEAP32[$11 >> 2] | 0; + $13 = ($f + 28) | 0; + $14 = HEAP32[$13 >> 2] | 0; + $15 = ($f + 32) | 0; + $16 = HEAP32[$15 >> 2] | 0; + $17 = ($f + 36) | 0; + $18 = HEAP32[$17 >> 2] | 0; + $19 = HEAP32[$g >> 2] | 0; + $20 = ($g + 4) | 0; + $21 = HEAP32[$20 >> 2] | 0; + $22 = ($g + 8) | 0; + $23 = HEAP32[$22 >> 2] | 0; + $24 = ($g + 12) | 0; + $25 = HEAP32[$24 >> 2] | 0; + $26 = ($g + 16) | 0; + $27 = HEAP32[$26 >> 2] | 0; + $28 = ($g + 20) | 0; + $29 = HEAP32[$28 >> 2] | 0; + $30 = ($g + 24) | 0; + $31 = HEAP32[$30 >> 2] | 0; + $32 = ($g + 28) | 0; + $33 = HEAP32[$32 >> 2] | 0; + $34 = ($g + 32) | 0; + $35 = HEAP32[$34 >> 2] | 0; + $36 = ($g + 36) | 0; + $37 = HEAP32[$36 >> 2] | 0; + $38 = ($21 * 19) | 0; + $39 = ($23 * 19) | 0; + $40 = ($25 * 19) | 0; + $41 = ($27 * 19) | 0; + $42 = ($29 * 19) | 0; + $43 = ($31 * 19) | 0; + $44 = ($33 * 19) | 0; + $45 = ($35 * 19) | 0; + $46 = ($37 * 19) | 0; + $47 = $2 << 1; + $48 = $6 << 1; + $49 = $10 << 1; + $50 = $14 << 1; + $51 = $18 << 1; + $52 = ($0 | 0) < 0; + $53 = ($52 << 31) >> 31; + $54 = ($19 | 0) < 0; + $55 = ($54 << 31) >> 31; + $56 = ___muldi3($19 | 0, $55 | 0, $0 | 0, $53 | 0) | 0; + $57 = tempRet0; + $58 = ($21 | 0) < 0; + $59 = ($58 << 31) >> 31; + $60 = ___muldi3($21 | 0, $59 | 0, $0 | 0, $53 | 0) | 0; + $61 = tempRet0; + $62 = ($23 | 0) < 0; + $63 = ($62 << 31) >> 31; + $64 = ___muldi3($23 | 0, $63 | 0, $0 | 0, $53 | 0) | 0; + $65 = tempRet0; + $66 = ($25 | 0) < 0; + $67 = ($66 << 31) >> 31; + $68 = ___muldi3($25 | 0, $67 | 0, $0 | 0, $53 | 0) | 0; + $69 = tempRet0; + $70 = ($27 | 0) < 0; + $71 = ($70 << 31) >> 31; + $72 = ___muldi3($27 | 0, $71 | 0, $0 | 0, $53 | 0) | 0; + $73 = tempRet0; + $74 = ($29 | 0) < 0; + $75 = ($74 << 31) >> 31; + $76 = ___muldi3($29 | 0, $75 | 0, $0 | 0, $53 | 0) | 0; + $77 = tempRet0; + $78 = ($31 | 0) < 0; + $79 = ($78 << 31) >> 31; + $80 = ___muldi3($31 | 0, $79 | 0, $0 | 0, $53 | 0) | 0; + $81 = tempRet0; + $82 = ($33 | 0) < 0; + $83 = ($82 << 31) >> 31; + $84 = ___muldi3($33 | 0, $83 | 0, $0 | 0, $53 | 0) | 0; + $85 = tempRet0; + $86 = ($35 | 0) < 0; + $87 = ($86 << 31) >> 31; + $88 = ___muldi3($35 | 0, $87 | 0, $0 | 0, $53 | 0) | 0; + $89 = tempRet0; + $90 = ($37 | 0) < 0; + $91 = ($90 << 31) >> 31; + $92 = ___muldi3($37 | 0, $91 | 0, $0 | 0, $53 | 0) | 0; + $93 = tempRet0; + $94 = ($2 | 0) < 0; + $95 = ($94 << 31) >> 31; + $96 = ___muldi3($19 | 0, $55 | 0, $2 | 0, $95 | 0) | 0; + $97 = tempRet0; + $98 = ($47 | 0) < 0; + $99 = ($98 << 31) >> 31; + $100 = ___muldi3($21 | 0, $59 | 0, $47 | 0, $99 | 0) | 0; + $101 = tempRet0; + $102 = ___muldi3($23 | 0, $63 | 0, $2 | 0, $95 | 0) | 0; + $103 = tempRet0; + $104 = ___muldi3($25 | 0, $67 | 0, $47 | 0, $99 | 0) | 0; + $105 = tempRet0; + $106 = ___muldi3($27 | 0, $71 | 0, $2 | 0, $95 | 0) | 0; + $107 = tempRet0; + $108 = ___muldi3($29 | 0, $75 | 0, $47 | 0, $99 | 0) | 0; + $109 = tempRet0; + $110 = ___muldi3($31 | 0, $79 | 0, $2 | 0, $95 | 0) | 0; + $111 = tempRet0; + $112 = ___muldi3($33 | 0, $83 | 0, $47 | 0, $99 | 0) | 0; + $113 = tempRet0; + $114 = ___muldi3($35 | 0, $87 | 0, $2 | 0, $95 | 0) | 0; + $115 = tempRet0; + $116 = ($46 | 0) < 0; + $117 = ($116 << 31) >> 31; + $118 = ___muldi3($46 | 0, $117 | 0, $47 | 0, $99 | 0) | 0; + $119 = tempRet0; + $120 = ($4 | 0) < 0; + $121 = ($120 << 31) >> 31; + $122 = ___muldi3($19 | 0, $55 | 0, $4 | 0, $121 | 0) | 0; + $123 = tempRet0; + $124 = ___muldi3($21 | 0, $59 | 0, $4 | 0, $121 | 0) | 0; + $125 = tempRet0; + $126 = ___muldi3($23 | 0, $63 | 0, $4 | 0, $121 | 0) | 0; + $127 = tempRet0; + $128 = ___muldi3($25 | 0, $67 | 0, $4 | 0, $121 | 0) | 0; + $129 = tempRet0; + $130 = ___muldi3($27 | 0, $71 | 0, $4 | 0, $121 | 0) | 0; + $131 = tempRet0; + $132 = ___muldi3($29 | 0, $75 | 0, $4 | 0, $121 | 0) | 0; + $133 = tempRet0; + $134 = ___muldi3($31 | 0, $79 | 0, $4 | 0, $121 | 0) | 0; + $135 = tempRet0; + $136 = ___muldi3($33 | 0, $83 | 0, $4 | 0, $121 | 0) | 0; + $137 = tempRet0; + $138 = ($45 | 0) < 0; + $139 = ($138 << 31) >> 31; + $140 = ___muldi3($45 | 0, $139 | 0, $4 | 0, $121 | 0) | 0; + $141 = tempRet0; + $142 = ___muldi3($46 | 0, $117 | 0, $4 | 0, $121 | 0) | 0; + $143 = tempRet0; + $144 = ($6 | 0) < 0; + $145 = ($144 << 31) >> 31; + $146 = ___muldi3($19 | 0, $55 | 0, $6 | 0, $145 | 0) | 0; + $147 = tempRet0; + $148 = ($48 | 0) < 0; + $149 = ($148 << 31) >> 31; + $150 = ___muldi3($21 | 0, $59 | 0, $48 | 0, $149 | 0) | 0; + $151 = tempRet0; + $152 = ___muldi3($23 | 0, $63 | 0, $6 | 0, $145 | 0) | 0; + $153 = tempRet0; + $154 = ___muldi3($25 | 0, $67 | 0, $48 | 0, $149 | 0) | 0; + $155 = tempRet0; + $156 = ___muldi3($27 | 0, $71 | 0, $6 | 0, $145 | 0) | 0; + $157 = tempRet0; + $158 = ___muldi3($29 | 0, $75 | 0, $48 | 0, $149 | 0) | 0; + $159 = tempRet0; + $160 = ___muldi3($31 | 0, $79 | 0, $6 | 0, $145 | 0) | 0; + $161 = tempRet0; + $162 = ($44 | 0) < 0; + $163 = ($162 << 31) >> 31; + $164 = ___muldi3($44 | 0, $163 | 0, $48 | 0, $149 | 0) | 0; + $165 = tempRet0; + $166 = ___muldi3($45 | 0, $139 | 0, $6 | 0, $145 | 0) | 0; + $167 = tempRet0; + $168 = ___muldi3($46 | 0, $117 | 0, $48 | 0, $149 | 0) | 0; + $169 = tempRet0; + $170 = ($8 | 0) < 0; + $171 = ($170 << 31) >> 31; + $172 = ___muldi3($19 | 0, $55 | 0, $8 | 0, $171 | 0) | 0; + $173 = tempRet0; + $174 = ___muldi3($21 | 0, $59 | 0, $8 | 0, $171 | 0) | 0; + $175 = tempRet0; + $176 = ___muldi3($23 | 0, $63 | 0, $8 | 0, $171 | 0) | 0; + $177 = tempRet0; + $178 = ___muldi3($25 | 0, $67 | 0, $8 | 0, $171 | 0) | 0; + $179 = tempRet0; + $180 = ___muldi3($27 | 0, $71 | 0, $8 | 0, $171 | 0) | 0; + $181 = tempRet0; + $182 = ___muldi3($29 | 0, $75 | 0, $8 | 0, $171 | 0) | 0; + $183 = tempRet0; + $184 = ($43 | 0) < 0; + $185 = ($184 << 31) >> 31; + $186 = ___muldi3($43 | 0, $185 | 0, $8 | 0, $171 | 0) | 0; + $187 = tempRet0; + $188 = ___muldi3($44 | 0, $163 | 0, $8 | 0, $171 | 0) | 0; + $189 = tempRet0; + $190 = ___muldi3($45 | 0, $139 | 0, $8 | 0, $171 | 0) | 0; + $191 = tempRet0; + $192 = ___muldi3($46 | 0, $117 | 0, $8 | 0, $171 | 0) | 0; + $193 = tempRet0; + $194 = ($10 | 0) < 0; + $195 = ($194 << 31) >> 31; + $196 = ___muldi3($19 | 0, $55 | 0, $10 | 0, $195 | 0) | 0; + $197 = tempRet0; + $198 = ($49 | 0) < 0; + $199 = ($198 << 31) >> 31; + $200 = ___muldi3($21 | 0, $59 | 0, $49 | 0, $199 | 0) | 0; + $201 = tempRet0; + $202 = ___muldi3($23 | 0, $63 | 0, $10 | 0, $195 | 0) | 0; + $203 = tempRet0; + $204 = ___muldi3($25 | 0, $67 | 0, $49 | 0, $199 | 0) | 0; + $205 = tempRet0; + $206 = ___muldi3($27 | 0, $71 | 0, $10 | 0, $195 | 0) | 0; + $207 = tempRet0; + $208 = ($42 | 0) < 0; + $209 = ($208 << 31) >> 31; + $210 = ___muldi3($42 | 0, $209 | 0, $49 | 0, $199 | 0) | 0; + $211 = tempRet0; + $212 = ___muldi3($43 | 0, $185 | 0, $10 | 0, $195 | 0) | 0; + $213 = tempRet0; + $214 = ___muldi3($44 | 0, $163 | 0, $49 | 0, $199 | 0) | 0; + $215 = tempRet0; + $216 = ___muldi3($45 | 0, $139 | 0, $10 | 0, $195 | 0) | 0; + $217 = tempRet0; + $218 = ___muldi3($46 | 0, $117 | 0, $49 | 0, $199 | 0) | 0; + $219 = tempRet0; + $220 = ($12 | 0) < 0; + $221 = ($220 << 31) >> 31; + $222 = ___muldi3($19 | 0, $55 | 0, $12 | 0, $221 | 0) | 0; + $223 = tempRet0; + $224 = ___muldi3($21 | 0, $59 | 0, $12 | 0, $221 | 0) | 0; + $225 = tempRet0; + $226 = ___muldi3($23 | 0, $63 | 0, $12 | 0, $221 | 0) | 0; + $227 = tempRet0; + $228 = ___muldi3($25 | 0, $67 | 0, $12 | 0, $221 | 0) | 0; + $229 = tempRet0; + $230 = ($41 | 0) < 0; + $231 = ($230 << 31) >> 31; + $232 = ___muldi3($41 | 0, $231 | 0, $12 | 0, $221 | 0) | 0; + $233 = tempRet0; + $234 = ___muldi3($42 | 0, $209 | 0, $12 | 0, $221 | 0) | 0; + $235 = tempRet0; + $236 = ___muldi3($43 | 0, $185 | 0, $12 | 0, $221 | 0) | 0; + $237 = tempRet0; + $238 = ___muldi3($44 | 0, $163 | 0, $12 | 0, $221 | 0) | 0; + $239 = tempRet0; + $240 = ___muldi3($45 | 0, $139 | 0, $12 | 0, $221 | 0) | 0; + $241 = tempRet0; + $242 = ___muldi3($46 | 0, $117 | 0, $12 | 0, $221 | 0) | 0; + $243 = tempRet0; + $244 = ($14 | 0) < 0; + $245 = ($244 << 31) >> 31; + $246 = ___muldi3($19 | 0, $55 | 0, $14 | 0, $245 | 0) | 0; + $247 = tempRet0; + $248 = ($50 | 0) < 0; + $249 = ($248 << 31) >> 31; + $250 = ___muldi3($21 | 0, $59 | 0, $50 | 0, $249 | 0) | 0; + $251 = tempRet0; + $252 = ___muldi3($23 | 0, $63 | 0, $14 | 0, $245 | 0) | 0; + $253 = tempRet0; + $254 = ($40 | 0) < 0; + $255 = ($254 << 31) >> 31; + $256 = ___muldi3($40 | 0, $255 | 0, $50 | 0, $249 | 0) | 0; + $257 = tempRet0; + $258 = ___muldi3($41 | 0, $231 | 0, $14 | 0, $245 | 0) | 0; + $259 = tempRet0; + $260 = ___muldi3($42 | 0, $209 | 0, $50 | 0, $249 | 0) | 0; + $261 = tempRet0; + $262 = ___muldi3($43 | 0, $185 | 0, $14 | 0, $245 | 0) | 0; + $263 = tempRet0; + $264 = ___muldi3($44 | 0, $163 | 0, $50 | 0, $249 | 0) | 0; + $265 = tempRet0; + $266 = ___muldi3($45 | 0, $139 | 0, $14 | 0, $245 | 0) | 0; + $267 = tempRet0; + $268 = ___muldi3($46 | 0, $117 | 0, $50 | 0, $249 | 0) | 0; + $269 = tempRet0; + $270 = ($16 | 0) < 0; + $271 = ($270 << 31) >> 31; + $272 = ___muldi3($19 | 0, $55 | 0, $16 | 0, $271 | 0) | 0; + $273 = tempRet0; + $274 = ___muldi3($21 | 0, $59 | 0, $16 | 0, $271 | 0) | 0; + $275 = tempRet0; + $276 = ($39 | 0) < 0; + $277 = ($276 << 31) >> 31; + $278 = ___muldi3($39 | 0, $277 | 0, $16 | 0, $271 | 0) | 0; + $279 = tempRet0; + $280 = ___muldi3($40 | 0, $255 | 0, $16 | 0, $271 | 0) | 0; + $281 = tempRet0; + $282 = ___muldi3($41 | 0, $231 | 0, $16 | 0, $271 | 0) | 0; + $283 = tempRet0; + $284 = ___muldi3($42 | 0, $209 | 0, $16 | 0, $271 | 0) | 0; + $285 = tempRet0; + $286 = ___muldi3($43 | 0, $185 | 0, $16 | 0, $271 | 0) | 0; + $287 = tempRet0; + $288 = ___muldi3($44 | 0, $163 | 0, $16 | 0, $271 | 0) | 0; + $289 = tempRet0; + $290 = ___muldi3($45 | 0, $139 | 0, $16 | 0, $271 | 0) | 0; + $291 = tempRet0; + $292 = ___muldi3($46 | 0, $117 | 0, $16 | 0, $271 | 0) | 0; + $293 = tempRet0; + $294 = ($18 | 0) < 0; + $295 = ($294 << 31) >> 31; + $296 = ___muldi3($19 | 0, $55 | 0, $18 | 0, $295 | 0) | 0; + $297 = tempRet0; + $298 = ($51 | 0) < 0; + $299 = ($298 << 31) >> 31; + $300 = ($38 | 0) < 0; + $301 = ($300 << 31) >> 31; + $302 = ___muldi3($38 | 0, $301 | 0, $51 | 0, $299 | 0) | 0; + $303 = tempRet0; + $304 = ___muldi3($39 | 0, $277 | 0, $18 | 0, $295 | 0) | 0; + $305 = tempRet0; + $306 = ___muldi3($40 | 0, $255 | 0, $51 | 0, $299 | 0) | 0; + $307 = tempRet0; + $308 = ___muldi3($41 | 0, $231 | 0, $18 | 0, $295 | 0) | 0; + $309 = tempRet0; + $310 = ___muldi3($42 | 0, $209 | 0, $51 | 0, $299 | 0) | 0; + $311 = tempRet0; + $312 = ___muldi3($43 | 0, $185 | 0, $18 | 0, $295 | 0) | 0; + $313 = tempRet0; + $314 = ___muldi3($44 | 0, $163 | 0, $51 | 0, $299 | 0) | 0; + $315 = tempRet0; + $316 = ___muldi3($45 | 0, $139 | 0, $18 | 0, $295 | 0) | 0; + $317 = tempRet0; + $318 = ___muldi3($46 | 0, $117 | 0, $51 | 0, $299 | 0) | 0; + $319 = tempRet0; + $320 = _i64Add($302 | 0, $303 | 0, $56 | 0, $57 | 0) | 0; + $321 = tempRet0; + $322 = _i64Add($320 | 0, $321 | 0, $278 | 0, $279 | 0) | 0; + $323 = tempRet0; + $324 = _i64Add($322 | 0, $323 | 0, $256 | 0, $257 | 0) | 0; + $325 = tempRet0; + $326 = _i64Add($324 | 0, $325 | 0, $232 | 0, $233 | 0) | 0; + $327 = tempRet0; + $328 = _i64Add($326 | 0, $327 | 0, $210 | 0, $211 | 0) | 0; + $329 = tempRet0; + $330 = _i64Add($328 | 0, $329 | 0, $186 | 0, $187 | 0) | 0; + $331 = tempRet0; + $332 = _i64Add($330 | 0, $331 | 0, $164 | 0, $165 | 0) | 0; + $333 = tempRet0; + $334 = _i64Add($332 | 0, $333 | 0, $140 | 0, $141 | 0) | 0; + $335 = tempRet0; + $336 = _i64Add($334 | 0, $335 | 0, $118 | 0, $119 | 0) | 0; + $337 = tempRet0; + $338 = _i64Add($60 | 0, $61 | 0, $96 | 0, $97 | 0) | 0; + $339 = tempRet0; + $340 = _i64Add($150 | 0, $151 | 0, $172 | 0, $173 | 0) | 0; + $341 = tempRet0; + $342 = _i64Add($340 | 0, $341 | 0, $126 | 0, $127 | 0) | 0; + $343 = tempRet0; + $344 = _i64Add($342 | 0, $343 | 0, $104 | 0, $105 | 0) | 0; + $345 = tempRet0; + $346 = _i64Add($344 | 0, $345 | 0, $72 | 0, $73 | 0) | 0; + $347 = tempRet0; + $348 = _i64Add($346 | 0, $347 | 0, $310 | 0, $311 | 0) | 0; + $349 = tempRet0; + $350 = _i64Add($348 | 0, $349 | 0, $286 | 0, $287 | 0) | 0; + $351 = tempRet0; + $352 = _i64Add($350 | 0, $351 | 0, $264 | 0, $265 | 0) | 0; + $353 = tempRet0; + $354 = _i64Add($352 | 0, $353 | 0, $240 | 0, $241 | 0) | 0; + $355 = tempRet0; + $356 = _i64Add($354 | 0, $355 | 0, $218 | 0, $219 | 0) | 0; + $357 = tempRet0; + $358 = _i64Add($336 | 0, $337 | 0, 33554432, 0) | 0; + $359 = tempRet0; + $360 = _bitshift64Ashr($358 | 0, $359 | 0, 26) | 0; + $361 = tempRet0; + $362 = _i64Add($338 | 0, $339 | 0, $304 | 0, $305 | 0) | 0; + $363 = tempRet0; + $364 = _i64Add($362 | 0, $363 | 0, $280 | 0, $281 | 0) | 0; + $365 = tempRet0; + $366 = _i64Add($364 | 0, $365 | 0, $258 | 0, $259 | 0) | 0; + $367 = tempRet0; + $368 = _i64Add($366 | 0, $367 | 0, $234 | 0, $235 | 0) | 0; + $369 = tempRet0; + $370 = _i64Add($368 | 0, $369 | 0, $212 | 0, $213 | 0) | 0; + $371 = tempRet0; + $372 = _i64Add($370 | 0, $371 | 0, $188 | 0, $189 | 0) | 0; + $373 = tempRet0; + $374 = _i64Add($372 | 0, $373 | 0, $166 | 0, $167 | 0) | 0; + $375 = tempRet0; + $376 = _i64Add($374 | 0, $375 | 0, $142 | 0, $143 | 0) | 0; + $377 = tempRet0; + $378 = _i64Add($376 | 0, $377 | 0, $360 | 0, $361 | 0) | 0; + $379 = tempRet0; + $380 = _bitshift64Shl($360 | 0, $361 | 0, 26) | 0; + $381 = tempRet0; + $382 = _i64Subtract($336 | 0, $337 | 0, $380 | 0, $381 | 0) | 0; + $383 = tempRet0; + $384 = _i64Add($356 | 0, $357 | 0, 33554432, 0) | 0; + $385 = tempRet0; + $386 = _bitshift64Ashr($384 | 0, $385 | 0, 26) | 0; + $387 = tempRet0; + $388 = _i64Add($174 | 0, $175 | 0, $196 | 0, $197 | 0) | 0; + $389 = tempRet0; + $390 = _i64Add($388 | 0, $389 | 0, $152 | 0, $153 | 0) | 0; + $391 = tempRet0; + $392 = _i64Add($390 | 0, $391 | 0, $128 | 0, $129 | 0) | 0; + $393 = tempRet0; + $394 = _i64Add($392 | 0, $393 | 0, $106 | 0, $107 | 0) | 0; + $395 = tempRet0; + $396 = _i64Add($394 | 0, $395 | 0, $76 | 0, $77 | 0) | 0; + $397 = tempRet0; + $398 = _i64Add($396 | 0, $397 | 0, $312 | 0, $313 | 0) | 0; + $399 = tempRet0; + $400 = _i64Add($398 | 0, $399 | 0, $288 | 0, $289 | 0) | 0; + $401 = tempRet0; + $402 = _i64Add($400 | 0, $401 | 0, $266 | 0, $267 | 0) | 0; + $403 = tempRet0; + $404 = _i64Add($402 | 0, $403 | 0, $242 | 0, $243 | 0) | 0; + $405 = tempRet0; + $406 = _i64Add($404 | 0, $405 | 0, $386 | 0, $387 | 0) | 0; + $407 = tempRet0; + $408 = _bitshift64Shl($386 | 0, $387 | 0, 26) | 0; + $409 = tempRet0; + $410 = _i64Subtract($356 | 0, $357 | 0, $408 | 0, $409 | 0) | 0; + $411 = tempRet0; + $412 = _i64Add($378 | 0, $379 | 0, 16777216, 0) | 0; + $413 = tempRet0; + $414 = _bitshift64Ashr($412 | 0, $413 | 0, 25) | 0; + $415 = tempRet0; + $416 = _i64Add($100 | 0, $101 | 0, $122 | 0, $123 | 0) | 0; + $417 = tempRet0; + $418 = _i64Add($416 | 0, $417 | 0, $64 | 0, $65 | 0) | 0; + $419 = tempRet0; + $420 = _i64Add($418 | 0, $419 | 0, $306 | 0, $307 | 0) | 0; + $421 = tempRet0; + $422 = _i64Add($420 | 0, $421 | 0, $282 | 0, $283 | 0) | 0; + $423 = tempRet0; + $424 = _i64Add($422 | 0, $423 | 0, $260 | 0, $261 | 0) | 0; + $425 = tempRet0; + $426 = _i64Add($424 | 0, $425 | 0, $236 | 0, $237 | 0) | 0; + $427 = tempRet0; + $428 = _i64Add($426 | 0, $427 | 0, $214 | 0, $215 | 0) | 0; + $429 = tempRet0; + $430 = _i64Add($428 | 0, $429 | 0, $190 | 0, $191 | 0) | 0; + $431 = tempRet0; + $432 = _i64Add($430 | 0, $431 | 0, $168 | 0, $169 | 0) | 0; + $433 = tempRet0; + $434 = _i64Add($432 | 0, $433 | 0, $414 | 0, $415 | 0) | 0; + $435 = tempRet0; + $436 = _bitshift64Shl($414 | 0, $415 | 0, 25) | 0; + $437 = tempRet0; + $438 = _i64Subtract($378 | 0, $379 | 0, $436 | 0, $437 | 0) | 0; + $439 = tempRet0; + $440 = _i64Add($406 | 0, $407 | 0, 16777216, 0) | 0; + $441 = tempRet0; + $442 = _bitshift64Ashr($440 | 0, $441 | 0, 25) | 0; + $443 = tempRet0; + $444 = _i64Add($200 | 0, $201 | 0, $222 | 0, $223 | 0) | 0; + $445 = tempRet0; + $446 = _i64Add($444 | 0, $445 | 0, $176 | 0, $177 | 0) | 0; + $447 = tempRet0; + $448 = _i64Add($446 | 0, $447 | 0, $154 | 0, $155 | 0) | 0; + $449 = tempRet0; + $450 = _i64Add($448 | 0, $449 | 0, $130 | 0, $131 | 0) | 0; + $451 = tempRet0; + $452 = _i64Add($450 | 0, $451 | 0, $108 | 0, $109 | 0) | 0; + $453 = tempRet0; + $454 = _i64Add($452 | 0, $453 | 0, $80 | 0, $81 | 0) | 0; + $455 = tempRet0; + $456 = _i64Add($454 | 0, $455 | 0, $314 | 0, $315 | 0) | 0; + $457 = tempRet0; + $458 = _i64Add($456 | 0, $457 | 0, $290 | 0, $291 | 0) | 0; + $459 = tempRet0; + $460 = _i64Add($458 | 0, $459 | 0, $268 | 0, $269 | 0) | 0; + $461 = tempRet0; + $462 = _i64Add($460 | 0, $461 | 0, $442 | 0, $443 | 0) | 0; + $463 = tempRet0; + $464 = _bitshift64Shl($442 | 0, $443 | 0, 25) | 0; + $465 = tempRet0; + $466 = _i64Subtract($406 | 0, $407 | 0, $464 | 0, $465 | 0) | 0; + $467 = tempRet0; + $468 = _i64Add($434 | 0, $435 | 0, 33554432, 0) | 0; + $469 = tempRet0; + $470 = _bitshift64Ashr($468 | 0, $469 | 0, 26) | 0; + $471 = tempRet0; + $472 = _i64Add($124 | 0, $125 | 0, $146 | 0, $147 | 0) | 0; + $473 = tempRet0; + $474 = _i64Add($472 | 0, $473 | 0, $102 | 0, $103 | 0) | 0; + $475 = tempRet0; + $476 = _i64Add($474 | 0, $475 | 0, $68 | 0, $69 | 0) | 0; + $477 = tempRet0; + $478 = _i64Add($476 | 0, $477 | 0, $308 | 0, $309 | 0) | 0; + $479 = tempRet0; + $480 = _i64Add($478 | 0, $479 | 0, $284 | 0, $285 | 0) | 0; + $481 = tempRet0; + $482 = _i64Add($480 | 0, $481 | 0, $262 | 0, $263 | 0) | 0; + $483 = tempRet0; + $484 = _i64Add($482 | 0, $483 | 0, $238 | 0, $239 | 0) | 0; + $485 = tempRet0; + $486 = _i64Add($484 | 0, $485 | 0, $216 | 0, $217 | 0) | 0; + $487 = tempRet0; + $488 = _i64Add($486 | 0, $487 | 0, $192 | 0, $193 | 0) | 0; + $489 = tempRet0; + $490 = _i64Add($488 | 0, $489 | 0, $470 | 0, $471 | 0) | 0; + $491 = tempRet0; + $492 = _bitshift64Shl($470 | 0, $471 | 0, 26) | 0; + $493 = tempRet0; + $494 = _i64Subtract($434 | 0, $435 | 0, $492 | 0, $493 | 0) | 0; + $495 = tempRet0; + $496 = _i64Add($462 | 0, $463 | 0, 33554432, 0) | 0; + $497 = tempRet0; + $498 = _bitshift64Ashr($496 | 0, $497 | 0, 26) | 0; + $499 = tempRet0; + $500 = _i64Add($224 | 0, $225 | 0, $246 | 0, $247 | 0) | 0; + $501 = tempRet0; + $502 = _i64Add($500 | 0, $501 | 0, $202 | 0, $203 | 0) | 0; + $503 = tempRet0; + $504 = _i64Add($502 | 0, $503 | 0, $178 | 0, $179 | 0) | 0; + $505 = tempRet0; + $506 = _i64Add($504 | 0, $505 | 0, $156 | 0, $157 | 0) | 0; + $507 = tempRet0; + $508 = _i64Add($506 | 0, $507 | 0, $132 | 0, $133 | 0) | 0; + $509 = tempRet0; + $510 = _i64Add($508 | 0, $509 | 0, $110 | 0, $111 | 0) | 0; + $511 = tempRet0; + $512 = _i64Add($510 | 0, $511 | 0, $84 | 0, $85 | 0) | 0; + $513 = tempRet0; + $514 = _i64Add($512 | 0, $513 | 0, $316 | 0, $317 | 0) | 0; + $515 = tempRet0; + $516 = _i64Add($514 | 0, $515 | 0, $292 | 0, $293 | 0) | 0; + $517 = tempRet0; + $518 = _i64Add($516 | 0, $517 | 0, $498 | 0, $499 | 0) | 0; + $519 = tempRet0; + $520 = _bitshift64Shl($498 | 0, $499 | 0, 26) | 0; + $521 = tempRet0; + $522 = _i64Subtract($462 | 0, $463 | 0, $520 | 0, $521 | 0) | 0; + $523 = tempRet0; + $524 = _i64Add($490 | 0, $491 | 0, 16777216, 0) | 0; + $525 = tempRet0; + $526 = _bitshift64Ashr($524 | 0, $525 | 0, 25) | 0; + $527 = tempRet0; + $528 = _i64Add($526 | 0, $527 | 0, $410 | 0, $411 | 0) | 0; + $529 = tempRet0; + $530 = _bitshift64Shl($526 | 0, $527 | 0, 25) | 0; + $531 = tempRet0; + $532 = _i64Subtract($490 | 0, $491 | 0, $530 | 0, $531 | 0) | 0; + $533 = tempRet0; + $534 = _i64Add($518 | 0, $519 | 0, 16777216, 0) | 0; + $535 = tempRet0; + $536 = _bitshift64Ashr($534 | 0, $535 | 0, 25) | 0; + $537 = tempRet0; + $538 = _i64Add($250 | 0, $251 | 0, $272 | 0, $273 | 0) | 0; + $539 = tempRet0; + $540 = _i64Add($538 | 0, $539 | 0, $226 | 0, $227 | 0) | 0; + $541 = tempRet0; + $542 = _i64Add($540 | 0, $541 | 0, $204 | 0, $205 | 0) | 0; + $543 = tempRet0; + $544 = _i64Add($542 | 0, $543 | 0, $180 | 0, $181 | 0) | 0; + $545 = tempRet0; + $546 = _i64Add($544 | 0, $545 | 0, $158 | 0, $159 | 0) | 0; + $547 = tempRet0; + $548 = _i64Add($546 | 0, $547 | 0, $134 | 0, $135 | 0) | 0; + $549 = tempRet0; + $550 = _i64Add($548 | 0, $549 | 0, $112 | 0, $113 | 0) | 0; + $551 = tempRet0; + $552 = _i64Add($550 | 0, $551 | 0, $88 | 0, $89 | 0) | 0; + $553 = tempRet0; + $554 = _i64Add($552 | 0, $553 | 0, $318 | 0, $319 | 0) | 0; + $555 = tempRet0; + $556 = _i64Add($554 | 0, $555 | 0, $536 | 0, $537 | 0) | 0; + $557 = tempRet0; + $558 = _bitshift64Shl($536 | 0, $537 | 0, 25) | 0; + $559 = tempRet0; + $560 = _i64Subtract($518 | 0, $519 | 0, $558 | 0, $559 | 0) | 0; + $561 = tempRet0; + $562 = _i64Add($528 | 0, $529 | 0, 33554432, 0) | 0; + $563 = tempRet0; + $564 = _bitshift64Ashr($562 | 0, $563 | 0, 26) | 0; + $565 = tempRet0; + $566 = _i64Add($466 | 0, $467 | 0, $564 | 0, $565 | 0) | 0; + $567 = tempRet0; + $568 = _bitshift64Shl($564 | 0, $565 | 0, 26) | 0; + $569 = tempRet0; + $570 = _i64Subtract($528 | 0, $529 | 0, $568 | 0, $569 | 0) | 0; + $571 = tempRet0; + $572 = _i64Add($556 | 0, $557 | 0, 33554432, 0) | 0; + $573 = tempRet0; + $574 = _bitshift64Ashr($572 | 0, $573 | 0, 26) | 0; + $575 = tempRet0; + $576 = _i64Add($274 | 0, $275 | 0, $296 | 0, $297 | 0) | 0; + $577 = tempRet0; + $578 = _i64Add($576 | 0, $577 | 0, $252 | 0, $253 | 0) | 0; + $579 = tempRet0; + $580 = _i64Add($578 | 0, $579 | 0, $228 | 0, $229 | 0) | 0; + $581 = tempRet0; + $582 = _i64Add($580 | 0, $581 | 0, $206 | 0, $207 | 0) | 0; + $583 = tempRet0; + $584 = _i64Add($582 | 0, $583 | 0, $182 | 0, $183 | 0) | 0; + $585 = tempRet0; + $586 = _i64Add($584 | 0, $585 | 0, $160 | 0, $161 | 0) | 0; + $587 = tempRet0; + $588 = _i64Add($586 | 0, $587 | 0, $136 | 0, $137 | 0) | 0; + $589 = tempRet0; + $590 = _i64Add($588 | 0, $589 | 0, $114 | 0, $115 | 0) | 0; + $591 = tempRet0; + $592 = _i64Add($590 | 0, $591 | 0, $92 | 0, $93 | 0) | 0; + $593 = tempRet0; + $594 = _i64Add($592 | 0, $593 | 0, $574 | 0, $575 | 0) | 0; + $595 = tempRet0; + $596 = _bitshift64Shl($574 | 0, $575 | 0, 26) | 0; + $597 = tempRet0; + $598 = _i64Subtract($556 | 0, $557 | 0, $596 | 0, $597 | 0) | 0; + $599 = tempRet0; + $600 = _i64Add($594 | 0, $595 | 0, 16777216, 0) | 0; + $601 = tempRet0; + $602 = _bitshift64Ashr($600 | 0, $601 | 0, 25) | 0; + $603 = tempRet0; + $604 = ___muldi3($602 | 0, $603 | 0, 19, 0) | 0; + $605 = tempRet0; + $606 = _i64Add($604 | 0, $605 | 0, $382 | 0, $383 | 0) | 0; + $607 = tempRet0; + $608 = _bitshift64Shl($602 | 0, $603 | 0, 25) | 0; + $609 = tempRet0; + $610 = _i64Subtract($594 | 0, $595 | 0, $608 | 0, $609 | 0) | 0; + $611 = tempRet0; + $612 = _i64Add($606 | 0, $607 | 0, 33554432, 0) | 0; + $613 = tempRet0; + $614 = _bitshift64Ashr($612 | 0, $613 | 0, 26) | 0; + $615 = tempRet0; + $616 = _i64Add($438 | 0, $439 | 0, $614 | 0, $615 | 0) | 0; + $617 = tempRet0; + $618 = _bitshift64Shl($614 | 0, $615 | 0, 26) | 0; + $619 = tempRet0; + $620 = _i64Subtract($606 | 0, $607 | 0, $618 | 0, $619 | 0) | 0; + $621 = tempRet0; + HEAP32[$h >> 2] = $620; + $622 = ($h + 4) | 0; + HEAP32[$622 >> 2] = $616; + $623 = ($h + 8) | 0; + HEAP32[$623 >> 2] = $494; + $624 = ($h + 12) | 0; + HEAP32[$624 >> 2] = $532; + $625 = ($h + 16) | 0; + HEAP32[$625 >> 2] = $570; + $626 = ($h + 20) | 0; + HEAP32[$626 >> 2] = $566; + $627 = ($h + 24) | 0; + HEAP32[$627 >> 2] = $522; + $628 = ($h + 28) | 0; + HEAP32[$628 >> 2] = $560; + $629 = ($h + 32) | 0; + HEAP32[$629 >> 2] = $598; + $630 = ($h + 36) | 0; + HEAP32[$630 >> 2] = $610; + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_fe_neg($h, $f) { + $h = $h | 0; + $f = $f | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0; + var $27 = 0, + $28 = 0, + $29 = 0, + $3 = 0, + $30 = 0, + $31 = 0, + $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $37 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP32[$f >> 2] | 0; + $1 = ($f + 4) | 0; + $2 = HEAP32[$1 >> 2] | 0; + $3 = ($f + 8) | 0; + $4 = HEAP32[$3 >> 2] | 0; + $5 = ($f + 12) | 0; + $6 = HEAP32[$5 >> 2] | 0; + $7 = ($f + 16) | 0; + $8 = HEAP32[$7 >> 2] | 0; + $9 = ($f + 20) | 0; + $10 = HEAP32[$9 >> 2] | 0; + $11 = ($f + 24) | 0; + $12 = HEAP32[$11 >> 2] | 0; + $13 = ($f + 28) | 0; + $14 = HEAP32[$13 >> 2] | 0; + $15 = ($f + 32) | 0; + $16 = HEAP32[$15 >> 2] | 0; + $17 = ($f + 36) | 0; + $18 = HEAP32[$17 >> 2] | 0; + $19 = (0 - $0) | 0; + $20 = (0 - $2) | 0; + $21 = (0 - $4) | 0; + $22 = (0 - $6) | 0; + $23 = (0 - $8) | 0; + $24 = (0 - $10) | 0; + $25 = (0 - $12) | 0; + $26 = (0 - $14) | 0; + $27 = (0 - $16) | 0; + $28 = (0 - $18) | 0; + HEAP32[$h >> 2] = $19; + $29 = ($h + 4) | 0; + HEAP32[$29 >> 2] = $20; + $30 = ($h + 8) | 0; + HEAP32[$30 >> 2] = $21; + $31 = ($h + 12) | 0; + HEAP32[$31 >> 2] = $22; + $32 = ($h + 16) | 0; + HEAP32[$32 >> 2] = $23; + $33 = ($h + 20) | 0; + HEAP32[$33 >> 2] = $24; + $34 = ($h + 24) | 0; + HEAP32[$34 >> 2] = $25; + $35 = ($h + 28) | 0; + HEAP32[$35 >> 2] = $26; + $36 = ($h + 32) | 0; + HEAP32[$36 >> 2] = $27; + $37 = ($h + 36) | 0; + HEAP32[$37 >> 2] = $28; + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_fe_pow22523($out, $z) { + $out = $out | 0; + $z = $z | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $exitcond = 0, + $exitcond10 = 0, + $exitcond11 = 0, + $i$74 = 0, + $i$83 = 0, + $i$92 = 0, + $t0 = 0, + $t1 = 0, + $t2 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 128) | 0; + $t0 = (sp + 80) | 0; + $t1 = (sp + 40) | 0; + $t2 = sp; + _crypto_sign_ed25519_ref10_fe_sq($t0, $z); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t0); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_mul($t1, $z, $t1); + _crypto_sign_ed25519_ref10_fe_mul($t0, $t0, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t0, $t0); + _crypto_sign_ed25519_ref10_fe_mul($t0, $t1, $t0); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t0); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_mul($t0, $t1, $t0); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t0); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_mul($t1, $t1, $t0); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + _crypto_sign_ed25519_ref10_fe_mul($t1, $t2, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + _crypto_sign_ed25519_ref10_fe_mul($t0, $t1, $t0); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t0); + $i$74 = 1; + while (1) { + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + $0 = ($i$74 + 1) | 0; + $exitcond11 = ($0 | 0) == 50; + if ($exitcond11) { + break; + } else { + $i$74 = $0; + } + } + _crypto_sign_ed25519_ref10_fe_mul($t1, $t1, $t0); + _crypto_sign_ed25519_ref10_fe_sq($t2, $t1); + $i$83 = 1; + while (1) { + _crypto_sign_ed25519_ref10_fe_sq($t2, $t2); + $1 = ($i$83 + 1) | 0; + $exitcond10 = ($1 | 0) == 100; + if ($exitcond10) { + break; + } else { + $i$83 = $1; + } + } + _crypto_sign_ed25519_ref10_fe_mul($t1, $t2, $t1); + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + $i$92 = 1; + while (1) { + _crypto_sign_ed25519_ref10_fe_sq($t1, $t1); + $2 = ($i$92 + 1) | 0; + $exitcond = ($2 | 0) == 50; + if ($exitcond) { + break; + } else { + $i$92 = $2; + } + } + _crypto_sign_ed25519_ref10_fe_mul($t0, $t1, $t0); + _crypto_sign_ed25519_ref10_fe_sq($t0, $t0); + _crypto_sign_ed25519_ref10_fe_sq($t0, $t0); + _crypto_sign_ed25519_ref10_fe_mul($out, $t0, $z); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_fe_sq($h, $f) { + $h = $h | 0; + $f = $f | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $101 = 0, + $102 = 0, + $103 = 0, + $104 = 0, + $105 = 0, + $106 = 0, + $107 = 0, + $108 = 0, + $109 = 0, + $11 = 0, + $110 = 0, + $111 = 0, + $112 = 0, + $113 = 0, + $114 = 0, + $115 = 0; + var $116 = 0, + $117 = 0, + $118 = 0, + $119 = 0, + $12 = 0, + $120 = 0, + $121 = 0, + $122 = 0, + $123 = 0, + $124 = 0, + $125 = 0, + $126 = 0, + $127 = 0, + $128 = 0, + $129 = 0, + $13 = 0, + $130 = 0, + $131 = 0, + $132 = 0, + $133 = 0; + var $134 = 0, + $135 = 0, + $136 = 0, + $137 = 0, + $138 = 0, + $139 = 0, + $14 = 0, + $140 = 0, + $141 = 0, + $142 = 0, + $143 = 0, + $144 = 0, + $145 = 0, + $146 = 0, + $147 = 0, + $148 = 0, + $149 = 0, + $15 = 0, + $150 = 0, + $151 = 0; + var $152 = 0, + $153 = 0, + $154 = 0, + $155 = 0, + $156 = 0, + $157 = 0, + $158 = 0, + $159 = 0, + $16 = 0, + $160 = 0, + $161 = 0, + $162 = 0, + $163 = 0, + $164 = 0, + $165 = 0, + $166 = 0, + $167 = 0, + $168 = 0, + $169 = 0, + $17 = 0; + var $170 = 0, + $171 = 0, + $172 = 0, + $173 = 0, + $174 = 0, + $175 = 0, + $176 = 0, + $177 = 0, + $178 = 0, + $179 = 0, + $18 = 0, + $180 = 0, + $181 = 0, + $182 = 0, + $183 = 0, + $184 = 0, + $185 = 0, + $186 = 0, + $187 = 0, + $188 = 0; + var $189 = 0, + $19 = 0, + $190 = 0, + $191 = 0, + $192 = 0, + $193 = 0, + $194 = 0, + $195 = 0, + $196 = 0, + $197 = 0, + $198 = 0, + $199 = 0, + $2 = 0, + $20 = 0, + $200 = 0, + $201 = 0, + $202 = 0, + $203 = 0, + $204 = 0, + $205 = 0; + var $206 = 0, + $207 = 0, + $208 = 0, + $209 = 0, + $21 = 0, + $210 = 0, + $211 = 0, + $212 = 0, + $213 = 0, + $214 = 0, + $215 = 0, + $216 = 0, + $217 = 0, + $218 = 0, + $219 = 0, + $22 = 0, + $220 = 0, + $221 = 0, + $222 = 0, + $223 = 0; + var $224 = 0, + $225 = 0, + $226 = 0, + $227 = 0, + $228 = 0, + $229 = 0, + $23 = 0, + $230 = 0, + $231 = 0, + $232 = 0, + $233 = 0, + $234 = 0, + $235 = 0, + $236 = 0, + $237 = 0, + $238 = 0, + $239 = 0, + $24 = 0, + $240 = 0, + $241 = 0; + var $242 = 0, + $243 = 0, + $244 = 0, + $245 = 0, + $246 = 0, + $247 = 0, + $248 = 0, + $249 = 0, + $25 = 0, + $250 = 0, + $251 = 0, + $252 = 0, + $253 = 0, + $254 = 0, + $255 = 0, + $256 = 0, + $257 = 0, + $258 = 0, + $259 = 0, + $26 = 0; + var $260 = 0, + $261 = 0, + $262 = 0, + $263 = 0, + $264 = 0, + $265 = 0, + $266 = 0, + $267 = 0, + $268 = 0, + $269 = 0, + $27 = 0, + $270 = 0, + $271 = 0, + $272 = 0, + $273 = 0, + $274 = 0, + $275 = 0, + $276 = 0, + $277 = 0, + $278 = 0; + var $279 = 0, + $28 = 0, + $280 = 0, + $281 = 0, + $282 = 0, + $283 = 0, + $284 = 0, + $285 = 0, + $286 = 0, + $287 = 0, + $288 = 0, + $289 = 0, + $29 = 0, + $290 = 0, + $291 = 0, + $292 = 0, + $293 = 0, + $294 = 0, + $295 = 0, + $296 = 0; + var $297 = 0, + $298 = 0, + $299 = 0, + $3 = 0, + $30 = 0, + $300 = 0, + $301 = 0, + $302 = 0, + $303 = 0, + $304 = 0, + $305 = 0, + $306 = 0, + $307 = 0, + $308 = 0, + $309 = 0, + $31 = 0, + $310 = 0, + $311 = 0, + $312 = 0, + $313 = 0; + var $314 = 0, + $315 = 0, + $316 = 0, + $317 = 0, + $318 = 0, + $319 = 0, + $32 = 0, + $320 = 0, + $321 = 0, + $322 = 0, + $323 = 0, + $324 = 0, + $325 = 0, + $326 = 0, + $327 = 0, + $328 = 0, + $329 = 0, + $33 = 0, + $330 = 0, + $331 = 0; + var $332 = 0, + $333 = 0, + $334 = 0, + $335 = 0, + $336 = 0, + $337 = 0, + $338 = 0, + $339 = 0, + $34 = 0, + $340 = 0, + $341 = 0, + $342 = 0, + $343 = 0, + $344 = 0, + $345 = 0, + $346 = 0, + $347 = 0, + $348 = 0, + $349 = 0, + $35 = 0; + var $350 = 0, + $351 = 0, + $352 = 0, + $353 = 0, + $354 = 0, + $355 = 0, + $356 = 0, + $357 = 0, + $358 = 0, + $359 = 0, + $36 = 0, + $360 = 0, + $361 = 0, + $362 = 0, + $363 = 0, + $364 = 0, + $365 = 0, + $366 = 0, + $367 = 0, + $368 = 0; + var $369 = 0, + $37 = 0, + $370 = 0, + $371 = 0, + $372 = 0, + $373 = 0, + $374 = 0, + $375 = 0, + $376 = 0, + $377 = 0, + $378 = 0, + $379 = 0, + $38 = 0, + $380 = 0, + $381 = 0, + $382 = 0, + $383 = 0, + $384 = 0, + $385 = 0, + $386 = 0; + var $387 = 0, + $388 = 0, + $389 = 0, + $39 = 0, + $390 = 0, + $391 = 0, + $392 = 0, + $393 = 0, + $394 = 0, + $395 = 0, + $396 = 0, + $397 = 0, + $398 = 0, + $399 = 0, + $4 = 0, + $40 = 0, + $400 = 0, + $401 = 0, + $402 = 0, + $403 = 0; + var $404 = 0, + $405 = 0, + $406 = 0, + $407 = 0, + $408 = 0, + $41 = 0, + $42 = 0, + $43 = 0, + $44 = 0, + $45 = 0, + $46 = 0, + $47 = 0, + $48 = 0, + $49 = 0, + $5 = 0, + $50 = 0, + $51 = 0, + $52 = 0, + $53 = 0, + $54 = 0; + var $55 = 0, + $56 = 0, + $57 = 0, + $58 = 0, + $59 = 0, + $6 = 0, + $60 = 0, + $61 = 0, + $62 = 0, + $63 = 0, + $64 = 0, + $65 = 0, + $66 = 0, + $67 = 0, + $68 = 0, + $69 = 0, + $7 = 0, + $70 = 0, + $71 = 0, + $72 = 0; + var $73 = 0, + $74 = 0, + $75 = 0, + $76 = 0, + $77 = 0, + $78 = 0, + $79 = 0, + $8 = 0, + $80 = 0, + $81 = 0, + $82 = 0, + $83 = 0, + $84 = 0, + $85 = 0, + $86 = 0, + $87 = 0, + $88 = 0, + $89 = 0, + $9 = 0, + $90 = 0; + var $91 = 0, + $92 = 0, + $93 = 0, + $94 = 0, + $95 = 0, + $96 = 0, + $97 = 0, + $98 = 0, + $99 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP32[$f >> 2] | 0; + $1 = ($f + 4) | 0; + $2 = HEAP32[$1 >> 2] | 0; + $3 = ($f + 8) | 0; + $4 = HEAP32[$3 >> 2] | 0; + $5 = ($f + 12) | 0; + $6 = HEAP32[$5 >> 2] | 0; + $7 = ($f + 16) | 0; + $8 = HEAP32[$7 >> 2] | 0; + $9 = ($f + 20) | 0; + $10 = HEAP32[$9 >> 2] | 0; + $11 = ($f + 24) | 0; + $12 = HEAP32[$11 >> 2] | 0; + $13 = ($f + 28) | 0; + $14 = HEAP32[$13 >> 2] | 0; + $15 = ($f + 32) | 0; + $16 = HEAP32[$15 >> 2] | 0; + $17 = ($f + 36) | 0; + $18 = HEAP32[$17 >> 2] | 0; + $19 = $0 << 1; + $20 = $2 << 1; + $21 = $4 << 1; + $22 = $6 << 1; + $23 = $8 << 1; + $24 = $10 << 1; + $25 = $12 << 1; + $26 = $14 << 1; + $27 = ($10 * 38) | 0; + $28 = ($12 * 19) | 0; + $29 = ($14 * 38) | 0; + $30 = ($16 * 19) | 0; + $31 = ($18 * 38) | 0; + $32 = ($0 | 0) < 0; + $33 = ($32 << 31) >> 31; + $34 = ___muldi3($0 | 0, $33 | 0, $0 | 0, $33 | 0) | 0; + $35 = tempRet0; + $36 = ($19 | 0) < 0; + $37 = ($36 << 31) >> 31; + $38 = ($2 | 0) < 0; + $39 = ($38 << 31) >> 31; + $40 = ___muldi3($19 | 0, $37 | 0, $2 | 0, $39 | 0) | 0; + $41 = tempRet0; + $42 = ($4 | 0) < 0; + $43 = ($42 << 31) >> 31; + $44 = ___muldi3($4 | 0, $43 | 0, $19 | 0, $37 | 0) | 0; + $45 = tempRet0; + $46 = ($6 | 0) < 0; + $47 = ($46 << 31) >> 31; + $48 = ___muldi3($6 | 0, $47 | 0, $19 | 0, $37 | 0) | 0; + $49 = tempRet0; + $50 = ($8 | 0) < 0; + $51 = ($50 << 31) >> 31; + $52 = ___muldi3($8 | 0, $51 | 0, $19 | 0, $37 | 0) | 0; + $53 = tempRet0; + $54 = ($10 | 0) < 0; + $55 = ($54 << 31) >> 31; + $56 = ___muldi3($10 | 0, $55 | 0, $19 | 0, $37 | 0) | 0; + $57 = tempRet0; + $58 = ($12 | 0) < 0; + $59 = ($58 << 31) >> 31; + $60 = ___muldi3($12 | 0, $59 | 0, $19 | 0, $37 | 0) | 0; + $61 = tempRet0; + $62 = ($14 | 0) < 0; + $63 = ($62 << 31) >> 31; + $64 = ___muldi3($14 | 0, $63 | 0, $19 | 0, $37 | 0) | 0; + $65 = tempRet0; + $66 = ($16 | 0) < 0; + $67 = ($66 << 31) >> 31; + $68 = ___muldi3($16 | 0, $67 | 0, $19 | 0, $37 | 0) | 0; + $69 = tempRet0; + $70 = ($18 | 0) < 0; + $71 = ($70 << 31) >> 31; + $72 = ___muldi3($18 | 0, $71 | 0, $19 | 0, $37 | 0) | 0; + $73 = tempRet0; + $74 = ($20 | 0) < 0; + $75 = ($74 << 31) >> 31; + $76 = ___muldi3($20 | 0, $75 | 0, $2 | 0, $39 | 0) | 0; + $77 = tempRet0; + $78 = ___muldi3($20 | 0, $75 | 0, $4 | 0, $43 | 0) | 0; + $79 = tempRet0; + $80 = ($22 | 0) < 0; + $81 = ($80 << 31) >> 31; + $82 = ___muldi3($22 | 0, $81 | 0, $20 | 0, $75 | 0) | 0; + $83 = tempRet0; + $84 = ___muldi3($8 | 0, $51 | 0, $20 | 0, $75 | 0) | 0; + $85 = tempRet0; + $86 = ($24 | 0) < 0; + $87 = ($86 << 31) >> 31; + $88 = ___muldi3($24 | 0, $87 | 0, $20 | 0, $75 | 0) | 0; + $89 = tempRet0; + $90 = ___muldi3($12 | 0, $59 | 0, $20 | 0, $75 | 0) | 0; + $91 = tempRet0; + $92 = ($26 | 0) < 0; + $93 = ($92 << 31) >> 31; + $94 = ___muldi3($26 | 0, $93 | 0, $20 | 0, $75 | 0) | 0; + $95 = tempRet0; + $96 = ___muldi3($16 | 0, $67 | 0, $20 | 0, $75 | 0) | 0; + $97 = tempRet0; + $98 = ($31 | 0) < 0; + $99 = ($98 << 31) >> 31; + $100 = ___muldi3($31 | 0, $99 | 0, $20 | 0, $75 | 0) | 0; + $101 = tempRet0; + $102 = ___muldi3($4 | 0, $43 | 0, $4 | 0, $43 | 0) | 0; + $103 = tempRet0; + $104 = ($21 | 0) < 0; + $105 = ($104 << 31) >> 31; + $106 = ___muldi3($21 | 0, $105 | 0, $6 | 0, $47 | 0) | 0; + $107 = tempRet0; + $108 = ___muldi3($8 | 0, $51 | 0, $21 | 0, $105 | 0) | 0; + $109 = tempRet0; + $110 = ___muldi3($10 | 0, $55 | 0, $21 | 0, $105 | 0) | 0; + $111 = tempRet0; + $112 = ___muldi3($12 | 0, $59 | 0, $21 | 0, $105 | 0) | 0; + $113 = tempRet0; + $114 = ___muldi3($14 | 0, $63 | 0, $21 | 0, $105 | 0) | 0; + $115 = tempRet0; + $116 = ($30 | 0) < 0; + $117 = ($116 << 31) >> 31; + $118 = ___muldi3($30 | 0, $117 | 0, $21 | 0, $105 | 0) | 0; + $119 = tempRet0; + $120 = ___muldi3($31 | 0, $99 | 0, $4 | 0, $43 | 0) | 0; + $121 = tempRet0; + $122 = ___muldi3($22 | 0, $81 | 0, $6 | 0, $47 | 0) | 0; + $123 = tempRet0; + $124 = ___muldi3($22 | 0, $81 | 0, $8 | 0, $51 | 0) | 0; + $125 = tempRet0; + $126 = ___muldi3($24 | 0, $87 | 0, $22 | 0, $81 | 0) | 0; + $127 = tempRet0; + $128 = ___muldi3($12 | 0, $59 | 0, $22 | 0, $81 | 0) | 0; + $129 = tempRet0; + $130 = ($29 | 0) < 0; + $131 = ($130 << 31) >> 31; + $132 = ___muldi3($29 | 0, $131 | 0, $22 | 0, $81 | 0) | 0; + $133 = tempRet0; + $134 = ___muldi3($30 | 0, $117 | 0, $22 | 0, $81 | 0) | 0; + $135 = tempRet0; + $136 = ___muldi3($31 | 0, $99 | 0, $22 | 0, $81 | 0) | 0; + $137 = tempRet0; + $138 = ___muldi3($8 | 0, $51 | 0, $8 | 0, $51 | 0) | 0; + $139 = tempRet0; + $140 = ($23 | 0) < 0; + $141 = ($140 << 31) >> 31; + $142 = ___muldi3($23 | 0, $141 | 0, $10 | 0, $55 | 0) | 0; + $143 = tempRet0; + $144 = ($28 | 0) < 0; + $145 = ($144 << 31) >> 31; + $146 = ___muldi3($28 | 0, $145 | 0, $23 | 0, $141 | 0) | 0; + $147 = tempRet0; + $148 = ___muldi3($29 | 0, $131 | 0, $8 | 0, $51 | 0) | 0; + $149 = tempRet0; + $150 = ___muldi3($30 | 0, $117 | 0, $23 | 0, $141 | 0) | 0; + $151 = tempRet0; + $152 = ___muldi3($31 | 0, $99 | 0, $8 | 0, $51 | 0) | 0; + $153 = tempRet0; + $154 = ($27 | 0) < 0; + $155 = ($154 << 31) >> 31; + $156 = ___muldi3($27 | 0, $155 | 0, $10 | 0, $55 | 0) | 0; + $157 = tempRet0; + $158 = ___muldi3($28 | 0, $145 | 0, $24 | 0, $87 | 0) | 0; + $159 = tempRet0; + $160 = ___muldi3($29 | 0, $131 | 0, $24 | 0, $87 | 0) | 0; + $161 = tempRet0; + $162 = ___muldi3($30 | 0, $117 | 0, $24 | 0, $87 | 0) | 0; + $163 = tempRet0; + $164 = ___muldi3($31 | 0, $99 | 0, $24 | 0, $87 | 0) | 0; + $165 = tempRet0; + $166 = ___muldi3($28 | 0, $145 | 0, $12 | 0, $59 | 0) | 0; + $167 = tempRet0; + $168 = ___muldi3($29 | 0, $131 | 0, $12 | 0, $59 | 0) | 0; + $169 = tempRet0; + $170 = ($25 | 0) < 0; + $171 = ($170 << 31) >> 31; + $172 = ___muldi3($30 | 0, $117 | 0, $25 | 0, $171 | 0) | 0; + $173 = tempRet0; + $174 = ___muldi3($31 | 0, $99 | 0, $12 | 0, $59 | 0) | 0; + $175 = tempRet0; + $176 = ___muldi3($29 | 0, $131 | 0, $14 | 0, $63 | 0) | 0; + $177 = tempRet0; + $178 = ___muldi3($30 | 0, $117 | 0, $26 | 0, $93 | 0) | 0; + $179 = tempRet0; + $180 = ___muldi3($31 | 0, $99 | 0, $26 | 0, $93 | 0) | 0; + $181 = tempRet0; + $182 = ___muldi3($30 | 0, $117 | 0, $16 | 0, $67 | 0) | 0; + $183 = tempRet0; + $184 = ___muldi3($31 | 0, $99 | 0, $16 | 0, $67 | 0) | 0; + $185 = tempRet0; + $186 = ___muldi3($31 | 0, $99 | 0, $18 | 0, $71 | 0) | 0; + $187 = tempRet0; + $188 = _i64Add($156 | 0, $157 | 0, $34 | 0, $35 | 0) | 0; + $189 = tempRet0; + $190 = _i64Add($188 | 0, $189 | 0, $146 | 0, $147 | 0) | 0; + $191 = tempRet0; + $192 = _i64Add($190 | 0, $191 | 0, $132 | 0, $133 | 0) | 0; + $193 = tempRet0; + $194 = _i64Add($192 | 0, $193 | 0, $118 | 0, $119 | 0) | 0; + $195 = tempRet0; + $196 = _i64Add($194 | 0, $195 | 0, $100 | 0, $101 | 0) | 0; + $197 = tempRet0; + $198 = _i64Add($44 | 0, $45 | 0, $76 | 0, $77 | 0) | 0; + $199 = tempRet0; + $200 = _i64Add($48 | 0, $49 | 0, $78 | 0, $79 | 0) | 0; + $201 = tempRet0; + $202 = _i64Add($82 | 0, $83 | 0, $102 | 0, $103 | 0) | 0; + $203 = tempRet0; + $204 = _i64Add($202 | 0, $203 | 0, $52 | 0, $53 | 0) | 0; + $205 = tempRet0; + $206 = _i64Add($204 | 0, $205 | 0, $176 | 0, $177 | 0) | 0; + $207 = tempRet0; + $208 = _i64Add($206 | 0, $207 | 0, $172 | 0, $173 | 0) | 0; + $209 = tempRet0; + $210 = _i64Add($208 | 0, $209 | 0, $164 | 0, $165 | 0) | 0; + $211 = tempRet0; + $212 = _i64Add($196 | 0, $197 | 0, 33554432, 0) | 0; + $213 = tempRet0; + $214 = _bitshift64Ashr($212 | 0, $213 | 0, 26) | 0; + $215 = tempRet0; + $216 = _i64Add($158 | 0, $159 | 0, $40 | 0, $41 | 0) | 0; + $217 = tempRet0; + $218 = _i64Add($216 | 0, $217 | 0, $148 | 0, $149 | 0) | 0; + $219 = tempRet0; + $220 = _i64Add($218 | 0, $219 | 0, $134 | 0, $135 | 0) | 0; + $221 = tempRet0; + $222 = _i64Add($220 | 0, $221 | 0, $120 | 0, $121 | 0) | 0; + $223 = tempRet0; + $224 = _i64Add($222 | 0, $223 | 0, $214 | 0, $215 | 0) | 0; + $225 = tempRet0; + $226 = _bitshift64Shl($214 | 0, $215 | 0, 26) | 0; + $227 = tempRet0; + $228 = _i64Subtract($196 | 0, $197 | 0, $226 | 0, $227 | 0) | 0; + $229 = tempRet0; + $230 = _i64Add($210 | 0, $211 | 0, 33554432, 0) | 0; + $231 = tempRet0; + $232 = _bitshift64Ashr($230 | 0, $231 | 0, 26) | 0; + $233 = tempRet0; + $234 = _i64Add($84 | 0, $85 | 0, $106 | 0, $107 | 0) | 0; + $235 = tempRet0; + $236 = _i64Add($234 | 0, $235 | 0, $56 | 0, $57 | 0) | 0; + $237 = tempRet0; + $238 = _i64Add($236 | 0, $237 | 0, $178 | 0, $179 | 0) | 0; + $239 = tempRet0; + $240 = _i64Add($238 | 0, $239 | 0, $174 | 0, $175 | 0) | 0; + $241 = tempRet0; + $242 = _i64Add($240 | 0, $241 | 0, $232 | 0, $233 | 0) | 0; + $243 = tempRet0; + $244 = _bitshift64Shl($232 | 0, $233 | 0, 26) | 0; + $245 = tempRet0; + $246 = _i64Subtract($210 | 0, $211 | 0, $244 | 0, $245 | 0) | 0; + $247 = tempRet0; + $248 = _i64Add($224 | 0, $225 | 0, 16777216, 0) | 0; + $249 = tempRet0; + $250 = _bitshift64Ashr($248 | 0, $249 | 0, 25) | 0; + $251 = tempRet0; + $252 = _i64Add($198 | 0, $199 | 0, $166 | 0, $167 | 0) | 0; + $253 = tempRet0; + $254 = _i64Add($252 | 0, $253 | 0, $160 | 0, $161 | 0) | 0; + $255 = tempRet0; + $256 = _i64Add($254 | 0, $255 | 0, $150 | 0, $151 | 0) | 0; + $257 = tempRet0; + $258 = _i64Add($256 | 0, $257 | 0, $136 | 0, $137 | 0) | 0; + $259 = tempRet0; + $260 = _i64Add($258 | 0, $259 | 0, $250 | 0, $251 | 0) | 0; + $261 = tempRet0; + $262 = _bitshift64Shl($250 | 0, $251 | 0, 25) | 0; + $263 = tempRet0; + $264 = _i64Subtract($224 | 0, $225 | 0, $262 | 0, $263 | 0) | 0; + $265 = tempRet0; + $266 = _i64Add($242 | 0, $243 | 0, 16777216, 0) | 0; + $267 = tempRet0; + $268 = _bitshift64Ashr($266 | 0, $267 | 0, 25) | 0; + $269 = tempRet0; + $270 = _i64Add($122 | 0, $123 | 0, $108 | 0, $109 | 0) | 0; + $271 = tempRet0; + $272 = _i64Add($270 | 0, $271 | 0, $88 | 0, $89 | 0) | 0; + $273 = tempRet0; + $274 = _i64Add($272 | 0, $273 | 0, $60 | 0, $61 | 0) | 0; + $275 = tempRet0; + $276 = _i64Add($274 | 0, $275 | 0, $182 | 0, $183 | 0) | 0; + $277 = tempRet0; + $278 = _i64Add($276 | 0, $277 | 0, $180 | 0, $181 | 0) | 0; + $279 = tempRet0; + $280 = _i64Add($278 | 0, $279 | 0, $268 | 0, $269 | 0) | 0; + $281 = tempRet0; + $282 = _bitshift64Shl($268 | 0, $269 | 0, 25) | 0; + $283 = tempRet0; + $284 = _i64Subtract($242 | 0, $243 | 0, $282 | 0, $283 | 0) | 0; + $285 = tempRet0; + $286 = _i64Add($260 | 0, $261 | 0, 33554432, 0) | 0; + $287 = tempRet0; + $288 = _bitshift64Ashr($286 | 0, $287 | 0, 26) | 0; + $289 = tempRet0; + $290 = _i64Add($200 | 0, $201 | 0, $168 | 0, $169 | 0) | 0; + $291 = tempRet0; + $292 = _i64Add($290 | 0, $291 | 0, $162 | 0, $163 | 0) | 0; + $293 = tempRet0; + $294 = _i64Add($292 | 0, $293 | 0, $152 | 0, $153 | 0) | 0; + $295 = tempRet0; + $296 = _i64Add($294 | 0, $295 | 0, $288 | 0, $289 | 0) | 0; + $297 = tempRet0; + $298 = _bitshift64Shl($288 | 0, $289 | 0, 26) | 0; + $299 = tempRet0; + $300 = _i64Subtract($260 | 0, $261 | 0, $298 | 0, $299 | 0) | 0; + $301 = tempRet0; + $302 = _i64Add($280 | 0, $281 | 0, 33554432, 0) | 0; + $303 = tempRet0; + $304 = _bitshift64Ashr($302 | 0, $303 | 0, 26) | 0; + $305 = tempRet0; + $306 = _i64Add($110 | 0, $111 | 0, $124 | 0, $125 | 0) | 0; + $307 = tempRet0; + $308 = _i64Add($306 | 0, $307 | 0, $90 | 0, $91 | 0) | 0; + $309 = tempRet0; + $310 = _i64Add($308 | 0, $309 | 0, $64 | 0, $65 | 0) | 0; + $311 = tempRet0; + $312 = _i64Add($310 | 0, $311 | 0, $184 | 0, $185 | 0) | 0; + $313 = tempRet0; + $314 = _i64Add($312 | 0, $313 | 0, $304 | 0, $305 | 0) | 0; + $315 = tempRet0; + $316 = _bitshift64Shl($304 | 0, $305 | 0, 26) | 0; + $317 = tempRet0; + $318 = _i64Subtract($280 | 0, $281 | 0, $316 | 0, $317 | 0) | 0; + $319 = tempRet0; + $320 = _i64Add($296 | 0, $297 | 0, 16777216, 0) | 0; + $321 = tempRet0; + $322 = _bitshift64Ashr($320 | 0, $321 | 0, 25) | 0; + $323 = tempRet0; + $324 = _i64Add($322 | 0, $323 | 0, $246 | 0, $247 | 0) | 0; + $325 = tempRet0; + $326 = _bitshift64Shl($322 | 0, $323 | 0, 25) | 0; + $327 = tempRet0; + $328 = _i64Subtract($296 | 0, $297 | 0, $326 | 0, $327 | 0) | 0; + $329 = tempRet0; + $330 = _i64Add($314 | 0, $315 | 0, 16777216, 0) | 0; + $331 = tempRet0; + $332 = _bitshift64Ashr($330 | 0, $331 | 0, 25) | 0; + $333 = tempRet0; + $334 = _i64Add($112 | 0, $113 | 0, $138 | 0, $139 | 0) | 0; + $335 = tempRet0; + $336 = _i64Add($334 | 0, $335 | 0, $126 | 0, $127 | 0) | 0; + $337 = tempRet0; + $338 = _i64Add($336 | 0, $337 | 0, $94 | 0, $95 | 0) | 0; + $339 = tempRet0; + $340 = _i64Add($338 | 0, $339 | 0, $68 | 0, $69 | 0) | 0; + $341 = tempRet0; + $342 = _i64Add($340 | 0, $341 | 0, $186 | 0, $187 | 0) | 0; + $343 = tempRet0; + $344 = _i64Add($342 | 0, $343 | 0, $332 | 0, $333 | 0) | 0; + $345 = tempRet0; + $346 = _bitshift64Shl($332 | 0, $333 | 0, 25) | 0; + $347 = tempRet0; + $348 = _i64Subtract($314 | 0, $315 | 0, $346 | 0, $347 | 0) | 0; + $349 = tempRet0; + $350 = _i64Add($324 | 0, $325 | 0, 33554432, 0) | 0; + $351 = tempRet0; + $352 = _bitshift64Ashr($350 | 0, $351 | 0, 26) | 0; + $353 = tempRet0; + $354 = _i64Add($284 | 0, $285 | 0, $352 | 0, $353 | 0) | 0; + $355 = tempRet0; + $356 = _bitshift64Shl($352 | 0, $353 | 0, 26) | 0; + $357 = tempRet0; + $358 = _i64Subtract($324 | 0, $325 | 0, $356 | 0, $357 | 0) | 0; + $359 = tempRet0; + $360 = _i64Add($344 | 0, $345 | 0, 33554432, 0) | 0; + $361 = tempRet0; + $362 = _bitshift64Ashr($360 | 0, $361 | 0, 26) | 0; + $363 = tempRet0; + $364 = _i64Add($128 | 0, $129 | 0, $142 | 0, $143 | 0) | 0; + $365 = tempRet0; + $366 = _i64Add($364 | 0, $365 | 0, $114 | 0, $115 | 0) | 0; + $367 = tempRet0; + $368 = _i64Add($366 | 0, $367 | 0, $96 | 0, $97 | 0) | 0; + $369 = tempRet0; + $370 = _i64Add($368 | 0, $369 | 0, $72 | 0, $73 | 0) | 0; + $371 = tempRet0; + $372 = _i64Add($370 | 0, $371 | 0, $362 | 0, $363 | 0) | 0; + $373 = tempRet0; + $374 = _bitshift64Shl($362 | 0, $363 | 0, 26) | 0; + $375 = tempRet0; + $376 = _i64Subtract($344 | 0, $345 | 0, $374 | 0, $375 | 0) | 0; + $377 = tempRet0; + $378 = _i64Add($372 | 0, $373 | 0, 16777216, 0) | 0; + $379 = tempRet0; + $380 = _bitshift64Ashr($378 | 0, $379 | 0, 25) | 0; + $381 = tempRet0; + $382 = ___muldi3($380 | 0, $381 | 0, 19, 0) | 0; + $383 = tempRet0; + $384 = _i64Add($382 | 0, $383 | 0, $228 | 0, $229 | 0) | 0; + $385 = tempRet0; + $386 = _bitshift64Shl($380 | 0, $381 | 0, 25) | 0; + $387 = tempRet0; + $388 = _i64Subtract($372 | 0, $373 | 0, $386 | 0, $387 | 0) | 0; + $389 = tempRet0; + $390 = _i64Add($384 | 0, $385 | 0, 33554432, 0) | 0; + $391 = tempRet0; + $392 = _bitshift64Ashr($390 | 0, $391 | 0, 26) | 0; + $393 = tempRet0; + $394 = _i64Add($264 | 0, $265 | 0, $392 | 0, $393 | 0) | 0; + $395 = tempRet0; + $396 = _bitshift64Shl($392 | 0, $393 | 0, 26) | 0; + $397 = tempRet0; + $398 = _i64Subtract($384 | 0, $385 | 0, $396 | 0, $397 | 0) | 0; + $399 = tempRet0; + HEAP32[$h >> 2] = $398; + $400 = ($h + 4) | 0; + HEAP32[$400 >> 2] = $394; + $401 = ($h + 8) | 0; + HEAP32[$401 >> 2] = $300; + $402 = ($h + 12) | 0; + HEAP32[$402 >> 2] = $328; + $403 = ($h + 16) | 0; + HEAP32[$403 >> 2] = $358; + $404 = ($h + 20) | 0; + HEAP32[$404 >> 2] = $354; + $405 = ($h + 24) | 0; + HEAP32[$405 >> 2] = $318; + $406 = ($h + 28) | 0; + HEAP32[$406 >> 2] = $348; + $407 = ($h + 32) | 0; + HEAP32[$407 >> 2] = $376; + $408 = ($h + 36) | 0; + HEAP32[$408 >> 2] = $388; + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_fe_sq2($h, $f) { + $h = $h | 0; + $f = $f | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $101 = 0, + $102 = 0, + $103 = 0, + $104 = 0, + $105 = 0, + $106 = 0, + $107 = 0, + $108 = 0, + $109 = 0, + $11 = 0, + $110 = 0, + $111 = 0, + $112 = 0, + $113 = 0, + $114 = 0, + $115 = 0; + var $116 = 0, + $117 = 0, + $118 = 0, + $119 = 0, + $12 = 0, + $120 = 0, + $121 = 0, + $122 = 0, + $123 = 0, + $124 = 0, + $125 = 0, + $126 = 0, + $127 = 0, + $128 = 0, + $129 = 0, + $13 = 0, + $130 = 0, + $131 = 0, + $132 = 0, + $133 = 0; + var $134 = 0, + $135 = 0, + $136 = 0, + $137 = 0, + $138 = 0, + $139 = 0, + $14 = 0, + $140 = 0, + $141 = 0, + $142 = 0, + $143 = 0, + $144 = 0, + $145 = 0, + $146 = 0, + $147 = 0, + $148 = 0, + $149 = 0, + $15 = 0, + $150 = 0, + $151 = 0; + var $152 = 0, + $153 = 0, + $154 = 0, + $155 = 0, + $156 = 0, + $157 = 0, + $158 = 0, + $159 = 0, + $16 = 0, + $160 = 0, + $161 = 0, + $162 = 0, + $163 = 0, + $164 = 0, + $165 = 0, + $166 = 0, + $167 = 0, + $168 = 0, + $169 = 0, + $17 = 0; + var $170 = 0, + $171 = 0, + $172 = 0, + $173 = 0, + $174 = 0, + $175 = 0, + $176 = 0, + $177 = 0, + $178 = 0, + $179 = 0, + $18 = 0, + $180 = 0, + $181 = 0, + $182 = 0, + $183 = 0, + $184 = 0, + $185 = 0, + $186 = 0, + $187 = 0, + $188 = 0; + var $189 = 0, + $19 = 0, + $190 = 0, + $191 = 0, + $192 = 0, + $193 = 0, + $194 = 0, + $195 = 0, + $196 = 0, + $197 = 0, + $198 = 0, + $199 = 0, + $2 = 0, + $20 = 0, + $200 = 0, + $201 = 0, + $202 = 0, + $203 = 0, + $204 = 0, + $205 = 0; + var $206 = 0, + $207 = 0, + $208 = 0, + $209 = 0, + $21 = 0, + $210 = 0, + $211 = 0, + $212 = 0, + $213 = 0, + $214 = 0, + $215 = 0, + $216 = 0, + $217 = 0, + $218 = 0, + $219 = 0, + $22 = 0, + $220 = 0, + $221 = 0, + $222 = 0, + $223 = 0; + var $224 = 0, + $225 = 0, + $226 = 0, + $227 = 0, + $228 = 0, + $229 = 0, + $23 = 0, + $230 = 0, + $231 = 0, + $232 = 0, + $233 = 0, + $234 = 0, + $235 = 0, + $236 = 0, + $237 = 0, + $238 = 0, + $239 = 0, + $24 = 0, + $240 = 0, + $241 = 0; + var $242 = 0, + $243 = 0, + $244 = 0, + $245 = 0, + $246 = 0, + $247 = 0, + $248 = 0, + $249 = 0, + $25 = 0, + $250 = 0, + $251 = 0, + $252 = 0, + $253 = 0, + $254 = 0, + $255 = 0, + $256 = 0, + $257 = 0, + $258 = 0, + $259 = 0, + $26 = 0; + var $260 = 0, + $261 = 0, + $262 = 0, + $263 = 0, + $264 = 0, + $265 = 0, + $266 = 0, + $267 = 0, + $268 = 0, + $269 = 0, + $27 = 0, + $270 = 0, + $271 = 0, + $272 = 0, + $273 = 0, + $274 = 0, + $275 = 0, + $276 = 0, + $277 = 0, + $278 = 0; + var $279 = 0, + $28 = 0, + $280 = 0, + $281 = 0, + $282 = 0, + $283 = 0, + $284 = 0, + $285 = 0, + $286 = 0, + $287 = 0, + $288 = 0, + $289 = 0, + $29 = 0, + $290 = 0, + $291 = 0, + $292 = 0, + $293 = 0, + $294 = 0, + $295 = 0, + $296 = 0; + var $297 = 0, + $298 = 0, + $299 = 0, + $3 = 0, + $30 = 0, + $300 = 0, + $301 = 0, + $302 = 0, + $303 = 0, + $304 = 0, + $305 = 0, + $306 = 0, + $307 = 0, + $308 = 0, + $309 = 0, + $31 = 0, + $310 = 0, + $311 = 0, + $312 = 0, + $313 = 0; + var $314 = 0, + $315 = 0, + $316 = 0, + $317 = 0, + $318 = 0, + $319 = 0, + $32 = 0, + $320 = 0, + $321 = 0, + $322 = 0, + $323 = 0, + $324 = 0, + $325 = 0, + $326 = 0, + $327 = 0, + $328 = 0, + $329 = 0, + $33 = 0, + $330 = 0, + $331 = 0; + var $332 = 0, + $333 = 0, + $334 = 0, + $335 = 0, + $336 = 0, + $337 = 0, + $338 = 0, + $339 = 0, + $34 = 0, + $340 = 0, + $341 = 0, + $342 = 0, + $343 = 0, + $344 = 0, + $345 = 0, + $346 = 0, + $347 = 0, + $348 = 0, + $349 = 0, + $35 = 0; + var $350 = 0, + $351 = 0, + $352 = 0, + $353 = 0, + $354 = 0, + $355 = 0, + $356 = 0, + $357 = 0, + $358 = 0, + $359 = 0, + $36 = 0, + $360 = 0, + $361 = 0, + $362 = 0, + $363 = 0, + $364 = 0, + $365 = 0, + $366 = 0, + $367 = 0, + $368 = 0; + var $369 = 0, + $37 = 0, + $370 = 0, + $371 = 0, + $372 = 0, + $373 = 0, + $374 = 0, + $375 = 0, + $376 = 0, + $377 = 0, + $378 = 0, + $379 = 0, + $38 = 0, + $380 = 0, + $381 = 0, + $382 = 0, + $383 = 0, + $384 = 0, + $385 = 0, + $386 = 0; + var $387 = 0, + $388 = 0, + $389 = 0, + $39 = 0, + $390 = 0, + $391 = 0, + $392 = 0, + $393 = 0, + $394 = 0, + $395 = 0, + $396 = 0, + $397 = 0, + $398 = 0, + $399 = 0, + $4 = 0, + $40 = 0, + $400 = 0, + $401 = 0, + $402 = 0, + $403 = 0; + var $404 = 0, + $405 = 0, + $406 = 0, + $407 = 0, + $408 = 0, + $409 = 0, + $41 = 0, + $410 = 0, + $411 = 0, + $412 = 0, + $413 = 0, + $414 = 0, + $415 = 0, + $416 = 0, + $417 = 0, + $418 = 0, + $419 = 0, + $42 = 0, + $420 = 0, + $421 = 0; + var $422 = 0, + $423 = 0, + $424 = 0, + $425 = 0, + $426 = 0, + $427 = 0, + $428 = 0, + $43 = 0, + $44 = 0, + $45 = 0, + $46 = 0, + $47 = 0, + $48 = 0, + $49 = 0, + $5 = 0, + $50 = 0, + $51 = 0, + $52 = 0, + $53 = 0, + $54 = 0; + var $55 = 0, + $56 = 0, + $57 = 0, + $58 = 0, + $59 = 0, + $6 = 0, + $60 = 0, + $61 = 0, + $62 = 0, + $63 = 0, + $64 = 0, + $65 = 0, + $66 = 0, + $67 = 0, + $68 = 0, + $69 = 0, + $7 = 0, + $70 = 0, + $71 = 0, + $72 = 0; + var $73 = 0, + $74 = 0, + $75 = 0, + $76 = 0, + $77 = 0, + $78 = 0, + $79 = 0, + $8 = 0, + $80 = 0, + $81 = 0, + $82 = 0, + $83 = 0, + $84 = 0, + $85 = 0, + $86 = 0, + $87 = 0, + $88 = 0, + $89 = 0, + $9 = 0, + $90 = 0; + var $91 = 0, + $92 = 0, + $93 = 0, + $94 = 0, + $95 = 0, + $96 = 0, + $97 = 0, + $98 = 0, + $99 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP32[$f >> 2] | 0; + $1 = ($f + 4) | 0; + $2 = HEAP32[$1 >> 2] | 0; + $3 = ($f + 8) | 0; + $4 = HEAP32[$3 >> 2] | 0; + $5 = ($f + 12) | 0; + $6 = HEAP32[$5 >> 2] | 0; + $7 = ($f + 16) | 0; + $8 = HEAP32[$7 >> 2] | 0; + $9 = ($f + 20) | 0; + $10 = HEAP32[$9 >> 2] | 0; + $11 = ($f + 24) | 0; + $12 = HEAP32[$11 >> 2] | 0; + $13 = ($f + 28) | 0; + $14 = HEAP32[$13 >> 2] | 0; + $15 = ($f + 32) | 0; + $16 = HEAP32[$15 >> 2] | 0; + $17 = ($f + 36) | 0; + $18 = HEAP32[$17 >> 2] | 0; + $19 = $0 << 1; + $20 = $2 << 1; + $21 = $4 << 1; + $22 = $6 << 1; + $23 = $8 << 1; + $24 = $10 << 1; + $25 = $12 << 1; + $26 = $14 << 1; + $27 = ($10 * 38) | 0; + $28 = ($12 * 19) | 0; + $29 = ($14 * 38) | 0; + $30 = ($16 * 19) | 0; + $31 = ($18 * 38) | 0; + $32 = ($0 | 0) < 0; + $33 = ($32 << 31) >> 31; + $34 = ___muldi3($0 | 0, $33 | 0, $0 | 0, $33 | 0) | 0; + $35 = tempRet0; + $36 = ($19 | 0) < 0; + $37 = ($36 << 31) >> 31; + $38 = ($2 | 0) < 0; + $39 = ($38 << 31) >> 31; + $40 = ___muldi3($19 | 0, $37 | 0, $2 | 0, $39 | 0) | 0; + $41 = tempRet0; + $42 = ($4 | 0) < 0; + $43 = ($42 << 31) >> 31; + $44 = ___muldi3($4 | 0, $43 | 0, $19 | 0, $37 | 0) | 0; + $45 = tempRet0; + $46 = ($6 | 0) < 0; + $47 = ($46 << 31) >> 31; + $48 = ___muldi3($6 | 0, $47 | 0, $19 | 0, $37 | 0) | 0; + $49 = tempRet0; + $50 = ($8 | 0) < 0; + $51 = ($50 << 31) >> 31; + $52 = ___muldi3($8 | 0, $51 | 0, $19 | 0, $37 | 0) | 0; + $53 = tempRet0; + $54 = ($10 | 0) < 0; + $55 = ($54 << 31) >> 31; + $56 = ___muldi3($10 | 0, $55 | 0, $19 | 0, $37 | 0) | 0; + $57 = tempRet0; + $58 = ($12 | 0) < 0; + $59 = ($58 << 31) >> 31; + $60 = ___muldi3($12 | 0, $59 | 0, $19 | 0, $37 | 0) | 0; + $61 = tempRet0; + $62 = ($14 | 0) < 0; + $63 = ($62 << 31) >> 31; + $64 = ___muldi3($14 | 0, $63 | 0, $19 | 0, $37 | 0) | 0; + $65 = tempRet0; + $66 = ($16 | 0) < 0; + $67 = ($66 << 31) >> 31; + $68 = ___muldi3($16 | 0, $67 | 0, $19 | 0, $37 | 0) | 0; + $69 = tempRet0; + $70 = ($18 | 0) < 0; + $71 = ($70 << 31) >> 31; + $72 = ___muldi3($18 | 0, $71 | 0, $19 | 0, $37 | 0) | 0; + $73 = tempRet0; + $74 = ($20 | 0) < 0; + $75 = ($74 << 31) >> 31; + $76 = ___muldi3($20 | 0, $75 | 0, $2 | 0, $39 | 0) | 0; + $77 = tempRet0; + $78 = ___muldi3($20 | 0, $75 | 0, $4 | 0, $43 | 0) | 0; + $79 = tempRet0; + $80 = ($22 | 0) < 0; + $81 = ($80 << 31) >> 31; + $82 = ___muldi3($22 | 0, $81 | 0, $20 | 0, $75 | 0) | 0; + $83 = tempRet0; + $84 = ___muldi3($8 | 0, $51 | 0, $20 | 0, $75 | 0) | 0; + $85 = tempRet0; + $86 = ($24 | 0) < 0; + $87 = ($86 << 31) >> 31; + $88 = ___muldi3($24 | 0, $87 | 0, $20 | 0, $75 | 0) | 0; + $89 = tempRet0; + $90 = ___muldi3($12 | 0, $59 | 0, $20 | 0, $75 | 0) | 0; + $91 = tempRet0; + $92 = ($26 | 0) < 0; + $93 = ($92 << 31) >> 31; + $94 = ___muldi3($26 | 0, $93 | 0, $20 | 0, $75 | 0) | 0; + $95 = tempRet0; + $96 = ___muldi3($16 | 0, $67 | 0, $20 | 0, $75 | 0) | 0; + $97 = tempRet0; + $98 = ($31 | 0) < 0; + $99 = ($98 << 31) >> 31; + $100 = ___muldi3($31 | 0, $99 | 0, $20 | 0, $75 | 0) | 0; + $101 = tempRet0; + $102 = ___muldi3($4 | 0, $43 | 0, $4 | 0, $43 | 0) | 0; + $103 = tempRet0; + $104 = ($21 | 0) < 0; + $105 = ($104 << 31) >> 31; + $106 = ___muldi3($21 | 0, $105 | 0, $6 | 0, $47 | 0) | 0; + $107 = tempRet0; + $108 = ___muldi3($8 | 0, $51 | 0, $21 | 0, $105 | 0) | 0; + $109 = tempRet0; + $110 = ___muldi3($10 | 0, $55 | 0, $21 | 0, $105 | 0) | 0; + $111 = tempRet0; + $112 = ___muldi3($12 | 0, $59 | 0, $21 | 0, $105 | 0) | 0; + $113 = tempRet0; + $114 = ___muldi3($14 | 0, $63 | 0, $21 | 0, $105 | 0) | 0; + $115 = tempRet0; + $116 = ($30 | 0) < 0; + $117 = ($116 << 31) >> 31; + $118 = ___muldi3($30 | 0, $117 | 0, $21 | 0, $105 | 0) | 0; + $119 = tempRet0; + $120 = ___muldi3($31 | 0, $99 | 0, $4 | 0, $43 | 0) | 0; + $121 = tempRet0; + $122 = ___muldi3($22 | 0, $81 | 0, $6 | 0, $47 | 0) | 0; + $123 = tempRet0; + $124 = ___muldi3($22 | 0, $81 | 0, $8 | 0, $51 | 0) | 0; + $125 = tempRet0; + $126 = ___muldi3($24 | 0, $87 | 0, $22 | 0, $81 | 0) | 0; + $127 = tempRet0; + $128 = ___muldi3($12 | 0, $59 | 0, $22 | 0, $81 | 0) | 0; + $129 = tempRet0; + $130 = ($29 | 0) < 0; + $131 = ($130 << 31) >> 31; + $132 = ___muldi3($29 | 0, $131 | 0, $22 | 0, $81 | 0) | 0; + $133 = tempRet0; + $134 = ___muldi3($30 | 0, $117 | 0, $22 | 0, $81 | 0) | 0; + $135 = tempRet0; + $136 = ___muldi3($31 | 0, $99 | 0, $22 | 0, $81 | 0) | 0; + $137 = tempRet0; + $138 = ___muldi3($8 | 0, $51 | 0, $8 | 0, $51 | 0) | 0; + $139 = tempRet0; + $140 = ($23 | 0) < 0; + $141 = ($140 << 31) >> 31; + $142 = ___muldi3($23 | 0, $141 | 0, $10 | 0, $55 | 0) | 0; + $143 = tempRet0; + $144 = ($28 | 0) < 0; + $145 = ($144 << 31) >> 31; + $146 = ___muldi3($28 | 0, $145 | 0, $23 | 0, $141 | 0) | 0; + $147 = tempRet0; + $148 = ___muldi3($29 | 0, $131 | 0, $8 | 0, $51 | 0) | 0; + $149 = tempRet0; + $150 = ___muldi3($30 | 0, $117 | 0, $23 | 0, $141 | 0) | 0; + $151 = tempRet0; + $152 = ___muldi3($31 | 0, $99 | 0, $8 | 0, $51 | 0) | 0; + $153 = tempRet0; + $154 = ($27 | 0) < 0; + $155 = ($154 << 31) >> 31; + $156 = ___muldi3($27 | 0, $155 | 0, $10 | 0, $55 | 0) | 0; + $157 = tempRet0; + $158 = ___muldi3($28 | 0, $145 | 0, $24 | 0, $87 | 0) | 0; + $159 = tempRet0; + $160 = ___muldi3($29 | 0, $131 | 0, $24 | 0, $87 | 0) | 0; + $161 = tempRet0; + $162 = ___muldi3($30 | 0, $117 | 0, $24 | 0, $87 | 0) | 0; + $163 = tempRet0; + $164 = ___muldi3($31 | 0, $99 | 0, $24 | 0, $87 | 0) | 0; + $165 = tempRet0; + $166 = ___muldi3($28 | 0, $145 | 0, $12 | 0, $59 | 0) | 0; + $167 = tempRet0; + $168 = ___muldi3($29 | 0, $131 | 0, $12 | 0, $59 | 0) | 0; + $169 = tempRet0; + $170 = ($25 | 0) < 0; + $171 = ($170 << 31) >> 31; + $172 = ___muldi3($30 | 0, $117 | 0, $25 | 0, $171 | 0) | 0; + $173 = tempRet0; + $174 = ___muldi3($31 | 0, $99 | 0, $12 | 0, $59 | 0) | 0; + $175 = tempRet0; + $176 = ___muldi3($29 | 0, $131 | 0, $14 | 0, $63 | 0) | 0; + $177 = tempRet0; + $178 = ___muldi3($30 | 0, $117 | 0, $26 | 0, $93 | 0) | 0; + $179 = tempRet0; + $180 = ___muldi3($31 | 0, $99 | 0, $26 | 0, $93 | 0) | 0; + $181 = tempRet0; + $182 = ___muldi3($30 | 0, $117 | 0, $16 | 0, $67 | 0) | 0; + $183 = tempRet0; + $184 = ___muldi3($31 | 0, $99 | 0, $16 | 0, $67 | 0) | 0; + $185 = tempRet0; + $186 = ___muldi3($31 | 0, $99 | 0, $18 | 0, $71 | 0) | 0; + $187 = tempRet0; + $188 = _i64Add($156 | 0, $157 | 0, $34 | 0, $35 | 0) | 0; + $189 = tempRet0; + $190 = _i64Add($188 | 0, $189 | 0, $146 | 0, $147 | 0) | 0; + $191 = tempRet0; + $192 = _i64Add($190 | 0, $191 | 0, $132 | 0, $133 | 0) | 0; + $193 = tempRet0; + $194 = _i64Add($192 | 0, $193 | 0, $118 | 0, $119 | 0) | 0; + $195 = tempRet0; + $196 = _i64Add($194 | 0, $195 | 0, $100 | 0, $101 | 0) | 0; + $197 = tempRet0; + $198 = _i64Add($158 | 0, $159 | 0, $40 | 0, $41 | 0) | 0; + $199 = tempRet0; + $200 = _i64Add($198 | 0, $199 | 0, $148 | 0, $149 | 0) | 0; + $201 = tempRet0; + $202 = _i64Add($200 | 0, $201 | 0, $134 | 0, $135 | 0) | 0; + $203 = tempRet0; + $204 = _i64Add($202 | 0, $203 | 0, $120 | 0, $121 | 0) | 0; + $205 = tempRet0; + $206 = _i64Add($44 | 0, $45 | 0, $76 | 0, $77 | 0) | 0; + $207 = tempRet0; + $208 = _i64Add($206 | 0, $207 | 0, $166 | 0, $167 | 0) | 0; + $209 = tempRet0; + $210 = _i64Add($208 | 0, $209 | 0, $160 | 0, $161 | 0) | 0; + $211 = tempRet0; + $212 = _i64Add($210 | 0, $211 | 0, $150 | 0, $151 | 0) | 0; + $213 = tempRet0; + $214 = _i64Add($212 | 0, $213 | 0, $136 | 0, $137 | 0) | 0; + $215 = tempRet0; + $216 = _i64Add($48 | 0, $49 | 0, $78 | 0, $79 | 0) | 0; + $217 = tempRet0; + $218 = _i64Add($216 | 0, $217 | 0, $168 | 0, $169 | 0) | 0; + $219 = tempRet0; + $220 = _i64Add($218 | 0, $219 | 0, $162 | 0, $163 | 0) | 0; + $221 = tempRet0; + $222 = _i64Add($220 | 0, $221 | 0, $152 | 0, $153 | 0) | 0; + $223 = tempRet0; + $224 = _i64Add($82 | 0, $83 | 0, $102 | 0, $103 | 0) | 0; + $225 = tempRet0; + $226 = _i64Add($224 | 0, $225 | 0, $52 | 0, $53 | 0) | 0; + $227 = tempRet0; + $228 = _i64Add($226 | 0, $227 | 0, $176 | 0, $177 | 0) | 0; + $229 = tempRet0; + $230 = _i64Add($228 | 0, $229 | 0, $172 | 0, $173 | 0) | 0; + $231 = tempRet0; + $232 = _i64Add($230 | 0, $231 | 0, $164 | 0, $165 | 0) | 0; + $233 = tempRet0; + $234 = _i64Add($84 | 0, $85 | 0, $106 | 0, $107 | 0) | 0; + $235 = tempRet0; + $236 = _i64Add($234 | 0, $235 | 0, $56 | 0, $57 | 0) | 0; + $237 = tempRet0; + $238 = _i64Add($236 | 0, $237 | 0, $178 | 0, $179 | 0) | 0; + $239 = tempRet0; + $240 = _i64Add($238 | 0, $239 | 0, $174 | 0, $175 | 0) | 0; + $241 = tempRet0; + $242 = _i64Add($122 | 0, $123 | 0, $108 | 0, $109 | 0) | 0; + $243 = tempRet0; + $244 = _i64Add($242 | 0, $243 | 0, $88 | 0, $89 | 0) | 0; + $245 = tempRet0; + $246 = _i64Add($244 | 0, $245 | 0, $60 | 0, $61 | 0) | 0; + $247 = tempRet0; + $248 = _i64Add($246 | 0, $247 | 0, $182 | 0, $183 | 0) | 0; + $249 = tempRet0; + $250 = _i64Add($248 | 0, $249 | 0, $180 | 0, $181 | 0) | 0; + $251 = tempRet0; + $252 = _i64Add($110 | 0, $111 | 0, $124 | 0, $125 | 0) | 0; + $253 = tempRet0; + $254 = _i64Add($252 | 0, $253 | 0, $90 | 0, $91 | 0) | 0; + $255 = tempRet0; + $256 = _i64Add($254 | 0, $255 | 0, $64 | 0, $65 | 0) | 0; + $257 = tempRet0; + $258 = _i64Add($256 | 0, $257 | 0, $184 | 0, $185 | 0) | 0; + $259 = tempRet0; + $260 = _i64Add($112 | 0, $113 | 0, $138 | 0, $139 | 0) | 0; + $261 = tempRet0; + $262 = _i64Add($260 | 0, $261 | 0, $126 | 0, $127 | 0) | 0; + $263 = tempRet0; + $264 = _i64Add($262 | 0, $263 | 0, $94 | 0, $95 | 0) | 0; + $265 = tempRet0; + $266 = _i64Add($264 | 0, $265 | 0, $68 | 0, $69 | 0) | 0; + $267 = tempRet0; + $268 = _i64Add($266 | 0, $267 | 0, $186 | 0, $187 | 0) | 0; + $269 = tempRet0; + $270 = _i64Add($128 | 0, $129 | 0, $142 | 0, $143 | 0) | 0; + $271 = tempRet0; + $272 = _i64Add($270 | 0, $271 | 0, $114 | 0, $115 | 0) | 0; + $273 = tempRet0; + $274 = _i64Add($272 | 0, $273 | 0, $96 | 0, $97 | 0) | 0; + $275 = tempRet0; + $276 = _i64Add($274 | 0, $275 | 0, $72 | 0, $73 | 0) | 0; + $277 = tempRet0; + $278 = _bitshift64Shl($196 | 0, $197 | 0, 1) | 0; + $279 = tempRet0; + $280 = _bitshift64Shl($204 | 0, $205 | 0, 1) | 0; + $281 = tempRet0; + $282 = _bitshift64Shl($214 | 0, $215 | 0, 1) | 0; + $283 = tempRet0; + $284 = _bitshift64Shl($222 | 0, $223 | 0, 1) | 0; + $285 = tempRet0; + $286 = _bitshift64Shl($232 | 0, $233 | 0, 1) | 0; + $287 = tempRet0; + $288 = _bitshift64Shl($240 | 0, $241 | 0, 1) | 0; + $289 = tempRet0; + $290 = _bitshift64Shl($250 | 0, $251 | 0, 1) | 0; + $291 = tempRet0; + $292 = _bitshift64Shl($258 | 0, $259 | 0, 1) | 0; + $293 = tempRet0; + $294 = _bitshift64Shl($268 | 0, $269 | 0, 1) | 0; + $295 = tempRet0; + $296 = _bitshift64Shl($276 | 0, $277 | 0, 1) | 0; + $297 = tempRet0; + $298 = _i64Add($278 | 0, $279 | 0, 33554432, 0) | 0; + $299 = tempRet0; + $300 = _bitshift64Ashr($298 | 0, $299 | 0, 26) | 0; + $301 = tempRet0; + $302 = _i64Add($300 | 0, $301 | 0, $280 | 0, $281 | 0) | 0; + $303 = tempRet0; + $304 = _bitshift64Shl($300 | 0, $301 | 0, 26) | 0; + $305 = tempRet0; + $306 = _i64Subtract($278 | 0, $279 | 0, $304 | 0, $305 | 0) | 0; + $307 = tempRet0; + $308 = _i64Add($286 | 0, $287 | 0, 33554432, 0) | 0; + $309 = tempRet0; + $310 = _bitshift64Ashr($308 | 0, $309 | 0, 26) | 0; + $311 = tempRet0; + $312 = _i64Add($310 | 0, $311 | 0, $288 | 0, $289 | 0) | 0; + $313 = tempRet0; + $314 = _bitshift64Shl($310 | 0, $311 | 0, 26) | 0; + $315 = tempRet0; + $316 = _i64Subtract($286 | 0, $287 | 0, $314 | 0, $315 | 0) | 0; + $317 = tempRet0; + $318 = _i64Add($302 | 0, $303 | 0, 16777216, 0) | 0; + $319 = tempRet0; + $320 = _bitshift64Ashr($318 | 0, $319 | 0, 25) | 0; + $321 = tempRet0; + $322 = _i64Add($320 | 0, $321 | 0, $282 | 0, $283 | 0) | 0; + $323 = tempRet0; + $324 = _bitshift64Shl($320 | 0, $321 | 0, 25) | 0; + $325 = tempRet0; + $326 = _i64Subtract($302 | 0, $303 | 0, $324 | 0, $325 | 0) | 0; + $327 = tempRet0; + $328 = _i64Add($312 | 0, $313 | 0, 16777216, 0) | 0; + $329 = tempRet0; + $330 = _bitshift64Ashr($328 | 0, $329 | 0, 25) | 0; + $331 = tempRet0; + $332 = _i64Add($330 | 0, $331 | 0, $290 | 0, $291 | 0) | 0; + $333 = tempRet0; + $334 = _bitshift64Shl($330 | 0, $331 | 0, 25) | 0; + $335 = tempRet0; + $336 = _i64Subtract($312 | 0, $313 | 0, $334 | 0, $335 | 0) | 0; + $337 = tempRet0; + $338 = _i64Add($322 | 0, $323 | 0, 33554432, 0) | 0; + $339 = tempRet0; + $340 = _bitshift64Ashr($338 | 0, $339 | 0, 26) | 0; + $341 = tempRet0; + $342 = _i64Add($340 | 0, $341 | 0, $284 | 0, $285 | 0) | 0; + $343 = tempRet0; + $344 = _bitshift64Shl($340 | 0, $341 | 0, 26) | 0; + $345 = tempRet0; + $346 = _i64Subtract($322 | 0, $323 | 0, $344 | 0, $345 | 0) | 0; + $347 = tempRet0; + $348 = _i64Add($332 | 0, $333 | 0, 33554432, 0) | 0; + $349 = tempRet0; + $350 = _bitshift64Ashr($348 | 0, $349 | 0, 26) | 0; + $351 = tempRet0; + $352 = _i64Add($350 | 0, $351 | 0, $292 | 0, $293 | 0) | 0; + $353 = tempRet0; + $354 = _bitshift64Shl($350 | 0, $351 | 0, 26) | 0; + $355 = tempRet0; + $356 = _i64Subtract($332 | 0, $333 | 0, $354 | 0, $355 | 0) | 0; + $357 = tempRet0; + $358 = _i64Add($342 | 0, $343 | 0, 16777216, 0) | 0; + $359 = tempRet0; + $360 = _bitshift64Ashr($358 | 0, $359 | 0, 25) | 0; + $361 = tempRet0; + $362 = _i64Add($360 | 0, $361 | 0, $316 | 0, $317 | 0) | 0; + $363 = tempRet0; + $364 = _bitshift64Shl($360 | 0, $361 | 0, 25) | 0; + $365 = tempRet0; + $366 = _i64Subtract($342 | 0, $343 | 0, $364 | 0, $365 | 0) | 0; + $367 = tempRet0; + $368 = _i64Add($352 | 0, $353 | 0, 16777216, 0) | 0; + $369 = tempRet0; + $370 = _bitshift64Ashr($368 | 0, $369 | 0, 25) | 0; + $371 = tempRet0; + $372 = _i64Add($370 | 0, $371 | 0, $294 | 0, $295 | 0) | 0; + $373 = tempRet0; + $374 = _bitshift64Shl($370 | 0, $371 | 0, 25) | 0; + $375 = tempRet0; + $376 = _i64Subtract($352 | 0, $353 | 0, $374 | 0, $375 | 0) | 0; + $377 = tempRet0; + $378 = _i64Add($362 | 0, $363 | 0, 33554432, 0) | 0; + $379 = tempRet0; + $380 = _bitshift64Ashr($378 | 0, $379 | 0, 26) | 0; + $381 = tempRet0; + $382 = _i64Add($336 | 0, $337 | 0, $380 | 0, $381 | 0) | 0; + $383 = tempRet0; + $384 = _bitshift64Shl($380 | 0, $381 | 0, 26) | 0; + $385 = tempRet0; + $386 = _i64Subtract($362 | 0, $363 | 0, $384 | 0, $385 | 0) | 0; + $387 = tempRet0; + $388 = _i64Add($372 | 0, $373 | 0, 33554432, 0) | 0; + $389 = tempRet0; + $390 = _bitshift64Ashr($388 | 0, $389 | 0, 26) | 0; + $391 = tempRet0; + $392 = _i64Add($390 | 0, $391 | 0, $296 | 0, $297 | 0) | 0; + $393 = tempRet0; + $394 = _bitshift64Shl($390 | 0, $391 | 0, 26) | 0; + $395 = tempRet0; + $396 = _i64Subtract($372 | 0, $373 | 0, $394 | 0, $395 | 0) | 0; + $397 = tempRet0; + $398 = _i64Add($392 | 0, $393 | 0, 16777216, 0) | 0; + $399 = tempRet0; + $400 = _bitshift64Ashr($398 | 0, $399 | 0, 25) | 0; + $401 = tempRet0; + $402 = ___muldi3($400 | 0, $401 | 0, 19, 0) | 0; + $403 = tempRet0; + $404 = _i64Add($402 | 0, $403 | 0, $306 | 0, $307 | 0) | 0; + $405 = tempRet0; + $406 = _bitshift64Shl($400 | 0, $401 | 0, 25) | 0; + $407 = tempRet0; + $408 = _i64Subtract($392 | 0, $393 | 0, $406 | 0, $407 | 0) | 0; + $409 = tempRet0; + $410 = _i64Add($404 | 0, $405 | 0, 33554432, 0) | 0; + $411 = tempRet0; + $412 = _bitshift64Ashr($410 | 0, $411 | 0, 26) | 0; + $413 = tempRet0; + $414 = _i64Add($326 | 0, $327 | 0, $412 | 0, $413 | 0) | 0; + $415 = tempRet0; + $416 = _bitshift64Shl($412 | 0, $413 | 0, 26) | 0; + $417 = tempRet0; + $418 = _i64Subtract($404 | 0, $405 | 0, $416 | 0, $417 | 0) | 0; + $419 = tempRet0; + HEAP32[$h >> 2] = $418; + $420 = ($h + 4) | 0; + HEAP32[$420 >> 2] = $414; + $421 = ($h + 8) | 0; + HEAP32[$421 >> 2] = $346; + $422 = ($h + 12) | 0; + HEAP32[$422 >> 2] = $366; + $423 = ($h + 16) | 0; + HEAP32[$423 >> 2] = $386; + $424 = ($h + 20) | 0; + HEAP32[$424 >> 2] = $382; + $425 = ($h + 24) | 0; + HEAP32[$425 >> 2] = $356; + $426 = ($h + 28) | 0; + HEAP32[$426 >> 2] = $376; + $427 = ($h + 32) | 0; + HEAP32[$427 >> 2] = $396; + $428 = ($h + 36) | 0; + HEAP32[$428 >> 2] = $408; + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_fe_sub($h, $f, $g) { + $h = $h | 0; + $f = $f | 0; + $g = $g | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0; + var $27 = 0, + $28 = 0, + $29 = 0, + $3 = 0, + $30 = 0, + $31 = 0, + $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $37 = 0, + $38 = 0, + $39 = 0, + $4 = 0, + $40 = 0, + $41 = 0, + $42 = 0, + $43 = 0, + $44 = 0; + var $45 = 0, + $46 = 0, + $47 = 0, + $48 = 0, + $49 = 0, + $5 = 0, + $50 = 0, + $51 = 0, + $52 = 0, + $53 = 0, + $54 = 0, + $55 = 0, + $56 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP32[$f >> 2] | 0; + $1 = ($f + 4) | 0; + $2 = HEAP32[$1 >> 2] | 0; + $3 = ($f + 8) | 0; + $4 = HEAP32[$3 >> 2] | 0; + $5 = ($f + 12) | 0; + $6 = HEAP32[$5 >> 2] | 0; + $7 = ($f + 16) | 0; + $8 = HEAP32[$7 >> 2] | 0; + $9 = ($f + 20) | 0; + $10 = HEAP32[$9 >> 2] | 0; + $11 = ($f + 24) | 0; + $12 = HEAP32[$11 >> 2] | 0; + $13 = ($f + 28) | 0; + $14 = HEAP32[$13 >> 2] | 0; + $15 = ($f + 32) | 0; + $16 = HEAP32[$15 >> 2] | 0; + $17 = ($f + 36) | 0; + $18 = HEAP32[$17 >> 2] | 0; + $19 = HEAP32[$g >> 2] | 0; + $20 = ($g + 4) | 0; + $21 = HEAP32[$20 >> 2] | 0; + $22 = ($g + 8) | 0; + $23 = HEAP32[$22 >> 2] | 0; + $24 = ($g + 12) | 0; + $25 = HEAP32[$24 >> 2] | 0; + $26 = ($g + 16) | 0; + $27 = HEAP32[$26 >> 2] | 0; + $28 = ($g + 20) | 0; + $29 = HEAP32[$28 >> 2] | 0; + $30 = ($g + 24) | 0; + $31 = HEAP32[$30 >> 2] | 0; + $32 = ($g + 28) | 0; + $33 = HEAP32[$32 >> 2] | 0; + $34 = ($g + 32) | 0; + $35 = HEAP32[$34 >> 2] | 0; + $36 = ($g + 36) | 0; + $37 = HEAP32[$36 >> 2] | 0; + $38 = ($0 - $19) | 0; + $39 = ($2 - $21) | 0; + $40 = ($4 - $23) | 0; + $41 = ($6 - $25) | 0; + $42 = ($8 - $27) | 0; + $43 = ($10 - $29) | 0; + $44 = ($12 - $31) | 0; + $45 = ($14 - $33) | 0; + $46 = ($16 - $35) | 0; + $47 = ($18 - $37) | 0; + HEAP32[$h >> 2] = $38; + $48 = ($h + 4) | 0; + HEAP32[$48 >> 2] = $39; + $49 = ($h + 8) | 0; + HEAP32[$49 >> 2] = $40; + $50 = ($h + 12) | 0; + HEAP32[$50 >> 2] = $41; + $51 = ($h + 16) | 0; + HEAP32[$51 >> 2] = $42; + $52 = ($h + 20) | 0; + HEAP32[$52 >> 2] = $43; + $53 = ($h + 24) | 0; + HEAP32[$53 >> 2] = $44; + $54 = ($h + 28) | 0; + HEAP32[$54 >> 2] = $45; + $55 = ($h + 32) | 0; + HEAP32[$55 >> 2] = $46; + $56 = ($h + 36) | 0; + HEAP32[$56 >> 2] = $47; + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_fe_tobytes($s, $h) { + $s = $s | 0; + $h = $h | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $101 = 0, + $102 = 0, + $103 = 0, + $104 = 0, + $105 = 0, + $106 = 0, + $107 = 0, + $108 = 0, + $109 = 0, + $11 = 0, + $110 = 0, + $111 = 0, + $112 = 0, + $113 = 0, + $114 = 0, + $115 = 0; + var $116 = 0, + $117 = 0, + $118 = 0, + $119 = 0, + $12 = 0, + $120 = 0, + $121 = 0, + $122 = 0, + $123 = 0, + $124 = 0, + $125 = 0, + $126 = 0, + $127 = 0, + $128 = 0, + $129 = 0, + $13 = 0, + $130 = 0, + $131 = 0, + $132 = 0, + $133 = 0; + var $134 = 0, + $135 = 0, + $136 = 0, + $137 = 0, + $138 = 0, + $139 = 0, + $14 = 0, + $140 = 0, + $141 = 0, + $142 = 0, + $143 = 0, + $144 = 0, + $145 = 0, + $146 = 0, + $147 = 0, + $148 = 0, + $149 = 0, + $15 = 0, + $150 = 0, + $151 = 0; + var $152 = 0, + $153 = 0, + $154 = 0, + $155 = 0, + $156 = 0, + $157 = 0, + $158 = 0, + $159 = 0, + $16 = 0, + $160 = 0, + $161 = 0, + $162 = 0, + $163 = 0, + $164 = 0, + $165 = 0, + $166 = 0, + $167 = 0, + $168 = 0, + $169 = 0, + $17 = 0; + var $170 = 0, + $171 = 0, + $172 = 0, + $173 = 0, + $174 = 0, + $175 = 0, + $176 = 0, + $177 = 0, + $178 = 0, + $179 = 0, + $18 = 0, + $180 = 0, + $181 = 0, + $182 = 0, + $183 = 0, + $184 = 0, + $185 = 0, + $186 = 0, + $187 = 0, + $188 = 0; + var $189 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0, + $27 = 0, + $28 = 0, + $29 = 0, + $3 = 0, + $30 = 0, + $31 = 0, + $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0; + var $36 = 0, + $37 = 0, + $38 = 0, + $39 = 0, + $4 = 0, + $40 = 0, + $41 = 0, + $42 = 0, + $43 = 0, + $44 = 0, + $45 = 0, + $46 = 0, + $47 = 0, + $48 = 0, + $49 = 0, + $5 = 0, + $50 = 0, + $51 = 0, + $52 = 0, + $53 = 0; + var $54 = 0, + $55 = 0, + $56 = 0, + $57 = 0, + $58 = 0, + $59 = 0, + $6 = 0, + $60 = 0, + $61 = 0, + $62 = 0, + $63 = 0, + $64 = 0, + $65 = 0, + $66 = 0, + $67 = 0, + $68 = 0, + $69 = 0, + $7 = 0, + $70 = 0, + $71 = 0; + var $72 = 0, + $73 = 0, + $74 = 0, + $75 = 0, + $76 = 0, + $77 = 0, + $78 = 0, + $79 = 0, + $8 = 0, + $80 = 0, + $81 = 0, + $82 = 0, + $83 = 0, + $84 = 0, + $85 = 0, + $86 = 0, + $87 = 0, + $88 = 0, + $89 = 0, + $9 = 0; + var $90 = 0, + $91 = 0, + $92 = 0, + $93 = 0, + $94 = 0, + $95 = 0, + $96 = 0, + $97 = 0, + $98 = 0, + $99 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP32[$h >> 2] | 0; + $1 = ($h + 4) | 0; + $2 = HEAP32[$1 >> 2] | 0; + $3 = ($h + 8) | 0; + $4 = HEAP32[$3 >> 2] | 0; + $5 = ($h + 12) | 0; + $6 = HEAP32[$5 >> 2] | 0; + $7 = ($h + 16) | 0; + $8 = HEAP32[$7 >> 2] | 0; + $9 = ($h + 20) | 0; + $10 = HEAP32[$9 >> 2] | 0; + $11 = ($h + 24) | 0; + $12 = HEAP32[$11 >> 2] | 0; + $13 = ($h + 28) | 0; + $14 = HEAP32[$13 >> 2] | 0; + $15 = ($h + 32) | 0; + $16 = HEAP32[$15 >> 2] | 0; + $17 = ($h + 36) | 0; + $18 = HEAP32[$17 >> 2] | 0; + $19 = ($18 * 19) | 0; + $20 = ($19 + 16777216) | 0; + $21 = $20 >> 25; + $22 = ($21 + $0) | 0; + $23 = $22 >> 26; + $24 = ($23 + $2) | 0; + $25 = $24 >> 25; + $26 = ($25 + $4) | 0; + $27 = $26 >> 26; + $28 = ($27 + $6) | 0; + $29 = $28 >> 25; + $30 = ($29 + $8) | 0; + $31 = $30 >> 26; + $32 = ($31 + $10) | 0; + $33 = $32 >> 25; + $34 = ($33 + $12) | 0; + $35 = $34 >> 26; + $36 = ($35 + $14) | 0; + $37 = $36 >> 25; + $38 = ($37 + $16) | 0; + $39 = $38 >> 26; + $40 = ($39 + $18) | 0; + $41 = $40 >> 25; + $42 = ($41 * 19) | 0; + $43 = ($42 + $0) | 0; + $44 = $43 >> 26; + $45 = ($44 + $2) | 0; + $46 = $44 << 26; + $47 = ($43 - $46) | 0; + $48 = $45 >> 25; + $49 = ($48 + $4) | 0; + $50 = $48 << 25; + $51 = ($45 - $50) | 0; + $52 = $49 >> 26; + $53 = ($52 + $6) | 0; + $54 = $52 << 26; + $55 = ($49 - $54) | 0; + $56 = $53 >> 25; + $57 = ($56 + $8) | 0; + $58 = $56 << 25; + $59 = ($53 - $58) | 0; + $60 = $57 >> 26; + $61 = ($60 + $10) | 0; + $62 = $60 << 26; + $63 = ($57 - $62) | 0; + $64 = $61 >> 25; + $65 = ($64 + $12) | 0; + $66 = $64 << 25; + $67 = ($61 - $66) | 0; + $68 = $65 >> 26; + $69 = ($68 + $14) | 0; + $70 = $68 << 26; + $71 = ($65 - $70) | 0; + $72 = $69 >> 25; + $73 = ($72 + $16) | 0; + $74 = $72 << 25; + $75 = ($69 - $74) | 0; + $76 = $73 >> 26; + $77 = ($76 + $18) | 0; + $78 = $76 << 26; + $79 = ($73 - $78) | 0; + $80 = $77 & 33554431; + $81 = $47 & 255; + HEAP8[$s >> 0] = $81; + $82 = $47 >>> 8; + $83 = $82 & 255; + $84 = ($s + 1) | 0; + HEAP8[$84 >> 0] = $83; + $85 = $47 >>> 16; + $86 = $85 & 255; + $87 = ($s + 2) | 0; + HEAP8[$87 >> 0] = $86; + $88 = $47 >>> 24; + $89 = $51 << 2; + $90 = $89 | $88; + $91 = $90 & 255; + $92 = ($s + 3) | 0; + HEAP8[$92 >> 0] = $91; + $93 = $51 >>> 6; + $94 = $93 & 255; + $95 = ($s + 4) | 0; + HEAP8[$95 >> 0] = $94; + $96 = $51 >>> 14; + $97 = $96 & 255; + $98 = ($s + 5) | 0; + HEAP8[$98 >> 0] = $97; + $99 = $51 >>> 22; + $100 = $55 << 3; + $101 = $100 | $99; + $102 = $101 & 255; + $103 = ($s + 6) | 0; + HEAP8[$103 >> 0] = $102; + $104 = $55 >>> 5; + $105 = $104 & 255; + $106 = ($s + 7) | 0; + HEAP8[$106 >> 0] = $105; + $107 = $55 >>> 13; + $108 = $107 & 255; + $109 = ($s + 8) | 0; + HEAP8[$109 >> 0] = $108; + $110 = $55 >>> 21; + $111 = $59 << 5; + $112 = $111 | $110; + $113 = $112 & 255; + $114 = ($s + 9) | 0; + HEAP8[$114 >> 0] = $113; + $115 = $59 >>> 3; + $116 = $115 & 255; + $117 = ($s + 10) | 0; + HEAP8[$117 >> 0] = $116; + $118 = $59 >>> 11; + $119 = $118 & 255; + $120 = ($s + 11) | 0; + HEAP8[$120 >> 0] = $119; + $121 = $59 >>> 19; + $122 = $63 << 6; + $123 = $122 | $121; + $124 = $123 & 255; + $125 = ($s + 12) | 0; + HEAP8[$125 >> 0] = $124; + $126 = $63 >>> 2; + $127 = $126 & 255; + $128 = ($s + 13) | 0; + HEAP8[$128 >> 0] = $127; + $129 = $63 >>> 10; + $130 = $129 & 255; + $131 = ($s + 14) | 0; + HEAP8[$131 >> 0] = $130; + $132 = $63 >>> 18; + $133 = $132 & 255; + $134 = ($s + 15) | 0; + HEAP8[$134 >> 0] = $133; + $135 = $67 & 255; + $136 = ($s + 16) | 0; + HEAP8[$136 >> 0] = $135; + $137 = $67 >>> 8; + $138 = $137 & 255; + $139 = ($s + 17) | 0; + HEAP8[$139 >> 0] = $138; + $140 = $67 >>> 16; + $141 = $140 & 255; + $142 = ($s + 18) | 0; + HEAP8[$142 >> 0] = $141; + $143 = $67 >>> 24; + $144 = $71 << 1; + $145 = $144 | $143; + $146 = $145 & 255; + $147 = ($s + 19) | 0; + HEAP8[$147 >> 0] = $146; + $148 = $71 >>> 7; + $149 = $148 & 255; + $150 = ($s + 20) | 0; + HEAP8[$150 >> 0] = $149; + $151 = $71 >>> 15; + $152 = $151 & 255; + $153 = ($s + 21) | 0; + HEAP8[$153 >> 0] = $152; + $154 = $71 >>> 23; + $155 = $75 << 3; + $156 = $155 | $154; + $157 = $156 & 255; + $158 = ($s + 22) | 0; + HEAP8[$158 >> 0] = $157; + $159 = $75 >>> 5; + $160 = $159 & 255; + $161 = ($s + 23) | 0; + HEAP8[$161 >> 0] = $160; + $162 = $75 >>> 13; + $163 = $162 & 255; + $164 = ($s + 24) | 0; + HEAP8[$164 >> 0] = $163; + $165 = $75 >>> 21; + $166 = $79 << 4; + $167 = $166 | $165; + $168 = $167 & 255; + $169 = ($s + 25) | 0; + HEAP8[$169 >> 0] = $168; + $170 = $79 >>> 4; + $171 = $170 & 255; + $172 = ($s + 26) | 0; + HEAP8[$172 >> 0] = $171; + $173 = $79 >>> 12; + $174 = $173 & 255; + $175 = ($s + 27) | 0; + HEAP8[$175 >> 0] = $174; + $176 = $79 >>> 20; + $177 = $80 << 6; + $178 = $176 | $177; + $179 = $178 & 255; + $180 = ($s + 28) | 0; + HEAP8[$180 >> 0] = $179; + $181 = $77 >>> 2; + $182 = $181 & 255; + $183 = ($s + 29) | 0; + HEAP8[$183 >> 0] = $182; + $184 = $77 >>> 10; + $185 = $184 & 255; + $186 = ($s + 30) | 0; + HEAP8[$186 >> 0] = $185; + $187 = $80 >>> 18; + $188 = $187 & 255; + $189 = ($s + 31) | 0; + HEAP8[$189 >> 0] = $188; + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_ge_add($r, $p, $q) { + $r = $r | 0; + $p = $p | 0; + $q = $q | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $t0 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 48) | 0; + $t0 = sp; + $0 = ($p + 40) | 0; + _crypto_sign_ed25519_ref10_fe_add($r, $0, $p); + $1 = ($r + 40) | 0; + _crypto_sign_ed25519_ref10_fe_sub($1, $0, $p); + $2 = ($r + 80) | 0; + _crypto_sign_ed25519_ref10_fe_mul($2, $r, $q); + $3 = ($q + 40) | 0; + _crypto_sign_ed25519_ref10_fe_mul($1, $1, $3); + $4 = ($r + 120) | 0; + $5 = ($q + 120) | 0; + $6 = ($p + 120) | 0; + _crypto_sign_ed25519_ref10_fe_mul($4, $5, $6); + $7 = ($p + 80) | 0; + $8 = ($q + 80) | 0; + _crypto_sign_ed25519_ref10_fe_mul($r, $7, $8); + _crypto_sign_ed25519_ref10_fe_add($t0, $r, $r); + _crypto_sign_ed25519_ref10_fe_sub($r, $2, $1); + _crypto_sign_ed25519_ref10_fe_add($1, $2, $1); + _crypto_sign_ed25519_ref10_fe_add($2, $t0, $4); + _crypto_sign_ed25519_ref10_fe_sub($4, $t0, $4); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_ge_double_scalarmult_vartime( + $r, + $a, + $A, + $b + ) { + $r = $r | 0; + $a = $a | 0; + $A = $A | 0; + $b = $b | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0; + var $27 = 0, + $28 = 0, + $29 = 0, + $3 = 0, + $30 = 0, + $31 = 0, + $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $37 = 0, + $38 = 0, + $39 = 0, + $4 = 0, + $40 = 0, + $41 = 0, + $5 = 0, + $6 = 0, + $7 = 0; + var $8 = 0, + $9 = 0, + $A2 = 0, + $Ai = 0, + $aslide = 0, + $bslide = 0, + $i$0$lcssa = 0, + $i$02 = 0, + $i$11 = 0, + $t = 0, + $u = 0, + label = 0, + sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 2272) | 0; + $aslide = (sp + 2016) | 0; + $bslide = (sp + 1760) | 0; + $Ai = (sp + 480) | 0; + $t = (sp + 320) | 0; + $u = (sp + 160) | 0; + $A2 = sp; + _slide($aslide, $a); + _slide($bslide, $b); + _crypto_sign_ed25519_ref10_ge_p3_to_cached($Ai, $A); + _crypto_sign_ed25519_ref10_ge_p3_dbl($t, $A); + _crypto_sign_ed25519_ref10_ge_p1p1_to_p3($A2, $t); + _crypto_sign_ed25519_ref10_ge_add($t, $A2, $Ai); + _crypto_sign_ed25519_ref10_ge_p1p1_to_p3($u, $t); + $0 = ($Ai + 160) | 0; + _crypto_sign_ed25519_ref10_ge_p3_to_cached($0, $u); + _crypto_sign_ed25519_ref10_ge_add($t, $A2, $0); + _crypto_sign_ed25519_ref10_ge_p1p1_to_p3($u, $t); + $1 = ($Ai + 320) | 0; + _crypto_sign_ed25519_ref10_ge_p3_to_cached($1, $u); + _crypto_sign_ed25519_ref10_ge_add($t, $A2, $1); + _crypto_sign_ed25519_ref10_ge_p1p1_to_p3($u, $t); + $2 = ($Ai + 480) | 0; + _crypto_sign_ed25519_ref10_ge_p3_to_cached($2, $u); + _crypto_sign_ed25519_ref10_ge_add($t, $A2, $2); + _crypto_sign_ed25519_ref10_ge_p1p1_to_p3($u, $t); + $3 = ($Ai + 640) | 0; + _crypto_sign_ed25519_ref10_ge_p3_to_cached($3, $u); + _crypto_sign_ed25519_ref10_ge_add($t, $A2, $3); + _crypto_sign_ed25519_ref10_ge_p1p1_to_p3($u, $t); + $4 = ($Ai + 800) | 0; + _crypto_sign_ed25519_ref10_ge_p3_to_cached($4, $u); + _crypto_sign_ed25519_ref10_ge_add($t, $A2, $4); + _crypto_sign_ed25519_ref10_ge_p1p1_to_p3($u, $t); + $5 = ($Ai + 960) | 0; + _crypto_sign_ed25519_ref10_ge_p3_to_cached($5, $u); + _crypto_sign_ed25519_ref10_ge_add($t, $A2, $5); + _crypto_sign_ed25519_ref10_ge_p1p1_to_p3($u, $t); + $6 = ($Ai + 1120) | 0; + _crypto_sign_ed25519_ref10_ge_p3_to_cached($6, $u); + _crypto_sign_ed25519_ref10_ge_p2_0($r); + $i$02 = 255; + while (1) { + $8 = ($aslide + $i$02) | 0; + $9 = HEAP8[$8 >> 0] | 0; + $10 = ($9 << 24) >> 24 == 0; + if (!$10) { + $i$0$lcssa = $i$02; + break; + } + $11 = ($bslide + $i$02) | 0; + $12 = HEAP8[$11 >> 0] | 0; + $13 = ($12 << 24) >> 24 == 0; + $14 = ($i$02 + -1) | 0; + if (!$13) { + $i$0$lcssa = $i$02; + break; + } + $7 = ($i$02 | 0) > 0; + if ($7) { + $i$02 = $14; + } else { + $i$0$lcssa = $14; + break; + } + } + $15 = ($i$0$lcssa | 0) > -1; + if ($15) { + $i$11 = $i$0$lcssa; + } else { + STACKTOP = sp; + return; + } + while (1) { + _crypto_sign_ed25519_ref10_ge_p2_dbl($t, $r); + $16 = ($aslide + $i$11) | 0; + $17 = HEAP8[$16 >> 0] | 0; + $18 = ($17 << 24) >> 24 > 0; + if ($18) { + _crypto_sign_ed25519_ref10_ge_p1p1_to_p3($u, $t); + $19 = HEAP8[$16 >> 0] | 0; + $20 = ($19 << 24) >> 24; + $21 = (($20 | 0) / 2) & -1; + $22 = ($Ai + (($21 * 160) | 0)) | 0; + _crypto_sign_ed25519_ref10_ge_add($t, $u, $22); + } else { + $23 = ($17 << 24) >> 24 < 0; + if ($23) { + _crypto_sign_ed25519_ref10_ge_p1p1_to_p3($u, $t); + $24 = HEAP8[$16 >> 0] | 0; + $25 = ($24 << 24) >> 24; + $26 = (($25 | 0) / -2) & -1; + $27 = ($Ai + (($26 * 160) | 0)) | 0; + _crypto_sign_ed25519_ref10_ge_sub($t, $u, $27); + } + } + $28 = ($bslide + $i$11) | 0; + $29 = HEAP8[$28 >> 0] | 0; + $30 = ($29 << 24) >> 24 > 0; + if ($30) { + _crypto_sign_ed25519_ref10_ge_p1p1_to_p3($u, $t); + $31 = HEAP8[$28 >> 0] | 0; + $32 = ($31 << 24) >> 24; + $33 = (($32 | 0) / 2) & -1; + $34 = (40 + (($33 * 120) | 0)) | 0; + _crypto_sign_ed25519_ref10_ge_madd($t, $u, $34); + } else { + $35 = ($29 << 24) >> 24 < 0; + if ($35) { + _crypto_sign_ed25519_ref10_ge_p1p1_to_p3($u, $t); + $36 = HEAP8[$28 >> 0] | 0; + $37 = ($36 << 24) >> 24; + $38 = (($37 | 0) / -2) & -1; + $39 = (40 + (($38 * 120) | 0)) | 0; + _crypto_sign_ed25519_ref10_ge_msub($t, $u, $39); + } + } + _crypto_sign_ed25519_ref10_ge_p1p1_to_p2($r, $t); + $40 = ($i$11 + -1) | 0; + $41 = ($i$11 | 0) > 0; + if ($41) { + $i$11 = $40; + } else { + break; + } + } + STACKTOP = sp; + return; + } + function _slide($r, $a) { + $r = $r | 0; + $a = $a | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0; + var $27 = 0, + $28 = 0, + $29 = 0, + $3 = 0, + $30 = 0, + $31 = 0, + $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + $b$03 = 0, + $exitcond = 0, + $exitcond10 = 0; + var $i$08 = 0, + $i$15 = 0, + $k$01 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $i$08 = 0; + while (1) { + $0 = $i$08 >> 3; + $1 = ($a + $0) | 0; + $2 = HEAP8[$1 >> 0] | 0; + $3 = $2 & 255; + $4 = $i$08 & 7; + $5 = $3 >>> $4; + $6 = $5 & 1; + $7 = $6 & 255; + $8 = ($r + $i$08) | 0; + HEAP8[$8 >> 0] = $7; + $9 = ($i$08 + 1) | 0; + $exitcond10 = ($9 | 0) == 256; + if ($exitcond10) { + $i$15 = 0; + break; + } else { + $i$08 = $9; + } + } + while (1) { + $10 = ($r + $i$15) | 0; + $11 = HEAP8[$10 >> 0] | 0; + $12 = ($11 << 24) >> 24 == 0; + L5: do { + if (!$12) { + $b$03 = 1; + while (1) { + $13 = ($b$03 + $i$15) | 0; + $14 = ($13 | 0) < 256; + if (!$14) { + break L5; + } + $15 = ($r + $13) | 0; + $16 = HEAP8[$15 >> 0] | 0; + $17 = ($16 << 24) >> 24 == 0; + L9: do { + if (!$17) { + $18 = HEAP8[$10 >> 0] | 0; + $19 = ($18 << 24) >> 24; + $20 = ($16 << 24) >> 24; + $21 = $20 << $b$03; + $22 = ($19 + $21) | 0; + $23 = ($22 | 0) < 16; + if ($23) { + $24 = $22 & 255; + HEAP8[$10 >> 0] = $24; + HEAP8[$15 >> 0] = 0; + break; + } + $25 = ($19 - $21) | 0; + $26 = ($25 | 0) > -16; + if (!$26) { + break L5; + } + $27 = $25 & 255; + HEAP8[$10 >> 0] = $27; + $28 = ($13 | 0) < 256; + if ($28) { + $k$01 = $13; + while (1) { + $29 = ($r + $k$01) | 0; + $30 = HEAP8[$29 >> 0] | 0; + $31 = ($30 << 24) >> 24 == 0; + if ($31) { + break; + } + HEAP8[$29 >> 0] = 0; + $32 = ($k$01 + 1) | 0; + $33 = ($32 | 0) < 256; + if ($33) { + $k$01 = $32; + } else { + break L9; + } + } + HEAP8[$29 >> 0] = 1; + } + } + } while (0); + $34 = ($b$03 + 1) | 0; + $35 = ($34 | 0) < 7; + if ($35) { + $b$03 = $34; + } else { + break; + } + } + } + } while (0); + $36 = ($i$15 + 1) | 0; + $exitcond = ($36 | 0) == 256; + if ($exitcond) { + break; + } else { + $i$15 = $36; + } + } + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_ge_frombytes_negate_vartime($h, $s) { + $h = $h | 0; + $s = $s | 0; + var $$0 = 0, + $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + $check = 0, + $u = 0, + $v = 0, + $v3 = 0, + $vxx = 0, + label = 0; + var sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 208) | 0; + $u = (sp + 160) | 0; + $v = (sp + 120) | 0; + $v3 = (sp + 80) | 0; + $vxx = (sp + 40) | 0; + $check = sp; + $0 = ($h + 40) | 0; + _crypto_sign_ed25519_ref10_fe_frombytes($0, $s); + $1 = ($h + 80) | 0; + _crypto_sign_ed25519_ref10_fe_1($1); + _crypto_sign_ed25519_ref10_fe_sq($u, $0); + _crypto_sign_ed25519_ref10_fe_mul($v, $u, 1000); + _crypto_sign_ed25519_ref10_fe_sub($u, $u, $1); + _crypto_sign_ed25519_ref10_fe_add($v, $v, $1); + _crypto_sign_ed25519_ref10_fe_sq($v3, $v); + _crypto_sign_ed25519_ref10_fe_mul($v3, $v3, $v); + _crypto_sign_ed25519_ref10_fe_sq($h, $v3); + _crypto_sign_ed25519_ref10_fe_mul($h, $h, $v); + _crypto_sign_ed25519_ref10_fe_mul($h, $h, $u); + _crypto_sign_ed25519_ref10_fe_pow22523($h, $h); + _crypto_sign_ed25519_ref10_fe_mul($h, $h, $v3); + _crypto_sign_ed25519_ref10_fe_mul($h, $h, $u); + _crypto_sign_ed25519_ref10_fe_sq($vxx, $h); + _crypto_sign_ed25519_ref10_fe_mul($vxx, $vxx, $v); + _crypto_sign_ed25519_ref10_fe_sub($check, $vxx, $u); + $2 = _crypto_sign_ed25519_ref10_fe_isnonzero($check) | 0; + $3 = ($2 | 0) == 0; + do { + if (!$3) { + _crypto_sign_ed25519_ref10_fe_add($check, $vxx, $u); + $4 = _crypto_sign_ed25519_ref10_fe_isnonzero($check) | 0; + $5 = ($4 | 0) == 0; + if ($5) { + _crypto_sign_ed25519_ref10_fe_mul($h, $h, 1040); + break; + } else { + $$0 = -1; + STACKTOP = sp; + return $$0 | 0; + } + } + } while (0); + $6 = _crypto_sign_ed25519_ref10_fe_isnegative($h) | 0; + $7 = ($s + 31) | 0; + $8 = HEAP8[$7 >> 0] | 0; + $9 = $8 & 255; + $10 = $9 >>> 7; + $11 = ($6 | 0) == ($10 | 0); + if ($11) { + _crypto_sign_ed25519_ref10_fe_neg($h, $h); + } + $12 = ($h + 120) | 0; + _crypto_sign_ed25519_ref10_fe_mul($12, $h, $0); + $$0 = 0; + STACKTOP = sp; + return $$0 | 0; + } + function _crypto_sign_ed25519_ref10_ge_madd($r, $p, $q) { + $r = $r | 0; + $p = $p | 0; + $q = $q | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $t0 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 48) | 0; + $t0 = sp; + $0 = ($p + 40) | 0; + _crypto_sign_ed25519_ref10_fe_add($r, $0, $p); + $1 = ($r + 40) | 0; + _crypto_sign_ed25519_ref10_fe_sub($1, $0, $p); + $2 = ($r + 80) | 0; + _crypto_sign_ed25519_ref10_fe_mul($2, $r, $q); + $3 = ($q + 40) | 0; + _crypto_sign_ed25519_ref10_fe_mul($1, $1, $3); + $4 = ($r + 120) | 0; + $5 = ($q + 80) | 0; + $6 = ($p + 120) | 0; + _crypto_sign_ed25519_ref10_fe_mul($4, $5, $6); + $7 = ($p + 80) | 0; + _crypto_sign_ed25519_ref10_fe_add($t0, $7, $7); + _crypto_sign_ed25519_ref10_fe_sub($r, $2, $1); + _crypto_sign_ed25519_ref10_fe_add($1, $2, $1); + _crypto_sign_ed25519_ref10_fe_add($2, $t0, $4); + _crypto_sign_ed25519_ref10_fe_sub($4, $t0, $4); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_ge_msub($r, $p, $q) { + $r = $r | 0; + $p = $p | 0; + $q = $q | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $t0 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 48) | 0; + $t0 = sp; + $0 = ($p + 40) | 0; + _crypto_sign_ed25519_ref10_fe_add($r, $0, $p); + $1 = ($r + 40) | 0; + _crypto_sign_ed25519_ref10_fe_sub($1, $0, $p); + $2 = ($r + 80) | 0; + $3 = ($q + 40) | 0; + _crypto_sign_ed25519_ref10_fe_mul($2, $r, $3); + _crypto_sign_ed25519_ref10_fe_mul($1, $1, $q); + $4 = ($r + 120) | 0; + $5 = ($q + 80) | 0; + $6 = ($p + 120) | 0; + _crypto_sign_ed25519_ref10_fe_mul($4, $5, $6); + $7 = ($p + 80) | 0; + _crypto_sign_ed25519_ref10_fe_add($t0, $7, $7); + _crypto_sign_ed25519_ref10_fe_sub($r, $2, $1); + _crypto_sign_ed25519_ref10_fe_add($1, $2, $1); + _crypto_sign_ed25519_ref10_fe_sub($2, $t0, $4); + _crypto_sign_ed25519_ref10_fe_add($4, $t0, $4); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_ge_p1p1_to_p2($r, $p) { + $r = $r | 0; + $p = $p | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = ($p + 120) | 0; + _crypto_sign_ed25519_ref10_fe_mul($r, $p, $0); + $1 = ($r + 40) | 0; + $2 = ($p + 40) | 0; + $3 = ($p + 80) | 0; + _crypto_sign_ed25519_ref10_fe_mul($1, $2, $3); + $4 = ($r + 80) | 0; + _crypto_sign_ed25519_ref10_fe_mul($4, $3, $0); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_ge_p1p1_to_p3($r, $p) { + $r = $r | 0; + $p = $p | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = ($p + 120) | 0; + _crypto_sign_ed25519_ref10_fe_mul($r, $p, $0); + $1 = ($r + 40) | 0; + $2 = ($p + 40) | 0; + $3 = ($p + 80) | 0; + _crypto_sign_ed25519_ref10_fe_mul($1, $2, $3); + $4 = ($r + 80) | 0; + _crypto_sign_ed25519_ref10_fe_mul($4, $3, $0); + $5 = ($r + 120) | 0; + _crypto_sign_ed25519_ref10_fe_mul($5, $p, $2); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_ge_p2_0($h) { + $h = $h | 0; + var $0 = 0, + $1 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + _crypto_sign_ed25519_ref10_fe_0($h); + $0 = ($h + 40) | 0; + _crypto_sign_ed25519_ref10_fe_1($0); + $1 = ($h + 80) | 0; + _crypto_sign_ed25519_ref10_fe_1($1); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_ge_p2_dbl($r, $p) { + $r = $r | 0; + $p = $p | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $t0 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 48) | 0; + $t0 = sp; + _crypto_sign_ed25519_ref10_fe_sq($r, $p); + $0 = ($r + 80) | 0; + $1 = ($p + 40) | 0; + _crypto_sign_ed25519_ref10_fe_sq($0, $1); + $2 = ($r + 120) | 0; + $3 = ($p + 80) | 0; + _crypto_sign_ed25519_ref10_fe_sq2($2, $3); + $4 = ($r + 40) | 0; + _crypto_sign_ed25519_ref10_fe_add($4, $p, $1); + _crypto_sign_ed25519_ref10_fe_sq($t0, $4); + _crypto_sign_ed25519_ref10_fe_add($4, $0, $r); + _crypto_sign_ed25519_ref10_fe_sub($0, $0, $r); + _crypto_sign_ed25519_ref10_fe_sub($r, $t0, $4); + _crypto_sign_ed25519_ref10_fe_sub($2, $2, $0); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_ge_p3_0($h) { + $h = $h | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + _crypto_sign_ed25519_ref10_fe_0($h); + $0 = ($h + 40) | 0; + _crypto_sign_ed25519_ref10_fe_1($0); + $1 = ($h + 80) | 0; + _crypto_sign_ed25519_ref10_fe_1($1); + $2 = ($h + 120) | 0; + _crypto_sign_ed25519_ref10_fe_0($2); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_ge_p3_dbl($r, $p) { + $r = $r | 0; + $p = $p | 0; + var $q = 0, + label = 0, + sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 128) | 0; + $q = sp; + _crypto_sign_ed25519_ref10_ge_p3_to_p2($q, $p); + _crypto_sign_ed25519_ref10_ge_p2_dbl($r, $q); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_ge_p3_to_cached($r, $p) { + $r = $r | 0; + $p = $p | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = ($p + 40) | 0; + _crypto_sign_ed25519_ref10_fe_add($r, $0, $p); + $1 = ($r + 40) | 0; + _crypto_sign_ed25519_ref10_fe_sub($1, $0, $p); + $2 = ($r + 80) | 0; + $3 = ($p + 80) | 0; + _crypto_sign_ed25519_ref10_fe_copy($2, $3); + $4 = ($r + 120) | 0; + $5 = ($p + 120) | 0; + _crypto_sign_ed25519_ref10_fe_mul($4, $5, 1080); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_ge_p3_to_p2($r, $p) { + $r = $r | 0; + $p = $p | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $3 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + _crypto_sign_ed25519_ref10_fe_copy($r, $p); + $0 = ($r + 40) | 0; + $1 = ($p + 40) | 0; + _crypto_sign_ed25519_ref10_fe_copy($0, $1); + $2 = ($r + 80) | 0; + $3 = ($p + 80) | 0; + _crypto_sign_ed25519_ref10_fe_copy($2, $3); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_ge_p3_tobytes($s, $h) { + $s = $s | 0; + $h = $h | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $recip = 0, + $x = 0, + $y = 0, + label = 0, + sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 128) | 0; + $recip = (sp + 80) | 0; + $x = (sp + 40) | 0; + $y = sp; + $0 = ($h + 80) | 0; + _crypto_sign_ed25519_ref10_fe_invert($recip, $0); + _crypto_sign_ed25519_ref10_fe_mul($x, $h, $recip); + $1 = ($h + 40) | 0; + _crypto_sign_ed25519_ref10_fe_mul($y, $1, $recip); + _crypto_sign_ed25519_ref10_fe_tobytes($s, $y); + $2 = _crypto_sign_ed25519_ref10_fe_isnegative($x) | 0; + $3 = $2 << 7; + $4 = ($s + 31) | 0; + $5 = HEAP8[$4 >> 0] | 0; + $6 = $5 & 255; + $7 = $6 ^ $3; + $8 = $7 & 255; + HEAP8[$4 >> 0] = $8; + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_ge_precomp_0($h) { + $h = $h | 0; + var $0 = 0, + $1 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + _crypto_sign_ed25519_ref10_fe_1($h); + $0 = ($h + 40) | 0; + _crypto_sign_ed25519_ref10_fe_1($0); + $1 = ($h + 80) | 0; + _crypto_sign_ed25519_ref10_fe_0($1); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_ge_scalarmult_base($h, $a) { + $h = $h | 0; + $a = $a | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0; + var $27 = 0, + $28 = 0, + $29 = 0, + $3 = 0, + $30 = 0, + $31 = 0, + $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + $carry$04 = 0, + $e = 0, + $exitcond = 0, + $exitcond7 = 0; + var $i$06 = 0, + $i$15 = 0, + $i$23 = 0, + $i$32 = 0, + $r = 0, + $s = 0, + $sext = 0, + $sext1 = 0, + $t = 0, + label = 0, + sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 464) | 0; + $e = (sp + 400) | 0; + $r = (sp + 240) | 0; + $s = (sp + 120) | 0; + $t = sp; + $i$06 = 0; + while (1) { + $0 = ($a + $i$06) | 0; + $1 = HEAP8[$0 >> 0] | 0; + $2 = $1 & 255; + $3 = $2 & 15; + $4 = $3 & 255; + $5 = $i$06 << 1; + $6 = ($e + $5) | 0; + HEAP8[$6 >> 0] = $4; + $7 = HEAP8[$0 >> 0] | 0; + $8 = ($7 & 255) >>> 4; + $9 = $5 | 1; + $10 = ($e + $9) | 0; + HEAP8[$10 >> 0] = $8; + $11 = ($i$06 + 1) | 0; + $exitcond7 = ($11 | 0) == 32; + if ($exitcond7) { + $carry$04 = 0; + $i$15 = 0; + break; + } else { + $i$06 = $11; + } + } + while (1) { + $12 = ($e + $i$15) | 0; + $13 = HEAP8[$12 >> 0] | 0; + $14 = $13 & 255; + $15 = ($14 + $carry$04) | 0; + $sext = $15 << 24; + $sext1 = ($sext + 134217728) | 0; + $16 = $sext1 >> 28; + $17 = $16 << 4; + $18 = ($15 - $17) | 0; + $19 = $18 & 255; + HEAP8[$12 >> 0] = $19; + $20 = ($i$15 + 1) | 0; + $exitcond = ($20 | 0) == 63; + if ($exitcond) { + break; + } else { + $carry$04 = $16; + $i$15 = $20; + } + } + $21 = ($e + 63) | 0; + $22 = HEAP8[$21 >> 0] | 0; + $23 = $22 & 255; + $24 = ($23 + $16) | 0; + $25 = $24 & 255; + HEAP8[$21 >> 0] = $25; + _crypto_sign_ed25519_ref10_ge_p3_0($h); + $i$23 = 1; + while (1) { + $26 = (($i$23 | 0) / 2) & -1; + $27 = ($e + $i$23) | 0; + $28 = HEAP8[$27 >> 0] | 0; + _select($t, $26, $28); + _crypto_sign_ed25519_ref10_ge_madd($r, $h, $t); + _crypto_sign_ed25519_ref10_ge_p1p1_to_p3($h, $r); + $29 = ($i$23 + 2) | 0; + $30 = ($29 | 0) < 64; + if ($30) { + $i$23 = $29; + } else { + break; + } + } + _crypto_sign_ed25519_ref10_ge_p3_dbl($r, $h); + _crypto_sign_ed25519_ref10_ge_p1p1_to_p2($s, $r); + _crypto_sign_ed25519_ref10_ge_p2_dbl($r, $s); + _crypto_sign_ed25519_ref10_ge_p1p1_to_p2($s, $r); + _crypto_sign_ed25519_ref10_ge_p2_dbl($r, $s); + _crypto_sign_ed25519_ref10_ge_p1p1_to_p2($s, $r); + _crypto_sign_ed25519_ref10_ge_p2_dbl($r, $s); + _crypto_sign_ed25519_ref10_ge_p1p1_to_p3($h, $r); + $i$32 = 0; + while (1) { + $31 = (($i$32 | 0) / 2) & -1; + $32 = ($e + $i$32) | 0; + $33 = HEAP8[$32 >> 0] | 0; + _select($t, $31, $33); + _crypto_sign_ed25519_ref10_ge_madd($r, $h, $t); + _crypto_sign_ed25519_ref10_ge_p1p1_to_p3($h, $r); + $34 = ($i$32 + 2) | 0; + $35 = ($34 | 0) < 64; + if ($35) { + $i$32 = $34; + } else { + break; + } + } + STACKTOP = sp; + return; + } + function _select($t, $pos, $b) { + $t = $t | 0; + $pos = $pos | 0; + $b = $b | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0; + var $27 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + $minust = 0, + label = 0, + sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 128) | 0; + $minust = sp; + $0 = _negative($b) | 0; + $1 = ($b << 24) >> 24; + $2 = $0 & 255; + $3 = (0 - $2) | 0; + $4 = $1 & $3; + $5 = $4 << 1; + $6 = ($1 - $5) | 0; + $7 = $6 & 255; + _crypto_sign_ed25519_ref10_ge_precomp_0($t); + $8 = (1120 + (($pos * 960) | 0)) | 0; + $9 = _equal($7, 1) | 0; + _cmov($t, $8, $9); + $10 = (((1120 + (($pos * 960) | 0)) | 0) + 120) | 0; + $11 = _equal($7, 2) | 0; + _cmov($t, $10, $11); + $12 = (((1120 + (($pos * 960) | 0)) | 0) + 240) | 0; + $13 = _equal($7, 3) | 0; + _cmov($t, $12, $13); + $14 = (((1120 + (($pos * 960) | 0)) | 0) + 360) | 0; + $15 = _equal($7, 4) | 0; + _cmov($t, $14, $15); + $16 = (((1120 + (($pos * 960) | 0)) | 0) + 480) | 0; + $17 = _equal($7, 5) | 0; + _cmov($t, $16, $17); + $18 = (((1120 + (($pos * 960) | 0)) | 0) + 600) | 0; + $19 = _equal($7, 6) | 0; + _cmov($t, $18, $19); + $20 = (((1120 + (($pos * 960) | 0)) | 0) + 720) | 0; + $21 = _equal($7, 7) | 0; + _cmov($t, $20, $21); + $22 = (((1120 + (($pos * 960) | 0)) | 0) + 840) | 0; + $23 = _equal($7, 8) | 0; + _cmov($t, $22, $23); + $24 = ($t + 40) | 0; + _crypto_sign_ed25519_ref10_fe_copy($minust, $24); + $25 = ($minust + 40) | 0; + _crypto_sign_ed25519_ref10_fe_copy($25, $t); + $26 = ($minust + 80) | 0; + $27 = ($t + 80) | 0; + _crypto_sign_ed25519_ref10_fe_neg($26, $27); + _cmov($t, $minust, $0); + STACKTOP = sp; + return; + } + function _negative($b) { + $b = $b | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = ($b << 24) >> 24; + $1 = ($0 | 0) < 0; + $2 = ($1 << 31) >> 31; + $3 = _bitshift64Lshr($0 | 0, $2 | 0, 63) | 0; + $4 = tempRet0; + $5 = $3 & 255; + STACKTOP = sp; + return $5 | 0; + } + function _equal($b, $c) { + $b = $b | 0; + $c = $c | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = $c ^ $b; + $1 = $0 & 255; + $2 = ($1 + -1) | 0; + $3 = $2 >>> 31; + $4 = $3 & 255; + STACKTOP = sp; + return $4 | 0; + } + function _cmov($t, $u, $b) { + $t = $t | 0; + $u = $u | 0; + $b = $b | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = $b & 255; + _crypto_sign_ed25519_ref10_fe_cmov($t, $u, $0); + $1 = ($t + 40) | 0; + $2 = ($u + 40) | 0; + _crypto_sign_ed25519_ref10_fe_cmov($1, $2, $0); + $3 = ($t + 80) | 0; + $4 = ($u + 80) | 0; + _crypto_sign_ed25519_ref10_fe_cmov($3, $4, $0); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_ge_sub($r, $p, $q) { + $r = $r | 0; + $p = $p | 0; + $q = $q | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $t0 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 48) | 0; + $t0 = sp; + $0 = ($p + 40) | 0; + _crypto_sign_ed25519_ref10_fe_add($r, $0, $p); + $1 = ($r + 40) | 0; + _crypto_sign_ed25519_ref10_fe_sub($1, $0, $p); + $2 = ($r + 80) | 0; + $3 = ($q + 40) | 0; + _crypto_sign_ed25519_ref10_fe_mul($2, $r, $3); + _crypto_sign_ed25519_ref10_fe_mul($1, $1, $q); + $4 = ($r + 120) | 0; + $5 = ($q + 120) | 0; + $6 = ($p + 120) | 0; + _crypto_sign_ed25519_ref10_fe_mul($4, $5, $6); + $7 = ($p + 80) | 0; + $8 = ($q + 80) | 0; + _crypto_sign_ed25519_ref10_fe_mul($r, $7, $8); + _crypto_sign_ed25519_ref10_fe_add($t0, $r, $r); + _crypto_sign_ed25519_ref10_fe_sub($r, $2, $1); + _crypto_sign_ed25519_ref10_fe_add($1, $2, $1); + _crypto_sign_ed25519_ref10_fe_sub($2, $t0, $4); + _crypto_sign_ed25519_ref10_fe_add($4, $t0, $4); + STACKTOP = sp; + return; + } + function _crypto_sign_ed25519_ref10_ge_tobytes($s, $h) { + $s = $s | 0; + $h = $h | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $recip = 0, + $x = 0, + $y = 0, + label = 0, + sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 128) | 0; + $recip = (sp + 80) | 0; + $x = (sp + 40) | 0; + $y = sp; + $0 = ($h + 80) | 0; + _crypto_sign_ed25519_ref10_fe_invert($recip, $0); + _crypto_sign_ed25519_ref10_fe_mul($x, $h, $recip); + $1 = ($h + 40) | 0; + _crypto_sign_ed25519_ref10_fe_mul($y, $1, $recip); + _crypto_sign_ed25519_ref10_fe_tobytes($s, $y); + $2 = _crypto_sign_ed25519_ref10_fe_isnegative($x) | 0; + $3 = $2 << 7; + $4 = ($s + 31) | 0; + $5 = HEAP8[$4 >> 0] | 0; + $6 = $5 & 255; + $7 = $6 ^ $3; + $8 = $7 & 255; + HEAP8[$4 >> 0] = $8; + STACKTOP = sp; + return; + } + function _crypto_sign_edwards25519sha512batch_ref10_open( + $m, + $mlen, + $sm, + $0, + $1, + $pk + ) { + $m = $m | 0; + $mlen = $mlen | 0; + $sm = $sm | 0; + $0 = $0 | 0; + $1 = $1 | 0; + $pk = $pk | 0; + var $$0 = 0, + $$sum = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0; + var $27 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + $A = 0, + $R = 0, + $h = 0, + $pkcopy1 = 0, + $rcheck = 0, + $rcopy = 0, + $scopy = 0, + dest = 0, + label = 0, + sp = 0, + src = 0, + stop = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 480) | 0; + $pkcopy1 = (sp + 440) | 0; + $rcopy = (sp + 408) | 0; + $scopy = (sp + 376) | 0; + $h = (sp + 312) | 0; + $rcheck = (sp + 280) | 0; + $A = (sp + 120) | 0; + $R = sp; + $2 = $1 >>> 0 < 0; + $3 = $0 >>> 0 < 64; + $4 = ($1 | 0) == 0; + $5 = $4 & $3; + $6 = $2 | $5; + if (!$6) { + $7 = ($sm + 63) | 0; + $8 = HEAP8[$7 >> 0] | 0; + $9 = ($8 & 255) > 31; + if (!$9) { + $10 = + _crypto_sign_ed25519_ref10_ge_frombytes_negate_vartime($A, $pk) | 0; + $11 = ($10 | 0) == 0; + if ($11) { + dest = ($pkcopy1 + 0) | 0; + src = ($pk + 0) | 0; + stop = (dest + 32) | 0; + do { + HEAP8[dest >> 0] = HEAP8[src >> 0] | 0; + dest = (dest + 1) | 0; + src = (src + 1) | 0; + } while ((dest | 0) < (stop | 0)); + dest = ($rcopy + 0) | 0; + src = ($sm + 0) | 0; + stop = (dest + 32) | 0; + do { + HEAP8[dest >> 0] = HEAP8[src >> 0] | 0; + dest = (dest + 1) | 0; + src = (src + 1) | 0; + } while ((dest | 0) < (stop | 0)); + $12 = ($sm + 32) | 0; + dest = ($scopy + 0) | 0; + src = ($12 + 0) | 0; + stop = (dest + 32) | 0; + do { + HEAP8[dest >> 0] = HEAP8[src >> 0] | 0; + dest = (dest + 1) | 0; + src = (src + 1) | 0; + } while ((dest | 0) < (stop | 0)); + _memmove($m | 0, $sm | 0, $0 | 0) | 0; + $13 = ($m + 32) | 0; + dest = ($13 + 0) | 0; + src = ($pkcopy1 + 0) | 0; + stop = (dest + 32) | 0; + do { + HEAP8[dest >> 0] = HEAP8[src >> 0] | 0; + dest = (dest + 1) | 0; + src = (src + 1) | 0; + } while ((dest | 0) < (stop | 0)); + _crypto_hash_sha512_ref($h, $m, $0, $1) | 0; + _crypto_sign_ed25519_ref10_sc_reduce($h); + _crypto_sign_ed25519_ref10_ge_double_scalarmult_vartime( + $R, + $h, + $A, + $scopy + ); + _crypto_sign_ed25519_ref10_ge_tobytes($rcheck, $R); + $14 = _crypto_verify_32_ref($rcheck, $rcopy) | 0; + $15 = ($14 | 0) == 0; + if ($15) { + $16 = ($m + 64) | 0; + $17 = _i64Add($0 | 0, $1 | 0, -64, -1) | 0; + $18 = tempRet0; + _memmove($m | 0, $16 | 0, $17 | 0) | 0; + $$sum = ($0 + -64) | 0; + $19 = ($m + $$sum) | 0; + dest = ($19 + 0) | 0; + stop = (dest + 64) | 0; + do { + HEAP8[dest >> 0] = 0 | 0; + dest = (dest + 1) | 0; + } while ((dest | 0) < (stop | 0)); + $20 = $mlen; + $21 = $20; + HEAP32[$21 >> 2] = $17; + $22 = ($20 + 4) | 0; + $23 = $22; + HEAP32[$23 >> 2] = $18; + $$0 = 0; + STACKTOP = sp; + return $$0 | 0; + } + } + } + } + $24 = $mlen; + $25 = $24; + HEAP32[$25 >> 2] = -1; + $26 = ($24 + 4) | 0; + $27 = $26; + HEAP32[$27 >> 2] = -1; + _memset($m | 0, 0, $0 | 0) | 0; + $$0 = -1; + STACKTOP = sp; + return $$0 | 0; + } + function _crypto_sign_ed25519_ref10_sc_muladd($s, $a, $b, $c) { + $s = $s | 0; + $a = $a | 0; + $b = $b | 0; + $c = $c | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $1000 = 0, + $1001 = 0, + $1002 = 0, + $1003 = 0, + $1004 = 0, + $1005 = 0, + $1006 = 0, + $1007 = 0, + $1008 = 0, + $1009 = 0, + $101 = 0, + $1010 = 0, + $1011 = 0, + $1012 = 0, + $1013 = 0, + $1014 = 0; + var $1015 = 0, + $1016 = 0, + $1017 = 0, + $1018 = 0, + $1019 = 0, + $102 = 0, + $1020 = 0, + $1021 = 0, + $1022 = 0, + $1023 = 0, + $1024 = 0, + $1025 = 0, + $1026 = 0, + $1027 = 0, + $1028 = 0, + $1029 = 0, + $103 = 0, + $1030 = 0, + $1031 = 0, + $1032 = 0; + var $1033 = 0, + $1034 = 0, + $1035 = 0, + $1036 = 0, + $1037 = 0, + $1038 = 0, + $1039 = 0, + $104 = 0, + $1040 = 0, + $1041 = 0, + $1042 = 0, + $1043 = 0, + $1044 = 0, + $1045 = 0, + $1046 = 0, + $1047 = 0, + $1048 = 0, + $1049 = 0, + $105 = 0, + $1050 = 0; + var $1051 = 0, + $1052 = 0, + $1053 = 0, + $1054 = 0, + $1055 = 0, + $1056 = 0, + $1057 = 0, + $1058 = 0, + $1059 = 0, + $106 = 0, + $1060 = 0, + $1061 = 0, + $1062 = 0, + $1063 = 0, + $1064 = 0, + $1065 = 0, + $1066 = 0, + $1067 = 0, + $1068 = 0, + $1069 = 0; + var $107 = 0, + $1070 = 0, + $1071 = 0, + $1072 = 0, + $1073 = 0, + $1074 = 0, + $1075 = 0, + $1076 = 0, + $1077 = 0, + $1078 = 0, + $1079 = 0, + $108 = 0, + $1080 = 0, + $1081 = 0, + $1082 = 0, + $1083 = 0, + $1084 = 0, + $1085 = 0, + $1086 = 0, + $1087 = 0; + var $1088 = 0, + $1089 = 0, + $109 = 0, + $1090 = 0, + $1091 = 0, + $1092 = 0, + $1093 = 0, + $1094 = 0, + $1095 = 0, + $1096 = 0, + $1097 = 0, + $1098 = 0, + $1099 = 0, + $11 = 0, + $110 = 0, + $1100 = 0, + $1101 = 0, + $1102 = 0, + $1103 = 0, + $1104 = 0; + var $1105 = 0, + $1106 = 0, + $1107 = 0, + $1108 = 0, + $1109 = 0, + $111 = 0, + $1110 = 0, + $1111 = 0, + $1112 = 0, + $1113 = 0, + $1114 = 0, + $1115 = 0, + $1116 = 0, + $1117 = 0, + $1118 = 0, + $1119 = 0, + $112 = 0, + $1120 = 0, + $1121 = 0, + $1122 = 0; + var $1123 = 0, + $1124 = 0, + $1125 = 0, + $1126 = 0, + $1127 = 0, + $1128 = 0, + $1129 = 0, + $113 = 0, + $1130 = 0, + $1131 = 0, + $1132 = 0, + $1133 = 0, + $1134 = 0, + $1135 = 0, + $1136 = 0, + $1137 = 0, + $1138 = 0, + $1139 = 0, + $114 = 0, + $1140 = 0; + var $1141 = 0, + $1142 = 0, + $1143 = 0, + $1144 = 0, + $1145 = 0, + $1146 = 0, + $1147 = 0, + $1148 = 0, + $1149 = 0, + $115 = 0, + $1150 = 0, + $1151 = 0, + $1152 = 0, + $1153 = 0, + $1154 = 0, + $1155 = 0, + $1156 = 0, + $1157 = 0, + $1158 = 0, + $1159 = 0; + var $116 = 0, + $1160 = 0, + $1161 = 0, + $1162 = 0, + $1163 = 0, + $1164 = 0, + $1165 = 0, + $1166 = 0, + $1167 = 0, + $1168 = 0, + $1169 = 0, + $117 = 0, + $1170 = 0, + $1171 = 0, + $1172 = 0, + $1173 = 0, + $1174 = 0, + $1175 = 0, + $1176 = 0, + $1177 = 0; + var $1178 = 0, + $1179 = 0, + $118 = 0, + $1180 = 0, + $1181 = 0, + $1182 = 0, + $1183 = 0, + $1184 = 0, + $1185 = 0, + $1186 = 0, + $1187 = 0, + $1188 = 0, + $1189 = 0, + $119 = 0, + $1190 = 0, + $1191 = 0, + $1192 = 0, + $1193 = 0, + $1194 = 0, + $1195 = 0; + var $1196 = 0, + $1197 = 0, + $1198 = 0, + $1199 = 0, + $12 = 0, + $120 = 0, + $1200 = 0, + $1201 = 0, + $1202 = 0, + $1203 = 0, + $1204 = 0, + $1205 = 0, + $1206 = 0, + $1207 = 0, + $1208 = 0, + $1209 = 0, + $121 = 0, + $1210 = 0, + $1211 = 0, + $1212 = 0; + var $1213 = 0, + $1214 = 0, + $1215 = 0, + $1216 = 0, + $1217 = 0, + $1218 = 0, + $1219 = 0, + $122 = 0, + $1220 = 0, + $1221 = 0, + $1222 = 0, + $1223 = 0, + $1224 = 0, + $1225 = 0, + $1226 = 0, + $1227 = 0, + $1228 = 0, + $1229 = 0, + $123 = 0, + $1230 = 0; + var $1231 = 0, + $1232 = 0, + $1233 = 0, + $1234 = 0, + $1235 = 0, + $1236 = 0, + $1237 = 0, + $1238 = 0, + $1239 = 0, + $124 = 0, + $1240 = 0, + $1241 = 0, + $1242 = 0, + $1243 = 0, + $1244 = 0, + $1245 = 0, + $1246 = 0, + $1247 = 0, + $1248 = 0, + $1249 = 0; + var $125 = 0, + $1250 = 0, + $1251 = 0, + $1252 = 0, + $1253 = 0, + $1254 = 0, + $1255 = 0, + $1256 = 0, + $1257 = 0, + $1258 = 0, + $1259 = 0, + $126 = 0, + $1260 = 0, + $1261 = 0, + $1262 = 0, + $1263 = 0, + $1264 = 0, + $1265 = 0, + $1266 = 0, + $1267 = 0; + var $1268 = 0, + $1269 = 0, + $127 = 0, + $1270 = 0, + $1271 = 0, + $1272 = 0, + $1273 = 0, + $1274 = 0, + $1275 = 0, + $1276 = 0, + $1277 = 0, + $1278 = 0, + $1279 = 0, + $128 = 0, + $1280 = 0, + $1281 = 0, + $1282 = 0, + $1283 = 0, + $1284 = 0, + $1285 = 0; + var $1286 = 0, + $1287 = 0, + $1288 = 0, + $1289 = 0, + $129 = 0, + $1290 = 0, + $1291 = 0, + $1292 = 0, + $1293 = 0, + $1294 = 0, + $1295 = 0, + $1296 = 0, + $1297 = 0, + $1298 = 0, + $1299 = 0, + $13 = 0, + $130 = 0, + $1300 = 0, + $1301 = 0, + $1302 = 0; + var $1303 = 0, + $1304 = 0, + $1305 = 0, + $1306 = 0, + $1307 = 0, + $1308 = 0, + $1309 = 0, + $131 = 0, + $1310 = 0, + $1311 = 0, + $1312 = 0, + $1313 = 0, + $1314 = 0, + $1315 = 0, + $1316 = 0, + $1317 = 0, + $1318 = 0, + $1319 = 0, + $132 = 0, + $1320 = 0; + var $1321 = 0, + $1322 = 0, + $1323 = 0, + $1324 = 0, + $1325 = 0, + $1326 = 0, + $1327 = 0, + $1328 = 0, + $1329 = 0, + $133 = 0, + $1330 = 0, + $1331 = 0, + $1332 = 0, + $1333 = 0, + $1334 = 0, + $1335 = 0, + $1336 = 0, + $1337 = 0, + $1338 = 0, + $1339 = 0; + var $134 = 0, + $1340 = 0, + $1341 = 0, + $1342 = 0, + $1343 = 0, + $1344 = 0, + $1345 = 0, + $1346 = 0, + $1347 = 0, + $1348 = 0, + $1349 = 0, + $135 = 0, + $1350 = 0, + $1351 = 0, + $1352 = 0, + $1353 = 0, + $1354 = 0, + $1355 = 0, + $1356 = 0, + $1357 = 0; + var $1358 = 0, + $1359 = 0, + $136 = 0, + $1360 = 0, + $1361 = 0, + $1362 = 0, + $1363 = 0, + $1364 = 0, + $1365 = 0, + $1366 = 0, + $1367 = 0, + $1368 = 0, + $1369 = 0, + $137 = 0, + $1370 = 0, + $1371 = 0, + $1372 = 0, + $1373 = 0, + $1374 = 0, + $1375 = 0; + var $1376 = 0, + $1377 = 0, + $1378 = 0, + $1379 = 0, + $138 = 0, + $1380 = 0, + $1381 = 0, + $1382 = 0, + $1383 = 0, + $1384 = 0, + $1385 = 0, + $1386 = 0, + $1387 = 0, + $1388 = 0, + $1389 = 0, + $139 = 0, + $1390 = 0, + $1391 = 0, + $1392 = 0, + $1393 = 0; + var $1394 = 0, + $1395 = 0, + $1396 = 0, + $1397 = 0, + $1398 = 0, + $1399 = 0, + $14 = 0, + $140 = 0, + $1400 = 0, + $1401 = 0, + $1402 = 0, + $1403 = 0, + $1404 = 0, + $1405 = 0, + $1406 = 0, + $1407 = 0, + $1408 = 0, + $1409 = 0, + $141 = 0, + $1410 = 0; + var $1411 = 0, + $1412 = 0, + $1413 = 0, + $1414 = 0, + $1415 = 0, + $1416 = 0, + $1417 = 0, + $1418 = 0, + $1419 = 0, + $142 = 0, + $1420 = 0, + $1421 = 0, + $1422 = 0, + $1423 = 0, + $1424 = 0, + $1425 = 0, + $1426 = 0, + $1427 = 0, + $1428 = 0, + $1429 = 0; + var $143 = 0, + $1430 = 0, + $1431 = 0, + $1432 = 0, + $1433 = 0, + $1434 = 0, + $1435 = 0, + $1436 = 0, + $1437 = 0, + $1438 = 0, + $1439 = 0, + $144 = 0, + $1440 = 0, + $1441 = 0, + $1442 = 0, + $1443 = 0, + $1444 = 0, + $1445 = 0, + $1446 = 0, + $1447 = 0; + var $1448 = 0, + $1449 = 0, + $145 = 0, + $1450 = 0, + $1451 = 0, + $1452 = 0, + $1453 = 0, + $1454 = 0, + $1455 = 0, + $1456 = 0, + $1457 = 0, + $1458 = 0, + $1459 = 0, + $146 = 0, + $1460 = 0, + $1461 = 0, + $1462 = 0, + $1463 = 0, + $1464 = 0, + $1465 = 0; + var $1466 = 0, + $1467 = 0, + $1468 = 0, + $1469 = 0, + $147 = 0, + $1470 = 0, + $1471 = 0, + $1472 = 0, + $1473 = 0, + $1474 = 0, + $1475 = 0, + $1476 = 0, + $1477 = 0, + $1478 = 0, + $1479 = 0, + $148 = 0, + $1480 = 0, + $1481 = 0, + $1482 = 0, + $1483 = 0; + var $1484 = 0, + $1485 = 0, + $1486 = 0, + $1487 = 0, + $1488 = 0, + $1489 = 0, + $149 = 0, + $1490 = 0, + $1491 = 0, + $1492 = 0, + $1493 = 0, + $1494 = 0, + $1495 = 0, + $1496 = 0, + $1497 = 0, + $1498 = 0, + $1499 = 0, + $15 = 0, + $150 = 0, + $1500 = 0; + var $1501 = 0, + $1502 = 0, + $1503 = 0, + $1504 = 0, + $1505 = 0, + $1506 = 0, + $1507 = 0, + $1508 = 0, + $1509 = 0, + $151 = 0, + $1510 = 0, + $1511 = 0, + $1512 = 0, + $1513 = 0, + $1514 = 0, + $1515 = 0, + $1516 = 0, + $1517 = 0, + $1518 = 0, + $1519 = 0; + var $152 = 0, + $1520 = 0, + $1521 = 0, + $1522 = 0, + $1523 = 0, + $1524 = 0, + $1525 = 0, + $1526 = 0, + $1527 = 0, + $1528 = 0, + $1529 = 0, + $153 = 0, + $1530 = 0, + $1531 = 0, + $1532 = 0, + $1533 = 0, + $1534 = 0, + $1535 = 0, + $1536 = 0, + $1537 = 0; + var $1538 = 0, + $1539 = 0, + $154 = 0, + $1540 = 0, + $1541 = 0, + $1542 = 0, + $1543 = 0, + $1544 = 0, + $1545 = 0, + $1546 = 0, + $1547 = 0, + $1548 = 0, + $1549 = 0, + $155 = 0, + $1550 = 0, + $1551 = 0, + $1552 = 0, + $1553 = 0, + $1554 = 0, + $1555 = 0; + var $1556 = 0, + $1557 = 0, + $1558 = 0, + $1559 = 0, + $156 = 0, + $1560 = 0, + $1561 = 0, + $1562 = 0, + $1563 = 0, + $1564 = 0, + $1565 = 0, + $1566 = 0, + $1567 = 0, + $1568 = 0, + $1569 = 0, + $157 = 0, + $1570 = 0, + $1571 = 0, + $1572 = 0, + $1573 = 0; + var $1574 = 0, + $1575 = 0, + $1576 = 0, + $1577 = 0, + $1578 = 0, + $1579 = 0, + $158 = 0, + $1580 = 0, + $1581 = 0, + $1582 = 0, + $1583 = 0, + $1584 = 0, + $1585 = 0, + $1586 = 0, + $1587 = 0, + $1588 = 0, + $1589 = 0, + $159 = 0, + $1590 = 0, + $1591 = 0; + var $1592 = 0, + $1593 = 0, + $1594 = 0, + $1595 = 0, + $1596 = 0, + $1597 = 0, + $1598 = 0, + $1599 = 0, + $16 = 0, + $160 = 0, + $1600 = 0, + $1601 = 0, + $1602 = 0, + $1603 = 0, + $1604 = 0, + $1605 = 0, + $1606 = 0, + $1607 = 0, + $1608 = 0, + $1609 = 0; + var $161 = 0, + $1610 = 0, + $1611 = 0, + $1612 = 0, + $1613 = 0, + $1614 = 0, + $1615 = 0, + $1616 = 0, + $1617 = 0, + $1618 = 0, + $1619 = 0, + $162 = 0, + $1620 = 0, + $1621 = 0, + $1622 = 0, + $1623 = 0, + $1624 = 0, + $1625 = 0, + $1626 = 0, + $1627 = 0; + var $1628 = 0, + $1629 = 0, + $163 = 0, + $1630 = 0, + $1631 = 0, + $1632 = 0, + $1633 = 0, + $1634 = 0, + $1635 = 0, + $1636 = 0, + $1637 = 0, + $1638 = 0, + $1639 = 0, + $164 = 0, + $1640 = 0, + $1641 = 0, + $1642 = 0, + $1643 = 0, + $1644 = 0, + $1645 = 0; + var $1646 = 0, + $1647 = 0, + $1648 = 0, + $1649 = 0, + $165 = 0, + $1650 = 0, + $1651 = 0, + $1652 = 0, + $1653 = 0, + $1654 = 0, + $1655 = 0, + $1656 = 0, + $1657 = 0, + $1658 = 0, + $1659 = 0, + $166 = 0, + $1660 = 0, + $1661 = 0, + $1662 = 0, + $1663 = 0; + var $1664 = 0, + $1665 = 0, + $1666 = 0, + $1667 = 0, + $1668 = 0, + $1669 = 0, + $167 = 0, + $1670 = 0, + $1671 = 0, + $1672 = 0, + $1673 = 0, + $1674 = 0, + $1675 = 0, + $1676 = 0, + $1677 = 0, + $1678 = 0, + $1679 = 0, + $168 = 0, + $1680 = 0, + $1681 = 0; + var $1682 = 0, + $1683 = 0, + $1684 = 0, + $1685 = 0, + $1686 = 0, + $1687 = 0, + $1688 = 0, + $1689 = 0, + $169 = 0, + $1690 = 0, + $1691 = 0, + $1692 = 0, + $1693 = 0, + $1694 = 0, + $1695 = 0, + $1696 = 0, + $1697 = 0, + $1698 = 0, + $1699 = 0, + $17 = 0; + var $170 = 0, + $1700 = 0, + $1701 = 0, + $1702 = 0, + $1703 = 0, + $1704 = 0, + $1705 = 0, + $1706 = 0, + $1707 = 0, + $1708 = 0, + $1709 = 0, + $171 = 0, + $1710 = 0, + $1711 = 0, + $1712 = 0, + $1713 = 0, + $1714 = 0, + $1715 = 0, + $1716 = 0, + $1717 = 0; + var $1718 = 0, + $1719 = 0, + $172 = 0, + $1720 = 0, + $1721 = 0, + $1722 = 0, + $1723 = 0, + $1724 = 0, + $1725 = 0, + $1726 = 0, + $1727 = 0, + $1728 = 0, + $1729 = 0, + $173 = 0, + $1730 = 0, + $1731 = 0, + $1732 = 0, + $1733 = 0, + $1734 = 0, + $1735 = 0; + var $1736 = 0, + $1737 = 0, + $1738 = 0, + $1739 = 0, + $174 = 0, + $1740 = 0, + $1741 = 0, + $1742 = 0, + $1743 = 0, + $1744 = 0, + $1745 = 0, + $1746 = 0, + $1747 = 0, + $1748 = 0, + $1749 = 0, + $175 = 0, + $1750 = 0, + $1751 = 0, + $1752 = 0, + $1753 = 0; + var $1754 = 0, + $1755 = 0, + $1756 = 0, + $1757 = 0, + $1758 = 0, + $1759 = 0, + $176 = 0, + $1760 = 0, + $1761 = 0, + $1762 = 0, + $1763 = 0, + $1764 = 0, + $1765 = 0, + $1766 = 0, + $1767 = 0, + $1768 = 0, + $1769 = 0, + $177 = 0, + $1770 = 0, + $1771 = 0; + var $1772 = 0, + $1773 = 0, + $1774 = 0, + $1775 = 0, + $1776 = 0, + $1777 = 0, + $1778 = 0, + $1779 = 0, + $178 = 0, + $1780 = 0, + $1781 = 0, + $1782 = 0, + $1783 = 0, + $1784 = 0, + $1785 = 0, + $1786 = 0, + $1787 = 0, + $1788 = 0, + $1789 = 0, + $179 = 0; + var $1790 = 0, + $1791 = 0, + $1792 = 0, + $1793 = 0, + $1794 = 0, + $1795 = 0, + $1796 = 0, + $1797 = 0, + $1798 = 0, + $1799 = 0, + $18 = 0, + $180 = 0, + $1800 = 0, + $1801 = 0, + $1802 = 0, + $1803 = 0, + $1804 = 0, + $1805 = 0, + $1806 = 0, + $1807 = 0; + var $1808 = 0, + $1809 = 0, + $181 = 0, + $1810 = 0, + $1811 = 0, + $1812 = 0, + $1813 = 0, + $1814 = 0, + $1815 = 0, + $1816 = 0, + $1817 = 0, + $1818 = 0, + $1819 = 0, + $182 = 0, + $1820 = 0, + $1821 = 0, + $1822 = 0, + $1823 = 0, + $1824 = 0, + $1825 = 0; + var $1826 = 0, + $1827 = 0, + $1828 = 0, + $1829 = 0, + $183 = 0, + $1830 = 0, + $1831 = 0, + $1832 = 0, + $1833 = 0, + $1834 = 0, + $1835 = 0, + $1836 = 0, + $1837 = 0, + $1838 = 0, + $1839 = 0, + $184 = 0, + $1840 = 0, + $1841 = 0, + $1842 = 0, + $1843 = 0; + var $1844 = 0, + $1845 = 0, + $1846 = 0, + $1847 = 0, + $1848 = 0, + $1849 = 0, + $185 = 0, + $1850 = 0, + $1851 = 0, + $1852 = 0, + $1853 = 0, + $1854 = 0, + $1855 = 0, + $1856 = 0, + $1857 = 0, + $1858 = 0, + $1859 = 0, + $186 = 0, + $1860 = 0, + $1861 = 0; + var $1862 = 0, + $1863 = 0, + $1864 = 0, + $1865 = 0, + $1866 = 0, + $1867 = 0, + $1868 = 0, + $1869 = 0, + $187 = 0, + $1870 = 0, + $1871 = 0, + $1872 = 0, + $1873 = 0, + $1874 = 0, + $1875 = 0, + $1876 = 0, + $1877 = 0, + $1878 = 0, + $188 = 0, + $189 = 0; + var $19 = 0, + $190 = 0, + $191 = 0, + $192 = 0, + $193 = 0, + $194 = 0, + $195 = 0, + $196 = 0, + $197 = 0, + $198 = 0, + $199 = 0, + $2 = 0, + $20 = 0, + $200 = 0, + $201 = 0, + $202 = 0, + $203 = 0, + $204 = 0, + $205 = 0, + $206 = 0; + var $207 = 0, + $208 = 0, + $209 = 0, + $21 = 0, + $210 = 0, + $211 = 0, + $212 = 0, + $213 = 0, + $214 = 0, + $215 = 0, + $216 = 0, + $217 = 0, + $218 = 0, + $219 = 0, + $22 = 0, + $220 = 0, + $221 = 0, + $222 = 0, + $223 = 0, + $224 = 0; + var $225 = 0, + $226 = 0, + $227 = 0, + $228 = 0, + $229 = 0, + $23 = 0, + $230 = 0, + $231 = 0, + $232 = 0, + $233 = 0, + $234 = 0, + $235 = 0, + $236 = 0, + $237 = 0, + $238 = 0, + $239 = 0, + $24 = 0, + $240 = 0, + $241 = 0, + $242 = 0; + var $243 = 0, + $244 = 0, + $245 = 0, + $246 = 0, + $247 = 0, + $248 = 0, + $249 = 0, + $25 = 0, + $250 = 0, + $251 = 0, + $252 = 0, + $253 = 0, + $254 = 0, + $255 = 0, + $256 = 0, + $257 = 0, + $258 = 0, + $259 = 0, + $26 = 0, + $260 = 0; + var $261 = 0, + $262 = 0, + $263 = 0, + $264 = 0, + $265 = 0, + $266 = 0, + $267 = 0, + $268 = 0, + $269 = 0, + $27 = 0, + $270 = 0, + $271 = 0, + $272 = 0, + $273 = 0, + $274 = 0, + $275 = 0, + $276 = 0, + $277 = 0, + $278 = 0, + $279 = 0; + var $28 = 0, + $280 = 0, + $281 = 0, + $282 = 0, + $283 = 0, + $284 = 0, + $285 = 0, + $286 = 0, + $287 = 0, + $288 = 0, + $289 = 0, + $29 = 0, + $290 = 0, + $291 = 0, + $292 = 0, + $293 = 0, + $294 = 0, + $295 = 0, + $296 = 0, + $297 = 0; + var $298 = 0, + $299 = 0, + $3 = 0, + $30 = 0, + $300 = 0, + $301 = 0, + $302 = 0, + $303 = 0, + $304 = 0, + $305 = 0, + $306 = 0, + $307 = 0, + $308 = 0, + $309 = 0, + $31 = 0, + $310 = 0, + $311 = 0, + $312 = 0, + $313 = 0, + $314 = 0; + var $315 = 0, + $316 = 0, + $317 = 0, + $318 = 0, + $319 = 0, + $32 = 0, + $320 = 0, + $321 = 0, + $322 = 0, + $323 = 0, + $324 = 0, + $325 = 0, + $326 = 0, + $327 = 0, + $328 = 0, + $329 = 0, + $33 = 0, + $330 = 0, + $331 = 0, + $332 = 0; + var $333 = 0, + $334 = 0, + $335 = 0, + $336 = 0, + $337 = 0, + $338 = 0, + $339 = 0, + $34 = 0, + $340 = 0, + $341 = 0, + $342 = 0, + $343 = 0, + $344 = 0, + $345 = 0, + $346 = 0, + $347 = 0, + $348 = 0, + $349 = 0, + $35 = 0, + $350 = 0; + var $351 = 0, + $352 = 0, + $353 = 0, + $354 = 0, + $355 = 0, + $356 = 0, + $357 = 0, + $358 = 0, + $359 = 0, + $36 = 0, + $360 = 0, + $361 = 0, + $362 = 0, + $363 = 0, + $364 = 0, + $365 = 0, + $366 = 0, + $367 = 0, + $368 = 0, + $369 = 0; + var $37 = 0, + $370 = 0, + $371 = 0, + $372 = 0, + $373 = 0, + $374 = 0, + $375 = 0, + $376 = 0, + $377 = 0, + $378 = 0, + $379 = 0, + $38 = 0, + $380 = 0, + $381 = 0, + $382 = 0, + $383 = 0, + $384 = 0, + $385 = 0, + $386 = 0, + $387 = 0; + var $388 = 0, + $389 = 0, + $39 = 0, + $390 = 0, + $391 = 0, + $392 = 0, + $393 = 0, + $394 = 0, + $395 = 0, + $396 = 0, + $397 = 0, + $398 = 0, + $399 = 0, + $4 = 0, + $40 = 0, + $400 = 0, + $401 = 0, + $402 = 0, + $403 = 0, + $404 = 0; + var $405 = 0, + $406 = 0, + $407 = 0, + $408 = 0, + $409 = 0, + $41 = 0, + $410 = 0, + $411 = 0, + $412 = 0, + $413 = 0, + $414 = 0, + $415 = 0, + $416 = 0, + $417 = 0, + $418 = 0, + $419 = 0, + $42 = 0, + $420 = 0, + $421 = 0, + $422 = 0; + var $423 = 0, + $424 = 0, + $425 = 0, + $426 = 0, + $427 = 0, + $428 = 0, + $429 = 0, + $43 = 0, + $430 = 0, + $431 = 0, + $432 = 0, + $433 = 0, + $434 = 0, + $435 = 0, + $436 = 0, + $437 = 0, + $438 = 0, + $439 = 0, + $44 = 0, + $440 = 0; + var $441 = 0, + $442 = 0, + $443 = 0, + $444 = 0, + $445 = 0, + $446 = 0, + $447 = 0, + $448 = 0, + $449 = 0, + $45 = 0, + $450 = 0, + $451 = 0, + $452 = 0, + $453 = 0, + $454 = 0, + $455 = 0, + $456 = 0, + $457 = 0, + $458 = 0, + $459 = 0; + var $46 = 0, + $460 = 0, + $461 = 0, + $462 = 0, + $463 = 0, + $464 = 0, + $465 = 0, + $466 = 0, + $467 = 0, + $468 = 0, + $469 = 0, + $47 = 0, + $470 = 0, + $471 = 0, + $472 = 0, + $473 = 0, + $474 = 0, + $475 = 0, + $476 = 0, + $477 = 0; + var $478 = 0, + $479 = 0, + $48 = 0, + $480 = 0, + $481 = 0, + $482 = 0, + $483 = 0, + $484 = 0, + $485 = 0, + $486 = 0, + $487 = 0, + $488 = 0, + $489 = 0, + $49 = 0, + $490 = 0, + $491 = 0, + $492 = 0, + $493 = 0, + $494 = 0, + $495 = 0; + var $496 = 0, + $497 = 0, + $498 = 0, + $499 = 0, + $5 = 0, + $50 = 0, + $500 = 0, + $501 = 0, + $502 = 0, + $503 = 0, + $504 = 0, + $505 = 0, + $506 = 0, + $507 = 0, + $508 = 0, + $509 = 0, + $51 = 0, + $510 = 0, + $511 = 0, + $512 = 0; + var $513 = 0, + $514 = 0, + $515 = 0, + $516 = 0, + $517 = 0, + $518 = 0, + $519 = 0, + $52 = 0, + $520 = 0, + $521 = 0, + $522 = 0, + $523 = 0, + $524 = 0, + $525 = 0, + $526 = 0, + $527 = 0, + $528 = 0, + $529 = 0, + $53 = 0, + $530 = 0; + var $531 = 0, + $532 = 0, + $533 = 0, + $534 = 0, + $535 = 0, + $536 = 0, + $537 = 0, + $538 = 0, + $539 = 0, + $54 = 0, + $540 = 0, + $541 = 0, + $542 = 0, + $543 = 0, + $544 = 0, + $545 = 0, + $546 = 0, + $547 = 0, + $548 = 0, + $549 = 0; + var $55 = 0, + $550 = 0, + $551 = 0, + $552 = 0, + $553 = 0, + $554 = 0, + $555 = 0, + $556 = 0, + $557 = 0, + $558 = 0, + $559 = 0, + $56 = 0, + $560 = 0, + $561 = 0, + $562 = 0, + $563 = 0, + $564 = 0, + $565 = 0, + $566 = 0, + $567 = 0; + var $568 = 0, + $569 = 0, + $57 = 0, + $570 = 0, + $571 = 0, + $572 = 0, + $573 = 0, + $574 = 0, + $575 = 0, + $576 = 0, + $577 = 0, + $578 = 0, + $579 = 0, + $58 = 0, + $580 = 0, + $581 = 0, + $582 = 0, + $583 = 0, + $584 = 0, + $585 = 0; + var $586 = 0, + $587 = 0, + $588 = 0, + $589 = 0, + $59 = 0, + $590 = 0, + $591 = 0, + $592 = 0, + $593 = 0, + $594 = 0, + $595 = 0, + $596 = 0, + $597 = 0, + $598 = 0, + $599 = 0, + $6 = 0, + $60 = 0, + $600 = 0, + $601 = 0, + $602 = 0; + var $603 = 0, + $604 = 0, + $605 = 0, + $606 = 0, + $607 = 0, + $608 = 0, + $609 = 0, + $61 = 0, + $610 = 0, + $611 = 0, + $612 = 0, + $613 = 0, + $614 = 0, + $615 = 0, + $616 = 0, + $617 = 0, + $618 = 0, + $619 = 0, + $62 = 0, + $620 = 0; + var $621 = 0, + $622 = 0, + $623 = 0, + $624 = 0, + $625 = 0, + $626 = 0, + $627 = 0, + $628 = 0, + $629 = 0, + $63 = 0, + $630 = 0, + $631 = 0, + $632 = 0, + $633 = 0, + $634 = 0, + $635 = 0, + $636 = 0, + $637 = 0, + $638 = 0, + $639 = 0; + var $64 = 0, + $640 = 0, + $641 = 0, + $642 = 0, + $643 = 0, + $644 = 0, + $645 = 0, + $646 = 0, + $647 = 0, + $648 = 0, + $649 = 0, + $65 = 0, + $650 = 0, + $651 = 0, + $652 = 0, + $653 = 0, + $654 = 0, + $655 = 0, + $656 = 0, + $657 = 0; + var $658 = 0, + $659 = 0, + $66 = 0, + $660 = 0, + $661 = 0, + $662 = 0, + $663 = 0, + $664 = 0, + $665 = 0, + $666 = 0, + $667 = 0, + $668 = 0, + $669 = 0, + $67 = 0, + $670 = 0, + $671 = 0, + $672 = 0, + $673 = 0, + $674 = 0, + $675 = 0; + var $676 = 0, + $677 = 0, + $678 = 0, + $679 = 0, + $68 = 0, + $680 = 0, + $681 = 0, + $682 = 0, + $683 = 0, + $684 = 0, + $685 = 0, + $686 = 0, + $687 = 0, + $688 = 0, + $689 = 0, + $69 = 0, + $690 = 0, + $691 = 0, + $692 = 0, + $693 = 0; + var $694 = 0, + $695 = 0, + $696 = 0, + $697 = 0, + $698 = 0, + $699 = 0, + $7 = 0, + $70 = 0, + $700 = 0, + $701 = 0, + $702 = 0, + $703 = 0, + $704 = 0, + $705 = 0, + $706 = 0, + $707 = 0, + $708 = 0, + $709 = 0, + $71 = 0, + $710 = 0; + var $711 = 0, + $712 = 0, + $713 = 0, + $714 = 0, + $715 = 0, + $716 = 0, + $717 = 0, + $718 = 0, + $719 = 0, + $72 = 0, + $720 = 0, + $721 = 0, + $722 = 0, + $723 = 0, + $724 = 0, + $725 = 0, + $726 = 0, + $727 = 0, + $728 = 0, + $729 = 0; + var $73 = 0, + $730 = 0, + $731 = 0, + $732 = 0, + $733 = 0, + $734 = 0, + $735 = 0, + $736 = 0, + $737 = 0, + $738 = 0, + $739 = 0, + $74 = 0, + $740 = 0, + $741 = 0, + $742 = 0, + $743 = 0, + $744 = 0, + $745 = 0, + $746 = 0, + $747 = 0; + var $748 = 0, + $749 = 0, + $75 = 0, + $750 = 0, + $751 = 0, + $752 = 0, + $753 = 0, + $754 = 0, + $755 = 0, + $756 = 0, + $757 = 0, + $758 = 0, + $759 = 0, + $76 = 0, + $760 = 0, + $761 = 0, + $762 = 0, + $763 = 0, + $764 = 0, + $765 = 0; + var $766 = 0, + $767 = 0, + $768 = 0, + $769 = 0, + $77 = 0, + $770 = 0, + $771 = 0, + $772 = 0, + $773 = 0, + $774 = 0, + $775 = 0, + $776 = 0, + $777 = 0, + $778 = 0, + $779 = 0, + $78 = 0, + $780 = 0, + $781 = 0, + $782 = 0, + $783 = 0; + var $784 = 0, + $785 = 0, + $786 = 0, + $787 = 0, + $788 = 0, + $789 = 0, + $79 = 0, + $790 = 0, + $791 = 0, + $792 = 0, + $793 = 0, + $794 = 0, + $795 = 0, + $796 = 0, + $797 = 0, + $798 = 0, + $799 = 0, + $8 = 0, + $80 = 0, + $800 = 0; + var $801 = 0, + $802 = 0, + $803 = 0, + $804 = 0, + $805 = 0, + $806 = 0, + $807 = 0, + $808 = 0, + $809 = 0, + $81 = 0, + $810 = 0, + $811 = 0, + $812 = 0, + $813 = 0, + $814 = 0, + $815 = 0, + $816 = 0, + $817 = 0, + $818 = 0, + $819 = 0; + var $82 = 0, + $820 = 0, + $821 = 0, + $822 = 0, + $823 = 0, + $824 = 0, + $825 = 0, + $826 = 0, + $827 = 0, + $828 = 0, + $829 = 0, + $83 = 0, + $830 = 0, + $831 = 0, + $832 = 0, + $833 = 0, + $834 = 0, + $835 = 0, + $836 = 0, + $837 = 0; + var $838 = 0, + $839 = 0, + $84 = 0, + $840 = 0, + $841 = 0, + $842 = 0, + $843 = 0, + $844 = 0, + $845 = 0, + $846 = 0, + $847 = 0, + $848 = 0, + $849 = 0, + $85 = 0, + $850 = 0, + $851 = 0, + $852 = 0, + $853 = 0, + $854 = 0, + $855 = 0; + var $856 = 0, + $857 = 0, + $858 = 0, + $859 = 0, + $86 = 0, + $860 = 0, + $861 = 0, + $862 = 0, + $863 = 0, + $864 = 0, + $865 = 0, + $866 = 0, + $867 = 0, + $868 = 0, + $869 = 0, + $87 = 0, + $870 = 0, + $871 = 0, + $872 = 0, + $873 = 0; + var $874 = 0, + $875 = 0, + $876 = 0, + $877 = 0, + $878 = 0, + $879 = 0, + $88 = 0, + $880 = 0, + $881 = 0, + $882 = 0, + $883 = 0, + $884 = 0, + $885 = 0, + $886 = 0, + $887 = 0, + $888 = 0, + $889 = 0, + $89 = 0, + $890 = 0, + $891 = 0; + var $892 = 0, + $893 = 0, + $894 = 0, + $895 = 0, + $896 = 0, + $897 = 0, + $898 = 0, + $899 = 0, + $9 = 0, + $90 = 0, + $900 = 0, + $901 = 0, + $902 = 0, + $903 = 0, + $904 = 0, + $905 = 0, + $906 = 0, + $907 = 0, + $908 = 0, + $909 = 0; + var $91 = 0, + $910 = 0, + $911 = 0, + $912 = 0, + $913 = 0, + $914 = 0, + $915 = 0, + $916 = 0, + $917 = 0, + $918 = 0, + $919 = 0, + $92 = 0, + $920 = 0, + $921 = 0, + $922 = 0, + $923 = 0, + $924 = 0, + $925 = 0, + $926 = 0, + $927 = 0; + var $928 = 0, + $929 = 0, + $93 = 0, + $930 = 0, + $931 = 0, + $932 = 0, + $933 = 0, + $934 = 0, + $935 = 0, + $936 = 0, + $937 = 0, + $938 = 0, + $939 = 0, + $94 = 0, + $940 = 0, + $941 = 0, + $942 = 0, + $943 = 0, + $944 = 0, + $945 = 0; + var $946 = 0, + $947 = 0, + $948 = 0, + $949 = 0, + $95 = 0, + $950 = 0, + $951 = 0, + $952 = 0, + $953 = 0, + $954 = 0, + $955 = 0, + $956 = 0, + $957 = 0, + $958 = 0, + $959 = 0, + $96 = 0, + $960 = 0, + $961 = 0, + $962 = 0, + $963 = 0; + var $964 = 0, + $965 = 0, + $966 = 0, + $967 = 0, + $968 = 0, + $969 = 0, + $97 = 0, + $970 = 0, + $971 = 0, + $972 = 0, + $973 = 0, + $974 = 0, + $975 = 0, + $976 = 0, + $977 = 0, + $978 = 0, + $979 = 0, + $98 = 0, + $980 = 0, + $981 = 0; + var $982 = 0, + $983 = 0, + $984 = 0, + $985 = 0, + $986 = 0, + $987 = 0, + $988 = 0, + $989 = 0, + $99 = 0, + $990 = 0, + $991 = 0, + $992 = 0, + $993 = 0, + $994 = 0, + $995 = 0, + $996 = 0, + $997 = 0, + $998 = 0, + $999 = 0, + label = 0; + var sp = 0; + sp = STACKTOP; + $0 = _load_347($a) | 0; + $1 = tempRet0; + $2 = $0 & 2097151; + $3 = ($a + 2) | 0; + $4 = _load_448($3) | 0; + $5 = tempRet0; + $6 = _bitshift64Lshr($4 | 0, $5 | 0, 5) | 0; + $7 = tempRet0; + $8 = $6 & 2097151; + $9 = ($a + 5) | 0; + $10 = _load_347($9) | 0; + $11 = tempRet0; + $12 = _bitshift64Lshr($10 | 0, $11 | 0, 2) | 0; + $13 = tempRet0; + $14 = $12 & 2097151; + $15 = ($a + 7) | 0; + $16 = _load_448($15) | 0; + $17 = tempRet0; + $18 = _bitshift64Lshr($16 | 0, $17 | 0, 7) | 0; + $19 = tempRet0; + $20 = $18 & 2097151; + $21 = ($a + 10) | 0; + $22 = _load_448($21) | 0; + $23 = tempRet0; + $24 = _bitshift64Lshr($22 | 0, $23 | 0, 4) | 0; + $25 = tempRet0; + $26 = $24 & 2097151; + $27 = ($a + 13) | 0; + $28 = _load_347($27) | 0; + $29 = tempRet0; + $30 = _bitshift64Lshr($28 | 0, $29 | 0, 1) | 0; + $31 = tempRet0; + $32 = $30 & 2097151; + $33 = ($a + 15) | 0; + $34 = _load_448($33) | 0; + $35 = tempRet0; + $36 = _bitshift64Lshr($34 | 0, $35 | 0, 6) | 0; + $37 = tempRet0; + $38 = $36 & 2097151; + $39 = ($a + 18) | 0; + $40 = _load_347($39) | 0; + $41 = tempRet0; + $42 = _bitshift64Lshr($40 | 0, $41 | 0, 3) | 0; + $43 = tempRet0; + $44 = $42 & 2097151; + $45 = ($a + 21) | 0; + $46 = _load_347($45) | 0; + $47 = tempRet0; + $48 = $46 & 2097151; + $49 = ($a + 23) | 0; + $50 = _load_448($49) | 0; + $51 = tempRet0; + $52 = _bitshift64Lshr($50 | 0, $51 | 0, 5) | 0; + $53 = tempRet0; + $54 = $52 & 2097151; + $55 = ($a + 26) | 0; + $56 = _load_347($55) | 0; + $57 = tempRet0; + $58 = _bitshift64Lshr($56 | 0, $57 | 0, 2) | 0; + $59 = tempRet0; + $60 = $58 & 2097151; + $61 = ($a + 28) | 0; + $62 = _load_448($61) | 0; + $63 = tempRet0; + $64 = _bitshift64Lshr($62 | 0, $63 | 0, 7) | 0; + $65 = tempRet0; + $66 = _load_347($b) | 0; + $67 = tempRet0; + $68 = $66 & 2097151; + $69 = ($b + 2) | 0; + $70 = _load_448($69) | 0; + $71 = tempRet0; + $72 = _bitshift64Lshr($70 | 0, $71 | 0, 5) | 0; + $73 = tempRet0; + $74 = $72 & 2097151; + $75 = ($b + 5) | 0; + $76 = _load_347($75) | 0; + $77 = tempRet0; + $78 = _bitshift64Lshr($76 | 0, $77 | 0, 2) | 0; + $79 = tempRet0; + $80 = $78 & 2097151; + $81 = ($b + 7) | 0; + $82 = _load_448($81) | 0; + $83 = tempRet0; + $84 = _bitshift64Lshr($82 | 0, $83 | 0, 7) | 0; + $85 = tempRet0; + $86 = $84 & 2097151; + $87 = ($b + 10) | 0; + $88 = _load_448($87) | 0; + $89 = tempRet0; + $90 = _bitshift64Lshr($88 | 0, $89 | 0, 4) | 0; + $91 = tempRet0; + $92 = $90 & 2097151; + $93 = ($b + 13) | 0; + $94 = _load_347($93) | 0; + $95 = tempRet0; + $96 = _bitshift64Lshr($94 | 0, $95 | 0, 1) | 0; + $97 = tempRet0; + $98 = $96 & 2097151; + $99 = ($b + 15) | 0; + $100 = _load_448($99) | 0; + $101 = tempRet0; + $102 = _bitshift64Lshr($100 | 0, $101 | 0, 6) | 0; + $103 = tempRet0; + $104 = $102 & 2097151; + $105 = ($b + 18) | 0; + $106 = _load_347($105) | 0; + $107 = tempRet0; + $108 = _bitshift64Lshr($106 | 0, $107 | 0, 3) | 0; + $109 = tempRet0; + $110 = $108 & 2097151; + $111 = ($b + 21) | 0; + $112 = _load_347($111) | 0; + $113 = tempRet0; + $114 = $112 & 2097151; + $115 = ($b + 23) | 0; + $116 = _load_448($115) | 0; + $117 = tempRet0; + $118 = _bitshift64Lshr($116 | 0, $117 | 0, 5) | 0; + $119 = tempRet0; + $120 = $118 & 2097151; + $121 = ($b + 26) | 0; + $122 = _load_347($121) | 0; + $123 = tempRet0; + $124 = _bitshift64Lshr($122 | 0, $123 | 0, 2) | 0; + $125 = tempRet0; + $126 = $124 & 2097151; + $127 = ($b + 28) | 0; + $128 = _load_448($127) | 0; + $129 = tempRet0; + $130 = _bitshift64Lshr($128 | 0, $129 | 0, 7) | 0; + $131 = tempRet0; + $132 = _load_347($c) | 0; + $133 = tempRet0; + $134 = $132 & 2097151; + $135 = ($c + 2) | 0; + $136 = _load_448($135) | 0; + $137 = tempRet0; + $138 = _bitshift64Lshr($136 | 0, $137 | 0, 5) | 0; + $139 = tempRet0; + $140 = $138 & 2097151; + $141 = ($c + 5) | 0; + $142 = _load_347($141) | 0; + $143 = tempRet0; + $144 = _bitshift64Lshr($142 | 0, $143 | 0, 2) | 0; + $145 = tempRet0; + $146 = $144 & 2097151; + $147 = ($c + 7) | 0; + $148 = _load_448($147) | 0; + $149 = tempRet0; + $150 = _bitshift64Lshr($148 | 0, $149 | 0, 7) | 0; + $151 = tempRet0; + $152 = $150 & 2097151; + $153 = ($c + 10) | 0; + $154 = _load_448($153) | 0; + $155 = tempRet0; + $156 = _bitshift64Lshr($154 | 0, $155 | 0, 4) | 0; + $157 = tempRet0; + $158 = $156 & 2097151; + $159 = ($c + 13) | 0; + $160 = _load_347($159) | 0; + $161 = tempRet0; + $162 = _bitshift64Lshr($160 | 0, $161 | 0, 1) | 0; + $163 = tempRet0; + $164 = $162 & 2097151; + $165 = ($c + 15) | 0; + $166 = _load_448($165) | 0; + $167 = tempRet0; + $168 = _bitshift64Lshr($166 | 0, $167 | 0, 6) | 0; + $169 = tempRet0; + $170 = $168 & 2097151; + $171 = ($c + 18) | 0; + $172 = _load_347($171) | 0; + $173 = tempRet0; + $174 = _bitshift64Lshr($172 | 0, $173 | 0, 3) | 0; + $175 = tempRet0; + $176 = $174 & 2097151; + $177 = ($c + 21) | 0; + $178 = _load_347($177) | 0; + $179 = tempRet0; + $180 = $178 & 2097151; + $181 = ($c + 23) | 0; + $182 = _load_448($181) | 0; + $183 = tempRet0; + $184 = _bitshift64Lshr($182 | 0, $183 | 0, 5) | 0; + $185 = tempRet0; + $186 = $184 & 2097151; + $187 = ($c + 26) | 0; + $188 = _load_347($187) | 0; + $189 = tempRet0; + $190 = _bitshift64Lshr($188 | 0, $189 | 0, 2) | 0; + $191 = tempRet0; + $192 = $190 & 2097151; + $193 = ($c + 28) | 0; + $194 = _load_448($193) | 0; + $195 = tempRet0; + $196 = _bitshift64Lshr($194 | 0, $195 | 0, 7) | 0; + $197 = tempRet0; + $198 = ___muldi3($68 | 0, 0, $2 | 0, 0) | 0; + $199 = tempRet0; + $200 = _i64Add($134 | 0, 0, $198 | 0, $199 | 0) | 0; + $201 = tempRet0; + $202 = ___muldi3($74 | 0, 0, $2 | 0, 0) | 0; + $203 = tempRet0; + $204 = ___muldi3($68 | 0, 0, $8 | 0, 0) | 0; + $205 = tempRet0; + $206 = ___muldi3($80 | 0, 0, $2 | 0, 0) | 0; + $207 = tempRet0; + $208 = ___muldi3($74 | 0, 0, $8 | 0, 0) | 0; + $209 = tempRet0; + $210 = ___muldi3($68 | 0, 0, $14 | 0, 0) | 0; + $211 = tempRet0; + $212 = _i64Add($208 | 0, $209 | 0, $210 | 0, $211 | 0) | 0; + $213 = tempRet0; + $214 = _i64Add($212 | 0, $213 | 0, $206 | 0, $207 | 0) | 0; + $215 = tempRet0; + $216 = _i64Add($214 | 0, $215 | 0, $146 | 0, 0) | 0; + $217 = tempRet0; + $218 = ___muldi3($86 | 0, 0, $2 | 0, 0) | 0; + $219 = tempRet0; + $220 = ___muldi3($80 | 0, 0, $8 | 0, 0) | 0; + $221 = tempRet0; + $222 = ___muldi3($74 | 0, 0, $14 | 0, 0) | 0; + $223 = tempRet0; + $224 = ___muldi3($68 | 0, 0, $20 | 0, 0) | 0; + $225 = tempRet0; + $226 = ___muldi3($92 | 0, 0, $2 | 0, 0) | 0; + $227 = tempRet0; + $228 = ___muldi3($86 | 0, 0, $8 | 0, 0) | 0; + $229 = tempRet0; + $230 = ___muldi3($80 | 0, 0, $14 | 0, 0) | 0; + $231 = tempRet0; + $232 = ___muldi3($74 | 0, 0, $20 | 0, 0) | 0; + $233 = tempRet0; + $234 = ___muldi3($68 | 0, 0, $26 | 0, 0) | 0; + $235 = tempRet0; + $236 = _i64Add($232 | 0, $233 | 0, $234 | 0, $235 | 0) | 0; + $237 = tempRet0; + $238 = _i64Add($236 | 0, $237 | 0, $230 | 0, $231 | 0) | 0; + $239 = tempRet0; + $240 = _i64Add($238 | 0, $239 | 0, $228 | 0, $229 | 0) | 0; + $241 = tempRet0; + $242 = _i64Add($240 | 0, $241 | 0, $226 | 0, $227 | 0) | 0; + $243 = tempRet0; + $244 = _i64Add($242 | 0, $243 | 0, $158 | 0, 0) | 0; + $245 = tempRet0; + $246 = ___muldi3($98 | 0, 0, $2 | 0, 0) | 0; + $247 = tempRet0; + $248 = ___muldi3($92 | 0, 0, $8 | 0, 0) | 0; + $249 = tempRet0; + $250 = ___muldi3($86 | 0, 0, $14 | 0, 0) | 0; + $251 = tempRet0; + $252 = ___muldi3($80 | 0, 0, $20 | 0, 0) | 0; + $253 = tempRet0; + $254 = ___muldi3($74 | 0, 0, $26 | 0, 0) | 0; + $255 = tempRet0; + $256 = ___muldi3($68 | 0, 0, $32 | 0, 0) | 0; + $257 = tempRet0; + $258 = ___muldi3($104 | 0, 0, $2 | 0, 0) | 0; + $259 = tempRet0; + $260 = ___muldi3($98 | 0, 0, $8 | 0, 0) | 0; + $261 = tempRet0; + $262 = ___muldi3($92 | 0, 0, $14 | 0, 0) | 0; + $263 = tempRet0; + $264 = ___muldi3($86 | 0, 0, $20 | 0, 0) | 0; + $265 = tempRet0; + $266 = ___muldi3($80 | 0, 0, $26 | 0, 0) | 0; + $267 = tempRet0; + $268 = ___muldi3($74 | 0, 0, $32 | 0, 0) | 0; + $269 = tempRet0; + $270 = ___muldi3($68 | 0, 0, $38 | 0, 0) | 0; + $271 = tempRet0; + $272 = _i64Add($268 | 0, $269 | 0, $270 | 0, $271 | 0) | 0; + $273 = tempRet0; + $274 = _i64Add($272 | 0, $273 | 0, $266 | 0, $267 | 0) | 0; + $275 = tempRet0; + $276 = _i64Add($274 | 0, $275 | 0, $264 | 0, $265 | 0) | 0; + $277 = tempRet0; + $278 = _i64Add($276 | 0, $277 | 0, $262 | 0, $263 | 0) | 0; + $279 = tempRet0; + $280 = _i64Add($278 | 0, $279 | 0, $260 | 0, $261 | 0) | 0; + $281 = tempRet0; + $282 = _i64Add($280 | 0, $281 | 0, $258 | 0, $259 | 0) | 0; + $283 = tempRet0; + $284 = _i64Add($282 | 0, $283 | 0, $170 | 0, 0) | 0; + $285 = tempRet0; + $286 = ___muldi3($110 | 0, 0, $2 | 0, 0) | 0; + $287 = tempRet0; + $288 = ___muldi3($104 | 0, 0, $8 | 0, 0) | 0; + $289 = tempRet0; + $290 = ___muldi3($98 | 0, 0, $14 | 0, 0) | 0; + $291 = tempRet0; + $292 = ___muldi3($92 | 0, 0, $20 | 0, 0) | 0; + $293 = tempRet0; + $294 = ___muldi3($86 | 0, 0, $26 | 0, 0) | 0; + $295 = tempRet0; + $296 = ___muldi3($80 | 0, 0, $32 | 0, 0) | 0; + $297 = tempRet0; + $298 = ___muldi3($74 | 0, 0, $38 | 0, 0) | 0; + $299 = tempRet0; + $300 = ___muldi3($68 | 0, 0, $44 | 0, 0) | 0; + $301 = tempRet0; + $302 = ___muldi3($114 | 0, 0, $2 | 0, 0) | 0; + $303 = tempRet0; + $304 = ___muldi3($110 | 0, 0, $8 | 0, 0) | 0; + $305 = tempRet0; + $306 = ___muldi3($104 | 0, 0, $14 | 0, 0) | 0; + $307 = tempRet0; + $308 = ___muldi3($98 | 0, 0, $20 | 0, 0) | 0; + $309 = tempRet0; + $310 = ___muldi3($92 | 0, 0, $26 | 0, 0) | 0; + $311 = tempRet0; + $312 = ___muldi3($86 | 0, 0, $32 | 0, 0) | 0; + $313 = tempRet0; + $314 = ___muldi3($80 | 0, 0, $38 | 0, 0) | 0; + $315 = tempRet0; + $316 = ___muldi3($74 | 0, 0, $44 | 0, 0) | 0; + $317 = tempRet0; + $318 = ___muldi3($68 | 0, 0, $48 | 0, 0) | 0; + $319 = tempRet0; + $320 = _i64Add($316 | 0, $317 | 0, $318 | 0, $319 | 0) | 0; + $321 = tempRet0; + $322 = _i64Add($320 | 0, $321 | 0, $314 | 0, $315 | 0) | 0; + $323 = tempRet0; + $324 = _i64Add($322 | 0, $323 | 0, $312 | 0, $313 | 0) | 0; + $325 = tempRet0; + $326 = _i64Add($324 | 0, $325 | 0, $310 | 0, $311 | 0) | 0; + $327 = tempRet0; + $328 = _i64Add($326 | 0, $327 | 0, $308 | 0, $309 | 0) | 0; + $329 = tempRet0; + $330 = _i64Add($328 | 0, $329 | 0, $306 | 0, $307 | 0) | 0; + $331 = tempRet0; + $332 = _i64Add($330 | 0, $331 | 0, $302 | 0, $303 | 0) | 0; + $333 = tempRet0; + $334 = _i64Add($332 | 0, $333 | 0, $304 | 0, $305 | 0) | 0; + $335 = tempRet0; + $336 = _i64Add($334 | 0, $335 | 0, $180 | 0, 0) | 0; + $337 = tempRet0; + $338 = ___muldi3($120 | 0, 0, $2 | 0, 0) | 0; + $339 = tempRet0; + $340 = ___muldi3($114 | 0, 0, $8 | 0, 0) | 0; + $341 = tempRet0; + $342 = ___muldi3($110 | 0, 0, $14 | 0, 0) | 0; + $343 = tempRet0; + $344 = ___muldi3($104 | 0, 0, $20 | 0, 0) | 0; + $345 = tempRet0; + $346 = ___muldi3($98 | 0, 0, $26 | 0, 0) | 0; + $347 = tempRet0; + $348 = ___muldi3($92 | 0, 0, $32 | 0, 0) | 0; + $349 = tempRet0; + $350 = ___muldi3($86 | 0, 0, $38 | 0, 0) | 0; + $351 = tempRet0; + $352 = ___muldi3($80 | 0, 0, $44 | 0, 0) | 0; + $353 = tempRet0; + $354 = ___muldi3($74 | 0, 0, $48 | 0, 0) | 0; + $355 = tempRet0; + $356 = ___muldi3($68 | 0, 0, $54 | 0, 0) | 0; + $357 = tempRet0; + $358 = ___muldi3($126 | 0, 0, $2 | 0, 0) | 0; + $359 = tempRet0; + $360 = ___muldi3($120 | 0, 0, $8 | 0, 0) | 0; + $361 = tempRet0; + $362 = ___muldi3($114 | 0, 0, $14 | 0, 0) | 0; + $363 = tempRet0; + $364 = ___muldi3($110 | 0, 0, $20 | 0, 0) | 0; + $365 = tempRet0; + $366 = ___muldi3($104 | 0, 0, $26 | 0, 0) | 0; + $367 = tempRet0; + $368 = ___muldi3($98 | 0, 0, $32 | 0, 0) | 0; + $369 = tempRet0; + $370 = ___muldi3($92 | 0, 0, $38 | 0, 0) | 0; + $371 = tempRet0; + $372 = ___muldi3($86 | 0, 0, $44 | 0, 0) | 0; + $373 = tempRet0; + $374 = ___muldi3($80 | 0, 0, $48 | 0, 0) | 0; + $375 = tempRet0; + $376 = ___muldi3($74 | 0, 0, $54 | 0, 0) | 0; + $377 = tempRet0; + $378 = ___muldi3($68 | 0, 0, $60 | 0, 0) | 0; + $379 = tempRet0; + $380 = _i64Add($376 | 0, $377 | 0, $378 | 0, $379 | 0) | 0; + $381 = tempRet0; + $382 = _i64Add($380 | 0, $381 | 0, $374 | 0, $375 | 0) | 0; + $383 = tempRet0; + $384 = _i64Add($382 | 0, $383 | 0, $372 | 0, $373 | 0) | 0; + $385 = tempRet0; + $386 = _i64Add($384 | 0, $385 | 0, $370 | 0, $371 | 0) | 0; + $387 = tempRet0; + $388 = _i64Add($386 | 0, $387 | 0, $368 | 0, $369 | 0) | 0; + $389 = tempRet0; + $390 = _i64Add($388 | 0, $389 | 0, $366 | 0, $367 | 0) | 0; + $391 = tempRet0; + $392 = _i64Add($390 | 0, $391 | 0, $362 | 0, $363 | 0) | 0; + $393 = tempRet0; + $394 = _i64Add($392 | 0, $393 | 0, $364 | 0, $365 | 0) | 0; + $395 = tempRet0; + $396 = _i64Add($394 | 0, $395 | 0, $360 | 0, $361 | 0) | 0; + $397 = tempRet0; + $398 = _i64Add($396 | 0, $397 | 0, $358 | 0, $359 | 0) | 0; + $399 = tempRet0; + $400 = _i64Add($398 | 0, $399 | 0, $192 | 0, 0) | 0; + $401 = tempRet0; + $402 = ___muldi3($130 | 0, $131 | 0, $2 | 0, 0) | 0; + $403 = tempRet0; + $404 = ___muldi3($126 | 0, 0, $8 | 0, 0) | 0; + $405 = tempRet0; + $406 = ___muldi3($120 | 0, 0, $14 | 0, 0) | 0; + $407 = tempRet0; + $408 = ___muldi3($114 | 0, 0, $20 | 0, 0) | 0; + $409 = tempRet0; + $410 = ___muldi3($110 | 0, 0, $26 | 0, 0) | 0; + $411 = tempRet0; + $412 = ___muldi3($104 | 0, 0, $32 | 0, 0) | 0; + $413 = tempRet0; + $414 = ___muldi3($98 | 0, 0, $38 | 0, 0) | 0; + $415 = tempRet0; + $416 = ___muldi3($92 | 0, 0, $44 | 0, 0) | 0; + $417 = tempRet0; + $418 = ___muldi3($86 | 0, 0, $48 | 0, 0) | 0; + $419 = tempRet0; + $420 = ___muldi3($80 | 0, 0, $54 | 0, 0) | 0; + $421 = tempRet0; + $422 = ___muldi3($74 | 0, 0, $60 | 0, 0) | 0; + $423 = tempRet0; + $424 = ___muldi3($68 | 0, 0, $64 | 0, $65 | 0) | 0; + $425 = tempRet0; + $426 = ___muldi3($130 | 0, $131 | 0, $8 | 0, 0) | 0; + $427 = tempRet0; + $428 = ___muldi3($126 | 0, 0, $14 | 0, 0) | 0; + $429 = tempRet0; + $430 = ___muldi3($120 | 0, 0, $20 | 0, 0) | 0; + $431 = tempRet0; + $432 = ___muldi3($114 | 0, 0, $26 | 0, 0) | 0; + $433 = tempRet0; + $434 = ___muldi3($110 | 0, 0, $32 | 0, 0) | 0; + $435 = tempRet0; + $436 = ___muldi3($104 | 0, 0, $38 | 0, 0) | 0; + $437 = tempRet0; + $438 = ___muldi3($98 | 0, 0, $44 | 0, 0) | 0; + $439 = tempRet0; + $440 = ___muldi3($92 | 0, 0, $48 | 0, 0) | 0; + $441 = tempRet0; + $442 = ___muldi3($86 | 0, 0, $54 | 0, 0) | 0; + $443 = tempRet0; + $444 = ___muldi3($80 | 0, 0, $60 | 0, 0) | 0; + $445 = tempRet0; + $446 = ___muldi3($74 | 0, 0, $64 | 0, $65 | 0) | 0; + $447 = tempRet0; + $448 = _i64Add($444 | 0, $445 | 0, $446 | 0, $447 | 0) | 0; + $449 = tempRet0; + $450 = _i64Add($448 | 0, $449 | 0, $442 | 0, $443 | 0) | 0; + $451 = tempRet0; + $452 = _i64Add($450 | 0, $451 | 0, $440 | 0, $441 | 0) | 0; + $453 = tempRet0; + $454 = _i64Add($452 | 0, $453 | 0, $438 | 0, $439 | 0) | 0; + $455 = tempRet0; + $456 = _i64Add($454 | 0, $455 | 0, $436 | 0, $437 | 0) | 0; + $457 = tempRet0; + $458 = _i64Add($456 | 0, $457 | 0, $432 | 0, $433 | 0) | 0; + $459 = tempRet0; + $460 = _i64Add($458 | 0, $459 | 0, $434 | 0, $435 | 0) | 0; + $461 = tempRet0; + $462 = _i64Add($460 | 0, $461 | 0, $430 | 0, $431 | 0) | 0; + $463 = tempRet0; + $464 = _i64Add($462 | 0, $463 | 0, $428 | 0, $429 | 0) | 0; + $465 = tempRet0; + $466 = _i64Add($464 | 0, $465 | 0, $426 | 0, $427 | 0) | 0; + $467 = tempRet0; + $468 = ___muldi3($130 | 0, $131 | 0, $14 | 0, 0) | 0; + $469 = tempRet0; + $470 = ___muldi3($126 | 0, 0, $20 | 0, 0) | 0; + $471 = tempRet0; + $472 = ___muldi3($120 | 0, 0, $26 | 0, 0) | 0; + $473 = tempRet0; + $474 = ___muldi3($114 | 0, 0, $32 | 0, 0) | 0; + $475 = tempRet0; + $476 = ___muldi3($110 | 0, 0, $38 | 0, 0) | 0; + $477 = tempRet0; + $478 = ___muldi3($104 | 0, 0, $44 | 0, 0) | 0; + $479 = tempRet0; + $480 = ___muldi3($98 | 0, 0, $48 | 0, 0) | 0; + $481 = tempRet0; + $482 = ___muldi3($92 | 0, 0, $54 | 0, 0) | 0; + $483 = tempRet0; + $484 = ___muldi3($86 | 0, 0, $60 | 0, 0) | 0; + $485 = tempRet0; + $486 = ___muldi3($80 | 0, 0, $64 | 0, $65 | 0) | 0; + $487 = tempRet0; + $488 = ___muldi3($130 | 0, $131 | 0, $20 | 0, 0) | 0; + $489 = tempRet0; + $490 = ___muldi3($126 | 0, 0, $26 | 0, 0) | 0; + $491 = tempRet0; + $492 = ___muldi3($120 | 0, 0, $32 | 0, 0) | 0; + $493 = tempRet0; + $494 = ___muldi3($114 | 0, 0, $38 | 0, 0) | 0; + $495 = tempRet0; + $496 = ___muldi3($110 | 0, 0, $44 | 0, 0) | 0; + $497 = tempRet0; + $498 = ___muldi3($104 | 0, 0, $48 | 0, 0) | 0; + $499 = tempRet0; + $500 = ___muldi3($98 | 0, 0, $54 | 0, 0) | 0; + $501 = tempRet0; + $502 = ___muldi3($92 | 0, 0, $60 | 0, 0) | 0; + $503 = tempRet0; + $504 = ___muldi3($86 | 0, 0, $64 | 0, $65 | 0) | 0; + $505 = tempRet0; + $506 = _i64Add($502 | 0, $503 | 0, $504 | 0, $505 | 0) | 0; + $507 = tempRet0; + $508 = _i64Add($506 | 0, $507 | 0, $500 | 0, $501 | 0) | 0; + $509 = tempRet0; + $510 = _i64Add($508 | 0, $509 | 0, $498 | 0, $499 | 0) | 0; + $511 = tempRet0; + $512 = _i64Add($510 | 0, $511 | 0, $494 | 0, $495 | 0) | 0; + $513 = tempRet0; + $514 = _i64Add($512 | 0, $513 | 0, $496 | 0, $497 | 0) | 0; + $515 = tempRet0; + $516 = _i64Add($514 | 0, $515 | 0, $492 | 0, $493 | 0) | 0; + $517 = tempRet0; + $518 = _i64Add($516 | 0, $517 | 0, $490 | 0, $491 | 0) | 0; + $519 = tempRet0; + $520 = _i64Add($518 | 0, $519 | 0, $488 | 0, $489 | 0) | 0; + $521 = tempRet0; + $522 = ___muldi3($130 | 0, $131 | 0, $26 | 0, 0) | 0; + $523 = tempRet0; + $524 = ___muldi3($126 | 0, 0, $32 | 0, 0) | 0; + $525 = tempRet0; + $526 = ___muldi3($120 | 0, 0, $38 | 0, 0) | 0; + $527 = tempRet0; + $528 = ___muldi3($114 | 0, 0, $44 | 0, 0) | 0; + $529 = tempRet0; + $530 = ___muldi3($110 | 0, 0, $48 | 0, 0) | 0; + $531 = tempRet0; + $532 = ___muldi3($104 | 0, 0, $54 | 0, 0) | 0; + $533 = tempRet0; + $534 = ___muldi3($98 | 0, 0, $60 | 0, 0) | 0; + $535 = tempRet0; + $536 = ___muldi3($92 | 0, 0, $64 | 0, $65 | 0) | 0; + $537 = tempRet0; + $538 = ___muldi3($130 | 0, $131 | 0, $32 | 0, 0) | 0; + $539 = tempRet0; + $540 = ___muldi3($126 | 0, 0, $38 | 0, 0) | 0; + $541 = tempRet0; + $542 = ___muldi3($120 | 0, 0, $44 | 0, 0) | 0; + $543 = tempRet0; + $544 = ___muldi3($114 | 0, 0, $48 | 0, 0) | 0; + $545 = tempRet0; + $546 = ___muldi3($110 | 0, 0, $54 | 0, 0) | 0; + $547 = tempRet0; + $548 = ___muldi3($104 | 0, 0, $60 | 0, 0) | 0; + $549 = tempRet0; + $550 = ___muldi3($98 | 0, 0, $64 | 0, $65 | 0) | 0; + $551 = tempRet0; + $552 = _i64Add($548 | 0, $549 | 0, $550 | 0, $551 | 0) | 0; + $553 = tempRet0; + $554 = _i64Add($552 | 0, $553 | 0, $544 | 0, $545 | 0) | 0; + $555 = tempRet0; + $556 = _i64Add($554 | 0, $555 | 0, $546 | 0, $547 | 0) | 0; + $557 = tempRet0; + $558 = _i64Add($556 | 0, $557 | 0, $542 | 0, $543 | 0) | 0; + $559 = tempRet0; + $560 = _i64Add($558 | 0, $559 | 0, $540 | 0, $541 | 0) | 0; + $561 = tempRet0; + $562 = _i64Add($560 | 0, $561 | 0, $538 | 0, $539 | 0) | 0; + $563 = tempRet0; + $564 = ___muldi3($130 | 0, $131 | 0, $38 | 0, 0) | 0; + $565 = tempRet0; + $566 = ___muldi3($126 | 0, 0, $44 | 0, 0) | 0; + $567 = tempRet0; + $568 = ___muldi3($120 | 0, 0, $48 | 0, 0) | 0; + $569 = tempRet0; + $570 = ___muldi3($114 | 0, 0, $54 | 0, 0) | 0; + $571 = tempRet0; + $572 = ___muldi3($110 | 0, 0, $60 | 0, 0) | 0; + $573 = tempRet0; + $574 = ___muldi3($104 | 0, 0, $64 | 0, $65 | 0) | 0; + $575 = tempRet0; + $576 = ___muldi3($130 | 0, $131 | 0, $44 | 0, 0) | 0; + $577 = tempRet0; + $578 = ___muldi3($126 | 0, 0, $48 | 0, 0) | 0; + $579 = tempRet0; + $580 = ___muldi3($120 | 0, 0, $54 | 0, 0) | 0; + $581 = tempRet0; + $582 = ___muldi3($114 | 0, 0, $60 | 0, 0) | 0; + $583 = tempRet0; + $584 = ___muldi3($110 | 0, 0, $64 | 0, $65 | 0) | 0; + $585 = tempRet0; + $586 = _i64Add($584 | 0, $585 | 0, $582 | 0, $583 | 0) | 0; + $587 = tempRet0; + $588 = _i64Add($586 | 0, $587 | 0, $580 | 0, $581 | 0) | 0; + $589 = tempRet0; + $590 = _i64Add($588 | 0, $589 | 0, $578 | 0, $579 | 0) | 0; + $591 = tempRet0; + $592 = _i64Add($590 | 0, $591 | 0, $576 | 0, $577 | 0) | 0; + $593 = tempRet0; + $594 = ___muldi3($130 | 0, $131 | 0, $48 | 0, 0) | 0; + $595 = tempRet0; + $596 = ___muldi3($126 | 0, 0, $54 | 0, 0) | 0; + $597 = tempRet0; + $598 = ___muldi3($120 | 0, 0, $60 | 0, 0) | 0; + $599 = tempRet0; + $600 = ___muldi3($114 | 0, 0, $64 | 0, $65 | 0) | 0; + $601 = tempRet0; + $602 = ___muldi3($130 | 0, $131 | 0, $54 | 0, 0) | 0; + $603 = tempRet0; + $604 = ___muldi3($126 | 0, 0, $60 | 0, 0) | 0; + $605 = tempRet0; + $606 = ___muldi3($120 | 0, 0, $64 | 0, $65 | 0) | 0; + $607 = tempRet0; + $608 = _i64Add($604 | 0, $605 | 0, $606 | 0, $607 | 0) | 0; + $609 = tempRet0; + $610 = _i64Add($608 | 0, $609 | 0, $602 | 0, $603 | 0) | 0; + $611 = tempRet0; + $612 = ___muldi3($130 | 0, $131 | 0, $60 | 0, 0) | 0; + $613 = tempRet0; + $614 = ___muldi3($126 | 0, 0, $64 | 0, $65 | 0) | 0; + $615 = tempRet0; + $616 = _i64Add($612 | 0, $613 | 0, $614 | 0, $615 | 0) | 0; + $617 = tempRet0; + $618 = ___muldi3($130 | 0, $131 | 0, $64 | 0, $65 | 0) | 0; + $619 = tempRet0; + $620 = _i64Add($200 | 0, $201 | 0, 1048576, 0) | 0; + $621 = tempRet0; + $622 = _bitshift64Lshr($620 | 0, $621 | 0, 21) | 0; + $623 = tempRet0; + $624 = _i64Add($202 | 0, $203 | 0, $204 | 0, $205 | 0) | 0; + $625 = tempRet0; + $626 = _i64Add($624 | 0, $625 | 0, $140 | 0, 0) | 0; + $627 = tempRet0; + $628 = _i64Add($626 | 0, $627 | 0, $622 | 0, $623 | 0) | 0; + $629 = tempRet0; + $630 = _bitshift64Shl($622 | 0, $623 | 0, 21) | 0; + $631 = tempRet0; + $632 = _i64Subtract($200 | 0, $201 | 0, $630 | 0, $631 | 0) | 0; + $633 = tempRet0; + $634 = _i64Add($216 | 0, $217 | 0, 1048576, 0) | 0; + $635 = tempRet0; + $636 = _bitshift64Lshr($634 | 0, $635 | 0, 21) | 0; + $637 = tempRet0; + $638 = _i64Add($222 | 0, $223 | 0, $224 | 0, $225 | 0) | 0; + $639 = tempRet0; + $640 = _i64Add($638 | 0, $639 | 0, $220 | 0, $221 | 0) | 0; + $641 = tempRet0; + $642 = _i64Add($640 | 0, $641 | 0, $218 | 0, $219 | 0) | 0; + $643 = tempRet0; + $644 = _i64Add($642 | 0, $643 | 0, $152 | 0, 0) | 0; + $645 = tempRet0; + $646 = _i64Add($644 | 0, $645 | 0, $636 | 0, $637 | 0) | 0; + $647 = tempRet0; + $648 = _bitshift64Shl($636 | 0, $637 | 0, 21) | 0; + $649 = tempRet0; + $650 = _i64Add($244 | 0, $245 | 0, 1048576, 0) | 0; + $651 = tempRet0; + $652 = _bitshift64Ashr($650 | 0, $651 | 0, 21) | 0; + $653 = tempRet0; + $654 = _i64Add($254 | 0, $255 | 0, $256 | 0, $257 | 0) | 0; + $655 = tempRet0; + $656 = _i64Add($654 | 0, $655 | 0, $252 | 0, $253 | 0) | 0; + $657 = tempRet0; + $658 = _i64Add($656 | 0, $657 | 0, $250 | 0, $251 | 0) | 0; + $659 = tempRet0; + $660 = _i64Add($658 | 0, $659 | 0, $248 | 0, $249 | 0) | 0; + $661 = tempRet0; + $662 = _i64Add($660 | 0, $661 | 0, $246 | 0, $247 | 0) | 0; + $663 = tempRet0; + $664 = _i64Add($662 | 0, $663 | 0, $164 | 0, 0) | 0; + $665 = tempRet0; + $666 = _i64Add($664 | 0, $665 | 0, $652 | 0, $653 | 0) | 0; + $667 = tempRet0; + $668 = _bitshift64Shl($652 | 0, $653 | 0, 21) | 0; + $669 = tempRet0; + $670 = _i64Subtract($244 | 0, $245 | 0, $668 | 0, $669 | 0) | 0; + $671 = tempRet0; + $672 = _i64Add($284 | 0, $285 | 0, 1048576, 0) | 0; + $673 = tempRet0; + $674 = _bitshift64Ashr($672 | 0, $673 | 0, 21) | 0; + $675 = tempRet0; + $676 = _i64Add($298 | 0, $299 | 0, $300 | 0, $301 | 0) | 0; + $677 = tempRet0; + $678 = _i64Add($676 | 0, $677 | 0, $296 | 0, $297 | 0) | 0; + $679 = tempRet0; + $680 = _i64Add($678 | 0, $679 | 0, $294 | 0, $295 | 0) | 0; + $681 = tempRet0; + $682 = _i64Add($680 | 0, $681 | 0, $292 | 0, $293 | 0) | 0; + $683 = tempRet0; + $684 = _i64Add($682 | 0, $683 | 0, $290 | 0, $291 | 0) | 0; + $685 = tempRet0; + $686 = _i64Add($684 | 0, $685 | 0, $288 | 0, $289 | 0) | 0; + $687 = tempRet0; + $688 = _i64Add($686 | 0, $687 | 0, $286 | 0, $287 | 0) | 0; + $689 = tempRet0; + $690 = _i64Add($688 | 0, $689 | 0, $176 | 0, 0) | 0; + $691 = tempRet0; + $692 = _i64Add($690 | 0, $691 | 0, $674 | 0, $675 | 0) | 0; + $693 = tempRet0; + $694 = _bitshift64Shl($674 | 0, $675 | 0, 21) | 0; + $695 = tempRet0; + $696 = _i64Add($336 | 0, $337 | 0, 1048576, 0) | 0; + $697 = tempRet0; + $698 = _bitshift64Ashr($696 | 0, $697 | 0, 21) | 0; + $699 = tempRet0; + $700 = _i64Add($354 | 0, $355 | 0, $356 | 0, $357 | 0) | 0; + $701 = tempRet0; + $702 = _i64Add($700 | 0, $701 | 0, $352 | 0, $353 | 0) | 0; + $703 = tempRet0; + $704 = _i64Add($702 | 0, $703 | 0, $350 | 0, $351 | 0) | 0; + $705 = tempRet0; + $706 = _i64Add($704 | 0, $705 | 0, $348 | 0, $349 | 0) | 0; + $707 = tempRet0; + $708 = _i64Add($706 | 0, $707 | 0, $346 | 0, $347 | 0) | 0; + $709 = tempRet0; + $710 = _i64Add($708 | 0, $709 | 0, $344 | 0, $345 | 0) | 0; + $711 = tempRet0; + $712 = _i64Add($710 | 0, $711 | 0, $340 | 0, $341 | 0) | 0; + $713 = tempRet0; + $714 = _i64Add($712 | 0, $713 | 0, $342 | 0, $343 | 0) | 0; + $715 = tempRet0; + $716 = _i64Add($714 | 0, $715 | 0, $338 | 0, $339 | 0) | 0; + $717 = tempRet0; + $718 = _i64Add($716 | 0, $717 | 0, $186 | 0, 0) | 0; + $719 = tempRet0; + $720 = _i64Add($718 | 0, $719 | 0, $698 | 0, $699 | 0) | 0; + $721 = tempRet0; + $722 = _bitshift64Shl($698 | 0, $699 | 0, 21) | 0; + $723 = tempRet0; + $724 = _i64Add($400 | 0, $401 | 0, 1048576, 0) | 0; + $725 = tempRet0; + $726 = _bitshift64Ashr($724 | 0, $725 | 0, 21) | 0; + $727 = tempRet0; + $728 = _i64Add($422 | 0, $423 | 0, $424 | 0, $425 | 0) | 0; + $729 = tempRet0; + $730 = _i64Add($728 | 0, $729 | 0, $420 | 0, $421 | 0) | 0; + $731 = tempRet0; + $732 = _i64Add($730 | 0, $731 | 0, $418 | 0, $419 | 0) | 0; + $733 = tempRet0; + $734 = _i64Add($732 | 0, $733 | 0, $416 | 0, $417 | 0) | 0; + $735 = tempRet0; + $736 = _i64Add($734 | 0, $735 | 0, $414 | 0, $415 | 0) | 0; + $737 = tempRet0; + $738 = _i64Add($736 | 0, $737 | 0, $412 | 0, $413 | 0) | 0; + $739 = tempRet0; + $740 = _i64Add($738 | 0, $739 | 0, $408 | 0, $409 | 0) | 0; + $741 = tempRet0; + $742 = _i64Add($740 | 0, $741 | 0, $410 | 0, $411 | 0) | 0; + $743 = tempRet0; + $744 = _i64Add($742 | 0, $743 | 0, $406 | 0, $407 | 0) | 0; + $745 = tempRet0; + $746 = _i64Add($744 | 0, $745 | 0, $402 | 0, $403 | 0) | 0; + $747 = tempRet0; + $748 = _i64Add($746 | 0, $747 | 0, $404 | 0, $405 | 0) | 0; + $749 = tempRet0; + $750 = _i64Add($748 | 0, $749 | 0, $196 | 0, $197 | 0) | 0; + $751 = tempRet0; + $752 = _i64Add($750 | 0, $751 | 0, $726 | 0, $727 | 0) | 0; + $753 = tempRet0; + $754 = _bitshift64Shl($726 | 0, $727 | 0, 21) | 0; + $755 = tempRet0; + $756 = _i64Add($466 | 0, $467 | 0, 1048576, 0) | 0; + $757 = tempRet0; + $758 = _bitshift64Ashr($756 | 0, $757 | 0, 21) | 0; + $759 = tempRet0; + $760 = _i64Add($484 | 0, $485 | 0, $486 | 0, $487 | 0) | 0; + $761 = tempRet0; + $762 = _i64Add($760 | 0, $761 | 0, $482 | 0, $483 | 0) | 0; + $763 = tempRet0; + $764 = _i64Add($762 | 0, $763 | 0, $480 | 0, $481 | 0) | 0; + $765 = tempRet0; + $766 = _i64Add($764 | 0, $765 | 0, $478 | 0, $479 | 0) | 0; + $767 = tempRet0; + $768 = _i64Add($766 | 0, $767 | 0, $474 | 0, $475 | 0) | 0; + $769 = tempRet0; + $770 = _i64Add($768 | 0, $769 | 0, $476 | 0, $477 | 0) | 0; + $771 = tempRet0; + $772 = _i64Add($770 | 0, $771 | 0, $472 | 0, $473 | 0) | 0; + $773 = tempRet0; + $774 = _i64Add($772 | 0, $773 | 0, $470 | 0, $471 | 0) | 0; + $775 = tempRet0; + $776 = _i64Add($774 | 0, $775 | 0, $468 | 0, $469 | 0) | 0; + $777 = tempRet0; + $778 = _i64Add($776 | 0, $777 | 0, $758 | 0, $759 | 0) | 0; + $779 = tempRet0; + $780 = _bitshift64Shl($758 | 0, $759 | 0, 21) | 0; + $781 = tempRet0; + $782 = _i64Add($520 | 0, $521 | 0, 1048576, 0) | 0; + $783 = tempRet0; + $784 = _bitshift64Ashr($782 | 0, $783 | 0, 21) | 0; + $785 = tempRet0; + $786 = _i64Add($534 | 0, $535 | 0, $536 | 0, $537 | 0) | 0; + $787 = tempRet0; + $788 = _i64Add($786 | 0, $787 | 0, $532 | 0, $533 | 0) | 0; + $789 = tempRet0; + $790 = _i64Add($788 | 0, $789 | 0, $528 | 0, $529 | 0) | 0; + $791 = tempRet0; + $792 = _i64Add($790 | 0, $791 | 0, $530 | 0, $531 | 0) | 0; + $793 = tempRet0; + $794 = _i64Add($792 | 0, $793 | 0, $526 | 0, $527 | 0) | 0; + $795 = tempRet0; + $796 = _i64Add($794 | 0, $795 | 0, $524 | 0, $525 | 0) | 0; + $797 = tempRet0; + $798 = _i64Add($796 | 0, $797 | 0, $522 | 0, $523 | 0) | 0; + $799 = tempRet0; + $800 = _i64Add($798 | 0, $799 | 0, $784 | 0, $785 | 0) | 0; + $801 = tempRet0; + $802 = _bitshift64Shl($784 | 0, $785 | 0, 21) | 0; + $803 = tempRet0; + $804 = _i64Add($562 | 0, $563 | 0, 1048576, 0) | 0; + $805 = tempRet0; + $806 = _bitshift64Ashr($804 | 0, $805 | 0, 21) | 0; + $807 = tempRet0; + $808 = _i64Add($570 | 0, $571 | 0, $574 | 0, $575 | 0) | 0; + $809 = tempRet0; + $810 = _i64Add($808 | 0, $809 | 0, $572 | 0, $573 | 0) | 0; + $811 = tempRet0; + $812 = _i64Add($810 | 0, $811 | 0, $568 | 0, $569 | 0) | 0; + $813 = tempRet0; + $814 = _i64Add($812 | 0, $813 | 0, $566 | 0, $567 | 0) | 0; + $815 = tempRet0; + $816 = _i64Add($814 | 0, $815 | 0, $564 | 0, $565 | 0) | 0; + $817 = tempRet0; + $818 = _i64Add($816 | 0, $817 | 0, $806 | 0, $807 | 0) | 0; + $819 = tempRet0; + $820 = _bitshift64Shl($806 | 0, $807 | 0, 21) | 0; + $821 = tempRet0; + $822 = _i64Add($592 | 0, $593 | 0, 1048576, 0) | 0; + $823 = tempRet0; + $824 = _bitshift64Ashr($822 | 0, $823 | 0, 21) | 0; + $825 = tempRet0; + $826 = _i64Add($598 | 0, $599 | 0, $600 | 0, $601 | 0) | 0; + $827 = tempRet0; + $828 = _i64Add($826 | 0, $827 | 0, $596 | 0, $597 | 0) | 0; + $829 = tempRet0; + $830 = _i64Add($828 | 0, $829 | 0, $594 | 0, $595 | 0) | 0; + $831 = tempRet0; + $832 = _i64Add($830 | 0, $831 | 0, $824 | 0, $825 | 0) | 0; + $833 = tempRet0; + $834 = _bitshift64Shl($824 | 0, $825 | 0, 21) | 0; + $835 = tempRet0; + $836 = _i64Subtract($592 | 0, $593 | 0, $834 | 0, $835 | 0) | 0; + $837 = tempRet0; + $838 = _i64Add($610 | 0, $611 | 0, 1048576, 0) | 0; + $839 = tempRet0; + $840 = _bitshift64Lshr($838 | 0, $839 | 0, 21) | 0; + $841 = tempRet0; + $842 = _i64Add($616 | 0, $617 | 0, $840 | 0, $841 | 0) | 0; + $843 = tempRet0; + $844 = _bitshift64Shl($840 | 0, $841 | 0, 21) | 0; + $845 = tempRet0; + $846 = _i64Subtract($610 | 0, $611 | 0, $844 | 0, $845 | 0) | 0; + $847 = tempRet0; + $848 = _i64Add($618 | 0, $619 | 0, 1048576, 0) | 0; + $849 = tempRet0; + $850 = _bitshift64Lshr($848 | 0, $849 | 0, 21) | 0; + $851 = tempRet0; + $852 = _bitshift64Shl($850 | 0, $851 | 0, 21) | 0; + $853 = tempRet0; + $854 = _i64Subtract($618 | 0, $619 | 0, $852 | 0, $853 | 0) | 0; + $855 = tempRet0; + $856 = _i64Add($628 | 0, $629 | 0, 1048576, 0) | 0; + $857 = tempRet0; + $858 = _bitshift64Lshr($856 | 0, $857 | 0, 21) | 0; + $859 = tempRet0; + $860 = _bitshift64Shl($858 | 0, $859 | 0, 21) | 0; + $861 = tempRet0; + $862 = _i64Subtract($628 | 0, $629 | 0, $860 | 0, $861 | 0) | 0; + $863 = tempRet0; + $864 = _i64Add($646 | 0, $647 | 0, 1048576, 0) | 0; + $865 = tempRet0; + $866 = _bitshift64Ashr($864 | 0, $865 | 0, 21) | 0; + $867 = tempRet0; + $868 = _i64Add($670 | 0, $671 | 0, $866 | 0, $867 | 0) | 0; + $869 = tempRet0; + $870 = _bitshift64Shl($866 | 0, $867 | 0, 21) | 0; + $871 = tempRet0; + $872 = _i64Subtract($646 | 0, $647 | 0, $870 | 0, $871 | 0) | 0; + $873 = tempRet0; + $874 = _i64Add($666 | 0, $667 | 0, 1048576, 0) | 0; + $875 = tempRet0; + $876 = _bitshift64Ashr($874 | 0, $875 | 0, 21) | 0; + $877 = tempRet0; + $878 = _bitshift64Shl($876 | 0, $877 | 0, 21) | 0; + $879 = tempRet0; + $880 = _i64Subtract($666 | 0, $667 | 0, $878 | 0, $879 | 0) | 0; + $881 = tempRet0; + $882 = _i64Add($692 | 0, $693 | 0, 1048576, 0) | 0; + $883 = tempRet0; + $884 = _bitshift64Ashr($882 | 0, $883 | 0, 21) | 0; + $885 = tempRet0; + $886 = _bitshift64Shl($884 | 0, $885 | 0, 21) | 0; + $887 = tempRet0; + $888 = _i64Add($720 | 0, $721 | 0, 1048576, 0) | 0; + $889 = tempRet0; + $890 = _bitshift64Ashr($888 | 0, $889 | 0, 21) | 0; + $891 = tempRet0; + $892 = _bitshift64Shl($890 | 0, $891 | 0, 21) | 0; + $893 = tempRet0; + $894 = _i64Add($752 | 0, $753 | 0, 1048576, 0) | 0; + $895 = tempRet0; + $896 = _bitshift64Ashr($894 | 0, $895 | 0, 21) | 0; + $897 = tempRet0; + $898 = _bitshift64Shl($896 | 0, $897 | 0, 21) | 0; + $899 = tempRet0; + $900 = _i64Add($778 | 0, $779 | 0, 1048576, 0) | 0; + $901 = tempRet0; + $902 = _bitshift64Ashr($900 | 0, $901 | 0, 21) | 0; + $903 = tempRet0; + $904 = _bitshift64Shl($902 | 0, $903 | 0, 21) | 0; + $905 = tempRet0; + $906 = _i64Add($800 | 0, $801 | 0, 1048576, 0) | 0; + $907 = tempRet0; + $908 = _bitshift64Ashr($906 | 0, $907 | 0, 21) | 0; + $909 = tempRet0; + $910 = _bitshift64Shl($908 | 0, $909 | 0, 21) | 0; + $911 = tempRet0; + $912 = _i64Add($818 | 0, $819 | 0, 1048576, 0) | 0; + $913 = tempRet0; + $914 = _bitshift64Ashr($912 | 0, $913 | 0, 21) | 0; + $915 = tempRet0; + $916 = _i64Add($914 | 0, $915 | 0, $836 | 0, $837 | 0) | 0; + $917 = tempRet0; + $918 = _bitshift64Shl($914 | 0, $915 | 0, 21) | 0; + $919 = tempRet0; + $920 = _i64Subtract($818 | 0, $819 | 0, $918 | 0, $919 | 0) | 0; + $921 = tempRet0; + $922 = _i64Add($832 | 0, $833 | 0, 1048576, 0) | 0; + $923 = tempRet0; + $924 = _bitshift64Ashr($922 | 0, $923 | 0, 21) | 0; + $925 = tempRet0; + $926 = _i64Add($924 | 0, $925 | 0, $846 | 0, $847 | 0) | 0; + $927 = tempRet0; + $928 = _bitshift64Shl($924 | 0, $925 | 0, 21) | 0; + $929 = tempRet0; + $930 = _i64Subtract($832 | 0, $833 | 0, $928 | 0, $929 | 0) | 0; + $931 = tempRet0; + $932 = _i64Add($842 | 0, $843 | 0, 1048576, 0) | 0; + $933 = tempRet0; + $934 = _bitshift64Lshr($932 | 0, $933 | 0, 21) | 0; + $935 = tempRet0; + $936 = _i64Add($934 | 0, $935 | 0, $854 | 0, $855 | 0) | 0; + $937 = tempRet0; + $938 = _bitshift64Shl($934 | 0, $935 | 0, 21) | 0; + $939 = tempRet0; + $940 = _i64Subtract($842 | 0, $843 | 0, $938 | 0, $939 | 0) | 0; + $941 = tempRet0; + $942 = ___muldi3($850 | 0, $851 | 0, 666643, 0) | 0; + $943 = tempRet0; + $944 = ___muldi3($850 | 0, $851 | 0, 470296, 0) | 0; + $945 = tempRet0; + $946 = ___muldi3($850 | 0, $851 | 0, 654183, 0) | 0; + $947 = tempRet0; + $948 = ___muldi3($850 | 0, $851 | 0, -997805, -1) | 0; + $949 = tempRet0; + $950 = ___muldi3($850 | 0, $851 | 0, 136657, 0) | 0; + $951 = tempRet0; + $952 = ___muldi3($850 | 0, $851 | 0, -683901, -1) | 0; + $953 = tempRet0; + $954 = _i64Add($952 | 0, $953 | 0, $562 | 0, $563 | 0) | 0; + $955 = tempRet0; + $956 = _i64Subtract($954 | 0, $955 | 0, $820 | 0, $821 | 0) | 0; + $957 = tempRet0; + $958 = _i64Add($956 | 0, $957 | 0, $908 | 0, $909 | 0) | 0; + $959 = tempRet0; + $960 = ___muldi3($936 | 0, $937 | 0, 666643, 0) | 0; + $961 = tempRet0; + $962 = ___muldi3($936 | 0, $937 | 0, 470296, 0) | 0; + $963 = tempRet0; + $964 = ___muldi3($936 | 0, $937 | 0, 654183, 0) | 0; + $965 = tempRet0; + $966 = ___muldi3($936 | 0, $937 | 0, -997805, -1) | 0; + $967 = tempRet0; + $968 = ___muldi3($936 | 0, $937 | 0, 136657, 0) | 0; + $969 = tempRet0; + $970 = ___muldi3($936 | 0, $937 | 0, -683901, -1) | 0; + $971 = tempRet0; + $972 = ___muldi3($940 | 0, $941 | 0, 666643, 0) | 0; + $973 = tempRet0; + $974 = ___muldi3($940 | 0, $941 | 0, 470296, 0) | 0; + $975 = tempRet0; + $976 = ___muldi3($940 | 0, $941 | 0, 654183, 0) | 0; + $977 = tempRet0; + $978 = ___muldi3($940 | 0, $941 | 0, -997805, -1) | 0; + $979 = tempRet0; + $980 = ___muldi3($940 | 0, $941 | 0, 136657, 0) | 0; + $981 = tempRet0; + $982 = ___muldi3($940 | 0, $941 | 0, -683901, -1) | 0; + $983 = tempRet0; + $984 = _i64Add($948 | 0, $949 | 0, $520 | 0, $521 | 0) | 0; + $985 = tempRet0; + $986 = _i64Subtract($984 | 0, $985 | 0, $802 | 0, $803 | 0) | 0; + $987 = tempRet0; + $988 = _i64Add($986 | 0, $987 | 0, $902 | 0, $903 | 0) | 0; + $989 = tempRet0; + $990 = _i64Add($988 | 0, $989 | 0, $968 | 0, $969 | 0) | 0; + $991 = tempRet0; + $992 = _i64Add($990 | 0, $991 | 0, $982 | 0, $983 | 0) | 0; + $993 = tempRet0; + $994 = ___muldi3($926 | 0, $927 | 0, 666643, 0) | 0; + $995 = tempRet0; + $996 = ___muldi3($926 | 0, $927 | 0, 470296, 0) | 0; + $997 = tempRet0; + $998 = ___muldi3($926 | 0, $927 | 0, 654183, 0) | 0; + $999 = tempRet0; + $1000 = ___muldi3($926 | 0, $927 | 0, -997805, -1) | 0; + $1001 = tempRet0; + $1002 = ___muldi3($926 | 0, $927 | 0, 136657, 0) | 0; + $1003 = tempRet0; + $1004 = ___muldi3($926 | 0, $927 | 0, -683901, -1) | 0; + $1005 = tempRet0; + $1006 = ___muldi3($930 | 0, $931 | 0, 666643, 0) | 0; + $1007 = tempRet0; + $1008 = ___muldi3($930 | 0, $931 | 0, 470296, 0) | 0; + $1009 = tempRet0; + $1010 = ___muldi3($930 | 0, $931 | 0, 654183, 0) | 0; + $1011 = tempRet0; + $1012 = ___muldi3($930 | 0, $931 | 0, -997805, -1) | 0; + $1013 = tempRet0; + $1014 = ___muldi3($930 | 0, $931 | 0, 136657, 0) | 0; + $1015 = tempRet0; + $1016 = ___muldi3($930 | 0, $931 | 0, -683901, -1) | 0; + $1017 = tempRet0; + $1018 = _i64Add($944 | 0, $945 | 0, $466 | 0, $467 | 0) | 0; + $1019 = tempRet0; + $1020 = _i64Subtract($1018 | 0, $1019 | 0, $780 | 0, $781 | 0) | 0; + $1021 = tempRet0; + $1022 = _i64Add($1020 | 0, $1021 | 0, $964 | 0, $965 | 0) | 0; + $1023 = tempRet0; + $1024 = _i64Add($1022 | 0, $1023 | 0, $1002 | 0, $1003 | 0) | 0; + $1025 = tempRet0; + $1026 = _i64Add($1024 | 0, $1025 | 0, $978 | 0, $979 | 0) | 0; + $1027 = tempRet0; + $1028 = _i64Add($1026 | 0, $1027 | 0, $1016 | 0, $1017 | 0) | 0; + $1029 = tempRet0; + $1030 = _i64Add($1028 | 0, $1029 | 0, $896 | 0, $897 | 0) | 0; + $1031 = tempRet0; + $1032 = ___muldi3($916 | 0, $917 | 0, 666643, 0) | 0; + $1033 = tempRet0; + $1034 = _i64Add($1032 | 0, $1033 | 0, $284 | 0, $285 | 0) | 0; + $1035 = tempRet0; + $1036 = _i64Subtract($1034 | 0, $1035 | 0, $694 | 0, $695 | 0) | 0; + $1037 = tempRet0; + $1038 = _i64Add($1036 | 0, $1037 | 0, $876 | 0, $877 | 0) | 0; + $1039 = tempRet0; + $1040 = ___muldi3($916 | 0, $917 | 0, 470296, 0) | 0; + $1041 = tempRet0; + $1042 = ___muldi3($916 | 0, $917 | 0, 654183, 0) | 0; + $1043 = tempRet0; + $1044 = _i64Add($994 | 0, $995 | 0, $336 | 0, $337 | 0) | 0; + $1045 = tempRet0; + $1046 = _i64Add($1044 | 0, $1045 | 0, $1042 | 0, $1043 | 0) | 0; + $1047 = tempRet0; + $1048 = _i64Add($1046 | 0, $1047 | 0, $1008 | 0, $1009 | 0) | 0; + $1049 = tempRet0; + $1050 = _i64Subtract($1048 | 0, $1049 | 0, $722 | 0, $723 | 0) | 0; + $1051 = tempRet0; + $1052 = _i64Add($1050 | 0, $1051 | 0, $884 | 0, $885 | 0) | 0; + $1053 = tempRet0; + $1054 = ___muldi3($916 | 0, $917 | 0, -997805, -1) | 0; + $1055 = tempRet0; + $1056 = ___muldi3($916 | 0, $917 | 0, 136657, 0) | 0; + $1057 = tempRet0; + $1058 = _i64Add($998 | 0, $999 | 0, $960 | 0, $961 | 0) | 0; + $1059 = tempRet0; + $1060 = _i64Add($1058 | 0, $1059 | 0, $1056 | 0, $1057 | 0) | 0; + $1061 = tempRet0; + $1062 = _i64Add($1060 | 0, $1061 | 0, $974 | 0, $975 | 0) | 0; + $1063 = tempRet0; + $1064 = _i64Add($1062 | 0, $1063 | 0, $1012 | 0, $1013 | 0) | 0; + $1065 = tempRet0; + $1066 = _i64Add($1064 | 0, $1065 | 0, $400 | 0, $401 | 0) | 0; + $1067 = tempRet0; + $1068 = _i64Add($1066 | 0, $1067 | 0, $890 | 0, $891 | 0) | 0; + $1069 = tempRet0; + $1070 = _i64Subtract($1068 | 0, $1069 | 0, $754 | 0, $755 | 0) | 0; + $1071 = tempRet0; + $1072 = ___muldi3($916 | 0, $917 | 0, -683901, -1) | 0; + $1073 = tempRet0; + $1074 = _i64Add($1038 | 0, $1039 | 0, 1048576, 0) | 0; + $1075 = tempRet0; + $1076 = _bitshift64Ashr($1074 | 0, $1075 | 0, 21) | 0; + $1077 = tempRet0; + $1078 = _i64Add($1006 | 0, $1007 | 0, $1040 | 0, $1041 | 0) | 0; + $1079 = tempRet0; + $1080 = _i64Add($1078 | 0, $1079 | 0, $692 | 0, $693 | 0) | 0; + $1081 = tempRet0; + $1082 = _i64Subtract($1080 | 0, $1081 | 0, $886 | 0, $887 | 0) | 0; + $1083 = tempRet0; + $1084 = _i64Add($1082 | 0, $1083 | 0, $1076 | 0, $1077 | 0) | 0; + $1085 = tempRet0; + $1086 = _bitshift64Shl($1076 | 0, $1077 | 0, 21) | 0; + $1087 = tempRet0; + $1088 = _i64Add($1052 | 0, $1053 | 0, 1048576, 0) | 0; + $1089 = tempRet0; + $1090 = _bitshift64Ashr($1088 | 0, $1089 | 0, 21) | 0; + $1091 = tempRet0; + $1092 = _i64Add($1054 | 0, $1055 | 0, $996 | 0, $997 | 0) | 0; + $1093 = tempRet0; + $1094 = _i64Add($1092 | 0, $1093 | 0, $972 | 0, $973 | 0) | 0; + $1095 = tempRet0; + $1096 = _i64Add($1094 | 0, $1095 | 0, $1010 | 0, $1011 | 0) | 0; + $1097 = tempRet0; + $1098 = _i64Add($1096 | 0, $1097 | 0, $720 | 0, $721 | 0) | 0; + $1099 = tempRet0; + $1100 = _i64Subtract($1098 | 0, $1099 | 0, $892 | 0, $893 | 0) | 0; + $1101 = tempRet0; + $1102 = _i64Add($1100 | 0, $1101 | 0, $1090 | 0, $1091 | 0) | 0; + $1103 = tempRet0; + $1104 = _bitshift64Shl($1090 | 0, $1091 | 0, 21) | 0; + $1105 = tempRet0; + $1106 = _i64Add($1070 | 0, $1071 | 0, 1048576, 0) | 0; + $1107 = tempRet0; + $1108 = _bitshift64Ashr($1106 | 0, $1107 | 0, 21) | 0; + $1109 = tempRet0; + $1110 = _i64Add($962 | 0, $963 | 0, $942 | 0, $943 | 0) | 0; + $1111 = tempRet0; + $1112 = _i64Add($1110 | 0, $1111 | 0, $1000 | 0, $1001 | 0) | 0; + $1113 = tempRet0; + $1114 = _i64Add($1112 | 0, $1113 | 0, $1072 | 0, $1073 | 0) | 0; + $1115 = tempRet0; + $1116 = _i64Add($1114 | 0, $1115 | 0, $976 | 0, $977 | 0) | 0; + $1117 = tempRet0; + $1118 = _i64Add($1116 | 0, $1117 | 0, $1014 | 0, $1015 | 0) | 0; + $1119 = tempRet0; + $1120 = _i64Add($1118 | 0, $1119 | 0, $752 | 0, $753 | 0) | 0; + $1121 = tempRet0; + $1122 = _i64Subtract($1120 | 0, $1121 | 0, $898 | 0, $899 | 0) | 0; + $1123 = tempRet0; + $1124 = _i64Add($1122 | 0, $1123 | 0, $1108 | 0, $1109 | 0) | 0; + $1125 = tempRet0; + $1126 = _bitshift64Shl($1108 | 0, $1109 | 0, 21) | 0; + $1127 = tempRet0; + $1128 = _i64Add($1030 | 0, $1031 | 0, 1048576, 0) | 0; + $1129 = tempRet0; + $1130 = _bitshift64Ashr($1128 | 0, $1129 | 0, 21) | 0; + $1131 = tempRet0; + $1132 = _i64Add($778 | 0, $779 | 0, $946 | 0, $947 | 0) | 0; + $1133 = tempRet0; + $1134 = _i64Subtract($1132 | 0, $1133 | 0, $904 | 0, $905 | 0) | 0; + $1135 = tempRet0; + $1136 = _i64Add($1134 | 0, $1135 | 0, $966 | 0, $967 | 0) | 0; + $1137 = tempRet0; + $1138 = _i64Add($1136 | 0, $1137 | 0, $1004 | 0, $1005 | 0) | 0; + $1139 = tempRet0; + $1140 = _i64Add($1138 | 0, $1139 | 0, $980 | 0, $981 | 0) | 0; + $1141 = tempRet0; + $1142 = _i64Add($1140 | 0, $1141 | 0, $1130 | 0, $1131 | 0) | 0; + $1143 = tempRet0; + $1144 = _bitshift64Shl($1130 | 0, $1131 | 0, 21) | 0; + $1145 = tempRet0; + $1146 = _i64Subtract($1030 | 0, $1031 | 0, $1144 | 0, $1145 | 0) | 0; + $1147 = tempRet0; + $1148 = _i64Add($992 | 0, $993 | 0, 1048576, 0) | 0; + $1149 = tempRet0; + $1150 = _bitshift64Ashr($1148 | 0, $1149 | 0, 21) | 0; + $1151 = tempRet0; + $1152 = _i64Add($800 | 0, $801 | 0, $950 | 0, $951 | 0) | 0; + $1153 = tempRet0; + $1154 = _i64Subtract($1152 | 0, $1153 | 0, $910 | 0, $911 | 0) | 0; + $1155 = tempRet0; + $1156 = _i64Add($1154 | 0, $1155 | 0, $970 | 0, $971 | 0) | 0; + $1157 = tempRet0; + $1158 = _i64Add($1156 | 0, $1157 | 0, $1150 | 0, $1151 | 0) | 0; + $1159 = tempRet0; + $1160 = _bitshift64Shl($1150 | 0, $1151 | 0, 21) | 0; + $1161 = tempRet0; + $1162 = _i64Subtract($992 | 0, $993 | 0, $1160 | 0, $1161 | 0) | 0; + $1163 = tempRet0; + $1164 = _i64Add($958 | 0, $959 | 0, 1048576, 0) | 0; + $1165 = tempRet0; + $1166 = _bitshift64Ashr($1164 | 0, $1165 | 0, 21) | 0; + $1167 = tempRet0; + $1168 = _i64Add($1166 | 0, $1167 | 0, $920 | 0, $921 | 0) | 0; + $1169 = tempRet0; + $1170 = _bitshift64Shl($1166 | 0, $1167 | 0, 21) | 0; + $1171 = tempRet0; + $1172 = _i64Subtract($958 | 0, $959 | 0, $1170 | 0, $1171 | 0) | 0; + $1173 = tempRet0; + $1174 = _i64Add($1084 | 0, $1085 | 0, 1048576, 0) | 0; + $1175 = tempRet0; + $1176 = _bitshift64Ashr($1174 | 0, $1175 | 0, 21) | 0; + $1177 = tempRet0; + $1178 = _bitshift64Shl($1176 | 0, $1177 | 0, 21) | 0; + $1179 = tempRet0; + $1180 = _i64Add($1102 | 0, $1103 | 0, 1048576, 0) | 0; + $1181 = tempRet0; + $1182 = _bitshift64Ashr($1180 | 0, $1181 | 0, 21) | 0; + $1183 = tempRet0; + $1184 = _bitshift64Shl($1182 | 0, $1183 | 0, 21) | 0; + $1185 = tempRet0; + $1186 = _i64Add($1124 | 0, $1125 | 0, 1048576, 0) | 0; + $1187 = tempRet0; + $1188 = _bitshift64Ashr($1186 | 0, $1187 | 0, 21) | 0; + $1189 = tempRet0; + $1190 = _i64Add($1146 | 0, $1147 | 0, $1188 | 0, $1189 | 0) | 0; + $1191 = tempRet0; + $1192 = _bitshift64Shl($1188 | 0, $1189 | 0, 21) | 0; + $1193 = tempRet0; + $1194 = _i64Subtract($1124 | 0, $1125 | 0, $1192 | 0, $1193 | 0) | 0; + $1195 = tempRet0; + $1196 = _i64Add($1142 | 0, $1143 | 0, 1048576, 0) | 0; + $1197 = tempRet0; + $1198 = _bitshift64Ashr($1196 | 0, $1197 | 0, 21) | 0; + $1199 = tempRet0; + $1200 = _i64Add($1198 | 0, $1199 | 0, $1162 | 0, $1163 | 0) | 0; + $1201 = tempRet0; + $1202 = _bitshift64Shl($1198 | 0, $1199 | 0, 21) | 0; + $1203 = tempRet0; + $1204 = _i64Subtract($1142 | 0, $1143 | 0, $1202 | 0, $1203 | 0) | 0; + $1205 = tempRet0; + $1206 = _i64Add($1158 | 0, $1159 | 0, 1048576, 0) | 0; + $1207 = tempRet0; + $1208 = _bitshift64Ashr($1206 | 0, $1207 | 0, 21) | 0; + $1209 = tempRet0; + $1210 = _i64Add($1208 | 0, $1209 | 0, $1172 | 0, $1173 | 0) | 0; + $1211 = tempRet0; + $1212 = _bitshift64Shl($1208 | 0, $1209 | 0, 21) | 0; + $1213 = tempRet0; + $1214 = _i64Subtract($1158 | 0, $1159 | 0, $1212 | 0, $1213 | 0) | 0; + $1215 = tempRet0; + $1216 = ___muldi3($1168 | 0, $1169 | 0, 666643, 0) | 0; + $1217 = tempRet0; + $1218 = _i64Add($880 | 0, $881 | 0, $1216 | 0, $1217 | 0) | 0; + $1219 = tempRet0; + $1220 = ___muldi3($1168 | 0, $1169 | 0, 470296, 0) | 0; + $1221 = tempRet0; + $1222 = ___muldi3($1168 | 0, $1169 | 0, 654183, 0) | 0; + $1223 = tempRet0; + $1224 = ___muldi3($1168 | 0, $1169 | 0, -997805, -1) | 0; + $1225 = tempRet0; + $1226 = ___muldi3($1168 | 0, $1169 | 0, 136657, 0) | 0; + $1227 = tempRet0; + $1228 = ___muldi3($1168 | 0, $1169 | 0, -683901, -1) | 0; + $1229 = tempRet0; + $1230 = _i64Add($1070 | 0, $1071 | 0, $1228 | 0, $1229 | 0) | 0; + $1231 = tempRet0; + $1232 = _i64Subtract($1230 | 0, $1231 | 0, $1126 | 0, $1127 | 0) | 0; + $1233 = tempRet0; + $1234 = _i64Add($1232 | 0, $1233 | 0, $1182 | 0, $1183 | 0) | 0; + $1235 = tempRet0; + $1236 = ___muldi3($1210 | 0, $1211 | 0, 666643, 0) | 0; + $1237 = tempRet0; + $1238 = _i64Add($868 | 0, $869 | 0, $1236 | 0, $1237 | 0) | 0; + $1239 = tempRet0; + $1240 = ___muldi3($1210 | 0, $1211 | 0, 470296, 0) | 0; + $1241 = tempRet0; + $1242 = _i64Add($1218 | 0, $1219 | 0, $1240 | 0, $1241 | 0) | 0; + $1243 = tempRet0; + $1244 = ___muldi3($1210 | 0, $1211 | 0, 654183, 0) | 0; + $1245 = tempRet0; + $1246 = ___muldi3($1210 | 0, $1211 | 0, -997805, -1) | 0; + $1247 = tempRet0; + $1248 = ___muldi3($1210 | 0, $1211 | 0, 136657, 0) | 0; + $1249 = tempRet0; + $1250 = ___muldi3($1210 | 0, $1211 | 0, -683901, -1) | 0; + $1251 = tempRet0; + $1252 = ___muldi3($1214 | 0, $1215 | 0, 666643, 0) | 0; + $1253 = tempRet0; + $1254 = _i64Add($872 | 0, $873 | 0, $1252 | 0, $1253 | 0) | 0; + $1255 = tempRet0; + $1256 = ___muldi3($1214 | 0, $1215 | 0, 470296, 0) | 0; + $1257 = tempRet0; + $1258 = _i64Add($1238 | 0, $1239 | 0, $1256 | 0, $1257 | 0) | 0; + $1259 = tempRet0; + $1260 = ___muldi3($1214 | 0, $1215 | 0, 654183, 0) | 0; + $1261 = tempRet0; + $1262 = _i64Add($1242 | 0, $1243 | 0, $1260 | 0, $1261 | 0) | 0; + $1263 = tempRet0; + $1264 = ___muldi3($1214 | 0, $1215 | 0, -997805, -1) | 0; + $1265 = tempRet0; + $1266 = ___muldi3($1214 | 0, $1215 | 0, 136657, 0) | 0; + $1267 = tempRet0; + $1268 = ___muldi3($1214 | 0, $1215 | 0, -683901, -1) | 0; + $1269 = tempRet0; + $1270 = _i64Add($1052 | 0, $1053 | 0, $1224 | 0, $1225 | 0) | 0; + $1271 = tempRet0; + $1272 = _i64Subtract($1270 | 0, $1271 | 0, $1104 | 0, $1105 | 0) | 0; + $1273 = tempRet0; + $1274 = _i64Add($1272 | 0, $1273 | 0, $1176 | 0, $1177 | 0) | 0; + $1275 = tempRet0; + $1276 = _i64Add($1274 | 0, $1275 | 0, $1248 | 0, $1249 | 0) | 0; + $1277 = tempRet0; + $1278 = _i64Add($1276 | 0, $1277 | 0, $1268 | 0, $1269 | 0) | 0; + $1279 = tempRet0; + $1280 = ___muldi3($1200 | 0, $1201 | 0, 666643, 0) | 0; + $1281 = tempRet0; + $1282 = ___muldi3($1200 | 0, $1201 | 0, 470296, 0) | 0; + $1283 = tempRet0; + $1284 = ___muldi3($1200 | 0, $1201 | 0, 654183, 0) | 0; + $1285 = tempRet0; + $1286 = ___muldi3($1200 | 0, $1201 | 0, -997805, -1) | 0; + $1287 = tempRet0; + $1288 = ___muldi3($1200 | 0, $1201 | 0, 136657, 0) | 0; + $1289 = tempRet0; + $1290 = ___muldi3($1200 | 0, $1201 | 0, -683901, -1) | 0; + $1291 = tempRet0; + $1292 = ___muldi3($1204 | 0, $1205 | 0, 666643, 0) | 0; + $1293 = tempRet0; + $1294 = ___muldi3($1204 | 0, $1205 | 0, 470296, 0) | 0; + $1295 = tempRet0; + $1296 = ___muldi3($1204 | 0, $1205 | 0, 654183, 0) | 0; + $1297 = tempRet0; + $1298 = ___muldi3($1204 | 0, $1205 | 0, -997805, -1) | 0; + $1299 = tempRet0; + $1300 = ___muldi3($1204 | 0, $1205 | 0, 136657, 0) | 0; + $1301 = tempRet0; + $1302 = ___muldi3($1204 | 0, $1205 | 0, -683901, -1) | 0; + $1303 = tempRet0; + $1304 = _i64Add($1038 | 0, $1039 | 0, $1220 | 0, $1221 | 0) | 0; + $1305 = tempRet0; + $1306 = _i64Subtract($1304 | 0, $1305 | 0, $1086 | 0, $1087 | 0) | 0; + $1307 = tempRet0; + $1308 = _i64Add($1306 | 0, $1307 | 0, $1244 | 0, $1245 | 0) | 0; + $1309 = tempRet0; + $1310 = _i64Add($1308 | 0, $1309 | 0, $1264 | 0, $1265 | 0) | 0; + $1311 = tempRet0; + $1312 = _i64Add($1310 | 0, $1311 | 0, $1288 | 0, $1289 | 0) | 0; + $1313 = tempRet0; + $1314 = _i64Add($1312 | 0, $1313 | 0, $1302 | 0, $1303 | 0) | 0; + $1315 = tempRet0; + $1316 = ___muldi3($1190 | 0, $1191 | 0, 666643, 0) | 0; + $1317 = tempRet0; + $1318 = _i64Add($1316 | 0, $1317 | 0, $632 | 0, $633 | 0) | 0; + $1319 = tempRet0; + $1320 = ___muldi3($1190 | 0, $1191 | 0, 470296, 0) | 0; + $1321 = tempRet0; + $1322 = ___muldi3($1190 | 0, $1191 | 0, 654183, 0) | 0; + $1323 = tempRet0; + $1324 = _i64Add($858 | 0, $859 | 0, $216 | 0, $217 | 0) | 0; + $1325 = tempRet0; + $1326 = _i64Subtract($1324 | 0, $1325 | 0, $648 | 0, $649 | 0) | 0; + $1327 = tempRet0; + $1328 = _i64Add($1326 | 0, $1327 | 0, $1322 | 0, $1323 | 0) | 0; + $1329 = tempRet0; + $1330 = _i64Add($1328 | 0, $1329 | 0, $1280 | 0, $1281 | 0) | 0; + $1331 = tempRet0; + $1332 = _i64Add($1330 | 0, $1331 | 0, $1294 | 0, $1295 | 0) | 0; + $1333 = tempRet0; + $1334 = ___muldi3($1190 | 0, $1191 | 0, -997805, -1) | 0; + $1335 = tempRet0; + $1336 = ___muldi3($1190 | 0, $1191 | 0, 136657, 0) | 0; + $1337 = tempRet0; + $1338 = _i64Add($1258 | 0, $1259 | 0, $1336 | 0, $1337 | 0) | 0; + $1339 = tempRet0; + $1340 = _i64Add($1338 | 0, $1339 | 0, $1284 | 0, $1285 | 0) | 0; + $1341 = tempRet0; + $1342 = _i64Add($1340 | 0, $1341 | 0, $1298 | 0, $1299 | 0) | 0; + $1343 = tempRet0; + $1344 = ___muldi3($1190 | 0, $1191 | 0, -683901, -1) | 0; + $1345 = tempRet0; + $1346 = _i64Add($1318 | 0, $1319 | 0, 1048576, 0) | 0; + $1347 = tempRet0; + $1348 = _bitshift64Ashr($1346 | 0, $1347 | 0, 21) | 0; + $1349 = tempRet0; + $1350 = _i64Add($862 | 0, $863 | 0, $1320 | 0, $1321 | 0) | 0; + $1351 = tempRet0; + $1352 = _i64Add($1350 | 0, $1351 | 0, $1292 | 0, $1293 | 0) | 0; + $1353 = tempRet0; + $1354 = _i64Add($1352 | 0, $1353 | 0, $1348 | 0, $1349 | 0) | 0; + $1355 = tempRet0; + $1356 = _bitshift64Shl($1348 | 0, $1349 | 0, 21) | 0; + $1357 = tempRet0; + $1358 = _i64Subtract($1318 | 0, $1319 | 0, $1356 | 0, $1357 | 0) | 0; + $1359 = tempRet0; + $1360 = _i64Add($1332 | 0, $1333 | 0, 1048576, 0) | 0; + $1361 = tempRet0; + $1362 = _bitshift64Ashr($1360 | 0, $1361 | 0, 21) | 0; + $1363 = tempRet0; + $1364 = _i64Add($1254 | 0, $1255 | 0, $1334 | 0, $1335 | 0) | 0; + $1365 = tempRet0; + $1366 = _i64Add($1364 | 0, $1365 | 0, $1282 | 0, $1283 | 0) | 0; + $1367 = tempRet0; + $1368 = _i64Add($1366 | 0, $1367 | 0, $1296 | 0, $1297 | 0) | 0; + $1369 = tempRet0; + $1370 = _i64Add($1368 | 0, $1369 | 0, $1362 | 0, $1363 | 0) | 0; + $1371 = tempRet0; + $1372 = _bitshift64Shl($1362 | 0, $1363 | 0, 21) | 0; + $1373 = tempRet0; + $1374 = _i64Add($1342 | 0, $1343 | 0, 1048576, 0) | 0; + $1375 = tempRet0; + $1376 = _bitshift64Ashr($1374 | 0, $1375 | 0, 21) | 0; + $1377 = tempRet0; + $1378 = _i64Add($1262 | 0, $1263 | 0, $1344 | 0, $1345 | 0) | 0; + $1379 = tempRet0; + $1380 = _i64Add($1378 | 0, $1379 | 0, $1286 | 0, $1287 | 0) | 0; + $1381 = tempRet0; + $1382 = _i64Add($1380 | 0, $1381 | 0, $1300 | 0, $1301 | 0) | 0; + $1383 = tempRet0; + $1384 = _i64Add($1382 | 0, $1383 | 0, $1376 | 0, $1377 | 0) | 0; + $1385 = tempRet0; + $1386 = _bitshift64Shl($1376 | 0, $1377 | 0, 21) | 0; + $1387 = tempRet0; + $1388 = _i64Add($1314 | 0, $1315 | 0, 1048576, 0) | 0; + $1389 = tempRet0; + $1390 = _bitshift64Ashr($1388 | 0, $1389 | 0, 21) | 0; + $1391 = tempRet0; + $1392 = _i64Add($1084 | 0, $1085 | 0, $1222 | 0, $1223 | 0) | 0; + $1393 = tempRet0; + $1394 = _i64Add($1392 | 0, $1393 | 0, $1246 | 0, $1247 | 0) | 0; + $1395 = tempRet0; + $1396 = _i64Subtract($1394 | 0, $1395 | 0, $1178 | 0, $1179 | 0) | 0; + $1397 = tempRet0; + $1398 = _i64Add($1396 | 0, $1397 | 0, $1266 | 0, $1267 | 0) | 0; + $1399 = tempRet0; + $1400 = _i64Add($1398 | 0, $1399 | 0, $1290 | 0, $1291 | 0) | 0; + $1401 = tempRet0; + $1402 = _i64Add($1400 | 0, $1401 | 0, $1390 | 0, $1391 | 0) | 0; + $1403 = tempRet0; + $1404 = _bitshift64Shl($1390 | 0, $1391 | 0, 21) | 0; + $1405 = tempRet0; + $1406 = _i64Subtract($1314 | 0, $1315 | 0, $1404 | 0, $1405 | 0) | 0; + $1407 = tempRet0; + $1408 = _i64Add($1278 | 0, $1279 | 0, 1048576, 0) | 0; + $1409 = tempRet0; + $1410 = _bitshift64Ashr($1408 | 0, $1409 | 0, 21) | 0; + $1411 = tempRet0; + $1412 = _i64Add($1102 | 0, $1103 | 0, $1226 | 0, $1227 | 0) | 0; + $1413 = tempRet0; + $1414 = _i64Add($1412 | 0, $1413 | 0, $1250 | 0, $1251 | 0) | 0; + $1415 = tempRet0; + $1416 = _i64Subtract($1414 | 0, $1415 | 0, $1184 | 0, $1185 | 0) | 0; + $1417 = tempRet0; + $1418 = _i64Add($1416 | 0, $1417 | 0, $1410 | 0, $1411 | 0) | 0; + $1419 = tempRet0; + $1420 = _bitshift64Shl($1410 | 0, $1411 | 0, 21) | 0; + $1421 = tempRet0; + $1422 = _i64Subtract($1278 | 0, $1279 | 0, $1420 | 0, $1421 | 0) | 0; + $1423 = tempRet0; + $1424 = _i64Add($1234 | 0, $1235 | 0, 1048576, 0) | 0; + $1425 = tempRet0; + $1426 = _bitshift64Ashr($1424 | 0, $1425 | 0, 21) | 0; + $1427 = tempRet0; + $1428 = _i64Add($1194 | 0, $1195 | 0, $1426 | 0, $1427 | 0) | 0; + $1429 = tempRet0; + $1430 = _bitshift64Shl($1426 | 0, $1427 | 0, 21) | 0; + $1431 = tempRet0; + $1432 = _i64Subtract($1234 | 0, $1235 | 0, $1430 | 0, $1431 | 0) | 0; + $1433 = tempRet0; + $1434 = _i64Add($1354 | 0, $1355 | 0, 1048576, 0) | 0; + $1435 = tempRet0; + $1436 = _bitshift64Ashr($1434 | 0, $1435 | 0, 21) | 0; + $1437 = tempRet0; + $1438 = _bitshift64Shl($1436 | 0, $1437 | 0, 21) | 0; + $1439 = tempRet0; + $1440 = _i64Add($1370 | 0, $1371 | 0, 1048576, 0) | 0; + $1441 = tempRet0; + $1442 = _bitshift64Ashr($1440 | 0, $1441 | 0, 21) | 0; + $1443 = tempRet0; + $1444 = _bitshift64Shl($1442 | 0, $1443 | 0, 21) | 0; + $1445 = tempRet0; + $1446 = _i64Add($1384 | 0, $1385 | 0, 1048576, 0) | 0; + $1447 = tempRet0; + $1448 = _bitshift64Ashr($1446 | 0, $1447 | 0, 21) | 0; + $1449 = tempRet0; + $1450 = _i64Add($1406 | 0, $1407 | 0, $1448 | 0, $1449 | 0) | 0; + $1451 = tempRet0; + $1452 = _bitshift64Shl($1448 | 0, $1449 | 0, 21) | 0; + $1453 = tempRet0; + $1454 = _i64Add($1402 | 0, $1403 | 0, 1048576, 0) | 0; + $1455 = tempRet0; + $1456 = _bitshift64Ashr($1454 | 0, $1455 | 0, 21) | 0; + $1457 = tempRet0; + $1458 = _i64Add($1422 | 0, $1423 | 0, $1456 | 0, $1457 | 0) | 0; + $1459 = tempRet0; + $1460 = _bitshift64Shl($1456 | 0, $1457 | 0, 21) | 0; + $1461 = tempRet0; + $1462 = _i64Subtract($1402 | 0, $1403 | 0, $1460 | 0, $1461 | 0) | 0; + $1463 = tempRet0; + $1464 = _i64Add($1418 | 0, $1419 | 0, 1048576, 0) | 0; + $1465 = tempRet0; + $1466 = _bitshift64Ashr($1464 | 0, $1465 | 0, 21) | 0; + $1467 = tempRet0; + $1468 = _i64Add($1432 | 0, $1433 | 0, $1466 | 0, $1467 | 0) | 0; + $1469 = tempRet0; + $1470 = _bitshift64Shl($1466 | 0, $1467 | 0, 21) | 0; + $1471 = tempRet0; + $1472 = _i64Subtract($1418 | 0, $1419 | 0, $1470 | 0, $1471 | 0) | 0; + $1473 = tempRet0; + $1474 = _i64Add($1428 | 0, $1429 | 0, 1048576, 0) | 0; + $1475 = tempRet0; + $1476 = _bitshift64Ashr($1474 | 0, $1475 | 0, 21) | 0; + $1477 = tempRet0; + $1478 = _bitshift64Shl($1476 | 0, $1477 | 0, 21) | 0; + $1479 = tempRet0; + $1480 = _i64Subtract($1428 | 0, $1429 | 0, $1478 | 0, $1479 | 0) | 0; + $1481 = tempRet0; + $1482 = ___muldi3($1476 | 0, $1477 | 0, 666643, 0) | 0; + $1483 = tempRet0; + $1484 = _i64Add($1358 | 0, $1359 | 0, $1482 | 0, $1483 | 0) | 0; + $1485 = tempRet0; + $1486 = ___muldi3($1476 | 0, $1477 | 0, 470296, 0) | 0; + $1487 = tempRet0; + $1488 = ___muldi3($1476 | 0, $1477 | 0, 654183, 0) | 0; + $1489 = tempRet0; + $1490 = ___muldi3($1476 | 0, $1477 | 0, -997805, -1) | 0; + $1491 = tempRet0; + $1492 = ___muldi3($1476 | 0, $1477 | 0, 136657, 0) | 0; + $1493 = tempRet0; + $1494 = ___muldi3($1476 | 0, $1477 | 0, -683901, -1) | 0; + $1495 = tempRet0; + $1496 = _bitshift64Ashr($1484 | 0, $1485 | 0, 21) | 0; + $1497 = tempRet0; + $1498 = _i64Add($1486 | 0, $1487 | 0, $1354 | 0, $1355 | 0) | 0; + $1499 = tempRet0; + $1500 = _i64Subtract($1498 | 0, $1499 | 0, $1438 | 0, $1439 | 0) | 0; + $1501 = tempRet0; + $1502 = _i64Add($1500 | 0, $1501 | 0, $1496 | 0, $1497 | 0) | 0; + $1503 = tempRet0; + $1504 = _bitshift64Shl($1496 | 0, $1497 | 0, 21) | 0; + $1505 = tempRet0; + $1506 = _i64Subtract($1484 | 0, $1485 | 0, $1504 | 0, $1505 | 0) | 0; + $1507 = tempRet0; + $1508 = _bitshift64Ashr($1502 | 0, $1503 | 0, 21) | 0; + $1509 = tempRet0; + $1510 = _i64Add($1488 | 0, $1489 | 0, $1332 | 0, $1333 | 0) | 0; + $1511 = tempRet0; + $1512 = _i64Subtract($1510 | 0, $1511 | 0, $1372 | 0, $1373 | 0) | 0; + $1513 = tempRet0; + $1514 = _i64Add($1512 | 0, $1513 | 0, $1436 | 0, $1437 | 0) | 0; + $1515 = tempRet0; + $1516 = _i64Add($1514 | 0, $1515 | 0, $1508 | 0, $1509 | 0) | 0; + $1517 = tempRet0; + $1518 = _bitshift64Shl($1508 | 0, $1509 | 0, 21) | 0; + $1519 = tempRet0; + $1520 = _i64Subtract($1502 | 0, $1503 | 0, $1518 | 0, $1519 | 0) | 0; + $1521 = tempRet0; + $1522 = _bitshift64Ashr($1516 | 0, $1517 | 0, 21) | 0; + $1523 = tempRet0; + $1524 = _i64Add($1370 | 0, $1371 | 0, $1490 | 0, $1491 | 0) | 0; + $1525 = tempRet0; + $1526 = _i64Subtract($1524 | 0, $1525 | 0, $1444 | 0, $1445 | 0) | 0; + $1527 = tempRet0; + $1528 = _i64Add($1526 | 0, $1527 | 0, $1522 | 0, $1523 | 0) | 0; + $1529 = tempRet0; + $1530 = _bitshift64Shl($1522 | 0, $1523 | 0, 21) | 0; + $1531 = tempRet0; + $1532 = _i64Subtract($1516 | 0, $1517 | 0, $1530 | 0, $1531 | 0) | 0; + $1533 = tempRet0; + $1534 = _bitshift64Ashr($1528 | 0, $1529 | 0, 21) | 0; + $1535 = tempRet0; + $1536 = _i64Add($1492 | 0, $1493 | 0, $1342 | 0, $1343 | 0) | 0; + $1537 = tempRet0; + $1538 = _i64Subtract($1536 | 0, $1537 | 0, $1386 | 0, $1387 | 0) | 0; + $1539 = tempRet0; + $1540 = _i64Add($1538 | 0, $1539 | 0, $1442 | 0, $1443 | 0) | 0; + $1541 = tempRet0; + $1542 = _i64Add($1540 | 0, $1541 | 0, $1534 | 0, $1535 | 0) | 0; + $1543 = tempRet0; + $1544 = _bitshift64Shl($1534 | 0, $1535 | 0, 21) | 0; + $1545 = tempRet0; + $1546 = _i64Subtract($1528 | 0, $1529 | 0, $1544 | 0, $1545 | 0) | 0; + $1547 = tempRet0; + $1548 = _bitshift64Ashr($1542 | 0, $1543 | 0, 21) | 0; + $1549 = tempRet0; + $1550 = _i64Add($1384 | 0, $1385 | 0, $1494 | 0, $1495 | 0) | 0; + $1551 = tempRet0; + $1552 = _i64Subtract($1550 | 0, $1551 | 0, $1452 | 0, $1453 | 0) | 0; + $1553 = tempRet0; + $1554 = _i64Add($1552 | 0, $1553 | 0, $1548 | 0, $1549 | 0) | 0; + $1555 = tempRet0; + $1556 = _bitshift64Shl($1548 | 0, $1549 | 0, 21) | 0; + $1557 = tempRet0; + $1558 = _i64Subtract($1542 | 0, $1543 | 0, $1556 | 0, $1557 | 0) | 0; + $1559 = tempRet0; + $1560 = _bitshift64Ashr($1554 | 0, $1555 | 0, 21) | 0; + $1561 = tempRet0; + $1562 = _i64Add($1450 | 0, $1451 | 0, $1560 | 0, $1561 | 0) | 0; + $1563 = tempRet0; + $1564 = _bitshift64Shl($1560 | 0, $1561 | 0, 21) | 0; + $1565 = tempRet0; + $1566 = _i64Subtract($1554 | 0, $1555 | 0, $1564 | 0, $1565 | 0) | 0; + $1567 = tempRet0; + $1568 = _bitshift64Ashr($1562 | 0, $1563 | 0, 21) | 0; + $1569 = tempRet0; + $1570 = _i64Add($1568 | 0, $1569 | 0, $1462 | 0, $1463 | 0) | 0; + $1571 = tempRet0; + $1572 = _bitshift64Shl($1568 | 0, $1569 | 0, 21) | 0; + $1573 = tempRet0; + $1574 = _i64Subtract($1562 | 0, $1563 | 0, $1572 | 0, $1573 | 0) | 0; + $1575 = tempRet0; + $1576 = _bitshift64Ashr($1570 | 0, $1571 | 0, 21) | 0; + $1577 = tempRet0; + $1578 = _i64Add($1458 | 0, $1459 | 0, $1576 | 0, $1577 | 0) | 0; + $1579 = tempRet0; + $1580 = _bitshift64Shl($1576 | 0, $1577 | 0, 21) | 0; + $1581 = tempRet0; + $1582 = _i64Subtract($1570 | 0, $1571 | 0, $1580 | 0, $1581 | 0) | 0; + $1583 = tempRet0; + $1584 = _bitshift64Ashr($1578 | 0, $1579 | 0, 21) | 0; + $1585 = tempRet0; + $1586 = _i64Add($1584 | 0, $1585 | 0, $1472 | 0, $1473 | 0) | 0; + $1587 = tempRet0; + $1588 = _bitshift64Shl($1584 | 0, $1585 | 0, 21) | 0; + $1589 = tempRet0; + $1590 = _i64Subtract($1578 | 0, $1579 | 0, $1588 | 0, $1589 | 0) | 0; + $1591 = tempRet0; + $1592 = _bitshift64Ashr($1586 | 0, $1587 | 0, 21) | 0; + $1593 = tempRet0; + $1594 = _i64Add($1468 | 0, $1469 | 0, $1592 | 0, $1593 | 0) | 0; + $1595 = tempRet0; + $1596 = _bitshift64Shl($1592 | 0, $1593 | 0, 21) | 0; + $1597 = tempRet0; + $1598 = _i64Subtract($1586 | 0, $1587 | 0, $1596 | 0, $1597 | 0) | 0; + $1599 = tempRet0; + $1600 = _bitshift64Ashr($1594 | 0, $1595 | 0, 21) | 0; + $1601 = tempRet0; + $1602 = _i64Add($1600 | 0, $1601 | 0, $1480 | 0, $1481 | 0) | 0; + $1603 = tempRet0; + $1604 = _bitshift64Shl($1600 | 0, $1601 | 0, 21) | 0; + $1605 = tempRet0; + $1606 = _i64Subtract($1594 | 0, $1595 | 0, $1604 | 0, $1605 | 0) | 0; + $1607 = tempRet0; + $1608 = _bitshift64Ashr($1602 | 0, $1603 | 0, 21) | 0; + $1609 = tempRet0; + $1610 = _bitshift64Shl($1608 | 0, $1609 | 0, 21) | 0; + $1611 = tempRet0; + $1612 = _i64Subtract($1602 | 0, $1603 | 0, $1610 | 0, $1611 | 0) | 0; + $1613 = tempRet0; + $1614 = ___muldi3($1608 | 0, $1609 | 0, 666643, 0) | 0; + $1615 = tempRet0; + $1616 = _i64Add($1614 | 0, $1615 | 0, $1506 | 0, $1507 | 0) | 0; + $1617 = tempRet0; + $1618 = ___muldi3($1608 | 0, $1609 | 0, 470296, 0) | 0; + $1619 = tempRet0; + $1620 = _i64Add($1520 | 0, $1521 | 0, $1618 | 0, $1619 | 0) | 0; + $1621 = tempRet0; + $1622 = ___muldi3($1608 | 0, $1609 | 0, 654183, 0) | 0; + $1623 = tempRet0; + $1624 = _i64Add($1532 | 0, $1533 | 0, $1622 | 0, $1623 | 0) | 0; + $1625 = tempRet0; + $1626 = ___muldi3($1608 | 0, $1609 | 0, -997805, -1) | 0; + $1627 = tempRet0; + $1628 = _i64Add($1546 | 0, $1547 | 0, $1626 | 0, $1627 | 0) | 0; + $1629 = tempRet0; + $1630 = ___muldi3($1608 | 0, $1609 | 0, 136657, 0) | 0; + $1631 = tempRet0; + $1632 = _i64Add($1558 | 0, $1559 | 0, $1630 | 0, $1631 | 0) | 0; + $1633 = tempRet0; + $1634 = ___muldi3($1608 | 0, $1609 | 0, -683901, -1) | 0; + $1635 = tempRet0; + $1636 = _i64Add($1566 | 0, $1567 | 0, $1634 | 0, $1635 | 0) | 0; + $1637 = tempRet0; + $1638 = _bitshift64Ashr($1616 | 0, $1617 | 0, 21) | 0; + $1639 = tempRet0; + $1640 = _i64Add($1620 | 0, $1621 | 0, $1638 | 0, $1639 | 0) | 0; + $1641 = tempRet0; + $1642 = _bitshift64Shl($1638 | 0, $1639 | 0, 21) | 0; + $1643 = tempRet0; + $1644 = _i64Subtract($1616 | 0, $1617 | 0, $1642 | 0, $1643 | 0) | 0; + $1645 = tempRet0; + $1646 = _bitshift64Ashr($1640 | 0, $1641 | 0, 21) | 0; + $1647 = tempRet0; + $1648 = _i64Add($1624 | 0, $1625 | 0, $1646 | 0, $1647 | 0) | 0; + $1649 = tempRet0; + $1650 = _bitshift64Shl($1646 | 0, $1647 | 0, 21) | 0; + $1651 = tempRet0; + $1652 = _i64Subtract($1640 | 0, $1641 | 0, $1650 | 0, $1651 | 0) | 0; + $1653 = tempRet0; + $1654 = _bitshift64Ashr($1648 | 0, $1649 | 0, 21) | 0; + $1655 = tempRet0; + $1656 = _i64Add($1628 | 0, $1629 | 0, $1654 | 0, $1655 | 0) | 0; + $1657 = tempRet0; + $1658 = _bitshift64Shl($1654 | 0, $1655 | 0, 21) | 0; + $1659 = tempRet0; + $1660 = _i64Subtract($1648 | 0, $1649 | 0, $1658 | 0, $1659 | 0) | 0; + $1661 = tempRet0; + $1662 = _bitshift64Ashr($1656 | 0, $1657 | 0, 21) | 0; + $1663 = tempRet0; + $1664 = _i64Add($1632 | 0, $1633 | 0, $1662 | 0, $1663 | 0) | 0; + $1665 = tempRet0; + $1666 = _bitshift64Shl($1662 | 0, $1663 | 0, 21) | 0; + $1667 = tempRet0; + $1668 = _i64Subtract($1656 | 0, $1657 | 0, $1666 | 0, $1667 | 0) | 0; + $1669 = tempRet0; + $1670 = _bitshift64Ashr($1664 | 0, $1665 | 0, 21) | 0; + $1671 = tempRet0; + $1672 = _i64Add($1636 | 0, $1637 | 0, $1670 | 0, $1671 | 0) | 0; + $1673 = tempRet0; + $1674 = _bitshift64Shl($1670 | 0, $1671 | 0, 21) | 0; + $1675 = tempRet0; + $1676 = _i64Subtract($1664 | 0, $1665 | 0, $1674 | 0, $1675 | 0) | 0; + $1677 = tempRet0; + $1678 = _bitshift64Ashr($1672 | 0, $1673 | 0, 21) | 0; + $1679 = tempRet0; + $1680 = _i64Add($1678 | 0, $1679 | 0, $1574 | 0, $1575 | 0) | 0; + $1681 = tempRet0; + $1682 = _bitshift64Shl($1678 | 0, $1679 | 0, 21) | 0; + $1683 = tempRet0; + $1684 = _i64Subtract($1672 | 0, $1673 | 0, $1682 | 0, $1683 | 0) | 0; + $1685 = tempRet0; + $1686 = _bitshift64Ashr($1680 | 0, $1681 | 0, 21) | 0; + $1687 = tempRet0; + $1688 = _i64Add($1686 | 0, $1687 | 0, $1582 | 0, $1583 | 0) | 0; + $1689 = tempRet0; + $1690 = _bitshift64Shl($1686 | 0, $1687 | 0, 21) | 0; + $1691 = tempRet0; + $1692 = _i64Subtract($1680 | 0, $1681 | 0, $1690 | 0, $1691 | 0) | 0; + $1693 = tempRet0; + $1694 = _bitshift64Ashr($1688 | 0, $1689 | 0, 21) | 0; + $1695 = tempRet0; + $1696 = _i64Add($1694 | 0, $1695 | 0, $1590 | 0, $1591 | 0) | 0; + $1697 = tempRet0; + $1698 = _bitshift64Shl($1694 | 0, $1695 | 0, 21) | 0; + $1699 = tempRet0; + $1700 = _i64Subtract($1688 | 0, $1689 | 0, $1698 | 0, $1699 | 0) | 0; + $1701 = tempRet0; + $1702 = _bitshift64Ashr($1696 | 0, $1697 | 0, 21) | 0; + $1703 = tempRet0; + $1704 = _i64Add($1702 | 0, $1703 | 0, $1598 | 0, $1599 | 0) | 0; + $1705 = tempRet0; + $1706 = _bitshift64Shl($1702 | 0, $1703 | 0, 21) | 0; + $1707 = tempRet0; + $1708 = _i64Subtract($1696 | 0, $1697 | 0, $1706 | 0, $1707 | 0) | 0; + $1709 = tempRet0; + $1710 = _bitshift64Ashr($1704 | 0, $1705 | 0, 21) | 0; + $1711 = tempRet0; + $1712 = _i64Add($1710 | 0, $1711 | 0, $1606 | 0, $1607 | 0) | 0; + $1713 = tempRet0; + $1714 = _bitshift64Shl($1710 | 0, $1711 | 0, 21) | 0; + $1715 = tempRet0; + $1716 = _i64Subtract($1704 | 0, $1705 | 0, $1714 | 0, $1715 | 0) | 0; + $1717 = tempRet0; + $1718 = _bitshift64Ashr($1712 | 0, $1713 | 0, 21) | 0; + $1719 = tempRet0; + $1720 = _i64Add($1718 | 0, $1719 | 0, $1612 | 0, $1613 | 0) | 0; + $1721 = tempRet0; + $1722 = _bitshift64Shl($1718 | 0, $1719 | 0, 21) | 0; + $1723 = tempRet0; + $1724 = _i64Subtract($1712 | 0, $1713 | 0, $1722 | 0, $1723 | 0) | 0; + $1725 = tempRet0; + $1726 = $1644 & 255; + HEAP8[$s >> 0] = $1726; + $1727 = _bitshift64Lshr($1644 | 0, $1645 | 0, 8) | 0; + $1728 = tempRet0; + $1729 = $1727 & 255; + $1730 = ($s + 1) | 0; + HEAP8[$1730 >> 0] = $1729; + $1731 = _bitshift64Lshr($1644 | 0, $1645 | 0, 16) | 0; + $1732 = tempRet0; + $1733 = _bitshift64Shl($1652 | 0, $1653 | 0, 5) | 0; + $1734 = tempRet0; + $1735 = $1733 | $1731; + $1734 | $1732; + $1736 = $1735 & 255; + $1737 = ($s + 2) | 0; + HEAP8[$1737 >> 0] = $1736; + $1738 = _bitshift64Lshr($1652 | 0, $1653 | 0, 3) | 0; + $1739 = tempRet0; + $1740 = $1738 & 255; + $1741 = ($s + 3) | 0; + HEAP8[$1741 >> 0] = $1740; + $1742 = _bitshift64Lshr($1652 | 0, $1653 | 0, 11) | 0; + $1743 = tempRet0; + $1744 = $1742 & 255; + $1745 = ($s + 4) | 0; + HEAP8[$1745 >> 0] = $1744; + $1746 = _bitshift64Lshr($1652 | 0, $1653 | 0, 19) | 0; + $1747 = tempRet0; + $1748 = _bitshift64Shl($1660 | 0, $1661 | 0, 2) | 0; + $1749 = tempRet0; + $1750 = $1748 | $1746; + $1749 | $1747; + $1751 = $1750 & 255; + $1752 = ($s + 5) | 0; + HEAP8[$1752 >> 0] = $1751; + $1753 = _bitshift64Lshr($1660 | 0, $1661 | 0, 6) | 0; + $1754 = tempRet0; + $1755 = $1753 & 255; + $1756 = ($s + 6) | 0; + HEAP8[$1756 >> 0] = $1755; + $1757 = _bitshift64Lshr($1660 | 0, $1661 | 0, 14) | 0; + $1758 = tempRet0; + $1759 = _bitshift64Shl($1668 | 0, $1669 | 0, 7) | 0; + $1760 = tempRet0; + $1761 = $1759 | $1757; + $1760 | $1758; + $1762 = $1761 & 255; + $1763 = ($s + 7) | 0; + HEAP8[$1763 >> 0] = $1762; + $1764 = _bitshift64Lshr($1668 | 0, $1669 | 0, 1) | 0; + $1765 = tempRet0; + $1766 = $1764 & 255; + $1767 = ($s + 8) | 0; + HEAP8[$1767 >> 0] = $1766; + $1768 = _bitshift64Lshr($1668 | 0, $1669 | 0, 9) | 0; + $1769 = tempRet0; + $1770 = $1768 & 255; + $1771 = ($s + 9) | 0; + HEAP8[$1771 >> 0] = $1770; + $1772 = _bitshift64Lshr($1668 | 0, $1669 | 0, 17) | 0; + $1773 = tempRet0; + $1774 = _bitshift64Shl($1676 | 0, $1677 | 0, 4) | 0; + $1775 = tempRet0; + $1776 = $1774 | $1772; + $1775 | $1773; + $1777 = $1776 & 255; + $1778 = ($s + 10) | 0; + HEAP8[$1778 >> 0] = $1777; + $1779 = _bitshift64Lshr($1676 | 0, $1677 | 0, 4) | 0; + $1780 = tempRet0; + $1781 = $1779 & 255; + $1782 = ($s + 11) | 0; + HEAP8[$1782 >> 0] = $1781; + $1783 = _bitshift64Lshr($1676 | 0, $1677 | 0, 12) | 0; + $1784 = tempRet0; + $1785 = $1783 & 255; + $1786 = ($s + 12) | 0; + HEAP8[$1786 >> 0] = $1785; + $1787 = _bitshift64Lshr($1676 | 0, $1677 | 0, 20) | 0; + $1788 = tempRet0; + $1789 = _bitshift64Shl($1684 | 0, $1685 | 0, 1) | 0; + $1790 = tempRet0; + $1791 = $1789 | $1787; + $1790 | $1788; + $1792 = $1791 & 255; + $1793 = ($s + 13) | 0; + HEAP8[$1793 >> 0] = $1792; + $1794 = _bitshift64Lshr($1684 | 0, $1685 | 0, 7) | 0; + $1795 = tempRet0; + $1796 = $1794 & 255; + $1797 = ($s + 14) | 0; + HEAP8[$1797 >> 0] = $1796; + $1798 = _bitshift64Lshr($1684 | 0, $1685 | 0, 15) | 0; + $1799 = tempRet0; + $1800 = _bitshift64Shl($1692 | 0, $1693 | 0, 6) | 0; + $1801 = tempRet0; + $1802 = $1800 | $1798; + $1801 | $1799; + $1803 = $1802 & 255; + $1804 = ($s + 15) | 0; + HEAP8[$1804 >> 0] = $1803; + $1805 = _bitshift64Lshr($1692 | 0, $1693 | 0, 2) | 0; + $1806 = tempRet0; + $1807 = $1805 & 255; + $1808 = ($s + 16) | 0; + HEAP8[$1808 >> 0] = $1807; + $1809 = _bitshift64Lshr($1692 | 0, $1693 | 0, 10) | 0; + $1810 = tempRet0; + $1811 = $1809 & 255; + $1812 = ($s + 17) | 0; + HEAP8[$1812 >> 0] = $1811; + $1813 = _bitshift64Lshr($1692 | 0, $1693 | 0, 18) | 0; + $1814 = tempRet0; + $1815 = _bitshift64Shl($1700 | 0, $1701 | 0, 3) | 0; + $1816 = tempRet0; + $1817 = $1815 | $1813; + $1816 | $1814; + $1818 = $1817 & 255; + $1819 = ($s + 18) | 0; + HEAP8[$1819 >> 0] = $1818; + $1820 = _bitshift64Lshr($1700 | 0, $1701 | 0, 5) | 0; + $1821 = tempRet0; + $1822 = $1820 & 255; + $1823 = ($s + 19) | 0; + HEAP8[$1823 >> 0] = $1822; + $1824 = _bitshift64Lshr($1700 | 0, $1701 | 0, 13) | 0; + $1825 = tempRet0; + $1826 = $1824 & 255; + $1827 = ($s + 20) | 0; + HEAP8[$1827 >> 0] = $1826; + $1828 = $1708 & 255; + $1829 = ($s + 21) | 0; + HEAP8[$1829 >> 0] = $1828; + $1830 = _bitshift64Lshr($1708 | 0, $1709 | 0, 8) | 0; + $1831 = tempRet0; + $1832 = $1830 & 255; + $1833 = ($s + 22) | 0; + HEAP8[$1833 >> 0] = $1832; + $1834 = _bitshift64Lshr($1708 | 0, $1709 | 0, 16) | 0; + $1835 = tempRet0; + $1836 = _bitshift64Shl($1716 | 0, $1717 | 0, 5) | 0; + $1837 = tempRet0; + $1838 = $1836 | $1834; + $1837 | $1835; + $1839 = $1838 & 255; + $1840 = ($s + 23) | 0; + HEAP8[$1840 >> 0] = $1839; + $1841 = _bitshift64Lshr($1716 | 0, $1717 | 0, 3) | 0; + $1842 = tempRet0; + $1843 = $1841 & 255; + $1844 = ($s + 24) | 0; + HEAP8[$1844 >> 0] = $1843; + $1845 = _bitshift64Lshr($1716 | 0, $1717 | 0, 11) | 0; + $1846 = tempRet0; + $1847 = $1845 & 255; + $1848 = ($s + 25) | 0; + HEAP8[$1848 >> 0] = $1847; + $1849 = _bitshift64Lshr($1716 | 0, $1717 | 0, 19) | 0; + $1850 = tempRet0; + $1851 = _bitshift64Shl($1724 | 0, $1725 | 0, 2) | 0; + $1852 = tempRet0; + $1853 = $1851 | $1849; + $1852 | $1850; + $1854 = $1853 & 255; + $1855 = ($s + 26) | 0; + HEAP8[$1855 >> 0] = $1854; + $1856 = _bitshift64Lshr($1724 | 0, $1725 | 0, 6) | 0; + $1857 = tempRet0; + $1858 = $1856 & 255; + $1859 = ($s + 27) | 0; + HEAP8[$1859 >> 0] = $1858; + $1860 = _bitshift64Lshr($1724 | 0, $1725 | 0, 14) | 0; + $1861 = tempRet0; + $1862 = _bitshift64Shl($1720 | 0, $1721 | 0, 7) | 0; + $1863 = tempRet0; + $1864 = $1860 | $1862; + $1861 | $1863; + $1865 = $1864 & 255; + $1866 = ($s + 28) | 0; + HEAP8[$1866 >> 0] = $1865; + $1867 = _bitshift64Lshr($1720 | 0, $1721 | 0, 1) | 0; + $1868 = tempRet0; + $1869 = $1867 & 255; + $1870 = ($s + 29) | 0; + HEAP8[$1870 >> 0] = $1869; + $1871 = _bitshift64Lshr($1720 | 0, $1721 | 0, 9) | 0; + $1872 = tempRet0; + $1873 = $1871 & 255; + $1874 = ($s + 30) | 0; + HEAP8[$1874 >> 0] = $1873; + $1875 = _bitshift64Lshr($1720 | 0, $1721 | 0, 17) | 0; + $1876 = tempRet0; + $1877 = $1875 & 255; + $1878 = ($s + 31) | 0; + HEAP8[$1878 >> 0] = $1877; + STACKTOP = sp; + return; + } + function _load_347($in) { + $in = $in | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP8[$in >> 0] | 0; + $1 = $0 & 255; + $2 = ($in + 1) | 0; + $3 = HEAP8[$2 >> 0] | 0; + $4 = $3 & 255; + $5 = _bitshift64Shl($4 | 0, 0, 8) | 0; + $6 = tempRet0; + $7 = $5 | $1; + $8 = ($in + 2) | 0; + $9 = HEAP8[$8 >> 0] | 0; + $10 = $9 & 255; + $11 = _bitshift64Shl($10 | 0, 0, 16) | 0; + $12 = tempRet0; + $13 = $7 | $11; + $14 = $6 | $12; + tempRet0 = $14; + STACKTOP = sp; + return $13 | 0; + } + function _load_448($in) { + $in = $in | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0; + var $8 = 0, + $9 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP8[$in >> 0] | 0; + $1 = $0 & 255; + $2 = ($in + 1) | 0; + $3 = HEAP8[$2 >> 0] | 0; + $4 = $3 & 255; + $5 = _bitshift64Shl($4 | 0, 0, 8) | 0; + $6 = tempRet0; + $7 = $5 | $1; + $8 = ($in + 2) | 0; + $9 = HEAP8[$8 >> 0] | 0; + $10 = $9 & 255; + $11 = _bitshift64Shl($10 | 0, 0, 16) | 0; + $12 = tempRet0; + $13 = $7 | $11; + $14 = $6 | $12; + $15 = ($in + 3) | 0; + $16 = HEAP8[$15 >> 0] | 0; + $17 = $16 & 255; + $18 = _bitshift64Shl($17 | 0, 0, 24) | 0; + $19 = tempRet0; + $20 = $13 | $18; + $21 = $14 | $19; + tempRet0 = $21; + STACKTOP = sp; + return $20 | 0; + } + function _crypto_sign_ed25519_ref10_sc_reduce($s) { + $s = $s | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $1000 = 0, + $1001 = 0, + $1002 = 0, + $1003 = 0, + $1004 = 0, + $1005 = 0, + $1006 = 0, + $1007 = 0, + $1008 = 0, + $1009 = 0, + $101 = 0, + $1010 = 0, + $1011 = 0, + $1012 = 0, + $1013 = 0, + $1014 = 0; + var $1015 = 0, + $1016 = 0, + $1017 = 0, + $1018 = 0, + $1019 = 0, + $102 = 0, + $1020 = 0, + $1021 = 0, + $1022 = 0, + $103 = 0, + $104 = 0, + $105 = 0, + $106 = 0, + $107 = 0, + $108 = 0, + $109 = 0, + $11 = 0, + $110 = 0, + $111 = 0, + $112 = 0; + var $113 = 0, + $114 = 0, + $115 = 0, + $116 = 0, + $117 = 0, + $118 = 0, + $119 = 0, + $12 = 0, + $120 = 0, + $121 = 0, + $122 = 0, + $123 = 0, + $124 = 0, + $125 = 0, + $126 = 0, + $127 = 0, + $128 = 0, + $129 = 0, + $13 = 0, + $130 = 0; + var $131 = 0, + $132 = 0, + $133 = 0, + $134 = 0, + $135 = 0, + $136 = 0, + $137 = 0, + $138 = 0, + $139 = 0, + $14 = 0, + $140 = 0, + $141 = 0, + $142 = 0, + $143 = 0, + $144 = 0, + $145 = 0, + $146 = 0, + $147 = 0, + $148 = 0, + $149 = 0; + var $15 = 0, + $150 = 0, + $151 = 0, + $152 = 0, + $153 = 0, + $154 = 0, + $155 = 0, + $156 = 0, + $157 = 0, + $158 = 0, + $159 = 0, + $16 = 0, + $160 = 0, + $161 = 0, + $162 = 0, + $163 = 0, + $164 = 0, + $165 = 0, + $166 = 0, + $167 = 0; + var $168 = 0, + $169 = 0, + $17 = 0, + $170 = 0, + $171 = 0, + $172 = 0, + $173 = 0, + $174 = 0, + $175 = 0, + $176 = 0, + $177 = 0, + $178 = 0, + $179 = 0, + $18 = 0, + $180 = 0, + $181 = 0, + $182 = 0, + $183 = 0, + $184 = 0, + $185 = 0; + var $186 = 0, + $187 = 0, + $188 = 0, + $189 = 0, + $19 = 0, + $190 = 0, + $191 = 0, + $192 = 0, + $193 = 0, + $194 = 0, + $195 = 0, + $196 = 0, + $197 = 0, + $198 = 0, + $199 = 0, + $2 = 0, + $20 = 0, + $200 = 0, + $201 = 0, + $202 = 0; + var $203 = 0, + $204 = 0, + $205 = 0, + $206 = 0, + $207 = 0, + $208 = 0, + $209 = 0, + $21 = 0, + $210 = 0, + $211 = 0, + $212 = 0, + $213 = 0, + $214 = 0, + $215 = 0, + $216 = 0, + $217 = 0, + $218 = 0, + $219 = 0, + $22 = 0, + $220 = 0; + var $221 = 0, + $222 = 0, + $223 = 0, + $224 = 0, + $225 = 0, + $226 = 0, + $227 = 0, + $228 = 0, + $229 = 0, + $23 = 0, + $230 = 0, + $231 = 0, + $232 = 0, + $233 = 0, + $234 = 0, + $235 = 0, + $236 = 0, + $237 = 0, + $238 = 0, + $239 = 0; + var $24 = 0, + $240 = 0, + $241 = 0, + $242 = 0, + $243 = 0, + $244 = 0, + $245 = 0, + $246 = 0, + $247 = 0, + $248 = 0, + $249 = 0, + $25 = 0, + $250 = 0, + $251 = 0, + $252 = 0, + $253 = 0, + $254 = 0, + $255 = 0, + $256 = 0, + $257 = 0; + var $258 = 0, + $259 = 0, + $26 = 0, + $260 = 0, + $261 = 0, + $262 = 0, + $263 = 0, + $264 = 0, + $265 = 0, + $266 = 0, + $267 = 0, + $268 = 0, + $269 = 0, + $27 = 0, + $270 = 0, + $271 = 0, + $272 = 0, + $273 = 0, + $274 = 0, + $275 = 0; + var $276 = 0, + $277 = 0, + $278 = 0, + $279 = 0, + $28 = 0, + $280 = 0, + $281 = 0, + $282 = 0, + $283 = 0, + $284 = 0, + $285 = 0, + $286 = 0, + $287 = 0, + $288 = 0, + $289 = 0, + $29 = 0, + $290 = 0, + $291 = 0, + $292 = 0, + $293 = 0; + var $294 = 0, + $295 = 0, + $296 = 0, + $297 = 0, + $298 = 0, + $299 = 0, + $3 = 0, + $30 = 0, + $300 = 0, + $301 = 0, + $302 = 0, + $303 = 0, + $304 = 0, + $305 = 0, + $306 = 0, + $307 = 0, + $308 = 0, + $309 = 0, + $31 = 0, + $310 = 0; + var $311 = 0, + $312 = 0, + $313 = 0, + $314 = 0, + $315 = 0, + $316 = 0, + $317 = 0, + $318 = 0, + $319 = 0, + $32 = 0, + $320 = 0, + $321 = 0, + $322 = 0, + $323 = 0, + $324 = 0, + $325 = 0, + $326 = 0, + $327 = 0, + $328 = 0, + $329 = 0; + var $33 = 0, + $330 = 0, + $331 = 0, + $332 = 0, + $333 = 0, + $334 = 0, + $335 = 0, + $336 = 0, + $337 = 0, + $338 = 0, + $339 = 0, + $34 = 0, + $340 = 0, + $341 = 0, + $342 = 0, + $343 = 0, + $344 = 0, + $345 = 0, + $346 = 0, + $347 = 0; + var $348 = 0, + $349 = 0, + $35 = 0, + $350 = 0, + $351 = 0, + $352 = 0, + $353 = 0, + $354 = 0, + $355 = 0, + $356 = 0, + $357 = 0, + $358 = 0, + $359 = 0, + $36 = 0, + $360 = 0, + $361 = 0, + $362 = 0, + $363 = 0, + $364 = 0, + $365 = 0; + var $366 = 0, + $367 = 0, + $368 = 0, + $369 = 0, + $37 = 0, + $370 = 0, + $371 = 0, + $372 = 0, + $373 = 0, + $374 = 0, + $375 = 0, + $376 = 0, + $377 = 0, + $378 = 0, + $379 = 0, + $38 = 0, + $380 = 0, + $381 = 0, + $382 = 0, + $383 = 0; + var $384 = 0, + $385 = 0, + $386 = 0, + $387 = 0, + $388 = 0, + $389 = 0, + $39 = 0, + $390 = 0, + $391 = 0, + $392 = 0, + $393 = 0, + $394 = 0, + $395 = 0, + $396 = 0, + $397 = 0, + $398 = 0, + $399 = 0, + $4 = 0, + $40 = 0, + $400 = 0; + var $401 = 0, + $402 = 0, + $403 = 0, + $404 = 0, + $405 = 0, + $406 = 0, + $407 = 0, + $408 = 0, + $409 = 0, + $41 = 0, + $410 = 0, + $411 = 0, + $412 = 0, + $413 = 0, + $414 = 0, + $415 = 0, + $416 = 0, + $417 = 0, + $418 = 0, + $419 = 0; + var $42 = 0, + $420 = 0, + $421 = 0, + $422 = 0, + $423 = 0, + $424 = 0, + $425 = 0, + $426 = 0, + $427 = 0, + $428 = 0, + $429 = 0, + $43 = 0, + $430 = 0, + $431 = 0, + $432 = 0, + $433 = 0, + $434 = 0, + $435 = 0, + $436 = 0, + $437 = 0; + var $438 = 0, + $439 = 0, + $44 = 0, + $440 = 0, + $441 = 0, + $442 = 0, + $443 = 0, + $444 = 0, + $445 = 0, + $446 = 0, + $447 = 0, + $448 = 0, + $449 = 0, + $45 = 0, + $450 = 0, + $451 = 0, + $452 = 0, + $453 = 0, + $454 = 0, + $455 = 0; + var $456 = 0, + $457 = 0, + $458 = 0, + $459 = 0, + $46 = 0, + $460 = 0, + $461 = 0, + $462 = 0, + $463 = 0, + $464 = 0, + $465 = 0, + $466 = 0, + $467 = 0, + $468 = 0, + $469 = 0, + $47 = 0, + $470 = 0, + $471 = 0, + $472 = 0, + $473 = 0; + var $474 = 0, + $475 = 0, + $476 = 0, + $477 = 0, + $478 = 0, + $479 = 0, + $48 = 0, + $480 = 0, + $481 = 0, + $482 = 0, + $483 = 0, + $484 = 0, + $485 = 0, + $486 = 0, + $487 = 0, + $488 = 0, + $489 = 0, + $49 = 0, + $490 = 0, + $491 = 0; + var $492 = 0, + $493 = 0, + $494 = 0, + $495 = 0, + $496 = 0, + $497 = 0, + $498 = 0, + $499 = 0, + $5 = 0, + $50 = 0, + $500 = 0, + $501 = 0, + $502 = 0, + $503 = 0, + $504 = 0, + $505 = 0, + $506 = 0, + $507 = 0, + $508 = 0, + $509 = 0; + var $51 = 0, + $510 = 0, + $511 = 0, + $512 = 0, + $513 = 0, + $514 = 0, + $515 = 0, + $516 = 0, + $517 = 0, + $518 = 0, + $519 = 0, + $52 = 0, + $520 = 0, + $521 = 0, + $522 = 0, + $523 = 0, + $524 = 0, + $525 = 0, + $526 = 0, + $527 = 0; + var $528 = 0, + $529 = 0, + $53 = 0, + $530 = 0, + $531 = 0, + $532 = 0, + $533 = 0, + $534 = 0, + $535 = 0, + $536 = 0, + $537 = 0, + $538 = 0, + $539 = 0, + $54 = 0, + $540 = 0, + $541 = 0, + $542 = 0, + $543 = 0, + $544 = 0, + $545 = 0; + var $546 = 0, + $547 = 0, + $548 = 0, + $549 = 0, + $55 = 0, + $550 = 0, + $551 = 0, + $552 = 0, + $553 = 0, + $554 = 0, + $555 = 0, + $556 = 0, + $557 = 0, + $558 = 0, + $559 = 0, + $56 = 0, + $560 = 0, + $561 = 0, + $562 = 0, + $563 = 0; + var $564 = 0, + $565 = 0, + $566 = 0, + $567 = 0, + $568 = 0, + $569 = 0, + $57 = 0, + $570 = 0, + $571 = 0, + $572 = 0, + $573 = 0, + $574 = 0, + $575 = 0, + $576 = 0, + $577 = 0, + $578 = 0, + $579 = 0, + $58 = 0, + $580 = 0, + $581 = 0; + var $582 = 0, + $583 = 0, + $584 = 0, + $585 = 0, + $586 = 0, + $587 = 0, + $588 = 0, + $589 = 0, + $59 = 0, + $590 = 0, + $591 = 0, + $592 = 0, + $593 = 0, + $594 = 0, + $595 = 0, + $596 = 0, + $597 = 0, + $598 = 0, + $599 = 0, + $6 = 0; + var $60 = 0, + $600 = 0, + $601 = 0, + $602 = 0, + $603 = 0, + $604 = 0, + $605 = 0, + $606 = 0, + $607 = 0, + $608 = 0, + $609 = 0, + $61 = 0, + $610 = 0, + $611 = 0, + $612 = 0, + $613 = 0, + $614 = 0, + $615 = 0, + $616 = 0, + $617 = 0; + var $618 = 0, + $619 = 0, + $62 = 0, + $620 = 0, + $621 = 0, + $622 = 0, + $623 = 0, + $624 = 0, + $625 = 0, + $626 = 0, + $627 = 0, + $628 = 0, + $629 = 0, + $63 = 0, + $630 = 0, + $631 = 0, + $632 = 0, + $633 = 0, + $634 = 0, + $635 = 0; + var $636 = 0, + $637 = 0, + $638 = 0, + $639 = 0, + $64 = 0, + $640 = 0, + $641 = 0, + $642 = 0, + $643 = 0, + $644 = 0, + $645 = 0, + $646 = 0, + $647 = 0, + $648 = 0, + $649 = 0, + $65 = 0, + $650 = 0, + $651 = 0, + $652 = 0, + $653 = 0; + var $654 = 0, + $655 = 0, + $656 = 0, + $657 = 0, + $658 = 0, + $659 = 0, + $66 = 0, + $660 = 0, + $661 = 0, + $662 = 0, + $663 = 0, + $664 = 0, + $665 = 0, + $666 = 0, + $667 = 0, + $668 = 0, + $669 = 0, + $67 = 0, + $670 = 0, + $671 = 0; + var $672 = 0, + $673 = 0, + $674 = 0, + $675 = 0, + $676 = 0, + $677 = 0, + $678 = 0, + $679 = 0, + $68 = 0, + $680 = 0, + $681 = 0, + $682 = 0, + $683 = 0, + $684 = 0, + $685 = 0, + $686 = 0, + $687 = 0, + $688 = 0, + $689 = 0, + $69 = 0; + var $690 = 0, + $691 = 0, + $692 = 0, + $693 = 0, + $694 = 0, + $695 = 0, + $696 = 0, + $697 = 0, + $698 = 0, + $699 = 0, + $7 = 0, + $70 = 0, + $700 = 0, + $701 = 0, + $702 = 0, + $703 = 0, + $704 = 0, + $705 = 0, + $706 = 0, + $707 = 0; + var $708 = 0, + $709 = 0, + $71 = 0, + $710 = 0, + $711 = 0, + $712 = 0, + $713 = 0, + $714 = 0, + $715 = 0, + $716 = 0, + $717 = 0, + $718 = 0, + $719 = 0, + $72 = 0, + $720 = 0, + $721 = 0, + $722 = 0, + $723 = 0, + $724 = 0, + $725 = 0; + var $726 = 0, + $727 = 0, + $728 = 0, + $729 = 0, + $73 = 0, + $730 = 0, + $731 = 0, + $732 = 0, + $733 = 0, + $734 = 0, + $735 = 0, + $736 = 0, + $737 = 0, + $738 = 0, + $739 = 0, + $74 = 0, + $740 = 0, + $741 = 0, + $742 = 0, + $743 = 0; + var $744 = 0, + $745 = 0, + $746 = 0, + $747 = 0, + $748 = 0, + $749 = 0, + $75 = 0, + $750 = 0, + $751 = 0, + $752 = 0, + $753 = 0, + $754 = 0, + $755 = 0, + $756 = 0, + $757 = 0, + $758 = 0, + $759 = 0, + $76 = 0, + $760 = 0, + $761 = 0; + var $762 = 0, + $763 = 0, + $764 = 0, + $765 = 0, + $766 = 0, + $767 = 0, + $768 = 0, + $769 = 0, + $77 = 0, + $770 = 0, + $771 = 0, + $772 = 0, + $773 = 0, + $774 = 0, + $775 = 0, + $776 = 0, + $777 = 0, + $778 = 0, + $779 = 0, + $78 = 0; + var $780 = 0, + $781 = 0, + $782 = 0, + $783 = 0, + $784 = 0, + $785 = 0, + $786 = 0, + $787 = 0, + $788 = 0, + $789 = 0, + $79 = 0, + $790 = 0, + $791 = 0, + $792 = 0, + $793 = 0, + $794 = 0, + $795 = 0, + $796 = 0, + $797 = 0, + $798 = 0; + var $799 = 0, + $8 = 0, + $80 = 0, + $800 = 0, + $801 = 0, + $802 = 0, + $803 = 0, + $804 = 0, + $805 = 0, + $806 = 0, + $807 = 0, + $808 = 0, + $809 = 0, + $81 = 0, + $810 = 0, + $811 = 0, + $812 = 0, + $813 = 0, + $814 = 0, + $815 = 0; + var $816 = 0, + $817 = 0, + $818 = 0, + $819 = 0, + $82 = 0, + $820 = 0, + $821 = 0, + $822 = 0, + $823 = 0, + $824 = 0, + $825 = 0, + $826 = 0, + $827 = 0, + $828 = 0, + $829 = 0, + $83 = 0, + $830 = 0, + $831 = 0, + $832 = 0, + $833 = 0; + var $834 = 0, + $835 = 0, + $836 = 0, + $837 = 0, + $838 = 0, + $839 = 0, + $84 = 0, + $840 = 0, + $841 = 0, + $842 = 0, + $843 = 0, + $844 = 0, + $845 = 0, + $846 = 0, + $847 = 0, + $848 = 0, + $849 = 0, + $85 = 0, + $850 = 0, + $851 = 0; + var $852 = 0, + $853 = 0, + $854 = 0, + $855 = 0, + $856 = 0, + $857 = 0, + $858 = 0, + $859 = 0, + $86 = 0, + $860 = 0, + $861 = 0, + $862 = 0, + $863 = 0, + $864 = 0, + $865 = 0, + $866 = 0, + $867 = 0, + $868 = 0, + $869 = 0, + $87 = 0; + var $870 = 0, + $871 = 0, + $872 = 0, + $873 = 0, + $874 = 0, + $875 = 0, + $876 = 0, + $877 = 0, + $878 = 0, + $879 = 0, + $88 = 0, + $880 = 0, + $881 = 0, + $882 = 0, + $883 = 0, + $884 = 0, + $885 = 0, + $886 = 0, + $887 = 0, + $888 = 0; + var $889 = 0, + $89 = 0, + $890 = 0, + $891 = 0, + $892 = 0, + $893 = 0, + $894 = 0, + $895 = 0, + $896 = 0, + $897 = 0, + $898 = 0, + $899 = 0, + $9 = 0, + $90 = 0, + $900 = 0, + $901 = 0, + $902 = 0, + $903 = 0, + $904 = 0, + $905 = 0; + var $906 = 0, + $907 = 0, + $908 = 0, + $909 = 0, + $91 = 0, + $910 = 0, + $911 = 0, + $912 = 0, + $913 = 0, + $914 = 0, + $915 = 0, + $916 = 0, + $917 = 0, + $918 = 0, + $919 = 0, + $92 = 0, + $920 = 0, + $921 = 0, + $922 = 0, + $923 = 0; + var $924 = 0, + $925 = 0, + $926 = 0, + $927 = 0, + $928 = 0, + $929 = 0, + $93 = 0, + $930 = 0, + $931 = 0, + $932 = 0, + $933 = 0, + $934 = 0, + $935 = 0, + $936 = 0, + $937 = 0, + $938 = 0, + $939 = 0, + $94 = 0, + $940 = 0, + $941 = 0; + var $942 = 0, + $943 = 0, + $944 = 0, + $945 = 0, + $946 = 0, + $947 = 0, + $948 = 0, + $949 = 0, + $95 = 0, + $950 = 0, + $951 = 0, + $952 = 0, + $953 = 0, + $954 = 0, + $955 = 0, + $956 = 0, + $957 = 0, + $958 = 0, + $959 = 0, + $96 = 0; + var $960 = 0, + $961 = 0, + $962 = 0, + $963 = 0, + $964 = 0, + $965 = 0, + $966 = 0, + $967 = 0, + $968 = 0, + $969 = 0, + $97 = 0, + $970 = 0, + $971 = 0, + $972 = 0, + $973 = 0, + $974 = 0, + $975 = 0, + $976 = 0, + $977 = 0, + $978 = 0; + var $979 = 0, + $98 = 0, + $980 = 0, + $981 = 0, + $982 = 0, + $983 = 0, + $984 = 0, + $985 = 0, + $986 = 0, + $987 = 0, + $988 = 0, + $989 = 0, + $99 = 0, + $990 = 0, + $991 = 0, + $992 = 0, + $993 = 0, + $994 = 0, + $995 = 0, + $996 = 0; + var $997 = 0, + $998 = 0, + $999 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = _load_351($s) | 0; + $1 = tempRet0; + $2 = $0 & 2097151; + $3 = ($s + 2) | 0; + $4 = _load_452($3) | 0; + $5 = tempRet0; + $6 = _bitshift64Lshr($4 | 0, $5 | 0, 5) | 0; + $7 = tempRet0; + $8 = $6 & 2097151; + $9 = ($s + 5) | 0; + $10 = _load_351($9) | 0; + $11 = tempRet0; + $12 = _bitshift64Lshr($10 | 0, $11 | 0, 2) | 0; + $13 = tempRet0; + $14 = $12 & 2097151; + $15 = ($s + 7) | 0; + $16 = _load_452($15) | 0; + $17 = tempRet0; + $18 = _bitshift64Lshr($16 | 0, $17 | 0, 7) | 0; + $19 = tempRet0; + $20 = $18 & 2097151; + $21 = ($s + 10) | 0; + $22 = _load_452($21) | 0; + $23 = tempRet0; + $24 = _bitshift64Lshr($22 | 0, $23 | 0, 4) | 0; + $25 = tempRet0; + $26 = $24 & 2097151; + $27 = ($s + 13) | 0; + $28 = _load_351($27) | 0; + $29 = tempRet0; + $30 = _bitshift64Lshr($28 | 0, $29 | 0, 1) | 0; + $31 = tempRet0; + $32 = $30 & 2097151; + $33 = ($s + 15) | 0; + $34 = _load_452($33) | 0; + $35 = tempRet0; + $36 = _bitshift64Lshr($34 | 0, $35 | 0, 6) | 0; + $37 = tempRet0; + $38 = $36 & 2097151; + $39 = ($s + 18) | 0; + $40 = _load_351($39) | 0; + $41 = tempRet0; + $42 = _bitshift64Lshr($40 | 0, $41 | 0, 3) | 0; + $43 = tempRet0; + $44 = $42 & 2097151; + $45 = ($s + 21) | 0; + $46 = _load_351($45) | 0; + $47 = tempRet0; + $48 = $46 & 2097151; + $49 = ($s + 23) | 0; + $50 = _load_452($49) | 0; + $51 = tempRet0; + $52 = _bitshift64Lshr($50 | 0, $51 | 0, 5) | 0; + $53 = tempRet0; + $54 = $52 & 2097151; + $55 = ($s + 26) | 0; + $56 = _load_351($55) | 0; + $57 = tempRet0; + $58 = _bitshift64Lshr($56 | 0, $57 | 0, 2) | 0; + $59 = tempRet0; + $60 = $58 & 2097151; + $61 = ($s + 28) | 0; + $62 = _load_452($61) | 0; + $63 = tempRet0; + $64 = _bitshift64Lshr($62 | 0, $63 | 0, 7) | 0; + $65 = tempRet0; + $66 = $64 & 2097151; + $67 = ($s + 31) | 0; + $68 = _load_452($67) | 0; + $69 = tempRet0; + $70 = _bitshift64Lshr($68 | 0, $69 | 0, 4) | 0; + $71 = tempRet0; + $72 = $70 & 2097151; + $73 = ($s + 34) | 0; + $74 = _load_351($73) | 0; + $75 = tempRet0; + $76 = _bitshift64Lshr($74 | 0, $75 | 0, 1) | 0; + $77 = tempRet0; + $78 = $76 & 2097151; + $79 = ($s + 36) | 0; + $80 = _load_452($79) | 0; + $81 = tempRet0; + $82 = _bitshift64Lshr($80 | 0, $81 | 0, 6) | 0; + $83 = tempRet0; + $84 = $82 & 2097151; + $85 = ($s + 39) | 0; + $86 = _load_351($85) | 0; + $87 = tempRet0; + $88 = _bitshift64Lshr($86 | 0, $87 | 0, 3) | 0; + $89 = tempRet0; + $90 = $88 & 2097151; + $91 = ($s + 42) | 0; + $92 = _load_351($91) | 0; + $93 = tempRet0; + $94 = $92 & 2097151; + $95 = ($s + 44) | 0; + $96 = _load_452($95) | 0; + $97 = tempRet0; + $98 = _bitshift64Lshr($96 | 0, $97 | 0, 5) | 0; + $99 = tempRet0; + $100 = $98 & 2097151; + $101 = ($s + 47) | 0; + $102 = _load_351($101) | 0; + $103 = tempRet0; + $104 = _bitshift64Lshr($102 | 0, $103 | 0, 2) | 0; + $105 = tempRet0; + $106 = $104 & 2097151; + $107 = ($s + 49) | 0; + $108 = _load_452($107) | 0; + $109 = tempRet0; + $110 = _bitshift64Lshr($108 | 0, $109 | 0, 7) | 0; + $111 = tempRet0; + $112 = $110 & 2097151; + $113 = ($s + 52) | 0; + $114 = _load_452($113) | 0; + $115 = tempRet0; + $116 = _bitshift64Lshr($114 | 0, $115 | 0, 4) | 0; + $117 = tempRet0; + $118 = $116 & 2097151; + $119 = ($s + 55) | 0; + $120 = _load_351($119) | 0; + $121 = tempRet0; + $122 = _bitshift64Lshr($120 | 0, $121 | 0, 1) | 0; + $123 = tempRet0; + $124 = $122 & 2097151; + $125 = ($s + 57) | 0; + $126 = _load_452($125) | 0; + $127 = tempRet0; + $128 = _bitshift64Lshr($126 | 0, $127 | 0, 6) | 0; + $129 = tempRet0; + $130 = $128 & 2097151; + $131 = ($s + 60) | 0; + $132 = _load_452($131) | 0; + $133 = tempRet0; + $134 = _bitshift64Lshr($132 | 0, $133 | 0, 3) | 0; + $135 = tempRet0; + $136 = ___muldi3($134 | 0, $135 | 0, 666643, 0) | 0; + $137 = tempRet0; + $138 = ___muldi3($134 | 0, $135 | 0, 470296, 0) | 0; + $139 = tempRet0; + $140 = ___muldi3($134 | 0, $135 | 0, 654183, 0) | 0; + $141 = tempRet0; + $142 = ___muldi3($134 | 0, $135 | 0, -997805, -1) | 0; + $143 = tempRet0; + $144 = ___muldi3($134 | 0, $135 | 0, 136657, 0) | 0; + $145 = tempRet0; + $146 = _i64Add($144 | 0, $145 | 0, $90 | 0, 0) | 0; + $147 = tempRet0; + $148 = ___muldi3($134 | 0, $135 | 0, -683901, -1) | 0; + $149 = tempRet0; + $150 = _i64Add($148 | 0, $149 | 0, $94 | 0, 0) | 0; + $151 = tempRet0; + $152 = ___muldi3($130 | 0, 0, 666643, 0) | 0; + $153 = tempRet0; + $154 = ___muldi3($130 | 0, 0, 470296, 0) | 0; + $155 = tempRet0; + $156 = ___muldi3($130 | 0, 0, 654183, 0) | 0; + $157 = tempRet0; + $158 = ___muldi3($130 | 0, 0, -997805, -1) | 0; + $159 = tempRet0; + $160 = ___muldi3($130 | 0, 0, 136657, 0) | 0; + $161 = tempRet0; + $162 = ___muldi3($130 | 0, 0, -683901, -1) | 0; + $163 = tempRet0; + $164 = _i64Add($146 | 0, $147 | 0, $162 | 0, $163 | 0) | 0; + $165 = tempRet0; + $166 = ___muldi3($124 | 0, 0, 666643, 0) | 0; + $167 = tempRet0; + $168 = ___muldi3($124 | 0, 0, 470296, 0) | 0; + $169 = tempRet0; + $170 = ___muldi3($124 | 0, 0, 654183, 0) | 0; + $171 = tempRet0; + $172 = ___muldi3($124 | 0, 0, -997805, -1) | 0; + $173 = tempRet0; + $174 = ___muldi3($124 | 0, 0, 136657, 0) | 0; + $175 = tempRet0; + $176 = ___muldi3($124 | 0, 0, -683901, -1) | 0; + $177 = tempRet0; + $178 = _i64Add($176 | 0, $177 | 0, $84 | 0, 0) | 0; + $179 = tempRet0; + $180 = _i64Add($178 | 0, $179 | 0, $142 | 0, $143 | 0) | 0; + $181 = tempRet0; + $182 = _i64Add($180 | 0, $181 | 0, $160 | 0, $161 | 0) | 0; + $183 = tempRet0; + $184 = ___muldi3($118 | 0, 0, 666643, 0) | 0; + $185 = tempRet0; + $186 = ___muldi3($118 | 0, 0, 470296, 0) | 0; + $187 = tempRet0; + $188 = ___muldi3($118 | 0, 0, 654183, 0) | 0; + $189 = tempRet0; + $190 = ___muldi3($118 | 0, 0, -997805, -1) | 0; + $191 = tempRet0; + $192 = ___muldi3($118 | 0, 0, 136657, 0) | 0; + $193 = tempRet0; + $194 = ___muldi3($118 | 0, 0, -683901, -1) | 0; + $195 = tempRet0; + $196 = ___muldi3($112 | 0, 0, 666643, 0) | 0; + $197 = tempRet0; + $198 = ___muldi3($112 | 0, 0, 470296, 0) | 0; + $199 = tempRet0; + $200 = ___muldi3($112 | 0, 0, 654183, 0) | 0; + $201 = tempRet0; + $202 = ___muldi3($112 | 0, 0, -997805, -1) | 0; + $203 = tempRet0; + $204 = ___muldi3($112 | 0, 0, 136657, 0) | 0; + $205 = tempRet0; + $206 = ___muldi3($112 | 0, 0, -683901, -1) | 0; + $207 = tempRet0; + $208 = _i64Add($206 | 0, $207 | 0, $72 | 0, 0) | 0; + $209 = tempRet0; + $210 = _i64Add($208 | 0, $209 | 0, $192 | 0, $193 | 0) | 0; + $211 = tempRet0; + $212 = _i64Add($210 | 0, $211 | 0, $172 | 0, $173 | 0) | 0; + $213 = tempRet0; + $214 = _i64Add($212 | 0, $213 | 0, $138 | 0, $139 | 0) | 0; + $215 = tempRet0; + $216 = _i64Add($214 | 0, $215 | 0, $156 | 0, $157 | 0) | 0; + $217 = tempRet0; + $218 = ___muldi3($106 | 0, 0, 666643, 0) | 0; + $219 = tempRet0; + $220 = _i64Add($218 | 0, $219 | 0, $38 | 0, 0) | 0; + $221 = tempRet0; + $222 = ___muldi3($106 | 0, 0, 470296, 0) | 0; + $223 = tempRet0; + $224 = ___muldi3($106 | 0, 0, 654183, 0) | 0; + $225 = tempRet0; + $226 = _i64Add($224 | 0, $225 | 0, $48 | 0, 0) | 0; + $227 = tempRet0; + $228 = _i64Add($226 | 0, $227 | 0, $198 | 0, $199 | 0) | 0; + $229 = tempRet0; + $230 = _i64Add($228 | 0, $229 | 0, $184 | 0, $185 | 0) | 0; + $231 = tempRet0; + $232 = ___muldi3($106 | 0, 0, -997805, -1) | 0; + $233 = tempRet0; + $234 = ___muldi3($106 | 0, 0, 136657, 0) | 0; + $235 = tempRet0; + $236 = _i64Add($234 | 0, $235 | 0, $60 | 0, 0) | 0; + $237 = tempRet0; + $238 = _i64Add($236 | 0, $237 | 0, $202 | 0, $203 | 0) | 0; + $239 = tempRet0; + $240 = _i64Add($238 | 0, $239 | 0, $188 | 0, $189 | 0) | 0; + $241 = tempRet0; + $242 = _i64Add($240 | 0, $241 | 0, $168 | 0, $169 | 0) | 0; + $243 = tempRet0; + $244 = _i64Add($242 | 0, $243 | 0, $152 | 0, $153 | 0) | 0; + $245 = tempRet0; + $246 = ___muldi3($106 | 0, 0, -683901, -1) | 0; + $247 = tempRet0; + $248 = _i64Add($220 | 0, $221 | 0, 1048576, 0) | 0; + $249 = tempRet0; + $250 = _bitshift64Lshr($248 | 0, $249 | 0, 21) | 0; + $251 = tempRet0; + $252 = _i64Add($222 | 0, $223 | 0, $44 | 0, 0) | 0; + $253 = tempRet0; + $254 = _i64Add($252 | 0, $253 | 0, $196 | 0, $197 | 0) | 0; + $255 = tempRet0; + $256 = _i64Add($254 | 0, $255 | 0, $250 | 0, $251 | 0) | 0; + $257 = tempRet0; + $258 = _bitshift64Shl($250 | 0, $251 | 0, 21) | 0; + $259 = tempRet0; + $260 = _i64Subtract($220 | 0, $221 | 0, $258 | 0, $259 | 0) | 0; + $261 = tempRet0; + $262 = _i64Add($230 | 0, $231 | 0, 1048576, 0) | 0; + $263 = tempRet0; + $264 = _bitshift64Lshr($262 | 0, $263 | 0, 21) | 0; + $265 = tempRet0; + $266 = _i64Add($232 | 0, $233 | 0, $54 | 0, 0) | 0; + $267 = tempRet0; + $268 = _i64Add($266 | 0, $267 | 0, $200 | 0, $201 | 0) | 0; + $269 = tempRet0; + $270 = _i64Add($268 | 0, $269 | 0, $186 | 0, $187 | 0) | 0; + $271 = tempRet0; + $272 = _i64Add($270 | 0, $271 | 0, $166 | 0, $167 | 0) | 0; + $273 = tempRet0; + $274 = _i64Add($272 | 0, $273 | 0, $264 | 0, $265 | 0) | 0; + $275 = tempRet0; + $276 = _bitshift64Shl($264 | 0, $265 | 0, 21) | 0; + $277 = tempRet0; + $278 = _i64Subtract($230 | 0, $231 | 0, $276 | 0, $277 | 0) | 0; + $279 = tempRet0; + $280 = _i64Add($244 | 0, $245 | 0, 1048576, 0) | 0; + $281 = tempRet0; + $282 = _bitshift64Ashr($280 | 0, $281 | 0, 21) | 0; + $283 = tempRet0; + $284 = _i64Add($246 | 0, $247 | 0, $66 | 0, 0) | 0; + $285 = tempRet0; + $286 = _i64Add($284 | 0, $285 | 0, $204 | 0, $205 | 0) | 0; + $287 = tempRet0; + $288 = _i64Add($286 | 0, $287 | 0, $190 | 0, $191 | 0) | 0; + $289 = tempRet0; + $290 = _i64Add($288 | 0, $289 | 0, $170 | 0, $171 | 0) | 0; + $291 = tempRet0; + $292 = _i64Add($290 | 0, $291 | 0, $136 | 0, $137 | 0) | 0; + $293 = tempRet0; + $294 = _i64Add($292 | 0, $293 | 0, $154 | 0, $155 | 0) | 0; + $295 = tempRet0; + $296 = _i64Add($294 | 0, $295 | 0, $282 | 0, $283 | 0) | 0; + $297 = tempRet0; + $298 = _bitshift64Shl($282 | 0, $283 | 0, 21) | 0; + $299 = tempRet0; + $300 = _i64Subtract($244 | 0, $245 | 0, $298 | 0, $299 | 0) | 0; + $301 = tempRet0; + $302 = _i64Add($216 | 0, $217 | 0, 1048576, 0) | 0; + $303 = tempRet0; + $304 = _bitshift64Ashr($302 | 0, $303 | 0, 21) | 0; + $305 = tempRet0; + $306 = _i64Add($194 | 0, $195 | 0, $78 | 0, 0) | 0; + $307 = tempRet0; + $308 = _i64Add($306 | 0, $307 | 0, $174 | 0, $175 | 0) | 0; + $309 = tempRet0; + $310 = _i64Add($308 | 0, $309 | 0, $140 | 0, $141 | 0) | 0; + $311 = tempRet0; + $312 = _i64Add($310 | 0, $311 | 0, $158 | 0, $159 | 0) | 0; + $313 = tempRet0; + $314 = _i64Add($312 | 0, $313 | 0, $304 | 0, $305 | 0) | 0; + $315 = tempRet0; + $316 = _bitshift64Shl($304 | 0, $305 | 0, 21) | 0; + $317 = tempRet0; + $318 = _i64Subtract($216 | 0, $217 | 0, $316 | 0, $317 | 0) | 0; + $319 = tempRet0; + $320 = _i64Add($182 | 0, $183 | 0, 1048576, 0) | 0; + $321 = tempRet0; + $322 = _bitshift64Ashr($320 | 0, $321 | 0, 21) | 0; + $323 = tempRet0; + $324 = _i64Add($164 | 0, $165 | 0, $322 | 0, $323 | 0) | 0; + $325 = tempRet0; + $326 = _bitshift64Shl($322 | 0, $323 | 0, 21) | 0; + $327 = tempRet0; + $328 = _i64Subtract($182 | 0, $183 | 0, $326 | 0, $327 | 0) | 0; + $329 = tempRet0; + $330 = _i64Add($150 | 0, $151 | 0, 1048576, 0) | 0; + $331 = tempRet0; + $332 = _bitshift64Ashr($330 | 0, $331 | 0, 21) | 0; + $333 = tempRet0; + $334 = _i64Add($332 | 0, $333 | 0, $100 | 0, 0) | 0; + $335 = tempRet0; + $336 = _bitshift64Shl($332 | 0, $333 | 0, 21) | 0; + $337 = tempRet0; + $338 = _i64Subtract($150 | 0, $151 | 0, $336 | 0, $337 | 0) | 0; + $339 = tempRet0; + $340 = _i64Add($256 | 0, $257 | 0, 1048576, 0) | 0; + $341 = tempRet0; + $342 = _bitshift64Lshr($340 | 0, $341 | 0, 21) | 0; + $343 = tempRet0; + $344 = _i64Add($278 | 0, $279 | 0, $342 | 0, $343 | 0) | 0; + $345 = tempRet0; + $346 = _bitshift64Shl($342 | 0, $343 | 0, 21) | 0; + $347 = tempRet0; + $348 = _i64Subtract($256 | 0, $257 | 0, $346 | 0, $347 | 0) | 0; + $349 = tempRet0; + $350 = _i64Add($274 | 0, $275 | 0, 1048576, 0) | 0; + $351 = tempRet0; + $352 = _bitshift64Ashr($350 | 0, $351 | 0, 21) | 0; + $353 = tempRet0; + $354 = _i64Add($300 | 0, $301 | 0, $352 | 0, $353 | 0) | 0; + $355 = tempRet0; + $356 = _bitshift64Shl($352 | 0, $353 | 0, 21) | 0; + $357 = tempRet0; + $358 = _i64Subtract($274 | 0, $275 | 0, $356 | 0, $357 | 0) | 0; + $359 = tempRet0; + $360 = _i64Add($296 | 0, $297 | 0, 1048576, 0) | 0; + $361 = tempRet0; + $362 = _bitshift64Ashr($360 | 0, $361 | 0, 21) | 0; + $363 = tempRet0; + $364 = _i64Add($318 | 0, $319 | 0, $362 | 0, $363 | 0) | 0; + $365 = tempRet0; + $366 = _bitshift64Shl($362 | 0, $363 | 0, 21) | 0; + $367 = tempRet0; + $368 = _i64Subtract($296 | 0, $297 | 0, $366 | 0, $367 | 0) | 0; + $369 = tempRet0; + $370 = _i64Add($314 | 0, $315 | 0, 1048576, 0) | 0; + $371 = tempRet0; + $372 = _bitshift64Ashr($370 | 0, $371 | 0, 21) | 0; + $373 = tempRet0; + $374 = _i64Add($372 | 0, $373 | 0, $328 | 0, $329 | 0) | 0; + $375 = tempRet0; + $376 = _bitshift64Shl($372 | 0, $373 | 0, 21) | 0; + $377 = tempRet0; + $378 = _i64Subtract($314 | 0, $315 | 0, $376 | 0, $377 | 0) | 0; + $379 = tempRet0; + $380 = _i64Add($324 | 0, $325 | 0, 1048576, 0) | 0; + $381 = tempRet0; + $382 = _bitshift64Ashr($380 | 0, $381 | 0, 21) | 0; + $383 = tempRet0; + $384 = _i64Add($382 | 0, $383 | 0, $338 | 0, $339 | 0) | 0; + $385 = tempRet0; + $386 = _bitshift64Shl($382 | 0, $383 | 0, 21) | 0; + $387 = tempRet0; + $388 = _i64Subtract($324 | 0, $325 | 0, $386 | 0, $387 | 0) | 0; + $389 = tempRet0; + $390 = ___muldi3($334 | 0, $335 | 0, 666643, 0) | 0; + $391 = tempRet0; + $392 = _i64Add($390 | 0, $391 | 0, $32 | 0, 0) | 0; + $393 = tempRet0; + $394 = ___muldi3($334 | 0, $335 | 0, 470296, 0) | 0; + $395 = tempRet0; + $396 = _i64Add($260 | 0, $261 | 0, $394 | 0, $395 | 0) | 0; + $397 = tempRet0; + $398 = ___muldi3($334 | 0, $335 | 0, 654183, 0) | 0; + $399 = tempRet0; + $400 = _i64Add($348 | 0, $349 | 0, $398 | 0, $399 | 0) | 0; + $401 = tempRet0; + $402 = ___muldi3($334 | 0, $335 | 0, -997805, -1) | 0; + $403 = tempRet0; + $404 = _i64Add($344 | 0, $345 | 0, $402 | 0, $403 | 0) | 0; + $405 = tempRet0; + $406 = ___muldi3($334 | 0, $335 | 0, 136657, 0) | 0; + $407 = tempRet0; + $408 = _i64Add($358 | 0, $359 | 0, $406 | 0, $407 | 0) | 0; + $409 = tempRet0; + $410 = ___muldi3($334 | 0, $335 | 0, -683901, -1) | 0; + $411 = tempRet0; + $412 = _i64Add($354 | 0, $355 | 0, $410 | 0, $411 | 0) | 0; + $413 = tempRet0; + $414 = ___muldi3($384 | 0, $385 | 0, 666643, 0) | 0; + $415 = tempRet0; + $416 = ___muldi3($384 | 0, $385 | 0, 470296, 0) | 0; + $417 = tempRet0; + $418 = ___muldi3($384 | 0, $385 | 0, 654183, 0) | 0; + $419 = tempRet0; + $420 = _i64Add($396 | 0, $397 | 0, $418 | 0, $419 | 0) | 0; + $421 = tempRet0; + $422 = ___muldi3($384 | 0, $385 | 0, -997805, -1) | 0; + $423 = tempRet0; + $424 = _i64Add($400 | 0, $401 | 0, $422 | 0, $423 | 0) | 0; + $425 = tempRet0; + $426 = ___muldi3($384 | 0, $385 | 0, 136657, 0) | 0; + $427 = tempRet0; + $428 = _i64Add($404 | 0, $405 | 0, $426 | 0, $427 | 0) | 0; + $429 = tempRet0; + $430 = ___muldi3($384 | 0, $385 | 0, -683901, -1) | 0; + $431 = tempRet0; + $432 = _i64Add($408 | 0, $409 | 0, $430 | 0, $431 | 0) | 0; + $433 = tempRet0; + $434 = ___muldi3($388 | 0, $389 | 0, 666643, 0) | 0; + $435 = tempRet0; + $436 = ___muldi3($388 | 0, $389 | 0, 470296, 0) | 0; + $437 = tempRet0; + $438 = ___muldi3($388 | 0, $389 | 0, 654183, 0) | 0; + $439 = tempRet0; + $440 = ___muldi3($388 | 0, $389 | 0, -997805, -1) | 0; + $441 = tempRet0; + $442 = ___muldi3($388 | 0, $389 | 0, 136657, 0) | 0; + $443 = tempRet0; + $444 = ___muldi3($388 | 0, $389 | 0, -683901, -1) | 0; + $445 = tempRet0; + $446 = _i64Add($428 | 0, $429 | 0, $444 | 0, $445 | 0) | 0; + $447 = tempRet0; + $448 = ___muldi3($374 | 0, $375 | 0, 666643, 0) | 0; + $449 = tempRet0; + $450 = ___muldi3($374 | 0, $375 | 0, 470296, 0) | 0; + $451 = tempRet0; + $452 = ___muldi3($374 | 0, $375 | 0, 654183, 0) | 0; + $453 = tempRet0; + $454 = ___muldi3($374 | 0, $375 | 0, -997805, -1) | 0; + $455 = tempRet0; + $456 = ___muldi3($374 | 0, $375 | 0, 136657, 0) | 0; + $457 = tempRet0; + $458 = ___muldi3($374 | 0, $375 | 0, -683901, -1) | 0; + $459 = tempRet0; + $460 = ___muldi3($378 | 0, $379 | 0, 666643, 0) | 0; + $461 = tempRet0; + $462 = ___muldi3($378 | 0, $379 | 0, 470296, 0) | 0; + $463 = tempRet0; + $464 = ___muldi3($378 | 0, $379 | 0, 654183, 0) | 0; + $465 = tempRet0; + $466 = ___muldi3($378 | 0, $379 | 0, -997805, -1) | 0; + $467 = tempRet0; + $468 = ___muldi3($378 | 0, $379 | 0, 136657, 0) | 0; + $469 = tempRet0; + $470 = ___muldi3($378 | 0, $379 | 0, -683901, -1) | 0; + $471 = tempRet0; + $472 = _i64Add($420 | 0, $421 | 0, $456 | 0, $457 | 0) | 0; + $473 = tempRet0; + $474 = _i64Add($472 | 0, $473 | 0, $440 | 0, $441 | 0) | 0; + $475 = tempRet0; + $476 = _i64Add($474 | 0, $475 | 0, $470 | 0, $471 | 0) | 0; + $477 = tempRet0; + $478 = ___muldi3($364 | 0, $365 | 0, 666643, 0) | 0; + $479 = tempRet0; + $480 = _i64Add($478 | 0, $479 | 0, $2 | 0, 0) | 0; + $481 = tempRet0; + $482 = ___muldi3($364 | 0, $365 | 0, 470296, 0) | 0; + $483 = tempRet0; + $484 = ___muldi3($364 | 0, $365 | 0, 654183, 0) | 0; + $485 = tempRet0; + $486 = _i64Add($484 | 0, $485 | 0, $14 | 0, 0) | 0; + $487 = tempRet0; + $488 = _i64Add($486 | 0, $487 | 0, $448 | 0, $449 | 0) | 0; + $489 = tempRet0; + $490 = _i64Add($488 | 0, $489 | 0, $462 | 0, $463 | 0) | 0; + $491 = tempRet0; + $492 = ___muldi3($364 | 0, $365 | 0, -997805, -1) | 0; + $493 = tempRet0; + $494 = ___muldi3($364 | 0, $365 | 0, 136657, 0) | 0; + $495 = tempRet0; + $496 = _i64Add($494 | 0, $495 | 0, $26 | 0, 0) | 0; + $497 = tempRet0; + $498 = _i64Add($496 | 0, $497 | 0, $414 | 0, $415 | 0) | 0; + $499 = tempRet0; + $500 = _i64Add($498 | 0, $499 | 0, $452 | 0, $453 | 0) | 0; + $501 = tempRet0; + $502 = _i64Add($500 | 0, $501 | 0, $436 | 0, $437 | 0) | 0; + $503 = tempRet0; + $504 = _i64Add($502 | 0, $503 | 0, $466 | 0, $467 | 0) | 0; + $505 = tempRet0; + $506 = ___muldi3($364 | 0, $365 | 0, -683901, -1) | 0; + $507 = tempRet0; + $508 = _i64Add($480 | 0, $481 | 0, 1048576, 0) | 0; + $509 = tempRet0; + $510 = _bitshift64Ashr($508 | 0, $509 | 0, 21) | 0; + $511 = tempRet0; + $512 = _i64Add($482 | 0, $483 | 0, $8 | 0, 0) | 0; + $513 = tempRet0; + $514 = _i64Add($512 | 0, $513 | 0, $460 | 0, $461 | 0) | 0; + $515 = tempRet0; + $516 = _i64Add($514 | 0, $515 | 0, $510 | 0, $511 | 0) | 0; + $517 = tempRet0; + $518 = _bitshift64Shl($510 | 0, $511 | 0, 21) | 0; + $519 = tempRet0; + $520 = _i64Subtract($480 | 0, $481 | 0, $518 | 0, $519 | 0) | 0; + $521 = tempRet0; + $522 = _i64Add($490 | 0, $491 | 0, 1048576, 0) | 0; + $523 = tempRet0; + $524 = _bitshift64Ashr($522 | 0, $523 | 0, 21) | 0; + $525 = tempRet0; + $526 = _i64Add($492 | 0, $493 | 0, $20 | 0, 0) | 0; + $527 = tempRet0; + $528 = _i64Add($526 | 0, $527 | 0, $450 | 0, $451 | 0) | 0; + $529 = tempRet0; + $530 = _i64Add($528 | 0, $529 | 0, $434 | 0, $435 | 0) | 0; + $531 = tempRet0; + $532 = _i64Add($530 | 0, $531 | 0, $464 | 0, $465 | 0) | 0; + $533 = tempRet0; + $534 = _i64Add($532 | 0, $533 | 0, $524 | 0, $525 | 0) | 0; + $535 = tempRet0; + $536 = _bitshift64Shl($524 | 0, $525 | 0, 21) | 0; + $537 = tempRet0; + $538 = _i64Add($504 | 0, $505 | 0, 1048576, 0) | 0; + $539 = tempRet0; + $540 = _bitshift64Ashr($538 | 0, $539 | 0, 21) | 0; + $541 = tempRet0; + $542 = _i64Add($392 | 0, $393 | 0, $506 | 0, $507 | 0) | 0; + $543 = tempRet0; + $544 = _i64Add($542 | 0, $543 | 0, $416 | 0, $417 | 0) | 0; + $545 = tempRet0; + $546 = _i64Add($544 | 0, $545 | 0, $454 | 0, $455 | 0) | 0; + $547 = tempRet0; + $548 = _i64Add($546 | 0, $547 | 0, $438 | 0, $439 | 0) | 0; + $549 = tempRet0; + $550 = _i64Add($548 | 0, $549 | 0, $468 | 0, $469 | 0) | 0; + $551 = tempRet0; + $552 = _i64Add($550 | 0, $551 | 0, $540 | 0, $541 | 0) | 0; + $553 = tempRet0; + $554 = _bitshift64Shl($540 | 0, $541 | 0, 21) | 0; + $555 = tempRet0; + $556 = _i64Add($476 | 0, $477 | 0, 1048576, 0) | 0; + $557 = tempRet0; + $558 = _bitshift64Ashr($556 | 0, $557 | 0, 21) | 0; + $559 = tempRet0; + $560 = _i64Add($424 | 0, $425 | 0, $458 | 0, $459 | 0) | 0; + $561 = tempRet0; + $562 = _i64Add($560 | 0, $561 | 0, $442 | 0, $443 | 0) | 0; + $563 = tempRet0; + $564 = _i64Add($562 | 0, $563 | 0, $558 | 0, $559 | 0) | 0; + $565 = tempRet0; + $566 = _bitshift64Shl($558 | 0, $559 | 0, 21) | 0; + $567 = tempRet0; + $568 = _i64Subtract($476 | 0, $477 | 0, $566 | 0, $567 | 0) | 0; + $569 = tempRet0; + $570 = _i64Add($446 | 0, $447 | 0, 1048576, 0) | 0; + $571 = tempRet0; + $572 = _bitshift64Ashr($570 | 0, $571 | 0, 21) | 0; + $573 = tempRet0; + $574 = _i64Add($432 | 0, $433 | 0, $572 | 0, $573 | 0) | 0; + $575 = tempRet0; + $576 = _bitshift64Shl($572 | 0, $573 | 0, 21) | 0; + $577 = tempRet0; + $578 = _i64Subtract($446 | 0, $447 | 0, $576 | 0, $577 | 0) | 0; + $579 = tempRet0; + $580 = _i64Add($412 | 0, $413 | 0, 1048576, 0) | 0; + $581 = tempRet0; + $582 = _bitshift64Ashr($580 | 0, $581 | 0, 21) | 0; + $583 = tempRet0; + $584 = _i64Add($582 | 0, $583 | 0, $368 | 0, $369 | 0) | 0; + $585 = tempRet0; + $586 = _bitshift64Shl($582 | 0, $583 | 0, 21) | 0; + $587 = tempRet0; + $588 = _i64Subtract($412 | 0, $413 | 0, $586 | 0, $587 | 0) | 0; + $589 = tempRet0; + $590 = _i64Add($516 | 0, $517 | 0, 1048576, 0) | 0; + $591 = tempRet0; + $592 = _bitshift64Ashr($590 | 0, $591 | 0, 21) | 0; + $593 = tempRet0; + $594 = _bitshift64Shl($592 | 0, $593 | 0, 21) | 0; + $595 = tempRet0; + $596 = _i64Add($534 | 0, $535 | 0, 1048576, 0) | 0; + $597 = tempRet0; + $598 = _bitshift64Ashr($596 | 0, $597 | 0, 21) | 0; + $599 = tempRet0; + $600 = _bitshift64Shl($598 | 0, $599 | 0, 21) | 0; + $601 = tempRet0; + $602 = _i64Add($552 | 0, $553 | 0, 1048576, 0) | 0; + $603 = tempRet0; + $604 = _bitshift64Ashr($602 | 0, $603 | 0, 21) | 0; + $605 = tempRet0; + $606 = _i64Add($568 | 0, $569 | 0, $604 | 0, $605 | 0) | 0; + $607 = tempRet0; + $608 = _bitshift64Shl($604 | 0, $605 | 0, 21) | 0; + $609 = tempRet0; + $610 = _i64Add($564 | 0, $565 | 0, 1048576, 0) | 0; + $611 = tempRet0; + $612 = _bitshift64Ashr($610 | 0, $611 | 0, 21) | 0; + $613 = tempRet0; + $614 = _i64Add($578 | 0, $579 | 0, $612 | 0, $613 | 0) | 0; + $615 = tempRet0; + $616 = _bitshift64Shl($612 | 0, $613 | 0, 21) | 0; + $617 = tempRet0; + $618 = _i64Subtract($564 | 0, $565 | 0, $616 | 0, $617 | 0) | 0; + $619 = tempRet0; + $620 = _i64Add($574 | 0, $575 | 0, 1048576, 0) | 0; + $621 = tempRet0; + $622 = _bitshift64Ashr($620 | 0, $621 | 0, 21) | 0; + $623 = tempRet0; + $624 = _i64Add($588 | 0, $589 | 0, $622 | 0, $623 | 0) | 0; + $625 = tempRet0; + $626 = _bitshift64Shl($622 | 0, $623 | 0, 21) | 0; + $627 = tempRet0; + $628 = _i64Subtract($574 | 0, $575 | 0, $626 | 0, $627 | 0) | 0; + $629 = tempRet0; + $630 = _i64Add($584 | 0, $585 | 0, 1048576, 0) | 0; + $631 = tempRet0; + $632 = _bitshift64Ashr($630 | 0, $631 | 0, 21) | 0; + $633 = tempRet0; + $634 = _bitshift64Shl($632 | 0, $633 | 0, 21) | 0; + $635 = tempRet0; + $636 = _i64Subtract($584 | 0, $585 | 0, $634 | 0, $635 | 0) | 0; + $637 = tempRet0; + $638 = ___muldi3($632 | 0, $633 | 0, 666643, 0) | 0; + $639 = tempRet0; + $640 = _i64Add($520 | 0, $521 | 0, $638 | 0, $639 | 0) | 0; + $641 = tempRet0; + $642 = ___muldi3($632 | 0, $633 | 0, 470296, 0) | 0; + $643 = tempRet0; + $644 = ___muldi3($632 | 0, $633 | 0, 654183, 0) | 0; + $645 = tempRet0; + $646 = ___muldi3($632 | 0, $633 | 0, -997805, -1) | 0; + $647 = tempRet0; + $648 = ___muldi3($632 | 0, $633 | 0, 136657, 0) | 0; + $649 = tempRet0; + $650 = ___muldi3($632 | 0, $633 | 0, -683901, -1) | 0; + $651 = tempRet0; + $652 = _bitshift64Ashr($640 | 0, $641 | 0, 21) | 0; + $653 = tempRet0; + $654 = _i64Add($642 | 0, $643 | 0, $516 | 0, $517 | 0) | 0; + $655 = tempRet0; + $656 = _i64Subtract($654 | 0, $655 | 0, $594 | 0, $595 | 0) | 0; + $657 = tempRet0; + $658 = _i64Add($656 | 0, $657 | 0, $652 | 0, $653 | 0) | 0; + $659 = tempRet0; + $660 = _bitshift64Shl($652 | 0, $653 | 0, 21) | 0; + $661 = tempRet0; + $662 = _i64Subtract($640 | 0, $641 | 0, $660 | 0, $661 | 0) | 0; + $663 = tempRet0; + $664 = _bitshift64Ashr($658 | 0, $659 | 0, 21) | 0; + $665 = tempRet0; + $666 = _i64Add($644 | 0, $645 | 0, $490 | 0, $491 | 0) | 0; + $667 = tempRet0; + $668 = _i64Subtract($666 | 0, $667 | 0, $536 | 0, $537 | 0) | 0; + $669 = tempRet0; + $670 = _i64Add($668 | 0, $669 | 0, $592 | 0, $593 | 0) | 0; + $671 = tempRet0; + $672 = _i64Add($670 | 0, $671 | 0, $664 | 0, $665 | 0) | 0; + $673 = tempRet0; + $674 = _bitshift64Shl($664 | 0, $665 | 0, 21) | 0; + $675 = tempRet0; + $676 = _i64Subtract($658 | 0, $659 | 0, $674 | 0, $675 | 0) | 0; + $677 = tempRet0; + $678 = _bitshift64Ashr($672 | 0, $673 | 0, 21) | 0; + $679 = tempRet0; + $680 = _i64Add($534 | 0, $535 | 0, $646 | 0, $647 | 0) | 0; + $681 = tempRet0; + $682 = _i64Subtract($680 | 0, $681 | 0, $600 | 0, $601 | 0) | 0; + $683 = tempRet0; + $684 = _i64Add($682 | 0, $683 | 0, $678 | 0, $679 | 0) | 0; + $685 = tempRet0; + $686 = _bitshift64Shl($678 | 0, $679 | 0, 21) | 0; + $687 = tempRet0; + $688 = _i64Subtract($672 | 0, $673 | 0, $686 | 0, $687 | 0) | 0; + $689 = tempRet0; + $690 = _bitshift64Ashr($684 | 0, $685 | 0, 21) | 0; + $691 = tempRet0; + $692 = _i64Add($648 | 0, $649 | 0, $504 | 0, $505 | 0) | 0; + $693 = tempRet0; + $694 = _i64Subtract($692 | 0, $693 | 0, $554 | 0, $555 | 0) | 0; + $695 = tempRet0; + $696 = _i64Add($694 | 0, $695 | 0, $598 | 0, $599 | 0) | 0; + $697 = tempRet0; + $698 = _i64Add($696 | 0, $697 | 0, $690 | 0, $691 | 0) | 0; + $699 = tempRet0; + $700 = _bitshift64Shl($690 | 0, $691 | 0, 21) | 0; + $701 = tempRet0; + $702 = _i64Subtract($684 | 0, $685 | 0, $700 | 0, $701 | 0) | 0; + $703 = tempRet0; + $704 = _bitshift64Ashr($698 | 0, $699 | 0, 21) | 0; + $705 = tempRet0; + $706 = _i64Add($552 | 0, $553 | 0, $650 | 0, $651 | 0) | 0; + $707 = tempRet0; + $708 = _i64Subtract($706 | 0, $707 | 0, $608 | 0, $609 | 0) | 0; + $709 = tempRet0; + $710 = _i64Add($708 | 0, $709 | 0, $704 | 0, $705 | 0) | 0; + $711 = tempRet0; + $712 = _bitshift64Shl($704 | 0, $705 | 0, 21) | 0; + $713 = tempRet0; + $714 = _i64Subtract($698 | 0, $699 | 0, $712 | 0, $713 | 0) | 0; + $715 = tempRet0; + $716 = _bitshift64Ashr($710 | 0, $711 | 0, 21) | 0; + $717 = tempRet0; + $718 = _i64Add($606 | 0, $607 | 0, $716 | 0, $717 | 0) | 0; + $719 = tempRet0; + $720 = _bitshift64Shl($716 | 0, $717 | 0, 21) | 0; + $721 = tempRet0; + $722 = _i64Subtract($710 | 0, $711 | 0, $720 | 0, $721 | 0) | 0; + $723 = tempRet0; + $724 = _bitshift64Ashr($718 | 0, $719 | 0, 21) | 0; + $725 = tempRet0; + $726 = _i64Add($724 | 0, $725 | 0, $618 | 0, $619 | 0) | 0; + $727 = tempRet0; + $728 = _bitshift64Shl($724 | 0, $725 | 0, 21) | 0; + $729 = tempRet0; + $730 = _i64Subtract($718 | 0, $719 | 0, $728 | 0, $729 | 0) | 0; + $731 = tempRet0; + $732 = _bitshift64Ashr($726 | 0, $727 | 0, 21) | 0; + $733 = tempRet0; + $734 = _i64Add($614 | 0, $615 | 0, $732 | 0, $733 | 0) | 0; + $735 = tempRet0; + $736 = _bitshift64Shl($732 | 0, $733 | 0, 21) | 0; + $737 = tempRet0; + $738 = _i64Subtract($726 | 0, $727 | 0, $736 | 0, $737 | 0) | 0; + $739 = tempRet0; + $740 = _bitshift64Ashr($734 | 0, $735 | 0, 21) | 0; + $741 = tempRet0; + $742 = _i64Add($740 | 0, $741 | 0, $628 | 0, $629 | 0) | 0; + $743 = tempRet0; + $744 = _bitshift64Shl($740 | 0, $741 | 0, 21) | 0; + $745 = tempRet0; + $746 = _i64Subtract($734 | 0, $735 | 0, $744 | 0, $745 | 0) | 0; + $747 = tempRet0; + $748 = _bitshift64Ashr($742 | 0, $743 | 0, 21) | 0; + $749 = tempRet0; + $750 = _i64Add($624 | 0, $625 | 0, $748 | 0, $749 | 0) | 0; + $751 = tempRet0; + $752 = _bitshift64Shl($748 | 0, $749 | 0, 21) | 0; + $753 = tempRet0; + $754 = _i64Subtract($742 | 0, $743 | 0, $752 | 0, $753 | 0) | 0; + $755 = tempRet0; + $756 = _bitshift64Ashr($750 | 0, $751 | 0, 21) | 0; + $757 = tempRet0; + $758 = _i64Add($756 | 0, $757 | 0, $636 | 0, $637 | 0) | 0; + $759 = tempRet0; + $760 = _bitshift64Shl($756 | 0, $757 | 0, 21) | 0; + $761 = tempRet0; + $762 = _i64Subtract($750 | 0, $751 | 0, $760 | 0, $761 | 0) | 0; + $763 = tempRet0; + $764 = _bitshift64Ashr($758 | 0, $759 | 0, 21) | 0; + $765 = tempRet0; + $766 = _bitshift64Shl($764 | 0, $765 | 0, 21) | 0; + $767 = tempRet0; + $768 = _i64Subtract($758 | 0, $759 | 0, $766 | 0, $767 | 0) | 0; + $769 = tempRet0; + $770 = ___muldi3($764 | 0, $765 | 0, 666643, 0) | 0; + $771 = tempRet0; + $772 = _i64Add($770 | 0, $771 | 0, $662 | 0, $663 | 0) | 0; + $773 = tempRet0; + $774 = ___muldi3($764 | 0, $765 | 0, 470296, 0) | 0; + $775 = tempRet0; + $776 = _i64Add($676 | 0, $677 | 0, $774 | 0, $775 | 0) | 0; + $777 = tempRet0; + $778 = ___muldi3($764 | 0, $765 | 0, 654183, 0) | 0; + $779 = tempRet0; + $780 = _i64Add($688 | 0, $689 | 0, $778 | 0, $779 | 0) | 0; + $781 = tempRet0; + $782 = ___muldi3($764 | 0, $765 | 0, -997805, -1) | 0; + $783 = tempRet0; + $784 = _i64Add($702 | 0, $703 | 0, $782 | 0, $783 | 0) | 0; + $785 = tempRet0; + $786 = ___muldi3($764 | 0, $765 | 0, 136657, 0) | 0; + $787 = tempRet0; + $788 = _i64Add($714 | 0, $715 | 0, $786 | 0, $787 | 0) | 0; + $789 = tempRet0; + $790 = ___muldi3($764 | 0, $765 | 0, -683901, -1) | 0; + $791 = tempRet0; + $792 = _i64Add($722 | 0, $723 | 0, $790 | 0, $791 | 0) | 0; + $793 = tempRet0; + $794 = _bitshift64Ashr($772 | 0, $773 | 0, 21) | 0; + $795 = tempRet0; + $796 = _i64Add($776 | 0, $777 | 0, $794 | 0, $795 | 0) | 0; + $797 = tempRet0; + $798 = _bitshift64Shl($794 | 0, $795 | 0, 21) | 0; + $799 = tempRet0; + $800 = _i64Subtract($772 | 0, $773 | 0, $798 | 0, $799 | 0) | 0; + $801 = tempRet0; + $802 = _bitshift64Ashr($796 | 0, $797 | 0, 21) | 0; + $803 = tempRet0; + $804 = _i64Add($780 | 0, $781 | 0, $802 | 0, $803 | 0) | 0; + $805 = tempRet0; + $806 = _bitshift64Shl($802 | 0, $803 | 0, 21) | 0; + $807 = tempRet0; + $808 = _i64Subtract($796 | 0, $797 | 0, $806 | 0, $807 | 0) | 0; + $809 = tempRet0; + $810 = _bitshift64Ashr($804 | 0, $805 | 0, 21) | 0; + $811 = tempRet0; + $812 = _i64Add($784 | 0, $785 | 0, $810 | 0, $811 | 0) | 0; + $813 = tempRet0; + $814 = _bitshift64Shl($810 | 0, $811 | 0, 21) | 0; + $815 = tempRet0; + $816 = _i64Subtract($804 | 0, $805 | 0, $814 | 0, $815 | 0) | 0; + $817 = tempRet0; + $818 = _bitshift64Ashr($812 | 0, $813 | 0, 21) | 0; + $819 = tempRet0; + $820 = _i64Add($788 | 0, $789 | 0, $818 | 0, $819 | 0) | 0; + $821 = tempRet0; + $822 = _bitshift64Shl($818 | 0, $819 | 0, 21) | 0; + $823 = tempRet0; + $824 = _i64Subtract($812 | 0, $813 | 0, $822 | 0, $823 | 0) | 0; + $825 = tempRet0; + $826 = _bitshift64Ashr($820 | 0, $821 | 0, 21) | 0; + $827 = tempRet0; + $828 = _i64Add($792 | 0, $793 | 0, $826 | 0, $827 | 0) | 0; + $829 = tempRet0; + $830 = _bitshift64Shl($826 | 0, $827 | 0, 21) | 0; + $831 = tempRet0; + $832 = _i64Subtract($820 | 0, $821 | 0, $830 | 0, $831 | 0) | 0; + $833 = tempRet0; + $834 = _bitshift64Ashr($828 | 0, $829 | 0, 21) | 0; + $835 = tempRet0; + $836 = _i64Add($834 | 0, $835 | 0, $730 | 0, $731 | 0) | 0; + $837 = tempRet0; + $838 = _bitshift64Shl($834 | 0, $835 | 0, 21) | 0; + $839 = tempRet0; + $840 = _i64Subtract($828 | 0, $829 | 0, $838 | 0, $839 | 0) | 0; + $841 = tempRet0; + $842 = _bitshift64Ashr($836 | 0, $837 | 0, 21) | 0; + $843 = tempRet0; + $844 = _i64Add($842 | 0, $843 | 0, $738 | 0, $739 | 0) | 0; + $845 = tempRet0; + $846 = _bitshift64Shl($842 | 0, $843 | 0, 21) | 0; + $847 = tempRet0; + $848 = _i64Subtract($836 | 0, $837 | 0, $846 | 0, $847 | 0) | 0; + $849 = tempRet0; + $850 = _bitshift64Ashr($844 | 0, $845 | 0, 21) | 0; + $851 = tempRet0; + $852 = _i64Add($850 | 0, $851 | 0, $746 | 0, $747 | 0) | 0; + $853 = tempRet0; + $854 = _bitshift64Shl($850 | 0, $851 | 0, 21) | 0; + $855 = tempRet0; + $856 = _i64Subtract($844 | 0, $845 | 0, $854 | 0, $855 | 0) | 0; + $857 = tempRet0; + $858 = _bitshift64Ashr($852 | 0, $853 | 0, 21) | 0; + $859 = tempRet0; + $860 = _i64Add($858 | 0, $859 | 0, $754 | 0, $755 | 0) | 0; + $861 = tempRet0; + $862 = _bitshift64Shl($858 | 0, $859 | 0, 21) | 0; + $863 = tempRet0; + $864 = _i64Subtract($852 | 0, $853 | 0, $862 | 0, $863 | 0) | 0; + $865 = tempRet0; + $866 = _bitshift64Ashr($860 | 0, $861 | 0, 21) | 0; + $867 = tempRet0; + $868 = _i64Add($866 | 0, $867 | 0, $762 | 0, $763 | 0) | 0; + $869 = tempRet0; + $870 = _bitshift64Shl($866 | 0, $867 | 0, 21) | 0; + $871 = tempRet0; + $872 = _i64Subtract($860 | 0, $861 | 0, $870 | 0, $871 | 0) | 0; + $873 = tempRet0; + $874 = _bitshift64Ashr($868 | 0, $869 | 0, 21) | 0; + $875 = tempRet0; + $876 = _i64Add($874 | 0, $875 | 0, $768 | 0, $769 | 0) | 0; + $877 = tempRet0; + $878 = _bitshift64Shl($874 | 0, $875 | 0, 21) | 0; + $879 = tempRet0; + $880 = _i64Subtract($868 | 0, $869 | 0, $878 | 0, $879 | 0) | 0; + $881 = tempRet0; + $882 = $800 & 255; + HEAP8[$s >> 0] = $882; + $883 = _bitshift64Lshr($800 | 0, $801 | 0, 8) | 0; + $884 = tempRet0; + $885 = $883 & 255; + $886 = ($s + 1) | 0; + HEAP8[$886 >> 0] = $885; + $887 = _bitshift64Lshr($800 | 0, $801 | 0, 16) | 0; + $888 = tempRet0; + $889 = _bitshift64Shl($808 | 0, $809 | 0, 5) | 0; + $890 = tempRet0; + $891 = $889 | $887; + $890 | $888; + $892 = $891 & 255; + HEAP8[$3 >> 0] = $892; + $893 = _bitshift64Lshr($808 | 0, $809 | 0, 3) | 0; + $894 = tempRet0; + $895 = $893 & 255; + $896 = ($s + 3) | 0; + HEAP8[$896 >> 0] = $895; + $897 = _bitshift64Lshr($808 | 0, $809 | 0, 11) | 0; + $898 = tempRet0; + $899 = $897 & 255; + $900 = ($s + 4) | 0; + HEAP8[$900 >> 0] = $899; + $901 = _bitshift64Lshr($808 | 0, $809 | 0, 19) | 0; + $902 = tempRet0; + $903 = _bitshift64Shl($816 | 0, $817 | 0, 2) | 0; + $904 = tempRet0; + $905 = $903 | $901; + $904 | $902; + $906 = $905 & 255; + HEAP8[$9 >> 0] = $906; + $907 = _bitshift64Lshr($816 | 0, $817 | 0, 6) | 0; + $908 = tempRet0; + $909 = $907 & 255; + $910 = ($s + 6) | 0; + HEAP8[$910 >> 0] = $909; + $911 = _bitshift64Lshr($816 | 0, $817 | 0, 14) | 0; + $912 = tempRet0; + $913 = _bitshift64Shl($824 | 0, $825 | 0, 7) | 0; + $914 = tempRet0; + $915 = $913 | $911; + $914 | $912; + $916 = $915 & 255; + HEAP8[$15 >> 0] = $916; + $917 = _bitshift64Lshr($824 | 0, $825 | 0, 1) | 0; + $918 = tempRet0; + $919 = $917 & 255; + $920 = ($s + 8) | 0; + HEAP8[$920 >> 0] = $919; + $921 = _bitshift64Lshr($824 | 0, $825 | 0, 9) | 0; + $922 = tempRet0; + $923 = $921 & 255; + $924 = ($s + 9) | 0; + HEAP8[$924 >> 0] = $923; + $925 = _bitshift64Lshr($824 | 0, $825 | 0, 17) | 0; + $926 = tempRet0; + $927 = _bitshift64Shl($832 | 0, $833 | 0, 4) | 0; + $928 = tempRet0; + $929 = $927 | $925; + $928 | $926; + $930 = $929 & 255; + HEAP8[$21 >> 0] = $930; + $931 = _bitshift64Lshr($832 | 0, $833 | 0, 4) | 0; + $932 = tempRet0; + $933 = $931 & 255; + $934 = ($s + 11) | 0; + HEAP8[$934 >> 0] = $933; + $935 = _bitshift64Lshr($832 | 0, $833 | 0, 12) | 0; + $936 = tempRet0; + $937 = $935 & 255; + $938 = ($s + 12) | 0; + HEAP8[$938 >> 0] = $937; + $939 = _bitshift64Lshr($832 | 0, $833 | 0, 20) | 0; + $940 = tempRet0; + $941 = _bitshift64Shl($840 | 0, $841 | 0, 1) | 0; + $942 = tempRet0; + $943 = $941 | $939; + $942 | $940; + $944 = $943 & 255; + HEAP8[$27 >> 0] = $944; + $945 = _bitshift64Lshr($840 | 0, $841 | 0, 7) | 0; + $946 = tempRet0; + $947 = $945 & 255; + $948 = ($s + 14) | 0; + HEAP8[$948 >> 0] = $947; + $949 = _bitshift64Lshr($840 | 0, $841 | 0, 15) | 0; + $950 = tempRet0; + $951 = _bitshift64Shl($848 | 0, $849 | 0, 6) | 0; + $952 = tempRet0; + $953 = $951 | $949; + $952 | $950; + $954 = $953 & 255; + HEAP8[$33 >> 0] = $954; + $955 = _bitshift64Lshr($848 | 0, $849 | 0, 2) | 0; + $956 = tempRet0; + $957 = $955 & 255; + $958 = ($s + 16) | 0; + HEAP8[$958 >> 0] = $957; + $959 = _bitshift64Lshr($848 | 0, $849 | 0, 10) | 0; + $960 = tempRet0; + $961 = $959 & 255; + $962 = ($s + 17) | 0; + HEAP8[$962 >> 0] = $961; + $963 = _bitshift64Lshr($848 | 0, $849 | 0, 18) | 0; + $964 = tempRet0; + $965 = _bitshift64Shl($856 | 0, $857 | 0, 3) | 0; + $966 = tempRet0; + $967 = $965 | $963; + $966 | $964; + $968 = $967 & 255; + HEAP8[$39 >> 0] = $968; + $969 = _bitshift64Lshr($856 | 0, $857 | 0, 5) | 0; + $970 = tempRet0; + $971 = $969 & 255; + $972 = ($s + 19) | 0; + HEAP8[$972 >> 0] = $971; + $973 = _bitshift64Lshr($856 | 0, $857 | 0, 13) | 0; + $974 = tempRet0; + $975 = $973 & 255; + $976 = ($s + 20) | 0; + HEAP8[$976 >> 0] = $975; + $977 = $864 & 255; + HEAP8[$45 >> 0] = $977; + $978 = _bitshift64Lshr($864 | 0, $865 | 0, 8) | 0; + $979 = tempRet0; + $980 = $978 & 255; + $981 = ($s + 22) | 0; + HEAP8[$981 >> 0] = $980; + $982 = _bitshift64Lshr($864 | 0, $865 | 0, 16) | 0; + $983 = tempRet0; + $984 = _bitshift64Shl($872 | 0, $873 | 0, 5) | 0; + $985 = tempRet0; + $986 = $984 | $982; + $985 | $983; + $987 = $986 & 255; + HEAP8[$49 >> 0] = $987; + $988 = _bitshift64Lshr($872 | 0, $873 | 0, 3) | 0; + $989 = tempRet0; + $990 = $988 & 255; + $991 = ($s + 24) | 0; + HEAP8[$991 >> 0] = $990; + $992 = _bitshift64Lshr($872 | 0, $873 | 0, 11) | 0; + $993 = tempRet0; + $994 = $992 & 255; + $995 = ($s + 25) | 0; + HEAP8[$995 >> 0] = $994; + $996 = _bitshift64Lshr($872 | 0, $873 | 0, 19) | 0; + $997 = tempRet0; + $998 = _bitshift64Shl($880 | 0, $881 | 0, 2) | 0; + $999 = tempRet0; + $1000 = $998 | $996; + $999 | $997; + $1001 = $1000 & 255; + HEAP8[$55 >> 0] = $1001; + $1002 = _bitshift64Lshr($880 | 0, $881 | 0, 6) | 0; + $1003 = tempRet0; + $1004 = $1002 & 255; + $1005 = ($s + 27) | 0; + HEAP8[$1005 >> 0] = $1004; + $1006 = _bitshift64Lshr($880 | 0, $881 | 0, 14) | 0; + $1007 = tempRet0; + $1008 = _bitshift64Shl($876 | 0, $877 | 0, 7) | 0; + $1009 = tempRet0; + $1010 = $1006 | $1008; + $1007 | $1009; + $1011 = $1010 & 255; + HEAP8[$61 >> 0] = $1011; + $1012 = _bitshift64Lshr($876 | 0, $877 | 0, 1) | 0; + $1013 = tempRet0; + $1014 = $1012 & 255; + $1015 = ($s + 29) | 0; + HEAP8[$1015 >> 0] = $1014; + $1016 = _bitshift64Lshr($876 | 0, $877 | 0, 9) | 0; + $1017 = tempRet0; + $1018 = $1016 & 255; + $1019 = ($s + 30) | 0; + HEAP8[$1019 >> 0] = $1018; + $1020 = _bitshift64Lshr($876 | 0, $877 | 0, 17) | 0; + $1021 = tempRet0; + $1022 = $1020 & 255; + HEAP8[$67 >> 0] = $1022; + STACKTOP = sp; + return; + } + function _load_351($in) { + $in = $in | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP8[$in >> 0] | 0; + $1 = $0 & 255; + $2 = ($in + 1) | 0; + $3 = HEAP8[$2 >> 0] | 0; + $4 = $3 & 255; + $5 = _bitshift64Shl($4 | 0, 0, 8) | 0; + $6 = tempRet0; + $7 = $5 | $1; + $8 = ($in + 2) | 0; + $9 = HEAP8[$8 >> 0] | 0; + $10 = $9 & 255; + $11 = _bitshift64Shl($10 | 0, 0, 16) | 0; + $12 = tempRet0; + $13 = $7 | $11; + $14 = $6 | $12; + tempRet0 = $14; + STACKTOP = sp; + return $13 | 0; + } + function _load_452($in) { + $in = $in | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0; + var $8 = 0, + $9 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP8[$in >> 0] | 0; + $1 = $0 & 255; + $2 = ($in + 1) | 0; + $3 = HEAP8[$2 >> 0] | 0; + $4 = $3 & 255; + $5 = _bitshift64Shl($4 | 0, 0, 8) | 0; + $6 = tempRet0; + $7 = $5 | $1; + $8 = ($in + 2) | 0; + $9 = HEAP8[$8 >> 0] | 0; + $10 = $9 & 255; + $11 = _bitshift64Shl($10 | 0, 0, 16) | 0; + $12 = tempRet0; + $13 = $7 | $11; + $14 = $6 | $12; + $15 = ($in + 3) | 0; + $16 = HEAP8[$15 >> 0] | 0; + $17 = $16 & 255; + $18 = _bitshift64Shl($17 | 0, 0, 24) | 0; + $19 = tempRet0; + $20 = $13 | $18; + $21 = $14 | $19; + tempRet0 = $21; + STACKTOP = sp; + return $20 | 0; + } + function _sph_sha512_init($cc) { + $cc = $cc | 0; + var $0 = 0, + $1 = 0, + $2 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + dest = 0, + label = 0, + sp = 0, + src = 0, + stop = 0; + sp = STACKTOP; + $0 = ($cc + 128) | 0; + dest = ($0 + 0) | 0; + src = (31840 + 0) | 0; + stop = (dest + 64) | 0; + do { + HEAP32[dest >> 2] = HEAP32[src >> 2] | 0; + dest = (dest + 4) | 0; + src = (src + 4) | 0; + } while ((dest | 0) < (stop | 0)); + $1 = ($cc + 192) | 0; + $2 = $1; + $3 = $2; + HEAP32[$3 >> 2] = 0; + $4 = ($2 + 4) | 0; + $5 = $4; + HEAP32[$5 >> 2] = 0; + STACKTOP = sp; + return; + } + function _sph_sha384($cc, $data, $len) { + $cc = $cc | 0; + $data = $data | 0; + $len = $len | 0; + var $$01$ = 0, + $$012 = 0, + $$03 = 0, + $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0; + var $24 = 0, + $25 = 0, + $26 = 0, + $27 = 0, + $28 = 0, + $29 = 0, + $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + $current$04 = 0, + $current$1 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = ($cc + 192) | 0; + $1 = ($len | 0) == 0; + if ($1) { + STACKTOP = sp; + return; + } + $2 = $0; + $3 = $2; + $4 = HEAP32[$3 >> 2] | 0; + $5 = ($2 + 4) | 0; + $6 = $5; + $7 = HEAP32[$6 >> 2] | 0; + $8 = $4 & 127; + $9 = ($cc + 128) | 0; + $$012 = $len; + $$03 = $data; + $current$04 = $8; + while (1) { + $10 = (128 - $current$04) | 0; + $11 = $10 >>> 0 > $$012 >>> 0; + $$01$ = $11 ? $$012 : $10; + $12 = ($cc + $current$04) | 0; + _memcpy($12 | 0, $$03 | 0, $$01$ | 0) | 0; + $13 = ($$03 + $$01$) | 0; + $14 = ($$01$ + $current$04) | 0; + $15 = ($$012 - $$01$) | 0; + $16 = ($14 | 0) == 128; + if ($16) { + _sha3_round($cc, $9); + $current$1 = 0; + } else { + $current$1 = $14; + } + $17 = $0; + $18 = $17; + $19 = HEAP32[$18 >> 2] | 0; + $20 = ($17 + 4) | 0; + $21 = $20; + $22 = HEAP32[$21 >> 2] | 0; + $23 = _i64Add($19 | 0, $22 | 0, $$01$ | 0, 0) | 0; + $24 = tempRet0; + $25 = $0; + $26 = $25; + HEAP32[$26 >> 2] = $23; + $27 = ($25 + 4) | 0; + $28 = $27; + HEAP32[$28 >> 2] = $24; + $29 = ($$012 | 0) == ($$01$ | 0); + if ($29) { + break; + } else { + $$012 = $15; + $$03 = $13; + $current$04 = $current$1; + } + } + STACKTOP = sp; + return; + } + function _sph_sha512_close($cc, $dst) { + $cc = $cc | 0; + $dst = $dst | 0; + var label = 0, + sp = 0; + sp = STACKTOP; + _sha384_close($cc, $dst, 8); + _sph_sha512_init($cc); + STACKTOP = sp; + return; + } + function _sha3_round($data, $r) { + $data = $data | 0; + $r = $r | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $101 = 0, + $102 = 0, + $103 = 0, + $104 = 0, + $105 = 0, + $106 = 0, + $107 = 0, + $108 = 0, + $109 = 0, + $11 = 0, + $110 = 0, + $111 = 0, + $112 = 0, + $113 = 0, + $114 = 0, + $115 = 0; + var $116 = 0, + $117 = 0, + $118 = 0, + $119 = 0, + $12 = 0, + $120 = 0, + $121 = 0, + $122 = 0, + $123 = 0, + $124 = 0, + $125 = 0, + $126 = 0, + $127 = 0, + $128 = 0, + $129 = 0, + $13 = 0, + $130 = 0, + $131 = 0, + $132 = 0, + $133 = 0; + var $134 = 0, + $135 = 0, + $136 = 0, + $137 = 0, + $138 = 0, + $139 = 0, + $14 = 0, + $140 = 0, + $141 = 0, + $142 = 0, + $143 = 0, + $144 = 0, + $145 = 0, + $146 = 0, + $147 = 0, + $148 = 0, + $149 = 0, + $15 = 0, + $150 = 0, + $151 = 0; + var $152 = 0, + $153 = 0, + $154 = 0, + $155 = 0, + $156 = 0, + $157 = 0, + $158 = 0, + $159 = 0, + $16 = 0, + $160 = 0, + $161 = 0, + $162 = 0, + $163 = 0, + $164 = 0, + $165 = 0, + $166 = 0, + $167 = 0, + $168 = 0, + $169 = 0, + $17 = 0; + var $170 = 0, + $171 = 0, + $172 = 0, + $173 = 0, + $174 = 0, + $175 = 0, + $176 = 0, + $177 = 0, + $178 = 0, + $179 = 0, + $18 = 0, + $180 = 0, + $181 = 0, + $182 = 0, + $183 = 0, + $184 = 0, + $185 = 0, + $186 = 0, + $187 = 0, + $188 = 0; + var $189 = 0, + $19 = 0, + $190 = 0, + $191 = 0, + $192 = 0, + $193 = 0, + $194 = 0, + $195 = 0, + $196 = 0, + $197 = 0, + $198 = 0, + $199 = 0, + $2 = 0, + $20 = 0, + $200 = 0, + $201 = 0, + $202 = 0, + $203 = 0, + $204 = 0, + $205 = 0; + var $206 = 0, + $207 = 0, + $208 = 0, + $209 = 0, + $21 = 0, + $210 = 0, + $211 = 0, + $212 = 0, + $213 = 0, + $214 = 0, + $215 = 0, + $216 = 0, + $217 = 0, + $218 = 0, + $219 = 0, + $22 = 0, + $220 = 0, + $221 = 0, + $222 = 0, + $223 = 0; + var $224 = 0, + $225 = 0, + $226 = 0, + $227 = 0, + $228 = 0, + $229 = 0, + $23 = 0, + $230 = 0, + $231 = 0, + $232 = 0, + $233 = 0, + $234 = 0, + $235 = 0, + $236 = 0, + $237 = 0, + $238 = 0, + $239 = 0, + $24 = 0, + $240 = 0, + $241 = 0; + var $242 = 0, + $243 = 0, + $244 = 0, + $245 = 0, + $246 = 0, + $247 = 0, + $248 = 0, + $249 = 0, + $25 = 0, + $250 = 0, + $251 = 0, + $252 = 0, + $253 = 0, + $254 = 0, + $255 = 0, + $256 = 0, + $257 = 0, + $258 = 0, + $259 = 0, + $26 = 0; + var $260 = 0, + $261 = 0, + $262 = 0, + $263 = 0, + $264 = 0, + $265 = 0, + $266 = 0, + $267 = 0, + $268 = 0, + $269 = 0, + $27 = 0, + $270 = 0, + $271 = 0, + $272 = 0, + $273 = 0, + $274 = 0, + $275 = 0, + $276 = 0, + $277 = 0, + $278 = 0; + var $279 = 0, + $28 = 0, + $280 = 0, + $281 = 0, + $282 = 0, + $283 = 0, + $284 = 0, + $285 = 0, + $286 = 0, + $287 = 0, + $288 = 0, + $289 = 0, + $29 = 0, + $290 = 0, + $291 = 0, + $292 = 0, + $293 = 0, + $294 = 0, + $295 = 0, + $296 = 0; + var $297 = 0, + $298 = 0, + $299 = 0, + $3 = 0, + $30 = 0, + $300 = 0, + $301 = 0, + $302 = 0, + $303 = 0, + $304 = 0, + $305 = 0, + $306 = 0, + $307 = 0, + $308 = 0, + $309 = 0, + $31 = 0, + $310 = 0, + $311 = 0, + $312 = 0, + $313 = 0; + var $314 = 0, + $315 = 0, + $316 = 0, + $317 = 0, + $318 = 0, + $319 = 0, + $32 = 0, + $320 = 0, + $321 = 0, + $322 = 0, + $323 = 0, + $324 = 0, + $325 = 0, + $326 = 0, + $327 = 0, + $328 = 0, + $329 = 0, + $33 = 0, + $330 = 0, + $331 = 0; + var $332 = 0, + $333 = 0, + $334 = 0, + $335 = 0, + $336 = 0, + $337 = 0, + $338 = 0, + $339 = 0, + $34 = 0, + $340 = 0, + $341 = 0, + $342 = 0, + $343 = 0, + $344 = 0, + $345 = 0, + $346 = 0, + $347 = 0, + $348 = 0, + $349 = 0, + $35 = 0; + var $350 = 0, + $351 = 0, + $352 = 0, + $353 = 0, + $354 = 0, + $355 = 0, + $356 = 0, + $357 = 0, + $358 = 0, + $359 = 0, + $36 = 0, + $360 = 0, + $361 = 0, + $362 = 0, + $363 = 0, + $364 = 0, + $365 = 0, + $366 = 0, + $367 = 0, + $368 = 0; + var $369 = 0, + $37 = 0, + $370 = 0, + $371 = 0, + $372 = 0, + $373 = 0, + $374 = 0, + $375 = 0, + $376 = 0, + $377 = 0, + $378 = 0, + $379 = 0, + $38 = 0, + $380 = 0, + $381 = 0, + $382 = 0, + $383 = 0, + $384 = 0, + $385 = 0, + $386 = 0; + var $387 = 0, + $388 = 0, + $389 = 0, + $39 = 0, + $390 = 0, + $391 = 0, + $392 = 0, + $393 = 0, + $394 = 0, + $395 = 0, + $396 = 0, + $397 = 0, + $398 = 0, + $399 = 0, + $4 = 0, + $40 = 0, + $400 = 0, + $401 = 0, + $402 = 0, + $403 = 0; + var $404 = 0, + $405 = 0, + $406 = 0, + $407 = 0, + $408 = 0, + $409 = 0, + $41 = 0, + $410 = 0, + $411 = 0, + $412 = 0, + $413 = 0, + $414 = 0, + $415 = 0, + $416 = 0, + $417 = 0, + $418 = 0, + $419 = 0, + $42 = 0, + $420 = 0, + $421 = 0; + var $422 = 0, + $423 = 0, + $424 = 0, + $425 = 0, + $426 = 0, + $427 = 0, + $428 = 0, + $429 = 0, + $43 = 0, + $430 = 0, + $431 = 0, + $432 = 0, + $433 = 0, + $434 = 0, + $435 = 0, + $436 = 0, + $437 = 0, + $438 = 0, + $439 = 0, + $44 = 0; + var $440 = 0, + $441 = 0, + $442 = 0, + $443 = 0, + $444 = 0, + $445 = 0, + $446 = 0, + $447 = 0, + $448 = 0, + $449 = 0, + $45 = 0, + $450 = 0, + $451 = 0, + $452 = 0, + $453 = 0, + $454 = 0, + $455 = 0, + $456 = 0, + $457 = 0, + $458 = 0; + var $459 = 0, + $46 = 0, + $460 = 0, + $461 = 0, + $462 = 0, + $463 = 0, + $464 = 0, + $465 = 0, + $466 = 0, + $467 = 0, + $468 = 0, + $469 = 0, + $47 = 0, + $470 = 0, + $471 = 0, + $472 = 0, + $473 = 0, + $474 = 0, + $475 = 0, + $476 = 0; + var $477 = 0, + $478 = 0, + $479 = 0, + $48 = 0, + $480 = 0, + $481 = 0, + $482 = 0, + $483 = 0, + $484 = 0, + $485 = 0, + $486 = 0, + $487 = 0, + $488 = 0, + $489 = 0, + $49 = 0, + $490 = 0, + $491 = 0, + $492 = 0, + $493 = 0, + $494 = 0; + var $495 = 0, + $496 = 0, + $497 = 0, + $498 = 0, + $499 = 0, + $5 = 0, + $50 = 0, + $500 = 0, + $501 = 0, + $502 = 0, + $503 = 0, + $504 = 0, + $505 = 0, + $506 = 0, + $507 = 0, + $508 = 0, + $509 = 0, + $51 = 0, + $510 = 0, + $511 = 0; + var $512 = 0, + $513 = 0, + $514 = 0, + $515 = 0, + $516 = 0, + $517 = 0, + $518 = 0, + $519 = 0, + $52 = 0, + $520 = 0, + $521 = 0, + $522 = 0, + $523 = 0, + $524 = 0, + $525 = 0, + $526 = 0, + $527 = 0, + $528 = 0, + $529 = 0, + $53 = 0; + var $530 = 0, + $531 = 0, + $532 = 0, + $533 = 0, + $534 = 0, + $535 = 0, + $536 = 0, + $537 = 0, + $538 = 0, + $539 = 0, + $54 = 0, + $540 = 0, + $541 = 0, + $542 = 0, + $543 = 0, + $544 = 0, + $545 = 0, + $546 = 0, + $547 = 0, + $548 = 0; + var $549 = 0, + $55 = 0, + $550 = 0, + $551 = 0, + $552 = 0, + $553 = 0, + $554 = 0, + $555 = 0, + $556 = 0, + $557 = 0, + $558 = 0, + $559 = 0, + $56 = 0, + $560 = 0, + $561 = 0, + $562 = 0, + $563 = 0, + $564 = 0, + $565 = 0, + $566 = 0; + var $567 = 0, + $568 = 0, + $569 = 0, + $57 = 0, + $570 = 0, + $571 = 0, + $572 = 0, + $573 = 0, + $574 = 0, + $575 = 0, + $576 = 0, + $577 = 0, + $578 = 0, + $579 = 0, + $58 = 0, + $580 = 0, + $581 = 0, + $582 = 0, + $583 = 0, + $584 = 0; + var $585 = 0, + $586 = 0, + $587 = 0, + $588 = 0, + $589 = 0, + $59 = 0, + $590 = 0, + $591 = 0, + $592 = 0, + $593 = 0, + $594 = 0, + $595 = 0, + $596 = 0, + $597 = 0, + $598 = 0, + $599 = 0, + $6 = 0, + $60 = 0, + $600 = 0, + $601 = 0; + var $602 = 0, + $603 = 0, + $604 = 0, + $605 = 0, + $606 = 0, + $607 = 0, + $608 = 0, + $609 = 0, + $61 = 0, + $610 = 0, + $611 = 0, + $612 = 0, + $613 = 0, + $614 = 0, + $615 = 0, + $616 = 0, + $617 = 0, + $618 = 0, + $619 = 0, + $62 = 0; + var $620 = 0, + $621 = 0, + $622 = 0, + $623 = 0, + $624 = 0, + $625 = 0, + $626 = 0, + $627 = 0, + $628 = 0, + $629 = 0, + $63 = 0, + $630 = 0, + $631 = 0, + $632 = 0, + $633 = 0, + $634 = 0, + $635 = 0, + $636 = 0, + $637 = 0, + $638 = 0; + var $639 = 0, + $64 = 0, + $640 = 0, + $641 = 0, + $642 = 0, + $643 = 0, + $644 = 0, + $645 = 0, + $646 = 0, + $647 = 0, + $648 = 0, + $649 = 0, + $65 = 0, + $650 = 0, + $651 = 0, + $652 = 0, + $653 = 0, + $654 = 0, + $655 = 0, + $656 = 0; + var $657 = 0, + $658 = 0, + $659 = 0, + $66 = 0, + $660 = 0, + $661 = 0, + $662 = 0, + $663 = 0, + $664 = 0, + $665 = 0, + $666 = 0, + $667 = 0, + $668 = 0, + $669 = 0, + $67 = 0, + $670 = 0, + $671 = 0, + $672 = 0, + $673 = 0, + $674 = 0; + var $675 = 0, + $676 = 0, + $677 = 0, + $678 = 0, + $679 = 0, + $68 = 0, + $680 = 0, + $681 = 0, + $682 = 0, + $683 = 0, + $684 = 0, + $685 = 0, + $686 = 0, + $687 = 0, + $688 = 0, + $689 = 0, + $69 = 0, + $690 = 0, + $691 = 0, + $692 = 0; + var $693 = 0, + $694 = 0, + $695 = 0, + $696 = 0, + $697 = 0, + $698 = 0, + $699 = 0, + $7 = 0, + $70 = 0, + $700 = 0, + $701 = 0, + $702 = 0, + $703 = 0, + $704 = 0, + $705 = 0, + $706 = 0, + $707 = 0, + $708 = 0, + $709 = 0, + $71 = 0; + var $710 = 0, + $711 = 0, + $712 = 0, + $713 = 0, + $714 = 0, + $715 = 0, + $716 = 0, + $717 = 0, + $718 = 0, + $719 = 0, + $72 = 0, + $720 = 0, + $721 = 0, + $722 = 0, + $723 = 0, + $724 = 0, + $725 = 0, + $726 = 0, + $727 = 0, + $728 = 0; + var $729 = 0, + $73 = 0, + $730 = 0, + $731 = 0, + $732 = 0, + $733 = 0, + $734 = 0, + $735 = 0, + $736 = 0, + $737 = 0, + $738 = 0, + $739 = 0, + $74 = 0, + $740 = 0, + $741 = 0, + $742 = 0, + $743 = 0, + $744 = 0, + $745 = 0, + $746 = 0; + var $747 = 0, + $748 = 0, + $749 = 0, + $75 = 0, + $750 = 0, + $751 = 0, + $752 = 0, + $753 = 0, + $754 = 0, + $755 = 0, + $756 = 0, + $757 = 0, + $758 = 0, + $759 = 0, + $76 = 0, + $760 = 0, + $761 = 0, + $762 = 0, + $763 = 0, + $764 = 0; + var $765 = 0, + $766 = 0, + $767 = 0, + $768 = 0, + $769 = 0, + $77 = 0, + $770 = 0, + $771 = 0, + $772 = 0, + $773 = 0, + $774 = 0, + $775 = 0, + $776 = 0, + $777 = 0, + $778 = 0, + $779 = 0, + $78 = 0, + $780 = 0, + $781 = 0, + $782 = 0; + var $783 = 0, + $784 = 0, + $785 = 0, + $786 = 0, + $787 = 0, + $788 = 0, + $789 = 0, + $79 = 0, + $790 = 0, + $791 = 0, + $792 = 0, + $793 = 0, + $794 = 0, + $795 = 0, + $796 = 0, + $797 = 0, + $798 = 0, + $799 = 0, + $8 = 0, + $80 = 0; + var $800 = 0, + $801 = 0, + $802 = 0, + $803 = 0, + $804 = 0, + $805 = 0, + $806 = 0, + $807 = 0, + $808 = 0, + $809 = 0, + $81 = 0, + $810 = 0, + $811 = 0, + $812 = 0, + $813 = 0, + $814 = 0, + $815 = 0, + $816 = 0, + $817 = 0, + $818 = 0; + var $819 = 0, + $82 = 0, + $820 = 0, + $821 = 0, + $822 = 0, + $823 = 0, + $824 = 0, + $825 = 0, + $826 = 0, + $827 = 0, + $828 = 0, + $829 = 0, + $83 = 0, + $830 = 0, + $831 = 0, + $832 = 0, + $833 = 0, + $834 = 0, + $835 = 0, + $836 = 0; + var $837 = 0, + $838 = 0, + $839 = 0, + $84 = 0, + $840 = 0, + $841 = 0, + $842 = 0, + $843 = 0, + $844 = 0, + $845 = 0, + $846 = 0, + $847 = 0, + $848 = 0, + $849 = 0, + $85 = 0, + $850 = 0, + $851 = 0, + $852 = 0, + $853 = 0, + $854 = 0; + var $855 = 0, + $856 = 0, + $857 = 0, + $858 = 0, + $859 = 0, + $86 = 0, + $860 = 0, + $861 = 0, + $862 = 0, + $863 = 0, + $864 = 0, + $865 = 0, + $866 = 0, + $867 = 0, + $868 = 0, + $869 = 0, + $87 = 0, + $870 = 0, + $871 = 0, + $872 = 0; + var $873 = 0, + $874 = 0, + $875 = 0, + $876 = 0, + $877 = 0, + $878 = 0, + $879 = 0, + $88 = 0, + $880 = 0, + $881 = 0, + $882 = 0, + $883 = 0, + $884 = 0, + $885 = 0, + $886 = 0, + $887 = 0, + $888 = 0, + $889 = 0, + $89 = 0, + $890 = 0; + var $891 = 0, + $892 = 0, + $893 = 0, + $894 = 0, + $895 = 0, + $896 = 0, + $897 = 0, + $898 = 0, + $899 = 0, + $9 = 0, + $90 = 0, + $900 = 0, + $901 = 0, + $902 = 0, + $903 = 0, + $904 = 0, + $905 = 0, + $906 = 0, + $907 = 0, + $908 = 0; + var $909 = 0, + $91 = 0, + $910 = 0, + $911 = 0, + $912 = 0, + $913 = 0, + $914 = 0, + $915 = 0, + $916 = 0, + $917 = 0, + $918 = 0, + $919 = 0, + $92 = 0, + $920 = 0, + $921 = 0, + $922 = 0, + $923 = 0, + $924 = 0, + $925 = 0, + $926 = 0; + var $927 = 0, + $928 = 0, + $929 = 0, + $93 = 0, + $930 = 0, + $931 = 0, + $932 = 0, + $933 = 0, + $934 = 0, + $935 = 0, + $936 = 0, + $937 = 0, + $938 = 0, + $939 = 0, + $94 = 0, + $940 = 0, + $941 = 0, + $942 = 0, + $943 = 0, + $944 = 0; + var $945 = 0, + $946 = 0, + $947 = 0, + $948 = 0, + $949 = 0, + $95 = 0, + $950 = 0, + $951 = 0, + $952 = 0, + $953 = 0, + $96 = 0, + $97 = 0, + $98 = 0, + $99 = 0, + $W = 0, + $exitcond = 0, + $exitcond19 = 0, + $i$011 = 0, + $i$110 = 0, + $i$29 = 0; + var label = 0, + sp = 0; + sp = STACKTOP; + STACKTOP = (STACKTOP + 640) | 0; + $W = sp; + $i$011 = 0; + while (1) { + $0 = $i$011 << 3; + $1 = ($data + $0) | 0; + $2 = _sph_dec64be_aligned($1) | 0; + $3 = tempRet0; + $4 = ($W + ($i$011 << 3)) | 0; + $5 = $4; + $6 = $5; + HEAP32[$6 >> 2] = $2; + $7 = ($5 + 4) | 0; + $8 = $7; + HEAP32[$8 >> 2] = $3; + $9 = ($i$011 + 1) | 0; + $exitcond19 = ($9 | 0) == 16; + if ($exitcond19) { + $i$110 = 16; + break; + } else { + $i$011 = $9; + } + } + while (1) { + $10 = ($i$110 + -2) | 0; + $11 = ($W + ($10 << 3)) | 0; + $12 = $11; + $13 = $12; + $14 = HEAP32[$13 >> 2] | 0; + $15 = ($12 + 4) | 0; + $16 = $15; + $17 = HEAP32[$16 >> 2] | 0; + $18 = _bitshift64Shl($14 | 0, $17 | 0, 45) | 0; + $19 = tempRet0; + $20 = _bitshift64Lshr($14 | 0, $17 | 0, 19) | 0; + $21 = tempRet0; + $22 = $18 | $20; + $23 = $19 | $21; + $24 = _bitshift64Shl($14 | 0, $17 | 0, 3) | 0; + $25 = tempRet0; + $26 = _bitshift64Lshr($14 | 0, $17 | 0, 61) | 0; + $27 = tempRet0; + $28 = $24 | $26; + $29 = $25 | $27; + $30 = _bitshift64Lshr($14 | 0, $17 | 0, 6) | 0; + $31 = tempRet0; + $32 = $28 ^ $30; + $33 = $29 ^ $31; + $34 = $32 ^ $22; + $35 = $33 ^ $23; + $36 = ($i$110 + -7) | 0; + $37 = ($W + ($36 << 3)) | 0; + $38 = $37; + $39 = $38; + $40 = HEAP32[$39 >> 2] | 0; + $41 = ($38 + 4) | 0; + $42 = $41; + $43 = HEAP32[$42 >> 2] | 0; + $44 = ($i$110 + -15) | 0; + $45 = ($W + ($44 << 3)) | 0; + $46 = $45; + $47 = $46; + $48 = HEAP32[$47 >> 2] | 0; + $49 = ($46 + 4) | 0; + $50 = $49; + $51 = HEAP32[$50 >> 2] | 0; + $52 = _bitshift64Shl($48 | 0, $51 | 0, 63) | 0; + $53 = tempRet0; + $54 = _bitshift64Lshr($48 | 0, $51 | 0, 1) | 0; + $55 = tempRet0; + $56 = $52 | $54; + $57 = $53 | $55; + $58 = _bitshift64Shl($48 | 0, $51 | 0, 56) | 0; + $59 = tempRet0; + $60 = _bitshift64Lshr($48 | 0, $51 | 0, 8) | 0; + $61 = tempRet0; + $62 = $58 | $60; + $63 = $59 | $61; + $64 = _bitshift64Lshr($48 | 0, $51 | 0, 7) | 0; + $65 = tempRet0; + $66 = $62 ^ $64; + $67 = $63 ^ $65; + $68 = $66 ^ $56; + $69 = $67 ^ $57; + $70 = ($i$110 + -16) | 0; + $71 = ($W + ($70 << 3)) | 0; + $72 = $71; + $73 = $72; + $74 = HEAP32[$73 >> 2] | 0; + $75 = ($72 + 4) | 0; + $76 = $75; + $77 = HEAP32[$76 >> 2] | 0; + $78 = _i64Add($74 | 0, $77 | 0, $40 | 0, $43 | 0) | 0; + $79 = tempRet0; + $80 = _i64Add($78 | 0, $79 | 0, $34 | 0, $35 | 0) | 0; + $81 = tempRet0; + $82 = _i64Add($80 | 0, $81 | 0, $68 | 0, $69 | 0) | 0; + $83 = tempRet0; + $84 = ($W + ($i$110 << 3)) | 0; + $85 = $84; + $86 = $85; + HEAP32[$86 >> 2] = $82; + $87 = ($85 + 4) | 0; + $88 = $87; + HEAP32[$88 >> 2] = $83; + $89 = ($i$110 + 1) | 0; + $exitcond = ($89 | 0) == 80; + if ($exitcond) { + break; + } else { + $i$110 = $89; + } + } + $90 = $r; + $91 = $90; + $92 = HEAP32[$91 >> 2] | 0; + $93 = ($90 + 4) | 0; + $94 = $93; + $95 = HEAP32[$94 >> 2] | 0; + $96 = ($r + 8) | 0; + $97 = $96; + $98 = $97; + $99 = HEAP32[$98 >> 2] | 0; + $100 = ($97 + 4) | 0; + $101 = $100; + $102 = HEAP32[$101 >> 2] | 0; + $103 = ($r + 16) | 0; + $104 = $103; + $105 = $104; + $106 = HEAP32[$105 >> 2] | 0; + $107 = ($104 + 4) | 0; + $108 = $107; + $109 = HEAP32[$108 >> 2] | 0; + $110 = ($r + 24) | 0; + $111 = $110; + $112 = $111; + $113 = HEAP32[$112 >> 2] | 0; + $114 = ($111 + 4) | 0; + $115 = $114; + $116 = HEAP32[$115 >> 2] | 0; + $117 = ($r + 32) | 0; + $118 = $117; + $119 = $118; + $120 = HEAP32[$119 >> 2] | 0; + $121 = ($118 + 4) | 0; + $122 = $121; + $123 = HEAP32[$122 >> 2] | 0; + $124 = ($r + 40) | 0; + $125 = $124; + $126 = $125; + $127 = HEAP32[$126 >> 2] | 0; + $128 = ($125 + 4) | 0; + $129 = $128; + $130 = HEAP32[$129 >> 2] | 0; + $131 = ($r + 48) | 0; + $132 = $131; + $133 = $132; + $134 = HEAP32[$133 >> 2] | 0; + $135 = ($132 + 4) | 0; + $136 = $135; + $137 = HEAP32[$136 >> 2] | 0; + $138 = ($r + 56) | 0; + $139 = $138; + $140 = $139; + $141 = HEAP32[$140 >> 2] | 0; + $142 = ($139 + 4) | 0; + $143 = $142; + $144 = HEAP32[$143 >> 2] | 0; + $145 = $120; + $146 = $123; + $170 = $134; + $171 = $127; + $173 = $137; + $174 = $130; + $193 = $141; + $194 = $144; + $203 = $92; + $204 = $95; + $228 = $99; + $230 = $102; + $234 = $106; + $236 = $109; + $241 = $113; + $242 = $116; + $i$29 = 0; + while (1) { + $147 = _bitshift64Shl($145 | 0, $146 | 0, 50) | 0; + $148 = tempRet0; + $149 = _bitshift64Lshr($145 | 0, $146 | 0, 14) | 0; + $150 = tempRet0; + $151 = $147 | $149; + $152 = $148 | $150; + $153 = _bitshift64Shl($145 | 0, $146 | 0, 46) | 0; + $154 = tempRet0; + $155 = _bitshift64Lshr($145 | 0, $146 | 0, 18) | 0; + $156 = tempRet0; + $157 = $153 | $155; + $158 = $154 | $156; + $159 = $151 ^ $157; + $160 = $152 ^ $158; + $161 = _bitshift64Shl($145 | 0, $146 | 0, 23) | 0; + $162 = tempRet0; + $163 = _bitshift64Lshr($145 | 0, $146 | 0, 41) | 0; + $164 = tempRet0; + $165 = $161 | $163; + $166 = $162 | $164; + $167 = $159 ^ $165; + $168 = $160 ^ $166; + $169 = $171 ^ $170; + $172 = $174 ^ $173; + $175 = $169 & $145; + $176 = $172 & $146; + $177 = $175 ^ $170; + $178 = $176 ^ $173; + $179 = (31904 + ($i$29 << 3)) | 0; + $180 = $179; + $181 = $180; + $182 = HEAP32[$181 >> 2] | 0; + $183 = ($180 + 4) | 0; + $184 = $183; + $185 = HEAP32[$184 >> 2] | 0; + $186 = ($W + ($i$29 << 3)) | 0; + $187 = $186; + $188 = $187; + $189 = HEAP32[$188 >> 2] | 0; + $190 = ($187 + 4) | 0; + $191 = $190; + $192 = HEAP32[$191 >> 2] | 0; + $195 = _i64Add($177 | 0, $178 | 0, $193 | 0, $194 | 0) | 0; + $196 = tempRet0; + $197 = _i64Add($195 | 0, $196 | 0, $167 | 0, $168 | 0) | 0; + $198 = tempRet0; + $199 = _i64Add($197 | 0, $198 | 0, $182 | 0, $185 | 0) | 0; + $200 = tempRet0; + $201 = _i64Add($199 | 0, $200 | 0, $189 | 0, $192 | 0) | 0; + $202 = tempRet0; + $205 = _bitshift64Shl($203 | 0, $204 | 0, 36) | 0; + $206 = tempRet0; + $207 = _bitshift64Lshr($203 | 0, $204 | 0, 28) | 0; + $208 = tempRet0; + $209 = $205 | $207; + $210 = $206 | $208; + $211 = _bitshift64Shl($203 | 0, $204 | 0, 30) | 0; + $212 = tempRet0; + $213 = _bitshift64Lshr($203 | 0, $204 | 0, 34) | 0; + $214 = tempRet0; + $215 = $211 | $213; + $216 = $212 | $214; + $217 = $209 ^ $215; + $218 = $210 ^ $216; + $219 = _bitshift64Shl($203 | 0, $204 | 0, 25) | 0; + $220 = tempRet0; + $221 = _bitshift64Lshr($203 | 0, $204 | 0, 39) | 0; + $222 = tempRet0; + $223 = $219 | $221; + $224 = $220 | $222; + $225 = $217 ^ $223; + $226 = $218 ^ $224; + $227 = $203 & $228; + $229 = $204 & $230; + $231 = $203 | $228; + $232 = $204 | $230; + $233 = $231 & $234; + $235 = $232 & $236; + $237 = $233 | $227; + $238 = $235 | $229; + $239 = _i64Add($225 | 0, $226 | 0, $237 | 0, $238 | 0) | 0; + $240 = tempRet0; + $243 = _i64Add($201 | 0, $202 | 0, $241 | 0, $242 | 0) | 0; + $244 = tempRet0; + $245 = _i64Add($239 | 0, $240 | 0, $201 | 0, $202 | 0) | 0; + $246 = tempRet0; + $247 = _bitshift64Shl($243 | 0, $244 | 0, 50) | 0; + $248 = tempRet0; + $249 = _bitshift64Lshr($243 | 0, $244 | 0, 14) | 0; + $250 = tempRet0; + $251 = $247 | $249; + $252 = $248 | $250; + $253 = _bitshift64Shl($243 | 0, $244 | 0, 46) | 0; + $254 = tempRet0; + $255 = _bitshift64Lshr($243 | 0, $244 | 0, 18) | 0; + $256 = tempRet0; + $257 = $253 | $255; + $258 = $254 | $256; + $259 = $251 ^ $257; + $260 = $252 ^ $258; + $261 = _bitshift64Shl($243 | 0, $244 | 0, 23) | 0; + $262 = tempRet0; + $263 = _bitshift64Lshr($243 | 0, $244 | 0, 41) | 0; + $264 = tempRet0; + $265 = $261 | $263; + $266 = $262 | $264; + $267 = $259 ^ $265; + $268 = $260 ^ $266; + $269 = $145 ^ $171; + $270 = $146 ^ $174; + $271 = $243 & $269; + $272 = $244 & $270; + $273 = $271 ^ $171; + $274 = $272 ^ $174; + $275 = $i$29 | 1; + $276 = (31904 + ($275 << 3)) | 0; + $277 = $276; + $278 = $277; + $279 = HEAP32[$278 >> 2] | 0; + $280 = ($277 + 4) | 0; + $281 = $280; + $282 = HEAP32[$281 >> 2] | 0; + $283 = ($W + ($275 << 3)) | 0; + $284 = $283; + $285 = $284; + $286 = HEAP32[$285 >> 2] | 0; + $287 = ($284 + 4) | 0; + $288 = $287; + $289 = HEAP32[$288 >> 2] | 0; + $290 = _i64Add($279 | 0, $282 | 0, $170 | 0, $173 | 0) | 0; + $291 = tempRet0; + $292 = _i64Add($290 | 0, $291 | 0, $286 | 0, $289 | 0) | 0; + $293 = tempRet0; + $294 = _i64Add($292 | 0, $293 | 0, $273 | 0, $274 | 0) | 0; + $295 = tempRet0; + $296 = _i64Add($294 | 0, $295 | 0, $267 | 0, $268 | 0) | 0; + $297 = tempRet0; + $298 = _bitshift64Shl($245 | 0, $246 | 0, 36) | 0; + $299 = tempRet0; + $300 = _bitshift64Lshr($245 | 0, $246 | 0, 28) | 0; + $301 = tempRet0; + $302 = $298 | $300; + $303 = $299 | $301; + $304 = _bitshift64Shl($245 | 0, $246 | 0, 30) | 0; + $305 = tempRet0; + $306 = _bitshift64Lshr($245 | 0, $246 | 0, 34) | 0; + $307 = tempRet0; + $308 = $304 | $306; + $309 = $305 | $307; + $310 = $302 ^ $308; + $311 = $303 ^ $309; + $312 = _bitshift64Shl($245 | 0, $246 | 0, 25) | 0; + $313 = tempRet0; + $314 = _bitshift64Lshr($245 | 0, $246 | 0, 39) | 0; + $315 = tempRet0; + $316 = $312 | $314; + $317 = $313 | $315; + $318 = $310 ^ $316; + $319 = $311 ^ $317; + $320 = $245 & $203; + $321 = $246 & $204; + $322 = $245 | $203; + $323 = $246 | $204; + $324 = $322 & $228; + $325 = $323 & $230; + $326 = $324 | $320; + $327 = $325 | $321; + $328 = _i64Add($318 | 0, $319 | 0, $326 | 0, $327 | 0) | 0; + $329 = tempRet0; + $330 = _i64Add($296 | 0, $297 | 0, $234 | 0, $236 | 0) | 0; + $331 = tempRet0; + $332 = _i64Add($328 | 0, $329 | 0, $296 | 0, $297 | 0) | 0; + $333 = tempRet0; + $334 = _bitshift64Shl($330 | 0, $331 | 0, 50) | 0; + $335 = tempRet0; + $336 = _bitshift64Lshr($330 | 0, $331 | 0, 14) | 0; + $337 = tempRet0; + $338 = $334 | $336; + $339 = $335 | $337; + $340 = _bitshift64Shl($330 | 0, $331 | 0, 46) | 0; + $341 = tempRet0; + $342 = _bitshift64Lshr($330 | 0, $331 | 0, 18) | 0; + $343 = tempRet0; + $344 = $340 | $342; + $345 = $341 | $343; + $346 = $338 ^ $344; + $347 = $339 ^ $345; + $348 = _bitshift64Shl($330 | 0, $331 | 0, 23) | 0; + $349 = tempRet0; + $350 = _bitshift64Lshr($330 | 0, $331 | 0, 41) | 0; + $351 = tempRet0; + $352 = $348 | $350; + $353 = $349 | $351; + $354 = $346 ^ $352; + $355 = $347 ^ $353; + $356 = $243 ^ $145; + $357 = $244 ^ $146; + $358 = $330 & $356; + $359 = $331 & $357; + $360 = $358 ^ $145; + $361 = $359 ^ $146; + $362 = $i$29 | 2; + $363 = (31904 + ($362 << 3)) | 0; + $364 = $363; + $365 = $364; + $366 = HEAP32[$365 >> 2] | 0; + $367 = ($364 + 4) | 0; + $368 = $367; + $369 = HEAP32[$368 >> 2] | 0; + $370 = ($W + ($362 << 3)) | 0; + $371 = $370; + $372 = $371; + $373 = HEAP32[$372 >> 2] | 0; + $374 = ($371 + 4) | 0; + $375 = $374; + $376 = HEAP32[$375 >> 2] | 0; + $377 = _i64Add($366 | 0, $369 | 0, $171 | 0, $174 | 0) | 0; + $378 = tempRet0; + $379 = _i64Add($377 | 0, $378 | 0, $373 | 0, $376 | 0) | 0; + $380 = tempRet0; + $381 = _i64Add($379 | 0, $380 | 0, $360 | 0, $361 | 0) | 0; + $382 = tempRet0; + $383 = _i64Add($381 | 0, $382 | 0, $354 | 0, $355 | 0) | 0; + $384 = tempRet0; + $385 = _bitshift64Shl($332 | 0, $333 | 0, 36) | 0; + $386 = tempRet0; + $387 = _bitshift64Lshr($332 | 0, $333 | 0, 28) | 0; + $388 = tempRet0; + $389 = $385 | $387; + $390 = $386 | $388; + $391 = _bitshift64Shl($332 | 0, $333 | 0, 30) | 0; + $392 = tempRet0; + $393 = _bitshift64Lshr($332 | 0, $333 | 0, 34) | 0; + $394 = tempRet0; + $395 = $391 | $393; + $396 = $392 | $394; + $397 = $389 ^ $395; + $398 = $390 ^ $396; + $399 = _bitshift64Shl($332 | 0, $333 | 0, 25) | 0; + $400 = tempRet0; + $401 = _bitshift64Lshr($332 | 0, $333 | 0, 39) | 0; + $402 = tempRet0; + $403 = $399 | $401; + $404 = $400 | $402; + $405 = $397 ^ $403; + $406 = $398 ^ $404; + $407 = $332 & $245; + $408 = $333 & $246; + $409 = $332 | $245; + $410 = $333 | $246; + $411 = $409 & $203; + $412 = $410 & $204; + $413 = $411 | $407; + $414 = $412 | $408; + $415 = _i64Add($405 | 0, $406 | 0, $413 | 0, $414 | 0) | 0; + $416 = tempRet0; + $417 = _i64Add($383 | 0, $384 | 0, $228 | 0, $230 | 0) | 0; + $418 = tempRet0; + $419 = _i64Add($415 | 0, $416 | 0, $383 | 0, $384 | 0) | 0; + $420 = tempRet0; + $421 = _bitshift64Shl($417 | 0, $418 | 0, 50) | 0; + $422 = tempRet0; + $423 = _bitshift64Lshr($417 | 0, $418 | 0, 14) | 0; + $424 = tempRet0; + $425 = $421 | $423; + $426 = $422 | $424; + $427 = _bitshift64Shl($417 | 0, $418 | 0, 46) | 0; + $428 = tempRet0; + $429 = _bitshift64Lshr($417 | 0, $418 | 0, 18) | 0; + $430 = tempRet0; + $431 = $427 | $429; + $432 = $428 | $430; + $433 = $425 ^ $431; + $434 = $426 ^ $432; + $435 = _bitshift64Shl($417 | 0, $418 | 0, 23) | 0; + $436 = tempRet0; + $437 = _bitshift64Lshr($417 | 0, $418 | 0, 41) | 0; + $438 = tempRet0; + $439 = $435 | $437; + $440 = $436 | $438; + $441 = $433 ^ $439; + $442 = $434 ^ $440; + $443 = $330 ^ $243; + $444 = $331 ^ $244; + $445 = $417 & $443; + $446 = $418 & $444; + $447 = $445 ^ $243; + $448 = $446 ^ $244; + $449 = $i$29 | 3; + $450 = (31904 + ($449 << 3)) | 0; + $451 = $450; + $452 = $451; + $453 = HEAP32[$452 >> 2] | 0; + $454 = ($451 + 4) | 0; + $455 = $454; + $456 = HEAP32[$455 >> 2] | 0; + $457 = ($W + ($449 << 3)) | 0; + $458 = $457; + $459 = $458; + $460 = HEAP32[$459 >> 2] | 0; + $461 = ($458 + 4) | 0; + $462 = $461; + $463 = HEAP32[$462 >> 2] | 0; + $464 = _i64Add($453 | 0, $456 | 0, $145 | 0, $146 | 0) | 0; + $465 = tempRet0; + $466 = _i64Add($464 | 0, $465 | 0, $460 | 0, $463 | 0) | 0; + $467 = tempRet0; + $468 = _i64Add($466 | 0, $467 | 0, $447 | 0, $448 | 0) | 0; + $469 = tempRet0; + $470 = _i64Add($468 | 0, $469 | 0, $441 | 0, $442 | 0) | 0; + $471 = tempRet0; + $472 = _bitshift64Shl($419 | 0, $420 | 0, 36) | 0; + $473 = tempRet0; + $474 = _bitshift64Lshr($419 | 0, $420 | 0, 28) | 0; + $475 = tempRet0; + $476 = $472 | $474; + $477 = $473 | $475; + $478 = _bitshift64Shl($419 | 0, $420 | 0, 30) | 0; + $479 = tempRet0; + $480 = _bitshift64Lshr($419 | 0, $420 | 0, 34) | 0; + $481 = tempRet0; + $482 = $478 | $480; + $483 = $479 | $481; + $484 = $476 ^ $482; + $485 = $477 ^ $483; + $486 = _bitshift64Shl($419 | 0, $420 | 0, 25) | 0; + $487 = tempRet0; + $488 = _bitshift64Lshr($419 | 0, $420 | 0, 39) | 0; + $489 = tempRet0; + $490 = $486 | $488; + $491 = $487 | $489; + $492 = $484 ^ $490; + $493 = $485 ^ $491; + $494 = $419 & $332; + $495 = $420 & $333; + $496 = $419 | $332; + $497 = $420 | $333; + $498 = $496 & $245; + $499 = $497 & $246; + $500 = $498 | $494; + $501 = $499 | $495; + $502 = _i64Add($492 | 0, $493 | 0, $500 | 0, $501 | 0) | 0; + $503 = tempRet0; + $504 = _i64Add($470 | 0, $471 | 0, $203 | 0, $204 | 0) | 0; + $505 = tempRet0; + $506 = _i64Add($502 | 0, $503 | 0, $470 | 0, $471 | 0) | 0; + $507 = tempRet0; + $508 = _bitshift64Shl($504 | 0, $505 | 0, 50) | 0; + $509 = tempRet0; + $510 = _bitshift64Lshr($504 | 0, $505 | 0, 14) | 0; + $511 = tempRet0; + $512 = $508 | $510; + $513 = $509 | $511; + $514 = _bitshift64Shl($504 | 0, $505 | 0, 46) | 0; + $515 = tempRet0; + $516 = _bitshift64Lshr($504 | 0, $505 | 0, 18) | 0; + $517 = tempRet0; + $518 = $514 | $516; + $519 = $515 | $517; + $520 = $512 ^ $518; + $521 = $513 ^ $519; + $522 = _bitshift64Shl($504 | 0, $505 | 0, 23) | 0; + $523 = tempRet0; + $524 = _bitshift64Lshr($504 | 0, $505 | 0, 41) | 0; + $525 = tempRet0; + $526 = $522 | $524; + $527 = $523 | $525; + $528 = $520 ^ $526; + $529 = $521 ^ $527; + $530 = $417 ^ $330; + $531 = $418 ^ $331; + $532 = $504 & $530; + $533 = $505 & $531; + $534 = $532 ^ $330; + $535 = $533 ^ $331; + $536 = $i$29 | 4; + $537 = (31904 + ($536 << 3)) | 0; + $538 = $537; + $539 = $538; + $540 = HEAP32[$539 >> 2] | 0; + $541 = ($538 + 4) | 0; + $542 = $541; + $543 = HEAP32[$542 >> 2] | 0; + $544 = ($W + ($536 << 3)) | 0; + $545 = $544; + $546 = $545; + $547 = HEAP32[$546 >> 2] | 0; + $548 = ($545 + 4) | 0; + $549 = $548; + $550 = HEAP32[$549 >> 2] | 0; + $551 = _i64Add($540 | 0, $543 | 0, $243 | 0, $244 | 0) | 0; + $552 = tempRet0; + $553 = _i64Add($551 | 0, $552 | 0, $547 | 0, $550 | 0) | 0; + $554 = tempRet0; + $555 = _i64Add($553 | 0, $554 | 0, $534 | 0, $535 | 0) | 0; + $556 = tempRet0; + $557 = _i64Add($555 | 0, $556 | 0, $528 | 0, $529 | 0) | 0; + $558 = tempRet0; + $559 = _bitshift64Shl($506 | 0, $507 | 0, 36) | 0; + $560 = tempRet0; + $561 = _bitshift64Lshr($506 | 0, $507 | 0, 28) | 0; + $562 = tempRet0; + $563 = $559 | $561; + $564 = $560 | $562; + $565 = _bitshift64Shl($506 | 0, $507 | 0, 30) | 0; + $566 = tempRet0; + $567 = _bitshift64Lshr($506 | 0, $507 | 0, 34) | 0; + $568 = tempRet0; + $569 = $565 | $567; + $570 = $566 | $568; + $571 = $563 ^ $569; + $572 = $564 ^ $570; + $573 = _bitshift64Shl($506 | 0, $507 | 0, 25) | 0; + $574 = tempRet0; + $575 = _bitshift64Lshr($506 | 0, $507 | 0, 39) | 0; + $576 = tempRet0; + $577 = $573 | $575; + $578 = $574 | $576; + $579 = $571 ^ $577; + $580 = $572 ^ $578; + $581 = $506 & $419; + $582 = $507 & $420; + $583 = $506 | $419; + $584 = $507 | $420; + $585 = $583 & $332; + $586 = $584 & $333; + $587 = $585 | $581; + $588 = $586 | $582; + $589 = _i64Add($579 | 0, $580 | 0, $587 | 0, $588 | 0) | 0; + $590 = tempRet0; + $591 = _i64Add($557 | 0, $558 | 0, $245 | 0, $246 | 0) | 0; + $592 = tempRet0; + $593 = _i64Add($589 | 0, $590 | 0, $557 | 0, $558 | 0) | 0; + $594 = tempRet0; + $595 = _bitshift64Shl($591 | 0, $592 | 0, 50) | 0; + $596 = tempRet0; + $597 = _bitshift64Lshr($591 | 0, $592 | 0, 14) | 0; + $598 = tempRet0; + $599 = $595 | $597; + $600 = $596 | $598; + $601 = _bitshift64Shl($591 | 0, $592 | 0, 46) | 0; + $602 = tempRet0; + $603 = _bitshift64Lshr($591 | 0, $592 | 0, 18) | 0; + $604 = tempRet0; + $605 = $601 | $603; + $606 = $602 | $604; + $607 = $599 ^ $605; + $608 = $600 ^ $606; + $609 = _bitshift64Shl($591 | 0, $592 | 0, 23) | 0; + $610 = tempRet0; + $611 = _bitshift64Lshr($591 | 0, $592 | 0, 41) | 0; + $612 = tempRet0; + $613 = $609 | $611; + $614 = $610 | $612; + $615 = $607 ^ $613; + $616 = $608 ^ $614; + $617 = $504 ^ $417; + $618 = $505 ^ $418; + $619 = $591 & $617; + $620 = $592 & $618; + $621 = $619 ^ $417; + $622 = $620 ^ $418; + $623 = $i$29 | 5; + $624 = (31904 + ($623 << 3)) | 0; + $625 = $624; + $626 = $625; + $627 = HEAP32[$626 >> 2] | 0; + $628 = ($625 + 4) | 0; + $629 = $628; + $630 = HEAP32[$629 >> 2] | 0; + $631 = ($W + ($623 << 3)) | 0; + $632 = $631; + $633 = $632; + $634 = HEAP32[$633 >> 2] | 0; + $635 = ($632 + 4) | 0; + $636 = $635; + $637 = HEAP32[$636 >> 2] | 0; + $638 = _i64Add($627 | 0, $630 | 0, $330 | 0, $331 | 0) | 0; + $639 = tempRet0; + $640 = _i64Add($638 | 0, $639 | 0, $634 | 0, $637 | 0) | 0; + $641 = tempRet0; + $642 = _i64Add($640 | 0, $641 | 0, $621 | 0, $622 | 0) | 0; + $643 = tempRet0; + $644 = _i64Add($642 | 0, $643 | 0, $615 | 0, $616 | 0) | 0; + $645 = tempRet0; + $646 = _bitshift64Shl($593 | 0, $594 | 0, 36) | 0; + $647 = tempRet0; + $648 = _bitshift64Lshr($593 | 0, $594 | 0, 28) | 0; + $649 = tempRet0; + $650 = $646 | $648; + $651 = $647 | $649; + $652 = _bitshift64Shl($593 | 0, $594 | 0, 30) | 0; + $653 = tempRet0; + $654 = _bitshift64Lshr($593 | 0, $594 | 0, 34) | 0; + $655 = tempRet0; + $656 = $652 | $654; + $657 = $653 | $655; + $658 = $650 ^ $656; + $659 = $651 ^ $657; + $660 = _bitshift64Shl($593 | 0, $594 | 0, 25) | 0; + $661 = tempRet0; + $662 = _bitshift64Lshr($593 | 0, $594 | 0, 39) | 0; + $663 = tempRet0; + $664 = $660 | $662; + $665 = $661 | $663; + $666 = $658 ^ $664; + $667 = $659 ^ $665; + $668 = $593 & $506; + $669 = $594 & $507; + $670 = $593 | $506; + $671 = $594 | $507; + $672 = $670 & $419; + $673 = $671 & $420; + $674 = $672 | $668; + $675 = $673 | $669; + $676 = _i64Add($666 | 0, $667 | 0, $674 | 0, $675 | 0) | 0; + $677 = tempRet0; + $678 = _i64Add($644 | 0, $645 | 0, $332 | 0, $333 | 0) | 0; + $679 = tempRet0; + $680 = _i64Add($676 | 0, $677 | 0, $644 | 0, $645 | 0) | 0; + $681 = tempRet0; + $682 = _bitshift64Shl($678 | 0, $679 | 0, 50) | 0; + $683 = tempRet0; + $684 = _bitshift64Lshr($678 | 0, $679 | 0, 14) | 0; + $685 = tempRet0; + $686 = $682 | $684; + $687 = $683 | $685; + $688 = _bitshift64Shl($678 | 0, $679 | 0, 46) | 0; + $689 = tempRet0; + $690 = _bitshift64Lshr($678 | 0, $679 | 0, 18) | 0; + $691 = tempRet0; + $692 = $688 | $690; + $693 = $689 | $691; + $694 = $686 ^ $692; + $695 = $687 ^ $693; + $696 = _bitshift64Shl($678 | 0, $679 | 0, 23) | 0; + $697 = tempRet0; + $698 = _bitshift64Lshr($678 | 0, $679 | 0, 41) | 0; + $699 = tempRet0; + $700 = $696 | $698; + $701 = $697 | $699; + $702 = $694 ^ $700; + $703 = $695 ^ $701; + $704 = $591 ^ $504; + $705 = $592 ^ $505; + $706 = $678 & $704; + $707 = $679 & $705; + $708 = $706 ^ $504; + $709 = $707 ^ $505; + $710 = $i$29 | 6; + $711 = (31904 + ($710 << 3)) | 0; + $712 = $711; + $713 = $712; + $714 = HEAP32[$713 >> 2] | 0; + $715 = ($712 + 4) | 0; + $716 = $715; + $717 = HEAP32[$716 >> 2] | 0; + $718 = ($W + ($710 << 3)) | 0; + $719 = $718; + $720 = $719; + $721 = HEAP32[$720 >> 2] | 0; + $722 = ($719 + 4) | 0; + $723 = $722; + $724 = HEAP32[$723 >> 2] | 0; + $725 = _i64Add($721 | 0, $724 | 0, $714 | 0, $717 | 0) | 0; + $726 = tempRet0; + $727 = _i64Add($725 | 0, $726 | 0, $417 | 0, $418 | 0) | 0; + $728 = tempRet0; + $729 = _i64Add($727 | 0, $728 | 0, $708 | 0, $709 | 0) | 0; + $730 = tempRet0; + $731 = _i64Add($729 | 0, $730 | 0, $702 | 0, $703 | 0) | 0; + $732 = tempRet0; + $733 = _bitshift64Shl($680 | 0, $681 | 0, 36) | 0; + $734 = tempRet0; + $735 = _bitshift64Lshr($680 | 0, $681 | 0, 28) | 0; + $736 = tempRet0; + $737 = $733 | $735; + $738 = $734 | $736; + $739 = _bitshift64Shl($680 | 0, $681 | 0, 30) | 0; + $740 = tempRet0; + $741 = _bitshift64Lshr($680 | 0, $681 | 0, 34) | 0; + $742 = tempRet0; + $743 = $739 | $741; + $744 = $740 | $742; + $745 = $737 ^ $743; + $746 = $738 ^ $744; + $747 = _bitshift64Shl($680 | 0, $681 | 0, 25) | 0; + $748 = tempRet0; + $749 = _bitshift64Lshr($680 | 0, $681 | 0, 39) | 0; + $750 = tempRet0; + $751 = $747 | $749; + $752 = $748 | $750; + $753 = $745 ^ $751; + $754 = $746 ^ $752; + $755 = $680 & $593; + $756 = $681 & $594; + $757 = $680 | $593; + $758 = $681 | $594; + $759 = $757 & $506; + $760 = $758 & $507; + $761 = $759 | $755; + $762 = $760 | $756; + $763 = _i64Add($753 | 0, $754 | 0, $761 | 0, $762 | 0) | 0; + $764 = tempRet0; + $765 = _i64Add($731 | 0, $732 | 0, $419 | 0, $420 | 0) | 0; + $766 = tempRet0; + $767 = _i64Add($763 | 0, $764 | 0, $731 | 0, $732 | 0) | 0; + $768 = tempRet0; + $769 = _bitshift64Shl($765 | 0, $766 | 0, 50) | 0; + $770 = tempRet0; + $771 = _bitshift64Lshr($765 | 0, $766 | 0, 14) | 0; + $772 = tempRet0; + $773 = $769 | $771; + $774 = $770 | $772; + $775 = _bitshift64Shl($765 | 0, $766 | 0, 46) | 0; + $776 = tempRet0; + $777 = _bitshift64Lshr($765 | 0, $766 | 0, 18) | 0; + $778 = tempRet0; + $779 = $775 | $777; + $780 = $776 | $778; + $781 = $773 ^ $779; + $782 = $774 ^ $780; + $783 = _bitshift64Shl($765 | 0, $766 | 0, 23) | 0; + $784 = tempRet0; + $785 = _bitshift64Lshr($765 | 0, $766 | 0, 41) | 0; + $786 = tempRet0; + $787 = $783 | $785; + $788 = $784 | $786; + $789 = $781 ^ $787; + $790 = $782 ^ $788; + $791 = $678 ^ $591; + $792 = $679 ^ $592; + $793 = $765 & $791; + $794 = $766 & $792; + $795 = $793 ^ $591; + $796 = $794 ^ $592; + $797 = $i$29 | 7; + $798 = (31904 + ($797 << 3)) | 0; + $799 = $798; + $800 = $799; + $801 = HEAP32[$800 >> 2] | 0; + $802 = ($799 + 4) | 0; + $803 = $802; + $804 = HEAP32[$803 >> 2] | 0; + $805 = ($W + ($797 << 3)) | 0; + $806 = $805; + $807 = $806; + $808 = HEAP32[$807 >> 2] | 0; + $809 = ($806 + 4) | 0; + $810 = $809; + $811 = HEAP32[$810 >> 2] | 0; + $812 = _i64Add($808 | 0, $811 | 0, $801 | 0, $804 | 0) | 0; + $813 = tempRet0; + $814 = _i64Add($812 | 0, $813 | 0, $504 | 0, $505 | 0) | 0; + $815 = tempRet0; + $816 = _i64Add($814 | 0, $815 | 0, $795 | 0, $796 | 0) | 0; + $817 = tempRet0; + $818 = _i64Add($816 | 0, $817 | 0, $789 | 0, $790 | 0) | 0; + $819 = tempRet0; + $820 = _bitshift64Shl($767 | 0, $768 | 0, 36) | 0; + $821 = tempRet0; + $822 = _bitshift64Lshr($767 | 0, $768 | 0, 28) | 0; + $823 = tempRet0; + $824 = $820 | $822; + $825 = $821 | $823; + $826 = _bitshift64Shl($767 | 0, $768 | 0, 30) | 0; + $827 = tempRet0; + $828 = _bitshift64Lshr($767 | 0, $768 | 0, 34) | 0; + $829 = tempRet0; + $830 = $826 | $828; + $831 = $827 | $829; + $832 = $824 ^ $830; + $833 = $825 ^ $831; + $834 = _bitshift64Shl($767 | 0, $768 | 0, 25) | 0; + $835 = tempRet0; + $836 = _bitshift64Lshr($767 | 0, $768 | 0, 39) | 0; + $837 = tempRet0; + $838 = $834 | $836; + $839 = $835 | $837; + $840 = $832 ^ $838; + $841 = $833 ^ $839; + $842 = $767 & $680; + $843 = $768 & $681; + $844 = $767 | $680; + $845 = $768 | $681; + $846 = $844 & $593; + $847 = $845 & $594; + $848 = $846 | $842; + $849 = $847 | $843; + $850 = _i64Add($840 | 0, $841 | 0, $848 | 0, $849 | 0) | 0; + $851 = tempRet0; + $852 = _i64Add($818 | 0, $819 | 0, $506 | 0, $507 | 0) | 0; + $853 = tempRet0; + $854 = _i64Add($850 | 0, $851 | 0, $818 | 0, $819 | 0) | 0; + $855 = tempRet0; + $856 = ($i$29 + 8) | 0; + $857 = ($856 | 0) < 80; + if ($857) { + $145 = $852; + $146 = $853; + $170 = $678; + $171 = $765; + $173 = $679; + $174 = $766; + $193 = $591; + $194 = $592; + $203 = $854; + $204 = $855; + $228 = $767; + $230 = $768; + $234 = $680; + $236 = $681; + $241 = $593; + $242 = $594; + $i$29 = $856; + } else { + break; + } + } + $858 = $r; + $859 = $858; + $860 = HEAP32[$859 >> 2] | 0; + $861 = ($858 + 4) | 0; + $862 = $861; + $863 = HEAP32[$862 >> 2] | 0; + $864 = _i64Add($860 | 0, $863 | 0, $854 | 0, $855 | 0) | 0; + $865 = tempRet0; + $866 = $r; + $867 = $866; + HEAP32[$867 >> 2] = $864; + $868 = ($866 + 4) | 0; + $869 = $868; + HEAP32[$869 >> 2] = $865; + $870 = $96; + $871 = $870; + $872 = HEAP32[$871 >> 2] | 0; + $873 = ($870 + 4) | 0; + $874 = $873; + $875 = HEAP32[$874 >> 2] | 0; + $876 = _i64Add($872 | 0, $875 | 0, $767 | 0, $768 | 0) | 0; + $877 = tempRet0; + $878 = $96; + $879 = $878; + HEAP32[$879 >> 2] = $876; + $880 = ($878 + 4) | 0; + $881 = $880; + HEAP32[$881 >> 2] = $877; + $882 = $103; + $883 = $882; + $884 = HEAP32[$883 >> 2] | 0; + $885 = ($882 + 4) | 0; + $886 = $885; + $887 = HEAP32[$886 >> 2] | 0; + $888 = _i64Add($884 | 0, $887 | 0, $680 | 0, $681 | 0) | 0; + $889 = tempRet0; + $890 = $103; + $891 = $890; + HEAP32[$891 >> 2] = $888; + $892 = ($890 + 4) | 0; + $893 = $892; + HEAP32[$893 >> 2] = $889; + $894 = $110; + $895 = $894; + $896 = HEAP32[$895 >> 2] | 0; + $897 = ($894 + 4) | 0; + $898 = $897; + $899 = HEAP32[$898 >> 2] | 0; + $900 = _i64Add($896 | 0, $899 | 0, $593 | 0, $594 | 0) | 0; + $901 = tempRet0; + $902 = $110; + $903 = $902; + HEAP32[$903 >> 2] = $900; + $904 = ($902 + 4) | 0; + $905 = $904; + HEAP32[$905 >> 2] = $901; + $906 = $117; + $907 = $906; + $908 = HEAP32[$907 >> 2] | 0; + $909 = ($906 + 4) | 0; + $910 = $909; + $911 = HEAP32[$910 >> 2] | 0; + $912 = _i64Add($908 | 0, $911 | 0, $852 | 0, $853 | 0) | 0; + $913 = tempRet0; + $914 = $117; + $915 = $914; + HEAP32[$915 >> 2] = $912; + $916 = ($914 + 4) | 0; + $917 = $916; + HEAP32[$917 >> 2] = $913; + $918 = $124; + $919 = $918; + $920 = HEAP32[$919 >> 2] | 0; + $921 = ($918 + 4) | 0; + $922 = $921; + $923 = HEAP32[$922 >> 2] | 0; + $924 = _i64Add($920 | 0, $923 | 0, $765 | 0, $766 | 0) | 0; + $925 = tempRet0; + $926 = $124; + $927 = $926; + HEAP32[$927 >> 2] = $924; + $928 = ($926 + 4) | 0; + $929 = $928; + HEAP32[$929 >> 2] = $925; + $930 = $131; + $931 = $930; + $932 = HEAP32[$931 >> 2] | 0; + $933 = ($930 + 4) | 0; + $934 = $933; + $935 = HEAP32[$934 >> 2] | 0; + $936 = _i64Add($932 | 0, $935 | 0, $678 | 0, $679 | 0) | 0; + $937 = tempRet0; + $938 = $131; + $939 = $938; + HEAP32[$939 >> 2] = $936; + $940 = ($938 + 4) | 0; + $941 = $940; + HEAP32[$941 >> 2] = $937; + $942 = $138; + $943 = $942; + $944 = HEAP32[$943 >> 2] | 0; + $945 = ($942 + 4) | 0; + $946 = $945; + $947 = HEAP32[$946 >> 2] | 0; + $948 = _i64Add($944 | 0, $947 | 0, $591 | 0, $592 | 0) | 0; + $949 = tempRet0; + $950 = $138; + $951 = $950; + HEAP32[$951 >> 2] = $948; + $952 = ($950 + 4) | 0; + $953 = $952; + HEAP32[$953 >> 2] = $949; + STACKTOP = sp; + return; + } + function _sha384_close($cc, $dst, $rnum) { + $cc = $cc | 0; + $dst = $dst | 0; + $rnum = $rnum | 0; + var label = 0, + sp = 0; + sp = STACKTOP; + _sha384_addbits_and_close($cc, 0, 0, $dst, $rnum); + STACKTOP = sp; + return; + } + function _sha384_addbits_and_close($cc, $ub, $n, $dst, $rnum) { + $cc = $cc | 0; + $ub = $ub | 0; + $n = $n | 0; + $dst = $dst | 0; + $rnum = $rnum | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0; + var $27 = 0, + $28 = 0, + $29 = 0, + $3 = 0, + $30 = 0, + $31 = 0, + $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $37 = 0, + $38 = 0, + $39 = 0, + $4 = 0, + $40 = 0, + $41 = 0, + $42 = 0, + $43 = 0, + $44 = 0; + var $45 = 0, + $46 = 0, + $47 = 0, + $48 = 0, + $49 = 0, + $5 = 0, + $50 = 0, + $51 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + $exitcond = 0, + $u$01 = 0, + dest = 0, + label = 0, + sp = 0, + stop = 0; + sp = STACKTOP; + $0 = ($cc + 192) | 0; + $1 = $0; + $2 = $1; + $3 = HEAP32[$2 >> 2] | 0; + $4 = ($1 + 4) | 0; + $5 = $4; + $6 = HEAP32[$5 >> 2] | 0; + $7 = $3 & 127; + $8 = 128 >>> $n; + $9 = (0 - $8) | 0; + $10 = $9 & $ub; + $11 = $10 | $8; + $12 = $11 & 255; + $13 = ($7 + 1) | 0; + $14 = ($cc + $7) | 0; + HEAP8[$14 >> 0] = $12; + $15 = $13 >>> 0 > 112; + $16 = ($cc + $13) | 0; + if ($15) { + $17 = $7 ^ 127; + _memset($16 | 0, 0, $17 | 0) | 0; + $18 = ($cc + 128) | 0; + _sha3_round($cc, $18); + dest = ($cc + 0) | 0; + stop = (dest + 112) | 0; + do { + HEAP8[dest >> 0] = 0 | 0; + dest = (dest + 1) | 0; + } while ((dest | 0) < (stop | 0)); + } else { + $19 = (111 - $7) | 0; + _memset($16 | 0, 0, $19 | 0) | 0; + } + $20 = ($cc + 112) | 0; + $21 = $0; + $22 = $21; + $23 = HEAP32[$22 >> 2] | 0; + $24 = ($21 + 4) | 0; + $25 = $24; + $26 = HEAP32[$25 >> 2] | 0; + $27 = _bitshift64Lshr($23 | 0, $26 | 0, 61) | 0; + $28 = tempRet0; + _sph_enc64be_aligned($20, $27, $28); + $29 = ($cc + 120) | 0; + $30 = $0; + $31 = $30; + $32 = HEAP32[$31 >> 2] | 0; + $33 = ($30 + 4) | 0; + $34 = $33; + $35 = HEAP32[$34 >> 2] | 0; + $36 = _bitshift64Shl($32 | 0, $35 | 0, 3) | 0; + $37 = tempRet0; + $38 = _i64Add($36 | 0, $37 | 0, $n | 0, 0) | 0; + $39 = tempRet0; + _sph_enc64be_aligned($29, $38, $39); + $40 = ($cc + 128) | 0; + _sha3_round($cc, $40); + $41 = ($rnum | 0) == 0; + if ($41) { + STACKTOP = sp; + return; + } else { + $u$01 = 0; + } + while (1) { + $42 = $u$01 << 3; + $43 = ($dst + $42) | 0; + $44 = ($40 + ($u$01 << 3)) | 0; + $45 = $44; + $46 = $45; + $47 = HEAP32[$46 >> 2] | 0; + $48 = ($45 + 4) | 0; + $49 = $48; + $50 = HEAP32[$49 >> 2] | 0; + _sph_enc64be($43, $47, $50); + $51 = ($u$01 + 1) | 0; + $exitcond = ($51 | 0) == ($rnum | 0); + if ($exitcond) { + break; + } else { + $u$01 = $51; + } + } + STACKTOP = sp; + return; + } + function _sph_dec64be_aligned($src) { + $src = $src | 0; + var $0 = 0, + $1 = 0, + $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0; + var $27 = 0, + $28 = 0, + $29 = 0, + $3 = 0, + $30 = 0, + $31 = 0, + $32 = 0, + $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $37 = 0, + $38 = 0, + $39 = 0, + $4 = 0, + $40 = 0, + $41 = 0, + $42 = 0, + $43 = 0, + $44 = 0; + var $45 = 0, + $46 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = HEAP8[$src >> 0] | 0; + $1 = $0 & 255; + $2 = _bitshift64Shl($1 | 0, 0, 56) | 0; + $3 = tempRet0; + $4 = ($src + 1) | 0; + $5 = HEAP8[$4 >> 0] | 0; + $6 = $5 & 255; + $7 = _bitshift64Shl($6 | 0, 0, 48) | 0; + $8 = tempRet0; + $9 = $7 | $2; + $10 = $8 | $3; + $11 = ($src + 2) | 0; + $12 = HEAP8[$11 >> 0] | 0; + $13 = $12 & 255; + $14 = _bitshift64Shl($13 | 0, 0, 40) | 0; + $15 = tempRet0; + $16 = $9 | $14; + $17 = $10 | $15; + $18 = ($src + 3) | 0; + $19 = HEAP8[$18 >> 0] | 0; + $20 = $19 & 255; + $21 = $17 | $20; + $22 = ($src + 4) | 0; + $23 = HEAP8[$22 >> 0] | 0; + $24 = $23 & 255; + $25 = _bitshift64Shl($24 | 0, 0, 24) | 0; + $26 = tempRet0; + $27 = $16 | $25; + $28 = $21 | $26; + $29 = ($src + 5) | 0; + $30 = HEAP8[$29 >> 0] | 0; + $31 = $30 & 255; + $32 = _bitshift64Shl($31 | 0, 0, 16) | 0; + $33 = tempRet0; + $34 = $27 | $32; + $35 = $28 | $33; + $36 = ($src + 6) | 0; + $37 = HEAP8[$36 >> 0] | 0; + $38 = $37 & 255; + $39 = _bitshift64Shl($38 | 0, 0, 8) | 0; + $40 = tempRet0; + $41 = $34 | $39; + $42 = $35 | $40; + $43 = ($src + 7) | 0; + $44 = HEAP8[$43 >> 0] | 0; + $45 = $44 & 255; + $46 = $41 | $45; + tempRet0 = $42; + STACKTOP = sp; + return $46 | 0; + } + function _sph_enc64be_aligned($dst, $0, $1) { + $dst = $dst | 0; + $0 = $0 | 0; + $1 = $1 | 0; + var $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0, + $27 = 0, + $28 = 0; + var $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $2 = _bitshift64Lshr($0 | 0, $1 | 0, 56) | 0; + $3 = tempRet0; + $4 = $2 & 255; + HEAP8[$dst >> 0] = $4; + $5 = _bitshift64Lshr($0 | 0, $1 | 0, 48) | 0; + $6 = tempRet0; + $7 = $5 & 255; + $8 = ($dst + 1) | 0; + HEAP8[$8 >> 0] = $7; + $9 = _bitshift64Lshr($0 | 0, $1 | 0, 40) | 0; + $10 = tempRet0; + $11 = $9 & 255; + $12 = ($dst + 2) | 0; + HEAP8[$12 >> 0] = $11; + $13 = $1 & 255; + $14 = ($dst + 3) | 0; + HEAP8[$14 >> 0] = $13; + $15 = _bitshift64Lshr($0 | 0, $1 | 0, 24) | 0; + $16 = tempRet0; + $17 = $15 & 255; + $18 = ($dst + 4) | 0; + HEAP8[$18 >> 0] = $17; + $19 = _bitshift64Lshr($0 | 0, $1 | 0, 16) | 0; + $20 = tempRet0; + $21 = $19 & 255; + $22 = ($dst + 5) | 0; + HEAP8[$22 >> 0] = $21; + $23 = _bitshift64Lshr($0 | 0, $1 | 0, 8) | 0; + $24 = tempRet0; + $25 = $23 & 255; + $26 = ($dst + 6) | 0; + HEAP8[$26 >> 0] = $25; + $27 = $0 & 255; + $28 = ($dst + 7) | 0; + HEAP8[$28 >> 0] = $27; + STACKTOP = sp; + return; + } + function _sph_enc64be($dst, $0, $1) { + $dst = $dst | 0; + $0 = $0 | 0; + $1 = $1 | 0; + var $10 = 0, + $11 = 0, + $12 = 0, + $13 = 0, + $14 = 0, + $15 = 0, + $16 = 0, + $17 = 0, + $18 = 0, + $19 = 0, + $2 = 0, + $20 = 0, + $21 = 0, + $22 = 0, + $23 = 0, + $24 = 0, + $25 = 0, + $26 = 0, + $27 = 0, + $28 = 0; + var $3 = 0, + $4 = 0, + $5 = 0, + $6 = 0, + $7 = 0, + $8 = 0, + $9 = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $2 = _bitshift64Lshr($0 | 0, $1 | 0, 56) | 0; + $3 = tempRet0; + $4 = $2 & 255; + HEAP8[$dst >> 0] = $4; + $5 = _bitshift64Lshr($0 | 0, $1 | 0, 48) | 0; + $6 = tempRet0; + $7 = $5 & 255; + $8 = ($dst + 1) | 0; + HEAP8[$8 >> 0] = $7; + $9 = _bitshift64Lshr($0 | 0, $1 | 0, 40) | 0; + $10 = tempRet0; + $11 = $9 & 255; + $12 = ($dst + 2) | 0; + HEAP8[$12 >> 0] = $11; + $13 = $1 & 255; + $14 = ($dst + 3) | 0; + HEAP8[$14 >> 0] = $13; + $15 = _bitshift64Lshr($0 | 0, $1 | 0, 24) | 0; + $16 = tempRet0; + $17 = $15 & 255; + $18 = ($dst + 4) | 0; + HEAP8[$18 >> 0] = $17; + $19 = _bitshift64Lshr($0 | 0, $1 | 0, 16) | 0; + $20 = tempRet0; + $21 = $19 & 255; + $22 = ($dst + 5) | 0; + HEAP8[$22 >> 0] = $21; + $23 = _bitshift64Lshr($0 | 0, $1 | 0, 8) | 0; + $24 = tempRet0; + $25 = $23 & 255; + $26 = ($dst + 6) | 0; + HEAP8[$26 >> 0] = $25; + $27 = $0 & 255; + $28 = ($dst + 7) | 0; + HEAP8[$28 >> 0] = $27; + STACKTOP = sp; + return; + } + function _malloc($bytes) { + $bytes = $bytes | 0; + var $$pre = 0, + $$pre$i = 0, + $$pre$i$i = 0, + $$pre$i23$i = 0, + $$pre$i25 = 0, + $$pre$phi$i$iZ2D = 0, + $$pre$phi$i24$iZ2D = 0, + $$pre$phi$i26Z2D = 0, + $$pre$phi$iZ2D = 0, + $$pre$phi59$i$iZ2D = 0, + $$pre$phiZ2D = 0, + $$pre105 = 0, + $$pre58$i$i = 0, + $$rsize$0$i = 0, + $$rsize$3$i = 0, + $$sum = 0, + $$sum$i$i = 0, + $$sum$i$i$i = 0, + $$sum$i12$i = 0, + $$sum$i13$i = 0; + var $$sum$i16$i = 0, + $$sum$i19$i = 0, + $$sum$i2338 = 0, + $$sum$i32 = 0, + $$sum$i39 = 0, + $$sum1 = 0, + $$sum1$i = 0, + $$sum1$i$i = 0, + $$sum1$i14$i = 0, + $$sum1$i20$i = 0, + $$sum1$i24 = 0, + $$sum10 = 0, + $$sum10$i = 0, + $$sum10$i$i = 0, + $$sum10$pre$i$i = 0, + $$sum102$i = 0, + $$sum103$i = 0, + $$sum104$i = 0, + $$sum105$i = 0, + $$sum106$i = 0; + var $$sum107$i = 0, + $$sum108$i = 0, + $$sum109$i = 0, + $$sum11$i = 0, + $$sum11$i$i = 0, + $$sum11$i22$i = 0, + $$sum110$i = 0, + $$sum111$i = 0, + $$sum1112 = 0, + $$sum112$i = 0, + $$sum113$i = 0, + $$sum114$i = 0, + $$sum115$i = 0, + $$sum12$i = 0, + $$sum12$i$i = 0, + $$sum13$i = 0, + $$sum13$i$i = 0, + $$sum14$i$i = 0, + $$sum14$pre$i = 0, + $$sum15$i = 0; + var $$sum15$i$i = 0, + $$sum16$i = 0, + $$sum16$i$i = 0, + $$sum17$i = 0, + $$sum17$i$i = 0, + $$sum18$i = 0, + $$sum1819$i$i = 0, + $$sum2 = 0, + $$sum2$i = 0, + $$sum2$i$i = 0, + $$sum2$i$i$i = 0, + $$sum2$i15$i = 0, + $$sum2$i17$i = 0, + $$sum2$i21$i = 0, + $$sum2$pre$i = 0, + $$sum20$i$i = 0, + $$sum21$i$i = 0, + $$sum22$i$i = 0, + $$sum23$i$i = 0, + $$sum24$i$i = 0; + var $$sum25$i$i = 0, + $$sum26$pre$i$i = 0, + $$sum27$i$i = 0, + $$sum28$i$i = 0, + $$sum29$i$i = 0, + $$sum3$i = 0, + $$sum3$i$i = 0, + $$sum3$i27 = 0, + $$sum30$i$i = 0, + $$sum3132$i$i = 0, + $$sum34$i$i = 0, + $$sum3536$i$i = 0, + $$sum3738$i$i = 0, + $$sum39$i$i = 0, + $$sum4 = 0, + $$sum4$i = 0, + $$sum4$i28 = 0, + $$sum40$i$i = 0, + $$sum41$i$i = 0, + $$sum42$i$i = 0; + var $$sum5$i = 0, + $$sum5$i$i = 0, + $$sum56 = 0, + $$sum6$i = 0, + $$sum67$i$i = 0, + $$sum7$i = 0, + $$sum8$i = 0, + $$sum8$pre = 0, + $$sum9 = 0, + $$sum9$i = 0, + $$sum9$i$i = 0, + $$tsize$1$i = 0, + $$v$0$i = 0, + $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $1000 = 0, + $1001 = 0, + $1002 = 0; + var $1003 = 0, + $1004 = 0, + $1005 = 0, + $1006 = 0, + $1007 = 0, + $1008 = 0, + $1009 = 0, + $101 = 0, + $1010 = 0, + $1011 = 0, + $1012 = 0, + $1013 = 0, + $1014 = 0, + $1015 = 0, + $1016 = 0, + $1017 = 0, + $1018 = 0, + $1019 = 0, + $102 = 0, + $1020 = 0; + var $1021 = 0, + $1022 = 0, + $1023 = 0, + $1024 = 0, + $1025 = 0, + $1026 = 0, + $1027 = 0, + $1028 = 0, + $1029 = 0, + $103 = 0, + $1030 = 0, + $1031 = 0, + $1032 = 0, + $1033 = 0, + $1034 = 0, + $1035 = 0, + $1036 = 0, + $1037 = 0, + $1038 = 0, + $1039 = 0; + var $104 = 0, + $1040 = 0, + $1041 = 0, + $1042 = 0, + $1043 = 0, + $1044 = 0, + $1045 = 0, + $1046 = 0, + $1047 = 0, + $1048 = 0, + $1049 = 0, + $105 = 0, + $1050 = 0, + $1051 = 0, + $1052 = 0, + $1053 = 0, + $1054 = 0, + $1055 = 0, + $1056 = 0, + $1057 = 0; + var $1058 = 0, + $1059 = 0, + $106 = 0, + $1060 = 0, + $1061 = 0, + $1062 = 0, + $1063 = 0, + $1064 = 0, + $1065 = 0, + $1066 = 0, + $1067 = 0, + $1068 = 0, + $1069 = 0, + $107 = 0, + $1070 = 0, + $1071 = 0, + $1072 = 0, + $1073 = 0, + $1074 = 0, + $108 = 0; + var $109 = 0, + $11 = 0, + $110 = 0, + $111 = 0, + $112 = 0, + $113 = 0, + $114 = 0, + $115 = 0, + $116 = 0, + $117 = 0, + $118 = 0, + $119 = 0, + $12 = 0, + $120 = 0, + $121 = 0, + $122 = 0, + $123 = 0, + $124 = 0, + $125 = 0, + $126 = 0; + var $127 = 0, + $128 = 0, + $129 = 0, + $13 = 0, + $130 = 0, + $131 = 0, + $132 = 0, + $133 = 0, + $134 = 0, + $135 = 0, + $136 = 0, + $137 = 0, + $138 = 0, + $139 = 0, + $14 = 0, + $140 = 0, + $141 = 0, + $142 = 0, + $143 = 0, + $144 = 0; + var $145 = 0, + $146 = 0, + $147 = 0, + $148 = 0, + $149 = 0, + $15 = 0, + $150 = 0, + $151 = 0, + $152 = 0, + $153 = 0, + $154 = 0, + $155 = 0, + $156 = 0, + $157 = 0, + $158 = 0, + $159 = 0, + $16 = 0, + $160 = 0, + $161 = 0, + $162 = 0; + var $163 = 0, + $164 = 0, + $165 = 0, + $166 = 0, + $167 = 0, + $168 = 0, + $169 = 0, + $17 = 0, + $170 = 0, + $171 = 0, + $172 = 0, + $173 = 0, + $174 = 0, + $175 = 0, + $176 = 0, + $177 = 0, + $178 = 0, + $179 = 0, + $18 = 0, + $180 = 0; + var $181 = 0, + $182 = 0, + $183 = 0, + $184 = 0, + $185 = 0, + $186 = 0, + $187 = 0, + $188 = 0, + $189 = 0, + $19 = 0, + $190 = 0, + $191 = 0, + $192 = 0, + $193 = 0, + $194 = 0, + $195 = 0, + $196 = 0, + $197 = 0, + $198 = 0, + $199 = 0; + var $2 = 0, + $20 = 0, + $200 = 0, + $201 = 0, + $202 = 0, + $203 = 0, + $204 = 0, + $205 = 0, + $206 = 0, + $207 = 0, + $208 = 0, + $209 = 0, + $21 = 0, + $210 = 0, + $211 = 0, + $212 = 0, + $213 = 0, + $214 = 0, + $215 = 0, + $216 = 0; + var $217 = 0, + $218 = 0, + $219 = 0, + $22 = 0, + $220 = 0, + $221 = 0, + $222 = 0, + $223 = 0, + $224 = 0, + $225 = 0, + $226 = 0, + $227 = 0, + $228 = 0, + $229 = 0, + $23 = 0, + $230 = 0, + $231 = 0, + $232 = 0, + $233 = 0, + $234 = 0; + var $235 = 0, + $236 = 0, + $237 = 0, + $238 = 0, + $239 = 0, + $24 = 0, + $240 = 0, + $241 = 0, + $242 = 0, + $243 = 0, + $244 = 0, + $245 = 0, + $246 = 0, + $247 = 0, + $248 = 0, + $249 = 0, + $25 = 0, + $250 = 0, + $251 = 0, + $252 = 0; + var $253 = 0, + $254 = 0, + $255 = 0, + $256 = 0, + $257 = 0, + $258 = 0, + $259 = 0, + $26 = 0, + $260 = 0, + $261 = 0, + $262 = 0, + $263 = 0, + $264 = 0, + $265 = 0, + $266 = 0, + $267 = 0, + $268 = 0, + $269 = 0, + $27 = 0, + $270 = 0; + var $271 = 0, + $272 = 0, + $273 = 0, + $274 = 0, + $275 = 0, + $276 = 0, + $277 = 0, + $278 = 0, + $279 = 0, + $28 = 0, + $280 = 0, + $281 = 0, + $282 = 0, + $283 = 0, + $284 = 0, + $285 = 0, + $286 = 0, + $287 = 0, + $288 = 0, + $289 = 0; + var $29 = 0, + $290 = 0, + $291 = 0, + $292 = 0, + $293 = 0, + $294 = 0, + $295 = 0, + $296 = 0, + $297 = 0, + $298 = 0, + $299 = 0, + $3 = 0, + $30 = 0, + $300 = 0, + $301 = 0, + $302 = 0, + $303 = 0, + $304 = 0, + $305 = 0, + $306 = 0; + var $307 = 0, + $308 = 0, + $309 = 0, + $31 = 0, + $310 = 0, + $311 = 0, + $312 = 0, + $313 = 0, + $314 = 0, + $315 = 0, + $316 = 0, + $317 = 0, + $318 = 0, + $319 = 0, + $32 = 0, + $320 = 0, + $321 = 0, + $322 = 0, + $323 = 0, + $324 = 0; + var $325 = 0, + $326 = 0, + $327 = 0, + $328 = 0, + $329 = 0, + $33 = 0, + $330 = 0, + $331 = 0, + $332 = 0, + $333 = 0, + $334 = 0, + $335 = 0, + $336 = 0, + $337 = 0, + $338 = 0, + $339 = 0, + $34 = 0, + $340 = 0, + $341 = 0, + $342 = 0; + var $343 = 0, + $344 = 0, + $345 = 0, + $346 = 0, + $347 = 0, + $348 = 0, + $349 = 0, + $35 = 0, + $350 = 0, + $351 = 0, + $352 = 0, + $353 = 0, + $354 = 0, + $355 = 0, + $356 = 0, + $357 = 0, + $358 = 0, + $359 = 0, + $36 = 0, + $360 = 0; + var $361 = 0, + $362 = 0, + $363 = 0, + $364 = 0, + $365 = 0, + $366 = 0, + $367 = 0, + $368 = 0, + $369 = 0, + $37 = 0, + $370 = 0, + $371 = 0, + $372 = 0, + $373 = 0, + $374 = 0, + $375 = 0, + $376 = 0, + $377 = 0, + $378 = 0, + $379 = 0; + var $38 = 0, + $380 = 0, + $381 = 0, + $382 = 0, + $383 = 0, + $384 = 0, + $385 = 0, + $386 = 0, + $387 = 0, + $388 = 0, + $389 = 0, + $39 = 0, + $390 = 0, + $391 = 0, + $392 = 0, + $393 = 0, + $394 = 0, + $395 = 0, + $396 = 0, + $397 = 0; + var $398 = 0, + $399 = 0, + $4 = 0, + $40 = 0, + $400 = 0, + $401 = 0, + $402 = 0, + $403 = 0, + $404 = 0, + $405 = 0, + $406 = 0, + $407 = 0, + $408 = 0, + $409 = 0, + $41 = 0, + $410 = 0, + $411 = 0, + $412 = 0, + $413 = 0, + $414 = 0; + var $415 = 0, + $416 = 0, + $417 = 0, + $418 = 0, + $419 = 0, + $42 = 0, + $420 = 0, + $421 = 0, + $422 = 0, + $423 = 0, + $424 = 0, + $425 = 0, + $426 = 0, + $427 = 0, + $428 = 0, + $429 = 0, + $43 = 0, + $430 = 0, + $431 = 0, + $432 = 0; + var $433 = 0, + $434 = 0, + $435 = 0, + $436 = 0, + $437 = 0, + $438 = 0, + $439 = 0, + $44 = 0, + $440 = 0, + $441 = 0, + $442 = 0, + $443 = 0, + $444 = 0, + $445 = 0, + $446 = 0, + $447 = 0, + $448 = 0, + $449 = 0, + $45 = 0, + $450 = 0; + var $451 = 0, + $452 = 0, + $453 = 0, + $454 = 0, + $455 = 0, + $456 = 0, + $457 = 0, + $458 = 0, + $459 = 0, + $46 = 0, + $460 = 0, + $461 = 0, + $462 = 0, + $463 = 0, + $464 = 0, + $465 = 0, + $466 = 0, + $467 = 0, + $468 = 0, + $469 = 0; + var $47 = 0, + $470 = 0, + $471 = 0, + $472 = 0, + $473 = 0, + $474 = 0, + $475 = 0, + $476 = 0, + $477 = 0, + $478 = 0, + $479 = 0, + $48 = 0, + $480 = 0, + $481 = 0, + $482 = 0, + $483 = 0, + $484 = 0, + $485 = 0, + $486 = 0, + $487 = 0; + var $488 = 0, + $489 = 0, + $49 = 0, + $490 = 0, + $491 = 0, + $492 = 0, + $493 = 0, + $494 = 0, + $495 = 0, + $496 = 0, + $497 = 0, + $498 = 0, + $499 = 0, + $5 = 0, + $50 = 0, + $500 = 0, + $501 = 0, + $502 = 0, + $503 = 0, + $504 = 0; + var $505 = 0, + $506 = 0, + $507 = 0, + $508 = 0, + $509 = 0, + $51 = 0, + $510 = 0, + $511 = 0, + $512 = 0, + $513 = 0, + $514 = 0, + $515 = 0, + $516 = 0, + $517 = 0, + $518 = 0, + $519 = 0, + $52 = 0, + $520 = 0, + $521 = 0, + $522 = 0; + var $523 = 0, + $524 = 0, + $525 = 0, + $526 = 0, + $527 = 0, + $528 = 0, + $529 = 0, + $53 = 0, + $530 = 0, + $531 = 0, + $532 = 0, + $533 = 0, + $534 = 0, + $535 = 0, + $536 = 0, + $537 = 0, + $538 = 0, + $539 = 0, + $54 = 0, + $540 = 0; + var $541 = 0, + $542 = 0, + $543 = 0, + $544 = 0, + $545 = 0, + $546 = 0, + $547 = 0, + $548 = 0, + $549 = 0, + $55 = 0, + $550 = 0, + $551 = 0, + $552 = 0, + $553 = 0, + $554 = 0, + $555 = 0, + $556 = 0, + $557 = 0, + $558 = 0, + $559 = 0; + var $56 = 0, + $560 = 0, + $561 = 0, + $562 = 0, + $563 = 0, + $564 = 0, + $565 = 0, + $566 = 0, + $567 = 0, + $568 = 0, + $569 = 0, + $57 = 0, + $570 = 0, + $571 = 0, + $572 = 0, + $573 = 0, + $574 = 0, + $575 = 0, + $576 = 0, + $577 = 0; + var $578 = 0, + $579 = 0, + $58 = 0, + $580 = 0, + $581 = 0, + $582 = 0, + $583 = 0, + $584 = 0, + $585 = 0, + $586 = 0, + $587 = 0, + $588 = 0, + $589 = 0, + $59 = 0, + $590 = 0, + $591 = 0, + $592 = 0, + $593 = 0, + $594 = 0, + $595 = 0; + var $596 = 0, + $597 = 0, + $598 = 0, + $599 = 0, + $6 = 0, + $60 = 0, + $600 = 0, + $601 = 0, + $602 = 0, + $603 = 0, + $604 = 0, + $605 = 0, + $606 = 0, + $607 = 0, + $608 = 0, + $609 = 0, + $61 = 0, + $610 = 0, + $611 = 0, + $612 = 0; + var $613 = 0, + $614 = 0, + $615 = 0, + $616 = 0, + $617 = 0, + $618 = 0, + $619 = 0, + $62 = 0, + $620 = 0, + $621 = 0, + $622 = 0, + $623 = 0, + $624 = 0, + $625 = 0, + $626 = 0, + $627 = 0, + $628 = 0, + $629 = 0, + $63 = 0, + $630 = 0; + var $631 = 0, + $632 = 0, + $633 = 0, + $634 = 0, + $635 = 0, + $636 = 0, + $637 = 0, + $638 = 0, + $639 = 0, + $64 = 0, + $640 = 0, + $641 = 0, + $642 = 0, + $643 = 0, + $644 = 0, + $645 = 0, + $646 = 0, + $647 = 0, + $648 = 0, + $649 = 0; + var $65 = 0, + $650 = 0, + $651 = 0, + $652 = 0, + $653 = 0, + $654 = 0, + $655 = 0, + $656 = 0, + $657 = 0, + $658 = 0, + $659 = 0, + $66 = 0, + $660 = 0, + $661 = 0, + $662 = 0, + $663 = 0, + $664 = 0, + $665 = 0, + $666 = 0, + $667 = 0; + var $668 = 0, + $669 = 0, + $67 = 0, + $670 = 0, + $671 = 0, + $672 = 0, + $673 = 0, + $674 = 0, + $675 = 0, + $676 = 0, + $677 = 0, + $678 = 0, + $679 = 0, + $68 = 0, + $680 = 0, + $681 = 0, + $682 = 0, + $683 = 0, + $684 = 0, + $685 = 0; + var $686 = 0, + $687 = 0, + $688 = 0, + $689 = 0, + $69 = 0, + $690 = 0, + $691 = 0, + $692 = 0, + $693 = 0, + $694 = 0, + $695 = 0, + $696 = 0, + $697 = 0, + $698 = 0, + $699 = 0, + $7 = 0, + $70 = 0, + $700 = 0, + $701 = 0, + $702 = 0; + var $703 = 0, + $704 = 0, + $705 = 0, + $706 = 0, + $707 = 0, + $708 = 0, + $709 = 0, + $71 = 0, + $710 = 0, + $711 = 0, + $712 = 0, + $713 = 0, + $714 = 0, + $715 = 0, + $716 = 0, + $717 = 0, + $718 = 0, + $719 = 0, + $72 = 0, + $720 = 0; + var $721 = 0, + $722 = 0, + $723 = 0, + $724 = 0, + $725 = 0, + $726 = 0, + $727 = 0, + $728 = 0, + $729 = 0, + $73 = 0, + $730 = 0, + $731 = 0, + $732 = 0, + $733 = 0, + $734 = 0, + $735 = 0, + $736 = 0, + $737 = 0, + $738 = 0, + $739 = 0; + var $74 = 0, + $740 = 0, + $741 = 0, + $742 = 0, + $743 = 0, + $744 = 0, + $745 = 0, + $746 = 0, + $747 = 0, + $748 = 0, + $749 = 0, + $75 = 0, + $750 = 0, + $751 = 0, + $752 = 0, + $753 = 0, + $754 = 0, + $755 = 0, + $756 = 0, + $757 = 0; + var $758 = 0, + $759 = 0, + $76 = 0, + $760 = 0, + $761 = 0, + $762 = 0, + $763 = 0, + $764 = 0, + $765 = 0, + $766 = 0, + $767 = 0, + $768 = 0, + $769 = 0, + $77 = 0, + $770 = 0, + $771 = 0, + $772 = 0, + $773 = 0, + $774 = 0, + $775 = 0; + var $776 = 0, + $777 = 0, + $778 = 0, + $779 = 0, + $78 = 0, + $780 = 0, + $781 = 0, + $782 = 0, + $783 = 0, + $784 = 0, + $785 = 0, + $786 = 0, + $787 = 0, + $788 = 0, + $789 = 0, + $79 = 0, + $790 = 0, + $791 = 0, + $792 = 0, + $793 = 0; + var $794 = 0, + $795 = 0, + $796 = 0, + $797 = 0, + $798 = 0, + $799 = 0, + $8 = 0, + $80 = 0, + $800 = 0, + $801 = 0, + $802 = 0, + $803 = 0, + $804 = 0, + $805 = 0, + $806 = 0, + $807 = 0, + $808 = 0, + $809 = 0, + $81 = 0, + $810 = 0; + var $811 = 0, + $812 = 0, + $813 = 0, + $814 = 0, + $815 = 0, + $816 = 0, + $817 = 0, + $818 = 0, + $819 = 0, + $82 = 0, + $820 = 0, + $821 = 0, + $822 = 0, + $823 = 0, + $824 = 0, + $825 = 0, + $826 = 0, + $827 = 0, + $828 = 0, + $829 = 0; + var $83 = 0, + $830 = 0, + $831 = 0, + $832 = 0, + $833 = 0, + $834 = 0, + $835 = 0, + $836 = 0, + $837 = 0, + $838 = 0, + $839 = 0, + $84 = 0, + $840 = 0, + $841 = 0, + $842 = 0, + $843 = 0, + $844 = 0, + $845 = 0, + $846 = 0, + $847 = 0; + var $848 = 0, + $849 = 0, + $85 = 0, + $850 = 0, + $851 = 0, + $852 = 0, + $853 = 0, + $854 = 0, + $855 = 0, + $856 = 0, + $857 = 0, + $858 = 0, + $859 = 0, + $86 = 0, + $860 = 0, + $861 = 0, + $862 = 0, + $863 = 0, + $864 = 0, + $865 = 0; + var $866 = 0, + $867 = 0, + $868 = 0, + $869 = 0, + $87 = 0, + $870 = 0, + $871 = 0, + $872 = 0, + $873 = 0, + $874 = 0, + $875 = 0, + $876 = 0, + $877 = 0, + $878 = 0, + $879 = 0, + $88 = 0, + $880 = 0, + $881 = 0, + $882 = 0, + $883 = 0; + var $884 = 0, + $885 = 0, + $886 = 0, + $887 = 0, + $888 = 0, + $889 = 0, + $89 = 0, + $890 = 0, + $891 = 0, + $892 = 0, + $893 = 0, + $894 = 0, + $895 = 0, + $896 = 0, + $897 = 0, + $898 = 0, + $899 = 0, + $9 = 0, + $90 = 0, + $900 = 0; + var $901 = 0, + $902 = 0, + $903 = 0, + $904 = 0, + $905 = 0, + $906 = 0, + $907 = 0, + $908 = 0, + $909 = 0, + $91 = 0, + $910 = 0, + $911 = 0, + $912 = 0, + $913 = 0, + $914 = 0, + $915 = 0, + $916 = 0, + $917 = 0, + $918 = 0, + $919 = 0; + var $92 = 0, + $920 = 0, + $921 = 0, + $922 = 0, + $923 = 0, + $924 = 0, + $925 = 0, + $926 = 0, + $927 = 0, + $928 = 0, + $929 = 0, + $93 = 0, + $930 = 0, + $931 = 0, + $932 = 0, + $933 = 0, + $934 = 0, + $935 = 0, + $936 = 0, + $937 = 0; + var $938 = 0, + $939 = 0, + $94 = 0, + $940 = 0, + $941 = 0, + $942 = 0, + $943 = 0, + $944 = 0, + $945 = 0, + $946 = 0, + $947 = 0, + $948 = 0, + $949 = 0, + $95 = 0, + $950 = 0, + $951 = 0, + $952 = 0, + $953 = 0, + $954 = 0, + $955 = 0; + var $956 = 0, + $957 = 0, + $958 = 0, + $959 = 0, + $96 = 0, + $960 = 0, + $961 = 0, + $962 = 0, + $963 = 0, + $964 = 0, + $965 = 0, + $966 = 0, + $967 = 0, + $968 = 0, + $969 = 0, + $97 = 0, + $970 = 0, + $971 = 0, + $972 = 0, + $973 = 0; + var $974 = 0, + $975 = 0, + $976 = 0, + $977 = 0, + $978 = 0, + $979 = 0, + $98 = 0, + $980 = 0, + $981 = 0, + $982 = 0, + $983 = 0, + $984 = 0, + $985 = 0, + $986 = 0, + $987 = 0, + $988 = 0, + $989 = 0, + $99 = 0, + $990 = 0, + $991 = 0; + var $992 = 0, + $993 = 0, + $994 = 0, + $995 = 0, + $996 = 0, + $997 = 0, + $998 = 0, + $999 = 0, + $F$0$i$i = 0, + $F1$0$i = 0, + $F4$0 = 0, + $F4$0$i$i = 0, + $F5$0$i = 0, + $I1$0$c$i$i = 0, + $I1$0$i$i = 0, + $I7$0$i = 0, + $I7$0$i$i = 0, + $K12$027$i = 0, + $K2$015$i$i = 0, + $K8$053$i$i = 0; + var $R$0$i = 0, + $R$0$i$i = 0, + $R$0$i18 = 0, + $R$1$i = 0, + $R$1$i$i = 0, + $R$1$i20 = 0, + $RP$0$i = 0, + $RP$0$i$i = 0, + $RP$0$i17 = 0, + $T$0$lcssa$i = 0, + $T$0$lcssa$i$i = 0, + $T$0$lcssa$i26$i = 0, + $T$014$i$i = 0, + $T$026$i = 0, + $T$052$i$i = 0, + $br$0$i = 0, + $br$030$i = 0, + $cond$i = 0, + $cond$i$i = 0, + $cond$i21 = 0; + var $exitcond$i$i = 0, + $i$02$i$i = 0, + $idx$0$i = 0, + $mem$0 = 0, + $nb$0 = 0, + $oldfirst$0$i$i = 0, + $or$cond$i = 0, + $or$cond$i$i = 0, + $or$cond$i27$i = 0, + $or$cond$i29 = 0, + $or$cond1$i = 0, + $or$cond19$i = 0, + $or$cond2$i = 0, + $or$cond24$i = 0, + $or$cond3$i = 0, + $or$cond4$i = 0, + $or$cond47$i = 0, + $or$cond5$i = 0, + $or$cond6$i = 0, + $or$cond8$i = 0; + var $qsize$0$i$i = 0, + $rsize$0$i = 0, + $rsize$0$i15 = 0, + $rsize$1$i = 0, + $rsize$2$i = 0, + $rsize$3$lcssa$i = 0, + $rsize$331$i = 0, + $rst$0$i = 0, + $rst$1$i = 0, + $sizebits$0$i = 0, + $sp$0$i$i = 0, + $sp$0$i$i$i = 0, + $sp$073$i = 0, + $sp$166$i = 0, + $ssize$0$i = 0, + $ssize$1$i = 0, + $ssize$129$i = 0, + $ssize$2$i = 0, + $t$0$i = 0, + $t$0$i14 = 0; + var $t$1$i = 0, + $t$2$ph$i = 0, + $t$2$v$3$i = 0, + $t$230$i = 0, + $tbase$245$i = 0, + $tsize$03141$i = 0, + $tsize$1$i = 0, + $tsize$244$i = 0, + $v$0$i = 0, + $v$0$i16 = 0, + $v$1$i = 0, + $v$2$i = 0, + $v$3$lcssa$i = 0, + $v$332$i = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = $bytes >>> 0 < 245; + do { + if ($0) { + $1 = $bytes >>> 0 < 11; + if ($1) { + $5 = 16; + } else { + $2 = ($bytes + 11) | 0; + $3 = $2 & -8; + $5 = $3; + } + $4 = $5 >>> 3; + $6 = HEAP32[32544 >> 2] | 0; + $7 = $6 >>> $4; + $8 = $7 & 3; + $9 = ($8 | 0) == 0; + if (!$9) { + $10 = $7 & 1; + $11 = $10 ^ 1; + $12 = ($11 + $4) | 0; + $13 = $12 << 1; + $14 = (((32544 + ($13 << 2)) | 0) + 40) | 0; + $$sum10 = ($13 + 2) | 0; + $15 = (((32544 + ($$sum10 << 2)) | 0) + 40) | 0; + $16 = HEAP32[$15 >> 2] | 0; + $17 = ($16 + 8) | 0; + $18 = HEAP32[$17 >> 2] | 0; + $19 = ($14 | 0) == ($18 | 0); + do { + if ($19) { + $20 = 1 << $12; + $21 = $20 ^ -1; + $22 = $6 & $21; + HEAP32[32544 >> 2] = $22; + } else { + $23 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $24 = $18 >>> 0 < $23 >>> 0; + if ($24) { + _abort(); + // unreachable; + } + $25 = ($18 + 12) | 0; + $26 = HEAP32[$25 >> 2] | 0; + $27 = ($26 | 0) == ($16 | 0); + if ($27) { + HEAP32[$25 >> 2] = $14; + HEAP32[$15 >> 2] = $18; + break; + } else { + _abort(); + // unreachable; + } + } + } while (0); + $28 = $12 << 3; + $29 = $28 | 3; + $30 = ($16 + 4) | 0; + HEAP32[$30 >> 2] = $29; + $$sum1112 = $28 | 4; + $31 = ($16 + $$sum1112) | 0; + $32 = HEAP32[$31 >> 2] | 0; + $33 = $32 | 1; + HEAP32[$31 >> 2] = $33; + $mem$0 = $17; + STACKTOP = sp; + return $mem$0 | 0; + } + $34 = HEAP32[((32544 + 8) | 0) >> 2] | 0; + $35 = $5 >>> 0 > $34 >>> 0; + if ($35) { + $36 = ($7 | 0) == 0; + if (!$36) { + $37 = $7 << $4; + $38 = 2 << $4; + $39 = (0 - $38) | 0; + $40 = $38 | $39; + $41 = $37 & $40; + $42 = (0 - $41) | 0; + $43 = $41 & $42; + $44 = ($43 + -1) | 0; + $45 = $44 >>> 12; + $46 = $45 & 16; + $47 = $44 >>> $46; + $48 = $47 >>> 5; + $49 = $48 & 8; + $50 = $49 | $46; + $51 = $47 >>> $49; + $52 = $51 >>> 2; + $53 = $52 & 4; + $54 = $50 | $53; + $55 = $51 >>> $53; + $56 = $55 >>> 1; + $57 = $56 & 2; + $58 = $54 | $57; + $59 = $55 >>> $57; + $60 = $59 >>> 1; + $61 = $60 & 1; + $62 = $58 | $61; + $63 = $59 >>> $61; + $64 = ($62 + $63) | 0; + $65 = $64 << 1; + $66 = (((32544 + ($65 << 2)) | 0) + 40) | 0; + $$sum4 = ($65 + 2) | 0; + $67 = (((32544 + ($$sum4 << 2)) | 0) + 40) | 0; + $68 = HEAP32[$67 >> 2] | 0; + $69 = ($68 + 8) | 0; + $70 = HEAP32[$69 >> 2] | 0; + $71 = ($66 | 0) == ($70 | 0); + do { + if ($71) { + $72 = 1 << $64; + $73 = $72 ^ -1; + $74 = $6 & $73; + HEAP32[32544 >> 2] = $74; + $88 = $34; + } else { + $75 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $76 = $70 >>> 0 < $75 >>> 0; + if ($76) { + _abort(); + // unreachable; + } + $77 = ($70 + 12) | 0; + $78 = HEAP32[$77 >> 2] | 0; + $79 = ($78 | 0) == ($68 | 0); + if ($79) { + HEAP32[$77 >> 2] = $66; + HEAP32[$67 >> 2] = $70; + $$pre = HEAP32[((32544 + 8) | 0) >> 2] | 0; + $88 = $$pre; + break; + } else { + _abort(); + // unreachable; + } + } + } while (0); + $80 = $64 << 3; + $81 = ($80 - $5) | 0; + $82 = $5 | 3; + $83 = ($68 + 4) | 0; + HEAP32[$83 >> 2] = $82; + $84 = ($68 + $5) | 0; + $85 = $81 | 1; + $$sum56 = $5 | 4; + $86 = ($68 + $$sum56) | 0; + HEAP32[$86 >> 2] = $85; + $87 = ($68 + $80) | 0; + HEAP32[$87 >> 2] = $81; + $89 = ($88 | 0) == 0; + if (!$89) { + $90 = HEAP32[((32544 + 20) | 0) >> 2] | 0; + $91 = $88 >>> 3; + $92 = $91 << 1; + $93 = (((32544 + ($92 << 2)) | 0) + 40) | 0; + $94 = HEAP32[32544 >> 2] | 0; + $95 = 1 << $91; + $96 = $94 & $95; + $97 = ($96 | 0) == 0; + if ($97) { + $98 = $94 | $95; + HEAP32[32544 >> 2] = $98; + $$sum8$pre = ($92 + 2) | 0; + $$pre105 = (((32544 + ($$sum8$pre << 2)) | 0) + 40) | 0; + $$pre$phiZ2D = $$pre105; + $F4$0 = $93; + } else { + $$sum9 = ($92 + 2) | 0; + $99 = (((32544 + ($$sum9 << 2)) | 0) + 40) | 0; + $100 = HEAP32[$99 >> 2] | 0; + $101 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $102 = $100 >>> 0 < $101 >>> 0; + if ($102) { + _abort(); + // unreachable; + } else { + $$pre$phiZ2D = $99; + $F4$0 = $100; + } + } + HEAP32[$$pre$phiZ2D >> 2] = $90; + $103 = ($F4$0 + 12) | 0; + HEAP32[$103 >> 2] = $90; + $104 = ($90 + 8) | 0; + HEAP32[$104 >> 2] = $F4$0; + $105 = ($90 + 12) | 0; + HEAP32[$105 >> 2] = $93; + } + HEAP32[((32544 + 8) | 0) >> 2] = $81; + HEAP32[((32544 + 20) | 0) >> 2] = $84; + $mem$0 = $69; + STACKTOP = sp; + return $mem$0 | 0; + } + $106 = HEAP32[((32544 + 4) | 0) >> 2] | 0; + $107 = ($106 | 0) == 0; + if ($107) { + $nb$0 = $5; + } else { + $108 = (0 - $106) | 0; + $109 = $106 & $108; + $110 = ($109 + -1) | 0; + $111 = $110 >>> 12; + $112 = $111 & 16; + $113 = $110 >>> $112; + $114 = $113 >>> 5; + $115 = $114 & 8; + $116 = $115 | $112; + $117 = $113 >>> $115; + $118 = $117 >>> 2; + $119 = $118 & 4; + $120 = $116 | $119; + $121 = $117 >>> $119; + $122 = $121 >>> 1; + $123 = $122 & 2; + $124 = $120 | $123; + $125 = $121 >>> $123; + $126 = $125 >>> 1; + $127 = $126 & 1; + $128 = $124 | $127; + $129 = $125 >>> $127; + $130 = ($128 + $129) | 0; + $131 = (((32544 + ($130 << 2)) | 0) + 304) | 0; + $132 = HEAP32[$131 >> 2] | 0; + $133 = ($132 + 4) | 0; + $134 = HEAP32[$133 >> 2] | 0; + $135 = $134 & -8; + $136 = ($135 - $5) | 0; + $rsize$0$i = $136; + $t$0$i = $132; + $v$0$i = $132; + while (1) { + $137 = ($t$0$i + 16) | 0; + $138 = HEAP32[$137 >> 2] | 0; + $139 = ($138 | 0) == (0 | 0); + if ($139) { + $140 = ($t$0$i + 20) | 0; + $141 = HEAP32[$140 >> 2] | 0; + $142 = ($141 | 0) == (0 | 0); + if ($142) { + break; + } else { + $144 = $141; + } + } else { + $144 = $138; + } + $143 = ($144 + 4) | 0; + $145 = HEAP32[$143 >> 2] | 0; + $146 = $145 & -8; + $147 = ($146 - $5) | 0; + $148 = $147 >>> 0 < $rsize$0$i >>> 0; + $$rsize$0$i = $148 ? $147 : $rsize$0$i; + $$v$0$i = $148 ? $144 : $v$0$i; + $rsize$0$i = $$rsize$0$i; + $t$0$i = $144; + $v$0$i = $$v$0$i; + } + $149 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $150 = $v$0$i >>> 0 < $149 >>> 0; + if ($150) { + _abort(); + // unreachable; + } + $151 = ($v$0$i + $5) | 0; + $152 = $v$0$i >>> 0 < $151 >>> 0; + if (!$152) { + _abort(); + // unreachable; + } + $153 = ($v$0$i + 24) | 0; + $154 = HEAP32[$153 >> 2] | 0; + $155 = ($v$0$i + 12) | 0; + $156 = HEAP32[$155 >> 2] | 0; + $157 = ($156 | 0) == ($v$0$i | 0); + do { + if ($157) { + $167 = ($v$0$i + 20) | 0; + $168 = HEAP32[$167 >> 2] | 0; + $169 = ($168 | 0) == (0 | 0); + if ($169) { + $170 = ($v$0$i + 16) | 0; + $171 = HEAP32[$170 >> 2] | 0; + $172 = ($171 | 0) == (0 | 0); + if ($172) { + $R$1$i = 0; + break; + } else { + $R$0$i = $171; + $RP$0$i = $170; + } + } else { + $R$0$i = $168; + $RP$0$i = $167; + } + while (1) { + $173 = ($R$0$i + 20) | 0; + $174 = HEAP32[$173 >> 2] | 0; + $175 = ($174 | 0) == (0 | 0); + if (!$175) { + $R$0$i = $174; + $RP$0$i = $173; + continue; + } + $176 = ($R$0$i + 16) | 0; + $177 = HEAP32[$176 >> 2] | 0; + $178 = ($177 | 0) == (0 | 0); + if ($178) { + break; + } else { + $R$0$i = $177; + $RP$0$i = $176; + } + } + $179 = $RP$0$i >>> 0 < $149 >>> 0; + if ($179) { + _abort(); + // unreachable; + } else { + HEAP32[$RP$0$i >> 2] = 0; + $R$1$i = $R$0$i; + break; + } + } else { + $158 = ($v$0$i + 8) | 0; + $159 = HEAP32[$158 >> 2] | 0; + $160 = $159 >>> 0 < $149 >>> 0; + if ($160) { + _abort(); + // unreachable; + } + $161 = ($159 + 12) | 0; + $162 = HEAP32[$161 >> 2] | 0; + $163 = ($162 | 0) == ($v$0$i | 0); + if (!$163) { + _abort(); + // unreachable; + } + $164 = ($156 + 8) | 0; + $165 = HEAP32[$164 >> 2] | 0; + $166 = ($165 | 0) == ($v$0$i | 0); + if ($166) { + HEAP32[$161 >> 2] = $156; + HEAP32[$164 >> 2] = $159; + $R$1$i = $156; + break; + } else { + _abort(); + // unreachable; + } + } + } while (0); + $180 = ($154 | 0) == (0 | 0); + do { + if (!$180) { + $181 = ($v$0$i + 28) | 0; + $182 = HEAP32[$181 >> 2] | 0; + $183 = (((32544 + ($182 << 2)) | 0) + 304) | 0; + $184 = HEAP32[$183 >> 2] | 0; + $185 = ($v$0$i | 0) == ($184 | 0); + if ($185) { + HEAP32[$183 >> 2] = $R$1$i; + $cond$i = ($R$1$i | 0) == (0 | 0); + if ($cond$i) { + $186 = 1 << $182; + $187 = $186 ^ -1; + $188 = HEAP32[((32544 + 4) | 0) >> 2] | 0; + $189 = $188 & $187; + HEAP32[((32544 + 4) | 0) >> 2] = $189; + break; + } + } else { + $190 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $191 = $154 >>> 0 < $190 >>> 0; + if ($191) { + _abort(); + // unreachable; + } + $192 = ($154 + 16) | 0; + $193 = HEAP32[$192 >> 2] | 0; + $194 = ($193 | 0) == ($v$0$i | 0); + if ($194) { + HEAP32[$192 >> 2] = $R$1$i; + } else { + $195 = ($154 + 20) | 0; + HEAP32[$195 >> 2] = $R$1$i; + } + $196 = ($R$1$i | 0) == (0 | 0); + if ($196) { + break; + } + } + $197 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $198 = $R$1$i >>> 0 < $197 >>> 0; + if ($198) { + _abort(); + // unreachable; + } + $199 = ($R$1$i + 24) | 0; + HEAP32[$199 >> 2] = $154; + $200 = ($v$0$i + 16) | 0; + $201 = HEAP32[$200 >> 2] | 0; + $202 = ($201 | 0) == (0 | 0); + do { + if (!$202) { + $203 = $201 >>> 0 < $197 >>> 0; + if ($203) { + _abort(); + // unreachable; + } else { + $204 = ($R$1$i + 16) | 0; + HEAP32[$204 >> 2] = $201; + $205 = ($201 + 24) | 0; + HEAP32[$205 >> 2] = $R$1$i; + break; + } + } + } while (0); + $206 = ($v$0$i + 20) | 0; + $207 = HEAP32[$206 >> 2] | 0; + $208 = ($207 | 0) == (0 | 0); + if (!$208) { + $209 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $210 = $207 >>> 0 < $209 >>> 0; + if ($210) { + _abort(); + // unreachable; + } else { + $211 = ($R$1$i + 20) | 0; + HEAP32[$211 >> 2] = $207; + $212 = ($207 + 24) | 0; + HEAP32[$212 >> 2] = $R$1$i; + break; + } + } + } + } while (0); + $213 = $rsize$0$i >>> 0 < 16; + if ($213) { + $214 = ($rsize$0$i + $5) | 0; + $215 = $214 | 3; + $216 = ($v$0$i + 4) | 0; + HEAP32[$216 >> 2] = $215; + $$sum4$i = ($214 + 4) | 0; + $217 = ($v$0$i + $$sum4$i) | 0; + $218 = HEAP32[$217 >> 2] | 0; + $219 = $218 | 1; + HEAP32[$217 >> 2] = $219; + } else { + $220 = $5 | 3; + $221 = ($v$0$i + 4) | 0; + HEAP32[$221 >> 2] = $220; + $222 = $rsize$0$i | 1; + $$sum$i39 = $5 | 4; + $223 = ($v$0$i + $$sum$i39) | 0; + HEAP32[$223 >> 2] = $222; + $$sum1$i = ($rsize$0$i + $5) | 0; + $224 = ($v$0$i + $$sum1$i) | 0; + HEAP32[$224 >> 2] = $rsize$0$i; + $225 = HEAP32[((32544 + 8) | 0) >> 2] | 0; + $226 = ($225 | 0) == 0; + if (!$226) { + $227 = HEAP32[((32544 + 20) | 0) >> 2] | 0; + $228 = $225 >>> 3; + $229 = $228 << 1; + $230 = (((32544 + ($229 << 2)) | 0) + 40) | 0; + $231 = HEAP32[32544 >> 2] | 0; + $232 = 1 << $228; + $233 = $231 & $232; + $234 = ($233 | 0) == 0; + if ($234) { + $235 = $231 | $232; + HEAP32[32544 >> 2] = $235; + $$sum2$pre$i = ($229 + 2) | 0; + $$pre$i = (((32544 + ($$sum2$pre$i << 2)) | 0) + 40) | 0; + $$pre$phi$iZ2D = $$pre$i; + $F1$0$i = $230; + } else { + $$sum3$i = ($229 + 2) | 0; + $236 = (((32544 + ($$sum3$i << 2)) | 0) + 40) | 0; + $237 = HEAP32[$236 >> 2] | 0; + $238 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $239 = $237 >>> 0 < $238 >>> 0; + if ($239) { + _abort(); + // unreachable; + } else { + $$pre$phi$iZ2D = $236; + $F1$0$i = $237; + } + } + HEAP32[$$pre$phi$iZ2D >> 2] = $227; + $240 = ($F1$0$i + 12) | 0; + HEAP32[$240 >> 2] = $227; + $241 = ($227 + 8) | 0; + HEAP32[$241 >> 2] = $F1$0$i; + $242 = ($227 + 12) | 0; + HEAP32[$242 >> 2] = $230; + } + HEAP32[((32544 + 8) | 0) >> 2] = $rsize$0$i; + HEAP32[((32544 + 20) | 0) >> 2] = $151; + } + $243 = ($v$0$i + 8) | 0; + $mem$0 = $243; + STACKTOP = sp; + return $mem$0 | 0; + } + } else { + $nb$0 = $5; + } + } else { + $244 = $bytes >>> 0 > 4294967231; + if ($244) { + $nb$0 = -1; + } else { + $245 = ($bytes + 11) | 0; + $246 = $245 & -8; + $247 = HEAP32[((32544 + 4) | 0) >> 2] | 0; + $248 = ($247 | 0) == 0; + if ($248) { + $nb$0 = $246; + } else { + $249 = (0 - $246) | 0; + $250 = $245 >>> 8; + $251 = ($250 | 0) == 0; + if ($251) { + $idx$0$i = 0; + } else { + $252 = $246 >>> 0 > 16777215; + if ($252) { + $idx$0$i = 31; + } else { + $253 = ($250 + 1048320) | 0; + $254 = $253 >>> 16; + $255 = $254 & 8; + $256 = $250 << $255; + $257 = ($256 + 520192) | 0; + $258 = $257 >>> 16; + $259 = $258 & 4; + $260 = $259 | $255; + $261 = $256 << $259; + $262 = ($261 + 245760) | 0; + $263 = $262 >>> 16; + $264 = $263 & 2; + $265 = $260 | $264; + $266 = (14 - $265) | 0; + $267 = $261 << $264; + $268 = $267 >>> 15; + $269 = ($266 + $268) | 0; + $270 = $269 << 1; + $271 = ($269 + 7) | 0; + $272 = $246 >>> $271; + $273 = $272 & 1; + $274 = $273 | $270; + $idx$0$i = $274; + } + } + $275 = (((32544 + ($idx$0$i << 2)) | 0) + 304) | 0; + $276 = HEAP32[$275 >> 2] | 0; + $277 = ($276 | 0) == (0 | 0); + L126: do { + if ($277) { + $rsize$2$i = $249; + $t$1$i = 0; + $v$2$i = 0; + } else { + $278 = ($idx$0$i | 0) == 31; + if ($278) { + $282 = 0; + } else { + $279 = $idx$0$i >>> 1; + $280 = (25 - $279) | 0; + $282 = $280; + } + $281 = $246 << $282; + $rsize$0$i15 = $249; + $rst$0$i = 0; + $sizebits$0$i = $281; + $t$0$i14 = $276; + $v$0$i16 = 0; + while (1) { + $283 = ($t$0$i14 + 4) | 0; + $284 = HEAP32[$283 >> 2] | 0; + $285 = $284 & -8; + $286 = ($285 - $246) | 0; + $287 = $286 >>> 0 < $rsize$0$i15 >>> 0; + if ($287) { + $288 = ($285 | 0) == ($246 | 0); + if ($288) { + $rsize$2$i = $286; + $t$1$i = $t$0$i14; + $v$2$i = $t$0$i14; + break L126; + } else { + $rsize$1$i = $286; + $v$1$i = $t$0$i14; + } + } else { + $rsize$1$i = $rsize$0$i15; + $v$1$i = $v$0$i16; + } + $289 = ($t$0$i14 + 20) | 0; + $290 = HEAP32[$289 >> 2] | 0; + $291 = $sizebits$0$i >>> 31; + $292 = ((($t$0$i14 + ($291 << 2)) | 0) + 16) | 0; + $293 = HEAP32[$292 >> 2] | 0; + $294 = ($290 | 0) == (0 | 0); + $295 = ($290 | 0) == ($293 | 0); + $or$cond19$i = $294 | $295; + $rst$1$i = $or$cond19$i ? $rst$0$i : $290; + $296 = ($293 | 0) == (0 | 0); + $297 = $sizebits$0$i << 1; + if ($296) { + $rsize$2$i = $rsize$1$i; + $t$1$i = $rst$1$i; + $v$2$i = $v$1$i; + break; + } else { + $rsize$0$i15 = $rsize$1$i; + $rst$0$i = $rst$1$i; + $sizebits$0$i = $297; + $t$0$i14 = $293; + $v$0$i16 = $v$1$i; + } + } + } + } while (0); + $298 = ($t$1$i | 0) == (0 | 0); + $299 = ($v$2$i | 0) == (0 | 0); + $or$cond$i = $298 & $299; + if ($or$cond$i) { + $300 = 2 << $idx$0$i; + $301 = (0 - $300) | 0; + $302 = $300 | $301; + $303 = $247 & $302; + $304 = ($303 | 0) == 0; + if ($304) { + $nb$0 = $246; + break; + } + $305 = (0 - $303) | 0; + $306 = $303 & $305; + $307 = ($306 + -1) | 0; + $308 = $307 >>> 12; + $309 = $308 & 16; + $310 = $307 >>> $309; + $311 = $310 >>> 5; + $312 = $311 & 8; + $313 = $312 | $309; + $314 = $310 >>> $312; + $315 = $314 >>> 2; + $316 = $315 & 4; + $317 = $313 | $316; + $318 = $314 >>> $316; + $319 = $318 >>> 1; + $320 = $319 & 2; + $321 = $317 | $320; + $322 = $318 >>> $320; + $323 = $322 >>> 1; + $324 = $323 & 1; + $325 = $321 | $324; + $326 = $322 >>> $324; + $327 = ($325 + $326) | 0; + $328 = (((32544 + ($327 << 2)) | 0) + 304) | 0; + $329 = HEAP32[$328 >> 2] | 0; + $t$2$ph$i = $329; + } else { + $t$2$ph$i = $t$1$i; + } + $330 = ($t$2$ph$i | 0) == (0 | 0); + if ($330) { + $rsize$3$lcssa$i = $rsize$2$i; + $v$3$lcssa$i = $v$2$i; + } else { + $rsize$331$i = $rsize$2$i; + $t$230$i = $t$2$ph$i; + $v$332$i = $v$2$i; + while (1) { + $331 = ($t$230$i + 4) | 0; + $332 = HEAP32[$331 >> 2] | 0; + $333 = $332 & -8; + $334 = ($333 - $246) | 0; + $335 = $334 >>> 0 < $rsize$331$i >>> 0; + $$rsize$3$i = $335 ? $334 : $rsize$331$i; + $t$2$v$3$i = $335 ? $t$230$i : $v$332$i; + $336 = ($t$230$i + 16) | 0; + $337 = HEAP32[$336 >> 2] | 0; + $338 = ($337 | 0) == (0 | 0); + if (!$338) { + $rsize$331$i = $$rsize$3$i; + $t$230$i = $337; + $v$332$i = $t$2$v$3$i; + continue; + } + $339 = ($t$230$i + 20) | 0; + $340 = HEAP32[$339 >> 2] | 0; + $341 = ($340 | 0) == (0 | 0); + if ($341) { + $rsize$3$lcssa$i = $$rsize$3$i; + $v$3$lcssa$i = $t$2$v$3$i; + break; + } else { + $rsize$331$i = $$rsize$3$i; + $t$230$i = $340; + $v$332$i = $t$2$v$3$i; + } + } + } + $342 = ($v$3$lcssa$i | 0) == (0 | 0); + if ($342) { + $nb$0 = $246; + } else { + $343 = HEAP32[((32544 + 8) | 0) >> 2] | 0; + $344 = ($343 - $246) | 0; + $345 = $rsize$3$lcssa$i >>> 0 < $344 >>> 0; + if ($345) { + $346 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $347 = $v$3$lcssa$i >>> 0 < $346 >>> 0; + if ($347) { + _abort(); + // unreachable; + } + $348 = ($v$3$lcssa$i + $246) | 0; + $349 = $v$3$lcssa$i >>> 0 < $348 >>> 0; + if (!$349) { + _abort(); + // unreachable; + } + $350 = ($v$3$lcssa$i + 24) | 0; + $351 = HEAP32[$350 >> 2] | 0; + $352 = ($v$3$lcssa$i + 12) | 0; + $353 = HEAP32[$352 >> 2] | 0; + $354 = ($353 | 0) == ($v$3$lcssa$i | 0); + do { + if ($354) { + $364 = ($v$3$lcssa$i + 20) | 0; + $365 = HEAP32[$364 >> 2] | 0; + $366 = ($365 | 0) == (0 | 0); + if ($366) { + $367 = ($v$3$lcssa$i + 16) | 0; + $368 = HEAP32[$367 >> 2] | 0; + $369 = ($368 | 0) == (0 | 0); + if ($369) { + $R$1$i20 = 0; + break; + } else { + $R$0$i18 = $368; + $RP$0$i17 = $367; + } + } else { + $R$0$i18 = $365; + $RP$0$i17 = $364; + } + while (1) { + $370 = ($R$0$i18 + 20) | 0; + $371 = HEAP32[$370 >> 2] | 0; + $372 = ($371 | 0) == (0 | 0); + if (!$372) { + $R$0$i18 = $371; + $RP$0$i17 = $370; + continue; + } + $373 = ($R$0$i18 + 16) | 0; + $374 = HEAP32[$373 >> 2] | 0; + $375 = ($374 | 0) == (0 | 0); + if ($375) { + break; + } else { + $R$0$i18 = $374; + $RP$0$i17 = $373; + } + } + $376 = $RP$0$i17 >>> 0 < $346 >>> 0; + if ($376) { + _abort(); + // unreachable; + } else { + HEAP32[$RP$0$i17 >> 2] = 0; + $R$1$i20 = $R$0$i18; + break; + } + } else { + $355 = ($v$3$lcssa$i + 8) | 0; + $356 = HEAP32[$355 >> 2] | 0; + $357 = $356 >>> 0 < $346 >>> 0; + if ($357) { + _abort(); + // unreachable; + } + $358 = ($356 + 12) | 0; + $359 = HEAP32[$358 >> 2] | 0; + $360 = ($359 | 0) == ($v$3$lcssa$i | 0); + if (!$360) { + _abort(); + // unreachable; + } + $361 = ($353 + 8) | 0; + $362 = HEAP32[$361 >> 2] | 0; + $363 = ($362 | 0) == ($v$3$lcssa$i | 0); + if ($363) { + HEAP32[$358 >> 2] = $353; + HEAP32[$361 >> 2] = $356; + $R$1$i20 = $353; + break; + } else { + _abort(); + // unreachable; + } + } + } while (0); + $377 = ($351 | 0) == (0 | 0); + do { + if (!$377) { + $378 = ($v$3$lcssa$i + 28) | 0; + $379 = HEAP32[$378 >> 2] | 0; + $380 = (((32544 + ($379 << 2)) | 0) + 304) | 0; + $381 = HEAP32[$380 >> 2] | 0; + $382 = ($v$3$lcssa$i | 0) == ($381 | 0); + if ($382) { + HEAP32[$380 >> 2] = $R$1$i20; + $cond$i21 = ($R$1$i20 | 0) == (0 | 0); + if ($cond$i21) { + $383 = 1 << $379; + $384 = $383 ^ -1; + $385 = HEAP32[((32544 + 4) | 0) >> 2] | 0; + $386 = $385 & $384; + HEAP32[((32544 + 4) | 0) >> 2] = $386; + break; + } + } else { + $387 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $388 = $351 >>> 0 < $387 >>> 0; + if ($388) { + _abort(); + // unreachable; + } + $389 = ($351 + 16) | 0; + $390 = HEAP32[$389 >> 2] | 0; + $391 = ($390 | 0) == ($v$3$lcssa$i | 0); + if ($391) { + HEAP32[$389 >> 2] = $R$1$i20; + } else { + $392 = ($351 + 20) | 0; + HEAP32[$392 >> 2] = $R$1$i20; + } + $393 = ($R$1$i20 | 0) == (0 | 0); + if ($393) { + break; + } + } + $394 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $395 = $R$1$i20 >>> 0 < $394 >>> 0; + if ($395) { + _abort(); + // unreachable; + } + $396 = ($R$1$i20 + 24) | 0; + HEAP32[$396 >> 2] = $351; + $397 = ($v$3$lcssa$i + 16) | 0; + $398 = HEAP32[$397 >> 2] | 0; + $399 = ($398 | 0) == (0 | 0); + do { + if (!$399) { + $400 = $398 >>> 0 < $394 >>> 0; + if ($400) { + _abort(); + // unreachable; + } else { + $401 = ($R$1$i20 + 16) | 0; + HEAP32[$401 >> 2] = $398; + $402 = ($398 + 24) | 0; + HEAP32[$402 >> 2] = $R$1$i20; + break; + } + } + } while (0); + $403 = ($v$3$lcssa$i + 20) | 0; + $404 = HEAP32[$403 >> 2] | 0; + $405 = ($404 | 0) == (0 | 0); + if (!$405) { + $406 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $407 = $404 >>> 0 < $406 >>> 0; + if ($407) { + _abort(); + // unreachable; + } else { + $408 = ($R$1$i20 + 20) | 0; + HEAP32[$408 >> 2] = $404; + $409 = ($404 + 24) | 0; + HEAP32[$409 >> 2] = $R$1$i20; + break; + } + } + } + } while (0); + $410 = $rsize$3$lcssa$i >>> 0 < 16; + L204: do { + if ($410) { + $411 = ($rsize$3$lcssa$i + $246) | 0; + $412 = $411 | 3; + $413 = ($v$3$lcssa$i + 4) | 0; + HEAP32[$413 >> 2] = $412; + $$sum18$i = ($411 + 4) | 0; + $414 = ($v$3$lcssa$i + $$sum18$i) | 0; + $415 = HEAP32[$414 >> 2] | 0; + $416 = $415 | 1; + HEAP32[$414 >> 2] = $416; + } else { + $417 = $246 | 3; + $418 = ($v$3$lcssa$i + 4) | 0; + HEAP32[$418 >> 2] = $417; + $419 = $rsize$3$lcssa$i | 1; + $$sum$i2338 = $246 | 4; + $420 = ($v$3$lcssa$i + $$sum$i2338) | 0; + HEAP32[$420 >> 2] = $419; + $$sum1$i24 = ($rsize$3$lcssa$i + $246) | 0; + $421 = ($v$3$lcssa$i + $$sum1$i24) | 0; + HEAP32[$421 >> 2] = $rsize$3$lcssa$i; + $422 = $rsize$3$lcssa$i >>> 3; + $423 = $rsize$3$lcssa$i >>> 0 < 256; + if ($423) { + $424 = $422 << 1; + $425 = (((32544 + ($424 << 2)) | 0) + 40) | 0; + $426 = HEAP32[32544 >> 2] | 0; + $427 = 1 << $422; + $428 = $426 & $427; + $429 = ($428 | 0) == 0; + do { + if ($429) { + $430 = $426 | $427; + HEAP32[32544 >> 2] = $430; + $$sum14$pre$i = ($424 + 2) | 0; + $$pre$i25 = + (((32544 + ($$sum14$pre$i << 2)) | 0) + 40) | 0; + $$pre$phi$i26Z2D = $$pre$i25; + $F5$0$i = $425; + } else { + $$sum17$i = ($424 + 2) | 0; + $431 = (((32544 + ($$sum17$i << 2)) | 0) + 40) | 0; + $432 = HEAP32[$431 >> 2] | 0; + $433 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $434 = $432 >>> 0 < $433 >>> 0; + if (!$434) { + $$pre$phi$i26Z2D = $431; + $F5$0$i = $432; + break; + } + _abort(); + // unreachable; + } + } while (0); + HEAP32[$$pre$phi$i26Z2D >> 2] = $348; + $435 = ($F5$0$i + 12) | 0; + HEAP32[$435 >> 2] = $348; + $$sum15$i = ($246 + 8) | 0; + $436 = ($v$3$lcssa$i + $$sum15$i) | 0; + HEAP32[$436 >> 2] = $F5$0$i; + $$sum16$i = ($246 + 12) | 0; + $437 = ($v$3$lcssa$i + $$sum16$i) | 0; + HEAP32[$437 >> 2] = $425; + break; + } + $438 = $rsize$3$lcssa$i >>> 8; + $439 = ($438 | 0) == 0; + if ($439) { + $I7$0$i = 0; + } else { + $440 = $rsize$3$lcssa$i >>> 0 > 16777215; + if ($440) { + $I7$0$i = 31; + } else { + $441 = ($438 + 1048320) | 0; + $442 = $441 >>> 16; + $443 = $442 & 8; + $444 = $438 << $443; + $445 = ($444 + 520192) | 0; + $446 = $445 >>> 16; + $447 = $446 & 4; + $448 = $447 | $443; + $449 = $444 << $447; + $450 = ($449 + 245760) | 0; + $451 = $450 >>> 16; + $452 = $451 & 2; + $453 = $448 | $452; + $454 = (14 - $453) | 0; + $455 = $449 << $452; + $456 = $455 >>> 15; + $457 = ($454 + $456) | 0; + $458 = $457 << 1; + $459 = ($457 + 7) | 0; + $460 = $rsize$3$lcssa$i >>> $459; + $461 = $460 & 1; + $462 = $461 | $458; + $I7$0$i = $462; + } + } + $463 = (((32544 + ($I7$0$i << 2)) | 0) + 304) | 0; + $$sum2$i = ($246 + 28) | 0; + $464 = ($v$3$lcssa$i + $$sum2$i) | 0; + HEAP32[$464 >> 2] = $I7$0$i; + $$sum3$i27 = ($246 + 16) | 0; + $465 = ($v$3$lcssa$i + $$sum3$i27) | 0; + $$sum4$i28 = ($246 + 20) | 0; + $466 = ($v$3$lcssa$i + $$sum4$i28) | 0; + HEAP32[$466 >> 2] = 0; + HEAP32[$465 >> 2] = 0; + $467 = HEAP32[((32544 + 4) | 0) >> 2] | 0; + $468 = 1 << $I7$0$i; + $469 = $467 & $468; + $470 = ($469 | 0) == 0; + if ($470) { + $471 = $467 | $468; + HEAP32[((32544 + 4) | 0) >> 2] = $471; + HEAP32[$463 >> 2] = $348; + $$sum5$i = ($246 + 24) | 0; + $472 = ($v$3$lcssa$i + $$sum5$i) | 0; + HEAP32[$472 >> 2] = $463; + $$sum6$i = ($246 + 12) | 0; + $473 = ($v$3$lcssa$i + $$sum6$i) | 0; + HEAP32[$473 >> 2] = $348; + $$sum7$i = ($246 + 8) | 0; + $474 = ($v$3$lcssa$i + $$sum7$i) | 0; + HEAP32[$474 >> 2] = $348; + break; + } + $475 = HEAP32[$463 >> 2] | 0; + $476 = ($I7$0$i | 0) == 31; + if ($476) { + $484 = 0; + } else { + $477 = $I7$0$i >>> 1; + $478 = (25 - $477) | 0; + $484 = $478; + } + $479 = ($475 + 4) | 0; + $480 = HEAP32[$479 >> 2] | 0; + $481 = $480 & -8; + $482 = ($481 | 0) == ($rsize$3$lcssa$i | 0); + L225: do { + if ($482) { + $T$0$lcssa$i = $475; + } else { + $483 = $rsize$3$lcssa$i << $484; + $K12$027$i = $483; + $T$026$i = $475; + while (1) { + $491 = $K12$027$i >>> 31; + $492 = ((($T$026$i + ($491 << 2)) | 0) + 16) | 0; + $487 = HEAP32[$492 >> 2] | 0; + $493 = ($487 | 0) == (0 | 0); + if ($493) { + break; + } + $485 = $K12$027$i << 1; + $486 = ($487 + 4) | 0; + $488 = HEAP32[$486 >> 2] | 0; + $489 = $488 & -8; + $490 = ($489 | 0) == ($rsize$3$lcssa$i | 0); + if ($490) { + $T$0$lcssa$i = $487; + break L225; + } else { + $K12$027$i = $485; + $T$026$i = $487; + } + } + $494 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $495 = $492 >>> 0 < $494 >>> 0; + if ($495) { + _abort(); + // unreachable; + } else { + HEAP32[$492 >> 2] = $348; + $$sum11$i = ($246 + 24) | 0; + $496 = ($v$3$lcssa$i + $$sum11$i) | 0; + HEAP32[$496 >> 2] = $T$026$i; + $$sum12$i = ($246 + 12) | 0; + $497 = ($v$3$lcssa$i + $$sum12$i) | 0; + HEAP32[$497 >> 2] = $348; + $$sum13$i = ($246 + 8) | 0; + $498 = ($v$3$lcssa$i + $$sum13$i) | 0; + HEAP32[$498 >> 2] = $348; + break L204; + } + } + } while (0); + $499 = ($T$0$lcssa$i + 8) | 0; + $500 = HEAP32[$499 >> 2] | 0; + $501 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $502 = $T$0$lcssa$i >>> 0 >= $501 >>> 0; + $503 = $500 >>> 0 >= $501 >>> 0; + $or$cond24$i = $502 & $503; + if ($or$cond24$i) { + $504 = ($500 + 12) | 0; + HEAP32[$504 >> 2] = $348; + HEAP32[$499 >> 2] = $348; + $$sum8$i = ($246 + 8) | 0; + $505 = ($v$3$lcssa$i + $$sum8$i) | 0; + HEAP32[$505 >> 2] = $500; + $$sum9$i = ($246 + 12) | 0; + $506 = ($v$3$lcssa$i + $$sum9$i) | 0; + HEAP32[$506 >> 2] = $T$0$lcssa$i; + $$sum10$i = ($246 + 24) | 0; + $507 = ($v$3$lcssa$i + $$sum10$i) | 0; + HEAP32[$507 >> 2] = 0; + break; + } else { + _abort(); + // unreachable; + } + } + } while (0); + $508 = ($v$3$lcssa$i + 8) | 0; + $mem$0 = $508; + STACKTOP = sp; + return $mem$0 | 0; + } else { + $nb$0 = $246; + } + } + } + } + } + } while (0); + $509 = HEAP32[((32544 + 8) | 0) >> 2] | 0; + $510 = $509 >>> 0 < $nb$0 >>> 0; + if (!$510) { + $511 = ($509 - $nb$0) | 0; + $512 = HEAP32[((32544 + 20) | 0) >> 2] | 0; + $513 = $511 >>> 0 > 15; + if ($513) { + $514 = ($512 + $nb$0) | 0; + HEAP32[((32544 + 20) | 0) >> 2] = $514; + HEAP32[((32544 + 8) | 0) >> 2] = $511; + $515 = $511 | 1; + $$sum2 = ($nb$0 + 4) | 0; + $516 = ($512 + $$sum2) | 0; + HEAP32[$516 >> 2] = $515; + $517 = ($512 + $509) | 0; + HEAP32[$517 >> 2] = $511; + $518 = $nb$0 | 3; + $519 = ($512 + 4) | 0; + HEAP32[$519 >> 2] = $518; + } else { + HEAP32[((32544 + 8) | 0) >> 2] = 0; + HEAP32[((32544 + 20) | 0) >> 2] = 0; + $520 = $509 | 3; + $521 = ($512 + 4) | 0; + HEAP32[$521 >> 2] = $520; + $$sum1 = ($509 + 4) | 0; + $522 = ($512 + $$sum1) | 0; + $523 = HEAP32[$522 >> 2] | 0; + $524 = $523 | 1; + HEAP32[$522 >> 2] = $524; + } + $525 = ($512 + 8) | 0; + $mem$0 = $525; + STACKTOP = sp; + return $mem$0 | 0; + } + $526 = HEAP32[((32544 + 12) | 0) >> 2] | 0; + $527 = $526 >>> 0 > $nb$0 >>> 0; + if ($527) { + $528 = ($526 - $nb$0) | 0; + HEAP32[((32544 + 12) | 0) >> 2] = $528; + $529 = HEAP32[((32544 + 24) | 0) >> 2] | 0; + $530 = ($529 + $nb$0) | 0; + HEAP32[((32544 + 24) | 0) >> 2] = $530; + $531 = $528 | 1; + $$sum = ($nb$0 + 4) | 0; + $532 = ($529 + $$sum) | 0; + HEAP32[$532 >> 2] = $531; + $533 = $nb$0 | 3; + $534 = ($529 + 4) | 0; + HEAP32[$534 >> 2] = $533; + $535 = ($529 + 8) | 0; + $mem$0 = $535; + STACKTOP = sp; + return $mem$0 | 0; + } + $536 = HEAP32[33016 >> 2] | 0; + $537 = ($536 | 0) == 0; + do { + if ($537) { + $538 = _sysconf(30) | 0; + $539 = ($538 + -1) | 0; + $540 = $539 & $538; + $541 = ($540 | 0) == 0; + if ($541) { + HEAP32[((33016 + 8) | 0) >> 2] = $538; + HEAP32[((33016 + 4) | 0) >> 2] = $538; + HEAP32[((33016 + 12) | 0) >> 2] = -1; + HEAP32[((33016 + 16) | 0) >> 2] = -1; + HEAP32[((33016 + 20) | 0) >> 2] = 0; + HEAP32[((32544 + 444) | 0) >> 2] = 0; + $542 = _time(0 | 0) | 0; + $543 = $542 & -16; + $544 = $543 ^ 1431655768; + HEAP32[33016 >> 2] = $544; + break; + } else { + _abort(); + // unreachable; + } + } + } while (0); + $545 = ($nb$0 + 48) | 0; + $546 = HEAP32[((33016 + 8) | 0) >> 2] | 0; + $547 = ($nb$0 + 47) | 0; + $548 = ($546 + $547) | 0; + $549 = (0 - $546) | 0; + $550 = $548 & $549; + $551 = $550 >>> 0 > $nb$0 >>> 0; + if (!$551) { + $mem$0 = 0; + STACKTOP = sp; + return $mem$0 | 0; + } + $552 = HEAP32[((32544 + 440) | 0) >> 2] | 0; + $553 = ($552 | 0) == 0; + if (!$553) { + $554 = HEAP32[((32544 + 432) | 0) >> 2] | 0; + $555 = ($554 + $550) | 0; + $556 = $555 >>> 0 <= $554 >>> 0; + $557 = $555 >>> 0 > $552 >>> 0; + $or$cond1$i = $556 | $557; + if ($or$cond1$i) { + $mem$0 = 0; + STACKTOP = sp; + return $mem$0 | 0; + } + } + $558 = HEAP32[((32544 + 444) | 0) >> 2] | 0; + $559 = $558 & 4; + $560 = ($559 | 0) == 0; + L266: do { + if ($560) { + $561 = HEAP32[((32544 + 24) | 0) >> 2] | 0; + $562 = ($561 | 0) == (0 | 0); + L268: do { + if ($562) { + label = 181; + } else { + $sp$0$i$i = (32544 + 448) | 0; + while (1) { + $563 = HEAP32[$sp$0$i$i >> 2] | 0; + $564 = $563 >>> 0 > $561 >>> 0; + if (!$564) { + $565 = ($sp$0$i$i + 4) | 0; + $566 = HEAP32[$565 >> 2] | 0; + $567 = ($563 + $566) | 0; + $568 = $567 >>> 0 > $561 >>> 0; + if ($568) { + break; + } + } + $569 = ($sp$0$i$i + 8) | 0; + $570 = HEAP32[$569 >> 2] | 0; + $571 = ($570 | 0) == (0 | 0); + if ($571) { + label = 181; + break L268; + } else { + $sp$0$i$i = $570; + } + } + $572 = ($sp$0$i$i | 0) == (0 | 0); + if ($572) { + label = 181; + } else { + $595 = HEAP32[((32544 + 12) | 0) >> 2] | 0; + $596 = ($548 - $595) | 0; + $597 = $596 & $549; + $598 = $597 >>> 0 < 2147483647; + if ($598) { + $599 = _sbrk($597 | 0) | 0; + $600 = HEAP32[$sp$0$i$i >> 2] | 0; + $601 = HEAP32[$565 >> 2] | 0; + $602 = ($600 + $601) | 0; + $603 = ($599 | 0) == ($602 | 0); + if ($603) { + $br$0$i = $599; + $ssize$1$i = $597; + label = 190; + } else { + $br$030$i = $599; + $ssize$129$i = $597; + label = 191; + } + } else { + $tsize$03141$i = 0; + } + } + } + } while (0); + do { + if ((label | 0) == 181) { + $573 = _sbrk(0) | 0; + $574 = ($573 | 0) == (-1 | 0); + if ($574) { + $tsize$03141$i = 0; + } else { + $575 = $573; + $576 = HEAP32[((33016 + 4) | 0) >> 2] | 0; + $577 = ($576 + -1) | 0; + $578 = $577 & $575; + $579 = ($578 | 0) == 0; + if ($579) { + $ssize$0$i = $550; + } else { + $580 = ($577 + $575) | 0; + $581 = (0 - $576) | 0; + $582 = $580 & $581; + $583 = ($550 - $575) | 0; + $584 = ($583 + $582) | 0; + $ssize$0$i = $584; + } + $585 = HEAP32[((32544 + 432) | 0) >> 2] | 0; + $586 = ($585 + $ssize$0$i) | 0; + $587 = $ssize$0$i >>> 0 > $nb$0 >>> 0; + $588 = $ssize$0$i >>> 0 < 2147483647; + $or$cond$i29 = $587 & $588; + if ($or$cond$i29) { + $589 = HEAP32[((32544 + 440) | 0) >> 2] | 0; + $590 = ($589 | 0) == 0; + if (!$590) { + $591 = $586 >>> 0 <= $585 >>> 0; + $592 = $586 >>> 0 > $589 >>> 0; + $or$cond2$i = $591 | $592; + if ($or$cond2$i) { + $tsize$03141$i = 0; + break; + } + } + $593 = _sbrk($ssize$0$i | 0) | 0; + $594 = ($593 | 0) == ($573 | 0); + if ($594) { + $br$0$i = $573; + $ssize$1$i = $ssize$0$i; + label = 190; + } else { + $br$030$i = $593; + $ssize$129$i = $ssize$0$i; + label = 191; + } + } else { + $tsize$03141$i = 0; + } + } + } + } while (0); + L288: do { + if ((label | 0) == 190) { + $604 = ($br$0$i | 0) == (-1 | 0); + if ($604) { + $tsize$03141$i = $ssize$1$i; + } else { + $tbase$245$i = $br$0$i; + $tsize$244$i = $ssize$1$i; + label = 201; + break L266; + } + } else if ((label | 0) == 191) { + $605 = (0 - $ssize$129$i) | 0; + $606 = ($br$030$i | 0) != (-1 | 0); + $607 = $ssize$129$i >>> 0 < 2147483647; + $or$cond5$i = $606 & $607; + $608 = $545 >>> 0 > $ssize$129$i >>> 0; + $or$cond4$i = $or$cond5$i & $608; + do { + if ($or$cond4$i) { + $609 = HEAP32[((33016 + 8) | 0) >> 2] | 0; + $610 = ($547 - $ssize$129$i) | 0; + $611 = ($610 + $609) | 0; + $612 = (0 - $609) | 0; + $613 = $611 & $612; + $614 = $613 >>> 0 < 2147483647; + if ($614) { + $615 = _sbrk($613 | 0) | 0; + $616 = ($615 | 0) == (-1 | 0); + if ($616) { + _sbrk($605 | 0) | 0; + $tsize$03141$i = 0; + break L288; + } else { + $617 = ($613 + $ssize$129$i) | 0; + $ssize$2$i = $617; + break; + } + } else { + $ssize$2$i = $ssize$129$i; + } + } else { + $ssize$2$i = $ssize$129$i; + } + } while (0); + $618 = ($br$030$i | 0) == (-1 | 0); + if ($618) { + $tsize$03141$i = 0; + } else { + $tbase$245$i = $br$030$i; + $tsize$244$i = $ssize$2$i; + label = 201; + break L266; + } + } + } while (0); + $619 = HEAP32[((32544 + 444) | 0) >> 2] | 0; + $620 = $619 | 4; + HEAP32[((32544 + 444) | 0) >> 2] = $620; + $tsize$1$i = $tsize$03141$i; + label = 198; + } else { + $tsize$1$i = 0; + label = 198; + } + } while (0); + if ((label | 0) == 198) { + $621 = $550 >>> 0 < 2147483647; + if ($621) { + $622 = _sbrk($550 | 0) | 0; + $623 = _sbrk(0) | 0; + $624 = ($622 | 0) != (-1 | 0); + $625 = ($623 | 0) != (-1 | 0); + $or$cond3$i = $624 & $625; + $626 = $622 >>> 0 < $623 >>> 0; + $or$cond6$i = $or$cond3$i & $626; + if ($or$cond6$i) { + $627 = $623; + $628 = $622; + $629 = ($627 - $628) | 0; + $630 = ($nb$0 + 40) | 0; + $631 = $629 >>> 0 > $630 >>> 0; + $$tsize$1$i = $631 ? $629 : $tsize$1$i; + if ($631) { + $tbase$245$i = $622; + $tsize$244$i = $$tsize$1$i; + label = 201; + } + } + } + } + if ((label | 0) == 201) { + $632 = HEAP32[((32544 + 432) | 0) >> 2] | 0; + $633 = ($632 + $tsize$244$i) | 0; + HEAP32[((32544 + 432) | 0) >> 2] = $633; + $634 = HEAP32[((32544 + 436) | 0) >> 2] | 0; + $635 = $633 >>> 0 > $634 >>> 0; + if ($635) { + HEAP32[((32544 + 436) | 0) >> 2] = $633; + } + $636 = HEAP32[((32544 + 24) | 0) >> 2] | 0; + $637 = ($636 | 0) == (0 | 0); + L308: do { + if ($637) { + $638 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $639 = ($638 | 0) == (0 | 0); + $640 = $tbase$245$i >>> 0 < $638 >>> 0; + $or$cond8$i = $639 | $640; + if ($or$cond8$i) { + HEAP32[((32544 + 16) | 0) >> 2] = $tbase$245$i; + } + HEAP32[((32544 + 448) | 0) >> 2] = $tbase$245$i; + HEAP32[((32544 + 452) | 0) >> 2] = $tsize$244$i; + HEAP32[((32544 + 460) | 0) >> 2] = 0; + $641 = HEAP32[33016 >> 2] | 0; + HEAP32[((32544 + 36) | 0) >> 2] = $641; + HEAP32[((32544 + 32) | 0) >> 2] = -1; + $i$02$i$i = 0; + while (1) { + $642 = $i$02$i$i << 1; + $643 = (((32544 + ($642 << 2)) | 0) + 40) | 0; + $$sum$i$i = ($642 + 3) | 0; + $644 = (((32544 + ($$sum$i$i << 2)) | 0) + 40) | 0; + HEAP32[$644 >> 2] = $643; + $$sum1$i$i = ($642 + 2) | 0; + $645 = (((32544 + ($$sum1$i$i << 2)) | 0) + 40) | 0; + HEAP32[$645 >> 2] = $643; + $646 = ($i$02$i$i + 1) | 0; + $exitcond$i$i = ($646 | 0) == 32; + if ($exitcond$i$i) { + break; + } else { + $i$02$i$i = $646; + } + } + $647 = ($tsize$244$i + -40) | 0; + $648 = ($tbase$245$i + 8) | 0; + $649 = $648; + $650 = $649 & 7; + $651 = ($650 | 0) == 0; + if ($651) { + $655 = 0; + } else { + $652 = (0 - $649) | 0; + $653 = $652 & 7; + $655 = $653; + } + $654 = ($tbase$245$i + $655) | 0; + $656 = ($647 - $655) | 0; + HEAP32[((32544 + 24) | 0) >> 2] = $654; + HEAP32[((32544 + 12) | 0) >> 2] = $656; + $657 = $656 | 1; + $$sum$i12$i = ($655 + 4) | 0; + $658 = ($tbase$245$i + $$sum$i12$i) | 0; + HEAP32[$658 >> 2] = $657; + $$sum2$i$i = ($tsize$244$i + -36) | 0; + $659 = ($tbase$245$i + $$sum2$i$i) | 0; + HEAP32[$659 >> 2] = 40; + $660 = HEAP32[((33016 + 16) | 0) >> 2] | 0; + HEAP32[((32544 + 28) | 0) >> 2] = $660; + } else { + $sp$073$i = (32544 + 448) | 0; + while (1) { + $661 = HEAP32[$sp$073$i >> 2] | 0; + $662 = ($sp$073$i + 4) | 0; + $663 = HEAP32[$662 >> 2] | 0; + $664 = ($661 + $663) | 0; + $665 = ($tbase$245$i | 0) == ($664 | 0); + if ($665) { + label = 213; + break; + } + $666 = ($sp$073$i + 8) | 0; + $667 = HEAP32[$666 >> 2] | 0; + $668 = ($667 | 0) == (0 | 0); + if ($668) { + break; + } else { + $sp$073$i = $667; + } + } + if ((label | 0) == 213) { + $669 = ($sp$073$i + 12) | 0; + $670 = HEAP32[$669 >> 2] | 0; + $671 = $670 & 8; + $672 = ($671 | 0) == 0; + if ($672) { + $673 = $636 >>> 0 >= $661 >>> 0; + $674 = $636 >>> 0 < $tbase$245$i >>> 0; + $or$cond47$i = $673 & $674; + if ($or$cond47$i) { + $675 = ($663 + $tsize$244$i) | 0; + HEAP32[$662 >> 2] = $675; + $676 = HEAP32[((32544 + 12) | 0) >> 2] | 0; + $677 = ($676 + $tsize$244$i) | 0; + $678 = ($636 + 8) | 0; + $679 = $678; + $680 = $679 & 7; + $681 = ($680 | 0) == 0; + if ($681) { + $685 = 0; + } else { + $682 = (0 - $679) | 0; + $683 = $682 & 7; + $685 = $683; + } + $684 = ($636 + $685) | 0; + $686 = ($677 - $685) | 0; + HEAP32[((32544 + 24) | 0) >> 2] = $684; + HEAP32[((32544 + 12) | 0) >> 2] = $686; + $687 = $686 | 1; + $$sum$i16$i = ($685 + 4) | 0; + $688 = ($636 + $$sum$i16$i) | 0; + HEAP32[$688 >> 2] = $687; + $$sum2$i17$i = ($677 + 4) | 0; + $689 = ($636 + $$sum2$i17$i) | 0; + HEAP32[$689 >> 2] = 40; + $690 = HEAP32[((33016 + 16) | 0) >> 2] | 0; + HEAP32[((32544 + 28) | 0) >> 2] = $690; + break; + } + } + } + $691 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $692 = $tbase$245$i >>> 0 < $691 >>> 0; + if ($692) { + HEAP32[((32544 + 16) | 0) >> 2] = $tbase$245$i; + $756 = $tbase$245$i; + } else { + $756 = $691; + } + $693 = ($tbase$245$i + $tsize$244$i) | 0; + $sp$166$i = (32544 + 448) | 0; + while (1) { + $694 = HEAP32[$sp$166$i >> 2] | 0; + $695 = ($694 | 0) == ($693 | 0); + if ($695) { + label = 223; + break; + } + $696 = ($sp$166$i + 8) | 0; + $697 = HEAP32[$696 >> 2] | 0; + $698 = ($697 | 0) == (0 | 0); + if ($698) { + break; + } else { + $sp$166$i = $697; + } + } + if ((label | 0) == 223) { + $699 = ($sp$166$i + 12) | 0; + $700 = HEAP32[$699 >> 2] | 0; + $701 = $700 & 8; + $702 = ($701 | 0) == 0; + if ($702) { + HEAP32[$sp$166$i >> 2] = $tbase$245$i; + $703 = ($sp$166$i + 4) | 0; + $704 = HEAP32[$703 >> 2] | 0; + $705 = ($704 + $tsize$244$i) | 0; + HEAP32[$703 >> 2] = $705; + $706 = ($tbase$245$i + 8) | 0; + $707 = $706; + $708 = $707 & 7; + $709 = ($708 | 0) == 0; + if ($709) { + $713 = 0; + } else { + $710 = (0 - $707) | 0; + $711 = $710 & 7; + $713 = $711; + } + $712 = ($tbase$245$i + $713) | 0; + $$sum102$i = ($tsize$244$i + 8) | 0; + $714 = ($tbase$245$i + $$sum102$i) | 0; + $715 = $714; + $716 = $715 & 7; + $717 = ($716 | 0) == 0; + if ($717) { + $720 = 0; + } else { + $718 = (0 - $715) | 0; + $719 = $718 & 7; + $720 = $719; + } + $$sum103$i = ($720 + $tsize$244$i) | 0; + $721 = ($tbase$245$i + $$sum103$i) | 0; + $722 = $721; + $723 = $712; + $724 = ($722 - $723) | 0; + $$sum$i19$i = ($713 + $nb$0) | 0; + $725 = ($tbase$245$i + $$sum$i19$i) | 0; + $726 = ($724 - $nb$0) | 0; + $727 = $nb$0 | 3; + $$sum1$i20$i = ($713 + 4) | 0; + $728 = ($tbase$245$i + $$sum1$i20$i) | 0; + HEAP32[$728 >> 2] = $727; + $729 = ($721 | 0) == ($636 | 0); + L345: do { + if ($729) { + $730 = HEAP32[((32544 + 12) | 0) >> 2] | 0; + $731 = ($730 + $726) | 0; + HEAP32[((32544 + 12) | 0) >> 2] = $731; + HEAP32[((32544 + 24) | 0) >> 2] = $725; + $732 = $731 | 1; + $$sum42$i$i = ($$sum$i19$i + 4) | 0; + $733 = ($tbase$245$i + $$sum42$i$i) | 0; + HEAP32[$733 >> 2] = $732; + } else { + $734 = HEAP32[((32544 + 20) | 0) >> 2] | 0; + $735 = ($721 | 0) == ($734 | 0); + if ($735) { + $736 = HEAP32[((32544 + 8) | 0) >> 2] | 0; + $737 = ($736 + $726) | 0; + HEAP32[((32544 + 8) | 0) >> 2] = $737; + HEAP32[((32544 + 20) | 0) >> 2] = $725; + $738 = $737 | 1; + $$sum40$i$i = ($$sum$i19$i + 4) | 0; + $739 = ($tbase$245$i + $$sum40$i$i) | 0; + HEAP32[$739 >> 2] = $738; + $$sum41$i$i = ($737 + $$sum$i19$i) | 0; + $740 = ($tbase$245$i + $$sum41$i$i) | 0; + HEAP32[$740 >> 2] = $737; + break; + } + $$sum2$i21$i = ($tsize$244$i + 4) | 0; + $$sum104$i = ($$sum2$i21$i + $720) | 0; + $741 = ($tbase$245$i + $$sum104$i) | 0; + $742 = HEAP32[$741 >> 2] | 0; + $743 = $742 & 3; + $744 = ($743 | 0) == 1; + if ($744) { + $745 = $742 & -8; + $746 = $742 >>> 3; + $747 = $742 >>> 0 < 256; + L353: do { + if ($747) { + $$sum3738$i$i = $720 | 8; + $$sum114$i = ($$sum3738$i$i + $tsize$244$i) | 0; + $748 = ($tbase$245$i + $$sum114$i) | 0; + $749 = HEAP32[$748 >> 2] | 0; + $$sum39$i$i = ($tsize$244$i + 12) | 0; + $$sum115$i = ($$sum39$i$i + $720) | 0; + $750 = ($tbase$245$i + $$sum115$i) | 0; + $751 = HEAP32[$750 >> 2] | 0; + $752 = $746 << 1; + $753 = (((32544 + ($752 << 2)) | 0) + 40) | 0; + $754 = ($749 | 0) == ($753 | 0); + do { + if (!$754) { + $755 = $749 >>> 0 < $756 >>> 0; + if ($755) { + _abort(); + // unreachable; + } + $757 = ($749 + 12) | 0; + $758 = HEAP32[$757 >> 2] | 0; + $759 = ($758 | 0) == ($721 | 0); + if ($759) { + break; + } + _abort(); + // unreachable; + } + } while (0); + $760 = ($751 | 0) == ($749 | 0); + if ($760) { + $761 = 1 << $746; + $762 = $761 ^ -1; + $763 = HEAP32[32544 >> 2] | 0; + $764 = $763 & $762; + HEAP32[32544 >> 2] = $764; + break; + } + $765 = ($751 | 0) == ($753 | 0); + do { + if ($765) { + $$pre58$i$i = ($751 + 8) | 0; + $$pre$phi59$i$iZ2D = $$pre58$i$i; + } else { + $766 = $751 >>> 0 < $756 >>> 0; + if ($766) { + _abort(); + // unreachable; + } + $767 = ($751 + 8) | 0; + $768 = HEAP32[$767 >> 2] | 0; + $769 = ($768 | 0) == ($721 | 0); + if ($769) { + $$pre$phi59$i$iZ2D = $767; + break; + } + _abort(); + // unreachable; + } + } while (0); + $770 = ($749 + 12) | 0; + HEAP32[$770 >> 2] = $751; + HEAP32[$$pre$phi59$i$iZ2D >> 2] = $749; + } else { + $$sum34$i$i = $720 | 24; + $$sum105$i = ($$sum34$i$i + $tsize$244$i) | 0; + $771 = ($tbase$245$i + $$sum105$i) | 0; + $772 = HEAP32[$771 >> 2] | 0; + $$sum5$i$i = ($tsize$244$i + 12) | 0; + $$sum106$i = ($$sum5$i$i + $720) | 0; + $773 = ($tbase$245$i + $$sum106$i) | 0; + $774 = HEAP32[$773 >> 2] | 0; + $775 = ($774 | 0) == ($721 | 0); + do { + if ($775) { + $$sum67$i$i = $720 | 16; + $$sum112$i = ($$sum2$i21$i + $$sum67$i$i) | 0; + $785 = ($tbase$245$i + $$sum112$i) | 0; + $786 = HEAP32[$785 >> 2] | 0; + $787 = ($786 | 0) == (0 | 0); + if ($787) { + $$sum113$i = ($$sum67$i$i + $tsize$244$i) | 0; + $788 = ($tbase$245$i + $$sum113$i) | 0; + $789 = HEAP32[$788 >> 2] | 0; + $790 = ($789 | 0) == (0 | 0); + if ($790) { + $R$1$i$i = 0; + break; + } else { + $R$0$i$i = $789; + $RP$0$i$i = $788; + } + } else { + $R$0$i$i = $786; + $RP$0$i$i = $785; + } + while (1) { + $791 = ($R$0$i$i + 20) | 0; + $792 = HEAP32[$791 >> 2] | 0; + $793 = ($792 | 0) == (0 | 0); + if (!$793) { + $R$0$i$i = $792; + $RP$0$i$i = $791; + continue; + } + $794 = ($R$0$i$i + 16) | 0; + $795 = HEAP32[$794 >> 2] | 0; + $796 = ($795 | 0) == (0 | 0); + if ($796) { + break; + } else { + $R$0$i$i = $795; + $RP$0$i$i = $794; + } + } + $797 = $RP$0$i$i >>> 0 < $756 >>> 0; + if ($797) { + _abort(); + // unreachable; + } else { + HEAP32[$RP$0$i$i >> 2] = 0; + $R$1$i$i = $R$0$i$i; + break; + } + } else { + $$sum3536$i$i = $720 | 8; + $$sum107$i = ($$sum3536$i$i + $tsize$244$i) | 0; + $776 = ($tbase$245$i + $$sum107$i) | 0; + $777 = HEAP32[$776 >> 2] | 0; + $778 = $777 >>> 0 < $756 >>> 0; + if ($778) { + _abort(); + // unreachable; + } + $779 = ($777 + 12) | 0; + $780 = HEAP32[$779 >> 2] | 0; + $781 = ($780 | 0) == ($721 | 0); + if (!$781) { + _abort(); + // unreachable; + } + $782 = ($774 + 8) | 0; + $783 = HEAP32[$782 >> 2] | 0; + $784 = ($783 | 0) == ($721 | 0); + if ($784) { + HEAP32[$779 >> 2] = $774; + HEAP32[$782 >> 2] = $777; + $R$1$i$i = $774; + break; + } else { + _abort(); + // unreachable; + } + } + } while (0); + $798 = ($772 | 0) == (0 | 0); + if ($798) { + break; + } + $$sum30$i$i = ($tsize$244$i + 28) | 0; + $$sum108$i = ($$sum30$i$i + $720) | 0; + $799 = ($tbase$245$i + $$sum108$i) | 0; + $800 = HEAP32[$799 >> 2] | 0; + $801 = (((32544 + ($800 << 2)) | 0) + 304) | 0; + $802 = HEAP32[$801 >> 2] | 0; + $803 = ($721 | 0) == ($802 | 0); + do { + if ($803) { + HEAP32[$801 >> 2] = $R$1$i$i; + $cond$i$i = ($R$1$i$i | 0) == (0 | 0); + if (!$cond$i$i) { + break; + } + $804 = 1 << $800; + $805 = $804 ^ -1; + $806 = HEAP32[((32544 + 4) | 0) >> 2] | 0; + $807 = $806 & $805; + HEAP32[((32544 + 4) | 0) >> 2] = $807; + break L353; + } else { + $808 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $809 = $772 >>> 0 < $808 >>> 0; + if ($809) { + _abort(); + // unreachable; + } + $810 = ($772 + 16) | 0; + $811 = HEAP32[$810 >> 2] | 0; + $812 = ($811 | 0) == ($721 | 0); + if ($812) { + HEAP32[$810 >> 2] = $R$1$i$i; + } else { + $813 = ($772 + 20) | 0; + HEAP32[$813 >> 2] = $R$1$i$i; + } + $814 = ($R$1$i$i | 0) == (0 | 0); + if ($814) { + break L353; + } + } + } while (0); + $815 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $816 = $R$1$i$i >>> 0 < $815 >>> 0; + if ($816) { + _abort(); + // unreachable; + } + $817 = ($R$1$i$i + 24) | 0; + HEAP32[$817 >> 2] = $772; + $$sum3132$i$i = $720 | 16; + $$sum109$i = ($$sum3132$i$i + $tsize$244$i) | 0; + $818 = ($tbase$245$i + $$sum109$i) | 0; + $819 = HEAP32[$818 >> 2] | 0; + $820 = ($819 | 0) == (0 | 0); + do { + if (!$820) { + $821 = $819 >>> 0 < $815 >>> 0; + if ($821) { + _abort(); + // unreachable; + } else { + $822 = ($R$1$i$i + 16) | 0; + HEAP32[$822 >> 2] = $819; + $823 = ($819 + 24) | 0; + HEAP32[$823 >> 2] = $R$1$i$i; + break; + } + } + } while (0); + $$sum110$i = ($$sum2$i21$i + $$sum3132$i$i) | 0; + $824 = ($tbase$245$i + $$sum110$i) | 0; + $825 = HEAP32[$824 >> 2] | 0; + $826 = ($825 | 0) == (0 | 0); + if ($826) { + break; + } + $827 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $828 = $825 >>> 0 < $827 >>> 0; + if ($828) { + _abort(); + // unreachable; + } else { + $829 = ($R$1$i$i + 20) | 0; + HEAP32[$829 >> 2] = $825; + $830 = ($825 + 24) | 0; + HEAP32[$830 >> 2] = $R$1$i$i; + break; + } + } + } while (0); + $$sum9$i$i = $745 | $720; + $$sum111$i = ($$sum9$i$i + $tsize$244$i) | 0; + $831 = ($tbase$245$i + $$sum111$i) | 0; + $832 = ($745 + $726) | 0; + $oldfirst$0$i$i = $831; + $qsize$0$i$i = $832; + } else { + $oldfirst$0$i$i = $721; + $qsize$0$i$i = $726; + } + $833 = ($oldfirst$0$i$i + 4) | 0; + $834 = HEAP32[$833 >> 2] | 0; + $835 = $834 & -2; + HEAP32[$833 >> 2] = $835; + $836 = $qsize$0$i$i | 1; + $$sum10$i$i = ($$sum$i19$i + 4) | 0; + $837 = ($tbase$245$i + $$sum10$i$i) | 0; + HEAP32[$837 >> 2] = $836; + $$sum11$i22$i = ($qsize$0$i$i + $$sum$i19$i) | 0; + $838 = ($tbase$245$i + $$sum11$i22$i) | 0; + HEAP32[$838 >> 2] = $qsize$0$i$i; + $839 = $qsize$0$i$i >>> 3; + $840 = $qsize$0$i$i >>> 0 < 256; + if ($840) { + $841 = $839 << 1; + $842 = (((32544 + ($841 << 2)) | 0) + 40) | 0; + $843 = HEAP32[32544 >> 2] | 0; + $844 = 1 << $839; + $845 = $843 & $844; + $846 = ($845 | 0) == 0; + do { + if ($846) { + $847 = $843 | $844; + HEAP32[32544 >> 2] = $847; + $$sum26$pre$i$i = ($841 + 2) | 0; + $$pre$i23$i = + (((32544 + ($$sum26$pre$i$i << 2)) | 0) + 40) | 0; + $$pre$phi$i24$iZ2D = $$pre$i23$i; + $F4$0$i$i = $842; + } else { + $$sum29$i$i = ($841 + 2) | 0; + $848 = (((32544 + ($$sum29$i$i << 2)) | 0) + 40) | 0; + $849 = HEAP32[$848 >> 2] | 0; + $850 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $851 = $849 >>> 0 < $850 >>> 0; + if (!$851) { + $$pre$phi$i24$iZ2D = $848; + $F4$0$i$i = $849; + break; + } + _abort(); + // unreachable; + } + } while (0); + HEAP32[$$pre$phi$i24$iZ2D >> 2] = $725; + $852 = ($F4$0$i$i + 12) | 0; + HEAP32[$852 >> 2] = $725; + $$sum27$i$i = ($$sum$i19$i + 8) | 0; + $853 = ($tbase$245$i + $$sum27$i$i) | 0; + HEAP32[$853 >> 2] = $F4$0$i$i; + $$sum28$i$i = ($$sum$i19$i + 12) | 0; + $854 = ($tbase$245$i + $$sum28$i$i) | 0; + HEAP32[$854 >> 2] = $842; + break; + } + $855 = $qsize$0$i$i >>> 8; + $856 = ($855 | 0) == 0; + do { + if ($856) { + $I7$0$i$i = 0; + } else { + $857 = $qsize$0$i$i >>> 0 > 16777215; + if ($857) { + $I7$0$i$i = 31; + break; + } + $858 = ($855 + 1048320) | 0; + $859 = $858 >>> 16; + $860 = $859 & 8; + $861 = $855 << $860; + $862 = ($861 + 520192) | 0; + $863 = $862 >>> 16; + $864 = $863 & 4; + $865 = $864 | $860; + $866 = $861 << $864; + $867 = ($866 + 245760) | 0; + $868 = $867 >>> 16; + $869 = $868 & 2; + $870 = $865 | $869; + $871 = (14 - $870) | 0; + $872 = $866 << $869; + $873 = $872 >>> 15; + $874 = ($871 + $873) | 0; + $875 = $874 << 1; + $876 = ($874 + 7) | 0; + $877 = $qsize$0$i$i >>> $876; + $878 = $877 & 1; + $879 = $878 | $875; + $I7$0$i$i = $879; + } + } while (0); + $880 = (((32544 + ($I7$0$i$i << 2)) | 0) + 304) | 0; + $$sum12$i$i = ($$sum$i19$i + 28) | 0; + $881 = ($tbase$245$i + $$sum12$i$i) | 0; + HEAP32[$881 >> 2] = $I7$0$i$i; + $$sum13$i$i = ($$sum$i19$i + 16) | 0; + $882 = ($tbase$245$i + $$sum13$i$i) | 0; + $$sum14$i$i = ($$sum$i19$i + 20) | 0; + $883 = ($tbase$245$i + $$sum14$i$i) | 0; + HEAP32[$883 >> 2] = 0; + HEAP32[$882 >> 2] = 0; + $884 = HEAP32[((32544 + 4) | 0) >> 2] | 0; + $885 = 1 << $I7$0$i$i; + $886 = $884 & $885; + $887 = ($886 | 0) == 0; + if ($887) { + $888 = $884 | $885; + HEAP32[((32544 + 4) | 0) >> 2] = $888; + HEAP32[$880 >> 2] = $725; + $$sum15$i$i = ($$sum$i19$i + 24) | 0; + $889 = ($tbase$245$i + $$sum15$i$i) | 0; + HEAP32[$889 >> 2] = $880; + $$sum16$i$i = ($$sum$i19$i + 12) | 0; + $890 = ($tbase$245$i + $$sum16$i$i) | 0; + HEAP32[$890 >> 2] = $725; + $$sum17$i$i = ($$sum$i19$i + 8) | 0; + $891 = ($tbase$245$i + $$sum17$i$i) | 0; + HEAP32[$891 >> 2] = $725; + break; + } + $892 = HEAP32[$880 >> 2] | 0; + $893 = ($I7$0$i$i | 0) == 31; + if ($893) { + $901 = 0; + } else { + $894 = $I7$0$i$i >>> 1; + $895 = (25 - $894) | 0; + $901 = $895; + } + $896 = ($892 + 4) | 0; + $897 = HEAP32[$896 >> 2] | 0; + $898 = $897 & -8; + $899 = ($898 | 0) == ($qsize$0$i$i | 0); + L442: do { + if ($899) { + $T$0$lcssa$i26$i = $892; + } else { + $900 = $qsize$0$i$i << $901; + $K8$053$i$i = $900; + $T$052$i$i = $892; + while (1) { + $908 = $K8$053$i$i >>> 31; + $909 = ((($T$052$i$i + ($908 << 2)) | 0) + 16) | 0; + $904 = HEAP32[$909 >> 2] | 0; + $910 = ($904 | 0) == (0 | 0); + if ($910) { + break; + } + $902 = $K8$053$i$i << 1; + $903 = ($904 + 4) | 0; + $905 = HEAP32[$903 >> 2] | 0; + $906 = $905 & -8; + $907 = ($906 | 0) == ($qsize$0$i$i | 0); + if ($907) { + $T$0$lcssa$i26$i = $904; + break L442; + } else { + $K8$053$i$i = $902; + $T$052$i$i = $904; + } + } + $911 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $912 = $909 >>> 0 < $911 >>> 0; + if ($912) { + _abort(); + // unreachable; + } else { + HEAP32[$909 >> 2] = $725; + $$sum23$i$i = ($$sum$i19$i + 24) | 0; + $913 = ($tbase$245$i + $$sum23$i$i) | 0; + HEAP32[$913 >> 2] = $T$052$i$i; + $$sum24$i$i = ($$sum$i19$i + 12) | 0; + $914 = ($tbase$245$i + $$sum24$i$i) | 0; + HEAP32[$914 >> 2] = $725; + $$sum25$i$i = ($$sum$i19$i + 8) | 0; + $915 = ($tbase$245$i + $$sum25$i$i) | 0; + HEAP32[$915 >> 2] = $725; + break L345; + } + } + } while (0); + $916 = ($T$0$lcssa$i26$i + 8) | 0; + $917 = HEAP32[$916 >> 2] | 0; + $918 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $919 = $T$0$lcssa$i26$i >>> 0 >= $918 >>> 0; + $920 = $917 >>> 0 >= $918 >>> 0; + $or$cond$i27$i = $919 & $920; + if ($or$cond$i27$i) { + $921 = ($917 + 12) | 0; + HEAP32[$921 >> 2] = $725; + HEAP32[$916 >> 2] = $725; + $$sum20$i$i = ($$sum$i19$i + 8) | 0; + $922 = ($tbase$245$i + $$sum20$i$i) | 0; + HEAP32[$922 >> 2] = $917; + $$sum21$i$i = ($$sum$i19$i + 12) | 0; + $923 = ($tbase$245$i + $$sum21$i$i) | 0; + HEAP32[$923 >> 2] = $T$0$lcssa$i26$i; + $$sum22$i$i = ($$sum$i19$i + 24) | 0; + $924 = ($tbase$245$i + $$sum22$i$i) | 0; + HEAP32[$924 >> 2] = 0; + break; + } else { + _abort(); + // unreachable; + } + } + } while (0); + $$sum1819$i$i = $713 | 8; + $925 = ($tbase$245$i + $$sum1819$i$i) | 0; + $mem$0 = $925; + STACKTOP = sp; + return $mem$0 | 0; + } + } + $sp$0$i$i$i = (32544 + 448) | 0; + while (1) { + $926 = HEAP32[$sp$0$i$i$i >> 2] | 0; + $927 = $926 >>> 0 > $636 >>> 0; + if (!$927) { + $928 = ($sp$0$i$i$i + 4) | 0; + $929 = HEAP32[$928 >> 2] | 0; + $930 = ($926 + $929) | 0; + $931 = $930 >>> 0 > $636 >>> 0; + if ($931) { + break; + } + } + $932 = ($sp$0$i$i$i + 8) | 0; + $933 = HEAP32[$932 >> 2] | 0; + $sp$0$i$i$i = $933; + } + $$sum$i13$i = ($929 + -47) | 0; + $$sum1$i14$i = ($929 + -39) | 0; + $934 = ($926 + $$sum1$i14$i) | 0; + $935 = $934; + $936 = $935 & 7; + $937 = ($936 | 0) == 0; + if ($937) { + $940 = 0; + } else { + $938 = (0 - $935) | 0; + $939 = $938 & 7; + $940 = $939; + } + $$sum2$i15$i = ($$sum$i13$i + $940) | 0; + $941 = ($926 + $$sum2$i15$i) | 0; + $942 = ($636 + 16) | 0; + $943 = $941 >>> 0 < $942 >>> 0; + $944 = $943 ? $636 : $941; + $945 = ($944 + 8) | 0; + $946 = ($tsize$244$i + -40) | 0; + $947 = ($tbase$245$i + 8) | 0; + $948 = $947; + $949 = $948 & 7; + $950 = ($949 | 0) == 0; + if ($950) { + $954 = 0; + } else { + $951 = (0 - $948) | 0; + $952 = $951 & 7; + $954 = $952; + } + $953 = ($tbase$245$i + $954) | 0; + $955 = ($946 - $954) | 0; + HEAP32[((32544 + 24) | 0) >> 2] = $953; + HEAP32[((32544 + 12) | 0) >> 2] = $955; + $956 = $955 | 1; + $$sum$i$i$i = ($954 + 4) | 0; + $957 = ($tbase$245$i + $$sum$i$i$i) | 0; + HEAP32[$957 >> 2] = $956; + $$sum2$i$i$i = ($tsize$244$i + -36) | 0; + $958 = ($tbase$245$i + $$sum2$i$i$i) | 0; + HEAP32[$958 >> 2] = 40; + $959 = HEAP32[((33016 + 16) | 0) >> 2] | 0; + HEAP32[((32544 + 28) | 0) >> 2] = $959; + $960 = ($944 + 4) | 0; + HEAP32[$960 >> 2] = 27; + HEAP32[($945 + 0) >> 2] = HEAP32[(((32544 + 448) | 0) + 0) >> 2] | 0; + HEAP32[($945 + 4) >> 2] = HEAP32[(((32544 + 448) | 0) + 4) >> 2] | 0; + HEAP32[($945 + 8) >> 2] = HEAP32[(((32544 + 448) | 0) + 8) >> 2] | 0; + HEAP32[($945 + 12) >> 2] = + HEAP32[(((32544 + 448) | 0) + 12) >> 2] | 0; + HEAP32[((32544 + 448) | 0) >> 2] = $tbase$245$i; + HEAP32[((32544 + 452) | 0) >> 2] = $tsize$244$i; + HEAP32[((32544 + 460) | 0) >> 2] = 0; + HEAP32[((32544 + 456) | 0) >> 2] = $945; + $961 = ($944 + 28) | 0; + HEAP32[$961 >> 2] = 7; + $962 = ($944 + 32) | 0; + $963 = $962 >>> 0 < $930 >>> 0; + if ($963) { + $965 = $961; + while (1) { + $964 = ($965 + 4) | 0; + HEAP32[$964 >> 2] = 7; + $966 = ($965 + 8) | 0; + $967 = $966 >>> 0 < $930 >>> 0; + if ($967) { + $965 = $964; + } else { + break; + } + } + } + $968 = ($944 | 0) == ($636 | 0); + if (!$968) { + $969 = $944; + $970 = $636; + $971 = ($969 - $970) | 0; + $972 = ($636 + $971) | 0; + $$sum3$i$i = ($971 + 4) | 0; + $973 = ($636 + $$sum3$i$i) | 0; + $974 = HEAP32[$973 >> 2] | 0; + $975 = $974 & -2; + HEAP32[$973 >> 2] = $975; + $976 = $971 | 1; + $977 = ($636 + 4) | 0; + HEAP32[$977 >> 2] = $976; + HEAP32[$972 >> 2] = $971; + $978 = $971 >>> 3; + $979 = $971 >>> 0 < 256; + if ($979) { + $980 = $978 << 1; + $981 = (((32544 + ($980 << 2)) | 0) + 40) | 0; + $982 = HEAP32[32544 >> 2] | 0; + $983 = 1 << $978; + $984 = $982 & $983; + $985 = ($984 | 0) == 0; + do { + if ($985) { + $986 = $982 | $983; + HEAP32[32544 >> 2] = $986; + $$sum10$pre$i$i = ($980 + 2) | 0; + $$pre$i$i = (((32544 + ($$sum10$pre$i$i << 2)) | 0) + 40) | 0; + $$pre$phi$i$iZ2D = $$pre$i$i; + $F$0$i$i = $981; + } else { + $$sum11$i$i = ($980 + 2) | 0; + $987 = (((32544 + ($$sum11$i$i << 2)) | 0) + 40) | 0; + $988 = HEAP32[$987 >> 2] | 0; + $989 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $990 = $988 >>> 0 < $989 >>> 0; + if (!$990) { + $$pre$phi$i$iZ2D = $987; + $F$0$i$i = $988; + break; + } + _abort(); + // unreachable; + } + } while (0); + HEAP32[$$pre$phi$i$iZ2D >> 2] = $636; + $991 = ($F$0$i$i + 12) | 0; + HEAP32[$991 >> 2] = $636; + $992 = ($636 + 8) | 0; + HEAP32[$992 >> 2] = $F$0$i$i; + $993 = ($636 + 12) | 0; + HEAP32[$993 >> 2] = $981; + break; + } + $994 = $971 >>> 8; + $995 = ($994 | 0) == 0; + if ($995) { + $I1$0$i$i = 0; + } else { + $996 = $971 >>> 0 > 16777215; + if ($996) { + $I1$0$i$i = 31; + } else { + $997 = ($994 + 1048320) | 0; + $998 = $997 >>> 16; + $999 = $998 & 8; + $1000 = $994 << $999; + $1001 = ($1000 + 520192) | 0; + $1002 = $1001 >>> 16; + $1003 = $1002 & 4; + $1004 = $1003 | $999; + $1005 = $1000 << $1003; + $1006 = ($1005 + 245760) | 0; + $1007 = $1006 >>> 16; + $1008 = $1007 & 2; + $1009 = $1004 | $1008; + $1010 = (14 - $1009) | 0; + $1011 = $1005 << $1008; + $1012 = $1011 >>> 15; + $1013 = ($1010 + $1012) | 0; + $1014 = $1013 << 1; + $1015 = ($1013 + 7) | 0; + $1016 = $971 >>> $1015; + $1017 = $1016 & 1; + $1018 = $1017 | $1014; + $I1$0$i$i = $1018; + } + } + $1019 = (((32544 + ($I1$0$i$i << 2)) | 0) + 304) | 0; + $1020 = ($636 + 28) | 0; + $I1$0$c$i$i = $I1$0$i$i; + HEAP32[$1020 >> 2] = $I1$0$c$i$i; + $1021 = ($636 + 20) | 0; + HEAP32[$1021 >> 2] = 0; + $1022 = ($636 + 16) | 0; + HEAP32[$1022 >> 2] = 0; + $1023 = HEAP32[((32544 + 4) | 0) >> 2] | 0; + $1024 = 1 << $I1$0$i$i; + $1025 = $1023 & $1024; + $1026 = ($1025 | 0) == 0; + if ($1026) { + $1027 = $1023 | $1024; + HEAP32[((32544 + 4) | 0) >> 2] = $1027; + HEAP32[$1019 >> 2] = $636; + $1028 = ($636 + 24) | 0; + HEAP32[$1028 >> 2] = $1019; + $1029 = ($636 + 12) | 0; + HEAP32[$1029 >> 2] = $636; + $1030 = ($636 + 8) | 0; + HEAP32[$1030 >> 2] = $636; + break; + } + $1031 = HEAP32[$1019 >> 2] | 0; + $1032 = ($I1$0$i$i | 0) == 31; + if ($1032) { + $1040 = 0; + } else { + $1033 = $I1$0$i$i >>> 1; + $1034 = (25 - $1033) | 0; + $1040 = $1034; + } + $1035 = ($1031 + 4) | 0; + $1036 = HEAP32[$1035 >> 2] | 0; + $1037 = $1036 & -8; + $1038 = ($1037 | 0) == ($971 | 0); + L493: do { + if ($1038) { + $T$0$lcssa$i$i = $1031; + } else { + $1039 = $971 << $1040; + $K2$015$i$i = $1039; + $T$014$i$i = $1031; + while (1) { + $1047 = $K2$015$i$i >>> 31; + $1048 = ((($T$014$i$i + ($1047 << 2)) | 0) + 16) | 0; + $1043 = HEAP32[$1048 >> 2] | 0; + $1049 = ($1043 | 0) == (0 | 0); + if ($1049) { + break; + } + $1041 = $K2$015$i$i << 1; + $1042 = ($1043 + 4) | 0; + $1044 = HEAP32[$1042 >> 2] | 0; + $1045 = $1044 & -8; + $1046 = ($1045 | 0) == ($971 | 0); + if ($1046) { + $T$0$lcssa$i$i = $1043; + break L493; + } else { + $K2$015$i$i = $1041; + $T$014$i$i = $1043; + } + } + $1050 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $1051 = $1048 >>> 0 < $1050 >>> 0; + if ($1051) { + _abort(); + // unreachable; + } else { + HEAP32[$1048 >> 2] = $636; + $1052 = ($636 + 24) | 0; + HEAP32[$1052 >> 2] = $T$014$i$i; + $1053 = ($636 + 12) | 0; + HEAP32[$1053 >> 2] = $636; + $1054 = ($636 + 8) | 0; + HEAP32[$1054 >> 2] = $636; + break L308; + } + } + } while (0); + $1055 = ($T$0$lcssa$i$i + 8) | 0; + $1056 = HEAP32[$1055 >> 2] | 0; + $1057 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $1058 = $T$0$lcssa$i$i >>> 0 >= $1057 >>> 0; + $1059 = $1056 >>> 0 >= $1057 >>> 0; + $or$cond$i$i = $1058 & $1059; + if ($or$cond$i$i) { + $1060 = ($1056 + 12) | 0; + HEAP32[$1060 >> 2] = $636; + HEAP32[$1055 >> 2] = $636; + $1061 = ($636 + 8) | 0; + HEAP32[$1061 >> 2] = $1056; + $1062 = ($636 + 12) | 0; + HEAP32[$1062 >> 2] = $T$0$lcssa$i$i; + $1063 = ($636 + 24) | 0; + HEAP32[$1063 >> 2] = 0; + break; + } else { + _abort(); + // unreachable; + } + } + } + } while (0); + $1064 = HEAP32[((32544 + 12) | 0) >> 2] | 0; + $1065 = $1064 >>> 0 > $nb$0 >>> 0; + if ($1065) { + $1066 = ($1064 - $nb$0) | 0; + HEAP32[((32544 + 12) | 0) >> 2] = $1066; + $1067 = HEAP32[((32544 + 24) | 0) >> 2] | 0; + $1068 = ($1067 + $nb$0) | 0; + HEAP32[((32544 + 24) | 0) >> 2] = $1068; + $1069 = $1066 | 1; + $$sum$i32 = ($nb$0 + 4) | 0; + $1070 = ($1067 + $$sum$i32) | 0; + HEAP32[$1070 >> 2] = $1069; + $1071 = $nb$0 | 3; + $1072 = ($1067 + 4) | 0; + HEAP32[$1072 >> 2] = $1071; + $1073 = ($1067 + 8) | 0; + $mem$0 = $1073; + STACKTOP = sp; + return $mem$0 | 0; + } + } + $1074 = ___errno_location() | 0; + HEAP32[$1074 >> 2] = 12; + $mem$0 = 0; + STACKTOP = sp; + return $mem$0 | 0; + } + function _free($mem) { + $mem = $mem | 0; + var $$pre = 0, + $$pre$phi66Z2D = 0, + $$pre$phi68Z2D = 0, + $$pre$phiZ2D = 0, + $$pre65 = 0, + $$pre67 = 0, + $$sum = 0, + $$sum16$pre = 0, + $$sum17 = 0, + $$sum18 = 0, + $$sum19 = 0, + $$sum2 = 0, + $$sum20 = 0, + $$sum2324 = 0, + $$sum25 = 0, + $$sum26 = 0, + $$sum28 = 0, + $$sum29 = 0, + $$sum3 = 0, + $$sum30 = 0; + var $$sum31 = 0, + $$sum32 = 0, + $$sum33 = 0, + $$sum34 = 0, + $$sum35 = 0, + $$sum36 = 0, + $$sum37 = 0, + $$sum5 = 0, + $$sum67 = 0, + $$sum8 = 0, + $$sum9 = 0, + $0 = 0, + $1 = 0, + $10 = 0, + $100 = 0, + $101 = 0, + $102 = 0, + $103 = 0, + $104 = 0, + $105 = 0; + var $106 = 0, + $107 = 0, + $108 = 0, + $109 = 0, + $11 = 0, + $110 = 0, + $111 = 0, + $112 = 0, + $113 = 0, + $114 = 0, + $115 = 0, + $116 = 0, + $117 = 0, + $118 = 0, + $119 = 0, + $12 = 0, + $120 = 0, + $121 = 0, + $122 = 0, + $123 = 0; + var $124 = 0, + $125 = 0, + $126 = 0, + $127 = 0, + $128 = 0, + $129 = 0, + $13 = 0, + $130 = 0, + $131 = 0, + $132 = 0, + $133 = 0, + $134 = 0, + $135 = 0, + $136 = 0, + $137 = 0, + $138 = 0, + $139 = 0, + $14 = 0, + $140 = 0, + $141 = 0; + var $142 = 0, + $143 = 0, + $144 = 0, + $145 = 0, + $146 = 0, + $147 = 0, + $148 = 0, + $149 = 0, + $15 = 0, + $150 = 0, + $151 = 0, + $152 = 0, + $153 = 0, + $154 = 0, + $155 = 0, + $156 = 0, + $157 = 0, + $158 = 0, + $159 = 0, + $16 = 0; + var $160 = 0, + $161 = 0, + $162 = 0, + $163 = 0, + $164 = 0, + $165 = 0, + $166 = 0, + $167 = 0, + $168 = 0, + $169 = 0, + $17 = 0, + $170 = 0, + $171 = 0, + $172 = 0, + $173 = 0, + $174 = 0, + $175 = 0, + $176 = 0, + $177 = 0, + $178 = 0; + var $179 = 0, + $18 = 0, + $180 = 0, + $181 = 0, + $182 = 0, + $183 = 0, + $184 = 0, + $185 = 0, + $186 = 0, + $187 = 0, + $188 = 0, + $189 = 0, + $19 = 0, + $190 = 0, + $191 = 0, + $192 = 0, + $193 = 0, + $194 = 0, + $195 = 0, + $196 = 0; + var $197 = 0, + $198 = 0, + $199 = 0, + $2 = 0, + $20 = 0, + $200 = 0, + $201 = 0, + $202 = 0, + $203 = 0, + $204 = 0, + $205 = 0, + $206 = 0, + $207 = 0, + $208 = 0, + $209 = 0, + $21 = 0, + $210 = 0, + $211 = 0, + $212 = 0, + $213 = 0; + var $214 = 0, + $215 = 0, + $216 = 0, + $217 = 0, + $218 = 0, + $219 = 0, + $22 = 0, + $220 = 0, + $221 = 0, + $222 = 0, + $223 = 0, + $224 = 0, + $225 = 0, + $226 = 0, + $227 = 0, + $228 = 0, + $229 = 0, + $23 = 0, + $230 = 0, + $231 = 0; + var $232 = 0, + $233 = 0, + $234 = 0, + $235 = 0, + $236 = 0, + $237 = 0, + $238 = 0, + $239 = 0, + $24 = 0, + $240 = 0, + $241 = 0, + $242 = 0, + $243 = 0, + $244 = 0, + $245 = 0, + $246 = 0, + $247 = 0, + $248 = 0, + $249 = 0, + $25 = 0; + var $250 = 0, + $251 = 0, + $252 = 0, + $253 = 0, + $254 = 0, + $255 = 0, + $256 = 0, + $257 = 0, + $258 = 0, + $259 = 0, + $26 = 0, + $260 = 0, + $261 = 0, + $262 = 0, + $263 = 0, + $264 = 0, + $265 = 0, + $266 = 0, + $267 = 0, + $268 = 0; + var $269 = 0, + $27 = 0, + $270 = 0, + $271 = 0, + $272 = 0, + $273 = 0, + $274 = 0, + $275 = 0, + $276 = 0, + $277 = 0, + $278 = 0, + $279 = 0, + $28 = 0, + $280 = 0, + $281 = 0, + $282 = 0, + $283 = 0, + $284 = 0, + $285 = 0, + $286 = 0; + var $287 = 0, + $288 = 0, + $289 = 0, + $29 = 0, + $290 = 0, + $291 = 0, + $292 = 0, + $293 = 0, + $294 = 0, + $295 = 0, + $296 = 0, + $297 = 0, + $298 = 0, + $299 = 0, + $3 = 0, + $30 = 0, + $300 = 0, + $301 = 0, + $302 = 0, + $303 = 0; + var $304 = 0, + $305 = 0, + $306 = 0, + $307 = 0, + $308 = 0, + $309 = 0, + $31 = 0, + $310 = 0, + $311 = 0, + $312 = 0, + $313 = 0, + $314 = 0, + $315 = 0, + $316 = 0, + $317 = 0, + $318 = 0, + $319 = 0, + $32 = 0, + $320 = 0, + $321 = 0; + var $33 = 0, + $34 = 0, + $35 = 0, + $36 = 0, + $37 = 0, + $38 = 0, + $39 = 0, + $4 = 0, + $40 = 0, + $41 = 0, + $42 = 0, + $43 = 0, + $44 = 0, + $45 = 0, + $46 = 0, + $47 = 0, + $48 = 0, + $49 = 0, + $5 = 0, + $50 = 0; + var $51 = 0, + $52 = 0, + $53 = 0, + $54 = 0, + $55 = 0, + $56 = 0, + $57 = 0, + $58 = 0, + $59 = 0, + $6 = 0, + $60 = 0, + $61 = 0, + $62 = 0, + $63 = 0, + $64 = 0, + $65 = 0, + $66 = 0, + $67 = 0, + $68 = 0, + $69 = 0; + var $7 = 0, + $70 = 0, + $71 = 0, + $72 = 0, + $73 = 0, + $74 = 0, + $75 = 0, + $76 = 0, + $77 = 0, + $78 = 0, + $79 = 0, + $8 = 0, + $80 = 0, + $81 = 0, + $82 = 0, + $83 = 0, + $84 = 0, + $85 = 0, + $86 = 0, + $87 = 0; + var $88 = 0, + $89 = 0, + $9 = 0, + $90 = 0, + $91 = 0, + $92 = 0, + $93 = 0, + $94 = 0, + $95 = 0, + $96 = 0, + $97 = 0, + $98 = 0, + $99 = 0, + $F16$0 = 0, + $I18$0 = 0, + $I18$0$c = 0, + $K19$058 = 0, + $R$0 = 0, + $R$1 = 0, + $R7$0 = 0; + var $R7$1 = 0, + $RP$0 = 0, + $RP9$0 = 0, + $T$0$lcssa = 0, + $T$057 = 0, + $cond = 0, + $cond54 = 0, + $or$cond = 0, + $p$0 = 0, + $psize$0 = 0, + $psize$1 = 0, + $sp$0$i = 0, + $sp$0$in$i = 0, + label = 0, + sp = 0; + sp = STACKTOP; + $0 = ($mem | 0) == (0 | 0); + if ($0) { + STACKTOP = sp; + return; + } + $1 = ($mem + -8) | 0; + $2 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $3 = $1 >>> 0 < $2 >>> 0; + if ($3) { + _abort(); + // unreachable; + } + $4 = ($mem + -4) | 0; + $5 = HEAP32[$4 >> 2] | 0; + $6 = $5 & 3; + $7 = ($6 | 0) == 1; + if ($7) { + _abort(); + // unreachable; + } + $8 = $5 & -8; + $$sum = ($8 + -8) | 0; + $9 = ($mem + $$sum) | 0; + $10 = $5 & 1; + $11 = ($10 | 0) == 0; + do { + if ($11) { + $12 = HEAP32[$1 >> 2] | 0; + $13 = ($6 | 0) == 0; + if ($13) { + STACKTOP = sp; + return; + } + $$sum2 = (-8 - $12) | 0; + $14 = ($mem + $$sum2) | 0; + $15 = ($12 + $8) | 0; + $16 = $14 >>> 0 < $2 >>> 0; + if ($16) { + _abort(); + // unreachable; + } + $17 = HEAP32[((32544 + 20) | 0) >> 2] | 0; + $18 = ($14 | 0) == ($17 | 0); + if ($18) { + $$sum3 = ($8 + -4) | 0; + $103 = ($mem + $$sum3) | 0; + $104 = HEAP32[$103 >> 2] | 0; + $105 = $104 & 3; + $106 = ($105 | 0) == 3; + if (!$106) { + $p$0 = $14; + $psize$0 = $15; + break; + } + HEAP32[((32544 + 8) | 0) >> 2] = $15; + $107 = $104 & -2; + HEAP32[$103 >> 2] = $107; + $108 = $15 | 1; + $$sum26 = ($$sum2 + 4) | 0; + $109 = ($mem + $$sum26) | 0; + HEAP32[$109 >> 2] = $108; + HEAP32[$9 >> 2] = $15; + STACKTOP = sp; + return; + } + $19 = $12 >>> 3; + $20 = $12 >>> 0 < 256; + if ($20) { + $$sum36 = ($$sum2 + 8) | 0; + $21 = ($mem + $$sum36) | 0; + $22 = HEAP32[$21 >> 2] | 0; + $$sum37 = ($$sum2 + 12) | 0; + $23 = ($mem + $$sum37) | 0; + $24 = HEAP32[$23 >> 2] | 0; + $25 = $19 << 1; + $26 = (((32544 + ($25 << 2)) | 0) + 40) | 0; + $27 = ($22 | 0) == ($26 | 0); + if (!$27) { + $28 = $22 >>> 0 < $2 >>> 0; + if ($28) { + _abort(); + // unreachable; + } + $29 = ($22 + 12) | 0; + $30 = HEAP32[$29 >> 2] | 0; + $31 = ($30 | 0) == ($14 | 0); + if (!$31) { + _abort(); + // unreachable; + } + } + $32 = ($24 | 0) == ($22 | 0); + if ($32) { + $33 = 1 << $19; + $34 = $33 ^ -1; + $35 = HEAP32[32544 >> 2] | 0; + $36 = $35 & $34; + HEAP32[32544 >> 2] = $36; + $p$0 = $14; + $psize$0 = $15; + break; + } + $37 = ($24 | 0) == ($26 | 0); + if ($37) { + $$pre67 = ($24 + 8) | 0; + $$pre$phi68Z2D = $$pre67; + } else { + $38 = $24 >>> 0 < $2 >>> 0; + if ($38) { + _abort(); + // unreachable; + } + $39 = ($24 + 8) | 0; + $40 = HEAP32[$39 >> 2] | 0; + $41 = ($40 | 0) == ($14 | 0); + if ($41) { + $$pre$phi68Z2D = $39; + } else { + _abort(); + // unreachable; + } + } + $42 = ($22 + 12) | 0; + HEAP32[$42 >> 2] = $24; + HEAP32[$$pre$phi68Z2D >> 2] = $22; + $p$0 = $14; + $psize$0 = $15; + break; + } + $$sum28 = ($$sum2 + 24) | 0; + $43 = ($mem + $$sum28) | 0; + $44 = HEAP32[$43 >> 2] | 0; + $$sum29 = ($$sum2 + 12) | 0; + $45 = ($mem + $$sum29) | 0; + $46 = HEAP32[$45 >> 2] | 0; + $47 = ($46 | 0) == ($14 | 0); + do { + if ($47) { + $$sum31 = ($$sum2 + 20) | 0; + $57 = ($mem + $$sum31) | 0; + $58 = HEAP32[$57 >> 2] | 0; + $59 = ($58 | 0) == (0 | 0); + if ($59) { + $$sum30 = ($$sum2 + 16) | 0; + $60 = ($mem + $$sum30) | 0; + $61 = HEAP32[$60 >> 2] | 0; + $62 = ($61 | 0) == (0 | 0); + if ($62) { + $R$1 = 0; + break; + } else { + $R$0 = $61; + $RP$0 = $60; + } + } else { + $R$0 = $58; + $RP$0 = $57; + } + while (1) { + $63 = ($R$0 + 20) | 0; + $64 = HEAP32[$63 >> 2] | 0; + $65 = ($64 | 0) == (0 | 0); + if (!$65) { + $R$0 = $64; + $RP$0 = $63; + continue; + } + $66 = ($R$0 + 16) | 0; + $67 = HEAP32[$66 >> 2] | 0; + $68 = ($67 | 0) == (0 | 0); + if ($68) { + break; + } else { + $R$0 = $67; + $RP$0 = $66; + } + } + $69 = $RP$0 >>> 0 < $2 >>> 0; + if ($69) { + _abort(); + // unreachable; + } else { + HEAP32[$RP$0 >> 2] = 0; + $R$1 = $R$0; + break; + } + } else { + $$sum35 = ($$sum2 + 8) | 0; + $48 = ($mem + $$sum35) | 0; + $49 = HEAP32[$48 >> 2] | 0; + $50 = $49 >>> 0 < $2 >>> 0; + if ($50) { + _abort(); + // unreachable; + } + $51 = ($49 + 12) | 0; + $52 = HEAP32[$51 >> 2] | 0; + $53 = ($52 | 0) == ($14 | 0); + if (!$53) { + _abort(); + // unreachable; + } + $54 = ($46 + 8) | 0; + $55 = HEAP32[$54 >> 2] | 0; + $56 = ($55 | 0) == ($14 | 0); + if ($56) { + HEAP32[$51 >> 2] = $46; + HEAP32[$54 >> 2] = $49; + $R$1 = $46; + break; + } else { + _abort(); + // unreachable; + } + } + } while (0); + $70 = ($44 | 0) == (0 | 0); + if ($70) { + $p$0 = $14; + $psize$0 = $15; + } else { + $$sum32 = ($$sum2 + 28) | 0; + $71 = ($mem + $$sum32) | 0; + $72 = HEAP32[$71 >> 2] | 0; + $73 = (((32544 + ($72 << 2)) | 0) + 304) | 0; + $74 = HEAP32[$73 >> 2] | 0; + $75 = ($14 | 0) == ($74 | 0); + if ($75) { + HEAP32[$73 >> 2] = $R$1; + $cond = ($R$1 | 0) == (0 | 0); + if ($cond) { + $76 = 1 << $72; + $77 = $76 ^ -1; + $78 = HEAP32[((32544 + 4) | 0) >> 2] | 0; + $79 = $78 & $77; + HEAP32[((32544 + 4) | 0) >> 2] = $79; + $p$0 = $14; + $psize$0 = $15; + break; + } + } else { + $80 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $81 = $44 >>> 0 < $80 >>> 0; + if ($81) { + _abort(); + // unreachable; + } + $82 = ($44 + 16) | 0; + $83 = HEAP32[$82 >> 2] | 0; + $84 = ($83 | 0) == ($14 | 0); + if ($84) { + HEAP32[$82 >> 2] = $R$1; + } else { + $85 = ($44 + 20) | 0; + HEAP32[$85 >> 2] = $R$1; + } + $86 = ($R$1 | 0) == (0 | 0); + if ($86) { + $p$0 = $14; + $psize$0 = $15; + break; + } + } + $87 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $88 = $R$1 >>> 0 < $87 >>> 0; + if ($88) { + _abort(); + // unreachable; + } + $89 = ($R$1 + 24) | 0; + HEAP32[$89 >> 2] = $44; + $$sum33 = ($$sum2 + 16) | 0; + $90 = ($mem + $$sum33) | 0; + $91 = HEAP32[$90 >> 2] | 0; + $92 = ($91 | 0) == (0 | 0); + do { + if (!$92) { + $93 = $91 >>> 0 < $87 >>> 0; + if ($93) { + _abort(); + // unreachable; + } else { + $94 = ($R$1 + 16) | 0; + HEAP32[$94 >> 2] = $91; + $95 = ($91 + 24) | 0; + HEAP32[$95 >> 2] = $R$1; + break; + } + } + } while (0); + $$sum34 = ($$sum2 + 20) | 0; + $96 = ($mem + $$sum34) | 0; + $97 = HEAP32[$96 >> 2] | 0; + $98 = ($97 | 0) == (0 | 0); + if ($98) { + $p$0 = $14; + $psize$0 = $15; + } else { + $99 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $100 = $97 >>> 0 < $99 >>> 0; + if ($100) { + _abort(); + // unreachable; + } else { + $101 = ($R$1 + 20) | 0; + HEAP32[$101 >> 2] = $97; + $102 = ($97 + 24) | 0; + HEAP32[$102 >> 2] = $R$1; + $p$0 = $14; + $psize$0 = $15; + break; + } + } + } + } else { + $p$0 = $1; + $psize$0 = $8; + } + } while (0); + $110 = $p$0 >>> 0 < $9 >>> 0; + if (!$110) { + _abort(); + // unreachable; + } + $$sum25 = ($8 + -4) | 0; + $111 = ($mem + $$sum25) | 0; + $112 = HEAP32[$111 >> 2] | 0; + $113 = $112 & 1; + $114 = ($113 | 0) == 0; + if ($114) { + _abort(); + // unreachable; + } + $115 = $112 & 2; + $116 = ($115 | 0) == 0; + if ($116) { + $117 = HEAP32[((32544 + 24) | 0) >> 2] | 0; + $118 = ($9 | 0) == ($117 | 0); + if ($118) { + $119 = HEAP32[((32544 + 12) | 0) >> 2] | 0; + $120 = ($119 + $psize$0) | 0; + HEAP32[((32544 + 12) | 0) >> 2] = $120; + HEAP32[((32544 + 24) | 0) >> 2] = $p$0; + $121 = $120 | 1; + $122 = ($p$0 + 4) | 0; + HEAP32[$122 >> 2] = $121; + $123 = HEAP32[((32544 + 20) | 0) >> 2] | 0; + $124 = ($p$0 | 0) == ($123 | 0); + if (!$124) { + STACKTOP = sp; + return; + } + HEAP32[((32544 + 20) | 0) >> 2] = 0; + HEAP32[((32544 + 8) | 0) >> 2] = 0; + STACKTOP = sp; + return; + } + $125 = HEAP32[((32544 + 20) | 0) >> 2] | 0; + $126 = ($9 | 0) == ($125 | 0); + if ($126) { + $127 = HEAP32[((32544 + 8) | 0) >> 2] | 0; + $128 = ($127 + $psize$0) | 0; + HEAP32[((32544 + 8) | 0) >> 2] = $128; + HEAP32[((32544 + 20) | 0) >> 2] = $p$0; + $129 = $128 | 1; + $130 = ($p$0 + 4) | 0; + HEAP32[$130 >> 2] = $129; + $131 = ($p$0 + $128) | 0; + HEAP32[$131 >> 2] = $128; + STACKTOP = sp; + return; + } + $132 = $112 & -8; + $133 = ($132 + $psize$0) | 0; + $134 = $112 >>> 3; + $135 = $112 >>> 0 < 256; + do { + if ($135) { + $136 = ($mem + $8) | 0; + $137 = HEAP32[$136 >> 2] | 0; + $$sum2324 = $8 | 4; + $138 = ($mem + $$sum2324) | 0; + $139 = HEAP32[$138 >> 2] | 0; + $140 = $134 << 1; + $141 = (((32544 + ($140 << 2)) | 0) + 40) | 0; + $142 = ($137 | 0) == ($141 | 0); + if (!$142) { + $143 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $144 = $137 >>> 0 < $143 >>> 0; + if ($144) { + _abort(); + // unreachable; + } + $145 = ($137 + 12) | 0; + $146 = HEAP32[$145 >> 2] | 0; + $147 = ($146 | 0) == ($9 | 0); + if (!$147) { + _abort(); + // unreachable; + } + } + $148 = ($139 | 0) == ($137 | 0); + if ($148) { + $149 = 1 << $134; + $150 = $149 ^ -1; + $151 = HEAP32[32544 >> 2] | 0; + $152 = $151 & $150; + HEAP32[32544 >> 2] = $152; + break; + } + $153 = ($139 | 0) == ($141 | 0); + if ($153) { + $$pre65 = ($139 + 8) | 0; + $$pre$phi66Z2D = $$pre65; + } else { + $154 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $155 = $139 >>> 0 < $154 >>> 0; + if ($155) { + _abort(); + // unreachable; + } + $156 = ($139 + 8) | 0; + $157 = HEAP32[$156 >> 2] | 0; + $158 = ($157 | 0) == ($9 | 0); + if ($158) { + $$pre$phi66Z2D = $156; + } else { + _abort(); + // unreachable; + } + } + $159 = ($137 + 12) | 0; + HEAP32[$159 >> 2] = $139; + HEAP32[$$pre$phi66Z2D >> 2] = $137; + } else { + $$sum5 = ($8 + 16) | 0; + $160 = ($mem + $$sum5) | 0; + $161 = HEAP32[$160 >> 2] | 0; + $$sum67 = $8 | 4; + $162 = ($mem + $$sum67) | 0; + $163 = HEAP32[$162 >> 2] | 0; + $164 = ($163 | 0) == ($9 | 0); + do { + if ($164) { + $$sum9 = ($8 + 12) | 0; + $175 = ($mem + $$sum9) | 0; + $176 = HEAP32[$175 >> 2] | 0; + $177 = ($176 | 0) == (0 | 0); + if ($177) { + $$sum8 = ($8 + 8) | 0; + $178 = ($mem + $$sum8) | 0; + $179 = HEAP32[$178 >> 2] | 0; + $180 = ($179 | 0) == (0 | 0); + if ($180) { + $R7$1 = 0; + break; + } else { + $R7$0 = $179; + $RP9$0 = $178; + } + } else { + $R7$0 = $176; + $RP9$0 = $175; + } + while (1) { + $181 = ($R7$0 + 20) | 0; + $182 = HEAP32[$181 >> 2] | 0; + $183 = ($182 | 0) == (0 | 0); + if (!$183) { + $R7$0 = $182; + $RP9$0 = $181; + continue; + } + $184 = ($R7$0 + 16) | 0; + $185 = HEAP32[$184 >> 2] | 0; + $186 = ($185 | 0) == (0 | 0); + if ($186) { + break; + } else { + $R7$0 = $185; + $RP9$0 = $184; + } + } + $187 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $188 = $RP9$0 >>> 0 < $187 >>> 0; + if ($188) { + _abort(); + // unreachable; + } else { + HEAP32[$RP9$0 >> 2] = 0; + $R7$1 = $R7$0; + break; + } + } else { + $165 = ($mem + $8) | 0; + $166 = HEAP32[$165 >> 2] | 0; + $167 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $168 = $166 >>> 0 < $167 >>> 0; + if ($168) { + _abort(); + // unreachable; + } + $169 = ($166 + 12) | 0; + $170 = HEAP32[$169 >> 2] | 0; + $171 = ($170 | 0) == ($9 | 0); + if (!$171) { + _abort(); + // unreachable; + } + $172 = ($163 + 8) | 0; + $173 = HEAP32[$172 >> 2] | 0; + $174 = ($173 | 0) == ($9 | 0); + if ($174) { + HEAP32[$169 >> 2] = $163; + HEAP32[$172 >> 2] = $166; + $R7$1 = $163; + break; + } else { + _abort(); + // unreachable; + } + } + } while (0); + $189 = ($161 | 0) == (0 | 0); + if (!$189) { + $$sum18 = ($8 + 20) | 0; + $190 = ($mem + $$sum18) | 0; + $191 = HEAP32[$190 >> 2] | 0; + $192 = (((32544 + ($191 << 2)) | 0) + 304) | 0; + $193 = HEAP32[$192 >> 2] | 0; + $194 = ($9 | 0) == ($193 | 0); + if ($194) { + HEAP32[$192 >> 2] = $R7$1; + $cond54 = ($R7$1 | 0) == (0 | 0); + if ($cond54) { + $195 = 1 << $191; + $196 = $195 ^ -1; + $197 = HEAP32[((32544 + 4) | 0) >> 2] | 0; + $198 = $197 & $196; + HEAP32[((32544 + 4) | 0) >> 2] = $198; + break; + } + } else { + $199 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $200 = $161 >>> 0 < $199 >>> 0; + if ($200) { + _abort(); + // unreachable; + } + $201 = ($161 + 16) | 0; + $202 = HEAP32[$201 >> 2] | 0; + $203 = ($202 | 0) == ($9 | 0); + if ($203) { + HEAP32[$201 >> 2] = $R7$1; + } else { + $204 = ($161 + 20) | 0; + HEAP32[$204 >> 2] = $R7$1; + } + $205 = ($R7$1 | 0) == (0 | 0); + if ($205) { + break; + } + } + $206 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $207 = $R7$1 >>> 0 < $206 >>> 0; + if ($207) { + _abort(); + // unreachable; + } + $208 = ($R7$1 + 24) | 0; + HEAP32[$208 >> 2] = $161; + $$sum19 = ($8 + 8) | 0; + $209 = ($mem + $$sum19) | 0; + $210 = HEAP32[$209 >> 2] | 0; + $211 = ($210 | 0) == (0 | 0); + do { + if (!$211) { + $212 = $210 >>> 0 < $206 >>> 0; + if ($212) { + _abort(); + // unreachable; + } else { + $213 = ($R7$1 + 16) | 0; + HEAP32[$213 >> 2] = $210; + $214 = ($210 + 24) | 0; + HEAP32[$214 >> 2] = $R7$1; + break; + } + } + } while (0); + $$sum20 = ($8 + 12) | 0; + $215 = ($mem + $$sum20) | 0; + $216 = HEAP32[$215 >> 2] | 0; + $217 = ($216 | 0) == (0 | 0); + if (!$217) { + $218 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $219 = $216 >>> 0 < $218 >>> 0; + if ($219) { + _abort(); + // unreachable; + } else { + $220 = ($R7$1 + 20) | 0; + HEAP32[$220 >> 2] = $216; + $221 = ($216 + 24) | 0; + HEAP32[$221 >> 2] = $R7$1; + break; + } + } + } + } + } while (0); + $222 = $133 | 1; + $223 = ($p$0 + 4) | 0; + HEAP32[$223 >> 2] = $222; + $224 = ($p$0 + $133) | 0; + HEAP32[$224 >> 2] = $133; + $225 = HEAP32[((32544 + 20) | 0) >> 2] | 0; + $226 = ($p$0 | 0) == ($225 | 0); + if ($226) { + HEAP32[((32544 + 8) | 0) >> 2] = $133; + STACKTOP = sp; + return; + } else { + $psize$1 = $133; + } + } else { + $227 = $112 & -2; + HEAP32[$111 >> 2] = $227; + $228 = $psize$0 | 1; + $229 = ($p$0 + 4) | 0; + HEAP32[$229 >> 2] = $228; + $230 = ($p$0 + $psize$0) | 0; + HEAP32[$230 >> 2] = $psize$0; + $psize$1 = $psize$0; + } + $231 = $psize$1 >>> 3; + $232 = $psize$1 >>> 0 < 256; + if ($232) { + $233 = $231 << 1; + $234 = (((32544 + ($233 << 2)) | 0) + 40) | 0; + $235 = HEAP32[32544 >> 2] | 0; + $236 = 1 << $231; + $237 = $235 & $236; + $238 = ($237 | 0) == 0; + if ($238) { + $239 = $235 | $236; + HEAP32[32544 >> 2] = $239; + $$sum16$pre = ($233 + 2) | 0; + $$pre = (((32544 + ($$sum16$pre << 2)) | 0) + 40) | 0; + $$pre$phiZ2D = $$pre; + $F16$0 = $234; + } else { + $$sum17 = ($233 + 2) | 0; + $240 = (((32544 + ($$sum17 << 2)) | 0) + 40) | 0; + $241 = HEAP32[$240 >> 2] | 0; + $242 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $243 = $241 >>> 0 < $242 >>> 0; + if ($243) { + _abort(); + // unreachable; + } else { + $$pre$phiZ2D = $240; + $F16$0 = $241; + } + } + HEAP32[$$pre$phiZ2D >> 2] = $p$0; + $244 = ($F16$0 + 12) | 0; + HEAP32[$244 >> 2] = $p$0; + $245 = ($p$0 + 8) | 0; + HEAP32[$245 >> 2] = $F16$0; + $246 = ($p$0 + 12) | 0; + HEAP32[$246 >> 2] = $234; + STACKTOP = sp; + return; + } + $247 = $psize$1 >>> 8; + $248 = ($247 | 0) == 0; + if ($248) { + $I18$0 = 0; + } else { + $249 = $psize$1 >>> 0 > 16777215; + if ($249) { + $I18$0 = 31; + } else { + $250 = ($247 + 1048320) | 0; + $251 = $250 >>> 16; + $252 = $251 & 8; + $253 = $247 << $252; + $254 = ($253 + 520192) | 0; + $255 = $254 >>> 16; + $256 = $255 & 4; + $257 = $256 | $252; + $258 = $253 << $256; + $259 = ($258 + 245760) | 0; + $260 = $259 >>> 16; + $261 = $260 & 2; + $262 = $257 | $261; + $263 = (14 - $262) | 0; + $264 = $258 << $261; + $265 = $264 >>> 15; + $266 = ($263 + $265) | 0; + $267 = $266 << 1; + $268 = ($266 + 7) | 0; + $269 = $psize$1 >>> $268; + $270 = $269 & 1; + $271 = $270 | $267; + $I18$0 = $271; + } + } + $272 = (((32544 + ($I18$0 << 2)) | 0) + 304) | 0; + $273 = ($p$0 + 28) | 0; + $I18$0$c = $I18$0; + HEAP32[$273 >> 2] = $I18$0$c; + $274 = ($p$0 + 20) | 0; + HEAP32[$274 >> 2] = 0; + $275 = ($p$0 + 16) | 0; + HEAP32[$275 >> 2] = 0; + $276 = HEAP32[((32544 + 4) | 0) >> 2] | 0; + $277 = 1 << $I18$0; + $278 = $276 & $277; + $279 = ($278 | 0) == 0; + L199: do { + if ($279) { + $280 = $276 | $277; + HEAP32[((32544 + 4) | 0) >> 2] = $280; + HEAP32[$272 >> 2] = $p$0; + $281 = ($p$0 + 24) | 0; + HEAP32[$281 >> 2] = $272; + $282 = ($p$0 + 12) | 0; + HEAP32[$282 >> 2] = $p$0; + $283 = ($p$0 + 8) | 0; + HEAP32[$283 >> 2] = $p$0; + } else { + $284 = HEAP32[$272 >> 2] | 0; + $285 = ($I18$0 | 0) == 31; + if ($285) { + $293 = 0; + } else { + $286 = $I18$0 >>> 1; + $287 = (25 - $286) | 0; + $293 = $287; + } + $288 = ($284 + 4) | 0; + $289 = HEAP32[$288 >> 2] | 0; + $290 = $289 & -8; + $291 = ($290 | 0) == ($psize$1 | 0); + L205: do { + if ($291) { + $T$0$lcssa = $284; + } else { + $292 = $psize$1 << $293; + $K19$058 = $292; + $T$057 = $284; + while (1) { + $300 = $K19$058 >>> 31; + $301 = ((($T$057 + ($300 << 2)) | 0) + 16) | 0; + $296 = HEAP32[$301 >> 2] | 0; + $302 = ($296 | 0) == (0 | 0); + if ($302) { + break; + } + $294 = $K19$058 << 1; + $295 = ($296 + 4) | 0; + $297 = HEAP32[$295 >> 2] | 0; + $298 = $297 & -8; + $299 = ($298 | 0) == ($psize$1 | 0); + if ($299) { + $T$0$lcssa = $296; + break L205; + } else { + $K19$058 = $294; + $T$057 = $296; + } + } + $303 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $304 = $301 >>> 0 < $303 >>> 0; + if ($304) { + _abort(); + // unreachable; + } else { + HEAP32[$301 >> 2] = $p$0; + $305 = ($p$0 + 24) | 0; + HEAP32[$305 >> 2] = $T$057; + $306 = ($p$0 + 12) | 0; + HEAP32[$306 >> 2] = $p$0; + $307 = ($p$0 + 8) | 0; + HEAP32[$307 >> 2] = $p$0; + break L199; + } + } + } while (0); + $308 = ($T$0$lcssa + 8) | 0; + $309 = HEAP32[$308 >> 2] | 0; + $310 = HEAP32[((32544 + 16) | 0) >> 2] | 0; + $311 = $T$0$lcssa >>> 0 >= $310 >>> 0; + $312 = $309 >>> 0 >= $310 >>> 0; + $or$cond = $311 & $312; + if ($or$cond) { + $313 = ($309 + 12) | 0; + HEAP32[$313 >> 2] = $p$0; + HEAP32[$308 >> 2] = $p$0; + $314 = ($p$0 + 8) | 0; + HEAP32[$314 >> 2] = $309; + $315 = ($p$0 + 12) | 0; + HEAP32[$315 >> 2] = $T$0$lcssa; + $316 = ($p$0 + 24) | 0; + HEAP32[$316 >> 2] = 0; + break; + } else { + _abort(); + // unreachable; + } + } + } while (0); + $317 = HEAP32[((32544 + 32) | 0) >> 2] | 0; + $318 = ($317 + -1) | 0; + HEAP32[((32544 + 32) | 0) >> 2] = $318; + $319 = ($318 | 0) == 0; + if ($319) { + $sp$0$in$i = (32544 + 456) | 0; + } else { + STACKTOP = sp; + return; + } + while (1) { + $sp$0$i = HEAP32[$sp$0$in$i >> 2] | 0; + $320 = ($sp$0$i | 0) == (0 | 0); + $321 = ($sp$0$i + 8) | 0; + if ($320) { + break; + } else { + $sp$0$in$i = $321; + } + } + HEAP32[((32544 + 32) | 0) >> 2] = -1; + STACKTOP = sp; + return; + } + function runPostSets() {} + function _bitshift64Ashr(low, high, bits) { + low = low | 0; + high = high | 0; + bits = bits | 0; + var ander = 0; + if ((bits | 0) < 32) { + ander = ((1 << bits) - 1) | 0; + tempRet0 = high >> bits; + return (low >>> bits) | ((high & ander) << (32 - bits)); + } + tempRet0 = (high | 0) < 0 ? -1 : 0; + return (high >> (bits - 32)) | 0; + } + function _i64Subtract(a, b, c, d) { + a = a | 0; + b = b | 0; + c = c | 0; + d = d | 0; + var l = 0, + h = 0; + l = (a - c) >>> 0; + h = (b - d) >>> 0; + h = (b - d - ((c >>> 0 > a >>> 0) | 0)) >>> 0; // Borrow one from high word to low word on underflow. + return ((tempRet0 = h), l | 0) | 0; + } + function _i64Add(a, b, c, d) { + /* + x = a + b*2^32 + y = c + d*2^32 + result = l + h*2^32 + */ + a = a | 0; + b = b | 0; + c = c | 0; + d = d | 0; + var l = 0, + h = 0; + l = (a + c) >>> 0; + h = (b + d + ((l >>> 0 < a >>> 0) | 0)) >>> 0; // Add carry from low word to high word on overflow. + return ((tempRet0 = h), l | 0) | 0; + } + function _memset(ptr, value, num) { + ptr = ptr | 0; + value = value | 0; + num = num | 0; + var stop = 0, + value4 = 0, + stop4 = 0, + unaligned = 0; + stop = (ptr + num) | 0; + if ((num | 0) >= 20) { + // This is unaligned, but quite large, so work hard to get to aligned settings + value = value & 0xff; + unaligned = ptr & 3; + value4 = value | (value << 8) | (value << 16) | (value << 24); + stop4 = stop & ~3; + if (unaligned) { + unaligned = (ptr + 4 - unaligned) | 0; + while ((ptr | 0) < (unaligned | 0)) { + // no need to check for stop, since we have large num + HEAP8[ptr >> 0] = value; + ptr = (ptr + 1) | 0; + } + } + while ((ptr | 0) < (stop4 | 0)) { + HEAP32[ptr >> 2] = value4; + ptr = (ptr + 4) | 0; + } + } + while ((ptr | 0) < (stop | 0)) { + HEAP8[ptr >> 0] = value; + ptr = (ptr + 1) | 0; + } + return (ptr - num) | 0; + } + function _bitshift64Lshr(low, high, bits) { + low = low | 0; + high = high | 0; + bits = bits | 0; + var ander = 0; + if ((bits | 0) < 32) { + ander = ((1 << bits) - 1) | 0; + tempRet0 = high >>> bits; + return (low >>> bits) | ((high & ander) << (32 - bits)); + } + tempRet0 = 0; + return (high >>> (bits - 32)) | 0; + } + function _bitshift64Shl(low, high, bits) { + low = low | 0; + high = high | 0; + bits = bits | 0; + var ander = 0; + if ((bits | 0) < 32) { + ander = ((1 << bits) - 1) | 0; + tempRet0 = + (high << bits) | ((low & (ander << (32 - bits))) >>> (32 - bits)); + return low << bits; + } + tempRet0 = low << (bits - 32); + return 0; + } + function _strlen(ptr) { + ptr = ptr | 0; + var curr = 0; + curr = ptr; + while (HEAP8[curr >> 0] | 0) { + curr = (curr + 1) | 0; + } + return (curr - ptr) | 0; + } + function _memcpy(dest, src, num) { + dest = dest | 0; + src = src | 0; + num = num | 0; + var ret = 0; + if ((num | 0) >= 4096) + return _emscripten_memcpy_big(dest | 0, src | 0, num | 0) | 0; + ret = dest | 0; + if ((dest & 3) == (src & 3)) { + while (dest & 3) { + if ((num | 0) == 0) return ret | 0; + HEAP8[dest >> 0] = HEAP8[src >> 0] | 0; + dest = (dest + 1) | 0; + src = (src + 1) | 0; + num = (num - 1) | 0; + } + while ((num | 0) >= 4) { + HEAP32[dest >> 2] = HEAP32[src >> 2] | 0; + dest = (dest + 4) | 0; + src = (src + 4) | 0; + num = (num - 4) | 0; + } + } + while ((num | 0) > 0) { + HEAP8[dest >> 0] = HEAP8[src >> 0] | 0; + dest = (dest + 1) | 0; + src = (src + 1) | 0; + num = (num - 1) | 0; + } + return ret | 0; + } + function _memmove(dest, src, num) { + dest = dest | 0; + src = src | 0; + num = num | 0; + var ret = 0; + if (((src | 0) < (dest | 0)) & ((dest | 0) < ((src + num) | 0))) { + // Unlikely case: Copy backwards in a safe manner + ret = dest; + src = (src + num) | 0; + dest = (dest + num) | 0; + while ((num | 0) > 0) { + dest = (dest - 1) | 0; + src = (src - 1) | 0; + num = (num - 1) | 0; + HEAP8[dest >> 0] = HEAP8[src >> 0] | 0; + } + dest = ret; + } else { + _memcpy(dest, src, num) | 0; + } + return dest | 0; + } + function _llvm_ctlz_i32(x) { + x = x | 0; + var ret = 0; + ret = HEAP8[(ctlz_i8 + (x >>> 24)) >> 0] | 0; + if ((ret | 0) < 8) return ret | 0; + ret = HEAP8[(ctlz_i8 + ((x >> 16) & 0xff)) >> 0] | 0; + if ((ret | 0) < 8) return (ret + 8) | 0; + ret = HEAP8[(ctlz_i8 + ((x >> 8) & 0xff)) >> 0] | 0; + if ((ret | 0) < 8) return (ret + 16) | 0; + return ((HEAP8[(ctlz_i8 + (x & 0xff)) >> 0] | 0) + 24) | 0; + } + + function _llvm_cttz_i32(x) { + x = x | 0; + var ret = 0; + ret = HEAP8[(cttz_i8 + (x & 0xff)) >> 0] | 0; + if ((ret | 0) < 8) return ret | 0; + ret = HEAP8[(cttz_i8 + ((x >> 8) & 0xff)) >> 0] | 0; + if ((ret | 0) < 8) return (ret + 8) | 0; + ret = HEAP8[(cttz_i8 + ((x >> 16) & 0xff)) >> 0] | 0; + if ((ret | 0) < 8) return (ret + 16) | 0; + return ((HEAP8[(cttz_i8 + (x >>> 24)) >> 0] | 0) + 24) | 0; + } + + // ======== compiled code from system/lib/compiler-rt , see readme therein + function ___muldsi3($a, $b) { + $a = $a | 0; + $b = $b | 0; + var $1 = 0, + $2 = 0, + $3 = 0, + $6 = 0, + $8 = 0, + $11 = 0, + $12 = 0; + $1 = $a & 65535; + $2 = $b & 65535; + $3 = Math_imul($2, $1) | 0; + $6 = $a >>> 16; + $8 = (($3 >>> 16) + (Math_imul($2, $6) | 0)) | 0; + $11 = $b >>> 16; + $12 = Math_imul($11, $1) | 0; + return ( + ((tempRet0 = + (((($8 >>> 16) + (Math_imul($11, $6) | 0)) | 0) + + (((($8 & 65535) + $12) | 0) >>> 16)) | + 0), + 0 | ((($8 + $12) << 16) | ($3 & 65535))) | 0 + ); + } + function ___divdi3($a$0, $a$1, $b$0, $b$1) { + $a$0 = $a$0 | 0; + $a$1 = $a$1 | 0; + $b$0 = $b$0 | 0; + $b$1 = $b$1 | 0; + var $1$0 = 0, + $1$1 = 0, + $2$0 = 0, + $2$1 = 0, + $4$0 = 0, + $4$1 = 0, + $6$0 = 0, + $7$0 = 0, + $7$1 = 0, + $8$0 = 0, + $10$0 = 0; + $1$0 = ($a$1 >> 31) | ((($a$1 | 0) < 0 ? -1 : 0) << 1); + $1$1 = ((($a$1 | 0) < 0 ? -1 : 0) >> 31) | ((($a$1 | 0) < 0 ? -1 : 0) << 1); + $2$0 = ($b$1 >> 31) | ((($b$1 | 0) < 0 ? -1 : 0) << 1); + $2$1 = ((($b$1 | 0) < 0 ? -1 : 0) >> 31) | ((($b$1 | 0) < 0 ? -1 : 0) << 1); + $4$0 = _i64Subtract($1$0 ^ $a$0, $1$1 ^ $a$1, $1$0, $1$1) | 0; + $4$1 = tempRet0; + $6$0 = _i64Subtract($2$0 ^ $b$0, $2$1 ^ $b$1, $2$0, $2$1) | 0; + $7$0 = $2$0 ^ $1$0; + $7$1 = $2$1 ^ $1$1; + $8$0 = ___udivmoddi4($4$0, $4$1, $6$0, tempRet0, 0) | 0; + $10$0 = _i64Subtract($8$0 ^ $7$0, tempRet0 ^ $7$1, $7$0, $7$1) | 0; + return ((tempRet0 = tempRet0), $10$0) | 0; + } + function ___remdi3($a$0, $a$1, $b$0, $b$1) { + $a$0 = $a$0 | 0; + $a$1 = $a$1 | 0; + $b$0 = $b$0 | 0; + $b$1 = $b$1 | 0; + var $rem = 0, + $1$0 = 0, + $1$1 = 0, + $2$0 = 0, + $2$1 = 0, + $4$0 = 0, + $4$1 = 0, + $6$0 = 0, + $10$0 = 0, + $10$1 = 0, + __stackBase__ = 0; + __stackBase__ = STACKTOP; + STACKTOP = (STACKTOP + 8) | 0; + $rem = __stackBase__ | 0; + $1$0 = ($a$1 >> 31) | ((($a$1 | 0) < 0 ? -1 : 0) << 1); + $1$1 = ((($a$1 | 0) < 0 ? -1 : 0) >> 31) | ((($a$1 | 0) < 0 ? -1 : 0) << 1); + $2$0 = ($b$1 >> 31) | ((($b$1 | 0) < 0 ? -1 : 0) << 1); + $2$1 = ((($b$1 | 0) < 0 ? -1 : 0) >> 31) | ((($b$1 | 0) < 0 ? -1 : 0) << 1); + $4$0 = _i64Subtract($1$0 ^ $a$0, $1$1 ^ $a$1, $1$0, $1$1) | 0; + $4$1 = tempRet0; + $6$0 = _i64Subtract($2$0 ^ $b$0, $2$1 ^ $b$1, $2$0, $2$1) | 0; + ___udivmoddi4($4$0, $4$1, $6$0, tempRet0, $rem) | 0; + $10$0 = + _i64Subtract( + HEAP32[$rem >> 2] ^ $1$0, + HEAP32[($rem + 4) >> 2] ^ $1$1, + $1$0, + $1$1 + ) | 0; + $10$1 = tempRet0; + STACKTOP = __stackBase__; + return ((tempRet0 = $10$1), $10$0) | 0; + } + function ___muldi3($a$0, $a$1, $b$0, $b$1) { + $a$0 = $a$0 | 0; + $a$1 = $a$1 | 0; + $b$0 = $b$0 | 0; + $b$1 = $b$1 | 0; + var $x_sroa_0_0_extract_trunc = 0, + $y_sroa_0_0_extract_trunc = 0, + $1$0 = 0, + $1$1 = 0, + $2 = 0; + $x_sroa_0_0_extract_trunc = $a$0; + $y_sroa_0_0_extract_trunc = $b$0; + $1$0 = ___muldsi3($x_sroa_0_0_extract_trunc, $y_sroa_0_0_extract_trunc) | 0; + $1$1 = tempRet0; + $2 = Math_imul($a$1, $y_sroa_0_0_extract_trunc) | 0; + return ( + ((tempRet0 = + ((((Math_imul($b$1, $x_sroa_0_0_extract_trunc) | 0) + $2) | 0) + $1$1) | + ($1$1 & 0)), + 0 | ($1$0 & -1)) | 0 + ); + } + function ___udivdi3($a$0, $a$1, $b$0, $b$1) { + $a$0 = $a$0 | 0; + $a$1 = $a$1 | 0; + $b$0 = $b$0 | 0; + $b$1 = $b$1 | 0; + var $1$0 = 0; + $1$0 = ___udivmoddi4($a$0, $a$1, $b$0, $b$1, 0) | 0; + return ((tempRet0 = tempRet0), $1$0) | 0; + } + function ___uremdi3($a$0, $a$1, $b$0, $b$1) { + $a$0 = $a$0 | 0; + $a$1 = $a$1 | 0; + $b$0 = $b$0 | 0; + $b$1 = $b$1 | 0; + var $rem = 0, + __stackBase__ = 0; + __stackBase__ = STACKTOP; + STACKTOP = (STACKTOP + 8) | 0; + $rem = __stackBase__ | 0; + ___udivmoddi4($a$0, $a$1, $b$0, $b$1, $rem) | 0; + STACKTOP = __stackBase__; + return ( + ((tempRet0 = HEAP32[($rem + 4) >> 2] | 0), HEAP32[$rem >> 2] | 0) | 0 + ); + } + function ___udivmoddi4($a$0, $a$1, $b$0, $b$1, $rem) { + $a$0 = $a$0 | 0; + $a$1 = $a$1 | 0; + $b$0 = $b$0 | 0; + $b$1 = $b$1 | 0; + $rem = $rem | 0; + var $n_sroa_0_0_extract_trunc = 0, + $n_sroa_1_4_extract_shift$0 = 0, + $n_sroa_1_4_extract_trunc = 0, + $d_sroa_0_0_extract_trunc = 0, + $d_sroa_1_4_extract_shift$0 = 0, + $d_sroa_1_4_extract_trunc = 0, + $4 = 0, + $17 = 0, + $37 = 0, + $49 = 0, + $51 = 0, + $57 = 0, + $58 = 0, + $66 = 0, + $78 = 0, + $86 = 0, + $88 = 0, + $89 = 0, + $91 = 0, + $92 = 0, + $95 = 0, + $105 = 0, + $117 = 0, + $119 = 0, + $125 = 0, + $126 = 0, + $130 = 0, + $q_sroa_1_1_ph = 0, + $q_sroa_0_1_ph = 0, + $r_sroa_1_1_ph = 0, + $r_sroa_0_1_ph = 0, + $sr_1_ph = 0, + $d_sroa_0_0_insert_insert99$0 = 0, + $d_sroa_0_0_insert_insert99$1 = 0, + $137$0 = 0, + $137$1 = 0, + $carry_0203 = 0, + $sr_1202 = 0, + $r_sroa_0_1201 = 0, + $r_sroa_1_1200 = 0, + $q_sroa_0_1199 = 0, + $q_sroa_1_1198 = 0, + $147 = 0, + $149 = 0, + $r_sroa_0_0_insert_insert42$0 = 0, + $r_sroa_0_0_insert_insert42$1 = 0, + $150$1 = 0, + $151$0 = 0, + $152 = 0, + $154$0 = 0, + $r_sroa_0_0_extract_trunc = 0, + $r_sroa_1_4_extract_trunc = 0, + $155 = 0, + $carry_0_lcssa$0 = 0, + $carry_0_lcssa$1 = 0, + $r_sroa_0_1_lcssa = 0, + $r_sroa_1_1_lcssa = 0, + $q_sroa_0_1_lcssa = 0, + $q_sroa_1_1_lcssa = 0, + $q_sroa_0_0_insert_ext75$0 = 0, + $q_sroa_0_0_insert_ext75$1 = 0, + $q_sroa_0_0_insert_insert77$1 = 0, + $_0$0 = 0, + $_0$1 = 0; + $n_sroa_0_0_extract_trunc = $a$0; + $n_sroa_1_4_extract_shift$0 = $a$1; + $n_sroa_1_4_extract_trunc = $n_sroa_1_4_extract_shift$0; + $d_sroa_0_0_extract_trunc = $b$0; + $d_sroa_1_4_extract_shift$0 = $b$1; + $d_sroa_1_4_extract_trunc = $d_sroa_1_4_extract_shift$0; + if (($n_sroa_1_4_extract_trunc | 0) == 0) { + $4 = ($rem | 0) != 0; + if (($d_sroa_1_4_extract_trunc | 0) == 0) { + if ($4) { + HEAP32[$rem >> 2] = + ($n_sroa_0_0_extract_trunc >>> 0) % + ($d_sroa_0_0_extract_trunc >>> 0); + HEAP32[($rem + 4) >> 2] = 0; + } + $_0$1 = 0; + $_0$0 = + (($n_sroa_0_0_extract_trunc >>> 0) / + ($d_sroa_0_0_extract_trunc >>> 0)) >>> + 0; + return ((tempRet0 = $_0$1), $_0$0) | 0; + } else { + if (!$4) { + $_0$1 = 0; + $_0$0 = 0; + return ((tempRet0 = $_0$1), $_0$0) | 0; + } + HEAP32[$rem >> 2] = $a$0 & -1; + HEAP32[($rem + 4) >> 2] = $a$1 & 0; + $_0$1 = 0; + $_0$0 = 0; + return ((tempRet0 = $_0$1), $_0$0) | 0; + } + } + $17 = ($d_sroa_1_4_extract_trunc | 0) == 0; + do { + if (($d_sroa_0_0_extract_trunc | 0) == 0) { + if ($17) { + if (($rem | 0) != 0) { + HEAP32[$rem >> 2] = + ($n_sroa_1_4_extract_trunc >>> 0) % + ($d_sroa_0_0_extract_trunc >>> 0); + HEAP32[($rem + 4) >> 2] = 0; + } + $_0$1 = 0; + $_0$0 = + (($n_sroa_1_4_extract_trunc >>> 0) / + ($d_sroa_0_0_extract_trunc >>> 0)) >>> + 0; + return ((tempRet0 = $_0$1), $_0$0) | 0; + } + if (($n_sroa_0_0_extract_trunc | 0) == 0) { + if (($rem | 0) != 0) { + HEAP32[$rem >> 2] = 0; + HEAP32[($rem + 4) >> 2] = + ($n_sroa_1_4_extract_trunc >>> 0) % + ($d_sroa_1_4_extract_trunc >>> 0); + } + $_0$1 = 0; + $_0$0 = + (($n_sroa_1_4_extract_trunc >>> 0) / + ($d_sroa_1_4_extract_trunc >>> 0)) >>> + 0; + return ((tempRet0 = $_0$1), $_0$0) | 0; + } + $37 = ($d_sroa_1_4_extract_trunc - 1) | 0; + if ((($37 & $d_sroa_1_4_extract_trunc) | 0) == 0) { + if (($rem | 0) != 0) { + HEAP32[$rem >> 2] = 0 | ($a$0 & -1); + HEAP32[($rem + 4) >> 2] = + ($37 & $n_sroa_1_4_extract_trunc) | ($a$1 & 0); + } + $_0$1 = 0; + $_0$0 = + $n_sroa_1_4_extract_trunc >>> + ((_llvm_cttz_i32($d_sroa_1_4_extract_trunc | 0) | 0) >>> 0); + return ((tempRet0 = $_0$1), $_0$0) | 0; + } + $49 = _llvm_ctlz_i32($d_sroa_1_4_extract_trunc | 0) | 0; + $51 = ($49 - (_llvm_ctlz_i32($n_sroa_1_4_extract_trunc | 0) | 0)) | 0; + if ($51 >>> 0 <= 30) { + $57 = ($51 + 1) | 0; + $58 = (31 - $51) | 0; + $sr_1_ph = $57; + $r_sroa_0_1_ph = + ($n_sroa_1_4_extract_trunc << $58) | + ($n_sroa_0_0_extract_trunc >>> ($57 >>> 0)); + $r_sroa_1_1_ph = $n_sroa_1_4_extract_trunc >>> ($57 >>> 0); + $q_sroa_0_1_ph = 0; + $q_sroa_1_1_ph = $n_sroa_0_0_extract_trunc << $58; + break; + } + if (($rem | 0) == 0) { + $_0$1 = 0; + $_0$0 = 0; + return ((tempRet0 = $_0$1), $_0$0) | 0; + } + HEAP32[$rem >> 2] = 0 | ($a$0 & -1); + HEAP32[($rem + 4) >> 2] = $n_sroa_1_4_extract_shift$0 | ($a$1 & 0); + $_0$1 = 0; + $_0$0 = 0; + return ((tempRet0 = $_0$1), $_0$0) | 0; + } else { + if (!$17) { + $117 = _llvm_ctlz_i32($d_sroa_1_4_extract_trunc | 0) | 0; + $119 = + ($117 - (_llvm_ctlz_i32($n_sroa_1_4_extract_trunc | 0) | 0)) | 0; + if ($119 >>> 0 <= 31) { + $125 = ($119 + 1) | 0; + $126 = (31 - $119) | 0; + $130 = ($119 - 31) >> 31; + $sr_1_ph = $125; + $r_sroa_0_1_ph = + (($n_sroa_0_0_extract_trunc >>> ($125 >>> 0)) & $130) | + ($n_sroa_1_4_extract_trunc << $126); + $r_sroa_1_1_ph = + ($n_sroa_1_4_extract_trunc >>> ($125 >>> 0)) & $130; + $q_sroa_0_1_ph = 0; + $q_sroa_1_1_ph = $n_sroa_0_0_extract_trunc << $126; + break; + } + if (($rem | 0) == 0) { + $_0$1 = 0; + $_0$0 = 0; + return ((tempRet0 = $_0$1), $_0$0) | 0; + } + HEAP32[$rem >> 2] = 0 | ($a$0 & -1); + HEAP32[($rem + 4) >> 2] = $n_sroa_1_4_extract_shift$0 | ($a$1 & 0); + $_0$1 = 0; + $_0$0 = 0; + return ((tempRet0 = $_0$1), $_0$0) | 0; + } + $66 = ($d_sroa_0_0_extract_trunc - 1) | 0; + if ((($66 & $d_sroa_0_0_extract_trunc) | 0) != 0) { + $86 = ((_llvm_ctlz_i32($d_sroa_0_0_extract_trunc | 0) | 0) + 33) | 0; + $88 = ($86 - (_llvm_ctlz_i32($n_sroa_1_4_extract_trunc | 0) | 0)) | 0; + $89 = (64 - $88) | 0; + $91 = (32 - $88) | 0; + $92 = $91 >> 31; + $95 = ($88 - 32) | 0; + $105 = $95 >> 31; + $sr_1_ph = $88; + $r_sroa_0_1_ph = + ((($91 - 1) >> 31) & ($n_sroa_1_4_extract_trunc >>> ($95 >>> 0))) | + ((($n_sroa_1_4_extract_trunc << $91) | + ($n_sroa_0_0_extract_trunc >>> ($88 >>> 0))) & + $105); + $r_sroa_1_1_ph = $105 & ($n_sroa_1_4_extract_trunc >>> ($88 >>> 0)); + $q_sroa_0_1_ph = ($n_sroa_0_0_extract_trunc << $89) & $92; + $q_sroa_1_1_ph = + ((($n_sroa_1_4_extract_trunc << $89) | + ($n_sroa_0_0_extract_trunc >>> ($95 >>> 0))) & + $92) | + (($n_sroa_0_0_extract_trunc << $91) & (($88 - 33) >> 31)); + break; + } + if (($rem | 0) != 0) { + HEAP32[$rem >> 2] = $66 & $n_sroa_0_0_extract_trunc; + HEAP32[($rem + 4) >> 2] = 0; + } + if (($d_sroa_0_0_extract_trunc | 0) == 1) { + $_0$1 = $n_sroa_1_4_extract_shift$0 | ($a$1 & 0); + $_0$0 = 0 | ($a$0 & -1); + return ((tempRet0 = $_0$1), $_0$0) | 0; + } else { + $78 = _llvm_cttz_i32($d_sroa_0_0_extract_trunc | 0) | 0; + $_0$1 = 0 | ($n_sroa_1_4_extract_trunc >>> ($78 >>> 0)); + $_0$0 = + ($n_sroa_1_4_extract_trunc << (32 - $78)) | + ($n_sroa_0_0_extract_trunc >>> ($78 >>> 0)) | + 0; + return ((tempRet0 = $_0$1), $_0$0) | 0; + } + } + } while (0); + if (($sr_1_ph | 0) == 0) { + $q_sroa_1_1_lcssa = $q_sroa_1_1_ph; + $q_sroa_0_1_lcssa = $q_sroa_0_1_ph; + $r_sroa_1_1_lcssa = $r_sroa_1_1_ph; + $r_sroa_0_1_lcssa = $r_sroa_0_1_ph; + $carry_0_lcssa$1 = 0; + $carry_0_lcssa$0 = 0; + } else { + $d_sroa_0_0_insert_insert99$0 = 0 | ($b$0 & -1); + $d_sroa_0_0_insert_insert99$1 = $d_sroa_1_4_extract_shift$0 | ($b$1 & 0); + $137$0 = + _i64Add( + $d_sroa_0_0_insert_insert99$0, + $d_sroa_0_0_insert_insert99$1, + -1, + -1 + ) | 0; + $137$1 = tempRet0; + $q_sroa_1_1198 = $q_sroa_1_1_ph; + $q_sroa_0_1199 = $q_sroa_0_1_ph; + $r_sroa_1_1200 = $r_sroa_1_1_ph; + $r_sroa_0_1201 = $r_sroa_0_1_ph; + $sr_1202 = $sr_1_ph; + $carry_0203 = 0; + while (1) { + $147 = ($q_sroa_0_1199 >>> 31) | ($q_sroa_1_1198 << 1); + $149 = $carry_0203 | ($q_sroa_0_1199 << 1); + $r_sroa_0_0_insert_insert42$0 = + 0 | (($r_sroa_0_1201 << 1) | ($q_sroa_1_1198 >>> 31)); + $r_sroa_0_0_insert_insert42$1 = + ($r_sroa_0_1201 >>> 31) | ($r_sroa_1_1200 << 1) | 0; + _i64Subtract( + $137$0, + $137$1, + $r_sroa_0_0_insert_insert42$0, + $r_sroa_0_0_insert_insert42$1 + ) | 0; + $150$1 = tempRet0; + $151$0 = ($150$1 >> 31) | ((($150$1 | 0) < 0 ? -1 : 0) << 1); + $152 = $151$0 & 1; + $154$0 = + _i64Subtract( + $r_sroa_0_0_insert_insert42$0, + $r_sroa_0_0_insert_insert42$1, + $151$0 & $d_sroa_0_0_insert_insert99$0, + (((($150$1 | 0) < 0 ? -1 : 0) >> 31) | + ((($150$1 | 0) < 0 ? -1 : 0) << 1)) & + $d_sroa_0_0_insert_insert99$1 + ) | 0; + $r_sroa_0_0_extract_trunc = $154$0; + $r_sroa_1_4_extract_trunc = tempRet0; + $155 = ($sr_1202 - 1) | 0; + if (($155 | 0) == 0) { + break; + } else { + $q_sroa_1_1198 = $147; + $q_sroa_0_1199 = $149; + $r_sroa_1_1200 = $r_sroa_1_4_extract_trunc; + $r_sroa_0_1201 = $r_sroa_0_0_extract_trunc; + $sr_1202 = $155; + $carry_0203 = $152; + } + } + $q_sroa_1_1_lcssa = $147; + $q_sroa_0_1_lcssa = $149; + $r_sroa_1_1_lcssa = $r_sroa_1_4_extract_trunc; + $r_sroa_0_1_lcssa = $r_sroa_0_0_extract_trunc; + $carry_0_lcssa$1 = 0; + $carry_0_lcssa$0 = $152; + } + $q_sroa_0_0_insert_ext75$0 = $q_sroa_0_1_lcssa; + $q_sroa_0_0_insert_ext75$1 = 0; + $q_sroa_0_0_insert_insert77$1 = + $q_sroa_1_1_lcssa | $q_sroa_0_0_insert_ext75$1; + if (($rem | 0) != 0) { + HEAP32[$rem >> 2] = 0 | $r_sroa_0_1_lcssa; + HEAP32[($rem + 4) >> 2] = $r_sroa_1_1_lcssa | 0; + } + $_0$1 = + ((0 | $q_sroa_0_0_insert_ext75$0) >>> 31) | + ($q_sroa_0_0_insert_insert77$1 << 1) | + ((($q_sroa_0_0_insert_ext75$1 << 1) | + ($q_sroa_0_0_insert_ext75$0 >>> 31)) & + 0) | + $carry_0_lcssa$1; + $_0$0 = + ((($q_sroa_0_0_insert_ext75$0 << 1) | (0 >>> 31)) & -2) | + $carry_0_lcssa$0; + return ((tempRet0 = $_0$1), $_0$0) | 0; + } + // ======================================================================= + + // EMSCRIPTEN_END_FUNCS + + // EMSCRIPTEN_END_FUNCS + + return { + _curve25519_verify: _curve25519_verify, + _crypto_sign_ed25519_ref10_ge_scalarmult_base: _crypto_sign_ed25519_ref10_ge_scalarmult_base, + _curve25519_sign: _curve25519_sign, + _free: _free, + _i64Add: _i64Add, + _memmove: _memmove, + _bitshift64Ashr: _bitshift64Ashr, + _sph_sha512_init: _sph_sha512_init, + _curve25519_donna: _curve25519_donna, + _memset: _memset, + _malloc: _malloc, + _memcpy: _memcpy, + _strlen: _strlen, + _bitshift64Lshr: _bitshift64Lshr, + _i64Subtract: _i64Subtract, + _bitshift64Shl: _bitshift64Shl, + runPostSets: runPostSets, + stackAlloc: stackAlloc, + stackSave: stackSave, + stackRestore: stackRestore, + setThrew: setThrew, + setTempRet0: setTempRet0, + getTempRet0: getTempRet0, + }; +})( + // EMSCRIPTEN_END_ASM + Module.asmGlobalArg, + Module.asmLibraryArg, + buffer +); +var _curve25519_verify = (Module['_curve25519_verify'] = + asm['_curve25519_verify']); +var _crypto_sign_ed25519_ref10_ge_scalarmult_base = (Module[ + '_crypto_sign_ed25519_ref10_ge_scalarmult_base' +] = + asm['_crypto_sign_ed25519_ref10_ge_scalarmult_base']); +var _curve25519_sign = (Module['_curve25519_sign'] = asm['_curve25519_sign']); +var _free = (Module['_free'] = asm['_free']); +var _i64Add = (Module['_i64Add'] = asm['_i64Add']); +var _memmove = (Module['_memmove'] = asm['_memmove']); +var _bitshift64Ashr = (Module['_bitshift64Ashr'] = asm['_bitshift64Ashr']); +var _sph_sha512_init = (Module['_sph_sha512_init'] = asm['_sph_sha512_init']); +var _curve25519_donna = (Module['_curve25519_donna'] = + asm['_curve25519_donna']); +var _memset = (Module['_memset'] = asm['_memset']); +var _malloc = (Module['_malloc'] = asm['_malloc']); +var _memcpy = (Module['_memcpy'] = asm['_memcpy']); +var _strlen = (Module['_strlen'] = asm['_strlen']); +var _bitshift64Lshr = (Module['_bitshift64Lshr'] = asm['_bitshift64Lshr']); +var _i64Subtract = (Module['_i64Subtract'] = asm['_i64Subtract']); +var _bitshift64Shl = (Module['_bitshift64Shl'] = asm['_bitshift64Shl']); +var runPostSets = (Module['runPostSets'] = asm['runPostSets']); + +Runtime.stackAlloc = asm['stackAlloc']; +Runtime.stackSave = asm['stackSave']; +Runtime.stackRestore = asm['stackRestore']; +Runtime.setTempRet0 = asm['setTempRet0']; +Runtime.getTempRet0 = asm['getTempRet0']; + +// TODO: strip out parts of this we do not need + +//======= begin closure i64 code ======= + +// Copyright 2009 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Defines a Long class for representing a 64-bit two's-complement + * integer value, which faithfully simulates the behavior of a Java "long". This + * implementation is derived from LongLib in GWT. + * + */ + +var i64Math = (function() { + // Emscripten wrapper + var goog = { math: {} }; + + /** + * Constructs a 64-bit two's-complement integer, given its low and high 32-bit + * values as *signed* integers. See the from* functions below for more + * convenient ways of constructing Longs. + * + * The internal representation of a long is the two given signed, 32-bit values. + * We use 32-bit pieces because these are the size of integers on which + * Javascript performs bit-operations. For operations like addition and + * multiplication, we split each number into 16-bit pieces, which can easily be + * multiplied within Javascript's floating-point representation without overflow + * or change in sign. + * + * In the algorithms below, we frequently reduce the negative case to the + * positive case by negating the input(s) and then post-processing the result. + * Note that we must ALWAYS check specially whether those values are MIN_VALUE + * (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as + * a positive number, it overflows back into a negative). Not handling this + * case would often result in infinite recursion. + * + * @param {number} low The low (signed) 32 bits of the long. + * @param {number} high The high (signed) 32 bits of the long. + * @constructor + */ + goog.math.Long = function(low, high) { + /** + * @type {number} + * @private + */ + this.low_ = low | 0; // force into 32 signed bits. + + /** + * @type {number} + * @private + */ + this.high_ = high | 0; // force into 32 signed bits. + }; + + // NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the + // from* methods on which they depend. + + /** + * A cache of the Long representations of small integer values. + * @type {!Object} + * @private + */ + goog.math.Long.IntCache_ = {}; + + /** + * Returns a Long representing the given (32-bit) integer value. + * @param {number} value The 32-bit integer in question. + * @return {!goog.math.Long} The corresponding Long value. + */ + goog.math.Long.fromInt = function(value) { + if (-128 <= value && value < 128) { + var cachedObj = goog.math.Long.IntCache_[value]; + if (cachedObj) { + return cachedObj; + } + } + + var obj = new goog.math.Long(value | 0, value < 0 ? -1 : 0); + if (-128 <= value && value < 128) { + goog.math.Long.IntCache_[value] = obj; + } + return obj; + }; + + /** + * Returns a Long representing the given value, provided that it is a finite + * number. Otherwise, zero is returned. + * @param {number} value The number in question. + * @return {!goog.math.Long} The corresponding Long value. + */ + goog.math.Long.fromNumber = function(value) { + if (isNaN(value) || !isFinite(value)) { + return goog.math.Long.ZERO; + } else if (value <= -goog.math.Long.TWO_PWR_63_DBL_) { + return goog.math.Long.MIN_VALUE; + } else if (value + 1 >= goog.math.Long.TWO_PWR_63_DBL_) { + return goog.math.Long.MAX_VALUE; + } else if (value < 0) { + return goog.math.Long.fromNumber(-value).negate(); + } else { + return new goog.math.Long( + (value % goog.math.Long.TWO_PWR_32_DBL_) | 0, + (value / goog.math.Long.TWO_PWR_32_DBL_) | 0 + ); + } + }; + + /** + * Returns a Long representing the 64-bit integer that comes by concatenating + * the given high and low bits. Each is assumed to use 32 bits. + * @param {number} lowBits The low 32-bits. + * @param {number} highBits The high 32-bits. + * @return {!goog.math.Long} The corresponding Long value. + */ + goog.math.Long.fromBits = function(lowBits, highBits) { + return new goog.math.Long(lowBits, highBits); + }; + + /** + * Returns a Long representation of the given string, written using the given + * radix. + * @param {string} str The textual representation of the Long. + * @param {number=} opt_radix The radix in which the text is written. + * @return {!goog.math.Long} The corresponding Long value. + */ + goog.math.Long.fromString = function(str, opt_radix) { + if (str.length == 0) { + throw Error('number format error: empty string'); + } + + var radix = opt_radix || 10; + if (radix < 2 || 36 < radix) { + throw Error('radix out of range: ' + radix); + } + + if (str.charAt(0) == '-') { + return goog.math.Long.fromString(str.substring(1), radix).negate(); + } else if (str.indexOf('-') >= 0) { + throw Error('number format error: interior "-" character: ' + str); + } + + // Do several (8) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = goog.math.Long.fromNumber(Math.pow(radix, 8)); + + var result = goog.math.Long.ZERO; + for (var i = 0; i < str.length; i += 8) { + var size = Math.min(8, str.length - i); + var value = parseInt(str.substring(i, i + size), radix); + if (size < 8) { + var power = goog.math.Long.fromNumber(Math.pow(radix, size)); + result = result.multiply(power).add(goog.math.Long.fromNumber(value)); + } else { + result = result.multiply(radixToPower); + result = result.add(goog.math.Long.fromNumber(value)); + } + } + return result; + }; + + // NOTE: the compiler should inline these constant values below and then remove + // these variables, so there should be no runtime penalty for these. + + /** + * Number used repeated below in calculations. This must appear before the + * first call to any from* function below. + * @type {number} + * @private + */ + goog.math.Long.TWO_PWR_16_DBL_ = 1 << 16; + + /** + * @type {number} + * @private + */ + goog.math.Long.TWO_PWR_24_DBL_ = 1 << 24; + + /** + * @type {number} + * @private + */ + goog.math.Long.TWO_PWR_32_DBL_ = + goog.math.Long.TWO_PWR_16_DBL_ * goog.math.Long.TWO_PWR_16_DBL_; + + /** + * @type {number} + * @private + */ + goog.math.Long.TWO_PWR_31_DBL_ = goog.math.Long.TWO_PWR_32_DBL_ / 2; + + /** + * @type {number} + * @private + */ + goog.math.Long.TWO_PWR_48_DBL_ = + goog.math.Long.TWO_PWR_32_DBL_ * goog.math.Long.TWO_PWR_16_DBL_; + + /** + * @type {number} + * @private + */ + goog.math.Long.TWO_PWR_64_DBL_ = + goog.math.Long.TWO_PWR_32_DBL_ * goog.math.Long.TWO_PWR_32_DBL_; + + /** + * @type {number} + * @private + */ + goog.math.Long.TWO_PWR_63_DBL_ = goog.math.Long.TWO_PWR_64_DBL_ / 2; + + /** @type {!goog.math.Long} */ + goog.math.Long.ZERO = goog.math.Long.fromInt(0); + + /** @type {!goog.math.Long} */ + goog.math.Long.ONE = goog.math.Long.fromInt(1); + + /** @type {!goog.math.Long} */ + goog.math.Long.NEG_ONE = goog.math.Long.fromInt(-1); + + /** @type {!goog.math.Long} */ + goog.math.Long.MAX_VALUE = goog.math.Long.fromBits( + 0xffffffff | 0, + 0x7fffffff | 0 + ); + + /** @type {!goog.math.Long} */ + goog.math.Long.MIN_VALUE = goog.math.Long.fromBits(0, 0x80000000 | 0); + + /** + * @type {!goog.math.Long} + * @private + */ + goog.math.Long.TWO_PWR_24_ = goog.math.Long.fromInt(1 << 24); + + /** @return {number} The value, assuming it is a 32-bit integer. */ + goog.math.Long.prototype.toInt = function() { + return this.low_; + }; + + /** @return {number} The closest floating-point representation to this value. */ + goog.math.Long.prototype.toNumber = function() { + return ( + this.high_ * goog.math.Long.TWO_PWR_32_DBL_ + this.getLowBitsUnsigned() + ); + }; + + /** + * @param {number=} opt_radix The radix in which the text should be written. + * @return {string} The textual representation of this value. + */ + goog.math.Long.prototype.toString = function(opt_radix) { + var radix = opt_radix || 10; + if (radix < 2 || 36 < radix) { + throw Error('radix out of range: ' + radix); + } + + if (this.isZero()) { + return '0'; + } + + if (this.isNegative()) { + if (this.equals(goog.math.Long.MIN_VALUE)) { + // We need to change the Long value before it can be negated, so we remove + // the bottom-most digit in this base and then recurse to do the rest. + var radixLong = goog.math.Long.fromNumber(radix); + var div = this.div(radixLong); + var rem = div.multiply(radixLong).subtract(this); + return div.toString(radix) + rem.toInt().toString(radix); + } else { + return '-' + this.negate().toString(radix); + } + } + + // Do several (6) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = goog.math.Long.fromNumber(Math.pow(radix, 6)); + + var rem = this; + var result = ''; + while (true) { + var remDiv = rem.div(radixToPower); + var intval = rem.subtract(remDiv.multiply(radixToPower)).toInt(); + var digits = intval.toString(radix); + + rem = remDiv; + if (rem.isZero()) { + return digits + result; + } else { + while (digits.length < 6) { + digits = '0' + digits; + } + result = '' + digits + result; + } + } + }; + + /** @return {number} The high 32-bits as a signed value. */ + goog.math.Long.prototype.getHighBits = function() { + return this.high_; + }; + + /** @return {number} The low 32-bits as a signed value. */ + goog.math.Long.prototype.getLowBits = function() { + return this.low_; + }; + + /** @return {number} The low 32-bits as an unsigned value. */ + goog.math.Long.prototype.getLowBitsUnsigned = function() { + return this.low_ >= 0 + ? this.low_ + : goog.math.Long.TWO_PWR_32_DBL_ + this.low_; + }; + + /** + * @return {number} Returns the number of bits needed to represent the absolute + * value of this Long. + */ + goog.math.Long.prototype.getNumBitsAbs = function() { + if (this.isNegative()) { + if (this.equals(goog.math.Long.MIN_VALUE)) { + return 64; + } else { + return this.negate().getNumBitsAbs(); + } + } else { + var val = this.high_ != 0 ? this.high_ : this.low_; + for (var bit = 31; bit > 0; bit--) { + if ((val & (1 << bit)) != 0) { + break; + } + } + return this.high_ != 0 ? bit + 33 : bit + 1; + } + }; + + /** @return {boolean} Whether this value is zero. */ + goog.math.Long.prototype.isZero = function() { + return this.high_ == 0 && this.low_ == 0; + }; + + /** @return {boolean} Whether this value is negative. */ + goog.math.Long.prototype.isNegative = function() { + return this.high_ < 0; + }; + + /** @return {boolean} Whether this value is odd. */ + goog.math.Long.prototype.isOdd = function() { + return (this.low_ & 1) == 1; + }; + + /** + * @param {goog.math.Long} other Long to compare against. + * @return {boolean} Whether this Long equals the other. + */ + goog.math.Long.prototype.equals = function(other) { + return this.high_ == other.high_ && this.low_ == other.low_; + }; + + /** + * @param {goog.math.Long} other Long to compare against. + * @return {boolean} Whether this Long does not equal the other. + */ + goog.math.Long.prototype.notEquals = function(other) { + return this.high_ != other.high_ || this.low_ != other.low_; + }; + + /** + * @param {goog.math.Long} other Long to compare against. + * @return {boolean} Whether this Long is less than the other. + */ + goog.math.Long.prototype.lessThan = function(other) { + return this.compare(other) < 0; + }; + + /** + * @param {goog.math.Long} other Long to compare against. + * @return {boolean} Whether this Long is less than or equal to the other. + */ + goog.math.Long.prototype.lessThanOrEqual = function(other) { + return this.compare(other) <= 0; + }; + + /** + * @param {goog.math.Long} other Long to compare against. + * @return {boolean} Whether this Long is greater than the other. + */ + goog.math.Long.prototype.greaterThan = function(other) { + return this.compare(other) > 0; + }; + + /** + * @param {goog.math.Long} other Long to compare against. + * @return {boolean} Whether this Long is greater than or equal to the other. + */ + goog.math.Long.prototype.greaterThanOrEqual = function(other) { + return this.compare(other) >= 0; + }; + + /** + * Compares this Long with the given one. + * @param {goog.math.Long} other Long to compare against. + * @return {number} 0 if they are the same, 1 if the this is greater, and -1 + * if the given one is greater. + */ + goog.math.Long.prototype.compare = function(other) { + if (this.equals(other)) { + return 0; + } + + var thisNeg = this.isNegative(); + var otherNeg = other.isNegative(); + if (thisNeg && !otherNeg) { + return -1; + } + if (!thisNeg && otherNeg) { + return 1; + } + + // at this point, the signs are the same, so subtraction will not overflow + if (this.subtract(other).isNegative()) { + return -1; + } else { + return 1; + } + }; + + /** @return {!goog.math.Long} The negation of this value. */ + goog.math.Long.prototype.negate = function() { + if (this.equals(goog.math.Long.MIN_VALUE)) { + return goog.math.Long.MIN_VALUE; + } else { + return this.not().add(goog.math.Long.ONE); + } + }; + + /** + * Returns the sum of this and the given Long. + * @param {goog.math.Long} other Long to add to this one. + * @return {!goog.math.Long} The sum of this and the given Long. + */ + goog.math.Long.prototype.add = function(other) { + // Divide each number into 4 chunks of 16 bits, and then sum the chunks. + + var a48 = this.high_ >>> 16; + var a32 = this.high_ & 0xffff; + var a16 = this.low_ >>> 16; + var a00 = this.low_ & 0xffff; + + var b48 = other.high_ >>> 16; + var b32 = other.high_ & 0xffff; + var b16 = other.low_ >>> 16; + var b00 = other.low_ & 0xffff; + + var c48 = 0, + c32 = 0, + c16 = 0, + c00 = 0; + c00 += a00 + b00; + c16 += c00 >>> 16; + c00 &= 0xffff; + c16 += a16 + b16; + c32 += c16 >>> 16; + c16 &= 0xffff; + c32 += a32 + b32; + c48 += c32 >>> 16; + c32 &= 0xffff; + c48 += a48 + b48; + c48 &= 0xffff; + return goog.math.Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32); + }; + + /** + * Returns the difference of this and the given Long. + * @param {goog.math.Long} other Long to subtract from this. + * @return {!goog.math.Long} The difference of this and the given Long. + */ + goog.math.Long.prototype.subtract = function(other) { + return this.add(other.negate()); + }; + + /** + * Returns the product of this and the given long. + * @param {goog.math.Long} other Long to multiply with this. + * @return {!goog.math.Long} The product of this and the other. + */ + goog.math.Long.prototype.multiply = function(other) { + if (this.isZero()) { + return goog.math.Long.ZERO; + } else if (other.isZero()) { + return goog.math.Long.ZERO; + } + + if (this.equals(goog.math.Long.MIN_VALUE)) { + return other.isOdd() ? goog.math.Long.MIN_VALUE : goog.math.Long.ZERO; + } else if (other.equals(goog.math.Long.MIN_VALUE)) { + return this.isOdd() ? goog.math.Long.MIN_VALUE : goog.math.Long.ZERO; + } + + if (this.isNegative()) { + if (other.isNegative()) { + return this.negate().multiply(other.negate()); + } else { + return this.negate() + .multiply(other) + .negate(); + } + } else if (other.isNegative()) { + return this.multiply(other.negate()).negate(); + } + + // If both longs are small, use float multiplication + if ( + this.lessThan(goog.math.Long.TWO_PWR_24_) && + other.lessThan(goog.math.Long.TWO_PWR_24_) + ) { + return goog.math.Long.fromNumber(this.toNumber() * other.toNumber()); + } + + // Divide each long into 4 chunks of 16 bits, and then add up 4x4 products. + // We can skip products that would overflow. + + var a48 = this.high_ >>> 16; + var a32 = this.high_ & 0xffff; + var a16 = this.low_ >>> 16; + var a00 = this.low_ & 0xffff; + + var b48 = other.high_ >>> 16; + var b32 = other.high_ & 0xffff; + var b16 = other.low_ >>> 16; + var b00 = other.low_ & 0xffff; + + var c48 = 0, + c32 = 0, + c16 = 0, + c00 = 0; + c00 += a00 * b00; + c16 += c00 >>> 16; + c00 &= 0xffff; + c16 += a16 * b00; + c32 += c16 >>> 16; + c16 &= 0xffff; + c16 += a00 * b16; + c32 += c16 >>> 16; + c16 &= 0xffff; + c32 += a32 * b00; + c48 += c32 >>> 16; + c32 &= 0xffff; + c32 += a16 * b16; + c48 += c32 >>> 16; + c32 &= 0xffff; + c32 += a00 * b32; + c48 += c32 >>> 16; + c32 &= 0xffff; + c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; + c48 &= 0xffff; + return goog.math.Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32); + }; + + /** + * Returns this Long divided by the given one. + * @param {goog.math.Long} other Long by which to divide. + * @return {!goog.math.Long} This Long divided by the given one. + */ + goog.math.Long.prototype.div = function(other) { + if (other.isZero()) { + throw Error('division by zero'); + } else if (this.isZero()) { + return goog.math.Long.ZERO; + } + + if (this.equals(goog.math.Long.MIN_VALUE)) { + if ( + other.equals(goog.math.Long.ONE) || + other.equals(goog.math.Long.NEG_ONE) + ) { + return goog.math.Long.MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE + } else if (other.equals(goog.math.Long.MIN_VALUE)) { + return goog.math.Long.ONE; + } else { + // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|. + var halfThis = this.shiftRight(1); + var approx = halfThis.div(other).shiftLeft(1); + if (approx.equals(goog.math.Long.ZERO)) { + return other.isNegative() + ? goog.math.Long.ONE + : goog.math.Long.NEG_ONE; + } else { + var rem = this.subtract(other.multiply(approx)); + var result = approx.add(rem.div(other)); + return result; + } + } + } else if (other.equals(goog.math.Long.MIN_VALUE)) { + return goog.math.Long.ZERO; + } + + if (this.isNegative()) { + if (other.isNegative()) { + return this.negate().div(other.negate()); + } else { + return this.negate() + .div(other) + .negate(); + } + } else if (other.isNegative()) { + return this.div(other.negate()).negate(); + } + + // Repeat the following until the remainder is less than other: find a + // floating-point that approximates remainder / other *from below*, add this + // into the result, and subtract it from the remainder. It is critical that + // the approximate value is less than or equal to the real value so that the + // remainder never becomes negative. + var res = goog.math.Long.ZERO; + var rem = this; + while (rem.greaterThanOrEqual(other)) { + // Approximate the result of division. This may be a little greater or + // smaller than the actual value. + var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber())); + + // We will tweak the approximate result by changing it in the 48-th digit or + // the smallest non-fractional digit, whichever is larger. + var log2 = Math.ceil(Math.log(approx) / Math.LN2); + var delta = log2 <= 48 ? 1 : Math.pow(2, log2 - 48); + + // Decrease the approximation until it is smaller than the remainder. Note + // that if it is too large, the product overflows and is negative. + var approxRes = goog.math.Long.fromNumber(approx); + var approxRem = approxRes.multiply(other); + while (approxRem.isNegative() || approxRem.greaterThan(rem)) { + approx -= delta; + approxRes = goog.math.Long.fromNumber(approx); + approxRem = approxRes.multiply(other); + } + + // We know the answer can't be zero... and actually, zero would cause + // infinite recursion since we would make no progress. + if (approxRes.isZero()) { + approxRes = goog.math.Long.ONE; + } + + res = res.add(approxRes); + rem = rem.subtract(approxRem); + } + return res; + }; + + /** + * Returns this Long modulo the given one. + * @param {goog.math.Long} other Long by which to mod. + * @return {!goog.math.Long} This Long modulo the given one. + */ + goog.math.Long.prototype.modulo = function(other) { + return this.subtract(this.div(other).multiply(other)); + }; + + /** @return {!goog.math.Long} The bitwise-NOT of this value. */ + goog.math.Long.prototype.not = function() { + return goog.math.Long.fromBits(~this.low_, ~this.high_); + }; + + /** + * Returns the bitwise-AND of this Long and the given one. + * @param {goog.math.Long} other The Long with which to AND. + * @return {!goog.math.Long} The bitwise-AND of this and the other. + */ + goog.math.Long.prototype.and = function(other) { + return goog.math.Long.fromBits( + this.low_ & other.low_, + this.high_ & other.high_ + ); + }; + + /** + * Returns the bitwise-OR of this Long and the given one. + * @param {goog.math.Long} other The Long with which to OR. + * @return {!goog.math.Long} The bitwise-OR of this and the other. + */ + goog.math.Long.prototype.or = function(other) { + return goog.math.Long.fromBits( + this.low_ | other.low_, + this.high_ | other.high_ + ); + }; + + /** + * Returns the bitwise-XOR of this Long and the given one. + * @param {goog.math.Long} other The Long with which to XOR. + * @return {!goog.math.Long} The bitwise-XOR of this and the other. + */ + goog.math.Long.prototype.xor = function(other) { + return goog.math.Long.fromBits( + this.low_ ^ other.low_, + this.high_ ^ other.high_ + ); + }; + + /** + * Returns this Long with bits shifted to the left by the given amount. + * @param {number} numBits The number of bits by which to shift. + * @return {!goog.math.Long} This shifted to the left by the given amount. + */ + goog.math.Long.prototype.shiftLeft = function(numBits) { + numBits &= 63; + if (numBits == 0) { + return this; + } else { + var low = this.low_; + if (numBits < 32) { + var high = this.high_; + return goog.math.Long.fromBits( + low << numBits, + (high << numBits) | (low >>> (32 - numBits)) + ); + } else { + return goog.math.Long.fromBits(0, low << (numBits - 32)); + } + } + }; + + /** + * Returns this Long with bits shifted to the right by the given amount. + * @param {number} numBits The number of bits by which to shift. + * @return {!goog.math.Long} This shifted to the right by the given amount. + */ + goog.math.Long.prototype.shiftRight = function(numBits) { + numBits &= 63; + if (numBits == 0) { + return this; + } else { + var high = this.high_; + if (numBits < 32) { + var low = this.low_; + return goog.math.Long.fromBits( + (low >>> numBits) | (high << (32 - numBits)), + high >> numBits + ); + } else { + return goog.math.Long.fromBits( + high >> (numBits - 32), + high >= 0 ? 0 : -1 + ); + } + } + }; + + /** + * Returns this Long with bits shifted to the right by the given amount, with + * the new top bits matching the current sign bit. + * @param {number} numBits The number of bits by which to shift. + * @return {!goog.math.Long} This shifted to the right by the given amount, with + * zeros placed into the new leading bits. + */ + goog.math.Long.prototype.shiftRightUnsigned = function(numBits) { + numBits &= 63; + if (numBits == 0) { + return this; + } else { + var high = this.high_; + if (numBits < 32) { + var low = this.low_; + return goog.math.Long.fromBits( + (low >>> numBits) | (high << (32 - numBits)), + high >>> numBits + ); + } else if (numBits == 32) { + return goog.math.Long.fromBits(high, 0); + } else { + return goog.math.Long.fromBits(high >>> (numBits - 32), 0); + } + } + }; + + //======= begin jsbn ======= + + var navigator = { appName: 'Modern Browser' }; // polyfill a little + + // Copyright (c) 2005 Tom Wu + // All Rights Reserved. + // http://www-cs-students.stanford.edu/~tjw/jsbn/ + + /* + * Copyright (c) 2003-2005 Tom Wu + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF + * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * In addition, the following condition applies: + * + * All redistributions must retain an intact copy of this copyright notice + * and disclaimer. + */ + + // Basic JavaScript BN library - subset useful for RSA encryption. + + // Bits per digit + var dbits; + + // JavaScript engine analysis + var canary = 0xdeadbeefcafe; + var j_lm = (canary & 0xffffff) == 0xefcafe; + + // (public) Constructor + function BigInteger(a, b, c) { + if (a != null) + if ('number' == typeof a) this.fromNumber(a, b, c); + else if (b == null && 'string' != typeof a) this.fromString(a, 256); + else this.fromString(a, b); + } + + // return new, unset BigInteger + function nbi() { + return new BigInteger(null); + } + + // am: Compute w_j += (x*this_i), propagate carries, + // c is initial carry, returns final carry. + // c < 3*dvalue, x < 2*dvalue, this_i < dvalue + // We need to select the fastest one that works in this environment. + + // am1: use a single mult and divide to get the high bits, + // max digit bits should be 26 because + // max internal value = 2*dvalue^2-2*dvalue (< 2^53) + function am1(i, x, w, j, c, n) { + while (--n >= 0) { + var v = x * this[i++] + w[j] + c; + c = Math.floor(v / 0x4000000); + w[j++] = v & 0x3ffffff; + } + return c; + } + // am2 avoids a big mult-and-extract completely. + // Max digit bits should be <= 30 because we do bitwise ops + // on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) + function am2(i, x, w, j, c, n) { + var xl = x & 0x7fff, + xh = x >> 15; + while (--n >= 0) { + var l = this[i] & 0x7fff; + var h = this[i++] >> 15; + var m = xh * l + h * xl; + l = xl * l + ((m & 0x7fff) << 15) + w[j] + (c & 0x3fffffff); + c = (l >>> 30) + (m >>> 15) + xh * h + (c >>> 30); + w[j++] = l & 0x3fffffff; + } + return c; + } + // Alternately, set max digit bits to 28 since some + // browsers slow down when dealing with 32-bit numbers. + function am3(i, x, w, j, c, n) { + var xl = x & 0x3fff, + xh = x >> 14; + while (--n >= 0) { + var l = this[i] & 0x3fff; + var h = this[i++] >> 14; + var m = xh * l + h * xl; + l = xl * l + ((m & 0x3fff) << 14) + w[j] + c; + c = (l >> 28) + (m >> 14) + xh * h; + w[j++] = l & 0xfffffff; + } + return c; + } + if (j_lm && navigator.appName == 'Microsoft Internet Explorer') { + BigInteger.prototype.am = am2; + dbits = 30; + } else if (j_lm && navigator.appName != 'Netscape') { + BigInteger.prototype.am = am1; + dbits = 26; + } else { + // Mozilla/Netscape seems to prefer am3 + BigInteger.prototype.am = am3; + dbits = 28; + } + + BigInteger.prototype.DB = dbits; + BigInteger.prototype.DM = (1 << dbits) - 1; + BigInteger.prototype.DV = 1 << dbits; + + var BI_FP = 52; + BigInteger.prototype.FV = Math.pow(2, BI_FP); + BigInteger.prototype.F1 = BI_FP - dbits; + BigInteger.prototype.F2 = 2 * dbits - BI_FP; + + // Digit conversions + var BI_RM = '0123456789abcdefghijklmnopqrstuvwxyz'; + var BI_RC = new Array(); + var rr, vv; + rr = '0'.charCodeAt(0); + for (vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv; + rr = 'a'.charCodeAt(0); + for (vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; + rr = 'A'.charCodeAt(0); + for (vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; + + function int2char(n) { + return BI_RM.charAt(n); + } + function intAt(s, i) { + var c = BI_RC[s.charCodeAt(i)]; + return c == null ? -1 : c; + } + + // (protected) copy this to r + function bnpCopyTo(r) { + for (var i = this.t - 1; i >= 0; --i) r[i] = this[i]; + r.t = this.t; + r.s = this.s; + } + + // (protected) set from integer value x, -DV <= x < DV + function bnpFromInt(x) { + this.t = 1; + this.s = x < 0 ? -1 : 0; + if (x > 0) this[0] = x; + else if (x < -1) this[0] = x + DV; + else this.t = 0; + } + + // return bigint initialized to value + function nbv(i) { + var r = nbi(); + r.fromInt(i); + return r; + } + + // (protected) set from string and radix + function bnpFromString(s, b) { + var k; + if (b == 16) k = 4; + else if (b == 8) k = 3; + else if (b == 256) k = 8; + // byte array + else if (b == 2) k = 1; + else if (b == 32) k = 5; + else if (b == 4) k = 2; + else { + this.fromRadix(s, b); + return; + } + this.t = 0; + this.s = 0; + var i = s.length, + mi = false, + sh = 0; + while (--i >= 0) { + var x = k == 8 ? s[i] & 0xff : intAt(s, i); + if (x < 0) { + if (s.charAt(i) == '-') mi = true; + continue; + } + mi = false; + if (sh == 0) this[this.t++] = x; + else if (sh + k > this.DB) { + this[this.t - 1] |= (x & ((1 << (this.DB - sh)) - 1)) << sh; + this[this.t++] = x >> (this.DB - sh); + } else this[this.t - 1] |= x << sh; + sh += k; + if (sh >= this.DB) sh -= this.DB; + } + if (k == 8 && (s[0] & 0x80) != 0) { + this.s = -1; + if (sh > 0) this[this.t - 1] |= ((1 << (this.DB - sh)) - 1) << sh; + } + this.clamp(); + if (mi) BigInteger.ZERO.subTo(this, this); + } + + // (protected) clamp off excess high words + function bnpClamp() { + var c = this.s & this.DM; + while (this.t > 0 && this[this.t - 1] == c) --this.t; + } + + // (public) return string representation in given radix + function bnToString(b) { + if (this.s < 0) return '-' + this.negate().toString(b); + var k; + if (b == 16) k = 4; + else if (b == 8) k = 3; + else if (b == 2) k = 1; + else if (b == 32) k = 5; + else if (b == 4) k = 2; + else return this.toRadix(b); + var km = (1 << k) - 1, + d, + m = false, + r = '', + i = this.t; + var p = this.DB - (i * this.DB) % k; + if (i-- > 0) { + if (p < this.DB && (d = this[i] >> p) > 0) { + m = true; + r = int2char(d); + } + while (i >= 0) { + if (p < k) { + d = (this[i] & ((1 << p) - 1)) << (k - p); + d |= this[--i] >> (p += this.DB - k); + } else { + d = (this[i] >> (p -= k)) & km; + if (p <= 0) { + p += this.DB; + --i; + } + } + if (d > 0) m = true; + if (m) r += int2char(d); + } + } + return m ? r : '0'; + } + + // (public) -this + function bnNegate() { + var r = nbi(); + BigInteger.ZERO.subTo(this, r); + return r; + } + + // (public) |this| + function bnAbs() { + return this.s < 0 ? this.negate() : this; + } + + // (public) return + if this > a, - if this < a, 0 if equal + function bnCompareTo(a) { + var r = this.s - a.s; + if (r != 0) return r; + var i = this.t; + r = i - a.t; + if (r != 0) return this.s < 0 ? -r : r; + while (--i >= 0) if ((r = this[i] - a[i]) != 0) return r; + return 0; + } + + // returns bit length of the integer x + function nbits(x) { + var r = 1, + t; + if ((t = x >>> 16) != 0) { + x = t; + r += 16; + } + if ((t = x >> 8) != 0) { + x = t; + r += 8; + } + if ((t = x >> 4) != 0) { + x = t; + r += 4; + } + if ((t = x >> 2) != 0) { + x = t; + r += 2; + } + if ((t = x >> 1) != 0) { + x = t; + r += 1; + } + return r; + } + + // (public) return the number of bits in "this" + function bnBitLength() { + if (this.t <= 0) return 0; + return ( + this.DB * (this.t - 1) + nbits(this[this.t - 1] ^ (this.s & this.DM)) + ); + } + + // (protected) r = this << n*DB + function bnpDLShiftTo(n, r) { + var i; + for (i = this.t - 1; i >= 0; --i) r[i + n] = this[i]; + for (i = n - 1; i >= 0; --i) r[i] = 0; + r.t = this.t + n; + r.s = this.s; + } + + // (protected) r = this >> n*DB + function bnpDRShiftTo(n, r) { + for (var i = n; i < this.t; ++i) r[i - n] = this[i]; + r.t = Math.max(this.t - n, 0); + r.s = this.s; + } + + // (protected) r = this << n + function bnpLShiftTo(n, r) { + var bs = n % this.DB; + var cbs = this.DB - bs; + var bm = (1 << cbs) - 1; + var ds = Math.floor(n / this.DB), + c = (this.s << bs) & this.DM, + i; + for (i = this.t - 1; i >= 0; --i) { + r[i + ds + 1] = (this[i] >> cbs) | c; + c = (this[i] & bm) << bs; + } + for (i = ds - 1; i >= 0; --i) r[i] = 0; + r[ds] = c; + r.t = this.t + ds + 1; + r.s = this.s; + r.clamp(); + } + + // (protected) r = this >> n + function bnpRShiftTo(n, r) { + r.s = this.s; + var ds = Math.floor(n / this.DB); + if (ds >= this.t) { + r.t = 0; + return; + } + var bs = n % this.DB; + var cbs = this.DB - bs; + var bm = (1 << bs) - 1; + r[0] = this[ds] >> bs; + for (var i = ds + 1; i < this.t; ++i) { + r[i - ds - 1] |= (this[i] & bm) << cbs; + r[i - ds] = this[i] >> bs; + } + if (bs > 0) r[this.t - ds - 1] |= (this.s & bm) << cbs; + r.t = this.t - ds; + r.clamp(); + } + + // (protected) r = this - a + function bnpSubTo(a, r) { + var i = 0, + c = 0, + m = Math.min(a.t, this.t); + while (i < m) { + c += this[i] - a[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + if (a.t < this.t) { + c -= a.s; + while (i < this.t) { + c += this[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + c += this.s; + } else { + c += this.s; + while (i < a.t) { + c -= a[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + c -= a.s; + } + r.s = c < 0 ? -1 : 0; + if (c < -1) r[i++] = this.DV + c; + else if (c > 0) r[i++] = c; + r.t = i; + r.clamp(); + } + + // (protected) r = this * a, r != this,a (HAC 14.12) + // "this" should be the larger one if appropriate. + function bnpMultiplyTo(a, r) { + var x = this.abs(), + y = a.abs(); + var i = x.t; + r.t = i + y.t; + while (--i >= 0) r[i] = 0; + for (i = 0; i < y.t; ++i) r[i + x.t] = x.am(0, y[i], r, i, 0, x.t); + r.s = 0; + r.clamp(); + if (this.s != a.s) BigInteger.ZERO.subTo(r, r); + } + + // (protected) r = this^2, r != this (HAC 14.16) + function bnpSquareTo(r) { + var x = this.abs(); + var i = (r.t = 2 * x.t); + while (--i >= 0) r[i] = 0; + for (i = 0; i < x.t - 1; ++i) { + var c = x.am(i, x[i], r, 2 * i, 0, 1); + if ( + (r[i + x.t] += x.am(i + 1, 2 * x[i], r, 2 * i + 1, c, x.t - i - 1)) >= + x.DV + ) { + r[i + x.t] -= x.DV; + r[i + x.t + 1] = 1; + } + } + if (r.t > 0) r[r.t - 1] += x.am(i, x[i], r, 2 * i, 0, 1); + r.s = 0; + r.clamp(); + } + + // (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) + // r != q, this != m. q or r may be null. + function bnpDivRemTo(m, q, r) { + var pm = m.abs(); + if (pm.t <= 0) return; + var pt = this.abs(); + if (pt.t < pm.t) { + if (q != null) q.fromInt(0); + if (r != null) this.copyTo(r); + return; + } + if (r == null) r = nbi(); + var y = nbi(), + ts = this.s, + ms = m.s; + var nsh = this.DB - nbits(pm[pm.t - 1]); // normalize modulus + if (nsh > 0) { + pm.lShiftTo(nsh, y); + pt.lShiftTo(nsh, r); + } else { + pm.copyTo(y); + pt.copyTo(r); + } + var ys = y.t; + var y0 = y[ys - 1]; + if (y0 == 0) return; + var yt = y0 * (1 << this.F1) + (ys > 1 ? y[ys - 2] >> this.F2 : 0); + var d1 = this.FV / yt, + d2 = (1 << this.F1) / yt, + e = 1 << this.F2; + var i = r.t, + j = i - ys, + t = q == null ? nbi() : q; + y.dlShiftTo(j, t); + if (r.compareTo(t) >= 0) { + r[r.t++] = 1; + r.subTo(t, r); + } + BigInteger.ONE.dlShiftTo(ys, t); + t.subTo(y, y); // "negative" y so we can replace sub with am later + while (y.t < ys) y[y.t++] = 0; + while (--j >= 0) { + // Estimate quotient digit + var qd = + r[--i] == y0 ? this.DM : Math.floor(r[i] * d1 + (r[i - 1] + e) * d2); + if ((r[i] += y.am(0, qd, r, j, 0, ys)) < qd) { + // Try it out + y.dlShiftTo(j, t); + r.subTo(t, r); + while (r[i] < --qd) r.subTo(t, r); + } + } + if (q != null) { + r.drShiftTo(ys, q); + if (ts != ms) BigInteger.ZERO.subTo(q, q); + } + r.t = ys; + r.clamp(); + if (nsh > 0) r.rShiftTo(nsh, r); // Denormalize remainder + if (ts < 0) BigInteger.ZERO.subTo(r, r); + } + + // (public) this mod a + function bnMod(a) { + var r = nbi(); + this.abs().divRemTo(a, null, r); + if (this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r, r); + return r; + } + + // Modular reduction using "classic" algorithm + function Classic(m) { + this.m = m; + } + function cConvert(x) { + if (x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); + else return x; + } + function cRevert(x) { + return x; + } + function cReduce(x) { + x.divRemTo(this.m, null, x); + } + function cMulTo(x, y, r) { + x.multiplyTo(y, r); + this.reduce(r); + } + function cSqrTo(x, r) { + x.squareTo(r); + this.reduce(r); + } + + Classic.prototype.convert = cConvert; + Classic.prototype.revert = cRevert; + Classic.prototype.reduce = cReduce; + Classic.prototype.mulTo = cMulTo; + Classic.prototype.sqrTo = cSqrTo; + + // (protected) return "-1/this % 2^DB"; useful for Mont. reduction + // justification: + // xy == 1 (mod m) + // xy = 1+km + // xy(2-xy) = (1+km)(1-km) + // x[y(2-xy)] = 1-k^2m^2 + // x[y(2-xy)] == 1 (mod m^2) + // if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 + // should reduce x and y(2-xy) by m^2 at each step to keep size bounded. + // JS multiply "overflows" differently from C/C++, so care is needed here. + function bnpInvDigit() { + if (this.t < 1) return 0; + var x = this[0]; + if ((x & 1) == 0) return 0; + var y = x & 3; // y == 1/x mod 2^2 + y = (y * (2 - (x & 0xf) * y)) & 0xf; // y == 1/x mod 2^4 + y = (y * (2 - (x & 0xff) * y)) & 0xff; // y == 1/x mod 2^8 + y = (y * (2 - (((x & 0xffff) * y) & 0xffff))) & 0xffff; // y == 1/x mod 2^16 + // last step - calculate inverse mod DV directly; + // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints + y = (y * (2 - (x * y) % this.DV)) % this.DV; // y == 1/x mod 2^dbits + // we really want the negative inverse, and -DV < y < DV + return y > 0 ? this.DV - y : -y; + } + + // Montgomery reduction + function Montgomery(m) { + this.m = m; + this.mp = m.invDigit(); + this.mpl = this.mp & 0x7fff; + this.mph = this.mp >> 15; + this.um = (1 << (m.DB - 15)) - 1; + this.mt2 = 2 * m.t; + } + + // xR mod m + function montConvert(x) { + var r = nbi(); + x.abs().dlShiftTo(this.m.t, r); + r.divRemTo(this.m, null, r); + if (x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r, r); + return r; + } + + // x/R mod m + function montRevert(x) { + var r = nbi(); + x.copyTo(r); + this.reduce(r); + return r; + } + + // x = x/R mod m (HAC 14.32) + function montReduce(x) { + while ( + x.t <= this.mt2 // pad x so am has enough room later + ) + x[x.t++] = 0; + for (var i = 0; i < this.m.t; ++i) { + // faster way of calculating u0 = x[i]*mp mod DV + var j = x[i] & 0x7fff; + var u0 = + (j * this.mpl + + (((j * this.mph + (x[i] >> 15) * this.mpl) & this.um) << 15)) & + x.DM; + // use am to combine the multiply-shift-add into one call + j = i + this.m.t; + x[j] += this.m.am(0, u0, x, i, 0, this.m.t); + // propagate carry + while (x[j] >= x.DV) { + x[j] -= x.DV; + x[++j]++; + } + } + x.clamp(); + x.drShiftTo(this.m.t, x); + if (x.compareTo(this.m) >= 0) x.subTo(this.m, x); + } + + // r = "x^2/R mod m"; x != r + function montSqrTo(x, r) { + x.squareTo(r); + this.reduce(r); + } + + // r = "xy/R mod m"; x,y != r + function montMulTo(x, y, r) { + x.multiplyTo(y, r); + this.reduce(r); + } + + Montgomery.prototype.convert = montConvert; + Montgomery.prototype.revert = montRevert; + Montgomery.prototype.reduce = montReduce; + Montgomery.prototype.mulTo = montMulTo; + Montgomery.prototype.sqrTo = montSqrTo; + + // (protected) true iff this is even + function bnpIsEven() { + return (this.t > 0 ? this[0] & 1 : this.s) == 0; + } + + // (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) + function bnpExp(e, z) { + if (e > 0xffffffff || e < 1) return BigInteger.ONE; + var r = nbi(), + r2 = nbi(), + g = z.convert(this), + i = nbits(e) - 1; + g.copyTo(r); + while (--i >= 0) { + z.sqrTo(r, r2); + if ((e & (1 << i)) > 0) z.mulTo(r2, g, r); + else { + var t = r; + r = r2; + r2 = t; + } + } + return z.revert(r); + } + + // (public) this^e % m, 0 <= e < 2^32 + function bnModPowInt(e, m) { + var z; + if (e < 256 || m.isEven()) z = new Classic(m); + else z = new Montgomery(m); + return this.exp(e, z); + } + + // protected + BigInteger.prototype.copyTo = bnpCopyTo; + BigInteger.prototype.fromInt = bnpFromInt; + BigInteger.prototype.fromString = bnpFromString; + BigInteger.prototype.clamp = bnpClamp; + BigInteger.prototype.dlShiftTo = bnpDLShiftTo; + BigInteger.prototype.drShiftTo = bnpDRShiftTo; + BigInteger.prototype.lShiftTo = bnpLShiftTo; + BigInteger.prototype.rShiftTo = bnpRShiftTo; + BigInteger.prototype.subTo = bnpSubTo; + BigInteger.prototype.multiplyTo = bnpMultiplyTo; + BigInteger.prototype.squareTo = bnpSquareTo; + BigInteger.prototype.divRemTo = bnpDivRemTo; + BigInteger.prototype.invDigit = bnpInvDigit; + BigInteger.prototype.isEven = bnpIsEven; + BigInteger.prototype.exp = bnpExp; + + // public + BigInteger.prototype.toString = bnToString; + BigInteger.prototype.negate = bnNegate; + BigInteger.prototype.abs = bnAbs; + BigInteger.prototype.compareTo = bnCompareTo; + BigInteger.prototype.bitLength = bnBitLength; + BigInteger.prototype.mod = bnMod; + BigInteger.prototype.modPowInt = bnModPowInt; + + // "constants" + BigInteger.ZERO = nbv(0); + BigInteger.ONE = nbv(1); + + // jsbn2 stuff + + // (protected) convert from radix string + function bnpFromRadix(s, b) { + this.fromInt(0); + if (b == null) b = 10; + var cs = this.chunkSize(b); + var d = Math.pow(b, cs), + mi = false, + j = 0, + w = 0; + for (var i = 0; i < s.length; ++i) { + var x = intAt(s, i); + if (x < 0) { + if (s.charAt(i) == '-' && this.signum() == 0) mi = true; + continue; + } + w = b * w + x; + if (++j >= cs) { + this.dMultiply(d); + this.dAddOffset(w, 0); + j = 0; + w = 0; + } + } + if (j > 0) { + this.dMultiply(Math.pow(b, j)); + this.dAddOffset(w, 0); + } + if (mi) BigInteger.ZERO.subTo(this, this); + } + + // (protected) return x s.t. r^x < DV + function bnpChunkSize(r) { + return Math.floor(Math.LN2 * this.DB / Math.log(r)); + } + + // (public) 0 if this == 0, 1 if this > 0 + function bnSigNum() { + if (this.s < 0) return -1; + else if (this.t <= 0 || (this.t == 1 && this[0] <= 0)) return 0; + else return 1; + } + + // (protected) this *= n, this >= 0, 1 < n < DV + function bnpDMultiply(n) { + this[this.t] = this.am(0, n - 1, this, 0, 0, this.t); + ++this.t; + this.clamp(); + } + + // (protected) this += n << w words, this >= 0 + function bnpDAddOffset(n, w) { + if (n == 0) return; + while (this.t <= w) this[this.t++] = 0; + this[w] += n; + while (this[w] >= this.DV) { + this[w] -= this.DV; + if (++w >= this.t) this[this.t++] = 0; + ++this[w]; + } + } + + // (protected) convert to radix string + function bnpToRadix(b) { + if (b == null) b = 10; + if (this.signum() == 0 || b < 2 || b > 36) return '0'; + var cs = this.chunkSize(b); + var a = Math.pow(b, cs); + var d = nbv(a), + y = nbi(), + z = nbi(), + r = ''; + this.divRemTo(d, y, z); + while (y.signum() > 0) { + r = (a + z.intValue()).toString(b).substr(1) + r; + y.divRemTo(d, y, z); + } + return z.intValue().toString(b) + r; + } + + // (public) return value as integer + function bnIntValue() { + if (this.s < 0) { + if (this.t == 1) return this[0] - this.DV; + else if (this.t == 0) return -1; + } else if (this.t == 1) return this[0]; + else if (this.t == 0) return 0; + // assumes 16 < DB < 32 + return ((this[1] & ((1 << (32 - this.DB)) - 1)) << this.DB) | this[0]; + } + + // (protected) r = this + a + function bnpAddTo(a, r) { + var i = 0, + c = 0, + m = Math.min(a.t, this.t); + while (i < m) { + c += this[i] + a[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + if (a.t < this.t) { + c += a.s; + while (i < this.t) { + c += this[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + c += this.s; + } else { + c += this.s; + while (i < a.t) { + c += a[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + c += a.s; + } + r.s = c < 0 ? -1 : 0; + if (c > 0) r[i++] = c; + else if (c < -1) r[i++] = this.DV + c; + r.t = i; + r.clamp(); + } + + BigInteger.prototype.fromRadix = bnpFromRadix; + BigInteger.prototype.chunkSize = bnpChunkSize; + BigInteger.prototype.signum = bnSigNum; + BigInteger.prototype.dMultiply = bnpDMultiply; + BigInteger.prototype.dAddOffset = bnpDAddOffset; + BigInteger.prototype.toRadix = bnpToRadix; + BigInteger.prototype.intValue = bnIntValue; + BigInteger.prototype.addTo = bnpAddTo; + + //======= end jsbn ======= + + // Emscripten wrapper + var Wrapper = { + abs: function(l, h) { + var x = new goog.math.Long(l, h); + var ret; + if (x.isNegative()) { + ret = x.negate(); + } else { + ret = x; + } + HEAP32[tempDoublePtr >> 2] = ret.low_; + HEAP32[(tempDoublePtr + 4) >> 2] = ret.high_; + }, + ensureTemps: function() { + if (Wrapper.ensuredTemps) return; + Wrapper.ensuredTemps = true; + Wrapper.two32 = new BigInteger(); + Wrapper.two32.fromString('4294967296', 10); + Wrapper.two64 = new BigInteger(); + Wrapper.two64.fromString('18446744073709551616', 10); + Wrapper.temp1 = new BigInteger(); + Wrapper.temp2 = new BigInteger(); + }, + lh2bignum: function(l, h) { + var a = new BigInteger(); + a.fromString(h.toString(), 10); + var b = new BigInteger(); + a.multiplyTo(Wrapper.two32, b); + var c = new BigInteger(); + c.fromString(l.toString(), 10); + var d = new BigInteger(); + c.addTo(b, d); + return d; + }, + stringify: function(l, h, unsigned) { + var ret = new goog.math.Long(l, h).toString(); + if (unsigned && ret[0] == '-') { + // unsign slowly using jsbn bignums + Wrapper.ensureTemps(); + var bignum = new BigInteger(); + bignum.fromString(ret, 10); + ret = new BigInteger(); + Wrapper.two64.addTo(bignum, ret); + ret = ret.toString(10); + } + return ret; + }, + fromString: function(str, base, min, max, unsigned) { + Wrapper.ensureTemps(); + var bignum = new BigInteger(); + bignum.fromString(str, base); + var bigmin = new BigInteger(); + bigmin.fromString(min, 10); + var bigmax = new BigInteger(); + bigmax.fromString(max, 10); + if (unsigned && bignum.compareTo(BigInteger.ZERO) < 0) { + var temp = new BigInteger(); + bignum.addTo(Wrapper.two64, temp); + bignum = temp; + } + var error = false; + if (bignum.compareTo(bigmin) < 0) { + bignum = bigmin; + error = true; + } else if (bignum.compareTo(bigmax) > 0) { + bignum = bigmax; + error = true; + } + var ret = goog.math.Long.fromString(bignum.toString()); // min-max checks should have clamped this to a range goog.math.Long can handle well + HEAP32[tempDoublePtr >> 2] = ret.low_; + HEAP32[(tempDoublePtr + 4) >> 2] = ret.high_; + if (error) throw 'range error'; + }, + }; + return Wrapper; +})(); + +//======= end closure i64 code ======= + +// === Auto-generated postamble setup entry stuff === + +if (memoryInitializer) { + if (typeof Module['locateFile'] === 'function') { + memoryInitializer = Module['locateFile'](memoryInitializer); + } else if (Module['memoryInitializerPrefixURL']) { + memoryInitializer = + Module['memoryInitializerPrefixURL'] + memoryInitializer; + } + if (ENVIRONMENT_IS_NODE || ENVIRONMENT_IS_SHELL) { + var data = Module['readBinary'](memoryInitializer); + HEAPU8.set(data, STATIC_BASE); + } else { + addRunDependency('memory initializer'); + Browser.asyncLoad( + memoryInitializer, + function(data) { + HEAPU8.set(data, STATIC_BASE); + removeRunDependency('memory initializer'); + }, + function(data) { + throw 'could not load memory initializer ' + memoryInitializer; + } + ); + } +} + +function ExitStatus(status) { + this.name = 'ExitStatus'; + this.message = 'Program terminated with exit(' + status + ')'; + this.status = status; +} +ExitStatus.prototype = new Error(); +ExitStatus.prototype.constructor = ExitStatus; + +var initialStackTop; +var preloadStartTime = null; +var calledMain = false; + +dependenciesFulfilled = function runCaller() { + // If run has never been called, and we should call run (INVOKE_RUN is true, and Module.noInitialRun is not false) + if (!Module['calledRun'] && shouldRunNow) run(); + if (!Module['calledRun']) dependenciesFulfilled = runCaller; // try this again later, after new deps are fulfilled +}; + +Module['callMain'] = Module.callMain = function callMain(args) { + assert( + runDependencies == 0, + 'cannot call main when async dependencies remain! (listen on __ATMAIN__)' + ); + assert( + __ATPRERUN__.length == 0, + 'cannot call main when preRun functions remain to be called' + ); + + args = args || []; + + ensureInitRuntime(); + + var argc = args.length + 1; + function pad() { + for (var i = 0; i < 4 - 1; i++) { + argv.push(0); + } + } + var argv = [ + allocate(intArrayFromString(Module['thisProgram']), 'i8', ALLOC_NORMAL), + ]; + pad(); + for (var i = 0; i < argc - 1; i = i + 1) { + argv.push(allocate(intArrayFromString(args[i]), 'i8', ALLOC_NORMAL)); + pad(); + } + argv.push(0); + argv = allocate(argv, 'i32', ALLOC_NORMAL); + + initialStackTop = STACKTOP; + + try { + var ret = Module['_main'](argc, argv, 0); + + // if we're not running an evented main loop, it's time to exit + exit(ret); + } catch (e) { + if (e instanceof ExitStatus) { + // exit() throws this once it's done to make sure execution + // has been stopped completely + return; + } else if (e == 'SimulateInfiniteLoop') { + // running an evented main loop, don't immediately exit + Module['noExitRuntime'] = true; + return; + } else { + if (e && typeof e === 'object' && e.stack) + Module.printErr('exception thrown: ' + [e, e.stack]); + throw e; + } + } finally { + calledMain = true; + } +}; + +function run(args) { + args = args || Module['arguments']; + + if (preloadStartTime === null) preloadStartTime = Date.now(); + + if (runDependencies > 0) { + return; + } + + preRun(); + + if (runDependencies > 0) return; // a preRun added a dependency, run will be called later + if (Module['calledRun']) return; // run may have just been called through dependencies being fulfilled just in this very frame + + function doRun() { + if (Module['calledRun']) return; // run may have just been called while the async setStatus time below was happening + Module['calledRun'] = true; + + if (ABORT) return; + + ensureInitRuntime(); + + preMain(); + + if (ENVIRONMENT_IS_WEB && preloadStartTime !== null) { + Module.printErr( + 'pre-main prep time: ' + (Date.now() - preloadStartTime) + ' ms' + ); + } + + if (Module['onRuntimeInitialized']) Module['onRuntimeInitialized'](); + + if (Module['_main'] && shouldRunNow) Module['callMain'](args); + + postRun(); + } + + if (Module['setStatus']) { + Module['setStatus']('Running...'); + setTimeout(function() { + setTimeout(function() { + Module['setStatus'](''); + }, 1); + doRun(); + }, 1); + } else { + doRun(); + } +} +Module['run'] = Module.run = run; + +function exit(status) { + if (Module['noExitRuntime']) { + return; + } + + ABORT = true; + EXITSTATUS = status; + STACKTOP = initialStackTop; + + // exit the runtime + exitRuntime(); + + if (ENVIRONMENT_IS_NODE) { + // Work around a node.js bug where stdout buffer is not flushed at process exit: + // Instead of process.exit() directly, wait for stdout flush event. + // See https://github.com/joyent/node/issues/1669 and https://github.com/kripken/emscripten/issues/2582 + // Workaround is based on https://github.com/RReverser/acorn/commit/50ab143cecc9ed71a2d66f78b4aec3bb2e9844f6 + process['stdout']['once']('drain', function() { + process['exit'](status); + }); + console.log(' '); // Make sure to print something to force the drain event to occur, in case the stdout buffer was empty. + // Work around another node bug where sometimes 'drain' is never fired - make another effort + // to emit the exit status, after a significant delay (if node hasn't fired drain by then, give up) + setTimeout(function() { + process['exit'](status); + }, 500); + } else if (ENVIRONMENT_IS_SHELL && typeof quit === 'function') { + quit(status); + } + // if we reach here, we must throw an exception to halt the current execution + throw new ExitStatus(status); +} +Module['exit'] = Module.exit = exit; + +function abort(text) { + if (text) { + Module.print(text); + Module.printErr(text); + } + + ABORT = true; + EXITSTATUS = 1; + + var extra = + '\nIf this abort() is unexpected, build with -s ASSERTIONS=1 which can give more information.'; + + throw 'abort() at ' + stackTrace() + extra; +} +Module['abort'] = Module.abort = abort; + +// {{PRE_RUN_ADDITIONS}} + +if (Module['preInit']) { + if (typeof Module['preInit'] == 'function') + Module['preInit'] = [Module['preInit']]; + while (Module['preInit'].length > 0) { + Module['preInit'].pop()(); + } +} + +// shouldRunNow refers to calling main(), not run(). +var shouldRunNow = true; +if (Module['noInitialRun']) { + shouldRunNow = false; +} + +run(); + +// {{POST_RUN_ADDITIONS}} + +// {{MODULE_ADDITIONS}} diff --git a/js/curve/curve25519_wrapper.js b/js/curve/curve25519_wrapper.js new file mode 100644 index 0000000000..3b1294180a --- /dev/null +++ b/js/curve/curve25519_wrapper.js @@ -0,0 +1,158 @@ +/* vim: ts=4:sw=4:expandtab */ +var Internal = global.Internal || {}; + +(function() { + 'use strict'; + + // Insert some bytes into the emscripten memory and return a pointer + function _allocate(bytes) { + var address = Module._malloc(bytes.length); + Module.HEAPU8.set(bytes, address); + + return address; + } + + function _readBytes(address, length, array) { + array.set(Module.HEAPU8.subarray(address, address + length)); + } + + var basepoint = new Uint8Array(32); + basepoint[0] = 9; + + Internal.curve25519 = { + keyPair: function(privKey) { + var priv = new Uint8Array(privKey); + priv[0] &= 248; + priv[31] &= 127; + priv[31] |= 64; + + // Where to store the result + var publicKey_ptr = Module._malloc(32); + + // Get a pointer to the private key + var privateKey_ptr = _allocate(priv); + + // The basepoint for generating public keys + var basepoint_ptr = _allocate(basepoint); + + // The return value is just 0, the operation is done in place + var err = Module._curve25519_donna( + publicKey_ptr, + privateKey_ptr, + basepoint_ptr + ); + + var res = new Uint8Array(32); + _readBytes(publicKey_ptr, 32, res); + + Module._free(publicKey_ptr); + Module._free(privateKey_ptr); + Module._free(basepoint_ptr); + + return { pubKey: res.buffer, privKey: priv.buffer }; + }, + sharedSecret: function(pubKey, privKey) { + // Where to store the result + var sharedKey_ptr = Module._malloc(32); + + // Get a pointer to our private key + var privateKey_ptr = _allocate(new Uint8Array(privKey)); + + // Get a pointer to their public key, the basepoint when you're + // generating a shared secret + var basepoint_ptr = _allocate(new Uint8Array(pubKey)); + + // Return value is 0 here too of course + var err = Module._curve25519_donna( + sharedKey_ptr, + privateKey_ptr, + basepoint_ptr + ); + + var res = new Uint8Array(32); + _readBytes(sharedKey_ptr, 32, res); + + Module._free(sharedKey_ptr); + Module._free(privateKey_ptr); + Module._free(basepoint_ptr); + + return res.buffer; + }, + sign: function(privKey, message) { + // Where to store the result + var signature_ptr = Module._malloc(64); + + // Get a pointer to our private key + var privateKey_ptr = _allocate(new Uint8Array(privKey)); + + // Get a pointer to the message + var message_ptr = _allocate(new Uint8Array(message)); + + var err = Module._curve25519_sign( + signature_ptr, + privateKey_ptr, + message_ptr, + message.byteLength + ); + + var res = new Uint8Array(64); + _readBytes(signature_ptr, 64, res); + + Module._free(signature_ptr); + Module._free(privateKey_ptr); + Module._free(message_ptr); + + return res.buffer; + }, + verify: function(pubKey, message, sig) { + // Get a pointer to their public key + var publicKey_ptr = _allocate(new Uint8Array(pubKey)); + + // Get a pointer to the signature + var signature_ptr = _allocate(new Uint8Array(sig)); + + // Get a pointer to the message + var message_ptr = _allocate(new Uint8Array(message)); + + var res = Module._curve25519_verify( + signature_ptr, + publicKey_ptr, + message_ptr, + message.byteLength + ); + + Module._free(publicKey_ptr); + Module._free(signature_ptr); + Module._free(message_ptr); + + return res !== 0; + }, + }; + + Internal.curve25519_async = { + keyPair: function(privKey) { + return new Promise(function(resolve) { + resolve(Internal.curve25519.keyPair(privKey)); + }); + }, + sharedSecret: function(pubKey, privKey) { + return new Promise(function(resolve) { + resolve(Internal.curve25519.sharedSecret(pubKey, privKey)); + }); + }, + sign: function(privKey, message) { + return new Promise(function(resolve) { + resolve(Internal.curve25519.sign(privKey, message)); + }); + }, + verify: function(pubKey, message, sig) { + return new Promise(function(resolve, reject) { + if (Internal.curve25519.verify(pubKey, message, sig)) { + reject(new Error('Invalid signature')); + } else { + resolve(); + } + }); + }, + }; +})(); diff --git a/js/delivery_receipts.js b/js/delivery_receipts.js index a88aa67a2f..c2fa998ef1 100644 --- a/js/delivery_receipts.js +++ b/js/delivery_receipts.js @@ -1,7 +1,11 @@ -/* global Backbone: false */ -/* global Whisper: false */ -/* global ConversationController: false */ -/* global _: false */ +/* global + Backbone, + Whisper, + ConversationController, + MessageController, + _, + libloki, +*/ /* eslint-disable more/no-then */ @@ -31,6 +35,15 @@ if (messages.length === 0) { return null; } + + const authorisation = await libloki.storage.getGrantAuthorisationForSecondaryPubKey( + source + ); + if (authorisation) { + // eslint-disable-next-line no-param-reassign + source = authorisation.primaryDevicePubKey; + } + const message = messages.find( item => !item.isIncoming() && source === item.get('conversationId') ); @@ -45,10 +58,15 @@ const ids = groups.pluck('id'); ids.push(source); - return messages.find( + const target = messages.find( item => !item.isIncoming() && _.contains(ids, item.get('conversationId')) ); + if (!target) { + return null; + } + + return MessageController.register(target.id, target); }, async onReceipt(receipt) { try { diff --git a/js/expire.js b/js/expire.js index 9caff73c19..32f9555fc9 100644 --- a/js/expire.js +++ b/js/expire.js @@ -1,22 +1,99 @@ +/* global LokiAppDotNetServerAPI, LokiFileServerAPI, semver, log */ // eslint-disable-next-line func-names (function() { 'use strict'; - let BUILD_EXPIRATION = 0; - try { - BUILD_EXPIRATION = parseInt(window.getExpiration(), 10); - if (BUILD_EXPIRATION) { - window.log.info( - 'Build expires: ', - new Date(BUILD_EXPIRATION).toISOString() - ); + // hold last result + let expiredVersion = null; + + window.tokenlessFileServerAdnAPI = new LokiAppDotNetServerAPI( + '', // no pubkey needed + window.getDefaultFileServer() + ); + window.tokenlessFileServerAdnAPI.pubKey = window.Signal.Crypto.base64ToArrayBuffer( + LokiFileServerAPI.secureRpcPubKey + ); + + let nextWaitSeconds = 1; + const checkForUpgrades = async () => { + const result = await window.tokenlessFileServerAdnAPI.serverRequest( + 'loki/v1/version/client/desktop' + ); + if ( + result && + result.response && + result.response.data && + result.response.data.length && + result.response.data[0].length + ) { + const latestVer = semver.clean(result.response.data[0][0]); + if (semver.valid(latestVer)) { + const ourVersion = window.getVersion(); + if (latestVer === ourVersion) { + log.info('You have the latest version', latestVer); + // change the following to true ot test/see expiration banner + expiredVersion = false; + } else { + // expire if latest is newer than current + expiredVersion = semver.gt(latestVer, ourVersion); + if (expiredVersion) { + log.info('There is a newer version available', latestVer); + } + } + } + } else { + // give it a minute + log.warn('Could not check to see if newer version is available', result); + nextWaitSeconds = 60; + setTimeout(async () => { + await checkForUpgrades(); + }, nextWaitSeconds * 1000); // wait a minute } - } catch (e) { - // nothing - } + // no message logged means serverRequest never returned... + }; + checkForUpgrades(); window.extension = window.extension || {}; - window.extension.expired = () => - BUILD_EXPIRATION && Date.now() > BUILD_EXPIRATION; + // eslint-disable-next-line no-unused-vars + const resolveWhenReady = (res, rej) => { + if (expiredVersion !== null) { + return res(expiredVersion); + } + function waitForVersion() { + if (expiredVersion !== null) { + return res(expiredVersion); + } + log.info( + 'Delaying sending checks for', + nextWaitSeconds, + 's, no version yet' + ); + setTimeout(waitForVersion, nextWaitSeconds * 1000); + return true; + } + waitForVersion(); + return true; + }; + + // just get current status + window.extension.expiredStatus = () => expiredVersion; + // actually wait until we know for sure + window.extension.expiredPromise = () => new Promise(resolveWhenReady); + window.extension.expired = cb => { + if (expiredVersion === null) { + // just give it another second + log.info( + 'Delaying expire banner determination for', + nextWaitSeconds, + 's' + ); + setTimeout(() => { + window.extension.expired(cb); + }, nextWaitSeconds * 1000); + return; + } + // yes we know + cb(expiredVersion); + }; })(); diff --git a/js/expiring_messages.js b/js/expiring_messages.js index dc3f43f4d9..365c03fc57 100644 --- a/js/expiring_messages.js +++ b/js/expiring_messages.js @@ -1,8 +1,11 @@ -/* global _: false */ -/* global Backbone: false */ -/* global i18n: false */ -/* global moment: false */ -/* global Whisper: false */ +/* global + _, + Backbone, + i18n, + MessageController, + moment, + Whisper +*/ // eslint-disable-next-line func-names (function() { @@ -18,7 +21,9 @@ }); await Promise.all( - messages.map(async message => { + messages.map(async fromDB => { + const message = MessageController.register(fromDB.id, fromDB); + window.log.info('Message expired', { sentAt: message.get('sent_at'), }); @@ -29,6 +34,12 @@ Message: Whisper.Message, }); + Whisper.events.trigger( + 'messageExpired', + message.id, + message.conversationId + ); + const conversation = message.getConversation(); if (conversation) { conversation.trigger('expired', message); diff --git a/js/libsignal-protocol-worker.js b/js/libsignal-protocol-worker.js index 20679574cb..07023b6143 100644 --- a/js/libsignal-protocol-worker.js +++ b/js/libsignal-protocol-worker.js @@ -646,11 +646,11 @@ function allocate(slab, types, allocator, ptr) { assert((ret & 3) == 0); stop = ret + (size & ~3); for (; ptr < stop; ptr += 4) { - HEAP32[((ptr)>>2)]=0; + HEAP32[ptr>>2]=0; } stop = ret + size; while (ptr < stop) { - HEAP8[((ptr++)>>0)]=0; + HEAP8[ptr++>>0]=0; } return ret; } @@ -22848,12 +22848,12 @@ function _memset(ptr, value, num) { } } while ((ptr|0) < (stop4|0)) { - HEAP32[((ptr)>>2)]=value4; + HEAP32[ptr>>2]=value4; ptr = (ptr+4)|0; } } while ((ptr|0) < (stop|0)) { - HEAP8[((ptr)>>0)]=value; + HEAP8[ptr>>0]=value; ptr = (ptr+1)|0; } return (ptr-num)|0; @@ -22898,20 +22898,20 @@ function _memcpy(dest, src, num) { if ((dest&3) == (src&3)) { while (dest & 3) { if ((num|0) == 0) return ret|0; - HEAP8[((dest)>>0)]=((HEAP8[((src)>>0)])|0); + HEAP8[dest>>0]=((HEAP8[src>>0])|0); dest = (dest+1)|0; src = (src+1)|0; num = (num-1)|0; } while ((num|0) >= 4) { - HEAP32[((dest)>>2)]=((HEAP32[((src)>>2)])|0); + HEAP32[dest>>2]=((HEAP32[src>>2])|0); dest = (dest+4)|0; src = (src+4)|0; num = (num-4)|0; } } while ((num|0) > 0) { - HEAP8[((dest)>>0)]=((HEAP8[((src)>>0)])|0); + HEAP8[dest>>0]=((HEAP8[src>>0])|0); dest = (dest+1)|0; src = (src+1)|0; num = (num-1)|0; diff --git a/js/link_previews_helper.js b/js/link_previews_helper.js deleted file mode 100644 index 87b1899c55..0000000000 --- a/js/link_previews_helper.js +++ /dev/null @@ -1,142 +0,0 @@ -/* global - Signal, - textsecure, - StringView -*/ - -/* eslint-disable no-bitwise */ - -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - window.Signal = window.Signal || {}; - window.Signal.LinkPreviews = window.Signal.LinkPreviews || {}; - - async function makeChunkedRequest(url) { - const PARALLELISM = 3; - const size = await textsecure.messaging.getProxiedSize(url); - const chunks = await Signal.LinkPreviews.getChunkPattern(size); - - let results = []; - const jobs = chunks.map(chunk => async () => { - const { start, end } = chunk; - - const result = await textsecure.messaging.makeProxiedRequest(url, { - start, - end, - returnArrayBuffer: true, - }); - - return { - ...chunk, - ...result, - }; - }); - - while (jobs.length > 0) { - const activeJobs = []; - for (let i = 0, max = PARALLELISM; i < max; i += 1) { - if (!jobs.length) { - break; - } - - const job = jobs.shift(); - activeJobs.push(job()); - } - - // eslint-disable-next-line no-await-in-loop - results = results.concat(await Promise.all(activeJobs)); - } - - if (!results.length) { - throw new Error('No responses received'); - } - - const { contentType } = results[0]; - const data = Signal.LinkPreviews.assembleChunks(results); - - return { - contentType, - data, - }; - } - - async function sha256(string) { - const arraybuffer = new TextEncoder('utf-8').encode(string); - const digest = await window.crypto.subtle.digest('SHA-256', arraybuffer); - return StringView.arrayBufferToHex(digest); - } - - async function getPreview(url) { - let html; - try { - html = await textsecure.messaging.makeProxiedRequest(url); - } catch (error) { - if (error.code >= 300) { - return null; - } - } - - const title = Signal.LinkPreviews.getTitleMetaTag(html); - const imageUrl = Signal.LinkPreviews.getImageMetaTag(html); - - let image; - let objectUrl; - try { - if (imageUrl) { - if (!Signal.LinkPreviews.isMediaLinkInWhitelist(imageUrl)) { - const primaryDomain = Signal.LinkPreviews.getDomain(url); - const imageDomain = Signal.LinkPreviews.getDomain(imageUrl); - throw new Error( - `imageUrl for domain ${primaryDomain} did not match media whitelist. Domain: ${imageDomain}` - ); - } - - const data = await makeChunkedRequest(imageUrl); - - // Calculate dimensions - const file = new Blob([data.data], { - type: data.contentType, - }); - objectUrl = URL.createObjectURL(file); - - const dimensions = await Signal.Types.VisualAttachment.getImageDimensions( - { - objectUrl, - logger: window.log, - } - ); - - image = { - ...data, - ...dimensions, - contentType: file.type, - }; - } - } catch (error) { - // We still want to show the preview if we failed to get an image - window.log.error( - 'getPreview failed to get image for link preview:', - error.message - ); - } finally { - if (objectUrl) { - URL.revokeObjectURL(objectUrl); - } - } - - const hash = await sha256(url); - - return { - title, - url, - image, - hash, - }; - } - - window.Signal.LinkPreviews.helper = { - getPreview, - }; -})(); diff --git a/js/message_controller.js b/js/message_controller.js new file mode 100644 index 0000000000..18d58cde9b --- /dev/null +++ b/js/message_controller.js @@ -0,0 +1,65 @@ +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + const messageLookup = Object.create(null); + + const SECOND = 1000; + const MINUTE = SECOND * 60; + const FIVE_MINUTES = MINUTE * 5; + const HOUR = MINUTE * 60; + + function register(id, message) { + const existing = messageLookup[id]; + if (existing) { + messageLookup[id] = { + message: existing.message, + timestamp: Date.now(), + }; + return existing.message; + } + + messageLookup[id] = { + message, + timestamp: Date.now(), + }; + + return message; + } + + function unregister(id) { + delete messageLookup[id]; + } + + function cleanup() { + const messages = Object.values(messageLookup); + const now = Date.now(); + + for (let i = 0, max = messages.length; i < max; i += 1) { + const { message, timestamp } = messages[i]; + const conversation = message.getConversation(); + + if ( + now - timestamp > FIVE_MINUTES && + (!conversation || !conversation.messageCollection.length) + ) { + delete messageLookup[message.id]; + } + } + } + + function _get() { + return messageLookup; + } + + setInterval(cleanup, HOUR); + + window.MessageController = { + register, + unregister, + cleanup, + _get, + }; +})(); diff --git a/js/models/conversations.js b/js/models/conversations.js index a32f342a4d..29c9ab3179 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -1,15 +1,20 @@ -/* global _: false */ -/* global Backbone: false */ -/* global BlockedNumberController: false */ -/* global ConversationController: false */ -/* global clipboard: false */ -/* global i18n: false */ -/* global profileImages: false */ -/* global storage: false */ -/* global textsecure: false */ -/* global Whisper: false */ -/* global lokiP2pAPI: false */ -/* global JobQueue: false */ +/* global + $, + _, + log, + i18n, + Backbone, + ConversationController, + MessageController, + storage, + textsecure, + Whisper, + profileImages, + clipboard, + BlockedNumberController, + lokiPublicChatAPI, + JobQueue +*/ /* eslint-disable more/no-then */ @@ -80,7 +85,10 @@ unlockTimestamp: null, // Timestamp used for expiring friend requests. sessionResetStatus: SessionResetEnum.none, swarmNodes: [], + groupAdmins: [], + isKickedFromGroup: false, isOnline: false, + profileSharing: false, }; }, @@ -96,6 +104,17 @@ this.trigger('messageError', message, errors); }, + getContactCollection() { + const collection = new Backbone.Collection(); + const collator = new Intl.Collator(); + collection.comparator = (left, right) => { + const leftLower = left.getTitle().toLowerCase(); + const rightLower = right.getTitle().toLowerCase(); + return collator.compare(leftLower, rightLower); + }; + return collection; + }, + initialize() { this.ourNumber = textsecure.storage.user.getNumber(); this.verifiedEnum = textsecure.storage.protocol.VerifiedStatus; @@ -104,13 +123,7 @@ // our first save to the database. Or first fetch from the database. this.initialPromise = Promise.resolve(); - this.contactCollection = new Backbone.Collection(); - const collator = new Intl.Collator(); - this.contactCollection.comparator = (left, right) => { - const leftLower = left.getTitle().toLowerCase(); - const rightLower = right.getTitle().toLowerCase(); - return collator.compare(leftLower, rightLower); - }; + this.contactCollection = this.getContactCollection(); this.messageCollection = new Whisper.MessageCollection([], { conversation: this, }); @@ -145,6 +158,15 @@ this.on('expiration-change', this.updateAndMerge); this.on('expired', this.onExpired); + this.on('ourAvatarChanged', avatar => + this.updateAvatarOnPublicChat(avatar) + ); + + // Always share profile pics with public chats + if (this.isPublic) { + this.set('profileSharing', true); + } + const sealedSender = this.get('sealedSender'); if (sealedSender === undefined) { this.set({ sealedSender: SEALED_SENDER.UNKNOWN }); @@ -153,16 +175,24 @@ this.unset('unidentifiedDeliveryUnrestricted'); this.unset('hasFetchedProfile'); this.unset('tokens'); - this.unset('lastMessage'); - this.unset('lastMessageStatus'); this.typingRefreshTimer = null; this.typingPauseTimer = null; - // Online status handling - this.set({ isOnline: lokiP2pAPI.isOnline(this.id) }); + if (this.id === this.ourNumber) { + this.set({ friendRequestStatus: FriendRequestStatusEnum.friends }); + } this.messageSendQueue = new JobQueue(); + + this.selectedMessages = new Set(); + + // Keep props ready + const generateProps = () => { + this.cachedProps = this.getProps(); + }; + this.on('change', generateProps); + generateProps(); }, isOnline() { @@ -170,7 +200,16 @@ }, isMe() { - return this.id === this.ourNumber; + return this.id === window.storage.get('primaryDevicePubKey'); + }, + isPublic() { + return !!(this.id && this.id.match(/^publicChat:/)); + }, + isClosable() { + return !this.isRss() || this.get('closable'); + }, + isRss() { + return !!(this.id && this.id.match(/^rss:/)); }, isBlocked() { return BlockedNumberController.isBlocked(this.id); @@ -185,10 +224,105 @@ this.trigger('change'); this.messageCollection.forEach(m => m.trigger('change')); }, + async acceptFriendRequest() { + const messages = await window.Signal.Data.getMessagesByConversation( + this.id, + { limit: 1, MessageCollection: Whisper.MessageCollection } + ); + + const lastMessageModel = messages.at(0); + if (lastMessageModel) { + lastMessageModel.acceptFriendRequest(); + await this.markRead(); + window.Whisper.events.trigger( + 'showConversation', + this.id, + lastMessageModel.id + ); + } + }, + async declineFriendRequest() { + const messages = await window.Signal.Data.getMessagesByConversation( + this.id, + { limit: 1, MessageCollection: Whisper.MessageCollection } + ); + + const lastMessageModel = messages.at(0); + if (lastMessageModel) { + lastMessageModel.declineFriendRequest(); + } + }, + setMessageSelectionBackdrop() { + const messageSelected = this.selectedMessages.size > 0; + + if (messageSelected) { + // Hide ellipses icon + $('.title-wrapper .session-icon.ellipses').css({ opacity: 0 }); + + $('.messages li, .messages > div').addClass('shadowed'); + $('.message-selection-overlay').addClass('overlay'); + $('.module-conversation-header').addClass('overlayed'); + + let messageId; + // eslint-disable-next-line no-restricted-syntax + for (const item of this.selectedMessages) { + messageId = item.propsForMessage.id; + $(`#${messageId}`).removeClass('shadowed'); + } + } else { + // Hide ellipses icon + $('.title-wrapper .session-icon.ellipses').css({ opacity: 1 }); + + $('.messages li, .messages > div').removeClass('shadowed'); + $('.message-selection-overlay').removeClass('overlay'); + $('.module-conversation-header').removeClass('overlayed'); + } + }, + + addMessageSelection(id) { + // If the selection is empty, then we chage the mode to + // multiple selection by making it non-empty + const modeChanged = this.selectedMessages.size === 0; + this.selectedMessages.add(id); + + if (modeChanged) { + this.messageCollection.forEach(m => m.trigger('change')); + } + + this.trigger('message-selection-changed'); + this.setMessageSelectionBackdrop(); + }, + + removeMessageSelection(id) { + this.selectedMessages.delete(id); + // If the selection is empty after the deletion then we + // must have unselected the last one (we assume the id is valid) + const modeChanged = this.selectedMessages.size === 0; + + if (modeChanged) { + this.messageCollection.forEach(m => m.trigger('change')); + } + + this.trigger('message-selection-changed'); + this.setMessageSelectionBackdrop(); + }, + + resetMessageSelection() { + this.selectedMessages.clear(); + this.messageCollection.forEach(m => { + // eslint-disable-next-line no-param-reassign + m.selected = false; + m.trigger('change'); + }); + + this.trigger('message-selection-changed'); + this.setMessageSelectionBackdrop(); + }, - bumpTyping() { - // We don't send typing messages if the setting is disabled - if (!storage.get('typingIndicators')) { + async bumpTyping() { + // We don't send typing messages if the setting is disabled or we aren't friends + const hasFriendDevice = await this.isFriendWithAnyDevice(); + if (!storage.get('typing-indicators-setting') || !hasFriendDevice) { return; } @@ -248,16 +382,24 @@ }, sendTypingMessage(isTyping) { + // Loki - Temporarily disable typing messages for groups + if (!this.isPrivate()) { + return; + } + const groupId = !this.isPrivate() ? this.id : null; const recipientId = this.isPrivate() ? this.id : null; + const groupNumbers = this.getRecipients(); const sendOptions = this.getSendOptions(); + sendOptions.messageType = 'typing'; this.wrapSend( textsecure.messaging.sendTypingMessage( { - groupId, isTyping, recipientId, + groupId, + groupNumbers, }, sendOptions ) @@ -275,8 +417,15 @@ }, async updateProfileAvatar() { - const path = profileImages.getOrCreateImagePath(this.id); - await this.setProfileAvatar(path); + if (this.isRss() || this.isPublic()) { + return; + } + + // Remove old identicons + if (profileImages.hasImage(this.id)) { + profileImages.removeImage(this.id); + await this.setProfileAvatar(null); + } }, async updateAndMerge(message) { @@ -323,7 +472,9 @@ // Get messages with the given timestamp _getMessagesWithTimestamp(pubKey, timestamp) { - if (this.id !== pubKey) return []; + if (this.id !== pubKey) { + return []; + } // Go through our messages and find the one that we need to update return this.messageCollection.models.filter( @@ -336,9 +487,14 @@ await Promise.all(messages.map(m => m.setCalculatingPoW())); }, - async onP2pMessageSent(pubKey, timestamp) { + async onPublicMessageSent(pubKey, timestamp, serverId) { const messages = this._getMessagesWithTimestamp(pubKey, timestamp); - await Promise.all(messages.map(m => m.setIsP2p(true))); + await Promise.all( + messages.map(message => [ + message.setIsPublic(true), + message.setServerId(serverId), + ]) + ); }, async onNewMessage(message) { @@ -351,27 +507,6 @@ this.clearContactTypingTimer(identifier); }, - addSingleMessage(message, setToExpire = true) { - const model = this.messageCollection.add(message, { merge: true }); - if (setToExpire) model.setToExpire(); - return model; - }, - format() { - const { format } = PhoneNumber; - const regionCode = storage.get('regionCode'); - const color = this.getColor(); - - return { - phoneNumber: format(this.id, { - ourRegionCode: regionCode, - }), - color, - avatarPath: this.getAvatarPath(), - name: this.getName(), - profileName: this.getProfileName(), - title: this.getTitle(), - }; - }, // This goes through all our message history and finds a friend request async getFriendRequests(direction = null, status = ['pending']) { // Theoretically all our messages could be friend requests, @@ -390,34 +525,70 @@ // Get the pending friend requests that match the direction // If no direction is supplied then return all pending friend requests return messages.models.filter(m => { - if (!status.includes(m.get('friendStatus'))) return false; + if (!status.includes(m.get('friendStatus'))) { + return false; + } return direction === null || m.get('direction') === direction; }); }, async getPendingFriendRequests(direction = null) { return this.getFriendRequests(direction, ['pending']); }, - getPropsForListItem() { + + addSingleMessage(message, setToExpire = true) { + const model = this.messageCollection.add(message, { merge: true }); + if (setToExpire) { + model.setToExpire(); + } + return model; + }, + format() { + return this.cachedProps; + }, + getProps() { + const { format } = PhoneNumber; + const regionCode = storage.get('regionCode'); + const color = this.getColor(); const typingKeys = Object.keys(this.contactTypingTimers || {}); const result = { - ...this.format(), - conversationType: this.isPrivate() ? 'direct' : 'group', + id: this.id, + isArchived: this.get('isArchived'), + activeAt: this.get('active_at'), + avatarPath: this.getAvatarPath(), + color, + type: this.isPrivate() ? 'direct' : 'group', + isMe: this.isMe(), + isPublic: this.isPublic(), + isRss: this.isRss(), + isClosable: this.isClosable(), + isTyping: typingKeys.length > 0, lastUpdated: this.get('timestamp'), + name: this.getName(), + profileName: this.getProfileName(), + timestamp: this.get('timestamp'), + title: this.getTitle(), unreadCount: this.get('unreadCount') || 0, - isSelected: this.isSelected, - showFriendRequestIndicator: this.isPendingFriendRequest(), + mentionedUs: this.get('mentionedUs') || false, + isPendingFriendRequest: this.isPendingFriendRequest(), + hasReceivedFriendRequest: this.hasReceivedFriendRequest(), + hasSentFriendRequest: this.hasSentFriendRequest(), isBlocked: this.isBlocked(), - - isTyping: typingKeys.length > 0, + isSecondary: !!this.get('secondaryStatus'), + phoneNumber: format(this.id, { + ourRegionCode: regionCode, + }), lastMessage: { - status: this.lastMessageStatus, - text: this.lastMessage, + status: this.get('lastMessageStatus'), + text: this.get('lastMessage'), + isRss: this.isRss(), }, isOnline: this.isOnline(), - isMe: this.isMe(), hasNickname: !!this.getNickname(), + isFriend: !!this.isFriendWithAnyCache, + + selectedMessages: this.selectedMessages, onClick: () => this.trigger('select', this), onBlockContact: () => this.block(), @@ -427,8 +598,13 @@ onCopyPublicKey: () => this.copyPublicKey(), onDeleteContact: () => this.deleteContact(), onDeleteMessages: () => this.deleteMessages(), + onCloseOverlay: () => this.resetMessageSelection(), + acceptFriendRequest: () => this.acceptFriendRequest(), + declineFriendRequest: () => this.declineFriendRequest(), }; + this.updateAsyncPropsCache(); + return result; }, @@ -586,6 +762,7 @@ hasSentFriendRequest() { const status = this.get('friendRequestStatus'); return ( + status === FriendRequestStatusEnum.pendingSend || status === FriendRequestStatusEnum.requestSent || status === FriendRequestStatusEnum.requestExpired ); @@ -601,8 +778,90 @@ this.get('friendRequestStatus') === FriendRequestStatusEnum.friends ); }, - updateTextInputState() { - switch (this.get('friendRequestStatus')) { + async getAnyDeviceFriendRequestStatus() { + const secondaryDevices = await window.libloki.storage.getSecondaryDevicesFor( + this.id + ); + const allDeviceStatus = secondaryDevices + // Get all the secondary device friend status' + .map(pubKey => { + const conversation = ConversationController.get(pubKey); + if (!conversation) { + return FriendRequestStatusEnum.none; + } + return conversation.getFriendRequestStatus(); + }) + // Also include this conversation's friend status + .concat(this.get('friendRequestStatus')) + .reduce((acc, cur) => { + if ( + acc === FriendRequestStatusEnum.friends || + cur === FriendRequestStatusEnum.friends + ) { + return FriendRequestStatusEnum.friends; + } + if (acc !== FriendRequestStatusEnum.none) { + return acc; + } + return cur; + }, FriendRequestStatusEnum.none); + return allDeviceStatus; + }, + async updateAsyncPropsCache() { + const isFriendWithAnyDevice = await this.isFriendWithAnyDevice(); + if (this.isFriendWithAnyCache !== isFriendWithAnyDevice) { + this.isFriendWithAnyCache = isFriendWithAnyDevice; + this.trigger('change'); + } + }, + async isFriendWithAnyDevice() { + const allDeviceStatus = await this.getAnyDeviceFriendRequestStatus(); + return allDeviceStatus === FriendRequestStatusEnum.friends; + }, + getFriendRequestStatus() { + return this.get('friendRequestStatus'); + }, + async getPrimaryConversation() { + if (!this.isSecondaryDevice()) { + // This is already the primary conversation + return this; + } + const authorisation = await window.libloki.storage.getAuthorisationForSecondaryPubKey( + this.id + ); + if (authorisation) { + return ConversationController.getOrCreateAndWait( + authorisation.primaryDevicePubKey, + 'private' + ); + } + // Something funky has happened + return this; + }, + async updateTextInputState() { + if (this.isRss()) { + // or if we're an rss conversation, disable it + this.trigger('disable:input', true); + return; + } + if (this.isSecondaryDevice()) { + // Or if we're a secondary device, update the primary device text input + const primaryConversation = await this.getPrimaryConversation(); + primaryConversation.updateTextInputState(); + return; + } + const allDeviceStatus = await this.getAnyDeviceFriendRequestStatus(); + + if (this.get('isKickedFromGroup')) { + this.trigger('disable:input', true); + return; + } + if (!this.isPrivate() && this.get('left')) { + this.trigger('disable:input', true); + this.trigger('change:placeholder', 'left-group'); + return; + } + switch (allDeviceStatus) { case FriendRequestStatusEnum.none: case FriendRequestStatusEnum.requestExpired: this.trigger('disable:input', false); @@ -622,31 +881,85 @@ throw new Error('Invalid friend request state'); } }, - async setFriendRequestStatus(newStatus) { + isSecondaryDevice() { + return !!this.get('secondaryStatus'); + }, + getPrimaryDevicePubKey() { + return this.get('primaryDevicePubKey') || this.id; + }, + async setSecondaryStatus(newStatus, primaryDevicePubKey) { + if (this.get('secondaryStatus') !== newStatus) { + this.set({ + secondaryStatus: newStatus, + primaryDevicePubKey, + }); + await window.Signal.Data.updateConversation(this.id, this.attributes, { + Conversation: Whisper.Conversation, + }); + } + }, + async setFriendRequestStatus(newStatus, options = {}) { + const { blockSync } = options; // Ensure that the new status is a valid FriendStatusEnum value - if (!(newStatus in Object.values(FriendRequestStatusEnum))) return; + if (!(newStatus in Object.values(FriendRequestStatusEnum))) { + return; + } + if ( + this.ourNumber === this.id && + newStatus !== FriendRequestStatusEnum.friends + ) { + return; + } if (this.get('friendRequestStatus') !== newStatus) { this.set({ friendRequestStatus: newStatus }); + if (newStatus === FriendRequestStatusEnum.friends) { + if (!blockSync) { + // Sync contact + this.wrapSend(textsecure.messaging.sendContactSyncMessage(this)); + } + // Only enable sending profileKey after becoming friends + this.set({ profileSharing: true }); + } await window.Signal.Data.updateConversation(this.id, this.attributes, { Conversation: Whisper.Conversation, }); - this.updateTextInputState(); + await this.updateTextInputState(); } }, + async updateGroupAdmins(groupAdmins) { + this.set({ groupAdmins }); + await window.Signal.Data.updateConversation(this.id, this.attributes, { + Conversation: Whisper.Conversation, + }); + }, async respondToAllFriendRequests(options) { const { response, status, direction = null } = options; // Ignore if no response supplied - if (!response) return; - const pending = await this.getFriendRequests(direction, status); + if (!response) { + return; + } + const primaryConversation = ConversationController.get( + this.getPrimaryDevicePubKey() + ); + // Should never happen + if (!primaryConversation) { + return; + } + const pending = await primaryConversation.getFriendRequests( + direction, + status + ); await Promise.all( pending.map(async request => { - if (request.hasErrors()) return; + if (request.hasErrors()) { + return; + } request.set({ friendStatus: response }); await window.Signal.Data.saveMessage(request.attributes, { Message: Whisper.Message, }); - this.trigger('updateMessage', request); + primaryConversation.trigger('updateMessage', request); }) ); }, @@ -671,39 +984,57 @@ direction: 'incoming', }); await window.libloki.storage.removeContactPreKeyBundle(this.id); + await this.destroyMessages(); + window.pushToast({ + title: i18n('friendRequestDeclined'), + type: 'success', + id: 'declineFriendRequest', + }); }, // We have accepted an incoming friend request - async onAcceptFriendRequest() { - if (this.unlockTimer) clearTimeout(this.unlockTimer); + async onAcceptFriendRequest(options = {}) { + if (this.unlockTimer) { + clearTimeout(this.unlockTimer); + } if (this.hasReceivedFriendRequest()) { - this.setFriendRequestStatus(FriendRequestStatusEnum.friends); + this.setFriendRequestStatus(FriendRequestStatusEnum.friends, options); + await this.respondToAllFriendRequests({ response: 'accepted', direction: 'incoming', status: ['pending', 'expired'], }); - window.libloki.api.sendFriendRequestAccepted(this.id); + window.libloki.api.sendBackgroundMessage(this.id); } }, // Our outgoing friend request has been accepted async onFriendRequestAccepted() { - if (this.unlockTimer) clearTimeout(this.unlockTimer); + if (this.isFriend()) { + return false; + } + if (this.unlockTimer) { + clearTimeout(this.unlockTimer); + } if (this.hasSentFriendRequest()) { this.setFriendRequestStatus(FriendRequestStatusEnum.friends); await this.respondToAllFriendRequests({ response: 'accepted', status: ['pending', 'expired'], }); - window.libloki.api.sendOnlineBroadcastMessage(this.id); + window.libloki.api.sendBackgroundMessage(this.id); return true; } return false; }, async onFriendRequestTimeout() { // Unset the timer - if (this.unlockTimer) clearTimeout(this.unlockTimer); + if (this.unlockTimer) { + clearTimeout(this.unlockTimer); + } this.unlockTimer = null; - if (this.isFriend()) return; + if (this.isFriend()) { + return; + } // Set the unlock timestamp to null if (this.get('unlockTimestamp')) { @@ -756,8 +1087,17 @@ } await this.setFriendRequestStatus(FriendRequestStatusEnum.requestSent); }, + friendRequestTimerIsExpired() { + const unlockTimestamp = this.get('unlockTimestamp'); + if (unlockTimestamp && unlockTimestamp > Date.now()) { + return false; + } + return true; + }, setFriendRequestExpiryTimeout() { - if (this.isFriend()) return; + if (this.isFriend()) { + return; + } const unlockTimestamp = this.get('unlockTimestamp'); if (unlockTimestamp && !this.unlockTimer) { const delta = Math.max(unlockTimestamp - Date.now(), 0); @@ -867,8 +1207,8 @@ onMemberVerifiedChange() { // If the verified state of a member changes, our aggregate state changes. // We trigger both events to replicate the behavior of Backbone.Model.set() - this.trigger('change:verified'); - this.trigger('change'); + this.trigger('change:verified', this); + this.trigger('change', this); }, toggleVerified() { if (this.isVerified()) { @@ -970,14 +1310,6 @@ }, async onReadMessage(message, readAt) { - const existing = this.messageCollection.get(message.id); - if (existing) { - const fetched = await window.Signal.Data.getMessageById(existing.id, { - Message: Whisper.Message, - }); - existing.merge(fetched); - } - // We mark as read everything older than this message - to clean up old stuff // still marked unread in the database. If the user generally doesn't read in // the desktop app, so the desktop app only gets read syncs, we can very @@ -1023,17 +1355,24 @@ }, validateNumber() { - if (!this.id) return 'Invalid ID'; - if (!this.isPrivate()) return null; + if (!this.id) { + return 'Invalid ID'; + } + if (!this.isPrivate()) { + return null; + } // Check if it's hex const isHex = this.id.replace(/[\s]*/g, '').match(/^[0-9a-fA-F]+$/); - if (!isHex) return 'Invalid Hex ID'; + if (!isHex) { + return 'Invalid Hex ID'; + } // Check if the pubkey length is 33 and leading with 05 or of length 32 const len = this.id.length; - if ((len !== 33 * 2 || !/^05/.test(this.id)) && len !== 32 * 2) + if ((len !== 33 * 2 || !/^05/.test(this.id)) && len !== 32 * 2) { return 'Invalid Pubkey Format'; + } this.set({ id: this.id }); return null; @@ -1076,41 +1415,20 @@ return _.without(this.get('members'), me); }, - async makeQuote(quotedMessage) { - const { getName } = Contact; - const contact = quotedMessage.getContact(); - const attachments = quotedMessage.get('attachments'); - const preview = quotedMessage.get('preview'); - - const body = quotedMessage.get('body'); - const embeddedContact = quotedMessage.get('contact'); - const embeddedContactName = - embeddedContact && embeddedContact.length > 0 - ? getName(embeddedContact[0]) - : ''; - - const media = - attachments && attachments.length ? attachments : preview || []; - - return { - author: contact.id, - id: quotedMessage.get('sent_at'), - text: body || embeddedContactName, - attachments: await Promise.all( - media + async getQuoteAttachment(attachments, preview) { + if (attachments && attachments.length) { + return Promise.all( + attachments .filter( attachment => attachment && - (attachment.image || (!attachment.pending && !attachment.error)) + attachment.contentType && + !attachment.pending && + !attachment.error ) .slice(0, 1) .map(async attachment => { - const { fileName } = attachment; - - const thumbnail = attachment.thumbnail || attachment.image; - const contentType = - attachment.contentType || - (attachment.image && attachment.image.contentType); + const { fileName, thumbnail, contentType } = attachment; return { contentType, @@ -1125,14 +1443,66 @@ : null, }; }) - ), - }; + ); + } + + if (preview && preview.length) { + return Promise.all( + preview + .filter(item => item && item.image) + .slice(0, 1) + .map(async attachment => { + const { image } = attachment; + const { contentType } = image; + + return { + contentType, + // Our protos library complains about this field being undefined, so we + // force it to null + fileName: null, + thumbnail: image + ? { + ...(await loadAttachmentData(image)), + objectUrl: getAbsoluteAttachmentPath(image.path), + } + : null, + }; + }) + ); + } + + return []; }, - async sendMessage(body, attachments, quote, preview) { - // Input should be blocked if there is a pending friend request - if (this.isPendingFriendRequest()) return; + async makeQuote(quotedMessage) { + const { getName } = Contact; + const contact = quotedMessage.getContact(); + const attachments = quotedMessage.get('attachments'); + const preview = quotedMessage.get('preview'); + const body = quotedMessage.get('body'); + const embeddedContact = quotedMessage.get('contact'); + const embeddedContactName = + embeddedContact && embeddedContact.length > 0 + ? getName(embeddedContact[0]) + : ''; + + return { + author: contact.id, + id: quotedMessage.get('sent_at'), + text: body || embeddedContactName, + attachments: await this.getQuoteAttachment(attachments, preview), + }; + }, + + async sendMessage( + body, + attachments, + quote, + preview, + groupInvitation = null, + otherOptions = {} + ) { this.clearTypingTimers(); const destination = this.id; @@ -1154,10 +1524,14 @@ now ); + const conversationType = this.get('type'); + let messageWithSchema = null; - // If we are a friend then let the user send the message normally - if (this.isFriend()) { + // If we are a friend with any of the devices, send the message normally + const canSendNormalMessage = await this.isFriendWithAnyDevice(); + const isGroup = conversationType === Message.GROUP; + if (canSendNormalMessage || isGroup) { messageWithSchema = await upgradeMessageSchema({ type: 'outgoing', body, @@ -1192,12 +1566,19 @@ // If the requests didn't error then don't add a new friend request // because one of them was sent successfully - if (friendRequestSent) return null; + if (friendRequestSent) { + return null; + } } await this.setFriendRequestStatus( FriendRequestStatusEnum.pendingSend ); + // Always share our profileKey in the friend request + // This will get added automatically after the FR + // is accepted, via the profileSharing flag + profileKey = storage.get('profileKey'); + // Send the friend request! messageWithSchema = await upgradeMessageSchema({ type: 'friend-request', @@ -1212,9 +1593,29 @@ }); } - const message = this.addSingleMessage(messageWithSchema); - this.lastMessage = message.getNotificationText(); - this.lastMessageStatus = 'sending'; + if (this.isPrivate()) { + messageWithSchema.destination = destination; + } else if (this.isPublic()) { + // Public chats require this data to detect duplicates + messageWithSchema.source = textsecure.storage.user.getNumber(); + messageWithSchema.sourceDevice = 1; + } + + const { sessionRestoration = false } = otherOptions; + + const attributes = { + ...messageWithSchema, + groupInvitation, + sessionRestoration, + id: window.getGuid(), + }; + + const model = this.addSingleMessage(attributes); + const message = MessageController.register(model.id, model); + await window.Signal.Data.saveMessage(message.attributes, { + forceSave: true, + Message: Whisper.Message, + }); if (this.isPrivate()) { message.set({ destination }); @@ -1226,8 +1627,11 @@ message.set({ id }); this.set({ + lastMessage: model.getNotificationText(), + lastMessageStatus: 'sending', active_at: now, timestamp: now, + isArchived: false, }); await window.Signal.Data.updateConversation(this.id, this.attributes, { Conversation: Whisper.Conversation, @@ -1245,44 +1649,83 @@ return null; } - const conversationType = this.get('type'); - const sendFunction = (() => { - switch (conversationType) { - case Message.PRIVATE: - return textsecure.messaging.sendMessageToNumber; - case Message.GROUP: - return textsecure.messaging.sendMessageToGroup; - default: - throw new TypeError( - `Invalid conversation type: '${conversationType}'` - ); - } - })(); - const attachmentsWithData = await Promise.all( messageWithSchema.attachments.map(loadAttachmentData) ); + const { + body: messageBody, + attachments: finalAttachments, + } = Whisper.Message.getLongMessageAttachment({ + body, + attachments: attachmentsWithData, + now, + }); + + // Special-case the self-send case - we send only a sync message + if (this.isMe()) { + const dataMessage = await textsecure.messaging.getMessageProto( + destination, + messageBody, + finalAttachments, + quote, + preview, + now, + expireTimer, + profileKey + ); + return message.sendSyncMessageOnly(dataMessage); + } + const options = this.getSendOptions(); options.messageType = message.get('type'); + options.isPublic = this.isPublic(); + if (options.isPublic) { + options.publicSendData = await this.getPublicSendData(); + } - // Add the message sending on another queue so that our UI doesn't get blocked - this.queueMessageSend(async () => { - message.send( - this.wrapSend( - sendFunction( + options.groupInvitation = groupInvitation; + options.sessionRestoration = sessionRestoration; + + const groupNumbers = this.getRecipients(); + + const promise = (() => { + switch (conversationType) { + case Message.PRIVATE: + return textsecure.messaging.sendMessageToNumber( destination, - body, - attachmentsWithData, + messageBody, + finalAttachments, quote, preview, now, expireTimer, profileKey, options - ) - ) - ); + ); + case Message.GROUP: + return textsecure.messaging.sendMessageToGroup( + destination, + groupNumbers, + messageBody, + finalAttachments, + quote, + preview, + now, + expireTimer, + profileKey, + options + ); + default: + throw new TypeError( + `Invalid conversation type: '${conversationType}'` + ); + } + })(); + + // Add the message sending on another queue so that our UI doesn't get blocked + this.queueMessageSend(async () => { + message.send(this.wrapSend(promise)); }); return true; @@ -1313,14 +1756,36 @@ ); }, + async updateAvatarOnPublicChat({ url, profileKey }) { + if (!this.isPublic()) { + return; + } + if (this.isRss()) { + return; + } + if (!this.get('profileSharing')) { + return; + } + + if (profileKey && typeof profileKey !== 'string') { + // eslint-disable-next-line no-param-reassign + profileKey = window.Signal.Crypto.arrayBufferToBase64(profileKey); + } + const serverAPI = await lokiPublicChatAPI.findOrCreateServer( + this.get('server') + ); + await serverAPI.setAvatar(url, profileKey); + }, + async handleMessageSendResult({ failoverNumbers, unidentifiedDeliveries, messageType, success, }) { - if (success && messageType === 'friend-request') + if (success && messageType === 'friend-request') { await this.onFriendRequestSent(); + } await Promise.all( (failoverNumbers || []).map(async number => { const conversation = ConversationController.get(number); @@ -1473,7 +1938,6 @@ ? lastMessageModel.getMessagePropStatus() : null; const lastMessageUpdate = Conversation.createLastMessageUpdate({ - currentLastMessageText: this.get('lastMessage') || null, currentTimestamp: this.get('timestamp') || null, lastMessage: lastMessageJSON, lastMessageStatus: lastMessageStatusModel, @@ -1482,17 +1946,6 @@ : null, }); - let hasChanged = false; - const { lastMessage, lastMessageStatus } = lastMessageUpdate; - delete lastMessageUpdate.lastMessage; - delete lastMessageUpdate.lastMessageStatus; - - hasChanged = hasChanged || lastMessage !== this.lastMessage; - this.lastMessage = lastMessage; - - hasChanged = hasChanged || lastMessageStatus !== this.lastMessageStatus; - this.lastMessageStatus = lastMessageStatus; - // Because we're no longer using Backbone-integrated saves, we need to manually // clear the changed fields here so our hasChanged() check below is useful. this.changed = {}; @@ -1502,11 +1955,16 @@ await window.Signal.Data.updateConversation(this.id, this.attributes, { Conversation: Whisper.Conversation, }); - } else if (hasChanged) { - this.trigger('change'); } }, + async setArchived(isArchived) { + this.set({ isArchived }); + await window.Signal.Data.updateConversation(this.id, this.attributes, { + Conversation: Whisper.Conversation, + }); + }, + async updateExpirationTimer( providedExpireTimer, providedSource, @@ -1578,25 +2036,48 @@ return message; } - let sendFunc; - if (this.get('type') === 'private') { - sendFunc = textsecure.messaging.sendExpirationTimerUpdateToNumber; - } else { - sendFunc = textsecure.messaging.sendExpirationTimerUpdateToGroup; - } let profileKey; if (this.get('profileSharing')) { profileKey = storage.get('profileKey'); } - const sendOptions = this.getSendOptions(); - const promise = sendFunc( - this.get('id'), - this.get('expireTimer'), - message.get('sent_at'), - profileKey, - sendOptions - ); + let promise; + + if (this.isMe()) { + const flags = + textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE; + const dataMessage = await textsecure.messaging.getMessageProto( + this.get('id'), + null, + [], + null, + [], + message.get('sent_at'), + expireTimer, + profileKey, + flags + ); + return message.sendSyncMessageOnly(dataMessage); + } + + if (this.get('type') === 'private') { + promise = textsecure.messaging.sendExpirationTimerUpdateToNumber( + this.get('id'), + expireTimer, + message.get('sent_at'), + profileKey, + sendOptions + ); + } else { + promise = textsecure.messaging.sendExpirationTimerUpdateToGroup( + this.get('id'), + this.getRecipients(), + expireTimer, + message.get('sent_at'), + profileKey, + sendOptions + ); + } await message.send(this.wrapSend(promise)); @@ -1608,7 +2089,9 @@ }, async setSessionResetStatus(newStatus) { // Ensure that the new status is a valid SessionResetEnum value - if (!(newStatus in Object.values(SessionResetEnum))) return; + if (!(newStatus in Object.values(SessionResetEnum))) { + return; + } if (this.get('sessionResetStatus') !== newStatus) { this.set({ sessionResetStatus: newStatus }); await window.Signal.Data.updateConversation(this.id, this.attributes, { @@ -1623,7 +2106,7 @@ await this.setSessionResetStatus(SessionResetEnum.request_received); // send empty message, this will trigger the new session to propagate // to the reset initiator. - await window.libloki.api.sendEmptyMessage(this.id); + window.libloki.api.sendBackgroundMessage(this.id); }, isSessionResetReceived() { @@ -1659,7 +2142,7 @@ async onNewSessionAdopted() { if (this.get('sessionResetStatus') === SessionResetEnum.initiated) { // send empty message to confirm that we have adopted the new session - await window.libloki.api.sendEmptyMessage(this.id); + window.libloki.api.sendBackgroundMessage(this.id); } await this.createAndStoreEndSessionMessage({ type: 'incoming', @@ -1729,6 +2212,7 @@ this.get('name'), this.get('avatar'), this.get('members'), + groupUpdate.recipients, options ) ) @@ -1738,6 +2222,7 @@ async leaveGroup() { const now = Date.now(); if (this.get('type') === 'group') { + const groupNumbers = this.getRecipients(); this.set({ left: true }); await window.Signal.Data.updateConversation(this.id, this.attributes, { Conversation: Whisper.Conversation, @@ -1758,8 +2243,12 @@ const options = this.getSendOptions(); message.send( - this.wrapSend(textsecure.messaging.leaveGroup(this.id, options)) + this.wrapSend( + textsecure.messaging.leaveGroup(this.id, groupNumbers, options) + ) ); + + this.updateTextInputState(); } }, @@ -1781,11 +2270,9 @@ let read = await Promise.all( _.map(oldUnread, async providedM => { - let m = providedM; + const m = MessageController.register(providedM.id, providedM); - if (this.messageCollection.get(m.id)) { - m = this.messageCollection.get(m.id); - } else { + if (!this.messageCollection.get(m.id)) { window.log.warn( 'Marked a message as read in the database, but ' + 'it was not in messageCollection.' @@ -1808,6 +2295,23 @@ const unreadCount = unreadMessages.length - read.length; this.set({ unreadCount }); + + const mentionRead = (() => { + const stillUnread = unreadMessages.filter( + m => m.get('received_at') > newestUnreadDate + ); + const ourNumber = textsecure.storage.user.getNumber(); + return !stillUnread.some( + m => + m.propsForMessage.text && + m.propsForMessage.text.indexOf(`@${ourNumber}`) !== -1 + ); + })(); + + if (mentionRead) { + this.set({ mentionedUs: false }); + } + await window.Signal.Data.updateConversation(this.id, this.attributes, { Conversation: Whisper.Conversation, }); @@ -1819,7 +2323,12 @@ // conversation is viewed, another error message shows up for the contact read = read.filter(item => !item.hasErrors); - if (read.length && options.sendReadReceipts) { + // Do not send read receipt if not friends yet + if (!this.isFriendWithAnyDevice()) { + return; + } + + if (this.isPrivate() && read.length && options.sendReadReceipts) { window.log.info(`Sending ${read.length} read receipts`); // Because syncReadMessages sends to our other devices, and sendReadReceipts goes // to a contact, we need accessKeys for both. @@ -1854,40 +2363,137 @@ async setNickname(nickname) { const trimmed = nickname && nickname.trim(); - if (this.get('nickname') === trimmed) return; + if (this.get('nickname') === trimmed) { + return; + } this.set({ nickname: trimmed }); await window.Signal.Data.updateConversation(this.id, this.attributes, { Conversation: Whisper.Conversation, }); - await this.updateProfile(); + await this.updateProfileName(); }, - async setProfile(profile) { - if (!_.isEqual(this.get('profile'), profile)) { - this.set({ profile }); + async setLokiProfile(newProfile) { + if (!_.isEqual(this.get('profile'), newProfile)) { + this.set({ profile: newProfile }); await window.Signal.Data.updateConversation(this.id, this.attributes, { Conversation: Whisper.Conversation, }); } - await this.updateProfile(); + // if set to null, it will show a jazzIcon + await this.setProfileAvatar({ path: newProfile.avatar }); + + await this.updateProfileName(); }, - async updateProfile() { + async updateProfileName() { // Prioritise nickname over the profile display name const nickname = this.getNickname(); - const profile = this.getLocalProfile(); - const displayName = profile && profile.name && profile.name.displayName; + const profile = this.getLokiProfile(); + const displayName = profile && profile.displayName; const profileName = nickname || displayName || null; await this.setProfileName(profileName); }, - getLocalProfile() { + getLokiProfile() { return this.get('profile'); }, getNickname() { return this.get('nickname'); }, + getRssSettings() { + if (!this.isRss()) { + return null; + } + return { + RSS_FEED: this.get('rssFeed'), + CONVO_ID: this.id, + title: this.get('name'), + closeable: this.get('closable'), + }; + }, + // maybe "Backend" instead of "Source"? + async setPublicSource(newServer, newChannelId) { + if (!this.isPublic()) { + log.warn( + `trying to setPublicSource on non public chat conversation ${this.id}` + ); + return; + } + if ( + this.get('server') !== newServer || + this.get('channelId') !== newChannelId + ) { + // mark active so it's not in the friends list but the conversation list + this.set({ + server: newServer, + channelId: newChannelId, + active_at: Date.now(), + }); + await window.Signal.Data.updateConversation(this.id, this.attributes, { + Conversation: Whisper.Conversation, + }); + } + }, + getPublicSource() { + if (!this.isPublic()) { + log.warn( + `trying to getPublicSource on non public chat conversation ${this.id}` + ); + return null; + } + return { + server: this.get('server'), + channelId: this.get('channelId'), + conversationId: this.get('id'), + }; + }, + async getPublicSendData() { + const channelAPI = await lokiPublicChatAPI.findOrCreateChannel( + this.get('server'), + this.get('channelId'), + this.id + ); + return channelAPI; + }, + getLastRetrievedMessage() { + if (!this.isPublic()) { + return null; + } + const lastMessageId = this.get('lastPublicMessage') || 0; + return lastMessageId; + }, + async setLastRetrievedMessage(newLastMessageId) { + if (!this.isPublic()) { + return; + } + if (this.get('lastPublicMessage') !== newLastMessageId) { + this.set({ lastPublicMessage: newLastMessageId }); + await window.Signal.Data.updateConversation(this.id, this.attributes, { + Conversation: Whisper.Conversation, + }); + } + }, + isModerator(pubKey) { + if (!this.isPublic()) { + return false; + } + const moderators = this.get('moderators'); + return Array.isArray(moderators) && moderators.includes(pubKey); + }, + async setModerators(moderators) { + if (!this.isPublic()) { + return; + } + // TODO: compare array properly + if (!_.isEqual(this.get('moderators'), moderators)) { + this.set({ moderators }); + await window.Signal.Data.updateConversation(this.id, this.attributes, { + Conversation: Whisper.Conversation, + }); + } + }, // SIGNAL PROFILES @@ -1914,7 +2520,7 @@ const c = await ConversationController.getOrCreateAndWait(id, 'private'); // We only need to update the profile as they are all stored inside the conversation - await c.updateProfile(); + await c.updateProfileName(); }, async setProfileName(name) { const profileName = this.get('profileName'); @@ -1925,10 +2531,40 @@ }); } }, - async setProfileAvatar(avatarPath) { + async setGroupName(name) { + const profileName = this.get('name'); + if (profileName !== name) { + this.set({ name }); + await window.Signal.Data.updateConversation(this.id, this.attributes, { + Conversation: Whisper.Conversation, + }); + } + }, + async setSubscriberCount(count) { + this.set({ subscriberCount: count }); + // Not sure if we care about updating the database + }, + async setGroupNameAndAvatar(name, avatarPath) { + const currentName = this.get('name'); const profileAvatar = this.get('profileAvatar'); - if (profileAvatar !== avatarPath) { - this.set({ profileAvatar: avatarPath }); + if (profileAvatar !== avatarPath || currentName !== name) { + // only update changed items + if (profileAvatar !== avatarPath) { + this.set({ profileAvatar: avatarPath }); + } + if (currentName !== name) { + this.set({ name }); + } + // save + await window.Signal.Data.updateConversation(this.id, this.attributes, { + Conversation: Whisper.Conversation, + }); + } + }, + async setProfileAvatar(avatar) { + const profileAvatar = this.get('profileAvatar'); + if (profileAvatar !== avatar) { + this.set({ profileAvatar: avatar }); await window.Signal.Data.updateConversation(this.id, this.attributes, { Conversation: Whisper.Conversation, }); @@ -2054,8 +2690,16 @@ copyPublicKey() { clipboard.writeText(this.id); - window.Whisper.events.trigger('showToast', { - message: i18n('copiedPublicKey'), + + const isGroup = this.getProps().type === 'group'; + const copiedMessage = isGroup + ? i18n('copiedChatId') + : i18n('copiedPublicKey'); + + window.pushToast({ + title: copiedMessage, + type: 'success', + id: 'copiedPublicKey', }); }, @@ -2068,17 +2712,84 @@ }, deleteContact() { - Whisper.events.trigger('showConfirmationDialog', { - message: i18n('deleteContactConfirmation'), - onOk: () => ConversationController.deleteContact(this.id), + const title = this.isPublic() + ? i18n('deletePublicChannel') + : i18n('deleteContact'); + + const message = this.isPublic() + ? i18n('deletePublicChannelConfirmation') + : i18n('deleteContactConfirmation'); + + window.confirmationDialog({ + title, + message, + resolve: () => ConversationController.deleteContact(this.id), }); }, + async deletePublicMessages(messages) { + const channelAPI = await this.getPublicSendData(); + if (!channelAPI) { + return false; + } + + const invalidMessages = messages.filter(m => !m.getServerId()); + const pendingMessages = messages.filter(m => m.getServerId()); + + let deletedServerIds = []; + let ignoredServerIds = []; + + if (pendingMessages.length > 0) { + const result = await channelAPI.deleteMessages( + pendingMessages.map(m => m.getServerId()) + ); + deletedServerIds = result.deletedIds; + ignoredServerIds = result.ignoredIds; + } + + const toDeleteLocallyServerIds = _.union( + deletedServerIds, + ignoredServerIds + ); + let toDeleteLocally = messages.filter(m => + toDeleteLocallyServerIds.includes(m.getServerId()) + ); + toDeleteLocally = _.union(toDeleteLocally, invalidMessages); + + toDeleteLocally.forEach(m => this.removeMessage(m.id)); + + return toDeleteLocally; + }, + + removeMessage(messageId) { + const message = this.messageCollection.models.find( + msg => msg.id === messageId + ); + if (message) { + message.trigger('unload'); + this.messageCollection.remove(messageId); + } + }, + deleteMessages() { - Whisper.events.trigger('showConfirmationDialog', { - message: i18n('deleteConversationConfirmation'), - onOk: () => this.destroyMessages(), - }); + this.resetMessageSelection(); + + let params; + if (this.isPublic()) { + params = { + title: i18n('deleteMessages'), + message: i18n('deletePublicConversationConfirmation'), + resolve: () => ConversationController.deleteContact(this.id), + }; + } else { + params = { + title: i18n('deleteMessages'), + message: i18n('deleteConversationConfirmation'), + resolve: () => this.destroyMessages(), + }; + } + + window.confirmationDialog(params); }, async destroyMessages() { @@ -2088,11 +2799,19 @@ this.messageCollection.reset([]); - this.set({ - lastMessage: null, - timestamp: null, - active_at: null, - }); + // let's try to keep the RSS conversation open just empty... + if (this.isRss()) { + this.set({ + lastMessage: null, + }); + } else { + // this will remove the conversation from conversation lists... + this.set({ + lastMessage: null, + timestamp: null, + active_at: null, + }); + } // Reset our friend status if we're not friends if (!this.isFriend()) { @@ -2108,7 +2827,7 @@ if (this.isPrivate()) { return this.get('name'); } - return this.get('name') || 'Unknown group'; + return this.get('name') || i18n('unknownGroup'); }, getTitle() { @@ -2183,20 +2902,20 @@ getAvatarPath() { const avatar = this.get('avatar') || this.get('profileAvatar'); - if (avatar) { - if (avatar.path) return getAbsoluteAttachmentPath(avatar.path); + if (typeof avatar === 'string') { return avatar; } + if (avatar && avatar.path && typeof avatar.path === 'string') { + return getAbsoluteAttachmentPath(avatar.path); + } + return null; }, getAvatar() { const title = this.get('name'); const color = this.getColor(); - const avatar = this.get('avatar') || this.get('profileAvatar'); - - const url = - avatar && avatar.path ? getAbsoluteAttachmentPath(avatar.path) : avatar; + const url = this.getAvatarPath(); if (url) { return { url, color }; @@ -2223,11 +2942,14 @@ notify(message) { if (message.isFriendRequest()) { - if (this.hasSentFriendRequest()) + if (this.hasSentFriendRequest()) { return this.notifyFriendRequest(message.get('source'), 'accepted'); + } return this.notifyFriendRequest(message.get('source'), 'requested'); } - if (!message.isIncoming()) return Promise.resolve(); + if (!message.isIncoming()) { + return Promise.resolve(); + } const conversationId = this.id; return ConversationController.getOrCreateAndWait( @@ -2260,9 +2982,12 @@ // Notification for friend request received async notifyFriendRequest(source, type) { // Data validation - if (!source) throw new Error('Invalid source'); - if (!['accepted', 'requested'].includes(type)) + if (!source) { + throw new Error('Invalid source'); + } + if (!['accepted', 'requested'].includes(type)) { throw new Error('Type must be accepted or requested.'); + } // Call the notification on the right conversation let conversation = this; @@ -2311,6 +3036,23 @@ return; } + // For groups, block typing messages from non-members (e.g. from kicked members) + if (this.get('type') === 'group') { + const knownMembers = this.get('members'); + + if (knownMembers) { + const fromMember = knownMembers.includes(sender); + + if (!fromMember) { + window.log.warn( + 'Blocking typing messages from a non-member: ', + sender + ); + return; + } + } + } + const identifier = `${sender}.${senderDevice}`; this.contactTypingTimers = this.contactTypingTimers || {}; @@ -2340,14 +3082,14 @@ if (!record) { // User was not previously typing before. State change! this.trigger('typing-update'); - this.trigger('change'); + this.trigger('change', this); } } else { delete this.contactTypingTimers[identifier]; if (record) { // User was previously typing, and is no longer. State change! this.trigger('typing-update'); - this.trigger('change'); + this.trigger('change', this); } } }, @@ -2362,7 +3104,7 @@ // User was previously typing, but timed out or we received message. State change! this.trigger('typing-update'); - this.trigger('change'); + this.trigger('change', this); } }, }); @@ -2384,21 +3126,6 @@ ); this.reset([]); }, - - async search(providedQuery) { - let query = providedQuery.trim().toLowerCase(); - query = query.replace(/[+-.()]*/g, ''); - - if (query.length === 0) { - return; - } - - const collection = await window.Signal.Data.searchConversations(query, { - ConversationCollection: Whisper.ConversationCollection, - }); - - this.reset(collection.models); - }, }); Whisper.Conversation.COLORS = COLORS.concat(['grey', 'default']).join(' '); diff --git a/js/models/messages.js b/js/models/messages.js index a04587eb3c..0fb0a6e617 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -1,14 +1,18 @@ -/* global _: false */ -/* global Backbone: false */ -/* global storage: false */ -/* global filesize: false */ -/* global ConversationController: false */ -/* global clipboard: false */ -/* global getAccountManager: false */ -/* global i18n: false */ -/* global Signal: false */ -/* global textsecure: false */ -/* global Whisper: false */ +/* global + _, + Backbone, + storage, + filesize, + ConversationController, + MessageController, + getAccountManager, + i18n, + Signal, + textsecure, + Whisper, + clipboard, + libloki, +*/ /* eslint-disable more/no-then */ @@ -18,22 +22,17 @@ window.Whisper = window.Whisper || {}; + const { Message: TypedMessage, Contact, PhoneNumber, Errors } = Signal.Types; + const { - Message: TypedMessage, - Contact, - PhoneNumber, - Attachment, - } = Signal.Types; - const { - deleteAttachmentData, deleteExternalMessageFiles, getAbsoluteAttachmentPath, loadAttachmentData, loadQuoteData, loadPreviewData, - writeNewAttachmentData, - writeAttachment, + upgradeMessageSchema, } = window.Signal.Migrations; + const { bytesFromString } = window.Signal.Crypto; window.AccountCache = Object.create(null); window.AccountJobs = Object.create(null); @@ -91,8 +90,50 @@ this.on('unload', this.unload); this.on('expired', this.onExpired); this.setToExpire(); + // Keep props ready + const generateProps = () => { + if (this.isExpirationTimerUpdate()) { + this.propsForTimerNotification = this.getPropsForTimerNotification(); + } else if (this.isKeyChange()) { + this.propsForSafetyNumberNotification = this.getPropsForSafetyNumberNotification(); + } else if (this.isVerifiedChange()) { + this.propsForVerificationNotification = this.getPropsForVerificationNotification(); + } else if (this.isEndSession()) { + this.propsForResetSessionNotification = this.getPropsForResetSessionNotification(); + } else if (this.isGroupUpdate()) { + this.propsForGroupNotification = this.getPropsForGroupNotification(); + } else if (this.isSessionRestoration()) { + // do nothing + } else if (this.isFriendRequest()) { + this.propsForFriendRequest = this.getPropsForFriendRequest(); + } else if (this.isGroupInvitation()) { + this.propsForGroupInvitation = this.getPropsForGroupInvitation(); + } else { + this.propsForSearchResult = this.getPropsForSearchResult(); + this.propsForMessage = this.getPropsForMessage(); + } + }; + this.on('change', generateProps); + + const applicableConversationChanges = + 'change:color change:name change:number change:profileName change:profileAvatar'; + + const conversation = this.getConversation(); + const fromContact = this.getIncomingContact(); + + this.listenTo(conversation, applicableConversationChanges, generateProps); + if (fromContact) { + this.listenTo( + fromContact, + applicableConversationChanges, + generateProps + ); + } + + this.selected = false; + window.contextMenuShown = false; - this.updatePreview(); + generateProps(); }, idForLogging() { return `${this.get('source')}.${this.get('sourceDevice')} ${this.get( @@ -118,73 +159,6 @@ // eslint-disable-next-line no-bitwise return !!(this.get('flags') & flag); }, - async updatePreview() { - // Don't generate link previews if user has turned them off - if (!storage.get('linkPreviews', false)) { - return; - } - - if (this.updatingPreview) { - return; - } - - // Only update the preview if we don't have any set - const preview = this.get('preview'); - if (!_.isEmpty(preview)) { - return; - } - - // Make sure we have links we can preview - const links = Signal.LinkPreviews.findLinks(this.get('body')); - const firstLink = links.find(link => - Signal.LinkPreviews.isLinkInWhitelist(link) - ); - if (!firstLink) { - return; - } - - this.updatingPreview = true; - - try { - const result = await Signal.LinkPreviews.helper.getPreview(firstLink); - - const { image, title, hash } = result; - - // A link preview isn't worth showing unless we have either a title or an image - if (!result || !(image || title)) { - this.updatingPreview = false; - return; - } - - // Save the image to disk - const { data } = image; - const extension = Attachment.getFileExtension(image); - if (data && extension) { - const hash32 = hash.substring(0, 32); - try { - const filePath = await writeAttachment({ - data, - path: `previews/${hash32}.${extension}`, - }); - - // return the image without the data - result.image = _.omit({ ...image, path: filePath }, 'data'); - } catch (e) { - window.log.warn('Failed to write preview to disk', e); - } - } - - // Save it!! - this.set({ preview: [result] }); - await window.Signal.Data.saveMessage(this.attributes, { - Message: Whisper.Message, - }); - } catch (e) { - window.log.warn(`Failed to load previews for message: ${this.id}`); - } finally { - this.updatingPreview = false; - } - }, getEndSessionTranslationKey() { const sessionType = this.get('endSessionType'); if (sessionType === 'ongoing') { @@ -229,6 +203,13 @@ } return conversation.getDisplayName(); }, + getLokiNameForNumber(number) { + const conversation = ConversationController.get(number); + if (!conversation) { + return number; + } + return conversation.getLokiProfile().displayName; + }, getDescription() { if (this.isGroupUpdate()) { const groupUpdate = this.get('group_update'); @@ -238,18 +219,22 @@ return i18n('leftTheGroup', this.getNameForNumber(groupUpdate.left)); } + if (groupUpdate.kicked === 'You') { + return i18n('youGotKickedFromGroup'); + } + const messages = []; - if (!groupUpdate.name && !groupUpdate.joined) { + if (!groupUpdate.name && !groupUpdate.joined && !groupUpdate.kicked) { messages.push(i18n('updatedTheGroup')); } if (groupUpdate.name) { messages.push(i18n('titleIsNow', groupUpdate.name)); } if (groupUpdate.joined && groupUpdate.joined.length) { - const names = _.map( - groupUpdate.joined, - this.getNameForNumber.bind(this) + const names = groupUpdate.joined.map(name => + this.getLokiNameForNumber(name) ); + if (names.length > 1) { messages.push(i18n('multipleJoinedTheGroup', names.join(', '))); } else { @@ -257,6 +242,18 @@ } } + if (groupUpdate.kicked && groupUpdate.kicked.length) { + const names = _.map( + groupUpdate.kicked, + this.getNameForNumber.bind(this) + ); + + if (names.length > 1) { + messages.push(i18n('multipleKickedFromTheGroup', names.join(', '))); + } else { + messages.push(i18n('kickedFromTheGroup', names[0])); + } + } return messages.join(', '); } if (this.isEndSession()) { @@ -265,6 +262,9 @@ if (this.isIncoming() && this.hasErrors()) { return i18n('incomingError'); } + if (this.isGroupInvitation()) { + return `<${i18n('groupInvitation')}>`; + } return this.get('body'); }, isVerifiedChange() { @@ -276,10 +276,22 @@ isFriendRequest() { return this.get('type') === 'friend-request'; }, + isGroupInvitation() { + return !!this.get('groupInvitation'); + }, + isSessionRestoration() { + const flag = textsecure.protobuf.DataMessage.Flags.SESSION_RESTORE; + // eslint-disable-next-line no-bitwise + const sessionRestoreFlag = !!(this.get('flags') & flag); + + return !!this.get('sessionRestoration') || sessionRestoreFlag; + }, getNotificationText() { const description = this.getDescription(); if (description) { - if (this.isFriendRequest()) return `Friend Request: ${description}`; + if (this.isFriendRequest()) { + return `Friend Request: ${description}`; + } return description; } if (this.get('attachments').length > 0) { @@ -315,6 +327,7 @@ this.cleanup(); }, async cleanup() { + MessageController.unregister(this.id); this.unload(); await deleteExternalMessageFiles(this.attributes); }, @@ -327,9 +340,12 @@ this.hasExpired = true; }, getPropsForTimerNotification() { - const { expireTimer, fromSync, source } = this.get( - 'expirationTimerUpdate' - ); + const timerUpdate = this.get('expirationTimerUpdate'); + if (!timerUpdate) { + return null; + } + + const { expireTimer, fromSync, source } = timerUpdate; const timespan = Whisper.ExpirationTimerOptions.getName(expireTimer || 0); const disabled = !expireTimer; @@ -385,9 +401,19 @@ }, async acceptFriendRequest() { - if (this.get('friendStatus') !== 'pending') return; - const conversation = this.getConversation(); - + if (this.get('friendStatus') !== 'pending') { + return; + } + const conversation = await this.getSourceDeviceConversation(); + // If we somehow received an old friend request (e.g. after having restored + // from seed, we won't be able to accept it, we should initiate our own + // friend request to reset the session: + if (conversation.get('sessionRestoreSeen')) { + conversation.sendMessage('', null, null, null, null, { + sessionRestoration: true, + }); + return; + } this.set({ friendStatus: 'accepted' }); await window.Signal.Data.saveMessage(this.attributes, { Message: Whisper.Message, @@ -395,7 +421,9 @@ conversation.onAcceptFriendRequest(); }, async declineFriendRequest() { - if (this.get('friendStatus') !== 'pending') return; + if (this.get('friendStatus') !== 'pending') { + return; + } const conversation = this.getConversation(); this.set({ friendStatus: 'declined' }); @@ -430,6 +458,7 @@ return { text: this.createNonBreakingLastSeparator(this.get('body')), + timestamp: this.get('sent_at'), status: this.getMessagePropStatus(), direction, friendStatus, @@ -442,6 +471,27 @@ onRetrySend, }; }, + getPropsForGroupInvitation() { + const invitation = this.get('groupInvitation'); + + let direction = this.get('direction'); + if (!direction) { + direction = this.get('type') === 'outgoing' ? 'outgoing' : 'incoming'; + } + + return { + serverName: invitation.serverName, + serverAddress: invitation.serverAddress, + direction, + onClick: () => { + Whisper.events.trigger( + 'publicChatInvitationAccepted', + invitation.serverAddress, + invitation.channelId + ); + }, + }; + }, findContact(phoneNumber) { return ConversationController.get(phoneNumber); }, @@ -485,6 +535,23 @@ }); } + if (groupUpdate.kicked === 'You') { + changes.push({ + type: 'kicked', + isMe: true, + }); + } else if (groupUpdate.kicked) { + changes.push({ + type: 'kicked', + contacts: _.map( + Array.isArray(groupUpdate.kicked) + ? groupUpdate.kicked + : [groupUpdate.kicked], + phoneNumber => this.findAndFormatContact(phoneNumber) + ), + }); + } + if (groupUpdate.left === 'You') { changes.push({ type: 'remove', @@ -544,11 +611,42 @@ return 'sent'; } const calculatingPoW = this.get('calculatingPoW'); - if (calculatingPoW) return 'pow'; + if (calculatingPoW) { + return 'pow'; + } return 'sending'; }, - getPropsForMessage() { + getPropsForSearchResult() { + const fromNumber = this.getSource(); + const from = this.findAndFormatContact(fromNumber); + if (fromNumber === this.OUR_NUMBER) { + from.isMe = true; + } + + const toNumber = this.get('conversationId'); + let to = this.findAndFormatContact(toNumber); + if (toNumber === this.OUR_NUMBER) { + to.isMe = true; + } else if (fromNumber === toNumber) { + to = { + isMe: true, + }; + } + + return { + from, + to, + + isSelected: this.isSelected, + + id: this.id, + conversationId: this.get('conversationId'), + receivedAt: this.get('received_at'), + snippet: this.get('snippet'), + }; + }, + getPropsForMessage(options) { const phoneNumber = this.getSource(); const contact = this.findAndFormatContact(phoneNumber); const contactModel = this.findContact(phoneNumber); @@ -565,14 +663,22 @@ ? expireTimerStart + expirationLength : null; + // TODO: investigate why conversation is undefined + // for the public group chat const conversation = this.getConversation(); - const isGroup = conversation && !conversation.isPrivate(); + + const isModerator = + conversation && !!conversation.isModerator(this.OUR_NUMBER); + + const convoId = conversation ? conversation.id : undefined; + const isGroup = !!conversation && !conversation.isPrivate(); const attachments = this.get('attachments') || []; const firstAttachment = attachments[0]; return { text: this.createNonBreakingLastSeparator(this.get('body')), + textPending: this.get('bodyPending'), id: this.id, direction: this.isIncoming() ? 'incoming' : 'outgoing', timestamp: this.get('sent_at'), @@ -583,18 +689,35 @@ authorProfileName: contact.profileName, authorPhoneNumber: contact.phoneNumber, conversationType: isGroup ? 'group' : 'direct', - attachments: attachments.map(attachment => - this.getPropsForAttachment(attachment) - ), + convoId, + attachments: attachments + .filter(attachment => !attachment.error) + .map(attachment => this.getPropsForAttachment(attachment)), previews: this.getPropsForPreview(), - quote: this.getPropsForQuote(), + quote: this.getPropsForQuote(options), authorAvatarPath, isExpired: this.hasExpired, expirationLength, expirationTimestamp, - isP2p: !!this.get('isP2p'), + selected: this.selected, + multiSelectMode: conversation && conversation.selectedMessages.size > 0, + isPublic: !!this.get('isPublic'), + isRss: !!this.get('isRss'), + senderIsModerator: + !!this.get('isPublic') && + conversation && + conversation.isModerator(phoneNumber), + isDeletable: + !this.get('isPublic') || + isModerator || + phoneNumber === this.OUR_NUMBER, + isModerator, onCopyText: () => this.copyText(), + onSelectMessage: () => this.selectMessage(), + onSelectMessageUnchecked: () => this.selectMessageUnchecked(), + onCopyPubKey: () => this.copyPubKey(), + onBanUser: () => this.banUser(), onReply: () => this.trigger('reply', this), onRetrySend: () => this.retrySend(), onShowDetail: () => this.trigger('show-message-detail', this), @@ -612,6 +735,10 @@ message: this, isDangerous, }), + onShowUserDetails: pubkey => + window.Whisper.events.trigger('onShowUserDetails', { + userPubKey: pubkey, + }), }; }, createNonBreakingLastSeparator(text) { @@ -622,11 +749,10 @@ const nbsp = '\xa0'; const regex = /(\S)( +)(\S+\s*)$/; return text.replace(regex, (match, start, spaces, end) => { - const newSpaces = _.reduce( - spaces, - accumulator => accumulator + nbsp, - '' - ); + const newSpaces = + end.length < 12 + ? _.reduce(spaces, accumulator => accumulator + nbsp, '') + : spaces; return `${start}${newSpaces}${end}`; }); }, @@ -660,7 +786,7 @@ // Would be nice to do this before render, on initial load of message if (!window.isSignalAccountCheckComplete(firstNumber)) { window.checkForSignalAccount(firstNumber).then(() => { - this.trigger('change'); + this.trigger('change', this); }); } @@ -694,7 +820,7 @@ }, getPropsForPreview() { // Don't generate link previews if user has turned them off - if (!storage.get('linkPreviews', false)) { + if (!storage.get('link-preview-setting', false)) { return null; } @@ -717,7 +843,8 @@ }; }); }, - getPropsForQuote() { + getPropsForQuote(options = {}) { + const { noClick } = options; const quote = this.get('quote'); if (!quote) { return null; @@ -736,13 +863,16 @@ const authorProfileName = contact ? contact.getProfileName() : null; const authorName = contact ? contact.getName() : null; const isFromMe = contact ? contact.id === this.OUR_NUMBER : false; - const onClick = () => { - this.trigger('scroll-to-message', { - author, - id, - referencedMessageNotFound, - }); - }; + const onClick = noClick + ? null + : event => { + event.stopPropagation(); + this.trigger('scroll-to-message', { + author, + id, + referencedMessageNotFound, + }); + }; const firstAttachment = quote.attachments && quote.attachments[0]; @@ -765,7 +895,7 @@ return null; } - const { path, flags, size, screenshot, thumbnail } = attachment; + const { path, pending, flags, size, screenshot, thumbnail } = attachment; return { ...attachment, @@ -774,7 +904,8 @@ flags && // eslint-disable-next-line no-bitwise flags & textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE, - url: getAbsoluteAttachmentPath(path), + pending, + url: path ? getAbsoluteAttachmentPath(path) : null, screenshot: screenshot ? { ...screenshot, @@ -808,11 +939,15 @@ return accumulator; }, Object.create(null)); + // We include numbers we didn't successfully send to so we can display errors. // Older messages don't have the recipients included on the message, so we fall // back to the conversation's current recipients const phoneNumbers = this.isIncoming() ? [this.get('source')] - : this.get('recipients') || this.conversation.getRecipients(); + : _.union( + this.get('sent_to') || [], + this.get('recipients') || this.getConversation().getRecipients() + ); // This will make the error message for outgoing key errors a bit nicer const allErrors = (this.get('errors') || []).map(error => { @@ -828,6 +963,7 @@ // that contact. Otherwise, it will be a standalone entry. const errors = _.reject(allErrors, error => Boolean(error.number)); const errorsGroupedById = _.groupBy(allErrors, 'number'); + const primaryDevicePubKey = this.get('conversationId'); const finalContacts = (phoneNumbers || []).map(id => { const errorsForContact = errorsGroupedById[id]; const isOutgoingKeyError = Boolean( @@ -837,12 +973,20 @@ storage.get('unidentifiedDeliveryIndicators') && this.isUnidentifiedDelivery(id, unidentifiedLookup); + const isPrimaryDevice = id === primaryDevicePubKey; + + const contact = this.findAndFormatContact(id); + const profileName = isPrimaryDevice + ? contact.profileName + : `${contact.profileName} (Secondary Device)`; return { - ...this.findAndFormatContact(id), + ...contact, status: this.getStatus(id), errors: errorsForContact, isOutgoingKeyError, isUnidentifiedDelivery, + isPrimaryDevice, + profileName, onSendAnyway: () => this.trigger('force-send', { contact: this.findContact(id), @@ -857,14 +1001,15 @@ // first; otherwise it's alphabetical const sortedContacts = _.sortBy( finalContacts, - contact => `${contact.errors ? '0' : '1'}${contact.title}` + contact => + `${contact.isPrimaryDevice ? '0' : '1'}${contact.phoneNumber}` ); return { sentAt: this.get('sent_at'), receivedAt: this.get('received_at'), message: { - ...this.getPropsForMessage(), + ...this.getPropsForMessage({ noClick: true }), disableMenu: true, // To ensure that group avatar doesn't show up conversationType: 'direct', @@ -874,10 +1019,78 @@ }; }, + copyPubKey() { + if (this.isIncoming()) { + clipboard.writeText(this.get('source')); + } else { + clipboard.writeText(this.OUR_NUMBER); + } + + window.pushToast({ + title: i18n('copiedPublicKey'), + type: 'success', + id: 'copiedPublicKey', + }); + }, + + banUser() { + window.confirmationDialog({ + title: i18n('banUser'), + message: i18n('banUserConfirm'), + resolve: async () => { + const source = this.get('source'); + const conversation = this.getConversation(); + + const channelAPI = await conversation.getPublicSendData(); + const success = await channelAPI.banUser(source); + + if (success) { + window.pushToast({ + title: i18n('userBanned'), + type: 'success', + id: 'userBanned', + }); + } else { + window.pushToast({ + title: i18n('userBanFailed'), + type: 'error', + id: 'userBanFailed', + }); + } + }, + }); + }, + + // Select message even if the context menu is shown + selectMessageUnchecked() { + this.selected = !this.selected; + + const convo = this.getConversation(); + + if (this.selected) { + convo.addMessageSelection(this); + } else { + convo.removeMessageSelection(this); + } + + this.trigger('change'); + }, + + selectMessage() { + if (window.contextMenuShown || this.get('isRss')) { + return; + } + + this.selectMessageUnchecked(); + }, + copyText() { clipboard.writeText(this.get('body')); - window.Whisper.events.trigger('showToast', { - message: i18n('copiedMessage'), + + window.pushToast({ + title: i18n('copiedMessage'), + type: 'success', + id: 'copiedMessage', }); }, @@ -888,52 +1101,66 @@ return null; } - const [retries, errors] = _.partition( - this.get('errors'), - this.isReplayableError.bind(this) - ); + this.set({ errors: null }); - // Remove the errors that aren't replayable - this.set({ errors }); + const conversation = this.getConversation(); + const intendedRecipients = this.get('recipients') || []; + const successfulRecipients = this.get('sent_to') || []; + const currentRecipients = conversation.getRecipients(); - const profileKey = null; - let numbers = retries - .map(retry => retry.number) - .filter(item => Boolean(item)); + const profileKey = conversation.get('profileSharing') + ? storage.get('profileKey') + : null; - if (!numbers.length) { - window.log.warn( - 'retrySend: No numbers in error set, using all recipients' - ); - const conversation = this.getConversation(); - if (conversation) { - numbers = conversation.getRecipients(); - this.set({ errors: null }); - } else { - throw new Error( - 'No numbers in error set, did not find conversation for message' - ); - } + let recipients = _.intersection(intendedRecipients, currentRecipients); + recipients = _.without(recipients, successfulRecipients); + + if (!recipients.length) { + window.log.warn('retrySend: Nobody to send to!'); + + return window.Signal.Data.saveMessage(this.attributes, { + Message: Whisper.Message, + }); } const attachmentsWithData = await Promise.all( (this.get('attachments') || []).map(loadAttachmentData) ); + const { body, attachments } = Whisper.Message.getLongMessageAttachment({ + body: this.get('body'), + attachments: attachmentsWithData, + now: this.get('sent_at'), + }); + const quoteWithData = await loadQuoteData(this.get('quote')); const previewWithData = await loadPreviewData(this.get('preview')); - const conversation = this.getConversation(); - const options = conversation.getSendOptions(); - options.messageType = this.get('type'); + // Special-case the self-send case - we send only a sync message + if (recipients.length === 1 && recipients[0] === this.OUR_NUMBER) { + const [number] = recipients; + const dataMessage = await textsecure.messaging.getMessageProto( + number, + body, + attachments, + quoteWithData, + previewWithData, + this.get('sent_at'), + this.get('expireTimer'), + profileKey + ); + return this.sendSyncMessageOnly(dataMessage); + } let promise; + const options = conversation.getSendOptions(); + options.messageType = this.get('type'); if (conversation.isPrivate()) { - const [number] = numbers; + const [number] = recipients; promise = textsecure.messaging.sendMessageToNumber( number, - this.get('body'), - attachmentsWithData, + body, + attachments, quoteWithData, previewWithData, this.get('sent_at'), @@ -947,10 +1174,10 @@ promise = textsecure.messaging.sendMessage( { - recipients: numbers, - body: this.get('body'), + recipients, + body, timestamp: this.get('sent_at'), - attachments: attachmentsWithData, + attachments, quote: quoteWithData, preview: previewWithData, needsSync: !this.get('synced'), @@ -984,31 +1211,55 @@ // One caller today: ConversationView.forceSend() async resend(number) { const error = this.removeOutgoingErrors(number); - if (error) { - const profileKey = null; - const attachmentsWithData = await Promise.all( - (this.get('attachments') || []).map(loadAttachmentData) - ); - const quoteWithData = await loadQuoteData(this.get('quote')); - const previewWithData = await loadPreviewData(this.get('preview')); + if (!error) { + window.log.warn('resend: requested number was not present in errors'); + return null; + } - const { wrap, sendOptions } = ConversationController.prepareForSend( - number - ); - const promise = textsecure.messaging.sendMessageToNumber( + const profileKey = null; + const attachmentsWithData = await Promise.all( + (this.get('attachments') || []).map(loadAttachmentData) + ); + const { body, attachments } = Whisper.Message.getLongMessageAttachment({ + body: this.get('body'), + attachments: attachmentsWithData, + now: this.get('sent_at'), + }); + + const quoteWithData = await loadQuoteData(this.get('quote')); + const previewWithData = await loadPreviewData(this.get('preview')); + + // Special-case the self-send case - we send only a sync message + if (number === this.OUR_NUMBER) { + const dataMessage = await textsecure.messaging.getMessageProto( number, - this.get('body'), - attachmentsWithData, + body, + attachments, quoteWithData, previewWithData, this.get('sent_at'), this.get('expireTimer'), - profileKey, - sendOptions + profileKey ); - - this.send(wrap(promise)); + return this.sendSyncMessageOnly(dataMessage); } + + const { wrap, sendOptions } = ConversationController.prepareForSend( + number + ); + const promise = textsecure.messaging.sendMessageToNumber( + number, + body, + attachments, + quoteWithData, + previewWithData, + this.get('sent_at'), + this.get('expireTimer'), + profileKey, + sendOptions + ); + + return this.send(wrap(promise)); }, removeOutgoingErrors(number) { const errors = _.partition( @@ -1031,6 +1282,14 @@ // the database. return ConversationController.getUnsafe(this.get('conversationId')); }, + getSourceDeviceConversation() { + // This gets the conversation of the device that sent this message + // while getConversation will return the primary device conversation + return ConversationController.getOrCreateAndWait( + this.get('source'), + 'private' + ); + }, getIncomingContact() { if (!this.isIncoming()) { return null; @@ -1095,7 +1354,9 @@ return null; }, async setCalculatingPoW() { - if (this.calculatingPoW) return; + if (this.calculatingPoW) { + return; + } this.set({ calculatingPoW: true, @@ -1105,11 +1366,29 @@ Message: Whisper.Message, }); }, - async setIsP2p(isP2p) { - if (_.isEqual(this.get('isP2p'), isP2p)) return; + getServerId() { + return this.get('serverId'); + }, + async setServerId(serverId) { + if (_.isEqual(this.get('serverId'), serverId)) { + return; + } + + this.set({ + serverId, + }); + + await window.Signal.Data.saveMessage(this.attributes, { + Message: Whisper.Message, + }); + }, + async setIsPublic(isPublic) { + if (_.isEqual(this.get('isPublic'), isPublic)) { + return; + } this.set({ - isP2p: !!isP2p, + isPublic: !!isPublic, }); await window.Signal.Data.saveMessage(this.attributes, { @@ -1123,7 +1402,7 @@ this.trigger('done'); // This is used by sendSyncMessage, then set to null - if (result.dataMessage) { + if (!this.get('synced') && result.dataMessage) { this.set({ dataMessage: result.dataMessage }); } @@ -1140,7 +1419,13 @@ }); this.trigger('sent', this); - this.sendSyncMessage(); + if (this.get('type') !== 'friend-request') { + const c = this.getConversation(); + // Don't bother sending sync messages to public chats + if (!c.isPublic()) { + this.sendSyncMessage(); + } + } }) .catch(result => { this.trigger('done'); @@ -1224,6 +1509,45 @@ return false; }, + async sendSyncMessageOnly(dataMessage) { + this.set({ dataMessage }); + + try { + this.set({ + // These are the same as a normal send() + sent_to: [this.OUR_NUMBER], + sent: true, + expirationStartTimestamp: Date.now(), + }); + const result = await this.sendSyncMessage(); + this.set({ + // We have to do this afterward, since we didn't have a previous send! + unidentifiedDeliveries: result ? result.unidentifiedDeliveries : null, + + // These are unique to a Note to Self message - immediately read/delivered + delivered_to: [this.OUR_NUMBER], + read_by: [this.OUR_NUMBER], + }); + } catch (result) { + const errors = (result && result.errors) || [ + new Error('Unknown error'), + ]; + this.set({ errors }); + } finally { + await window.Signal.Data.saveMessage(this.attributes, { + Message: Whisper.Message, + }); + this.trigger('done'); + + const errors = this.get('errors'); + if (errors) { + this.trigger('send-error', errors); + } else { + this.trigger('sent'); + } + } + }, + sendSyncMessage() { const ourNumber = textsecure.storage.user.getNumber(); const { wrap, sendOptions } = ConversationController.prepareForSend( @@ -1232,7 +1556,7 @@ ); this.syncPromise = this.syncPromise || Promise.resolve(); - this.syncPromise = this.syncPromise.then(() => { + const next = () => { const dataMessage = this.get('dataMessage'); if (this.get('synced') || !dataMessage) { return Promise.resolve(); @@ -1247,16 +1571,20 @@ this.get('unidentifiedDeliveries'), sendOptions ) - ).then(() => { + ).then(result => { this.set({ synced: true, dataMessage: null, }); return window.Signal.Data.saveMessage(this.attributes, { Message: Whisper.Message, - }); + }).then(() => result); }); - }); + }; + + this.syncPromise = this.syncPromise.then(next, next); + + return this.syncPromise; }, async saveErrors(providedErrors) { @@ -1304,23 +1632,417 @@ ); return !!error; }, - handleDataMessage(dataMessage, confirm) { + async queueAttachmentDownloads() { + const messageId = this.id; + let count = 0; + let bodyPending; + + const [longMessageAttachments, normalAttachments] = _.partition( + this.get('attachments') || [], + attachment => + attachment.contentType === Whisper.Message.LONG_MESSAGE_CONTENT_TYPE + ); + + if (longMessageAttachments.length > 1) { + window.log.error( + `Received more than one long message attachment in message ${this.idForLogging()}` + ); + } + if (longMessageAttachments.length > 0) { + count += 1; + bodyPending = true; + await window.Signal.AttachmentDownloads.addJob( + longMessageAttachments[0], + { + messageId, + type: 'long-message', + index: 0, + } + ); + } + + const attachments = await Promise.all( + normalAttachments.map((attachment, index) => { + count += 1; + return window.Signal.AttachmentDownloads.addJob(attachment, { + messageId, + type: 'attachment', + index, + }); + }) + ); + + const preview = await Promise.all( + (this.get('preview') || []).map(async (item, index) => { + if (!item.image) { + return item; + } + + count += 1; + return { + ...item, + image: await window.Signal.AttachmentDownloads.addJob(item.image, { + messageId, + type: 'preview', + index, + }), + }; + }) + ); + + const contact = await Promise.all( + (this.get('contact') || []).map(async (item, index) => { + if (!item.avatar || !item.avatar.avatar) { + return item; + } + + count += 1; + return { + ...item, + avatar: { + ...item.avatar, + avatar: await window.Signal.AttachmentDownloads.addJob( + item.avatar.avatar, + { + messageId, + type: 'contact', + index, + } + ), + }, + }; + }) + ); + + let quote = this.get('quote'); + if (quote && quote.attachments && quote.attachments.length) { + quote = { + ...quote, + attachments: await Promise.all( + (quote.attachments || []).map(async (item, index) => { + // If we already have a path, then we copied this image from the quoted + // message and we don't need to download the attachment. + if (!item.thumbnail || item.thumbnail.path) { + return item; + } + + count += 1; + return { + ...item, + thumbnail: await window.Signal.AttachmentDownloads.addJob( + item.thumbnail, + { + messageId, + type: 'quote', + index, + } + ), + }; + }) + ), + }; + } + + let group = this.get('group'); + if (group && group.avatar) { + group = { + ...group, + avatar: await window.Signal.AttachmentDownloads.addJob(group.avatar, { + messageId, + type: 'group-avatar', + index: 0, + }), + }; + } + + if (count > 0) { + this.set({ bodyPending, attachments, preview, contact, quote, group }); + + await window.Signal.Data.saveMessage(this.attributes, { + Message: Whisper.Message, + }); + + return true; + } + + return false; + }, + + async copyFromQuotedMessage(message, attemptCount = 1) { + const { quote } = message; + if (!quote) { + return message; + } + + const { attachments, id, author } = quote; + const firstAttachment = attachments[0]; + + const collection = await window.Signal.Data.getMessagesBySentAt(id, { + MessageCollection: Whisper.MessageCollection, + }); + const found = collection.find(item => { + const messageAuthor = item.getContact(); + + return messageAuthor && author === messageAuthor.id; + }); + + if (!found) { + // Exponential backoff, giving up after 5 attempts: + if (attemptCount < 5) { + setTimeout(() => { + window.log.info( + `Looking for the message id : ${id}, attempt: ${attemptCount + 1}` + ); + this.copyFromQuotedMessage(message, attemptCount + 1); + }, attemptCount * attemptCount * 500); + } + + quote.referencedMessageNotFound = true; + return message; + } + + window.log.info(`Found quoted message id: ${id}`); + quote.referencedMessageNotFound = false; + + const queryMessage = MessageController.register(found.id, found); + quote.text = queryMessage.get('body'); + + if (attemptCount > 1) { + // Normally the caller would save the message, but in case we are + // called by a timer, we need to update the message manually + this.set({ quote }); + await window.Signal.Data.saveMessage(this.attributes, { + Message: Whisper.Message, + }); + return null; + } + + if (firstAttachment) { + firstAttachment.thumbnail = null; + } + + if ( + !firstAttachment || + (!window.Signal.Util.GoogleChrome.isImageTypeSupported( + firstAttachment.contentType + ) && + !window.Signal.Util.GoogleChrome.isVideoTypeSupported( + firstAttachment.contentType + )) + ) { + return message; + } + + try { + if ( + queryMessage.get('schemaVersion') < + TypedMessage.VERSION_NEEDED_FOR_DISPLAY + ) { + const upgradedMessage = await upgradeMessageSchema( + queryMessage.attributes + ); + queryMessage.set(upgradedMessage); + await window.Signal.Data.saveMessage(upgradedMessage, { + Message: Whisper.Message, + }); + } + } catch (error) { + window.log.error( + 'Problem upgrading message quoted message from database', + Errors.toLogFormat(error) + ); + return message; + } + + const queryAttachments = queryMessage.get('attachments') || []; + + if (queryAttachments.length > 0) { + const queryFirst = queryAttachments[0]; + const { thumbnail } = queryFirst; + + if (thumbnail && thumbnail.path) { + firstAttachment.thumbnail = { + ...thumbnail, + copied: true, + }; + } + } + + const queryPreview = queryMessage.get('preview') || []; + if (queryPreview.length > 0) { + const queryFirst = queryPreview[0]; + const { image } = queryFirst; + + if (image && image.path) { + firstAttachment.thumbnail = { + ...image, + copied: true, + }; + } + } + + return message; + }, + + async handleDataMessage(initialMessage, confirm) { // This function is called from the background script in a few scenarios: // 1. on an incoming message // 2. on a sent message sync'd from another device // 3. in rare cases, an incoming message can be retried, though it will // still go through one of the previous two codepaths + const ourNumber = textsecure.storage.user.getNumber(); const message = this; const source = message.get('source'); const type = message.get('type'); let conversationId = message.get('conversationId'); - if (dataMessage.group) { - conversationId = dataMessage.group.id; + const authorisation = await libloki.storage.getGrantAuthorisationForSecondaryPubKey( + source + ); + const isGroupMessage = !!initialMessage.group; + if (isGroupMessage) { + conversationId = initialMessage.group.id; + } else if (source !== ourNumber && authorisation) { + // Ignore auth from our devices + conversationId = authorisation.primaryDevicePubKey; } + const GROUP_TYPES = textsecure.protobuf.GroupContext.Type; const conversation = ConversationController.get(conversationId); + + // NOTE: we use friends status to tell if this is + // the creation of the group (initial update) + const newGroup = !conversation.isFriend(); + const knownMembers = conversation.get('members'); + + if (!newGroup && knownMembers) { + const fromMember = knownMembers.includes(source); + + if (!fromMember) { + window.log.warn(`Ignoring group message from non-member: ${source}`); + confirm(); + return null; + } + } + + if ( + initialMessage.group && + initialMessage.group.members && + initialMessage.group.type === GROUP_TYPES.UPDATE + ) { + if (newGroup) { + conversation.updateGroupAdmins(initialMessage.group.admins); + + conversation.setFriendRequestStatus( + window.friends.friendRequestStatusEnum.friends + ); + } + + const fromAdmin = conversation.get('groupAdmins').includes(source); + + if (!fromAdmin) { + // Make sure the message is not removing members / renaming the group + const nameChanged = + conversation.get('name') !== initialMessage.group.name; + + if (nameChanged) { + window.log.warn( + 'Non-admin attempts to change the name of the group' + ); + } + + const membersMissing = + _.difference( + conversation.get('members'), + initialMessage.group.members + ).length > 0; + + if (membersMissing) { + window.log.warn('Non-admin attempts to remove group members'); + } + + const messageAllowed = !nameChanged && !membersMissing; + + if (!messageAllowed) { + confirm(); + return null; + } + } + // For every member, see if we need to establish a session: + initialMessage.group.members.forEach(memberPubKey => { + const haveSession = _.some( + textsecure.storage.protocol.sessions, + s => s.number === memberPubKey + ); + + const ourPubKey = textsecure.storage.user.getNumber(); + if (!haveSession && memberPubKey !== ourPubKey) { + ConversationController.getOrCreateAndWait( + memberPubKey, + 'private' + ).then(() => { + textsecure.messaging.sendMessageToNumber( + memberPubKey, + '(If you see this message, you must be using an out-of-date client)', + [], + undefined, + [], + Date.now(), + undefined, + undefined, + { messageType: 'friend-request', sessionRequest: true } + ); + }); + } + }); + } + + const isSessionRequest = + initialMessage.flags === + textsecure.protobuf.DataMessage.Flags.SESSION_REQUEST; + + if ( + // eslint-disable-next-line no-bitwise + initialMessage.flags & + textsecure.protobuf.DataMessage.Flags.SESSION_RESTORE + ) { + // Show that the session reset is "in progress" even though we had a valid session + this.set({ endSessionType: 'ongoing' }); + } + + if (message.isFriendRequest() && isSessionRequest) { + // Check if the contact is a member in one of our private groups: + const groupMember = window + .getConversations() + .models.filter(c => c.get('members')) + .reduce((acc, x) => window.Lodash.concat(acc, x.get('members')), []) + .includes(source); + + if (groupMember) { + window.log.info( + `Auto accepting a 'group' friend request for a known group member: ${groupMember}` + ); + + window.libloki.api.sendBackgroundMessage(message.get('source')); + + confirm(); + } + + // Wether or not we accepted the FR, we exit early so background friend requests + // cannot be used for establishing regular private conversations + return null; + } + return conversation.queueJob(async () => { + window.log.info( + `Starting handleDataMessage for message ${message.idForLogging()} in conversation ${conversation.idForLogging()}` + ); + + const withQuoteReference = await this.copyFromQuotedMessage( + initialMessage + ); + const dataMessage = await upgradeMessageSchema(withQuoteReference); + try { const now = new Date().getTime(); let attributes = { @@ -1337,40 +2059,49 @@ attributes = { ...attributes, name: dataMessage.group.name, - members: _.union( - dataMessage.group.members, - conversation.get('members') - ), + members: dataMessage.group.members, }; - // Update this group conversations's avatar on disk if it has changed. - if (dataMessage.group.avatar) { - attributes = await window.Signal.Types.Conversation.maybeUpdateAvatar( - attributes, - dataMessage.group.avatar.data, - { - writeNewAttachmentData, - deleteAttachmentData, - } - ); - } - groupUpdate = conversation.changedAttributes( _.pick(dataMessage.group, 'name', 'avatar') ) || {}; - const difference = _.difference( + const addedMembers = _.difference( attributes.members, conversation.get('members') ); - if (difference.length > 0) { - groupUpdate.joined = difference; + if (addedMembers.length > 0) { + groupUpdate.joined = addedMembers; } if (conversation.get('left')) { + // TODO: Maybe we shouldn't assume this message adds us: + // we could maybe still get this message by mistake window.log.warn('re-added to a left group'); attributes.left = false; } + + if (attributes.isKickedFromGroup) { + // Assume somebody re-invited us since we received this update + attributes.isKickedFromGroup = false; + } + + // Check if anyone got kicked: + const removedMembers = _.difference( + conversation.get('members'), + attributes.members + ); + + if (removedMembers.length > 0) { + if ( + removedMembers.includes(textsecure.storage.user.getNumber()) + ) { + groupUpdate.kicked = 'You'; + attributes.isKickedFromGroup = true; + } else { + groupUpdate.kicked = removedMembers; + } + } } else if (dataMessage.group.type === GROUP_TYPES.QUIT) { if (source === textsecure.storage.user.getNumber()) { attributes.left = true; @@ -1389,6 +2120,10 @@ } } + if (initialMessage.groupInvitation) { + message.set({ groupInvitation: initialMessage.groupInvitation }); + } + const urls = window.Signal.LinkPreviews.findLinks(dataMessage.body); const incomingPreview = dataMessage.preview || []; const preview = incomingPreview.filter( @@ -1420,9 +2155,6 @@ schemaVersion: dataMessage.schemaVersion, }); - // Update the previews if we need to - message.updatePreview(); - if (type === 'outgoing') { const receipts = Whisper.DeliveryReceipts.forMessage( conversation, @@ -1440,6 +2172,9 @@ attributes.active_at = now; conversation.set(attributes); + // Re-enable typing if re-joined the group + conversation.updateTextInputState(); + if (message.isExpirationTimerUpdate()) { message.set({ expirationTimerUpdate: { @@ -1517,10 +2252,17 @@ c.onReadMessage(message); } } else { - conversation.set( - 'unreadCount', - conversation.get('unreadCount') + 1 - ); + if ( + message.attributes.body && + message.attributes.body.indexOf(`@${ourNumber}`) !== -1 + ) { + conversation.set({ mentionedUs: true }); + } + + conversation.set({ + unreadCount: conversation.get('unreadCount') + 1, + isArchived: false, + }); } } @@ -1536,6 +2278,14 @@ }); } + // A sync'd message to ourself is automatically considered read and delivered + if (conversation.isMe()) { + message.set({ + read_by: conversation.getRecipients(), + delivered_to: conversation.getRecipients(), + }); + } + message.set({ recipients: conversation.getRecipients() }); } @@ -1550,6 +2300,10 @@ }); } + const sendingDeviceConversation = await ConversationController.getOrCreateAndWait( + source, + 'private' + ); if (dataMessage.profileKey) { const profileKey = dataMessage.profileKey.toString('base64'); if (source === textsecure.storage.user.getNumber()) { @@ -1557,34 +2311,60 @@ } else if (conversation.isPrivate()) { conversation.setProfileKey(profileKey); } else { - ConversationController.getOrCreateAndWait(source, 'private').then( - sender => { - sender.setProfileKey(profileKey); - } - ); + sendingDeviceConversation.setProfileKey(profileKey); } } let autoAccept = false; - if (message.get('type') === 'friend-request') { - if (conversation.hasSentFriendRequest()) { - // Automatically accept incoming friend requests if we have send one already - autoAccept = true; - message.set({ friendStatus: 'accepted' }); - await conversation.onFriendRequestAccepted(); - window.libloki.api.sendFriendRequestAccepted( - message.get('source') - ); - } else if (!conversation.isFriend()) { - await conversation.onFriendRequestReceived(); + // Make sure friend request logic doesn't trigger on messages aimed at groups + if (!isGroupMessage) { + if (message.get('type') === 'friend-request') { + /* + Here is the before and after state diagram for the operation before. + + None -> RequestReceived + PendingSend -> RequestReceived + RequestReceived -> RequestReceived + Sent -> Friends + Expired -> Friends + Friends -> Friends + + The cases where we auto accept are the following: + - We sent the user a friend request, + and that user sent us a friend request. + - We are friends with the user, + and that user just sent us a friend request. + */ + const isFriend = sendingDeviceConversation.isFriend(); + const hasSentFriendRequest = sendingDeviceConversation.hasSentFriendRequest(); + autoAccept = isFriend || hasSentFriendRequest; + + if (autoAccept) { + message.set({ friendStatus: 'accepted' }); + } + + if (isFriend) { + window.Whisper.events.trigger('endSession', source); + } else if (hasSentFriendRequest) { + await sendingDeviceConversation.onFriendRequestAccepted(); + } else { + await sendingDeviceConversation.onFriendRequestReceived(); + } + } else if (message.get('type') !== 'outgoing') { + // Ignore 'outgoing' messages because they are sync messages + await sendingDeviceConversation.onFriendRequestAccepted(); } - } else { - await conversation.onFriendRequestAccepted(); } const id = await window.Signal.Data.saveMessage(message.attributes, { Message: Whisper.Message, }); message.set({ id }); + MessageController.register(message.id, message); + + // Note that this can save the message again, if jobs were queued. We need to + // call it after we have an id for this message, because the jobs refer back + // to their source message. + await message.queueAttachmentDownloads(); await window.Signal.Data.updateConversation( conversationId, @@ -1629,9 +2409,11 @@ if (message.get('unread')) { // Need to do this here because the conversation has already changed states - if (autoAccept) + if (autoAccept) { await conversation.notifyFriendRequest(source, 'accepted'); - else await conversation.notify(message); + } else { + await conversation.notify(message); + } } confirm(); @@ -1709,17 +2491,42 @@ }, }); + // Receive will be enabled before we enable send + Whisper.Message.LONG_MESSAGE_CONTENT_TYPE = 'text/x-signal-plain'; + + Whisper.Message.getLongMessageAttachment = ({ body, attachments, now }) => { + if (body.length <= 2048) { + return { + body, + attachments, + }; + } + + const data = bytesFromString(body); + const attachment = { + contentType: Whisper.Message.LONG_MESSAGE_CONTENT_TYPE, + fileName: `long-message-${now}.txt`, + data, + size: data.byteLength, + }; + + return { + body: body.slice(0, 2048), + attachments: [attachment, ...attachments], + }; + }; + Whisper.Message.refreshExpirationTimer = () => Whisper.ExpiringMessagesListener.update(); Whisper.MessageCollection = Backbone.Collection.extend({ model: Whisper.Message, comparator(left, right) { - if (left.get('received_at') === right.get('received_at')) { - return (left.get('sent_at') || 0) - (right.get('sent_at') || 0); + if (left.get('sent_at') === right.get('sent_at')) { + return (left.get('received_at') || 0) - (right.get('received_at') || 0); } - return (left.get('received_at') || 0) - (right.get('received_at') || 0); + return (left.get('sent_at') || 0) - (right.get('sent_at') || 0); }, initialize(models, options) { if (options) { @@ -1761,7 +2568,9 @@ } ); - const models = messages.filter(message => Boolean(message.id)); + const models = messages + .filter(message => Boolean(message.id)) + .map(message => MessageController.register(message.id, message)); const eliminated = messages.length - models.length; if (eliminated > 0) { window.log.warn( diff --git a/js/models/profile.js b/js/models/profile.js deleted file mode 100644 index 6a92504d44..0000000000 --- a/js/models/profile.js +++ /dev/null @@ -1,58 +0,0 @@ -/* global storage, _ */ -/* global storage: false */ - -/* eslint-disable more/no-then */ - -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - window.Whisper = window.Whisper || {}; - - const PROFILE_ID = 'local-profile'; - - storage.getLocalProfile = () => { - const profile = storage.get(PROFILE_ID, null); - return profile; - }; - - storage.setProfileName = async newName => { - if (typeof newName !== 'string' && newName !== null) { - throw Error('Name must be a string!'); - } - - // Update our profiles accordingly' - const trimmed = newName && newName.trim(); - - // If we get an empty name then unset the name property - // Otherwise update it - const profile = storage.getLocalProfile(); - const newProfile = profile || {}; - if (_.isEmpty(trimmed)) { - delete newProfile.name; - } else { - newProfile.name = { - displayName: trimmed, - }; - } - - await storage.saveLocalProfile(newProfile); - }; - - storage.saveLocalProfile = async profile => { - const storedProfile = storage.get(PROFILE_ID, null); - - // Only store the profile if we have a different object - if (storedProfile && _.isEqual(storedProfile, profile)) { - return; - } - - window.log.info('saving local profile ', profile); - await storage.put(PROFILE_ID, profile); - }; - - storage.removeLocalProfile = async () => { - window.log.info('removing local profile'); - await storage.remove(PROFILE_ID); - }; -})(); diff --git a/js/modules/.eslintrc b/js/modules/.eslintrc index b540ae7dfd..7052037946 100644 --- a/js/modules/.eslintrc +++ b/js/modules/.eslintrc @@ -5,7 +5,8 @@ "node": false }, "globals": { - "console": true + "console": true, + "setTimeout": true }, "parserOptions": { "sourceType": "module" diff --git a/js/modules/attachment_downloads.js b/js/modules/attachment_downloads.js new file mode 100644 index 0000000000..e4356dc839 --- /dev/null +++ b/js/modules/attachment_downloads.js @@ -0,0 +1,388 @@ +/* global Whisper, Signal, setTimeout, clearTimeout, MessageController */ + +const { isFunction, isNumber, omit } = require('lodash'); +const getGuid = require('uuid/v4'); +const { + getMessageById, + getNextAttachmentDownloadJobs, + removeAttachmentDownloadJob, + resetAttachmentDownloadPending, + saveAttachmentDownloadJob, + saveMessage, + setAttachmentDownloadJobPending, +} = require('./data'); +const { stringFromBytes } = require('./crypto'); + +module.exports = { + start, + stop, + addJob, +}; + +const MAX_ATTACHMENT_JOB_PARALLELISM = 3; + +const SECOND = 1000; +const MINUTE = 60 * SECOND; +const HOUR = 60 * MINUTE; +const TICK_INTERVAL = MINUTE; + +const RETRY_BACKOFF = { + 1: 30 * SECOND, + 2: 30 * MINUTE, + 3: 6 * HOUR, +}; + +let enabled = false; +let timeout; +let getMessageReceiver; +let logger; +const _activeAttachmentDownloadJobs = {}; + +async function start(options = {}) { + ({ getMessageReceiver, logger } = options); + if (!isFunction(getMessageReceiver)) { + throw new Error( + 'attachment_downloads/start: getMessageReceiver must be a function' + ); + } + if (!logger) { + throw new Error('attachment_downloads/start: logger must be provided!'); + } + + enabled = true; + await resetAttachmentDownloadPending(); + + _tick(); +} + +async function stop() { + enabled = false; + if (timeout) { + clearTimeout(timeout); + timeout = null; + } +} + +async function addJob(attachment, job = {}) { + if (!attachment) { + throw new Error('attachments_download/addJob: attachment is required'); + } + + const { messageId, type, index } = job; + if (!messageId) { + throw new Error('attachments_download/addJob: job.messageId is required'); + } + if (!type) { + throw new Error('attachments_download/addJob: job.type is required'); + } + if (!isNumber(index)) { + throw new Error('attachments_download/addJob: index must be a number'); + } + + const id = getGuid(); + const timestamp = Date.now(); + const toSave = { + ...job, + id, + attachment, + timestamp, + pending: 0, + attempts: 0, + }; + + await saveAttachmentDownloadJob(toSave); + + _maybeStartJob(); + + return { + ...attachment, + pending: true, + downloadJobId: id, + }; +} + +async function _tick() { + _maybeStartJob(); + timeout = setTimeout(_tick, TICK_INTERVAL); +} + +async function _maybeStartJob() { + if (!enabled) { + return; + } + + const jobCount = getActiveJobCount(); + const limit = MAX_ATTACHMENT_JOB_PARALLELISM - jobCount; + if (limit <= 0) { + return; + } + + const nextJobs = await getNextAttachmentDownloadJobs(limit); + if (nextJobs.length <= 0) { + return; + } + + // To prevent the race condition caused by two parallel database calls, eached kicked + // off because the jobCount wasn't at the max. + const secondJobCount = getActiveJobCount(); + const needed = MAX_ATTACHMENT_JOB_PARALLELISM - secondJobCount; + if (needed <= 0) { + return; + } + + const jobs = nextJobs.slice(0, Math.min(needed, nextJobs.length)); + for (let i = 0, max = jobs.length; i < max; i += 1) { + const job = jobs[i]; + _activeAttachmentDownloadJobs[job.id] = _runJob(job); + } +} + +async function _runJob(job) { + const { id, messageId, attachment, type, index, attempts } = job || {}; + let message; + + try { + if (!job || !attachment || !messageId) { + throw new Error( + `_runJob: Key information required for job was missing. Job id: ${id}` + ); + } + + const found = await getMessageById(messageId, { + Message: Whisper.Message, + }); + if (!found) { + logger.error('_runJob: Source message not found, deleting job'); + await _finishJob(null, id); + return; + } + message = MessageController.register(found.id, found); + + const pending = true; + await setAttachmentDownloadJobPending(id, pending); + + let downloaded; + const messageReceiver = getMessageReceiver(); + if (!messageReceiver) { + throw new Error('_runJob: messageReceiver not found'); + } + + try { + downloaded = await messageReceiver.downloadAttachment(attachment); + } catch (error) { + // Attachments on the server expire after 30 days, then start returning 404 + if (error && error.code === 404) { + logger.warn( + `_runJob: Got 404 from server, marking attachment ${ + attachment.id + } from message ${message.idForLogging()} as permanent error` + ); + + await _finishJob(message, id); + await _addAttachmentToMessage( + message, + _markAttachmentAsError(attachment), + { type, index } + ); + + return; + } + throw error; + } + + const upgradedAttachment = await Signal.Migrations.processNewAttachment( + downloaded + ); + + await _addAttachmentToMessage(message, upgradedAttachment, { type, index }); + + await _finishJob(message, id); + } catch (error) { + const currentAttempt = (attempts || 0) + 1; + + if (currentAttempt >= 3) { + logger.error( + `_runJob: ${currentAttempt} failed attempts, marking attachment ${id} from message ${message.idForLogging()} as permament error:`, + error && error.stack ? error.stack : error + ); + + await _finishJob(message, id); + await _addAttachmentToMessage( + message, + _markAttachmentAsError(attachment), + { type, index } + ); + + return; + } + + logger.error( + `_runJob: Failed to download attachment type ${type} for message ${message.idForLogging()}, attempt ${currentAttempt}:`, + error && error.stack ? error.stack : error + ); + + const failedJob = { + ...job, + pending: 0, + attempts: currentAttempt, + timestamp: Date.now() + RETRY_BACKOFF[currentAttempt], + }; + + await saveAttachmentDownloadJob(failedJob); + delete _activeAttachmentDownloadJobs[id]; + _maybeStartJob(); + } +} + +async function _finishJob(message, id) { + if (message) { + await saveMessage(message.attributes, { + Message: Whisper.Message, + }); + const conversation = message.getConversation(); + if (conversation) { + const fromConversation = conversation.messageCollection.get(message.id); + + if (fromConversation && message !== fromConversation) { + fromConversation.set(message.attributes); + fromConversation.trigger('change', fromConversation); + } else { + message.trigger('change', message); + } + } + } + + await removeAttachmentDownloadJob(id); + delete _activeAttachmentDownloadJobs[id]; + _maybeStartJob(); +} + +function getActiveJobCount() { + return Object.keys(_activeAttachmentDownloadJobs).length; +} + +function _markAttachmentAsError(attachment) { + return { + ...omit(attachment, ['key', 'digest', 'id']), + error: true, + }; +} + +async function _addAttachmentToMessage(message, attachment, { type, index }) { + if (!message) { + return; + } + + const logPrefix = `${message.idForLogging()} (type: ${type}, index: ${index})`; + + if (type === 'long-message') { + try { + const { data } = await Signal.Migrations.loadAttachmentData(attachment); + message.set({ + body: attachment.isError ? message.get('body') : stringFromBytes(data), + bodyPending: false, + }); + } finally { + Signal.Migrations.deleteAttachmentData(attachment.path); + } + return; + } + + if (type === 'attachment') { + const attachments = message.get('attachments'); + if (!attachments || attachments.length <= index) { + throw new Error( + `_addAttachmentToMessage: attachments didn't exist or ${index} was too large` + ); + } + _replaceAttachment(attachments, index, attachment, logPrefix); + return; + } + + if (type === 'preview') { + const preview = message.get('preview'); + if (!preview || preview.length <= index) { + throw new Error( + `_addAttachmentToMessage: preview didn't exist or ${index} was too large` + ); + } + const item = preview[index]; + if (!item) { + throw new Error(`_addAttachmentToMessage: preview ${index} was falsey`); + } + _replaceAttachment(item, 'image', attachment, logPrefix); + return; + } + + if (type === 'contact') { + const contact = message.get('contact'); + if (!contact || contact.length <= index) { + throw new Error( + `_addAttachmentToMessage: contact didn't exist or ${index} was too large` + ); + } + const item = contact[index]; + if (item && item.avatar && item.avatar.avatar) { + _replaceAttachment(item.avatar, 'avatar', attachment, logPrefix); + } else { + logger.warn( + `_addAttachmentToMessage: Couldn't update contact with avatar attachment for message ${message.idForLogging()}` + ); + } + + return; + } + + if (type === 'quote') { + const quote = message.get('quote'); + if (!quote) { + throw new Error("_addAttachmentToMessage: quote didn't exist"); + } + const { attachments } = quote; + if (!attachments || attachments.length <= index) { + throw new Error( + `_addAttachmentToMessage: quote attachments didn't exist or ${index} was too large` + ); + } + + const item = attachments[index]; + if (!item) { + throw new Error( + `_addAttachmentToMessage: attachment ${index} was falsey` + ); + } + _replaceAttachment(item, 'thumbnail', attachment, logPrefix); + return; + } + + if (type === 'group-avatar') { + const group = message.get('group'); + if (!group) { + throw new Error("_addAttachmentToMessage: group didn't exist"); + } + + const existingAvatar = group.avatar; + if (existingAvatar && existingAvatar.path) { + await Signal.Migrations.deleteAttachmentData(existingAvatar.path); + } + + _replaceAttachment(group, 'avatar', attachment, logPrefix); + return; + } + + throw new Error( + `_addAttachmentToMessage: Unknown job type ${type} for message ${message.idForLogging()}` + ); +} + +function _replaceAttachment(object, key, newAttachment, logPrefix) { + const oldAttachment = object[key]; + if (oldAttachment && oldAttachment.path) { + logger.warn( + `_replaceAttachment: ${logPrefix} - old attachment already had path, not replacing` + ); + } + + // eslint-disable-next-line no-param-reassign + object[key] = newAttachment; +} diff --git a/js/modules/backup.js b/js/modules/backup.js index b486a6e10d..571b0e57d4 100644 --- a/js/modules/backup.js +++ b/js/modules/backup.js @@ -109,9 +109,9 @@ function createOutputStream(writer) { }; } -async function exportContactAndGroupsToFile(parent) { +async function exportConversationListToFile(parent) { const writer = await createFileAndWriter(parent, 'db.json'); - return exportContactsAndGroups(writer); + return exportConversationList(writer); } function writeArray(stream, array) { @@ -137,7 +137,7 @@ function getPlainJS(collection) { return collection.map(model => model.attributes); } -async function exportContactsAndGroups(fileWriter) { +async function exportConversationList(fileWriter) { const stream = createOutputStream(fileWriter); stream.write('{'); @@ -149,13 +149,6 @@ async function exportContactsAndGroups(fileWriter) { window.log.info(`Exporting ${conversations.length} conversations`); writeArray(stream, getPlainJS(conversations)); - stream.write(','); - - stream.write('"groups": '); - const groups = await window.Signal.Data.getAllGroups(); - window.log.info(`Exporting ${groups.length} groups`); - writeArray(stream, groups); - stream.write('}'); await stream.close(); } @@ -167,7 +160,7 @@ async function importNonMessages(parent, options) { } function eliminateClientConfigInBackup(data, targetPath) { - const cleaned = _.pick(data, 'conversations', 'groups'); + const cleaned = _.pick(data, 'conversations'); window.log.info('Writing configuration-free backup file back to disk'); try { fs.writeFileSync(targetPath, JSON.stringify(cleaned)); @@ -223,10 +216,8 @@ async function importFromJsonString(jsonString, targetPath, options) { _.defaults(options, { forceLightImport: false, conversationLookup: {}, - groupLookup: {}, }); - const { groupLookup } = options; const result = { fullImport: true, }; @@ -251,7 +242,7 @@ async function importFromJsonString(jsonString, targetPath, options) { // We mutate the on-disk backup to prevent the user from importing client // configuration more than once - that causes lots of encryption errors. - // This of course preserves the true data: conversations and groups. + // This of course preserves the true data: conversations. eliminateClientConfigInBackup(importObject, targetPath); const storeNames = _.keys(importObject); @@ -262,12 +253,12 @@ async function importFromJsonString(jsonString, targetPath, options) { const remainingStoreNames = _.without( storeNames, 'conversations', - 'unprocessed' + 'unprocessed', + 'groups' // in old data sets, but no longer included in database schema ); await importConversationsFromJSON(conversations, options); const SAVE_FUNCTIONS = { - groups: window.Signal.Data.createOrUpdateGroup, identityKeys: window.Signal.Data.createOrUpdateIdentityKey, items: window.Signal.Data.createOrUpdateItem, preKeys: window.Signal.Data.createOrUpdatePreKey, @@ -292,29 +283,17 @@ async function importFromJsonString(jsonString, targetPath, options) { return; } - let skipCount = 0; - for (let i = 0, max = toImport.length; i < max; i += 1) { const toAdd = unstringify(toImport[i]); - - const haveGroupAlready = - storeName === 'groups' && groupLookup[getGroupKey(toAdd)]; - - if (haveGroupAlready) { - skipCount += 1; - } else { - // eslint-disable-next-line no-await-in-loop - await save(toAdd); - } + // eslint-disable-next-line no-await-in-loop + await save(toAdd); } window.log.info( 'Done importing to store', storeName, 'Total count:', - toImport.length, - 'Skipped:', - skipCount + toImport.length ); }) ); @@ -1160,14 +1139,6 @@ async function loadConversationLookup() { return fromPairs(map(array, item => [getConversationKey(item), true])); } -function getGroupKey(group) { - return group.id; -} -async function loadGroupsLookup() { - const array = await window.Signal.Data.getAllGroupIds(); - return fromPairs(map(array, item => [getGroupKey(item), true])); -} - function getDirectoryForExport() { return getDirectory(); } @@ -1233,13 +1204,17 @@ function createTempDir() { } function deleteAll(pattern) { - window.log.info(`Deleting ${pattern}`); return pify(rimraf)(pattern); } const ARCHIVE_NAME = 'messages.tar.gz'; async function exportToDirectory(directory, options) { + const env = window.getEnvironment(); + if (env !== 'test') { + throw new Error('export is only supported in test mode'); + } + options = options || {}; if (!options.key) { @@ -1254,7 +1229,7 @@ async function exportToDirectory(directory, options) { const attachmentsDir = await createDirectory(directory, 'attachments'); - await exportContactAndGroupsToFile(stagingDir); + await exportConversationListToFile(stagingDir); await exportConversations( Object.assign({}, options, { messagesDir: stagingDir, @@ -1298,17 +1273,20 @@ async function importFromDirectory(directory, options) { const lookups = await Promise.all([ loadMessagesLookup(), loadConversationLookup(), - loadGroupsLookup(), ]); - const [messageLookup, conversationLookup, groupLookup] = lookups; + const [messageLookup, conversationLookup] = lookups; options = Object.assign({}, options, { messageLookup, conversationLookup, - groupLookup, }); const archivePath = path.join(directory, ARCHIVE_NAME); if (fs.existsSync(archivePath)) { + const env = window.getEnvironment(); + if (env !== 'test') { + throw new Error('import is only supported in test mode'); + } + // we're in the world of an encrypted, zipped backup if (!options.key) { throw new Error( diff --git a/js/modules/data.d.ts b/js/modules/data.d.ts new file mode 100644 index 0000000000..29e42df982 --- /dev/null +++ b/js/modules/data.d.ts @@ -0,0 +1,3 @@ +export function searchMessages(query: string): Promise>; +export function searchConversations(query: string): Promise>; +export function getPrimaryDeviceFor(pubKey: string): Promise; diff --git a/js/modules/data.js b/js/modules/data.js index 9fb5d1f07f..729f8c60e6 100644 --- a/js/modules/data.js +++ b/js/modules/data.js @@ -1,7 +1,9 @@ -/* global window, setTimeout, IDBKeyRange */ +/* global window, setTimeout, clearTimeout, IDBKeyRange, dcodeIO */ const electron = require('electron'); +// TODO: this results in poor readability, would be +// much better to explicitly call with `_`. const { cloneDeep, forEach, @@ -9,11 +11,13 @@ const { isFunction, isObject, map, - merge, set, omit, + isArrayBuffer, } = require('lodash'); +const _ = require('lodash'); + const { base64ToArrayBuffer, arrayBufferToBase64 } = require('./crypto'); const MessageType = require('./types/message'); @@ -47,22 +51,14 @@ module.exports = { close, removeDB, removeIndexedDBFiles, - getPasswordHash, - createOrUpdateGroup, - getGroupById, - getAllGroupIds, - getAllGroups, - bulkAddGroups, - removeGroupById, - removeAllGroups, - createOrUpdateIdentityKey, getIdentityKeyById, bulkAddIdentityKeys, removeIdentityKeyById, removeAllIdentityKeys, + getAllIdentityKeys, createOrUpdatePreKey, getPreKeyById, @@ -70,6 +66,7 @@ module.exports = { bulkAddPreKeys, removePreKeyById, removeAllPreKeys, + getAllPreKeys, createOrUpdateSignedPreKey, getSignedPreKeyById, @@ -95,6 +92,15 @@ module.exports = { removeContactSignedPreKeyByIdentityKey, removeAllContactSignedPreKeys, + createOrUpdatePairingAuthorisation, + removePairingAuthorisationForSecondaryPubKey, + getGrantAuthorisationForSecondaryPubKey, + getAuthorisationForSecondaryPubKey, + getGrantAuthorisationsForPrimaryPubKey, + getSecondaryDevicesFor, + getPrimaryDeviceFor, + getPairedDevicesFor, + createOrUpdateItem, getItemById, getAllItems, @@ -109,6 +115,7 @@ module.exports = { removeSessionById, removeSessionsByNumber, removeAllSessions, + getAllSessions, getSwarmNodesByPubkey, @@ -122,10 +129,20 @@ module.exports = { getAllConversations, getPubKeysWithFriendStatus, + getConversationsWithFriendStatus, getAllConversationIds, getAllPrivateConversations, + getAllRssFeedConversations, + getAllPublicConversations, + getPublicConversationsByServer, + getPubkeysInPublicConversation, + savePublicServerToken, + getPublicServerTokenByServerUrl, getAllGroupsInvolvingId, + searchConversations, + searchMessages, + searchMessagesInConversation, getMessageCount, saveMessage, @@ -143,6 +160,7 @@ module.exports = { removeAllMessagesInConversation, getMessageBySender, + getMessageByServerId, getMessageById, getAllMessages, getAllUnsentMessages, @@ -160,11 +178,22 @@ module.exports = { getUnprocessedById, saveUnprocessed, saveUnprocesseds, + updateUnprocessedAttempts, + updateUnprocessedWithData, removeUnprocessed, removeAllUnprocessed, + getNextAttachmentDownloadJobs, + saveAttachmentDownloadJob, + resetAttachmentDownloadPending, + setAttachmentDownloadJobPending, + removeAttachmentDownloadJob, + removeAllAttachmentDownloadJobs, + removeAll, removeAllConfiguration, + removeAllConversations, + removeAllPrivateConversations, removeOtherData, cleanupOrphanedAttachments, @@ -271,13 +300,13 @@ function _updateJob(id, data) { ...data, resolve: value => { _removeJob(id); - const end = Date.now(); - const delta = end - start; - if (delta > 10) { - window.log.debug( - `SQL channel job ${id} (${fnName}) succeeded in ${end - start}ms` - ); - } + // const end = Date.now(); + // const delta = end - start; + // if (delta > 10) { + // window.log.debug( + // `SQL channel job ${id} (${fnName}) succeeded in ${end - start}ms` + // ); + // } return resolve(value); }, reject: error => { @@ -297,6 +326,11 @@ function _removeJob(id) { return; } + if (_jobs[id].timer) { + clearTimeout(_jobs[id].timer); + _jobs[id].timer = null; + } + delete _jobs[id]; if (_shutdownCallback) { @@ -348,7 +382,7 @@ function makeChannel(fnName) { args: _DEBUG ? args : null, }); - setTimeout( + _jobs[jobId].timer = setTimeout( () => reject(new Error(`SQL channel job ${jobId} (${fnName}) timed out`)), DATABASE_UPDATE_TIMEOUT @@ -421,33 +455,6 @@ async function getPasswordHash() { return channels.getPasswordHash(); } -// Groups - -async function createOrUpdateGroup(data) { - await channels.createOrUpdateGroup(data); -} -async function getGroupById(id) { - const group = await channels.getGroupById(id); - return group; -} -async function getAllGroupIds() { - const ids = await channels.getAllGroupIds(); - return ids; -} -async function getAllGroups() { - const groups = await channels.getAllGroups(); - return groups; -} -async function bulkAddGroups(array) { - await channels.bulkAddGroups(array); -} -async function removeGroupById(id) { - await channels.removeGroupById(id); -} -async function removeAllGroups() { - await channels.removeAllGroups(); -} - // Identity Keys const IDENTITY_KEY_KEYS = ['publicKey']; @@ -471,6 +478,10 @@ async function removeIdentityKeyById(id) { async function removeAllIdentityKeys() { await channels.removeAllIdentityKeys(); } +async function getAllIdentityKeys() { + const keys = await channels.getAllIdentityKeys(); + return keys.map(key => keysToArrayBuffer(IDENTITY_KEY_KEYS, key)); +} // Pre Keys @@ -496,6 +507,10 @@ async function removePreKeyById(id) { async function removeAllPreKeys() { await channels.removeAllPreKeys(); } +async function getAllPreKeys() { + const keys = await channels.getAllPreKeys(); + return keys.map(key => keysToArrayBuffer(PRE_KEY_KEYS, key)); +} // Signed Pre Keys @@ -510,7 +525,7 @@ async function getSignedPreKeyById(id) { } async function getAllSignedPreKeys() { const keys = await channels.getAllSignedPreKeys(); - return keys; + return keys.map(key => keysToArrayBuffer(PRE_KEY_KEYS, key)); } async function bulkAddSignedPreKeys(array) { const updated = map(array, data => keysFromArrayBuffer(PRE_KEY_KEYS, data)); @@ -583,6 +598,63 @@ async function removeAllContactSignedPreKeys() { await channels.removeAllContactSignedPreKeys(); } +function signatureToBase64(signature) { + if (signature.constructor === dcodeIO.ByteBuffer) { + return dcodeIO.ByteBuffer.wrap(signature).toString('base64'); + } else if (isArrayBuffer(signature)) { + return arrayBufferToBase64(signature); + } else if (typeof signature === 'string') { + // assume it's already base64 + return signature; + } + throw new Error( + 'Invalid signature provided in createOrUpdatePairingAuthorisation. Needs to be either ArrayBuffer or ByteBuffer.' + ); +} + +async function createOrUpdatePairingAuthorisation(data) { + const { requestSignature, grantSignature } = data; + + return channels.createOrUpdatePairingAuthorisation({ + ...data, + requestSignature: signatureToBase64(requestSignature), + grantSignature: grantSignature ? signatureToBase64(grantSignature) : null, + }); +} + +async function removePairingAuthorisationForSecondaryPubKey(pubKey) { + if (!pubKey) { + return; + } + await channels.removePairingAuthorisationForSecondaryPubKey(pubKey); +} + +async function getGrantAuthorisationForSecondaryPubKey(pubKey) { + return channels.getAuthorisationForSecondaryPubKey(pubKey, { + granted: true, + }); +} + +async function getGrantAuthorisationsForPrimaryPubKey(pubKey) { + return channels.getGrantAuthorisationsForPrimaryPubKey(pubKey); +} + +function getAuthorisationForSecondaryPubKey(pubKey) { + return channels.getAuthorisationForSecondaryPubKey(pubKey); +} + +function getSecondaryDevicesFor(primaryDevicePubKey) { + return channels.getSecondaryDevicesFor(primaryDevicePubKey); +} + +function getPrimaryDeviceFor(secondaryDevicePubKey) { + return channels.getPrimaryDeviceFor(secondaryDevicePubKey); +} + +function getPairedDevicesFor(pubKey) { + return channels.getPairedDevicesFor(pubKey); +} + // Items const ITEM_KEYS = { @@ -662,18 +734,13 @@ async function removeSessionsByNumber(number) { async function removeAllSessions(id) { await channels.removeAllSessions(id); } +async function getAllSessions(id) { + const sessions = await channels.getAllSessions(id); + return sessions; +} // Conversation -function setifyProperty(data, propertyName) { - if (!data) return data; - const returnData = { ...data }; - if (Array.isArray(returnData[propertyName])) { - returnData[propertyName] = new Set(returnData[propertyName]); - } - return returnData; -} - async function getSwarmNodesByPubkey(pubkey) { return channels.getSwarmNodesByPubkey(pubkey); } @@ -702,13 +769,14 @@ async function updateConversation(id, data, { Conversation }) { if (!existing) { throw new Error(`Conversation ${id} does not exist!`); } - const setData = setifyProperty(data, 'swarmNodes'); - const setExisting = setifyProperty(existing.attributes, 'swarmNodes'); - const merged = merge({}, setExisting, setData); - if (merged.swarmNodes instanceof Set) { - merged.swarmNodes = Array.from(merged.swarmNodes); - } + const merged = _.merge({}, existing.attributes, data); + + // Merging is a really bad idea and not what we want here, e.g. + // it will take a union of old and new members and that's not + // what we want for member deletion, so: + merged.members = data.members; + merged.swarmNodes = data.swarmNodes; // Don't save the online status of the object const cleaned = omit(merged, 'isOnline'); @@ -731,8 +799,20 @@ async function _removeConversations(ids) { await channels.removeConversation(ids); } +async function getConversationsWithFriendStatus( + status, + { ConversationCollection } +) { + const conversations = await channels.getConversationsWithFriendStatus(status); + + const collection = new ConversationCollection(); + collection.add(conversations); + return collection; +} + async function getPubKeysWithFriendStatus(status) { - return channels.getPubKeysWithFriendStatus(status); + const conversations = await getConversationsWithFriendStatus(status); + return conversations.map(row => row.id); } async function getAllConversations({ ConversationCollection }) { @@ -748,6 +828,22 @@ async function getAllConversationIds() { return ids; } +async function getAllRssFeedConversations({ ConversationCollection }) { + const conversations = await channels.getAllRssFeedConversations(); + + const collection = new ConversationCollection(); + collection.add(conversations); + return collection; +} + +async function getAllPublicConversations({ ConversationCollection }) { + const conversations = await channels.getAllPublicConversations(); + + const collection = new ConversationCollection(); + collection.add(conversations); + return collection; +} + async function getAllPrivateConversations({ ConversationCollection }) { const conversations = await channels.getAllPrivateConversations(); @@ -756,22 +852,61 @@ async function getAllPrivateConversations({ ConversationCollection }) { return collection; } -async function getAllGroupsInvolvingId(id, { ConversationCollection }) { - const conversations = await channels.getAllGroupsInvolvingId(id); +async function getPubkeysInPublicConversation(id) { + return channels.getPubkeysInPublicConversation(id); +} + +async function savePublicServerToken(data) { + await channels.savePublicServerToken(data); +} + +async function getPublicServerTokenByServerUrl(serverUrl) { + const token = await channels.getPublicServerTokenByServerUrl(serverUrl); + return token; +} + +async function getPublicConversationsByServer( + server, + { ConversationCollection } +) { + const conversations = await channels.getPublicConversationsByServer(server); const collection = new ConversationCollection(); collection.add(conversations); return collection; } -async function searchConversations(query, { ConversationCollection }) { - const conversations = await channels.searchConversations(query); +async function getAllGroupsInvolvingId(id, { ConversationCollection }) { + const conversations = await channels.getAllGroupsInvolvingId(id); const collection = new ConversationCollection(); collection.add(conversations); return collection; } +async function searchConversations(query) { + const conversations = await channels.searchConversations(query); + return conversations; +} + +async function searchMessages(query, { limit } = {}) { + const messages = await channels.searchMessages(query, { limit }); + return messages; +} + +async function searchMessagesInConversation( + query, + conversationId, + { limit } = {} +) { + const messages = await channels.searchMessagesInConversation( + query, + conversationId, + { limit } + ); + return messages; +} + // Message async function getMessageCount() { return channels.getMessageCount(); @@ -860,6 +995,15 @@ async function _removeMessages(ids) { await channels.removeMessage(ids); } +async function getMessageByServerId(serverId, conversationId, { Message }) { + const message = await channels.getMessageByServerId(serverId, conversationId); + if (!message) { + return null; + } + + return new Message(message); +} + async function getMessageById(id, { Message }) { const message = await channels.getMessageById(id); if (!message) { @@ -988,13 +1132,8 @@ async function getAllUnprocessed() { return channels.getAllUnprocessed(); } -async function getUnprocessedById(id, { Unprocessed }) { - const unprocessed = await channels.getUnprocessedById(id); - if (!unprocessed) { - return null; - } - - return new Unprocessed(unprocessed); +async function getUnprocessedById(id) { + return channels.getUnprocessedById(id); } async function saveUnprocessed(data, { forceSave } = {}) { @@ -1008,6 +1147,13 @@ async function saveUnprocesseds(arrayOfUnprocessed, { forceSave } = {}) { }); } +async function updateUnprocessedAttempts(id, attempts) { + await channels.updateUnprocessedAttempts(id, attempts); +} +async function updateUnprocessedWithData(id, data) { + await channels.updateUnprocessedWithData(id, data); +} + async function removeUnprocessed(id) { await channels.removeUnprocessed(id); } @@ -1016,6 +1162,27 @@ async function removeAllUnprocessed() { await channels.removeAllUnprocessed(); } +// Attachment downloads + +async function getNextAttachmentDownloadJobs(limit) { + return channels.getNextAttachmentDownloadJobs(limit); +} +async function saveAttachmentDownloadJob(job) { + await channels.saveAttachmentDownloadJob(job); +} +async function setAttachmentDownloadJobPending(id, pending) { + await channels.setAttachmentDownloadJobPending(id, pending); +} +async function resetAttachmentDownloadPending() { + await channels.resetAttachmentDownloadPending(); +} +async function removeAttachmentDownloadJob(id) { + await channels.removeAttachmentDownloadJob(id); +} +async function removeAllAttachmentDownloadJobs() { + await channels.removeAllAttachmentDownloadJobs(); +} + // Other async function removeAll() { @@ -1026,6 +1193,14 @@ async function removeAllConfiguration() { await channels.removeAllConfiguration(); } +async function removeAllConversations() { + await channels.removeAllConversations(); +} + +async function removeAllPrivateConversations() { + await channels.removeAllPrivateConversations(); +} + async function cleanupOrphanedAttachments() { await callChannel(CLEANUP_ORPHANED_ATTACHMENTS_KEY); } diff --git a/js/modules/debuglogs.js b/js/modules/debuglogs.js index 2cdb0d0f7a..6bed2f0e42 100644 --- a/js/modules/debuglogs.js +++ b/js/modules/debuglogs.js @@ -6,7 +6,7 @@ const got = require('got'); const BASE_URL = 'https://debuglogs.org'; const VERSION = window.getVersion(); -const USER_AGENT = `Loki Messenger ${VERSION}`; +const USER_AGENT = `Session ${VERSION}`; // Workaround: Submitting `FormData` using native `FormData::submit` procedure // as integration with `got` results in S3 error saying we haven’t set the diff --git a/js/modules/link_previews.d.ts b/js/modules/link_previews.d.ts new file mode 100644 index 0000000000..ea3e3e3d18 --- /dev/null +++ b/js/modules/link_previews.d.ts @@ -0,0 +1 @@ +export function isLinkSneaky(link: string): boolean; diff --git a/js/modules/link_previews.js b/js/modules/link_previews.js index 3c3f7c5a7b..ba9b8020e1 100644 --- a/js/modules/link_previews.js +++ b/js/modules/link_previews.js @@ -1,6 +1,8 @@ /* global URL */ +const { isNumber, compact } = require('lodash'); const he = require('he'); +const nodeUrl = require('url'); const LinkifyIt = require('linkify-it'); const linkify = LinkifyIt(); @@ -15,6 +17,7 @@ module.exports = { getImageMetaTag, isLinkInWhitelist, isMediaLinkInWhitelist, + isLinkSneaky, }; const SUPPORTED_DOMAINS = [ @@ -28,9 +31,14 @@ const SUPPORTED_DOMAINS = [ 'imgur.com', 'www.imgur.com', 'm.imgur.com', + 'i.imgur.com', 'instagram.com', 'www.instagram.com', 'm.instagram.com', + 'tenor.com', + 'gph.is', + 'giphy.com', + 'media.giphy.com', ]; function isLinkInWhitelist(link) { try { @@ -55,7 +63,7 @@ function isLinkInWhitelist(link) { } } -const SUPPORTED_MEDIA_DOMAINS = /^([^.]+\.)*(ytimg.com|cdninstagram.com|redd.it|imgur.com|fbcdn.net)$/i; +const SUPPORTED_MEDIA_DOMAINS = /^([^.]+\.)*(ytimg.com|cdninstagram.com|redd.it|imgur.com|fbcdn.net|giphy.com|tenor.com)$/i; function isMediaLinkInWhitelist(link) { try { const url = new URL(link); @@ -78,8 +86,8 @@ function isMediaLinkInWhitelist(link) { } } -const META_TITLE = //im; -const META_IMAGE = //im; +const META_TITLE = //im; +const META_IMAGE = //im; function _getMetaTag(html, regularExpression) { const match = regularExpression.exec(html); if (match && match[1]) { @@ -93,12 +101,32 @@ function getTitleMetaTag(html) { return _getMetaTag(html, META_TITLE); } function getImageMetaTag(html) { - return _getMetaTag(html, META_IMAGE); + const tag = _getMetaTag(html, META_IMAGE); + return typeof tag === 'string' ? tag.replace('http://', 'https://') : tag; } -function findLinks(text) { +function findLinks(text, caretLocation) { + const haveCaretLocation = isNumber(caretLocation); + const textLength = text ? text.length : 0; + const matches = linkify.match(text || '') || []; - return matches.map(match => match.text); + return compact( + matches.map(match => { + if (!haveCaretLocation) { + return match.text; + } + + if (match.lastIndex === textLength && caretLocation === textLength) { + return match.text; + } + + if (match.index > caretLocation || match.lastIndex < caretLocation) { + return match.text; + } + + return null; + }) + ); } function getDomain(url) { @@ -174,3 +202,152 @@ function assembleChunks(chunkDescriptors) { return concatenateBytes(...chunks); } + +const LATIN_PATTERN = new RegExp( + '[' + + '\\u0041-\\u005A' + + '\\u0061-\\u007A' + + '\\u00AA' + + '\\u00BA' + + '\\u00C0-\\u00DC' + + '\\u00D8-\\u00F6' + + '\\u00F8-\\u01BA' + + ']' +); + +const CYRILLIC_PATTERN = new RegExp( + '[' + + '\\u0400-\\u0481' + + '\\u0482' + + '\\u0483-\\u0484' + + '\\u0487' + + '\\u0488-\\u0489' + + '\\u048A-\\u052F' + + '\\u1C80-\\u1C88' + + '\\u1D2B' + + '\\u1D78' + + '\\u2DE0-\\u2DFF' + + '\\uA640-\\uA66D' + + '\\uA66E' + + '\\uA66F' + + '\\uA670-\\uA672' + + '\\uA673' + + '\\uA674-\\uA67D' + + '\\uA67E' + + '\\uA67F' + + '\\uA680-\\uA69B' + + '\\uA69C-\\uA69D' + + '\\uA69E-\\uA69F' + + '\\uFE2E-\\uFE2F' + + ']' +); + +const GREEK_PATTERN = new RegExp( + '[' + + '\\u0370-\\u0373' + + '\\u0375' + + '\\u0376-\\u0377' + + '\\u037A' + + '\\u037B-\\u037D' + + '\\u037F' + + '\\u0384' + + '\\u0386' + + '\\u0388-\\u038A' + + '\\u038C' + + '\\u038E-\\u03A1' + + '\\u03A3-\\u03E1' + + '\\u03F0-\\u03F5' + + '\\u03F6' + + '\\u03F7-\\u03FF' + + '\\u1D26-\\u1D2A' + + '\\u1D5D-\\u1D61' + + '\\u1D66-\\u1D6A' + + '\\u1DBF' + + '\\u1F00-\\u1F15' + + '\\u1F18-\\u1F1D' + + '\\u1F20-\\u1F45' + + '\\u1F48-\\u1F4D' + + '\\u1F50-\\u1F57' + + '\\u1F59' + + '\\u1F5B' + + '\\u1F5D' + + '\\u1F5F-\\u1F7D' + + '\\u1F80-\\u1FB4' + + '\\u1FB6-\\u1FBC' + + '\\u1FBD' + + '\\u1FBE' + + '\\u1FBF-\\u1FC1' + + '\\u1FC2-\\u1FC4' + + '\\u1FC6-\\u1FCC' + + '\\u1FCD-\\u1FCF' + + '\\u1FD0-\\u1FD3' + + '\\u1FD6-\\u1FDB' + + '\\u1FDD-\\u1FDF' + + '\\u1FE0-\\u1FEC' + + '\\u1FED-\\u1FEF' + + '\\u1FF2-\\u1FF4' + + '\\u1FF6-\\u1FFC' + + '\\u1FFD-\\u1FFE' + + '\\u2126' + + '\\uAB65' + + ']' +); + +const HIGH_GREEK_PATTERN = new RegExp( + '[' + + `${String.fromCodePoint(0x10140)}-${String.fromCodePoint(0x10174)}` + + `${String.fromCodePoint(0x10175)}-${String.fromCodePoint(0x10178)}` + + `${String.fromCodePoint(0x10179)}-${String.fromCodePoint(0x10189)}` + + `${String.fromCodePoint(0x1018a)}-${String.fromCodePoint(0x1018b)}` + + `${String.fromCodePoint(0x1018c)}-${String.fromCodePoint(0x1018e)}` + + `${String.fromCodePoint(0x101a0)}` + + `${String.fromCodePoint(0x1d200)}-${String.fromCodePoint(0x1d241)}` + + `${String.fromCodePoint(0x1d242)}-${String.fromCodePoint(0x1d244)}` + + `${String.fromCodePoint(0x1d245)}` + + ']', + 'u' +); + +function isChunkSneaky(chunk) { + const hasLatin = LATIN_PATTERN.test(chunk); + if (!hasLatin) { + return false; + } + + const hasCyrillic = CYRILLIC_PATTERN.test(chunk); + if (hasCyrillic) { + return true; + } + + const hasGreek = GREEK_PATTERN.test(chunk); + if (hasGreek) { + return true; + } + + const hasHighGreek = HIGH_GREEK_PATTERN.test(chunk); + if (hasHighGreek) { + return true; + } + + return false; +} + +function isLinkSneaky(link) { + const domain = getDomain(link); + + // This is necesary because getDomain returns domains in punycode form. We check whether + // it's available for the StyleGuide. + const unicodeDomain = nodeUrl.domainToUnicode + ? nodeUrl.domainToUnicode(domain) + : domain; + + const chunks = unicodeDomain.split('.'); + for (let i = 0, max = chunks.length; i < max; i += 1) { + const chunk = chunks[i]; + if (isChunkSneaky(chunk)) { + return true; + } + } + + return false; +} diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js new file mode 100644 index 0000000000..38b229c732 --- /dev/null +++ b/js/modules/loki_app_dot_net_api.js @@ -0,0 +1,1828 @@ +/* global log, textsecure, libloki, Signal, Whisper, ConversationController, +clearTimeout, MessageController, libsignal, StringView, window, _, +dcodeIO, Buffer, lokiSnodeAPI, TextDecoder, process */ +const nodeFetch = require('node-fetch'); +const { URL, URLSearchParams } = require('url'); +const FormData = require('form-data'); +const https = require('https'); + +// Can't be less than 1200 if we have unauth'd requests +const PUBLICCHAT_MSG_POLL_EVERY = 1.5 * 1000; // 1.5s +const PUBLICCHAT_CHAN_POLL_EVERY = 20 * 1000; // 20s +const PUBLICCHAT_DELETION_POLL_EVERY = 5 * 1000; // 5s +const PUBLICCHAT_MOD_POLL_EVERY = 30 * 1000; // 30s +const PUBLICCHAT_MIN_TIME_BETWEEN_DUPLICATE_MESSAGES = 10 * 1000; // 10s + +const HOMESERVER_USER_ANNOTATION_TYPE = 'network.loki.messenger.homeserver'; +const AVATAR_USER_ANNOTATION_TYPE = 'network.loki.messenger.avatar'; +const SETTINGS_CHANNEL_ANNOTATION_TYPE = 'net.patter-app.settings'; +const MESSAGE_ATTACHMENT_TYPE = 'net.app.core.oembed'; +const LOKI_ATTACHMENT_TYPE = 'attachment'; +const LOKI_PREVIEW_TYPE = 'preview'; + +const snodeHttpsAgent = new https.Agent({ + rejectUnauthorized: false, +}); + +// the core ADN class that handles all communication with a specific server +class LokiAppDotNetServerAPI { + constructor(ourKey, url) { + this.ourKey = ourKey; + this.channels = []; + this.tokenPromise = null; + this.baseServerUrl = url; + log.info(`LokiAppDotNetAPI registered server ${url}`); + } + + async close() { + this.channels.forEach(channel => channel.stop()); + if (this.tokenPromise) { + await this.tokenPromise; + } + } + + // channel getter/factory + async findOrCreateChannel(chatAPI, channelId, conversationId) { + let thisChannel = this.channels.find( + channel => channel.channelId === channelId + ); + if (!thisChannel) { + // make sure we're subscribed + // eventually we'll need to move to account registration/add server + await this.serverRequest(`channels/${channelId}/subscribe`, { + method: 'POST', + }); + thisChannel = new LokiPublicChannelAPI( + chatAPI, + this, + channelId, + conversationId + ); + log.info( + 'LokiPublicChannelAPI started for', + channelId, + 'on', + this.baseServerUrl + ); + this.channels.push(thisChannel); + } + return thisChannel; + } + + async partChannel(channelId) { + await this.serverRequest(`channels/${channelId}/subscribe`, { + method: 'DELETE', + }); + this.unregisterChannel(channelId); + } + + // deallocate resources channel uses + unregisterChannel(channelId) { + let thisChannel; + let i = 0; + for (; i < this.channels.length; i += 1) { + if (this.channels[i].channelId === channelId) { + thisChannel = this.channels[i]; + break; + } + } + if (!thisChannel) { + return; + } + thisChannel.stop(); + this.channels.splice(i, 1); + } + + async setProfileName(profileName) { + // when we add an annotation, may need this + /* + const privKey = await this.getPrivateKey(); + // we might need an annotation that sets the homeserver for media + // better to include this with each attachment... + const objToSign = { + name: profileName, + version: 1, + annotations: [], + }; + const sig = await libsignal.Curve.async.calculateSignature( + privKey, + JSON.stringify(objToSign) + ); + */ + + const res = await this.serverRequest('users/me', { + method: 'PATCH', + objBody: { + name: profileName, + }, + }); + // no big deal if it fails... + if (res.err || !res.response || !res.response.data) { + if (res.err) { + log.error(`setProfileName Error ${res.err}`); + } + return []; + } + + // expecting a user object + return res.response.data.annotations || []; + + // if no profileName should we update the local from the server? + // no because there will be multiple public chat servers + } + + async setHomeServer(homeServer) { + const res = await this.serverRequest('users/me', { + method: 'PATCH', + objBody: { + annotations: [ + { + type: HOMESERVER_USER_ANNOTATION_TYPE, + value: homeServer, + }, + ], + }, + }); + + if (res.err || !res.response || !res.response.data) { + if (res.err) { + log.error(`setHomeServer Error ${res.err}`); + } + return []; + } + + // expecting a user object + return res.response.data.annotations || []; + } + + async setAvatar(url, profileKey) { + let value; // undefined will save bandwidth on the annotation if we don't need it (no avatar) + if (url && profileKey) { + value = { url, profileKey }; + } + return this.setSelfAnnotation(AVATAR_USER_ANNOTATION_TYPE, value); + } + + // get active token for this server + async getOrRefreshServerToken(forceRefresh = false) { + let token; + if (!forceRefresh) { + if (this.token) { + return this.token; + } + token = await Signal.Data.getPublicServerTokenByServerUrl( + this.baseServerUrl + ); + } + if (!token) { + token = await this.refreshServerToken(); + if (token) { + await Signal.Data.savePublicServerToken({ + serverUrl: this.baseServerUrl, + token, + }); + } + } + this.token = token; + + // if no token to verify, just bail now + if (!token) { + // + if (!forceRefresh) { + token = await this.getOrRefreshServerToken(true); + } + return token; + } + + // verify token info + const tokenRes = await this.serverRequest('token'); + // if no problems and we have data + if ( + !tokenRes.err && + tokenRes.response && + tokenRes.response.data && + tokenRes.response.data.user + ) { + // get our profile name + // FIXME: should this be window.storage.get('primaryDevicePubKey')? + const ourNumber = textsecure.storage.user.getNumber(); + const profileConvo = ConversationController.get(ourNumber); + const profileName = profileConvo.getProfileName(); + // if doesn't match, write it to the network + if (tokenRes.response.data.user.name !== profileName) { + // update our profile name if it got out of sync + this.setProfileName(profileName); + } + } + if (tokenRes.err) { + log.error(`token err`, tokenRes); + // didn't already try && this specific error + if ( + !forceRefresh && + tokenRes.response && + tokenRes.response.meta && + tokenRes.response.meta.code === 401 + ) { + // this token is not good + this.token = ''; // remove from object + await Signal.Data.savePublicServerToken({ + serverUrl: this.baseServerUrl, + token: '', + }); + token = await this.getOrRefreshServerToken(true); + } + } + + return token; + } + + // get active token from server (but only allow one request at a time) + async refreshServerToken() { + // if currently not in progress + if (this.tokenPromise === null) { + // FIXME: add timeout + // a broken/stuck token endpoint can prevent you from removing channels + // set lock + this.tokenPromise = new Promise(async res => { + // request the token + const token = await this.requestToken(); + if (!token) { + res(null); + return; + } + // activate the token + const registered = await this.submitToken(token); + if (!registered) { + res(null); + return; + } + // resolve promise to release lock + res(token); + }); + } + // wait until we have it set + const token = await this.tokenPromise; + // clear lock + this.tokenPromise = null; + return token; + } + + // request an token from the server + async requestToken() { + let res; + try { + const url = new URL(`${this.baseServerUrl}/loki/v1/get_challenge`); + const params = { + pubKey: this.ourKey, + }; + url.search = new URLSearchParams(params); + + res = await this.proxyFetch(url); + } catch (e) { + // should we retry here? + // no, this is the low level function + // not really an error, from a client's pov, network servers can fail... + if (e.code === 'ECONNREFUSED') { + // down + log.warn( + 'requestToken request can not connect', + this.baseServerUrl, + e.message + ); + } else if (e.code === 'ECONNRESET') { + // got disconnected + log.warn( + 'requestToken request lost connection', + this.baseServerUrl, + e.message + ); + } else { + log.error( + 'requestToken request failed', + this.baseServerUrl, + e.code, + e.message + ); + } + return null; + } + if (!res.ok) { + log.error('requestToken request failed'); + return null; + } + const body = await res.json(); + const token = await libloki.crypto.decryptToken(body); + return token; + } + + // activate token + async submitToken(token) { + const options = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + pubKey: this.ourKey, + token, + }), + }; + + try { + const res = await this.proxyFetch( + `${this.baseServerUrl}/loki/v1/submit_challenge`, + options + ); + return res.ok; + } catch (e) { + log.error('submitToken proxyFetch failure', e.code, e.message); + return false; + } + } + + async proxyFetch(urlObj, fetchOptions = { method: 'GET' }) { + if ( + window.lokiFeatureFlags.useSnodeProxy && + (this.baseServerUrl === 'https://file-dev.lokinet.org' || + this.baseServerUrl === 'https://file.lokinet.org' || + this.baseServerUrl === 'https://file-dev.getsession.org' || + this.baseServerUrl === 'https://file.getsession.org') + ) { + const finalOptions = { ...fetchOptions }; + if (!fetchOptions.method) { + finalOptions.method = 'GET'; + } + const urlStr = urlObj.toString(); + const endpoint = urlStr.replace(`${this.baseServerUrl}/`, ''); + const { response, result } = await this._sendToProxy( + endpoint, + finalOptions + ); + // emulate nodeFetch response... + return { + ok: result.status === 200, + json: () => response, + }; + } + return nodeFetch(urlObj, fetchOptions); + } + + async _sendToProxy(endpoint, fetchOptions) { + const randSnode = await lokiSnodeAPI.getRandomSnodeAddress(); + const url = `https://${randSnode.ip}:${randSnode.port}/file_proxy`; + + const payloadObj = { + // I think this is a stream, we may need to collect it all? + body: fetchOptions.body, // might need to b64 if binary... + endpoint, + method: fetchOptions.method, + headers: fetchOptions.headers, + }; + + // from https://github.com/sindresorhus/is-stream/blob/master/index.js + if ( + payloadObj.body && + typeof payloadObj.body === 'object' && + typeof payloadObj.body.pipe === 'function' + ) { + log.info('detected body is a stream'); + const fData = payloadObj.body.getBuffer(); + const fHeaders = payloadObj.body.getHeaders(); + // update headers for boundary + payloadObj.headers = { ...payloadObj.headers, ...fHeaders }; + // update body with base64 chunk + payloadObj.body = { + fileUpload: fData.toString('base64'), + }; + } + + // convert our payload to binary buffer + const payloadData = Buffer.from( + dcodeIO.ByteBuffer.wrap(JSON.stringify(payloadObj)).toArrayBuffer() + ); + payloadObj.body = false; // free memory + + // make temporary key for this request/response + const ephemeralKey = libsignal.Curve.generateKeyPair(); + + // mix server pub key with our priv key + const symKey = libsignal.Curve.calculateAgreement( + this.pubKey, // server's pubkey + ephemeralKey.privKey // our privkey + ); + + const ivAndCiphertext = await libloki.crypto.DHEncrypt(symKey, payloadData); + + // convert final buffer to base64 + const cipherText64 = dcodeIO.ByteBuffer.wrap(ivAndCiphertext).toString( + 'base64' + ); + + const ephemeralPubKey64 = dcodeIO.ByteBuffer.wrap( + ephemeralKey.pubKey + ).toString('base64'); + + const finalRequestHeader = { + 'X-Loki-File-Server-Ephemeral-Key': ephemeralPubKey64, + }; + + const firstHopOptions = { + method: 'POST', + // not sure why I can't use anything but json... + // text/plain would be preferred... + body: JSON.stringify({ cipherText64 }), + headers: { + 'Content-Type': 'application/json', + 'X-Loki-File-Server-Target': '/loki/v1/secure_rpc', + 'X-Loki-File-Server-Verb': 'POST', + 'X-Loki-File-Server-Headers': JSON.stringify(finalRequestHeader), + }, + // we are talking to a snode... + agent: snodeHttpsAgent, + }; + // weird this doesn't need NODE_TLS_REJECT_UNAUTHORIZED = 0 + const result = await nodeFetch(url, firstHopOptions); + + const txtResponse = await result.text(); + if (txtResponse === 'Service node is not ready: not in any swarm; \n') { + // mark snode bad + log.warn('Marking random snode bad', randSnode); + lokiSnodeAPI.markRandomNodeUnreachable(randSnode); + // retry (hopefully with new snode) + // FIXME: max number of retries... + return this._sendToProxy(endpoint, fetchOptions); + } + + let response = {}; + try { + response = JSON.parse(txtResponse); + } catch (e) { + log.warn(`_sendToProxy Could not parse outer JSON [${txtResponse}]`); + } + + if (response.meta && response.meta.code === 200) { + // convert base64 in response to binary + const ivAndCiphertextResponse = dcodeIO.ByteBuffer.wrap( + response.data, + 'base64' + ).toArrayBuffer(); + const decrypted = await libloki.crypto.DHDecrypt( + symKey, + ivAndCiphertextResponse + ); + const textDecoder = new TextDecoder(); + const json = textDecoder.decode(decrypted); + // replace response + try { + response = JSON.parse(json); + } catch (e) { + log.warn(`_sendToProxy Could not parse inner JSON [${json}]`); + } + } else { + log.warn( + 'file server secure_rpc gave an non-200 response: ', + response, + ` txtResponse[${txtResponse}]` + ); + } + return { result, txtResponse, response }; + } + + // make a request to the server + async serverRequest(endpoint, options = {}) { + const { + params = {}, + method, + rawBody, + objBody, + forceFreshToken = false, + } = options; + + const url = new URL(`${this.baseServerUrl}/${endpoint}`); + if (params) { + url.search = new URLSearchParams(params); + } + const fetchOptions = {}; + const headers = {}; + try { + if (forceFreshToken) { + await this.getOrRefreshServerToken(true); + } + if (this.token) { + headers.Authorization = `Bearer ${this.token}`; + } + if (method) { + fetchOptions.method = method; + } + if (objBody) { + headers['Content-Type'] = 'application/json'; + fetchOptions.body = JSON.stringify(objBody); + } else if (rawBody) { + fetchOptions.body = rawBody; + } + fetchOptions.headers = headers; + + // domain ends in .loki + if (endpoint.match(/\.loki\//)) { + fetchOptions.agent = snodeHttpsAgent; + } + } catch (e) { + log.info('serverRequest set up error:', e.code, e.message); + return { + err: e, + }; + } + + let response; + let result; + let txtResponse; + let mode = 'nodeFetch'; + try { + if ( + window.lokiFeatureFlags.useSnodeProxy && + (this.baseServerUrl === 'https://file-dev.lokinet.org' || + this.baseServerUrl === 'https://file.lokinet.org' || + this.baseServerUrl === 'https://file-dev.getsession.org' || + this.baseServerUrl === 'https://file.getsession.org') + ) { + mode = '_sendToProxy'; + + const endpointWithQS = url + .toString() + .replace(`${this.baseServerUrl}/`, ''); + ({ response, txtResponse, result } = await this._sendToProxy( + endpointWithQS, + fetchOptions + )); + } else { + // disable check for .loki + process.env.NODE_TLS_REJECT_UNAUTHORIZED = endpoint.match(/\.loki\//) + ? 0 + : 1; + result = await nodeFetch(url, fetchOptions); + // always make sure this check is enabled + process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; + txtResponse = await result.text(); + response = JSON.parse(txtResponse); + } + } catch (e) { + if (txtResponse) { + log.info( + `serverRequest ${mode} error`, + e.code, + e.message, + `json: ${txtResponse}` + ); + } else { + log.info(`serverRequest ${mode} error`, e.code, e.message); + } + return { + err: e, + }; + } + // if it's a response style with a meta + if (result.status !== 200) { + if (!forceFreshToken && (!response.meta || response.meta.code === 401)) { + // copy options because lint complains if we modify this directly + const updatedOptions = options; + // force it this time + updatedOptions.forceFreshToken = true; + // retry with updated options + return this.serverRequest(endpoint, updatedOptions); + } + return { + err: 'statusCode', + statusCode: result.status, + response, + }; + } + return { + statusCode: result.status, + response, + }; + } + + async getUserAnnotations(pubKey) { + if (!pubKey) { + log.warn('No pubkey provided to getUserAnnotations!'); + return []; + } + const res = await this.serverRequest(`users/@${pubKey}`, { + method: 'GET', + params: { + include_user_annotations: 1, + }, + }); + + if (res.err || !res.response || !res.response.data) { + if (res.err) { + log.error(`getUserAnnotations Error ${res.err}`); + } + return []; + } + + return res.response.data.annotations || []; + } + + async getModerators(channelId) { + if (!channelId) { + log.warn('No channelId provided to getModerators!'); + return []; + } + const res = await this.serverRequest( + `loki/v1/channels/${channelId}/moderators` + ); + + return (!res.err && res.response && res.response.moderators) || []; + } + + async addModerators(pubKeysParam) { + let pubKeys = pubKeysParam; + if (!Array.isArray(pubKeys)) { + pubKeys = [pubKeys]; + } + pubKeys = pubKeys.map(key => `@${key}`); + const users = await this.getUsers(pubKeys); + const validUsers = users.filter(user => !!user.id); + const results = await Promise.all( + validUsers.map(async user => { + log.info(`POSTing loki/v1/moderators/${user.id}`); + const res = await this.serverRequest(`loki/v1/moderators/${user.id}`, { + method: 'POST', + }); + return !!(!res.err && res.response && res.response.data); + }) + ); + const anyFailures = results.some(test => !test); + return anyFailures ? results : true; // return failures or total success + } + + async removeModerators(pubKeysParam) { + let pubKeys = pubKeysParam; + if (!Array.isArray(pubKeys)) { + pubKeys = [pubKeys]; + } + pubKeys = pubKeys.map(key => `@${key}`); + const users = await this.getUsers(pubKeys); + const validUsers = users.filter(user => !!user.id); + + const results = await Promise.all( + validUsers.map(async user => { + const res = await this.serverRequest(`loki/v1/moderators/${user.id}`, { + method: 'DELETE', + }); + return !!(!res.err && res.response && res.response.data); + }) + ); + const anyFailures = results.some(test => !test); + return anyFailures ? results : true; // return failures or total success + } + + async getSubscribers(channelId, wantObjects) { + if (!channelId) { + log.warn('No channelId provided to getSubscribers!'); + return []; + } + + let res = {}; + if (!Array.isArray(channelId) && wantObjects) { + res = await this.serverRequest(`channels/${channelId}/subscribers`, { + method: 'GET', + params: { + include_user_annotations: 1, + }, + }); + } else { + // not deployed on all backends yet + res.err = 'array subscribers endpoint not yet implemented'; + /* + var list = channelId; + if (!Array.isArray(list)) { + list = [channelId]; + } + const idres = await this.serverRequest(`channels/subscribers/ids`, { + method: 'GET', + params: { + ids: list.join(','), + include_user_annotations: 1, + }, + }); + if (wantObjects) { + if (idres.err || !idres.response || !idres.response.data) { + if (idres.err) { + log.error(`Error ${idres.err}`); + } + return []; + } + const userList = []; + await Promise.all(idres.response.data.map(async channelId => { + const channelUserObjs = await this.getUsers(idres.response.data[channelId]); + userList.push(...channelUserObjs); + })); + res = { + response: { + meta: { + code: 200, + }, + data: userList + } + } + } else { + res = idres; + } + */ + } + + if (res.err || !res.response || !res.response.data) { + if (res.err) { + log.error(`getSubscribers Error ${res.err}`); + } + return []; + } + + return res.response.data || []; + } + + async getUsers(pubKeys) { + if (!pubKeys) { + log.warn('No pubKeys provided to getUsers!'); + return []; + } + // ok to call without + if (!pubKeys.length) { + return []; + } + if (pubKeys.length > 200) { + log.warn('Too many pubKeys given to getUsers!'); + } + const res = await this.serverRequest('users', { + method: 'GET', + params: { + ids: pubKeys.join(','), + include_user_annotations: 1, + }, + }); + + if (res.err || !res.response || !res.response.data) { + if (res.err) { + log.error(`getUsers Error ${res.err}`); + } + return []; + } + + return res.response.data || []; + } + + // Only one annotation at a time + async setSelfAnnotation(type, value) { + const annotation = { type }; + + // to delete annotation, omit the "value" field + if (value) { + annotation.value = value; + } + + const res = await this.serverRequest('users/me', { + method: 'PATCH', + objBody: { + annotations: [annotation], + }, + }); + + if (!res.err && res.response) { + return res.response; + } + + return false; + } + + async uploadAvatar(data) { + const endpoint = 'users/me/avatar'; + + const options = { + method: 'POST', + rawBody: data, + }; + + const { statusCode, response } = await this.serverRequest( + endpoint, + options + ); + + if (statusCode !== 200) { + log.warn('Failed to upload avatar to fileserver'); + return null; + } + + const url = + response.data && + response.data.avatar_image && + response.data.avatar_image.url; + + // We don't use the server id for avatars + return { + url, + id: null, + }; + } + + async uploadData(data) { + const endpoint = 'files'; + const options = { + method: 'POST', + rawBody: data, + }; + + const { statusCode, response } = await this.serverRequest( + endpoint, + options + ); + if (statusCode !== 200) { + log.warn('Failed to upload data to server', this.baseServerUrl); + return null; + } + + const url = response.data && response.data.url; + const id = response.data && response.data.id; + return { + url, + id, + }; + } + + putAttachment(attachmentBin) { + const formData = new FormData(); + const buffer = Buffer.from(attachmentBin); + formData.append('type', 'network.loki'); + formData.append('content', buffer, { + contentType: 'application/octet-stream', + name: 'content', + filename: 'attachment', + knownLength: buffer.byteLength, + }); + + return this.uploadData(formData); + } +} + +// functions to a specific ADN channel on an ADN server +class LokiPublicChannelAPI { + constructor(chatAPI, serverAPI, channelId, conversationId) { + // properties + this.chatAPI = chatAPI; + this.serverAPI = serverAPI; + this.channelId = channelId; + this.baseChannelUrl = `channels/${this.channelId}`; + this.conversationId = conversationId; + this.conversation = ConversationController.get(conversationId); + this.lastGot = null; + this.modStatus = false; + this.deleteLastId = 1; + this.timers = {}; + this.running = true; + this.myPrivateKey = false; + // can escalated to SQL if it start uses too much memory + this.logMop = {}; + + // Cache for duplicate checking + this.lastMessagesCache = []; + + // end properties + + log.info( + `registered LokiPublicChannel ${channelId} on ${ + this.serverAPI.baseServerUrl + }` + ); + // start polling + this.pollForMessages(); + this.pollForDeletions(); + this.pollForChannel(); + this.pollForModerators(); + + // TODO: poll for group members here? + } + + async getPrivateKey() { + if (!this.myPrivateKey) { + const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair(); + this.myPrivateKey = myKeyPair.privKey; + } + return this.myPrivateKey; + } + + async banUser(pubkey) { + const res = await this.serverRequest( + `loki/v1/moderation/blacklist/@${pubkey}`, + { + method: 'POST', + } + ); + + if (res.err || !res.response || !res.response.data) { + if (res.err) { + log.error(`banUser Error ${res.err}`); + } + return false; + } + + return true; + } + + stop() { + this.running = false; + if (this.timers.channel) { + clearTimeout(this.timers.channel); + } + if (this.timers.moderator) { + clearTimeout(this.timers.moderator); + } + if (this.timers.delete) { + clearTimeout(this.timers.delete); + } + if (this.timers.message) { + clearTimeout(this.timers.message); + } + } + + serverRequest(endpoint, options = {}) { + return this.serverAPI.serverRequest(endpoint, options); + } + + getSubscribers() { + return this.serverAPI.getSubscribers(this.channelId, true); + } + + getModerators() { + return this.serverAPI.getModerators(this.channelId); + } + + // get moderation actions + async pollForModerators() { + try { + await this.pollOnceForModerators(); + } catch (e) { + log.warn( + 'Error while polling for public chat moderators:', + e.code, + e.message + ); + } + if (this.running) { + this.timers.moderator = setTimeout(() => { + this.pollForModerators(); + }, PUBLICCHAT_MOD_POLL_EVERY); + } + } + + // get moderator status + async pollOnceForModerators() { + // get moderator status + const res = await this.serverRequest( + `loki/v1/channels/${this.channelId}/moderators` + ); + // FIXME: should this be window.storage.get('primaryDevicePubKey')? + const ourNumber = textsecure.storage.user.getNumber(); + + // Get the list of moderators if no errors occurred + const moderators = !res.err && res.response && res.response.moderators; + + // if we encountered problems then we'll keep the old mod status + if (moderators) { + this.modStatus = moderators.includes(ourNumber); + } + + await this.conversation.setModerators(moderators || []); + } + + async setChannelSettings(settings) { + if (!this.modStatus) { + // need moderator access to set this + log.warn('Need moderator access to setChannelName'); + return false; + } + // racy! + const res = await this.serverRequest(this.baseChannelUrl, { + params: { include_annotations: 1 }, + }); + if (res.err) { + // state unknown + log.warn(`public chat channel state unknown, skipping set: ${res.err}`); + return false; + } + let notes = + res.response && res.response.data && res.response.data.annotations; + if (!notes) { + // ok if nothing is set yet + notes = []; + } + let settingNotes = notes.filter( + note => note.type === SETTINGS_CHANNEL_ANNOTATION_TYPE + ); + if (!settingNotes) { + // default name, description, avatar + settingNotes = [ + { + type: SETTINGS_CHANNEL_ANNOTATION_TYPE, + value: { + name: 'Your Public Chat', + description: 'Your public chat room', + avatar: 'images/group_default.png', + }, + }, + ]; + } + // update settings + settingNotes[0].value = Object.assign(settingNotes[0].value, settings); + // commit settings + const updateRes = await this.serverRequest( + `loki/v1/${this.baseChannelUrl}`, + { method: 'PUT', objBody: { annotations: settingNotes } } + ); + if (updateRes.err || !updateRes.response || !updateRes.response.data) { + if (updateRes.err) { + log.error(`setChannelSettings Error ${updateRes.err}`); + } + return false; + } + return true; + } + + // Do we need this? They definitely make it more clear... + setChannelName(name) { + return this.setChannelSettings({ name }); + } + setChannelDescription(description) { + return this.setChannelSettings({ description }); + } + setChannelAvatar(avatar) { + return this.setChannelSettings({ avatar }); + } + + // delete messages on the server + async deleteMessages(serverIds, canThrow = false) { + const res = await this.serverRequest( + this.modStatus ? `loki/v1/moderation/messages` : `loki/v1/messages`, + { method: 'DELETE', params: { ids: serverIds } } + ); + if (!res.err) { + const deletedIds = res.response.data + .filter(d => d.is_deleted) + .map(d => d.id); + + if (deletedIds.length > 0) { + log.info(`deleted ${serverIds} on ${this.baseChannelUrl}`); + } + + const failedIds = res.response.data + .filter(d => !d.is_deleted) + .map(d => d.id); + + if (failedIds.length > 0) { + log.warn(`failed to delete ${failedIds} on ${this.baseChannelUrl}`); + } + + // Note: if there is no entry for message, we assume it wasn't found + // on the server, so it is not treated as explicitly failed + const ignoredIds = _.difference( + serverIds, + _.union(failedIds, deletedIds) + ); + + if (ignoredIds.length > 0) { + log.warn(`No response for ${ignoredIds} on ${this.baseChannelUrl}`); + } + + return { deletedIds, ignoredIds }; + } + if (canThrow) { + throw new textsecure.PublicChatError( + 'Failed to delete public chat message' + ); + } + return { deletedIds: [], ignoredIds: [] }; + } + + // used for sending messages + getEndpoint() { + const endpoint = `${this.serverAPI.baseServerUrl}/${ + this.baseChannelUrl + }/messages`; + return endpoint; + } + + // get moderation actions + async pollForChannel() { + try { + await this.pollForChannelOnce(); + } catch (e) { + log.warn( + 'Error while polling for public chat room details', + e.code, + e.message + ); + } + if (this.running) { + this.timers.channel = setTimeout(() => { + this.pollForChannel(); + }, PUBLICCHAT_CHAN_POLL_EVERY); + } + } + + // update room details + async pollForChannelOnce() { + const res = await this.serverRequest(`${this.baseChannelUrl}`, { + params: { + include_annotations: 1, + }, + }); + + if (res.err || !res.response || !res.response.data) { + return; + } + + const { data } = res.response; + + if (data.annotations && data.annotations.length) { + // get our setting note + const settingNotes = data.annotations.filter( + note => note.type === SETTINGS_CHANNEL_ANNOTATION_TYPE + ); + const note = settingNotes && settingNotes.length ? settingNotes[0] : {}; + // setting_note.value.description only needed for directory + if (note.value && note.value.name) { + this.conversation.setGroupName(note.value.name); + } + if (note.value && note.value.avatar) { + this.conversation.setProfileAvatar(note.value.avatar); + } + // is it mutable? + // who are the moderators? + // else could set a default in case of server problems... + } + + if (data.counts && Number.isInteger(data.counts.subscribers)) { + this.conversation.setSubscriberCount(data.counts.subscribers); + } + } + + // get moderation actions + async pollForDeletions() { + try { + await this.pollOnceForDeletions(); + } catch (e) { + log.warn( + 'Error while polling for public chat deletions:', + e.code, + e.message + ); + } + if (this.running) { + this.timers.delete = setTimeout(() => { + this.pollForDeletions(); + }, PUBLICCHAT_DELETION_POLL_EVERY); + } + } + + async pollOnceForDeletions() { + // grab the last 200 deletions + const params = { + count: 200, + }; + + // start loop + let more = true; + while (more) { + // set params to from where we last checked + params.since_id = this.deleteLastId; + + // grab the next 200 deletions from where we last checked + // eslint-disable-next-line no-await-in-loop + const res = await this.serverRequest( + `loki/v1/channel/${this.channelId}/deletes`, + { params } + ); + + // if any problems, abort out + if (res.err || !res.response) { + if (res.err) { + log.error(`pollOnceForDeletions Error ${res.err}`); + } + break; + } + + // Process results + res.response.data.reverse().forEach(deleteEntry => { + // Escalate it up to the subsystem that can check to see if this has + // been processed + Whisper.events.trigger('deleteLocalPublicMessage', { + messageServerId: deleteEntry.message_id, + conversationId: this.conversationId, + }); + }); + + // update where we last checked + this.deleteLastId = res.response.meta.max_id; + more = res.response.meta.more && res.response.data.length >= params.count; + } + } + + static getSigData( + sigVer, + noteValue, + attachmentAnnotations, + previewAnnotations, + adnMessage + ) { + let sigString = ''; + sigString += adnMessage.text.trim(); + sigString += noteValue.timestamp; + if (noteValue.quote) { + sigString += noteValue.quote.id; + sigString += noteValue.quote.author; + sigString += noteValue.quote.text.trim(); + if (adnMessage.reply_to) { + sigString += adnMessage.reply_to; + } + } + sigString += [...attachmentAnnotations, ...previewAnnotations] + .map(data => data.id || data.image.id) + .sort() + .join(); + sigString += sigVer; + return dcodeIO.ByteBuffer.wrap(sigString, 'utf8').toArrayBuffer(); + } + + async getMessengerData(adnMessage) { + if ( + !Array.isArray(adnMessage.annotations) || + adnMessage.annotations.length === 0 + ) { + return false; + } + const noteValue = adnMessage.annotations[0].value; + + // signatures now required + if (!noteValue.sig || typeof noteValue.sig !== 'string') { + return false; + } + + // timestamp is the only required field we've had since the first deployed version + const { timestamp, quote } = noteValue; + + let profileKey = null; + let avatar = null; + const avatarNote = adnMessage.user.annotations.find( + note => note.type === AVATAR_USER_ANNOTATION_TYPE + ); + if (avatarNote) { + ({ profileKey, url: avatar } = avatarNote.value); + } + + if (quote) { + // TODO: Enable quote attachments again using proper ADN style + quote.attachments = []; + } + + // try to verify signature + const { sig, sigver } = noteValue; + const annoCopy = [...adnMessage.annotations]; + const attachments = annoCopy + .filter(anno => anno.value.lokiType === LOKI_ATTACHMENT_TYPE) + .map(attachment => ({ isRaw: true, ...attachment.value })); + const preview = annoCopy + .filter(anno => anno.value.lokiType === LOKI_PREVIEW_TYPE) + .map(LokiPublicChannelAPI.getPreviewFromAnnotation); + // strip out sig and sigver + annoCopy[0] = _.omit(annoCopy[0], ['value.sig', 'value.sigver']); + const sigData = LokiPublicChannelAPI.getSigData( + sigver, + noteValue, + attachments, + preview, + adnMessage + ); + + const pubKeyBin = StringView.hexToArrayBuffer(adnMessage.user.username); + const sigBin = StringView.hexToArrayBuffer(sig); + try { + await libsignal.Curve.async.verifySignature(pubKeyBin, sigData, sigBin); + } catch (e) { + if (e.message === 'Invalid signature') { + // keep noise out of the logs, once per start up is enough + if (this.logMop[adnMessage.id] === undefined) { + log.warn( + 'Invalid or missing signature on ', + this.serverAPI.baseServerUrl, + this.channelId, + adnMessage.id, + 'says', + adnMessage.text, + 'from', + adnMessage.user.username, + 'signature', + sig, + 'signature version', + sigver + ); + this.logMop[adnMessage.id] = true; + } + // we now only accept valid messages into the public chat + return false; + } + // any error should cause problem + log.error(`Unhandled message signature validation error ${e.message}`); + return false; + } + + return { + timestamp, + attachments, + preview, + quote, + avatar, + profileKey, + }; + } + + // get channel messages + async pollForMessages() { + try { + await this.pollOnceForMessages(); + } catch (e) { + log.warn( + 'Error while polling for public chat messages:', + e.code, + e.message + ); + } + if (this.running) { + this.timers.message = setTimeout(() => { + this.pollForMessages(); + }, PUBLICCHAT_MSG_POLL_EVERY); + } + } + + async pollOnceForMessages() { + const params = { + include_annotations: 1, + include_user_annotations: 1, // to get the home server + include_deleted: false, + }; + if (!this.conversation) { + log.warn('Trying to poll for non-existing public conversation'); + this.lastGot = 0; + } else if (!this.lastGot) { + this.lastGot = this.conversation.getLastRetrievedMessage(); + } + params.since_id = this.lastGot; + // Just grab the most recent 100 messages if you don't have a valid lastGot + params.count = this.lastGot === 0 ? -100 : 20; + // log.info(`Getting ${params.count} from ${this.lastGot} on ${this.baseChannelUrl}`); + const res = await this.serverRequest(`${this.baseChannelUrl}/messages`, { + params, + }); + + if (res.err || !res.response) { + return; + } + + let receivedAt = new Date().getTime(); + const homeServerPubKeys = {}; + let pendingMessages = []; + + // get our profile name + // FIXME: should this be window.storage.get('primaryDevicePubKey')? + const ourNumber = textsecure.storage.user.getNumber(); + let lastProfileName = false; + + // the signature forces this to be async + pendingMessages = await Promise.all( + // process these in chronological order + res.response.data.reverse().map(async adnMessage => { + // still update our last received if deleted, not signed or not valid + this.lastGot = !this.lastGot + ? adnMessage.id + : Math.max(this.lastGot, adnMessage.id); + + if ( + !adnMessage.id || + !adnMessage.user || + !adnMessage.user.username || // pubKey lives in the username field + !adnMessage.text || + adnMessage.is_deleted + ) { + return false; // Invalid or delete message + } + + const pubKey = adnMessage.user.username; + + const messengerData = await this.getMessengerData(adnMessage); + if (messengerData === false) { + return false; + } + + const { + timestamp, + quote, + attachments, + preview, + avatar, + profileKey, + } = messengerData; + if (!timestamp) { + return false; // Invalid message + } + + // Duplicate check + const isDuplicate = message => { + // The username in this case is the users pubKey + const sameUsername = message.username === pubKey; + const sameText = message.text === adnMessage.text; + // Don't filter out messages that are too far apart from each other + const timestampsSimilar = + Math.abs(message.timestamp - timestamp) <= + PUBLICCHAT_MIN_TIME_BETWEEN_DUPLICATE_MESSAGES; + + return sameUsername && sameText && timestampsSimilar; + }; + + // Filter out any messages that we got previously + if (this.lastMessagesCache.some(isDuplicate)) { + return false; // Duplicate message + } + + // FIXME: maybe move after the de-multidev-decode + // Add the message to the lastMessage cache and keep the last 5 recent messages + this.lastMessagesCache = [ + ...this.lastMessagesCache, + { + username: pubKey, + text: adnMessage.text, + timestamp, + }, + ].splice(-5); + + const from = adnMessage.user.name || 'Anonymous'; // profileName + + // if us + if (pubKey === ourNumber) { + // update the last name we saw from ourself + lastProfileName = from; + } + + // track sources for multidevice support + // sort it by home server + let homeServer = window.getDefaultFileServer(); + if (adnMessage.user && adnMessage.user.annotations.length) { + const homeNotes = adnMessage.user.annotations.filter( + note => note.type === HOMESERVER_USER_ANNOTATION_TYPE + ); + // FIXME: this annotation should probably be signed and verified... + homeServer = homeNotes.reduce( + (curVal, note) => (note.value ? note.value : curVal), + homeServer + ); + } + if (homeServerPubKeys[homeServer] === undefined) { + homeServerPubKeys[homeServer] = []; + } + if (homeServerPubKeys[homeServer].indexOf(`@${pubKey}`) === -1) { + homeServerPubKeys[homeServer].push(`@${pubKey}`); + } + + // generate signal message object + const messageData = { + serverId: adnMessage.id, + clientVerified: true, + friendRequest: false, + source: pubKey, + sourceDevice: 1, + timestamp, + + serverTimestamp: timestamp, + receivedAt, + isPublic: true, + message: { + body: + adnMessage.text === timestamp.toString() ? '' : adnMessage.text, + attachments, + group: { + id: this.conversationId, + type: textsecure.protobuf.GroupContext.Type.DELIVER, + }, + flags: 0, + expireTimer: 0, + profileKey, + timestamp, + received_at: receivedAt, + sent_at: timestamp, + quote, + contact: [], + preview, + profile: { + displayName: from, + avatar, + }, + }, + }; + receivedAt += 1; // Ensure different arrival times + + // now process any user meta data updates + // - update their conversation with a potentially new avatar + return messageData; + }) + ); + + // do we really need this? + if (!pendingMessages.length) { + this.conversation.setLastRetrievedMessage(this.lastGot); + return; + } + + // slave to primary map for this group of messages + let slavePrimaryMap = {}; + + // reduce list of servers into verified maps and keys + const verifiedPrimaryPKs = await Object.keys(homeServerPubKeys).reduce( + async (curVal, serverUrl) => { + // get an API to this server + const serverAPI = await window.lokiFileServerAPIFactory.establishConnection( + serverUrl + ); + + // get list of verified primary PKs + const result = await serverAPI.verifyPrimaryPubKeys( + homeServerPubKeys[serverUrl] + ); + + // merged these device mappings into our slavePrimaryMap + // should not be any collisions, since each pubKey can only have one home server + slavePrimaryMap = { ...slavePrimaryMap, ...result.slaveMap }; + + // copy verified pub keys into result + return curVal.concat(result.verifiedPrimaryPKs); + }, + [] + ); + + // filter out invalid messages + pendingMessages = pendingMessages.filter(messageData => !!messageData); + // separate messages coming from primary and secondary devices + const [primaryMessages, slaveMessages] = _.partition( + pendingMessages, + message => !(message.source in slavePrimaryMap) + ); + // process primary devices' message directly + primaryMessages.forEach(message => + this.chatAPI.emit('publicMessage', { + message, + }) + ); + + pendingMessages = []; // allow memory to be freed + + // get actual chat server data (mainly the name rn) of primary device + const verifiedDeviceResults = await this.serverAPI.getUsers( + verifiedPrimaryPKs + ); + + // build map of userProfileName to primaryKeys + /* eslint-disable no-param-reassign */ + this.primaryUserProfileName = verifiedDeviceResults.reduce( + (mapOut, user) => { + let avatar = null; + let profileKey = null; + const avatarNote = user.annotations.find( + note => note.type === AVATAR_USER_ANNOTATION_TYPE + ); + if (avatarNote) { + ({ profileKey, url: avatar } = avatarNote.value); + } + mapOut[user.username] = { + name: user.name, + avatar, + profileKey, + }; + return mapOut; + }, + {} + ); + /* eslint-enable no-param-reassign */ + + // process remaining messages + /* eslint-disable no-param-reassign */ + slaveMessages.forEach(messageData => { + const slaveKey = messageData.source; + + // prevent our own device sent messages from coming back in + if (slaveKey === ourNumber) { + // we originally sent these + return; + } + + // look up primary device once + const primaryPubKey = slavePrimaryMap[slaveKey]; + + // send out remaining messages for this merged identity + /* eslint-disable no-param-reassign */ + if (slavePrimaryMap[slaveKey]) { + // rewrite source, profile + messageData.source = primaryPubKey; + const primaryProfile = this.primaryUserProfileName[primaryPubKey]; + if (primaryProfile) { + const { name, avatar, profileKey } = primaryProfile; + messageData.message.profile.displayName = name; + messageData.message.profile.avatar = avatar; + messageData.message.profileKey = profileKey; + } + } + /* eslint-enable no-param-reassign */ + this.chatAPI.emit('publicMessage', { + message: messageData, + }); + }); + /* eslint-enable no-param-reassign */ + + // if we received one of our own messages + if (lastProfileName !== false) { + // get current profileName + const profileConvo = ConversationController.get(ourNumber); + const profileName = profileConvo.getProfileName(); + // check to see if it out of sync + if (profileName !== lastProfileName) { + // out of sync, update this server + this.serverAPI.setProfileName(profileName); + } + } + + // finally update our position + this.conversation.setLastRetrievedMessage(this.lastGot); + } + + static getPreviewFromAnnotation(annotation) { + const preview = { + title: annotation.value.linkPreviewTitle, + url: annotation.value.linkPreviewUrl, + image: { + isRaw: true, + caption: annotation.value.caption, + contentType: annotation.value.contentType, + digest: annotation.value.digest, + fileName: annotation.value.fileName, + flags: annotation.value.flags, + height: annotation.value.height, + id: annotation.value.id, + key: annotation.value.key, + size: annotation.value.size, + thumbnail: annotation.value.thumbnail, + url: annotation.value.url, + width: annotation.value.width, + }, + }; + return preview; + } + + static getAnnotationFromPreview(preview) { + const annotation = { + type: MESSAGE_ATTACHMENT_TYPE, + value: { + // Mandatory ADN fields + version: '1.0', + lokiType: LOKI_PREVIEW_TYPE, + + // Signal stuff we actually care about + linkPreviewTitle: preview.title, + linkPreviewUrl: preview.url, + caption: preview.image.caption, + contentType: preview.image.contentType, + digest: preview.image.digest, + fileName: preview.image.fileName, + flags: preview.image.flags, + height: preview.image.height, + id: preview.image.id, + key: preview.image.key, + size: preview.image.size, + thumbnail: preview.image.thumbnail, + url: preview.image.url, + width: preview.image.width, + }, + }; + return annotation; + } + + static getAnnotationFromAttachment(attachment) { + let type; + if (attachment.contentType.match(/^image/)) { + type = 'photo'; + } else if (attachment.contentType.match(/^video/)) { + type = 'video'; + } else if (attachment.contentType.match(/^audio/)) { + type = 'audio'; + } else { + type = 'other'; + } + const annotation = { + type: MESSAGE_ATTACHMENT_TYPE, + value: { + // Mandatory ADN fields + version: '1.0', + type, + lokiType: LOKI_ATTACHMENT_TYPE, + + // Signal stuff we actually care about + ...attachment, + }, + }; + return annotation; + } + + // create a message in the channel + async sendMessage(data, messageTimeStamp) { + const { quote, attachments, preview } = data; + const text = data.body || messageTimeStamp.toString(); + const attachmentAnnotations = attachments.map( + LokiPublicChannelAPI.getAnnotationFromAttachment + ); + const previewAnnotations = preview.map( + LokiPublicChannelAPI.getAnnotationFromPreview + ); + + const payload = { + text, + annotations: [ + { + type: 'network.loki.messenger.publicChat', + value: { + timestamp: messageTimeStamp, + }, + }, + ...attachmentAnnotations, + ...previewAnnotations, + ], + }; + + if (quote && quote.id) { + payload.annotations[0].value.quote = quote; + + // copied from model/message.js copyFromQuotedMessage + const collection = await Signal.Data.getMessagesBySentAt(quote.id, { + MessageCollection: Whisper.MessageCollection, + }); + const found = collection.find(item => { + const messageAuthor = item.getContact(); + + return messageAuthor && quote.author === messageAuthor.id; + }); + + if (found) { + const queryMessage = MessageController.register(found.id, found); + const replyTo = queryMessage.get('serverId'); + if (replyTo) { + payload.reply_to = replyTo; + } + } + } + const privKey = await this.getPrivateKey(); + const sigVer = 1; + const mockAdnMessage = { text }; + if (payload.reply_to) { + mockAdnMessage.reply_to = payload.reply_to; + } + const sigData = LokiPublicChannelAPI.getSigData( + sigVer, + payload.annotations[0].value, + attachmentAnnotations.map(anno => anno.value), + previewAnnotations.map(anno => anno.value), + mockAdnMessage + ); + const sig = await libsignal.Curve.async.calculateSignature( + privKey, + sigData + ); + payload.annotations[0].value.sig = StringView.arrayBufferToHex(sig); + payload.annotations[0].value.sigver = sigVer; + const res = await this.serverRequest(`${this.baseChannelUrl}/messages`, { + method: 'POST', + objBody: payload, + }); + if (!res.err && res.response) { + return res.response.data.id; + } + if (res.err) { + log.error(`POST ${this.baseChannelUrl}/messages failed`); + if (res.response && res.response.meta && res.response.meta.code === 401) { + log.error(`Got invalid token for ${this.serverAPI.token}`); + } + log.error(res.err); + log.error(res.response); + } else { + log.warn(res.response); + } + // there's no retry on desktop + // this is supposed to be after retries + return false; + } +} + +module.exports = LokiAppDotNetServerAPI; diff --git a/js/modules/loki_file_server_api.js b/js/modules/loki_file_server_api.js new file mode 100644 index 0000000000..c386094671 --- /dev/null +++ b/js/modules/loki_file_server_api.js @@ -0,0 +1,323 @@ +/* global log, libloki, process, window */ +/* global storage: false */ +/* global Signal: false */ +/* global log: false */ + +const LokiAppDotNetAPI = require('./loki_app_dot_net_api'); + +const DEVICE_MAPPING_USER_ANNOTATION_TYPE = + 'network.loki.messenger.devicemapping'; + +// const LOKIFOUNDATION_DEVFILESERVER_PUBKEY = +// 'BSZiMVxOco/b3sYfaeyiMWv/JnqokxGXkHoclEx8TmZ6'; +const LOKIFOUNDATION_FILESERVER_PUBKEY = + 'BWJQnVm97sQE3Q1InB4Vuo+U/T1hmwHBv0ipkiv8tzEc'; + +// can have multiple of these instances as each user can have a +// different home server +class LokiFileServerInstance { + constructor(ourKey) { + this.ourKey = ourKey; + + // do we have their pubkey locally? + /* + // get remote pubKey + this._server.serverRequest('loki/v1/public_key').then(keyRes => { + // we don't need to delay to protect identity because the token request + // should only be done over lokinet-lite + this.delayToken = true; + if (keyRes.err || !keyRes.response || !keyRes.response.data) { + if (keyRes.err) { + log.error(`Error ${keyRes.err}`); + } + } else { + // store it + this.pubKey = dcodeIO.ByteBuffer.wrap( + keyRes.response.data, + 'base64' + ).toArrayBuffer(); + // write it to a file + } + }); + */ + // Hard coded + this.pubKey = window.Signal.Crypto.base64ToArrayBuffer( + LOKIFOUNDATION_FILESERVER_PUBKEY + ); + if (this.pubKey.byteLength && this.pubKey.byteLength !== 33) { + log.error( + 'FILESERVER PUBKEY is invalid, length:', + this.pubKey.byteLength + ); + process.exit(1); + } + } + + // FIXME: this is not file-server specific + // and is currently called by LokiAppDotNetAPI. + // LokiAppDotNetAPI (base) should not know about LokiFileServer. + async establishConnection(serverUrl, options) { + // why don't we extend this? + this._server = new LokiAppDotNetAPI(this.ourKey, serverUrl); + + // configure proxy + this._server.pubKey = this.pubKey; + + if (options !== undefined && options.skipToken) { + return; + } + + // get a token for multidevice + const gotToken = await this._server.getOrRefreshServerToken(); + // TODO: Handle this failure gracefully + if (!gotToken) { + log.error('You are blacklisted form this home server'); + } + } + async getUserDeviceMapping(pubKey) { + const annotations = await this._server.getUserAnnotations(pubKey); + const deviceMapping = annotations.find( + annotation => annotation.type === DEVICE_MAPPING_USER_ANNOTATION_TYPE + ); + return deviceMapping ? deviceMapping.value : null; + } + + async verifyUserObjectDeviceMap(pubKeys, isRequest, iterator) { + const users = await this._server.getUsers(pubKeys); + + // go through each user and find deviceMap annotations + const notFoundUsers = []; + await Promise.all( + users.map(async user => { + let found = false; + if (!user.annotations || !user.annotations.length) { + log.info( + `verifyUserObjectDeviceMap no annotation for ${user.username}` + ); + return; + } + const mappingNote = user.annotations.find( + note => note.type === DEVICE_MAPPING_USER_ANNOTATION_TYPE + ); + const { authorisations } = mappingNote.value; + if (!Array.isArray(authorisations)) { + return; + } + await Promise.all( + authorisations.map(async auth => { + // only skip, if in secondary search mode + if (isRequest && auth.secondaryDevicePubKey !== user.username) { + // this is not the authorization we're looking for + log.info( + `Request and ${auth.secondaryDevicePubKey} != ${user.username}` + ); + return; + } + const valid = await libloki.crypto.validateAuthorisation(auth); + if (valid && iterator(user.username, auth)) { + found = true; + } + }) + ); // end map authorisations + + if (!found) { + notFoundUsers.push(user.username); + } + }) + ); // end map users + // log.info('done with users', users.length); + return notFoundUsers; + } + + // verifies list of pubKeys for any deviceMappings + // returns the relevant primary pubKeys + async verifyPrimaryPubKeys(pubKeys) { + const newSlavePrimaryMap = {}; // new slave to primary map + // checkSig disabled for now + // const checkSigs = {}; // cache for authorisation + const primaryPubKeys = []; + const result = { + verifiedPrimaryPKs: [], + slaveMap: {}, + }; + + // go through multiDeviceResults and get primary Pubkey + await this.verifyUserObjectDeviceMap(pubKeys, true, (slaveKey, auth) => { + // if we already have this key for a different device + if ( + newSlavePrimaryMap[slaveKey] && + newSlavePrimaryMap[slaveKey] !== auth.primaryDevicePubKey + ) { + log.warn( + `file server user annotation primaryKey mismatch, had ${ + newSlavePrimaryMap[slaveKey] + } now ${auth.primaryDevicePubKey} for ${slaveKey}` + ); + return; + } + // at this point it's valid + + // add to primaryPubKeys + if (primaryPubKeys.indexOf(`@${auth.primaryDevicePubKey}`) === -1) { + primaryPubKeys.push(`@${auth.primaryDevicePubKey}`); + } + + // add authorisation cache + /* + if (checkSigs[`${auth.primaryDevicePubKey}_${slaveKey}`] !== undefined) { + log.warn( + `file server ${auth.primaryDevicePubKey} to ${slaveKey} double signed` + ); + } + checkSigs[`${auth.primaryDevicePubKey}_${slaveKey}`] = auth; + */ + + // add map to newSlavePrimaryMap + newSlavePrimaryMap[slaveKey] = auth.primaryDevicePubKey; + }); // end verifyUserObjectDeviceMap + + // no valid primary pubkeys to check + if (!primaryPubKeys.length) { + // log.warn(`no valid primary pubkeys to check ${pubKeys}`); + // do we want to update slavePrimaryMap? + return result; + } + + const verifiedPrimaryPKs = []; + + // get a list of all of primary pubKeys to verify the secondaryDevice assertion + const notFoundUsers = await this.verifyUserObjectDeviceMap( + primaryPubKeys, + false, + primaryKey => { + // add to verified list if we don't already have it + if (verifiedPrimaryPKs.indexOf(`@${primaryKey}`) === -1) { + verifiedPrimaryPKs.push(`@${primaryKey}`); + } + + // assuming both are ordered the same way + // make sure our secondary and primary authorization match + /* + if ( + JSON.stringify(checkSigs[ + `${auth.primaryDevicePubKey}_${auth.secondaryDevicePubKey}` + ]) !== JSON.stringify(auth) + ) { + // should hopefully never happen + // it did, old pairing data, I think... + log.warn( + `Valid authorizations from ${ + auth.secondaryDevicePubKey + } does not match ${primaryKey}` + ); + return false; + } + */ + return true; + } + ); // end verifyUserObjectDeviceMap + + // remove from newSlavePrimaryMap if no valid mapping is found + notFoundUsers.forEach(primaryPubKey => { + Object.keys(newSlavePrimaryMap).forEach(slaveKey => { + if (newSlavePrimaryMap[slaveKey] === primaryPubKey) { + log.warn( + `removing unverifible ${slaveKey} to ${primaryPubKey} mapping` + ); + delete newSlavePrimaryMap[slaveKey]; + } + }); + }); + + log.info(`Updated device mappings ${JSON.stringify(newSlavePrimaryMap)}`); + + result.verifiedPrimaryPKs = verifiedPrimaryPKs; + result.slaveMap = newSlavePrimaryMap; + return result; + } +} + +// extends LokiFileServerInstance with functions we'd only perform on our own home server +// so we don't accidentally send info to the wrong file server +class LokiHomeServerInstance extends LokiFileServerInstance { + _setOurDeviceMapping(authorisations, isPrimary) { + const content = { + isPrimary: isPrimary ? '1' : '0', + authorisations, + }; + return this._server.setSelfAnnotation( + DEVICE_MAPPING_USER_ANNOTATION_TYPE, + content + ); + } + + async updateOurDeviceMapping() { + const isPrimary = !storage.get('isSecondaryDevice'); + let authorisations; + if (isPrimary) { + authorisations = await Signal.Data.getGrantAuthorisationsForPrimaryPubKey( + this.ourKey + ); + } else { + authorisations = [ + await Signal.Data.getGrantAuthorisationForSecondaryPubKey(this.ourKey), + ]; + } + return this._setOurDeviceMapping(authorisations, isPrimary); + } + + // you only upload to your own home server + // you can download from any server... + uploadAvatar(data) { + return this._server.uploadAvatar(data); + } + + uploadPrivateAttachment(data) { + return this._server.uploadData(data); + } + + clearOurDeviceMappingAnnotations() { + return this._server.setSelfAnnotation( + DEVICE_MAPPING_USER_ANNOTATION_TYPE, + null + ); + } +} + +// this will be our instance factory +class LokiFileServerFactoryAPI { + constructor(ourKey) { + this.ourKey = ourKey; + this.servers = []; + } + + establishHomeConnection(serverUrl) { + let thisServer = this.servers.find( + server => server._server.baseServerUrl === serverUrl + ); + if (!thisServer) { + thisServer = new LokiHomeServerInstance(this.ourKey); + log.info(`Registering HomeServer ${serverUrl}`); + thisServer.establishConnection(serverUrl); + this.servers.push(thisServer); + } + return thisServer; + } + + async establishConnection(serverUrl) { + let thisServer = this.servers.find( + server => server._server.baseServerUrl === serverUrl + ); + if (!thisServer) { + thisServer = new LokiFileServerInstance(this.ourKey); + log.info(`Registering FileServer ${serverUrl}`); + await thisServer.establishConnection(serverUrl, { skipToken: true }); + this.servers.push(thisServer); + } + return thisServer; + } +} +// smuggle some data out of this joint (for expire.js/version upgrade check) +LokiFileServerFactoryAPI.secureRpcPubKey = LOKIFOUNDATION_FILESERVER_PUBKEY; + +module.exports = LokiFileServerFactoryAPI; diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index 102ea30903..9ff394f411 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -1,281 +1,372 @@ /* eslint-disable no-await-in-loop */ /* eslint-disable no-loop-func */ -/* global log, dcodeIO, window, callWorker, lokiP2pAPI, lokiSnodeAPI, textsecure */ +/* global log, dcodeIO, window, callWorker, lokiSnodeAPI, textsecure */ const _ = require('lodash'); -const { rpc } = require('./loki_rpc'); +const { lokiRpc } = require('./loki_rpc'); -// Will be raised (to 3?) when we get more nodes -const MINIMUM_SUCCESSFUL_REQUESTS = 2; +const DEFAULT_CONNECTIONS = 3; +const MAX_ACCEPTABLE_FAILURES = 1; const LOKI_LONGPOLL_HEADER = 'X-Loki-Long-Poll'; +function sleepFor(time) { + return new Promise(resolve => { + setTimeout(() => resolve(), time); + }); +} + +const filterIncomingMessages = async messages => { + const incomingHashes = messages.map(m => m.hash); + const dupHashes = await window.Signal.Data.getSeenMessagesByHashList( + incomingHashes + ); + const newMessages = messages.filter(m => !dupHashes.includes(m.hash)); + if (newMessages.length) { + const newHashes = newMessages.map(m => ({ + expiresAt: m.expiration, + hash: m.hash, + })); + await window.Signal.Data.saveSeenMessageHashes(newHashes); + } + return newMessages; +}; + +const calcNonce = (messageEventData, pubKey, data64, timestamp, ttl) => { + const difficulty = window.storage.get('PoWDifficulty', null); + // Nonce is returned as a base64 string to include in header + window.Whisper.events.trigger('calculatingPoW', messageEventData); + return callWorker('calcPoW', timestamp, ttl, pubKey, data64, difficulty); +}; + class LokiMessageAPI { - constructor({ snodeServerPort }) { - this.snodeServerPort = snodeServerPort ? `:${snodeServerPort}` : ''; + constructor(ourKey) { this.jobQueue = new window.JobQueue(); + this.sendingData = {}; + this.ourKey = ourKey; } - async sendMessage(pubKey, data, messageTimeStamp, ttl, isPing = false) { - const timestamp = Date.now(); - + async sendMessage(pubKey, data, messageTimeStamp, ttl, options = {}) { + const { + isPublic = false, + numConnections = DEFAULT_CONNECTIONS, + publicSendData = null, + } = options; // Data required to identify a message in a conversation const messageEventData = { pubKey, timestamp: messageTimeStamp, }; - const data64 = dcodeIO.ByteBuffer.wrap(data).toString('base64'); - const p2pDetails = lokiP2pAPI.getContactP2pDetails(pubKey); - if (p2pDetails && (isPing || p2pDetails.isOnline)) { - try { - const port = p2pDetails.port ? `:${p2pDetails.port}` : ''; - - await rpc(p2pDetails.address, port, 'store', { - data: data64, - }); - lokiP2pAPI.setContactOnline(pubKey); - window.Whisper.events.trigger('p2pMessageSent', messageEventData); - if (isPing) { - log.info(`Successfully pinged ${pubKey}`); - } else { - log.info(`Successful p2p message to ${pubKey}`); - } - return; - } catch (e) { - lokiP2pAPI.setContactOffline(pubKey); - if (isPing) { - // If this was just a ping, we don't bother sending to storage server - log.warn('Ping failed, contact marked offline', e); - return; - } - log.warn('Failed to send P2P message, falling back to storage', e); + if (isPublic) { + if (!publicSendData) { + throw new window.textsecure.PublicChatError( + 'Missing public send data for public chat message' + ); } + const res = await publicSendData.sendMessage(data, messageTimeStamp); + if (res === false) { + throw new window.textsecure.PublicChatError( + 'Failed to send public chat message' + ); + } + messageEventData.serverId = res; + window.Whisper.events.trigger('publicMessageSent', messageEventData); + return; } - // Nonce is returned as a base64 string to include in header - let nonce; - try { - window.Whisper.events.trigger('calculatingPoW', messageEventData); - const development = window.getEnvironment() !== 'production'; - nonce = await callWorker( - 'calcPoW', - timestamp, - ttl, - pubKey, - data64, - development - ); - } catch (err) { - // Something went horribly wrong - throw err; - } - - const completedNodes = []; - const failedNodes = []; - let successfulRequests = 0; - let canResolve = true; - - let swarmNodes = await lokiSnodeAPI.getSwarmNodesForPubKey(pubKey); + const data64 = dcodeIO.ByteBuffer.wrap(data).toString('base64'); - const nodeComplete = nodeUrl => { - completedNodes.push(nodeUrl); - swarmNodes = swarmNodes.filter(node => node !== nodeUrl); + const timestamp = Date.now(); + const nonce = await calcNonce( + messageEventData, + window.getStoragePubKey(pubKey), + data64, + timestamp, + ttl + ); + // Using timestamp as a unique identifier + const swarm = await lokiSnodeAPI.getSwarmNodesForPubKey(pubKey); + this.sendingData[timestamp] = { + swarm, + hasFreshList: false, }; + if (this.sendingData[timestamp].swarm.length < numConnections) { + await this.refreshSendingSwarm(pubKey, timestamp); + } - const doRequest = async nodeUrl => { - const params = { - pubKey, - ttl: ttl.toString(), - nonce, - timestamp: timestamp.toString(), - data: data64, - }; - - try { - await rpc(`http://${nodeUrl}`, this.snodeServerPort, 'store', params); - - nodeComplete(nodeUrl); - successfulRequests += 1; - } catch (e) { - log.warn('Loki send message:', e); - if (e instanceof textsecure.WrongSwarmError) { - const { newSwarm } = e; - await lokiSnodeAPI.updateSwarmNodes(pubKey, newSwarm); - completedNodes.push(nodeUrl); - } else if (e instanceof textsecure.NotFoundError) { - canResolve = false; - } else if (e instanceof textsecure.HTTPError) { - // We mark the node as complete as we could still reach it - nodeComplete(nodeUrl); - } else { - const removeNode = await lokiSnodeAPI.unreachableNode( - pubKey, - nodeUrl - ); - if (removeNode) { - log.error('Loki send message:', e); - nodeComplete(nodeUrl); - failedNodes.push(nodeUrl); - } - } - } + const params = { + pubKey, + ttl: ttl.toString(), + nonce, + timestamp: timestamp.toString(), + data: data64, }; - - while (successfulRequests < MINIMUM_SUCCESSFUL_REQUESTS) { - if (!canResolve) { - throw new window.textsecure.DNSResolutionError('Sending messages'); - } - if (swarmNodes.length === 0) { - const freshNodes = await lokiSnodeAPI.getFreshSwarmNodes(pubKey); - const goodNodes = _.difference(freshNodes, failedNodes); - await lokiSnodeAPI.updateSwarmNodes(pubKey, goodNodes); - swarmNodes = _.difference(freshNodes, completedNodes); - if (swarmNodes.length === 0) { - if (successfulRequests !== 0) { - // TODO: Decide how to handle some completed requests but not enough - log.warn(`Partially successful storage message to ${pubKey}`); - return; - } - throw new window.textsecure.EmptySwarmError( - pubKey, - 'Ran out of swarm nodes to query' - ); + const promises = []; + let completedConnections = 0; + for (let i = 0; i < numConnections; i += 1) { + const connectionPromise = this.openSendConnection(params).finally(() => { + completedConnections += 1; + if (completedConnections >= numConnections) { + delete this.sendingData[timestamp]; } - } + }); + promises.push(connectionPromise); + } - const remainingRequests = - MINIMUM_SUCCESSFUL_REQUESTS - successfulRequests; + // Taken from https://stackoverflow.com/questions/51160260/clean-way-to-wait-for-first-true-returned-by-promise + // The promise returned by this function will resolve true when the first promise + // in ps resolves true *or* it will resolve false when all of ps resolve false + const firstTrue = ps => { + const newPs = ps.map( + p => + new Promise( + // eslint-disable-next-line more/no-then + (resolve, reject) => p.then(v => v && resolve(true), reject) + ) + ); + // eslint-disable-next-line more/no-then + newPs.push(Promise.all(ps).then(() => false)); + return Promise.race(newPs); + }; - await Promise.all( - swarmNodes - .splice(0, remainingRequests) - .map(nodeUrl => doRequest(nodeUrl)) + let success; + try { + // eslint-disable-next-line more/no-then + success = await firstTrue(promises); + } catch (e) { + if (e instanceof textsecure.WrongDifficultyError) { + // Force nonce recalculation + // NOTE: Currently if there are snodes with conflicting difficulties we + // will send the message twice (or more). Won't affect client side but snodes + // could store the same message multiple times because they will have different + // timestamps (and therefore nonces) + await this.sendMessage(pubKey, data, messageTimeStamp, ttl, options); + return; + } + throw e; + } + if (!success) { + throw new window.textsecure.EmptySwarmError( + pubKey, + 'Ran out of swarm nodes to query' ); } log.info(`Successful storage message to ${pubKey}`); } - async retrieveMessages(callback) { - const ourKey = window.textsecure.storage.user.getNumber(); - const completedNodes = []; - let canResolve = true; - let successfulRequests = 0; - - let ourSwarmNodes = await lokiSnodeAPI.getOurSwarmNodes(); + async refreshSendingSwarm(pubKey, timestamp) { + const freshNodes = await lokiSnodeAPI.getFreshSwarmNodes(pubKey); + await lokiSnodeAPI.updateSwarmNodes(pubKey, freshNodes); + this.sendingData[timestamp].swarm = freshNodes; + this.sendingData[timestamp].hasFreshList = true; + return true; + } - const filterIncomingMessages = async messages => { - const incomingHashes = messages.map(m => m.hash); - const dupHashes = await window.Signal.Data.getSeenMessagesByHashList( - incomingHashes + async openSendConnection(params) { + while (!_.isEmpty(this.sendingData[params.timestamp].swarm)) { + const snode = this.sendingData[params.timestamp].swarm.shift(); + // TODO: Revert back to using snode address instead of IP + const successfulSend = await this.sendToNode( + snode.ip, + snode.port, + snode, + params ); - const newMessages = messages.filter(m => !dupHashes.includes(m.hash)); - if (newMessages.length) { - const newHashes = newMessages.map(m => ({ - expiresAt: m.expiration, - hash: m.hash, - })); - await window.Signal.Data.saveSeenMessageHashes(newHashes); + if (successfulSend) { + return true; } - return newMessages; - }; - - const nodeComplete = nodeUrl => { - completedNodes.push(nodeUrl); - delete ourSwarmNodes[nodeUrl]; - }; + } - const doRequest = async (nodeUrl, nodeData) => { - const params = { - pubKey: ourKey, - lastHash: nodeData.lastHash || '', - }; - const options = { - timeout: 40000, - headers: { - [LOKI_LONGPOLL_HEADER]: true, - }, - }; + if (!this.sendingData[params.timestamp].hasFreshList) { + // Ensure that there is only a single refresh per outgoing message + if (!this.sendingData[params.timestamp].refreshPromise) { + this.sendingData[ + params.timestamp + ].refreshPromise = this.refreshSendingSwarm( + params.pubKey, + params.timestamp + ); + } + await this.sendingData[params.timestamp].refreshPromise; + // Retry with a fresh list again + return this.openSendConnection(params); + } + return false; + } + async sendToNode(address, port, targetNode, params) { + let successiveFailures = 0; + while (successiveFailures < MAX_ACCEPTABLE_FAILURES) { + await sleepFor(successiveFailures * 500); try { - const result = await rpc( - `http://${nodeUrl}`, - this.snodeServerPort, - 'retrieve', + const result = await lokiRpc( + `https://${address}`, + port, + 'store', params, - options + {}, + '/storage_rpc/v1', + targetNode ); - nodeComplete(nodeUrl); - successfulRequests += 1; - - if (Array.isArray(result.messages) && result.messages.length) { - const lastMessage = _.last(result.messages); - lokiSnodeAPI.updateLastHash( - nodeUrl, - lastMessage.hash, - lastMessage.expiration - ); - const filteredMessages = await this.jobQueue.add(() => - filterIncomingMessages(result.messages) - ); - if (filteredMessages.length) { - callback(filteredMessages); - } + // Make sure we aren't doing too much PoW + const currentDifficulty = window.storage.get('PoWDifficulty', null); + const newDifficulty = result.difficulty; + if (newDifficulty != null && newDifficulty !== currentDifficulty) { + window.storage.put('PoWDifficulty', newDifficulty); } + return true; } catch (e) { - log.warn('Loki retrieve messages:', e); + log.warn('Loki send message:', e); if (e instanceof textsecure.WrongSwarmError) { const { newSwarm } = e; - await lokiSnodeAPI.updateOurSwarmNodes(newSwarm); - completedNodes.push(nodeUrl); + await lokiSnodeAPI.updateSwarmNodes(params.pubKey, newSwarm); + this.sendingData[params.timestamp].swarm = newSwarm; + this.sendingData[params.timestamp].hasFreshList = true; + return false; + } else if (e instanceof textsecure.WrongDifficultyError) { + const { newDifficulty } = e; + if (!Number.isNaN(newDifficulty)) { + window.storage.put('PoWDifficulty', newDifficulty); + } + throw e; } else if (e instanceof textsecure.NotFoundError) { - canResolve = false; + // TODO: Handle resolution error + } else if (e instanceof textsecure.TimestampError) { + log.warn('Timestamp is invalid'); + throw e; } else if (e instanceof textsecure.HTTPError) { - // We mark the node as complete as we could still reach it - nodeComplete(nodeUrl); - } else { - const removeNode = await lokiSnodeAPI.unreachableNode( - ourKey, - nodeUrl - ); - if (removeNode) { - log.error('Loki retrieve messages:', e); - nodeComplete(nodeUrl); - } + // TODO: Handle working connection but error response + const body = await e.response.text(); + log.warn('HTTPError body:', body); } + successiveFailures += 1; } - }; + } + log.error(`Failed to send to node: ${address}`); + await lokiSnodeAPI.unreachableNode(params.pubKey, address); + return false; + } - while (successfulRequests < MINIMUM_SUCCESSFUL_REQUESTS) { - if (!canResolve) { - throw new window.textsecure.DNSResolutionError('Retrieving messages'); - } - if (Object.keys(ourSwarmNodes).length === 0) { - ourSwarmNodes = await lokiSnodeAPI.getOurSwarmNodes(); - // Filter out the nodes we have already got responses from - completedNodes.forEach(nodeUrl => delete ourSwarmNodes[nodeUrl]); + async openRetrieveConnection(stopPollingPromise, callback) { + let stopPollingResult = false; + // When message_receiver restarts from onoffline/ononline events it closes + // http-resources, which will then resolve the stopPollingPromise with true. We then + // want to cancel these polling connections because new ones will be created + // eslint-disable-next-line more/no-then + stopPollingPromise.then(result => { + stopPollingResult = result; + }); + + while (!stopPollingResult && !_.isEmpty(this.ourSwarmNodes)) { + const address = Object.keys(this.ourSwarmNodes)[0]; + const nodeData = this.ourSwarmNodes[address]; + delete this.ourSwarmNodes[address]; + let successiveFailures = 0; + while ( + !stopPollingResult && + successiveFailures < MAX_ACCEPTABLE_FAILURES + ) { + await sleepFor(successiveFailures * 1000); - if (Object.keys(ourSwarmNodes).length === 0) { - if (successfulRequests !== 0) { - // TODO: Decide how to handle some completed requests but not enough - return; + try { + // TODO: Revert back to using snode address instead of IP + let messages = await this.retrieveNextMessages(nodeData.ip, nodeData); + successiveFailures = 0; + if (messages.length) { + const lastMessage = _.last(messages); + nodeData.lastHash = lastMessage.hash; + await lokiSnodeAPI.updateLastHash( + address, + lastMessage.hash, + lastMessage.expiration + ); + messages = await this.jobQueue.add(() => + filterIncomingMessages(messages) + ); + } + // Execute callback even with empty array to signal online status + callback(messages); + } catch (e) { + log.warn('Loki retrieve messages:', e.code, e.message); + if (e instanceof textsecure.WrongSwarmError) { + const { newSwarm } = e; + await lokiSnodeAPI.updateSwarmNodes(this.ourKey, newSwarm); + for (let i = 0; i < newSwarm.length; i += 1) { + const lastHash = await window.Signal.Data.getLastHashBySnode( + newSwarm[i] + ); + this.ourSwarmNodes[newSwarm[i]] = { + lastHash, + }; + } + // Try another snode + break; + } else if (e instanceof textsecure.NotFoundError) { + // DNS/Lokinet error, needs to bubble up + throw new window.textsecure.DNSResolutionError( + 'Retrieving messages' + ); } - throw new window.textsecure.EmptySwarmError( - ourKey, - 'Ran out of swarm nodes to query' - ); + successiveFailures += 1; } } + if (successiveFailures >= MAX_ACCEPTABLE_FAILURES) { + await lokiSnodeAPI.unreachableNode(this.ourKey, address); + } + } + } + + async retrieveNextMessages(nodeUrl, nodeData) { + const params = { + pubKey: this.ourKey, + lastHash: nodeData.lastHash || '', + }; + const options = { + timeout: 40000, + headers: { + [LOKI_LONGPOLL_HEADER]: true, + }, + }; - const remainingRequests = - MINIMUM_SUCCESSFUL_REQUESTS - successfulRequests; + const result = await lokiRpc( + `https://${nodeUrl}`, + nodeData.port, + 'retrieve', + params, + options, + '/storage_rpc/v1', + nodeData + ); + return result.messages || []; + } - await Promise.all( - Object.entries(ourSwarmNodes) - .splice(0, remainingRequests) - .map(([nodeUrl, nodeData]) => doRequest(nodeUrl, nodeData)) + async startLongPolling(numConnections, stopPolling, callback) { + this.ourSwarmNodes = {}; + let nodes = await lokiSnodeAPI.getSwarmNodesForPubKey(this.ourKey); + if (nodes.length < numConnections) { + await lokiSnodeAPI.refreshSwarmNodesForPubKey(this.ourKey); + nodes = await lokiSnodeAPI.getSwarmNodesForPubKey(this.ourKey); + } + for (let i = 0; i < nodes.length; i += 1) { + const lastHash = await window.Signal.Data.getLastHashBySnode( + nodes[i].address ); + this.ourSwarmNodes[nodes[i].address] = { + ...nodes[i], + lastHash, + }; } + + const promises = []; + + for (let i = 0; i < numConnections; i += 1) { + promises.push(this.openRetrieveConnection(stopPolling, callback)); + } + + // blocks until all snodes in our swarms have been removed from the list + // or if there is network issues (ENOUTFOUND due to lokinet) + await Promise.all(promises); } } diff --git a/js/modules/loki_p2p_api.js b/js/modules/loki_p2p_api.js deleted file mode 100644 index 76b42484ed..0000000000 --- a/js/modules/loki_p2p_api.js +++ /dev/null @@ -1,128 +0,0 @@ -/* global setTimeout, clearTimeout */ - -const EventEmitter = require('events'); - -const offlinePingTime = 2 * 60 * 1000; // 2 minutes - -class LokiP2pAPI extends EventEmitter { - constructor(ourKey) { - super(); - this.contactP2pDetails = {}; - this.ourKey = ourKey; - } - - reset() { - Object.keys(this.contactP2pDetails).forEach(key => { - clearTimeout(this.contactP2pDetails[key].pingTimer); - delete this.contactP2pDetails[key]; - }); - } - - updateContactP2pDetails(pubKey, address, port, isPing = false) { - // Stagger the timers so the friends don't ping each other at the same time - const timerDuration = - pubKey < this.ourKey - ? 60 * 1000 // 1 minute - : 2 * 60 * 1000; // 2 minutes - - if (!this.contactP2pDetails[pubKey]) { - // We didn't have this contact's details - this.contactP2pDetails[pubKey] = { - address, - port, - timerDuration, - pingTimer: null, - isOnline: false, - }; - if (isPing) { - this.setContactOnline(pubKey); - return; - } - // Try ping - this.pingContact(pubKey); - return; - } - - // We already had this contact's details - const baseDetails = { ...this.contactP2pDetails[pubKey] }; - - if (isPing) { - // Received a ping - // Update details in case they are new and mark online - this.contactP2pDetails[pubKey].address = address; - this.contactP2pDetails[pubKey].port = port; - this.setContactOnline(pubKey); - return; - } - - // Received a storage broadcast message - if ( - baseDetails.isOnline || - baseDetails.address !== address || - baseDetails.port !== port - ) { - // Had the contact marked as online and details we had were the same - this.pingContact(pubKey); - return; - } - - // Had the contact marked as offline or got new details - this.contactP2pDetails[pubKey].address = address; - this.contactP2pDetails[pubKey].port = port; - this.setContactOffline(pubKey); - this.pingContact(pubKey); - } - - getContactP2pDetails(pubKey) { - return this.contactP2pDetails[pubKey] || null; - } - - isContactOnline(pubKey) { - const contactDetails = this.contactP2pDetails[pubKey]; - return !!(contactDetails && contactDetails.isOnline); - } - - setContactOffline(pubKey) { - this.emit('offline', pubKey); - if (!this.contactP2pDetails[pubKey]) { - return; - } - clearTimeout(this.contactP2pDetails[pubKey].pingTimer); - this.contactP2pDetails[pubKey].pingTimer = setTimeout( - this.pingContact.bind(this), - offlinePingTime, - pubKey - ); - this.contactP2pDetails[pubKey].isOnline = false; - } - - setContactOnline(pubKey) { - if (!this.contactP2pDetails[pubKey]) { - return; - } - this.emit('online', pubKey); - clearTimeout(this.contactP2pDetails[pubKey].pingTimer); - this.contactP2pDetails[pubKey].isOnline = true; - this.contactP2pDetails[pubKey].pingTimer = setTimeout( - this.pingContact.bind(this), - this.contactP2pDetails[pubKey].timerDuration, - pubKey - ); - } - - isOnline(pubKey) { - return !!( - this.contactP2pDetails[pubKey] && this.contactP2pDetails[pubKey].isOnline - ); - } - - pingContact(pubKey) { - if (!this.contactP2pDetails[pubKey]) { - // Don't ping if we don't have their details - return; - } - this.emit('pingContact', pubKey); - } -} - -module.exports = LokiP2pAPI; diff --git a/js/modules/loki_public_chat_api.js b/js/modules/loki_public_chat_api.js new file mode 100644 index 0000000000..8e4cd0f3dc --- /dev/null +++ b/js/modules/loki_public_chat_api.js @@ -0,0 +1,186 @@ +/* global log, window, process */ +const EventEmitter = require('events'); +const nodeFetch = require('node-fetch'); +const LokiAppDotNetAPI = require('./loki_app_dot_net_api'); + +class LokiPublicChatFactoryAPI extends EventEmitter { + constructor(ourKey) { + super(); + this.ourKey = ourKey; + this.servers = []; + this.allMembers = []; + // Multidevice states + this.primaryUserProfileName = {}; + } + + async close() { + await Promise.all(this.servers.map(server => server.close())); + } + + static async validServer(serverUrl) { + // test to make sure it's online (and maybe has a valid SSL cert) + try { + // allow .loki (may only need an agent but not sure + // until we have a .loki to test with) + process.env.NODE_TLS_REJECT_UNAUTHORIZED = serverUrl.match(/\.loki\//) + ? 0 + : 1; + await nodeFetch(serverUrl); + process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; + // const txt = await res.text(); + } catch (e) { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; + log.warn(`failing to created ${serverUrl}`, e.code, e.message); + // bail out if not valid enough + return false; + } + return true; + } + + // server getter/factory + async findOrCreateServer(serverUrl) { + let thisServer = this.servers.find( + server => server.baseServerUrl === serverUrl + ); + if (!thisServer) { + log.info(`LokiAppDotNetAPI creating ${serverUrl}`); + + if (!await this.constructor.validServer(serverUrl)) { + return null; + } + + // after verification then we can start up all the pollers + thisServer = new LokiAppDotNetAPI(this.ourKey, serverUrl); + + const gotToken = await thisServer.getOrRefreshServerToken(); + if (!gotToken) { + log.warn(`Invalid server ${serverUrl}`); + return null; + } + log.info(`set token ${thisServer.token} for ${serverUrl}`); + + this.servers.push(thisServer); + } + return thisServer; + } + + static async getServerTime() { + const url = `${window.getDefaultFileServer()}/loki/v1/time`; + let timestamp = NaN; + + try { + const res = await nodeFetch(url); + if (res.ok) { + timestamp = await res.text(); + } + } catch (e) { + return timestamp; + } + + return Number(timestamp); + } + + static async getTimeDifferential() { + // Get time differential between server and client in seconds + const serverTime = await this.getServerTime(); + const clientTime = Math.ceil(Date.now() / 1000); + + if (Number.isNaN(serverTime)) { + return 0; + } + return serverTime - clientTime; + } + + static async setClockParams() { + // Set server-client time difference + const maxTimeDifferential = 30; + const timeDifferential = await this.getTimeDifferential(); + + window.clientClockSynced = Math.abs(timeDifferential) < maxTimeDifferential; + return window.clientClockSynced; + } + + // channel getter/factory + async findOrCreateChannel(serverUrl, channelId, conversationId) { + const server = await this.findOrCreateServer(serverUrl); + if (!server) { + log.error(`Failed to create server for: ${serverUrl}`); + return null; + } + return server.findOrCreateChannel(this, channelId, conversationId); + } + + // deallocate resources server uses + unregisterChannel(serverUrl, channelId) { + const i = this.servers.findIndex( + server => server.baseServerUrl === serverUrl + ); + if (i === -1) { + log.warn(`Tried to unregister from nonexistent server ${serverUrl}`); + return; + } + const thisServer = this.servers[i]; + if (!thisServer) { + log.warn(`Tried to unregister from nonexistent server ${i}`); + return; + } + thisServer.unregisterChannel(channelId); + this.servers.splice(i, 1); + } + + // shouldn't this be scoped per conversation? + async getListOfMembers() { + // enable in the next release + /* + let members = []; + await Promise.all(this.servers.map(async server => { + await Promise.all(server.channels.map(async channel => { + const newMembers = await channel.getSubscribers(); + members = [...members, ...newMembers]; + })); + })); + const results = members.map(member => { + return { authorPhoneNumber: member.username }; + }); + */ + return this.allMembers; + } + + // TODO: make this private (or remove altogether) when + // we switch to polling the server for group members + setListOfMembers(members) { + this.allMembers = members; + } + + async setProfileName(profileName) { + await Promise.all( + this.servers.map(async server => { + await server.setProfileName(profileName); + }) + ); + } + + async setHomeServer(homeServer) { + await Promise.all( + this.servers.map(async server => { + // this may fail + // but we can't create a sql table to remember to retry forever + // I think we just silently fail for now + await server.setHomeServer(homeServer); + }) + ); + } + + async setAvatar(url, profileKey) { + await Promise.all( + this.servers.map(async server => { + // this may fail + // but we can't create a sql table to remember to retry forever + // I think we just silently fail for now + await server.setAvatar(url, profileKey); + }) + ); + } +} + +module.exports = LokiPublicChatFactoryAPI; diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index 19e54b71a2..1cd99b9c71 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -1,13 +1,106 @@ -/* global log, libloki, textsecure */ +/* global log, libloki, textsecure, getStoragePubKey, lokiSnodeAPI, StringView, + libsignal, window, TextDecoder, TextEncoder, dcodeIO, process */ const nodeFetch = require('node-fetch'); +const https = require('https'); const { parse } = require('url'); +const snodeHttpsAgent = new https.Agent({ + rejectUnauthorized: false, +}); + const LOKI_EPHEMKEY_HEADER = 'X-Loki-EphemKey'; -const endpointBase = '/v1/storage_rpc'; +const endpointBase = '/storage_rpc/v1'; + +const decryptResponse = async (response, address) => { + let plaintext = false; + try { + const ciphertext = await response.text(); + plaintext = await libloki.crypto.snodeCipher.decrypt(address, ciphertext); + const result = plaintext === '' ? {} : JSON.parse(plaintext); + return result; + } catch (e) { + log.warn( + `Could not decrypt response [${plaintext}] from [${address}],`, + e.code, + e.message + ); + } + return {}; +}; + +// TODO: Don't allow arbitrary URLs, only snodes and loki servers +const sendToProxy = async (options = {}, targetNode) => { + const randSnode = await lokiSnodeAPI.getRandomSnodeAddress(); + + const url = `https://${randSnode.ip}:${randSnode.port}/proxy`; + + const snPubkeyHex = StringView.hexToArrayBuffer(targetNode.pubkey_x25519); + + const myKeys = window.libloki.crypto.snodeCipher._ephemeralKeyPair; + + const symmetricKey = libsignal.Curve.calculateAgreement( + snPubkeyHex, + myKeys.privKey + ); + + const textEncoder = new TextEncoder(); + const body = JSON.stringify(options); + + const plainText = textEncoder.encode(body); + const ivAndCiphertext = await window.libloki.crypto.DHEncrypt( + symmetricKey, + plainText + ); + + const firstHopOptions = { + method: 'POST', + body: ivAndCiphertext, + headers: { + 'X-Sender-Public-Key': StringView.arrayBufferToHex(myKeys.pubKey), + 'X-Target-Snode-Key': targetNode.pubkey_ed25519, + }, + }; + + // we only proxy to snodes... + process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; + const response = await nodeFetch(url, firstHopOptions); + process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; + + const ciphertext = await response.text(); + + const ciphertextBuffer = dcodeIO.ByteBuffer.wrap( + ciphertext, + 'base64' + ).toArrayBuffer(); + + const plaintextBuffer = await window.libloki.crypto.DHDecrypt( + symmetricKey, + ciphertextBuffer + ); + + const textDecoder = new TextDecoder(); + const plaintext = textDecoder.decode(plaintextBuffer); + + try { + const jsonRes = JSON.parse(plaintext); + // emulate nodeFetch response... + jsonRes.json = () => JSON.parse(jsonRes.body); + return jsonRes; + } catch (e) { + log.error( + 'lokiRpc sendToProxy error', + e.code, + e.message, + 'json', + plaintext + ); + } + return false; +}; // A small wrapper around node-fetch which deserializes response -const fetch = async (url, options = {}) => { +const lokiFetch = async (url, options = {}, targetNode = null) => { const timeout = options.timeout || 10000; const method = options.method || 'GET'; @@ -32,56 +125,69 @@ const fetch = async (url, options = {}) => { } } + const fetchOptions = { + ...options, + timeout, + method, + }; + if (url.match(/https:\/\//)) { + fetchOptions.agent = snodeHttpsAgent; + } + try { - const response = await nodeFetch(url, { - ...options, - timeout, - method, - }); + if (window.lokiFeatureFlags.useSnodeProxy && targetNode) { + const result = await sendToProxy(fetchOptions, targetNode); + return result.json(); + } + + if (url.match(/https:\/\//)) { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; + } + const response = await nodeFetch(url, fetchOptions); + // restore TLS checking + process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1; + let result; + // Wrong swarm if (response.status === 421) { - let newSwarm = await response.text(); if (doEncryptChannel) { - try { - newSwarm = await libloki.crypto.snodeCipher.decrypt( - address, - newSwarm - ); - } catch (e) { - log.warn(`Could not decrypt response from ${address}`, e); - } - try { - newSwarm = newSwarm === '' ? {} : JSON.parse(newSwarm); - } catch (e) { - log.warn(`Could not parse string to json ${newSwarm}`, e); - } + result = decryptResponse(response, address); + } else { + result = await response.json(); } + const newSwarm = result.snodes ? result.snodes : []; throw new textsecure.WrongSwarmError(newSwarm); } + // Wrong PoW difficulty + if (response.status === 432) { + if (doEncryptChannel) { + result = decryptResponse(response, address); + } else { + result = await response.json(); + } + const { difficulty } = result; + throw new textsecure.WrongDifficultyError(difficulty); + } + + if (response.status === 406) { + throw new textsecure.TimestampError( + 'Invalid Timestamp (check your clock)' + ); + } + if (!response.ok) { throw new textsecure.HTTPError('Loki_rpc error', response); } - let result; if (response.headers.get('Content-Type') === 'application/json') { result = await response.json(); } else if (options.responseType === 'arraybuffer') { result = await response.buffer(); + } else if (doEncryptChannel) { + result = decryptResponse(response, address); } else { result = await response.text(); - if (doEncryptChannel) { - try { - result = await libloki.crypto.snodeCipher.decrypt(address, result); - } catch (e) { - log.warn(`Could not decrypt response from ${address}`, e); - } - try { - result = result === '' ? {} : JSON.parse(result); - } catch (e) { - log.warn(`Could not parse string to json ${result}`, e); - } - } } return result; @@ -94,10 +200,30 @@ const fetch = async (url, options = {}) => { }; // Wrapper for a JSON RPC request -const rpc = (address, port, method, params, options = {}) => { +const lokiRpc = ( + address, + port, + method, + params, + options = {}, + endpoint = endpointBase, + targetNode +) => { const headers = options.headers || {}; - const url = `${address}${port}${endpointBase}`; + const portString = port ? `:${port}` : ''; + const url = `${address}${portString}${endpoint}`; + // TODO: The jsonrpc and body field will be ignored on storage server + if (params.pubKey) { + // Ensure we always take a copy + // eslint-disable-next-line no-param-reassign + params = { + ...params, + pubKey: getStoragePubKey(params.pubKey), + }; + } const body = { + jsonrpc: '2.0', + id: '0', method, params, }; @@ -106,12 +232,15 @@ const rpc = (address, port, method, params, options = {}) => { method: 'POST', ...options, body: JSON.stringify(body), - headers, + headers: { + 'Content-Type': 'application/json', + ...headers, + }, }; - return fetch(url, fetchOptions); + return lokiFetch(url, fetchOptions, targetNode); }; module.exports = { - rpc, + lokiRpc, }; diff --git a/js/modules/loki_rss_api.js b/js/modules/loki_rss_api.js new file mode 100644 index 0000000000..566a2afc16 --- /dev/null +++ b/js/modules/loki_rss_api.js @@ -0,0 +1,161 @@ +/* eslint-disable no-await-in-loop */ +/* eslint-disable no-loop-func */ +/* global log, window, textsecure */ + +const EventEmitter = require('events'); + +const PER_MIN = 60 * 1000; +const PER_HR = 60 * PER_MIN; +const RSS_POLL_EVERY = 1 * PER_HR; // once an hour + +function xml2json(xml) { + try { + let obj = {}; + if (xml.children.length > 0) { + for (let i = 0; i < xml.children.length; i += 1) { + const item = xml.children.item(i); + const { nodeName } = item; + + if (typeof obj[nodeName] === 'undefined') { + obj[nodeName] = xml2json(item); + } else { + if (typeof obj[nodeName].push === 'undefined') { + const old = obj[nodeName]; + + obj[nodeName] = []; + obj[nodeName].push(old); + } + obj[nodeName].push(xml2json(item)); + } + } + } else { + obj = xml.textContent; + } + return obj; + } catch (e) { + log.error(e.message); + } + return {}; +} + +class LokiRssAPI extends EventEmitter { + constructor(settings) { + super(); + // properties + this.feedUrl = settings.RSS_FEED; + this.groupId = settings.CONVO_ID; + this.feedTitle = settings.title; + this.closeable = settings.closeable; + // non configureable options + this.feedTimer = null; + // initial set up + this.getFeed(); + } + + async getFeed() { + // deal with file server proxy hardcoding + const map = { + 'https://loki.network/category/messenger-updates/feed/': + 'loki/v1/rss/messenger', + 'https://loki.network/feed/': 'loki/v1/rss/loki', + }; + if (map[this.feedUrl] === undefined) { + log.warn('LokiRssAPI unsupported rss feed', this.feedUrl); + return; + } + const result = await window.lokiFileServerAPI._server.serverRequest( + map[this.feedUrl] + ); + if (!result) { + log.error('LokiRssAPI empty rss proxy response'); + return; + } + if (!result.response) { + log.error('LokiRssAPI rss proxy error, no response', result); + return; + } + if (!result.response.data) { + log.error( + 'LokiRssAPI rss proxy error, no data, response', + result.response + ); + return; + } + const responseXML = result.response.data; + let feedDOM = {}; + try { + feedDOM = await new window.DOMParser().parseFromString( + responseXML, + 'text/xml' + ); + } catch (e) { + log.error('LokiRssAPI xml parsing error', e, responseXML); + return; + } + const feedObj = xml2json(feedDOM); + let receivedAt = new Date().getTime(); + + if (!feedObj || !feedObj.rss || !feedObj.rss.channel) { + log.error( + 'LokiRssAPI rss structure error', + feedObj, + feedDOM, + responseXML + ); + return; + } + if (!feedObj.rss.channel.item) { + // no records, not an error + return; + } + if (feedObj.rss.channel.item.constructor !== Array) { + // Treat single record as array for consistency + feedObj.rss.channel.item = [feedObj.rss.channel.item]; + } + feedObj.rss.channel.item.reverse().forEach(item => { + // log.debug('item', item) + + const pubDate = new Date(item.pubDate); + + // if we use group style, we can put the title in the source + const messageData = { + friendRequest: false, + source: this.groupId, + sourceDevice: 1, + timestamp: pubDate.getTime(), + serverTimestamp: pubDate.getTime(), + receivedAt, + isRss: true, + message: { + body: `

${item.title}

${item.description}`, + attachments: [], + group: { + id: this.groupId, + type: textsecure.protobuf.GroupContext.Type.DELIVER, + }, + flags: 0, + expireTimer: 0, + profileKey: null, + timestamp: pubDate.getTime(), + received_at: receivedAt, + sent_at: pubDate.getTime(), + quote: null, + contact: [], + preview: [], + profile: null, + }, + }; + receivedAt += 1; // Ensure different arrival times + this.emit('rssMessage', { + message: messageData, + }); + }); + const ref = this; + function callTimer() { + ref.getFeed(); + } + this.feedTimer = setTimeout(callTimer, RSS_POLL_EVERY); + } +} + +module.exports = LokiRssAPI; diff --git a/js/modules/loki_snode_api.js b/js/modules/loki_snode_api.js index 79667f13f2..eb6bdc2f53 100644 --- a/js/modules/loki_snode_api.js +++ b/js/modules/loki_snode_api.js @@ -1,124 +1,100 @@ /* eslint-disable class-methods-use-this */ -/* global window, ConversationController */ +/* global window, ConversationController, _, log */ const is = require('@sindresorhus/is'); -const dns = require('dns'); -const process = require('process'); -const { rpc } = require('./loki_rpc'); - -// Will be raised (to 3?) when we get more nodes -const MINIMUM_SWARM_NODES = 1; -const FAILURE_THRESHOLD = 3; - -const resolve4 = url => - new Promise((resolve, reject) => { - dns.resolve4(url, (err, ip) => { - if (err) { - reject(err); - } else { - resolve(ip); - } - }); - }); - -const resolveCname = url => - new Promise((resolve, reject) => { - dns.resolveCname(url, (err, address) => { - if (err) { - reject(err); - } else { - resolve(address[0]); - } - }); - }); +const { lokiRpc } = require('./loki_rpc'); class LokiSnodeAPI { - constructor({ serverUrl, localUrl, snodeServerPort }) { + constructor({ serverUrl, localUrl }) { if (!is.string(serverUrl)) { throw new Error('WebAPI.initialize: Invalid server url'); } - this.serverUrl = serverUrl; - this.localUrl = localUrl; - this.snodeServerPort = snodeServerPort ? `:${snodeServerPort}` : ''; + this.serverUrl = serverUrl; // random.snode + this.localUrl = localUrl; // localhost.loki + this.randomSnodePool = []; this.swarmsPendingReplenish = {}; - this.ourSwarmNodes = {}; - this.contactSwarmNodes = {}; - // When we package lokinet with messenger we can ensure this ip is correct - if (process.platform === 'win32') { - dns.setServers(['127.0.0.1']); + } + + async getRandomSnodeAddress() { + /* resolve random snode */ + if (this.randomSnodePool.length === 0) { + await this.initialiseRandomPool(); + } + if (this.randomSnodePool.length === 0) { + throw new window.textsecure.SeedNodeError('Invalid seed node response'); } + return this.randomSnodePool[ + Math.floor(Math.random() * this.randomSnodePool.length) + ]; } - async getMyLokiIp() { + async initialiseRandomPool(seedNodes = [...window.seedNodeList]) { + const params = { + limit: 20, + active_only: true, + fields: { + public_ip: true, + storage_port: true, + pubkey_x25519: true, + pubkey_ed25519: true, + }, + }; + const seedNode = seedNodes.splice( + Math.floor(Math.random() * seedNodes.length), + 1 + )[0]; try { - const address = await resolveCname(this.localUrl); - return resolve4(address); - } catch (e) { - throw new window.textsecure.LokiIpError( - 'Failed to resolve localhost.loki', - e + const result = await lokiRpc( + `http://${seedNode.ip}`, + seedNode.port, + 'get_n_service_nodes', + params, + {}, // Options + '/json_rpc' // Seed request endpoint + ); + // Filter 0.0.0.0 nodes which haven't submitted uptime proofs + const snodes = result.result.service_node_states.filter( + snode => snode.public_ip !== '0.0.0.0' ); + this.randomSnodePool = snodes.map(snode => ({ + ip: snode.public_ip, + port: snode.storage_port, + pubkey_x25519: snode.pubkey_x25519, + pubkey_ed25519: snode.pubkey_ed25519, + })); + } catch (e) { + log.warn('initialiseRandomPool error', e.code, e.message); + if (seedNodes.length === 0) { + throw new window.textsecure.SeedNodeError( + 'Failed to contact seed node' + ); + } + this.initialiseRandomPool(seedNodes); } } - async getMyLokiAddress() { - /* resolve our local loki address */ - return resolveCname(this.localUrl); - } - - getRandomSnodeAddress() { - /* resolve random snode */ - return resolveCname(this.serverUrl); - } - + // nodeUrl is like 9hrje1bymy7hu6nmtjme9idyu3rm8gr3mkstakjyuw1997t7w4ny.snode async unreachableNode(pubKey, nodeUrl) { - if (pubKey === window.textsecure.storage.user.getNumber()) { - if (!this.ourSwarmNodes[nodeUrl]) { - this.ourSwarmNodes[nodeUrl] = { - failureCount: 1, - }; - } else { - this.ourSwarmNodes[nodeUrl].failureCount += 1; - } - if (this.ourSwarmNodes[nodeUrl].failureCount < FAILURE_THRESHOLD) { - return false; - } - delete this.ourSwarmNodes[nodeUrl]; - return true; - } - if (!this.contactSwarmNodes[nodeUrl]) { - this.contactSwarmNodes[nodeUrl] = { - failureCount: 1, - }; - } else { - this.contactSwarmNodes[nodeUrl].failureCount += 1; - } - if (this.contactSwarmNodes[nodeUrl].failureCount < FAILURE_THRESHOLD) { - return false; - } const conversation = ConversationController.get(pubKey); const swarmNodes = [...conversation.get('swarmNodes')]; - if (swarmNodes.includes(nodeUrl)) { - const filteredNodes = swarmNodes.filter(node => node !== nodeUrl); - await conversation.updateSwarmNodes(filteredNodes); - delete this.contactSwarmNodes[nodeUrl]; - } - return true; + const filteredNodes = swarmNodes.filter( + node => node.address !== nodeUrl && node.ip !== nodeUrl + ); + await conversation.updateSwarmNodes(filteredNodes); } - async updateLastHash(nodeUrl, lastHash, expiresAt) { - await window.Signal.Data.updateLastHash({ nodeUrl, lastHash, expiresAt }); - if (!this.ourSwarmNodes[nodeUrl]) { - this.ourSwarmNodes[nodeUrl] = { - failureCount: 0, - lastHash, - }; - } else { - this.ourSwarmNodes[nodeUrl].lastHash = lastHash; - } + markRandomNodeUnreachable(snode) { + this.randomSnodePool = _.without( + this.randomSnodePool, + _.find(this.randomSnodePool, { ip: snode.ip, port: snode.port }) + ); } - async getSwarmNodesForPubKey(pubKey) { + async updateLastHash(snode, hash, expiresAt) { + await window.Signal.Data.updateLastHash({ snode, hash, expiresAt }); + } + + getSwarmNodesForPubKey(pubKey) { try { const conversation = ConversationController.get(pubKey); const swarmNodes = [...conversation.get('swarmNodes')]; @@ -132,8 +108,9 @@ class LokiSnodeAPI { async updateSwarmNodes(pubKey, newNodes) { try { + const filteredNodes = newNodes.filter(snode => snode.ip !== '0.0.0.0'); const conversation = ConversationController.get(pubKey); - await conversation.updateSwarmNodes(newNodes); + await conversation.updateSwarmNodes(filteredNodes); } catch (e) { throw new window.textsecure.ReplayableError({ message: 'Could not get conversation', @@ -141,30 +118,6 @@ class LokiSnodeAPI { } } - async updateOurSwarmNodes(newNodes) { - this.ourSwarmNodes = {}; - const ps = newNodes.map(async url => { - const lastHash = await window.Signal.Data.getLastHashBySnode(url); - this.ourSwarmNodes[url] = { - failureCount: 0, - lastHash, - }; - }); - await Promise.all(ps); - } - - async getOurSwarmNodes() { - if ( - !this.ourSwarmNodes || - Object.keys(this.ourSwarmNodes).length < MINIMUM_SWARM_NODES - ) { - const ourKey = window.textsecure.storage.user.getNumber(); - const nodeAddresses = await this.getSwarmNodes(ourKey); - await this.updateOurSwarmNodes(nodeAddresses); - } - return { ...this.ourSwarmNodes }; - } - async refreshSwarmNodesForPubKey(pubKey) { const newNodes = await this.getFreshSwarmNodes(pubKey); this.updateSwarmNodes(pubKey, newNodes); @@ -190,17 +143,26 @@ class LokiSnodeAPI { async getSwarmNodes(pubKey) { // TODO: Hit multiple random nodes and merge lists? - const nodeUrl = await this.getRandomSnodeAddress(); - - const result = await rpc( - `http://${nodeUrl}`, - this.snodeServerPort, - 'get_snodes_for_pubkey', - { - pubKey, - } - ); - return result.snodes; + const snode = await this.getRandomSnodeAddress(); + try { + const result = await lokiRpc( + `https://${snode.ip}`, + snode.port, + 'get_snodes_for_pubkey', + { + pubKey, + }, + {}, + '/storage_rpc/v1', + snode + ); + const snodes = result.snodes.filter(tSnode => tSnode.ip !== '0.0.0.0'); + return snodes; + } catch (e) { + log.error('getSwarmNodes error', e.code, e.message); + this.markRandomNodeUnreachable(snode); + return this.getSwarmNodes(pubKey); + } } } diff --git a/js/modules/metadata/CiphertextMessage.js b/js/modules/metadata/CiphertextMessage.js index f381f3bec2..1e2ddb6ea2 100644 --- a/js/modules/metadata/CiphertextMessage.js +++ b/js/modules/metadata/CiphertextMessage.js @@ -10,4 +10,6 @@ module.exports = { SENDERKEY_DISTRIBUTION_TYPE: 5, ENCRYPTED_MESSAGE_OVERHEAD: 53, + + LOKI_FRIEND_REQUEST: 101, }; diff --git a/js/modules/metadata/SecretSessionCipher.js b/js/modules/metadata/SecretSessionCipher.js index 6422a20cb6..61420a1221 100644 --- a/js/modules/metadata/SecretSessionCipher.js +++ b/js/modules/metadata/SecretSessionCipher.js @@ -1,4 +1,4 @@ -/* global libsignal, textsecure */ +/* global libsignal, textsecure, dcodeIO, libloki */ /* eslint-disable no-bitwise */ @@ -102,37 +102,17 @@ function _createServerCertificateFromBuffer(serialized) { // public SenderCertificate(byte[] serialized) function _createSenderCertificateFromBuffer(serialized) { - const wrapper = textsecure.protobuf.SenderCertificate.decode(serialized); + const cert = textsecure.protobuf.SenderCertificate.decode(serialized); - if (!wrapper.signature || !wrapper.certificate) { - throw new Error('Missing fields'); - } - - const certificate = textsecure.protobuf.SenderCertificate.Certificate.decode( - wrapper.certificate.toArrayBuffer() - ); - - if ( - !certificate.signer || - !certificate.identityKey || - !certificate.senderDevice || - !certificate.expires || - !certificate.sender - ) { + if (!cert.senderDevice || !cert.sender) { throw new Error('Missing fields'); } return { - sender: certificate.sender, - senderDevice: certificate.senderDevice, - expires: certificate.expires.toNumber(), - identityKey: certificate.identityKey.toArrayBuffer(), - signer: _createServerCertificateFromBuffer( - certificate.signer.toArrayBuffer() - ), + sender: cert.sender, + senderDevice: cert.senderDevice, - certificate: wrapper.certificate.toArrayBuffer(), - signature: wrapper.signature.toArrayBuffer(), + certificate: cert.toArrayBuffer(), serialized, }; @@ -219,6 +199,9 @@ function _createUnidentifiedSenderMessageContentFromBuffer(serialized) { case TypeEnum.PREKEY_MESSAGE: type = CiphertextMessage.PREKEY_TYPE; break; + case TypeEnum.LOKI_FRIEND_REQUEST: + type = CiphertextMessage.LOKI_FRIEND_REQUEST; + break; default: throw new Error(`Unknown type: ${message.type}`); } @@ -243,6 +226,8 @@ function _getProtoMessageType(type) { return TypeEnum.MESSAGE; case CiphertextMessage.PREKEY_TYPE: return TypeEnum.PREKEY_MESSAGE; + case CiphertextMessage.LOKI_FRIEND_REQUEST: + return TypeEnum.LOKI_FRIEND_REQUEST; default: throw new Error(`_getProtoMessageType: type '${type}' does not exist`); } @@ -257,9 +242,7 @@ function _createUnidentifiedSenderMessageContent( ) { const innerMessage = new textsecure.protobuf.UnidentifiedSenderMessage.Message(); innerMessage.type = _getProtoMessageType(type); - innerMessage.senderCertificate = textsecure.protobuf.SenderCertificate.decode( - senderCertificate.serialized - ); + innerMessage.senderCertificate = senderCertificate; innerMessage.content = content; return { @@ -277,24 +260,24 @@ SecretSessionCipher.prototype = { // SenderCertificate senderCertificate, // byte[] paddedPlaintext // ) - async encrypt(destinationAddress, senderCertificate, paddedPlaintext) { + async encrypt( + destinationAddress, + senderCertificate, + paddedPlaintext, + cipher + ) { // Capture this.xxx variables to replicate Java's implicit this syntax - const { SessionCipher } = this; const signalProtocolStore = this.storage; const _calculateEphemeralKeys = this._calculateEphemeralKeys.bind(this); const _encryptWithSecretKeys = this._encryptWithSecretKeys.bind(this); const _calculateStaticKeys = this._calculateStaticKeys.bind(this); - const sessionCipher = new SessionCipher( - signalProtocolStore, - destinationAddress - ); - - const message = await sessionCipher.encrypt(paddedPlaintext); + const message = await cipher.encrypt(paddedPlaintext); const ourIdentity = await signalProtocolStore.getIdentityKeyPair(); - const theirIdentity = fromEncodedBinaryToArrayBuffer( - await signalProtocolStore.loadIdentityKey(destinationAddress.getName()) - ); + const theirIdentity = dcodeIO.ByteBuffer.wrap( + destinationAddress.getName(), + 'hex' + ).toArrayBuffer(); const ephemeral = await libsignal.Curve.async.generateKeyPair(); const ephemeralSalt = concatenateBytes( @@ -344,7 +327,7 @@ SecretSessionCipher.prototype = { // public Pair decrypt( // CertificateValidator validator, byte[] ciphertext, long timestamp) - async decrypt(validator, ciphertext, timestamp, me) { + async decrypt(ciphertext, me) { // Capture this.xxx variables to replicate Java's implicit this syntax const signalProtocolStore = this.storage; const _calculateEphemeralKeys = this._calculateEphemeralKeys.bind(this); @@ -392,15 +375,6 @@ SecretSessionCipher.prototype = { messageBytes ); - await validator.validate(content.senderCertificate, timestamp); - if ( - !constantTimeEqual(content.senderCertificate.identityKey, staticKeyBytes) - ) { - throw new Error( - "Sender's certificate key does not match key used in message" - ); - } - const { sender, senderDevice } = content.senderCertificate; const { number, deviceId } = me || {}; if (sender === number && senderDevice === deviceId) { @@ -414,8 +388,14 @@ SecretSessionCipher.prototype = { return { sender: address, content: await _decryptWithUnidentifiedSenderMessage(content), + type: content.type, }; } catch (error) { + if (!error) { + // eslint-disable-next-line no-ex-assign + error = new Error('Decryption error was falsey!'); + } + error.sender = address; throw error; @@ -514,6 +494,10 @@ SecretSessionCipher.prototype = { signalProtocolStore, sender ).decryptPreKeyWhisperMessage(message.content); + case CiphertextMessage.LOKI_FRIEND_REQUEST: + return new libloki.crypto.FallBackSessionCipher(sender).decrypt( + message.content + ); default: throw new Error(`Unknown type: ${message.type}`); } diff --git a/js/modules/migrate_to_sql.js b/js/modules/migrate_to_sql.js index 00dba4df49..4278ad0398 100644 --- a/js/modules/migrate_to_sql.js +++ b/js/modules/migrate_to_sql.js @@ -2,14 +2,12 @@ const { includes, isFunction, isString, last, map } = require('lodash'); const { - bulkAddGroups, bulkAddSessions, bulkAddIdentityKeys, bulkAddPreKeys, bulkAddSignedPreKeys, bulkAddItems, - removeGroupById, removeSessionById, removeIdentityKeyById, removePreKeyById, @@ -184,31 +182,6 @@ async function migrateToSQL({ complete = false; lastIndex = null; - while (!complete) { - // eslint-disable-next-line no-await-in-loop - const status = await migrateStoreToSQLite({ - db, - // eslint-disable-next-line no-loop-func - save: bulkAddGroups, - remove: removeGroupById, - storeName: 'groups', - handleDOMException, - lastIndex, - batchSize: 10, - }); - - ({ complete, lastIndex } = status); - } - window.log.info('migrateToSQL: migrate of groups complete'); - try { - await clearStores(['groups']); - } catch (error) { - window.log.warn('Failed to clear groups store'); - } - - complete = false; - lastIndex = null; - while (!complete) { // eslint-disable-next-line no-await-in-loop const status = await migrateStoreToSQLite({ diff --git a/js/modules/signal.js b/js/modules/signal.js index f97a421a11..f30e42cbc4 100644 --- a/js/modules/signal.js +++ b/js/modules/signal.js @@ -1,5 +1,6 @@ // The idea with this file is to make it webpackable for the style guide +const { bindActionCreators } = require('redux'); const Backbone = require('../../ts/backbone'); const Crypto = require('./crypto'); const Data = require('./data'); @@ -14,8 +15,12 @@ const { migrateToSQL } = require('./migrate_to_sql'); const Metadata = require('./metadata/SecretSessionCipher'); const RefreshSenderCertificate = require('./refresh_sender_certificate'); const LinkPreviews = require('./link_previews'); +const AttachmentDownloads = require('./attachment_downloads'); // Components +const { + ConversationLoadingScreen, +} = require('../../ts/components/ConversationLoadingScreen'); const { AttachmentList, } = require('../../ts/components/conversation/AttachmentList'); @@ -29,8 +34,8 @@ const { ConversationHeader, } = require('../../ts/components/conversation/ConversationHeader'); const { - ConversationListItem, -} = require('../../ts/components/ConversationListItem'); + SessionChannelSettings, +} = require('../../ts/components/session/SessionChannelSettings'); const { EmbeddedContact, } = require('../../ts/components/conversation/EmbeddedContact'); @@ -43,10 +48,71 @@ const { } = require('../../ts/components/conversation/GroupNotification'); const { Lightbox } = require('../../ts/components/Lightbox'); const { LightboxGallery } = require('../../ts/components/LightboxGallery'); +const { MemberList } = require('../../ts/components/conversation/MemberList'); +const { BulkEdit } = require('../../ts/components/conversation/BulkEdit'); +const { + CreateGroupDialog, +} = require('../../ts/components/conversation/CreateGroupDialog'); +const { EditProfileDialog } = require('../../ts/components/EditProfileDialog'); +const { UserDetailsDialog } = require('../../ts/components/UserDetailsDialog'); +const { + DevicePairingDialog, +} = require('../../ts/components/DevicePairingDialog'); +const { + SettingsView, +} = require('../../ts/components/session/settings/SessionSettings'); +const { SessionToast } = require('../../ts/components/session/SessionToast'); +const { SessionToggle } = require('../../ts/components/session/SessionToggle'); +const { SessionModal } = require('../../ts/components/session/SessionModal'); +const { + SessionQRModal, +} = require('../../ts/components/session/SessionQRModal'); +const { + SessionSeedModal, +} = require('../../ts/components/session/SessionSeedModal'); + +const { + SessionPasswordModal, +} = require('../../ts/components/session/SessionPasswordModal'); +const { + SessionPasswordPrompt, +} = require('../../ts/components/session/SessionPasswordPrompt'); + +const { + SessionConfirm, +} = require('../../ts/components/session/SessionConfirm'); + +const { + SessionDropdown, +} = require('../../ts/components/session/SessionDropdown'); +const { + SessionScrollButton, +} = require('../../ts/components/session/SessionScrollButton'); +const { + SessionRegistrationView, +} = require('../../ts/components/session/SessionRegistrationView'); + +const { + UpdateGroupDialog, +} = require('../../ts/components/conversation/UpdateGroupDialog'); +const { + InviteFriendsDialog, +} = require('../../ts/components/conversation/InviteFriendsDialog'); + +const { + AddModeratorsDialog, +} = require('../../ts/components/conversation/ModeratorsAddDialog'); +const { + RemoveModeratorsDialog, +} = require('../../ts/components/conversation/ModeratorsRemoveDialog'); + +const { + GroupInvitation, +} = require('../../ts/components/conversation/GroupInvitation'); +const { ConfirmDialog } = require('../../ts/components/ConfirmDialog'); const { MediaGallery, } = require('../../ts/components/conversation/media-gallery/MediaGallery'); -const { MainHeader } = require('../../ts/components/MainHeader'); const { Message } = require('../../ts/components/conversation/Message'); const { MessageBody } = require('../../ts/components/conversation/MessageBody'); const { @@ -72,6 +138,12 @@ const { VerificationNotification, } = require('../../ts/components/conversation/VerificationNotification'); +// State +const { createLeftPane } = require('../../ts/state/roots/createLeftPane'); +const { createStore } = require('../../ts/state/createStore'); +const conversationsDuck = require('../../ts/state/ducks/conversations'); +const userDuck = require('../../ts/state/ducks/user'); + // Migrations const { getPlaceholderMigrations, @@ -131,6 +203,7 @@ function initializeMigrations({ const loadQuoteData = MessageType.loadQuoteData(loadAttachmentData); const getAbsoluteAttachmentPath = createAbsolutePathGetter(attachmentsPath); const deleteOnDisk = Attachments.createDeleter(attachmentsPath); + const writeNewAttachmentData = createWriterForNew(attachmentsPath); return { attachmentsPath, @@ -148,11 +221,22 @@ function initializeMigrations({ loadQuoteData, readAttachmentData, run, + processNewAttachment: attachment => + MessageType.processNewAttachment(attachment, { + writeNewAttachmentData, + getAbsoluteAttachmentPath, + makeObjectUrl, + revokeObjectUrl, + getImageDimensions, + makeImageThumbnail, + makeVideoScreenshot, + logger, + }), upgradeMessageSchema: (message, options = {}) => { const { maxVersion } = options; return MessageType.upgradeSchema(message, { - writeNewAttachmentData: createWriterForNew(attachmentsPath), + writeNewAttachmentData, getRegionCode, getAbsoluteAttachmentPath, makeObjectUrl, @@ -187,20 +271,44 @@ exports.setup = (options = {}) => { }); const Components = { + ConversationLoadingScreen, AttachmentList, CaptionEditor, ContactDetail, ContactListItem, ContactName, ConversationHeader, - ConversationListItem, + SessionChannelSettings, + SettingsView, EmbeddedContact, Emojify, FriendRequest, GroupNotification, Lightbox, LightboxGallery, - MainHeader, + MemberList, + CreateGroupDialog, + EditProfileDialog, + UserDetailsDialog, + DevicePairingDialog, + SessionRegistrationView, + ConfirmDialog, + UpdateGroupDialog, + InviteFriendsDialog, + AddModeratorsDialog, + RemoveModeratorsDialog, + GroupInvitation, + BulkEdit, + SessionToast, + SessionToggle, + SessionConfirm, + SessionModal, + SessionQRModal, + SessionSeedModal, + SessionPasswordModal, + SessionPasswordPrompt, + SessionDropdown, + SessionScrollButton, MediaGallery, Message, MessageBody, @@ -217,6 +325,20 @@ exports.setup = (options = {}) => { VerificationNotification, }; + const Roots = { + createLeftPane, + }; + const Ducks = { + conversations: conversationsDuck, + user: userDuck, + }; + const State = { + bindActionCreators, + createStore, + Roots, + Ducks, + }; + const Types = { Attachment: AttachmentType, Contact, @@ -239,6 +361,7 @@ exports.setup = (options = {}) => { }; return { + AttachmentDownloads, Backbone, Components, Crypto, @@ -254,6 +377,7 @@ exports.setup = (options = {}) => { OS, RefreshSenderCertificate, Settings, + State, Types, Util, Views, diff --git a/js/modules/types/attachment.js b/js/modules/types/attachment.js index 0a63523501..1aaa1e0b06 100644 --- a/js/modules/types/attachment.js +++ b/js/modules/types/attachment.js @@ -20,7 +20,7 @@ const { // contentType: MIMEType // data: ArrayBuffer // digest: ArrayBuffer -// fileName: string | null +// fileName?: string // flags: null // key: ArrayBuffer // size: integer @@ -56,6 +56,11 @@ exports.autoOrientJPEG = async attachment => { return attachment; } + // If we haven't downloaded the attachment yet, we won't have the data + if (!attachment.data) { + return attachment; + } + const dataBlob = await arrayBufferToBlob( attachment.data, attachment.contentType @@ -235,6 +240,11 @@ exports.captureDimensionsAndScreenshot = async ( return attachment; } + // If the attachment hasn't been downloaded yet, we won't have a path + if (!attachment.path) { + return attachment; + } + const absolutePath = await getAbsoluteAttachmentPath(attachment.path); if (GoogleChrome.isImageTypeSupported(contentType)) { diff --git a/js/modules/types/attachment/migrate_data_to_file_system.js b/js/modules/types/attachment/migrate_data_to_file_system.js index 8358858726..dded244ad3 100644 --- a/js/modules/types/attachment/migrate_data_to_file_system.js +++ b/js/modules/types/attachment/migrate_data_to_file_system.js @@ -9,7 +9,7 @@ const { isArrayBuffer, isFunction, isUndefined, omit } = require('lodash'); // Promise Attachment exports.migrateDataToFileSystem = async ( attachment, - { writeNewAttachmentData, logger } = {} + { writeNewAttachmentData } = {} ) => { if (!isFunction(writeNewAttachmentData)) { throw new TypeError("'writeNewAttachmentData' must be a function"); @@ -19,7 +19,6 @@ exports.migrateDataToFileSystem = async ( const hasData = !isUndefined(data); const shouldSkipSchemaUpgrade = !hasData; if (shouldSkipSchemaUpgrade) { - logger.warn('WARNING: `attachment.data` is `undefined`'); return attachment; } diff --git a/js/modules/types/message.js b/js/modules/types/message.js index 405092b37c..382f899dbf 100644 --- a/js/modules/types/message.js +++ b/js/modules/types/message.js @@ -134,8 +134,7 @@ exports._withSchemaVersion = ({ schemaVersion, upgrade }) => { logger.warn( 'WARNING: Message._withSchemaVersion: Unexpected version:', `Expected message to have version ${expectedVersion},`, - `but got ${message.schemaVersion}.`, - message + `but got ${message.schemaVersion}.` ); return message; } @@ -203,7 +202,6 @@ exports._mapQuotedAttachments = upgradeAttachment => async ( if (!context || !isObject(context.logger)) { throw new Error('_mapQuotedAttachments: context must have logger object'); } - const { logger } = context; const upgradeWithContext = async attachment => { const { thumbnail } = attachment; @@ -211,11 +209,6 @@ exports._mapQuotedAttachments = upgradeAttachment => async ( return attachment; } - if (!thumbnail.data && !thumbnail.path) { - logger.warn('Quoted attachment did not have thumbnail data; removing it'); - return omit(attachment, ['thumbnail']); - } - const upgradedThumbnail = await upgradeAttachment(thumbnail, context); return Object.assign({}, attachment, { thumbnail: upgradedThumbnail, @@ -247,7 +240,6 @@ exports._mapPreviewAttachments = upgradeAttachment => async ( if (!context || !isObject(context.logger)) { throw new Error('_mapPreviewAttachments: context must have logger object'); } - const { logger } = context; const upgradeWithContext = async preview => { const { image } = preview; @@ -255,11 +247,6 @@ exports._mapPreviewAttachments = upgradeAttachment => async ( return preview; } - if (!image.data && !image.path) { - logger.warn('Preview did not have image data; removing it'); - return omit(preview, ['image']); - } - const upgradedImage = await upgradeAttachment(image, context); return Object.assign({}, preview, { image: upgradedImage, @@ -413,6 +400,68 @@ exports.upgradeSchema = async ( return message; }; +// Runs on attachments outside of the schema upgrade process, since attachments are +// downloaded out of band. +exports.processNewAttachment = async ( + attachment, + { + writeNewAttachmentData, + getAbsoluteAttachmentPath, + makeObjectUrl, + revokeObjectUrl, + getImageDimensions, + makeImageThumbnail, + makeVideoScreenshot, + logger, + } = {} +) => { + if (!isFunction(writeNewAttachmentData)) { + throw new TypeError('context.writeNewAttachmentData is required'); + } + if (!isFunction(getAbsoluteAttachmentPath)) { + throw new TypeError('context.getAbsoluteAttachmentPath is required'); + } + if (!isFunction(makeObjectUrl)) { + throw new TypeError('context.makeObjectUrl is required'); + } + if (!isFunction(revokeObjectUrl)) { + throw new TypeError('context.revokeObjectUrl is required'); + } + if (!isFunction(getImageDimensions)) { + throw new TypeError('context.getImageDimensions is required'); + } + if (!isFunction(makeImageThumbnail)) { + throw new TypeError('context.makeImageThumbnail is required'); + } + if (!isFunction(makeVideoScreenshot)) { + throw new TypeError('context.makeVideoScreenshot is required'); + } + if (!isObject(logger)) { + throw new TypeError('context.logger is required'); + } + + const rotatedAttachment = await Attachment.autoOrientJPEG(attachment); + const onDiskAttachment = await Attachment.migrateDataToFileSystem( + rotatedAttachment, + { writeNewAttachmentData } + ); + const finalAttachment = await Attachment.captureDimensionsAndScreenshot( + onDiskAttachment, + { + writeNewAttachmentData, + getAbsoluteAttachmentPath, + makeObjectUrl, + revokeObjectUrl, + getImageDimensions, + makeImageThumbnail, + makeVideoScreenshot, + logger, + } + ); + + return finalAttachment; +}; + exports.createAttachmentLoader = loadAttachmentData => { if (!isFunction(loadAttachmentData)) { throw new TypeError( @@ -508,7 +557,10 @@ exports.deleteAllExternalFiles = ({ deleteAttachmentData, deleteOnDisk }) => { quote.attachments.map(async attachment => { const { thumbnail } = attachment; - if (thumbnail && thumbnail.path) { + // To prevent spoofing, we copy the original image from the quoted message. + // If so, it will have a 'copied' field. We don't want to delete it if it has + // that field set to true. + if (thumbnail && thumbnail.path && !thumbnail.copied) { await deleteOnDisk(thumbnail.path); } }) diff --git a/js/modules/web_api.js b/js/modules/web_api.js index 0af4db9f7f..7d446bace9 100644 --- a/js/modules/web_api.js +++ b/js/modules/web_api.js @@ -2,10 +2,11 @@ const WebSocket = require('websocket').w3cwebsocket; const fetch = require('node-fetch'); const ProxyAgent = require('proxy-agent'); const { Agent } = require('https'); +const FormData = require('form-data'); const is = require('@sindresorhus/is'); -/* global Buffer, setTimeout, log, _ */ +/* global Buffer, setTimeout, log, _, lokiFileServerAPI */ /* eslint-disable more/no-then, no-bitwise, no-nested-ternary */ @@ -28,10 +29,12 @@ const Uint8ArrayToString = _call(new Uint8Array()); function _getString(thing) { if (typeof thing !== 'string') { - if (_call(thing) === Uint8ArrayToString) + if (_call(thing) === Uint8ArrayToString) { return String.fromCharCode.apply(null, thing); - if (_call(thing) === ArrayBufferToString) + } + if (_call(thing) === ArrayBufferToString) { return _getString(new Uint8Array(thing)); + } } return thing; } @@ -193,31 +196,33 @@ function _promiseAjax(providedUrl, options) { `${options.type} ${url}${options.unauthenticated ? ' (unauth)' : ''}` ); } + const timeout = typeof options.timeout !== 'undefined' ? options.timeout : 10000; const { proxyUrl } = options; const agentType = options.unauthenticated ? 'unauth' : 'auth'; + const cacheKey = `${proxyUrl}-${agentType}`; - const { timestamp } = agents[agentType] || {}; + const { timestamp } = agents[cacheKey] || {}; if (!timestamp || timestamp + FIVE_MINUTES < Date.now()) { if (timestamp) { - log.info(`Cycling agent for type ${agentType}`); + log.info(`Cycling agent for type ${cacheKey}`); } - agents[agentType] = { + agents[cacheKey] = { agent: proxyUrl ? new ProxyAgent(proxyUrl) : new Agent({ keepAlive: true }), timestamp: Date.now(), }; } - const { agent } = agents[agentType]; + const { agent } = agents[cacheKey]; const fetchOptions = { method: options.type, body: options.data || null, headers: { - 'User-Agent': 'Loki Messenger', + 'User-Agent': 'Session', 'X-Loki-Messenger-Agent': 'OWD', ...options.headers, }, @@ -458,6 +463,7 @@ function initialize({ getSenderCertificate, makeProxiedRequest, putAttachment, + putAvatar, registerKeys, registerSupportForUnauthenticatedDelivery, removeSignalingKey, @@ -839,41 +845,38 @@ function initialize({ }); } - function getAttachment(id) { - return _ajax({ - call: 'attachment', - httpType: 'GET', - urlParameters: `/${id}`, - responseType: 'json', - validateResponse: { location: 'string' }, - }).then(response => - // Using _outerAJAX, since it's not hardcoded to the Signal Server - _outerAjax(response.location, { - contentType: 'application/octet-stream', - proxyUrl, - responseType: 'arraybuffer', - timeout: 0, - type: 'GET', - }) - ); + function getAttachment(fileUrl) { + return _outerAjax(fileUrl, { + contentType: 'application/octet-stream', + proxyUrl, + responseType: 'arraybuffer', + timeout: 0, + type: 'GET', + }); } - function putAttachment(encryptedBin) { - return _ajax({ - call: 'attachment', - httpType: 'GET', - responseType: 'json', - }).then(response => - // Using _outerAJAX, since it's not hardcoded to the Signal Server - _outerAjax(response.location, { - contentType: 'application/octet-stream', - data: encryptedBin, - processData: false, - proxyUrl, - timeout: 0, - type: 'PUT', - }).then(() => response.idString) - ); + function putAttachment(maybeEncryptedBin) { + const formData = new FormData(); + const buffer = Buffer.from(maybeEncryptedBin); + formData.append('type', 'network.loki'); + formData.append('content', buffer, { + contentType: 'application/octet-stream', + name: 'content', + filename: 'attachment', + }); + + return lokiFileServerAPI.uploadPrivateAttachment(formData); + } + + function putAvatar(bin) { + const formData = new FormData(); + const buffer = Buffer.from(bin); + formData.append('avatar', buffer, { + contentType: 'application/octet-stream', + name: 'avatar', + filename: 'attachment', + }); + return lokiFileServerAPI.uploadAvatar(formData); } // eslint-disable-next-line no-shadow diff --git a/js/notifications.js b/js/notifications.js index f7f46324fe..d053ea7179 100644 --- a/js/notifications.js +++ b/js/notifications.js @@ -1,7 +1,6 @@ /* global Signal:false */ /* global Backbone: false */ -/* global ConversationController: false */ /* global drawAttention: false */ /* global i18n: false */ /* global isFocused: false */ @@ -23,6 +22,15 @@ MESSAGE: 'message', }; + function filter(text) { + return (text || '') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(//g, '>'); + } + Whisper.Notifications = new (Backbone.Collection.extend({ initialize() { this.isEnabled = false; @@ -39,10 +47,6 @@ this.fastUpdate = this.update; this.update = _.debounce(this.update, 1000); }, - onClick(conversationId) { - const conversation = ConversationController.get(conversationId); - this.trigger('click', conversation); - }, update() { if (this.lastNotification) { this.lastNotification.close(); @@ -104,7 +108,7 @@ const last = this.last().toJSON(); switch (userSetting) { case SettingNames.COUNT: - title = 'Loki Messenger'; + title = 'Session'; if (last.isFriendRequest) { message = `Friend request ${last.friendRequestType}`; @@ -156,14 +160,13 @@ drawAttention(); - const notification = new Notification(title, { - body: message, + this.lastNotification = new Notification(title, { + body: window.platform === 'linux' ? filter(message) : message, icon: iconUrl, - tag: isNotificationGroupingSupported ? 'signal' : undefined, silent: !status.shouldPlayNotificationSound, }); - notification.onclick = () => this.onClick(last.conversationId); - this.lastNotification = notification; + this.lastNotification.onclick = () => + this.trigger('click', last.conversationId, last.id); // We continue to build up more and more messages for our notifications // until the user comes back to our app or closes the app. Then we’ll diff --git a/js/permissions_popup_start.js b/js/permissions_popup_start.js index f153a0bf96..69fe851d73 100644 --- a/js/permissions_popup_start.js +++ b/js/permissions_popup_start.js @@ -1,4 +1,4 @@ -/* global $, Whisper, i18n */ +/* global $, i18n */ $(document).on('keyup', e => { 'use strict'; @@ -8,11 +8,8 @@ $(document).on('keyup', e => { } }); -const $body = $(document.body); -$body.addClass(`${window.theme}-theme`); - -window.view = new Whisper.ConfirmationDialogView({ - message: i18n('audioPermissionNeeded'), +window.confirmationDialog({ + title: i18n('audioPermissionNeeded'), okText: i18n('allowAccess'), resolve: () => { 'use strict'; @@ -20,7 +17,5 @@ window.view = new Whisper.ConfirmationDialogView({ window.setMediaPermissions(true); window.closePermissionsPopup(); }, - reject: window.closePermissionsPopup, + onClose: window.closePermissionsPopup, }); - -window.view.$el.appendTo($body); diff --git a/js/read_receipts.js b/js/read_receipts.js index 989629939c..e443808743 100644 --- a/js/read_receipts.js +++ b/js/read_receipts.js @@ -1,4 +1,11 @@ -/* global Whisper, Backbone, _, ConversationController, window */ +/* global + Whisper, + Backbone, + _, + ConversationController, + MessageController, + window +*/ /* eslint-disable more/no-then */ @@ -46,9 +53,14 @@ const ids = groups.pluck('id'); ids.push(reader); - return messages.find( + const target = messages.find( item => item.isOutgoing() && _.contains(ids, item.get('conversationId')) ); + if (!target) { + return null; + } + + return MessageController.register(target.id, target); }, async onReceipt(receipt) { try { diff --git a/js/read_syncs.js b/js/read_syncs.js index 4732445c35..9327943288 100644 --- a/js/read_syncs.js +++ b/js/read_syncs.js @@ -1,4 +1,8 @@ -/* global Backbone, Whisper */ +/* global + Backbone, + Whisper, + MessageController +*/ /* eslint-disable more/no-then */ @@ -30,19 +34,19 @@ } ); - const message = messages.find( + const found = messages.find( item => item.isIncoming() && item.get('source') === receipt.get('sender') ); - const notificationForMessage = message - ? Whisper.Notifications.findWhere({ messageId: message.id }) + const notificationForMessage = found + ? Whisper.Notifications.findWhere({ messageId: found.id }) : null; const removedNotification = Whisper.Notifications.remove( notificationForMessage ); const receiptSender = receipt.get('sender'); const receiptTimestamp = receipt.get('timestamp'); - const wasMessageFound = Boolean(message); + const wasMessageFound = Boolean(found); const wasNotificationFound = Boolean(notificationForMessage); const wasNotificationRemoved = Boolean(removedNotification); window.log.info('Receive read sync:', { @@ -53,10 +57,11 @@ wasNotificationRemoved, }); - if (!message) { + if (!found) { return; } + const message = MessageController.register(found.id, found); const readAt = receipt.get('read_at'); // If message is unread, we mark it read. Otherwise, we update the expiration diff --git a/js/registration.js b/js/registration.js index 499e981bf4..301e8cb887 100644 --- a/js/registration.js +++ b/js/registration.js @@ -21,6 +21,9 @@ storage.get('chromiumRegistrationDone') === '' ); }, + ongoingSecondaryDeviceRegistration() { + return storage.get('secondaryDeviceStatus') === 'ongoing'; + }, remove() { storage.remove('chromiumRegistrationDone'); }, diff --git a/js/rotate_signed_prekey_listener.js b/js/rotate_signed_prekey_listener.js index 98617ecda6..fe0725f29e 100644 --- a/js/rotate_signed_prekey_listener.js +++ b/js/rotate_signed_prekey_listener.js @@ -8,6 +8,7 @@ const ROTATION_INTERVAL = 48 * 60 * 60 * 1000; let timeout; let scheduledTime; + let shouldStop = false; function scheduleNextRotation() { const now = Date.now(); @@ -16,6 +17,9 @@ } function run() { + if (shouldStop) { + return; + } window.log.info('Rotating signed prekey...'); getAccountManager() .rotateSignedPreKey() @@ -64,7 +68,11 @@ clearTimeout(timeout); timeout = setTimeout(runWhenOnline, waitTime); } - + function onTimeTravel() { + if (Whisper.Registration.isDone()) { + setTimeoutForNextRun(); + } + } let initComplete; Whisper.RotateSignedPreKeyListener = { init(events, newVersion) { @@ -73,6 +81,7 @@ return; } initComplete = true; + shouldStop = false; if (newVersion) { runWhenOnline(); @@ -80,11 +89,13 @@ setTimeoutForNextRun(); } - events.on('timetravel', () => { - if (Whisper.Registration.isDone()) { - setTimeoutForNextRun(); - } - }); + events.on('timetravel', onTimeTravel); + }, + stop(events) { + initComplete = false; + shouldStop = true; + events.off('timetravel', onTimeTravel); + clearTimeout(timeout); }, }; })(); diff --git a/js/session_ui_manager.js b/js/session_ui_manager.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/js/settings_start.js b/js/settings_start.js index 607178f105..c6627ebd71 100644 --- a/js/settings_start.js +++ b/js/settings_start.js @@ -20,6 +20,7 @@ const getInitialData = async () => ({ messageTTL: await window.getMessageTTL(), readReceiptSetting: await window.getReadReceiptSetting(), + typingIndicatorsSetting: await window.getTypingIndicatorsSetting(), linkPreviewSetting: await window.getLinkPreviewSetting(), notificationSetting: await window.getNotificationSetting(), audioNotification: await window.getAudioNotification(), diff --git a/js/signal_protocol_store.js b/js/signal_protocol_store.js index be56da563c..631433d815 100644 --- a/js/signal_protocol_store.js +++ b/js/signal_protocol_store.js @@ -39,6 +39,21 @@ return false; } + function convertVerifiedStatusToProtoState(status) { + switch (status) { + case VerifiedStatus.VERIFIED: + return textsecure.protobuf.Verified.State.VERIFIED; + + case VerifiedStatus.UNVERIFIED: + return textsecure.protobuf.Verified.State.VERIFIED; + + case VerifiedStatus.DEFAULT: + // intentional fallthrough + default: + return textsecure.protobuf.Verified.State.DEFAULT; + } + } + const StaticByteBufferProto = new dcodeIO.ByteBuffer().__proto__; const StaticArrayBufferProto = new ArrayBuffer().__proto__; const StaticUint8ArrayProto = new Uint8Array().__proto__; @@ -107,7 +122,6 @@ return result === 0; } - const Unprocessed = Backbone.Model.extend(); const IdentityRecord = Backbone.Model.extend({ storeName: 'identityKeys', validAttributes: [ @@ -159,8 +173,51 @@ function SignalProtocolStore() {} + async function _hydrateCache(object, field, items, idField) { + const cache = Object.create(null); + for (let i = 0, max = items.length; i < max; i += 1) { + const item = items[i]; + const id = item[idField]; + + cache[id] = item; + } + + window.log.info(`SignalProtocolStore: Finished caching ${field} data`); + // eslint-disable-next-line no-param-reassign + object[field] = cache; + } + SignalProtocolStore.prototype = { constructor: SignalProtocolStore, + async hydrateCaches() { + await Promise.all([ + _hydrateCache( + this, + 'identityKeys', + await window.Signal.Data.getAllIdentityKeys(), + 'id' + ), + _hydrateCache( + this, + 'sessions', + await window.Signal.Data.getAllSessions(), + 'id' + ), + _hydrateCache( + this, + 'preKeys', + await window.Signal.Data.getAllPreKeys(), + 'id' + ), + _hydrateCache( + this, + 'signedPreKeys', + await window.Signal.Data.getAllSignedPreKeys(), + 'id' + ), + ]); + }, + async getIdentityKeyPair() { const item = await window.Signal.Data.getItemById('identityKey'); if (item) { @@ -178,9 +235,10 @@ return 1; }, - /* Returns a prekeypair object or undefined */ + // PreKeys + async loadPreKey(keyId) { - const key = await window.Signal.Data.getPreKeyById(keyId); + const key = this.preKeys[keyId]; if (key) { window.log.info('Successfully fetched prekey:', keyId); return { @@ -218,6 +276,7 @@ recipient: contactPubKey, }; + this.preKeys[keyId] = data; await window.Signal.Data.createOrUpdatePreKey(data); }, async removePreKey(keyId) { @@ -230,14 +289,18 @@ ); } + delete this.preKeys[keyId]; await window.Signal.Data.removePreKeyById(keyId); }, async clearPreKeyStore() { + this.preKeys = Object.create(null); await window.Signal.Data.removeAllPreKeys(); }, + + // Signed PreKeys /* Returns a signed keypair object or undefined */ async loadSignedPreKey(keyId) { - const key = await window.Signal.Data.getSignedPreKeyById(keyId); + const key = this.signedPreKeys[keyId]; if (key) { window.log.info('Successfully fetched signed prekey:', key.id); return { @@ -258,7 +321,7 @@ throw new Error('loadSignedPreKeys takes no arguments'); } - const keys = await window.Signal.Data.getAllSignedPreKeys(); + const keys = Object.values(this.signedPreKeys); return keys.map(prekey => ({ pubKey: prekey.publicKey, privKey: prekey.privateKey, @@ -269,7 +332,7 @@ })); }, async storeSignedPreKey(keyId, keyPair, confirmed, signature) { - const key = { + const data = { id: keyId, publicKey: keyPair.pubKey, privateKey: keyPair.privKey, @@ -277,20 +340,27 @@ confirmed: Boolean(confirmed), signature, }; - await window.Signal.Data.createOrUpdateSignedPreKey(key); + + this.signedPreKeys[keyId] = data; + await window.Signal.Data.createOrUpdateSignedPreKey(data); }, async removeSignedPreKey(keyId) { + delete this.signedPreKeys[keyId]; await window.Signal.Data.removeSignedPreKeyById(keyId); }, async clearSignedPreKeysStore() { + this.signedPreKeys = Object.create(null); await window.Signal.Data.removeAllSignedPreKeys(); }, + + // Sessions + async loadSession(encodedNumber) { if (encodedNumber === null || encodedNumber === undefined) { throw new Error('Tried to get session for undefined/null number'); } - const session = await window.Signal.Data.getSessionById(encodedNumber); + const session = this.sessions[encodedNumber]; if (session) { return session.record; } @@ -312,6 +382,7 @@ record, }; + this.sessions[encodedNumber] = data; await window.Signal.Data.createOrUpdateSession(data); }, async getDeviceIds(number) { @@ -319,11 +390,13 @@ throw new Error('Tried to get device ids for undefined/null number'); } - const sessions = await window.Signal.Data.getSessionsByNumber(number); + const allSessions = Object.values(this.sessions); + const sessions = allSessions.filter(session => session.number === number); return _.pluck(sessions, 'deviceId'); }, async removeSession(encodedNumber) { window.log.info('deleting session for ', encodedNumber); + delete this.sessions[encodedNumber]; await window.Signal.Data.removeSessionById(encodedNumber); }, async removeAllSessions(number) { @@ -331,6 +404,13 @@ throw new Error('Tried to remove sessions for undefined/null number'); } + const allSessions = Object.values(this.sessions); + for (let i = 0, max = allSessions.length; i < max; i += 1) { + const session = allSessions[i]; + if (session.number === number) { + delete this.sessions[session.id]; + } + } await window.Signal.Data.removeSessionsByNumber(number); }, async archiveSiblingSessions(identifier) { @@ -370,8 +450,12 @@ ); }, async clearSessionStore() { + this.sessions = Object.create(null); window.Signal.Data.removeAllSessions(); }, + + // Identity Keys + async isTrustedIdentity(identifier, publicKey, direction) { if (identifier === null || identifier === undefined) { throw new Error('Tried to get identity key for undefined/null key'); @@ -379,9 +463,7 @@ const number = textsecure.utils.unencodeNumber(identifier)[0]; const isOurNumber = number === textsecure.storage.user.getNumber(); - const identityRecord = await window.Signal.Data.getIdentityKeyById( - number - ); + const identityRecord = this.identityKeys[number]; if (isOurNumber) { const existing = identityRecord ? identityRecord.publicKey : null; @@ -431,9 +513,7 @@ throw new Error('Tried to get identity key for undefined/null key'); } const number = textsecure.utils.unencodeNumber(identifier)[0]; - const identityRecord = await window.Signal.Data.getIdentityKeyById( - number - ); + const identityRecord = this.identityKeys[number]; if (identityRecord) { return identityRecord.publicKey; @@ -441,6 +521,11 @@ return undefined; }, + async _saveIdentityKey(data) { + const { id } = data; + this.identityKeys[id] = data; + await window.Signal.Data.createOrUpdateIdentityKey(data); + }, async saveIdentity(identifier, publicKey, nonblockingApproval) { if (identifier === null || identifier === undefined) { throw new Error('Tried to put identity key for undefined/null key'); @@ -455,14 +540,12 @@ } const number = textsecure.utils.unencodeNumber(identifier)[0]; - const identityRecord = await window.Signal.Data.getIdentityKeyById( - number - ); + const identityRecord = this.identityKeys[number]; if (!identityRecord || !identityRecord.publicKey) { // Lookup failed, or the current key was removed, so save this one. window.log.info('Saving new identity...'); - await window.Signal.Data.createOrUpdateIdentityKey({ + await this._saveIdentityKey({ id: number, publicKey, firstUse: true, @@ -488,7 +571,7 @@ verifiedStatus = VerifiedStatus.DEFAULT; } - await window.Signal.Data.createOrUpdateIdentityKey({ + await this._saveIdentityKey({ id: number, publicKey, firstUse: false, @@ -512,7 +595,7 @@ window.log.info('Setting approval status...'); identityRecord.nonblockingApproval = nonblockingApproval; - await window.Signal.Data.createOrUpdateIdentityKey(identityRecord); + await this._saveIdentityKey(identityRecord); return false; } @@ -532,9 +615,7 @@ } const number = textsecure.utils.unencodeNumber(identifier)[0]; - const identityRecord = await window.Signal.Data.getIdentityKeyById( - number - ); + const identityRecord = this.identityKeys[number]; const updates = { id: number, @@ -544,7 +625,7 @@ const model = new IdentityRecord(updates); if (model.isValid()) { - await window.Signal.Data.createOrUpdateIdentityKey(updates); + await this._saveIdentityKey(updates); } else { throw model.validationError; } @@ -558,16 +639,14 @@ } const number = textsecure.utils.unencodeNumber(identifier)[0]; - const identityRecord = await window.Signal.Data.getIdentityKeyById( - number - ); + const identityRecord = this.identityKeys[number]; if (!identityRecord) { throw new Error(`No identity record for ${number}`); } identityRecord.nonblockingApproval = nonblockingApproval; - await window.Signal.Data.createOrUpdateIdentityKey(identityRecord); + await this._saveIdentityKey(identityRecord); }, async setVerified(number, verifiedStatus, publicKey) { if (number === null || number === undefined) { @@ -580,9 +659,7 @@ throw new Error('Invalid public key'); } - const identityRecord = await window.Signal.Data.getIdentityKeyById( - number - ); + const identityRecord = this.identityKeys[number]; if (!identityRecord) { throw new Error(`No identity record for ${number}`); } @@ -595,7 +672,7 @@ const model = new IdentityRecord(identityRecord); if (model.isValid()) { - await window.Signal.Data.createOrUpdateIdentityKey(identityRecord); + await this._saveIdentityKey(identityRecord); } else { throw identityRecord.validationError; } @@ -608,10 +685,7 @@ throw new Error('Tried to set verified for undefined/null key'); } - const identityRecord = await window.Signal.Data.getIdentityKeyById( - number - ); - + const identityRecord = this.identityKeys[number]; if (!identityRecord) { throw new Error(`No identity record for ${number}`); } @@ -645,9 +719,7 @@ throw new Error('Invalid public key'); } - const identityRecord = await window.Signal.Data.getIdentityKeyById( - number - ); + const identityRecord = this.identityKeys[number]; const isPresent = Boolean(identityRecord); let isEqual = false; @@ -712,9 +784,7 @@ throw new Error('Invalid public key'); } - const identityRecord = await window.Signal.Data.getIdentityKeyById( - number - ); + const identityRecord = this.identityKeys[number]; const isPresent = Boolean(identityRecord); let isEqual = false; @@ -783,10 +853,7 @@ throw new Error('Tried to set verified for undefined/null key'); } - const identityRecord = await window.Signal.Data.getIdentityKeyById( - number - ); - + const identityRecord = this.identityKeys[number]; if (!identityRecord) { throw new Error(`No identity record for ${number}`); } @@ -802,42 +869,9 @@ return false; }, async removeIdentityKey(number) { + delete this.identityKeys[number]; await window.Signal.Data.removeIdentityKeyById(number); - return textsecure.storage.protocol.removeAllSessions(number); - }, - - // Groups - async getGroup(groupId) { - if (groupId === null || groupId === undefined) { - throw new Error('Tried to get group for undefined/null id'); - } - - const group = await window.Signal.Data.getGroupById(groupId); - if (group) { - return group.data; - } - - return undefined; - }, - async putGroup(groupId, group) { - if (groupId === null || groupId === undefined) { - throw new Error('Tried to put group key for undefined/null id'); - } - if (group === null || group === undefined) { - throw new Error('Tried to put undefined/null group object'); - } - const data = { - id: groupId, - data: group, - }; - await window.Signal.Data.createOrUpdateGroup(data); - }, - async removeGroup(groupId) { - if (groupId === null || groupId === undefined) { - throw new Error('Tried to remove group key for undefined/null id'); - } - - await window.Signal.Data.removeGroupById(groupId); + await textsecure.storage.protocol.removeAllSessions(number); }, // Not yet processed messages - for resiliency @@ -848,27 +882,30 @@ return window.Signal.Data.getAllUnprocessed(); }, getUnprocessedById(id) { - return window.Signal.Data.getUnprocessedById(id, { Unprocessed }); + return window.Signal.Data.getUnprocessedById(id); }, addUnprocessed(data) { // We need to pass forceSave because the data has an id already, which will cause // an update instead of an insert. return window.Signal.Data.saveUnprocessed(data, { forceSave: true, - Unprocessed, }); }, - saveUnprocessed(data) { - return window.Signal.Data.saveUnprocessed(data, { Unprocessed }); + updateUnprocessedAttempts(id, attempts) { + return window.Signal.Data.updateUnprocessedAttempts(id, attempts); + }, + updateUnprocessedWithData(id, data) { + return window.Signal.Data.updateUnprocessedWithData(id, data); }, removeUnprocessed(id) { - return window.Signal.Data.removeUnprocessed(id, { Unprocessed }); + return window.Signal.Data.removeUnprocessed(id); }, removeAllUnprocessed() { return window.Signal.Data.removeAllUnprocessed(); }, async removeAllData() { await window.Signal.Data.removeAll(); + await this.hydrateCaches(); window.storage.reset(); await window.storage.fetch(); @@ -880,6 +917,7 @@ }, async removeAllConfiguration() { await window.Signal.Data.removeAllConfiguration(); + await this.hydrateCaches(); window.storage.reset(); await window.storage.fetch(); @@ -890,4 +928,5 @@ window.SignalProtocolStore = SignalProtocolStore; window.SignalProtocolStore.prototype.Direction = Direction; window.SignalProtocolStore.prototype.VerifiedStatus = VerifiedStatus; + window.SignalProtocolStore.prototype.convertVerifiedStatusToProtoState = convertVerifiedStatusToProtoState; })(); diff --git a/js/util_worker_tasks.js b/js/util_worker_tasks.js index 446ee7da86..90fa017cd8 100644 --- a/js/util_worker_tasks.js +++ b/js/util_worker_tasks.js @@ -46,8 +46,7 @@ function calcPoW( ttl, pubKey, data, - development, - nonceTrials = undefined, + difficulty = undefined, increment = 1, startNonce = 0 ) { @@ -56,8 +55,7 @@ function calcPoW( ttl, pubKey, data, - development, - nonceTrials, + difficulty, increment, startNonce ); diff --git a/js/views/.inbox_view.js.swp b/js/views/.inbox_view.js.swp deleted file mode 100644 index 460fcffa56..0000000000 Binary files a/js/views/.inbox_view.js.swp and /dev/null differ diff --git a/js/views/app_view.js b/js/views/app_view.js index 0fe238ac51..9b62d9a060 100644 --- a/js/views/app_view.js +++ b/js/views/app_view.js @@ -1,4 +1,4 @@ -/* global Backbone, Whisper, storage, _, ConversationController, $ */ +/* global Backbone, i18n, Whisper, storage, _, ConversationController, $ */ /* eslint-disable more/no-then */ @@ -15,6 +15,9 @@ this.applyTheme(); this.applyHideMenu(); + + this.showSeedDialog = this.showSeedDialog.bind(this); + this.showPasswordDialog = this.showPasswordDialog.bind(this); }, events: { 'click .openInstaller': 'openInstaller', // NetworkStatusView has this button @@ -22,7 +25,7 @@ }, applyTheme() { const iOS = storage.get('userAgent') === 'OWI'; - const theme = storage.get('theme-setting') || 'light'; + const theme = 'dark'; // storage.get('theme-setting') || 'dark'; this.$el .removeClass('light-theme') .removeClass('dark-theme') @@ -35,7 +38,7 @@ } }, applyHideMenu() { - const hideMenuBar = storage.get('hide-menu-bar', false); + const hideMenuBar = storage.get('hide-menu-bar', true); window.setAutoHideMenuBar(hideMenuBar); window.setMenuBarVisibility(!hideMenuBar); }, @@ -105,7 +108,7 @@ openStandalone() { window.addSetupMenuItems(); this.resetViews(); - this.standaloneView = new Whisper.StandaloneRegistrationView(); + this.standaloneView = new Whisper.SessionRegistrationView(); this.openView(this.standaloneView); }, closeStandalone() { @@ -127,8 +130,8 @@ // so its loading screen doesn't stick around forever. // Two primary techniques at play for this situation: - // - background.js has two openInbox() calls, and passes initalLoadComplete - // directly via the options parameter. + // - background.js has X number of openInbox() calls, + // and passes initalLoadComplete directly via the options parameter. // - in other situations openInbox() will be called with no options. So this // view keeps track of whether onEmpty() has ever been called with // this.initialLoadComplete. An example of this: on a phone-pairing setup. @@ -169,13 +172,21 @@ view.onProgress(count); } }, - openConversation(conversation) { - if (conversation) { + openConversation(id, messageId) { + if (id) { this.openInbox().then(() => { - this.inboxView.openConversation(conversation); + this.inboxView.openConversation(id, messageId); }); } }, + showEditProfileDialog(options) { + const dialog = new Whisper.EditProfileDialogView(options); + this.el.append(dialog.el); + }, + showUserDetailsDialog(options) { + const dialog = new Whisper.UserDetailsDialogView(options); + this.el.append(dialog.el); + }, showNicknameDialog({ pubKey, title, message, nickname, onOk, onCancel }) { const _title = title || `Change nickname for ${pubKey}`; const dialog = new Whisper.NicknameDialogView({ @@ -188,12 +199,69 @@ this.el.append(dialog.el); dialog.focusInput(); }, - showPasswordDialog({ type, resolve, reject }) { - const dialog = Whisper.getPasswordDialogView(type, resolve, reject); + showPasswordDialog(options) { + const dialog = new Whisper.PasswordDialogView(options); + this.el.append(dialog.el); + }, + showSeedDialog() { + const dialog = new Whisper.SeedDialogView(); + this.el.append(dialog.el); + }, + showQRDialog(string) { + const dialog = new Whisper.QRDialogView({ + value: string, + }); + this.el.append(dialog.el); + }, + showDevicePairingDialog(options) { + const dialog = new Whisper.DevicePairingDialogView(options); + + this.el.append(dialog.el); + }, + showDevicePairingWordsDialog() { + const dialog = new Whisper.DevicePairingWordsDialogView(); + this.el.append(dialog.el); + }, + showCreateGroup() { + // TODO: make it impossible to open 2 dialogs as once + // Currently, if the button is in focus, it is possible to + // create a new dialog by pressing 'Enter' + const dialog = new Whisper.CreateGroupDialogView(); + this.el.append(dialog.el); + }, + showUpdateGroupDialog(groupConvo) { + const dialog = new Whisper.UpdateGroupDialogView(groupConvo); + this.el.append(dialog.el); + }, + showSessionRestoreConfirmation(options) { + const dialog = new Whisper.ConfirmSessionResetView(options); + this.el.append(dialog.el); + }, + showLeaveGroupDialog(groupConvo) { + const title = groupConvo.isPublic() + ? i18n('deletePublicChannel') + : i18n('deleteContact'); + + const message = groupConvo.isPublic() + ? i18n('deletePublicChannelConfirmation') + : i18n('deleteContactConfirmation'); + + window.confirmationDialog({ + title, + message, + resolve: () => ConversationController.deleteContact(groupConvo.id), + }); + }, + showInviteFriendsDialog(groupConvo) { + const dialog = new Whisper.InviteFriendsDialogView(groupConvo); + this.el.append(dialog.el); + }, + showAddModeratorsDialog(groupConvo) { + const dialog = new Whisper.AddModeratorsDialogView(groupConvo); this.el.append(dialog.el); }, - showSeedDialog(seed) { - const dialog = new Whisper.SeedDialogView({ seed }); + showRemoveModeratorsDialog(groupConvo) { + const dialog = new Whisper.RemoveModeratorsDialogView(groupConvo); this.el.append(dialog.el); }, }); diff --git a/js/views/attachment_view.js b/js/views/attachment_view.js deleted file mode 100644 index 15654e82bd..0000000000 --- a/js/views/attachment_view.js +++ /dev/null @@ -1,230 +0,0 @@ -/* global $: false */ -/* global _: false */ -/* global Backbone: false */ -/* global filesize: false */ - -/* global i18n: false */ -/* global Signal: false */ -/* global Whisper: false */ - -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - const FileView = Whisper.View.extend({ - tagName: 'div', - className: 'fileView', - templateName: 'file-view', - render_attributes() { - return this.model; - }, - }); - - const ImageView = Backbone.View.extend({ - tagName: 'img', - initialize(blobUrl) { - this.blobUrl = blobUrl; - }, - events: { - load: 'update', - }, - update() { - this.trigger('update'); - }, - render() { - this.$el.attr('src', this.blobUrl); - return this; - }, - }); - - const MediaView = Backbone.View.extend({ - initialize(dataUrl, { contentType } = {}) { - this.dataUrl = dataUrl; - this.contentType = contentType; - this.$el.attr('controls', ''); - }, - events: { - canplay: 'canplay', - }, - canplay() { - this.trigger('update'); - }, - render() { - const $el = $(''); - $el.attr('src', this.dataUrl); - this.$el.append($el); - return this; - }, - }); - - const AudioView = MediaView.extend({ tagName: 'audio' }); - const VideoView = MediaView.extend({ tagName: 'video' }); - - // Blacklist common file types known to be unsupported in Chrome - const unsupportedFileTypes = ['audio/aiff', 'video/quicktime']; - - Whisper.AttachmentView = Backbone.View.extend({ - tagName: 'div', - className() { - if (this.isImage()) { - return 'attachment'; - } - return 'attachment bubbled'; - }, - initialize(options) { - this.blob = new Blob([this.model.data], { type: this.model.contentType }); - if (!this.model.size) { - this.model.size = this.model.data.byteLength; - } - if (options.timestamp) { - this.timestamp = options.timestamp; - } - }, - events: { - click: 'onClick', - }, - unload() { - this.blob = null; - - if (this.lightboxView) { - this.lightboxView.remove(); - } - if (this.fileView) { - this.fileView.remove(); - } - if (this.view) { - this.view.remove(); - } - - this.remove(); - }, - onClick() { - if (!this.isImage()) { - this.saveFile(); - return; - } - - const props = { - objectURL: this.objectUrl, - contentType: this.model.contentType, - onSave: () => this.saveFile(), - // implicit: `close` - }; - this.lightboxView = new Whisper.ReactWrapperView({ - Component: Signal.Components.Lightbox, - props, - onClose: () => Signal.Backbone.Views.Lightbox.hide(), - }); - Signal.Backbone.Views.Lightbox.show(this.lightboxView.el); - }, - isVoiceMessage() { - return Signal.Types.Attachment.isVoiceMessage(this.model); - }, - isAudio() { - const { contentType } = this.model; - // TODO: Implement and use `Signal.Util.GoogleChrome.isAudioTypeSupported`: - return Signal.Types.MIME.isAudio(contentType); - }, - isVideo() { - const { contentType } = this.model; - return Signal.Util.GoogleChrome.isVideoTypeSupported(contentType); - }, - isImage() { - const { contentType } = this.model; - return Signal.Util.GoogleChrome.isImageTypeSupported(contentType); - }, - mediaType() { - if (this.isVoiceMessage()) { - return 'voice'; - } else if (this.isAudio()) { - return 'audio'; - } else if (this.isVideo()) { - return 'video'; - } else if (this.isImage()) { - return 'image'; - } - - // NOTE: The existing code had no `return` but ESLint insists. Thought - // about throwing an error assuming this was unreachable code but it turns - // out that content type `image/tiff` falls through here: - return undefined; - }, - displayName() { - if (this.isVoiceMessage()) { - return i18n('voiceMessage'); - } - if (this.model.fileName) { - return this.model.fileName; - } - if (this.isAudio() || this.isVideo()) { - return i18n('mediaMessage'); - } - - return i18n('unnamedFile'); - }, - saveFile() { - Signal.Types.Attachment.save({ - attachment: this.model, - document, - getAbsolutePath: Signal.Migrations.getAbsoluteAttachmentPath, - timestamp: this.timestamp, - }); - }, - render() { - if (!this.isImage()) { - this.renderFileView(); - } - let View; - if (this.isImage()) { - View = ImageView; - } else if (this.isAudio()) { - View = AudioView; - } else if (this.isVideo()) { - View = VideoView; - } - - if (!View || _.contains(unsupportedFileTypes, this.model.contentType)) { - this.update(); - return this; - } - - if (!this.objectUrl) { - this.objectUrl = window.URL.createObjectURL(this.blob); - } - - const { blob } = this; - const { contentType } = this.model; - this.view = new View(this.objectUrl, { blob, contentType }); - this.view.$el.appendTo(this.$el); - this.listenTo(this.view, 'update', this.update); - this.view.render(); - if (View !== ImageView) { - this.timeout = setTimeout(this.onTimeout.bind(this), 5000); - } - return this; - }, - onTimeout() { - // Image or media element failed to load. Fall back to FileView. - this.stopListening(this.view); - this.update(); - }, - renderFileView() { - this.fileView = new FileView({ - model: { - mediaType: this.mediaType(), - fileName: this.displayName(), - fileSize: filesize(this.model.size), - altText: i18n('clickToSave'), - }, - }); - - this.fileView.$el.appendTo(this.$el.empty()); - this.fileView.render(); - return this; - }, - update() { - clearTimeout(this.timeout); - this.trigger('update'); - }, - }); -})(); diff --git a/js/views/beta_release_disclaimer_view.js b/js/views/beta_release_disclaimer_view.js new file mode 100644 index 0000000000..004d3582c6 --- /dev/null +++ b/js/views/beta_release_disclaimer_view.js @@ -0,0 +1,38 @@ +/* global Whisper, window */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.BetaReleaseDisclaimer = Whisper.View.extend({ + className: 'loki-dialog beta-disclaimer-dialog modal', + initialize() { + this.close = this.close.bind(this); + this.render(); + }, + + render() { + this.dialogView = new Whisper.ReactWrapperView({ + className: 'session-beta-disclaimer', + Component: window.Signal.Components.SessionConfirm, + props: { + title: window.i18n('betaDisclaimerTitle'), + message: window.i18n('betaDisclaimerSubtitle'), + messageSub: window.i18n('betaDisclaimerDescription'), + hideCancel: true, + onClickOk: this.close, + }, + }); + + this.$el.append(this.dialogView.el); + return this; + }, + + close() { + window.storage.put('betaReleaseDisclaimerAccepted', true); + this.remove(); + }, + }); +})(); diff --git a/js/views/blocked_number_view.js b/js/views/blocked_number_view.js index 3b8696355d..cc14c1c48a 100644 --- a/js/views/blocked_number_view.js +++ b/js/views/blocked_number_view.js @@ -39,7 +39,9 @@ }, onUnblock() { const number = this.$('select option:selected').val(); - if (!number) return; + if (!number) { + return; + } if (BlockedNumberController.isBlocked(number)) { BlockedNumberController.unblock(number); @@ -73,7 +75,9 @@ }, truncate(string, limit) { // Make sure an element and number of items to truncate is provided - if (!string || !limit) return string; + if (!string || !limit) { + return string; + } // Get the inner content of the element let content = string.trim(); @@ -84,7 +88,9 @@ // Convert the array of words back into a string // If there's content to add after it, add it - if (string.length > limit) content = `${content}...`; + if (string.length > limit) { + content = `${content}...`; + } return content; }, diff --git a/js/views/bulk_edit_view.js b/js/views/bulk_edit_view.js new file mode 100644 index 0000000000..ec6b37301b --- /dev/null +++ b/js/views/bulk_edit_view.js @@ -0,0 +1,37 @@ +/* global Whisper, */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.BulkEditView = Whisper.View.extend({ + initialize(options) { + this.memberView = null; + this.props = { + onCancel: options.onCancel, + onDelete: options.onDelete, + messageCount: 0, + }; + }, + render() { + if (this.memberView) { + this.memberView.update(this.props); + return; + } + this.memberView = new Whisper.ReactWrapperView({ + className: 'bulk-edit-view', + Component: window.Signal.Components.BulkEdit, + props: this.props, + }); + + this.$el.append(this.memberView.el); + }, + + update(selectionSize) { + this.props.messageCount = selectionSize; + this.render(); + }, + }); +})(); diff --git a/js/views/clear_data_view.js b/js/views/clear_data_view.js deleted file mode 100644 index 7af25d893f..0000000000 --- a/js/views/clear_data_view.js +++ /dev/null @@ -1,72 +0,0 @@ -/* global i18n: false */ -/* global Whisper: false */ - -/* eslint-disable no-new */ - -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - window.Whisper = window.Whisper || {}; - const { Logs } = window.Signal; - - const CLEAR_DATA_STEPS = { - CHOICE: 1, - DELETING: 2, - }; - window.Whisper.ClearDataView = Whisper.View.extend({ - templateName: 'clear-data', - className: 'full-screen-flow overlay', - events: { - 'click .cancel': 'onCancel', - 'click .delete-all-data': 'onDeleteAllData', - }, - initialize(onClear = null) { - this.step = CLEAR_DATA_STEPS.CHOICE; - this.onClear = onClear; - }, - onCancel() { - this.remove(); - }, - async onDeleteAllData() { - window.log.info('Deleting everything!'); - this.step = CLEAR_DATA_STEPS.DELETING; - this.render(); - - await this.clearAllData(); - }, - async clearAllData() { - if (this.onClear) { - this.onClear(); - } else { - try { - await Logs.deleteAll(); - - await window.Signal.Data.removeAll(); - await window.Signal.Data.close(); - await window.Signal.Data.removeDB(); - - await window.Signal.Data.removeOtherData(); - } catch (error) { - window.log.error( - 'Something went wrong deleting all data:', - error && error.stack ? error.stack : error - ); - } - window.restart(); - } - }, - render_attributes() { - return { - isStep1: this.step === CLEAR_DATA_STEPS.CHOICE, - header: i18n('deleteAllDataHeader'), - body: i18n('deleteAllDataBody'), - cancelButton: i18n('cancel'), - deleteButton: i18n('deleteAllDataButton'), - - isStep2: this.step === CLEAR_DATA_STEPS.DELETING, - deleting: i18n('deleteAllDataProgress'), - }; - }, - }); -})(); diff --git a/js/views/confirm_session_reset_view.js b/js/views/confirm_session_reset_view.js new file mode 100644 index 0000000000..aa34e420fd --- /dev/null +++ b/js/views/confirm_session_reset_view.js @@ -0,0 +1,50 @@ +/* global Whisper, i18n */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.ConfirmSessionResetView = Whisper.View.extend({ + className: 'loki-dialog modal', + initialize({ pubkey, onOk }) { + this.title = i18n('couldNotDecryptMessage'); + + this.onOk = onOk; + this.messageText = i18n('confirmSessionRestore', pubkey); + this.okText = i18n('yes'); + this.cancelText = i18n('cancel'); + + this.close = this.close.bind(this); + this.confirm = this.confirm.bind(this); + + this.$el.focus(); + this.render(); + }, + render() { + this.dialogView = new Whisper.ReactWrapperView({ + className: 'leave-group-dialog', + Component: window.Signal.Components.ConfirmDialog, + props: { + titleText: this.title, + messageText: this.messageText, + okText: this.okText, + cancelText: this.cancelText, + onConfirm: this.confirm, + onClose: this.close, + }, + }); + + this.$el.append(this.dialogView.el); + return this; + }, + async confirm() { + this.onOk(); + this.close(); + }, + close() { + this.remove(); + }, + }); +})(); diff --git a/js/views/connecting_to_server_dialog_view.js b/js/views/connecting_to_server_dialog_view.js new file mode 100644 index 0000000000..b2d800f1be --- /dev/null +++ b/js/views/connecting_to_server_dialog_view.js @@ -0,0 +1,61 @@ +/* global Whisper, i18n, log */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.ConnectingToServerDialogView = Whisper.View.extend({ + templateName: 'connecting-to-server-template', + className: 'loki-dialog connecting-to-server modal', + initialize(options = {}) { + this.title = i18n('connectingLoad'); + this.cancelText = options.cancelText || i18n('cancel'); + this.serverUrl = options.serverUrl; + this.channelId = options.channelId; + this.once('attemptConnection', () => + this.attemptConnection(options.serverUrl, options.channelId) + ); + this.render(); + }, + events: { + keyup: 'onKeyup', + 'click .cancel': 'close', + }, + async attemptConnection(serverUrl, channelId) { + let conversation = null; + try { + conversation = await window.attemptConnection(serverUrl, channelId); + } catch (e) { + log.error('can not connect', e.message, e.code); + return this.resolveWith({ errorCode: e.message }); + } + return this.resolveWith({ conversation }); + }, + resolveWith(result) { + this.trigger('connectionResult', result); + this.remove(); + }, + render_attributes() { + return { + title: this.title, + cancel: this.cancelText, + }; + }, + close() { + this.trigger('connectionResult', { cancelled: true }); + this.remove(); + }, + onKeyup(event) { + switch (event.key) { + case 'Escape': + case 'Esc': + this.close(); + break; + default: + break; + } + }, + }); +})(); diff --git a/js/views/conversation_list_view.js b/js/views/conversation_list_view.js index 0a69c78bf1..6d49ce8545 100644 --- a/js/views/conversation_list_view.js +++ b/js/views/conversation_list_view.js @@ -1,4 +1,4 @@ -/* global Whisper, getInboxCollection, getContactCollection, $ */ +/* global Whisper, getInboxCollection, $ */ // eslint-disable-next-line func-names (function() { @@ -68,11 +68,4 @@ } }, }); - - Whisper.ConversationContactListView = Whisper.ConversationListView.extend({ - itemView: Whisper.ConversationContactListItemView, - getCollection() { - return getContactCollection(); - }, - }); })(); diff --git a/js/views/conversation_loading_view.js b/js/views/conversation_loading_view.js new file mode 100644 index 0000000000..136de39cdf --- /dev/null +++ b/js/views/conversation_loading_view.js @@ -0,0 +1,22 @@ +/* global Whisper */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.ConversationLoadingScreen = Whisper.View.extend({ + initialize() {}, + + render() { + this.dialogView = new Whisper.ReactWrapperView({ + className: 'app-loading-wrapper', + Component: window.Signal.Components.ConversationLoadingScreen, + props: this.props, + }); + + this.$el.append(this.dialogView.el); + }, + }); +})(); diff --git a/js/views/conversation_search_view.js b/js/views/conversation_search_view.js index 234f8f7398..e1920e099f 100644 --- a/js/views/conversation_search_view.js +++ b/js/views/conversation_search_view.js @@ -1,6 +1,4 @@ -/* global ConversationController: false */ -/* global i18n: false */ -/* global Whisper: false */ +/* global ConversationController, i18n, textsecure, Whisper */ // eslint-disable-next-line func-names (function() { @@ -84,16 +82,27 @@ /* eslint-disable more/no-then */ this.pending = this.pending.then(() => this.typeahead.search(query).then(() => { - this.typeahead_view.collection.reset( - this.typeahead.filter(isSearchable) - ); + let results = this.typeahead.filter(isSearchable); + const noteToSelf = i18n('noteToSelf'); + if (noteToSelf.toLowerCase().indexOf(query.toLowerCase()) !== -1) { + const ourNumber = textsecure.storage.user.getNumber(); + const conversation = ConversationController.get(ourNumber); + if (conversation) { + // ensure that we don't have duplicates in our results + results = results.filter(item => item.id !== ourNumber); + results.unshift(conversation); + } + } + + this.typeahead_view.collection.reset(results); // This will allow us to show the last message when searching this.typeahead_view.collection.forEach(c => c.updateLastMessage()); // Show the new contact view if we already have results - if (this.typeahead_view.collection.length === 0) + if (this.typeahead_view.collection.length === 0) { this.new_contact_view.$el.show(); + } }) ); /* eslint-enable more/no-then */ diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 6e5cd98b3b..2e1fa720e6 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -17,32 +17,10 @@ 'use strict'; window.Whisper = window.Whisper || {}; - const { Message } = window.Signal.Types; - const { - upgradeMessageSchema, - getAbsoluteAttachmentPath, - } = window.Signal.Migrations; + const { getAbsoluteAttachmentPath } = window.Signal.Migrations; + + const MAX_MESSAGE_BODY_LENGTH = 64 * 1024; - Whisper.ExpiredToast = Whisper.ToastView.extend({ - render_attributes() { - return { toastMessage: i18n('expiredWarning') }; - }, - }); - Whisper.BlockedToast = Whisper.ToastView.extend({ - render_attributes() { - return { toastMessage: i18n('unblockToSend') }; - }, - }); - Whisper.BlockedGroupToast = Whisper.ToastView.extend({ - render_attributes() { - return { toastMessage: i18n('unblockGroupToSend') }; - }, - }); - Whisper.LeftGroupToast = Whisper.ToastView.extend({ - render_attributes() { - return { toastMessage: i18n('youLeftTheGroup') }; - }, - }); Whisper.OriginalNotFoundToast = Whisper.ToastView.extend({ render_attributes() { return { toastMessage: i18n('originalMessageNotFound') }; @@ -64,11 +42,6 @@ }, }); - Whisper.ConversationLoadingScreen = Whisper.View.extend({ - templateName: 'conversation-loading-screen', - className: 'conversation-loading-screen', - }); - Whisper.ConversationView = Whisper.View.extend({ className() { return ['conversation', this.model.get('type')].join(' '); @@ -81,17 +54,22 @@ return { 'disable-inputs': false, 'send-message': i18n('sendMessage'), - 'android-length-warning': i18n('androidMessageLengthWarning'), }; }, initialize(options) { this.listenTo(this.model, 'destroy', this.stopListening); this.listenTo(this.model, 'change:verified', this.onVerifiedChange); + this.listenTo( + this.model, + 'change:friendRequestStatus', + this.onFriendStatusChange + ); this.listenTo(this.model, 'newmessage', this.addMessage); this.listenTo(this.model, 'opened', this.onOpened); this.listenTo(this.model, 'prune', this.onPrune); this.listenTo(this.model, 'disable:input', this.onDisableInput); this.listenTo(this.model, 'change:placeholder', this.onChangePlaceholder); + this.listenTo(this.model, 'unload', () => this.unload('model trigger')); this.listenTo(this.model, 'typing-update', this.renderTypingBubble); this.listenTo( this.model.messageCollection, @@ -153,6 +131,7 @@ ); this.render(); + this.onFriendStatusChange(); this.model.updateTextInputState(); @@ -176,6 +155,9 @@ this.removeLinkPreview(); } }); + Whisper.events.on('mediaPermissionsChanged', () => + this.toggleMicrophone() + ); const getHeaderProps = () => { const expireTimer = this.model.get('expireTimer'); @@ -183,6 +165,8 @@ ? Whisper.ExpirationTimerOptions.getName(expireTimer || 0) : null; + const members = this.model.get('members') || []; + return { id: this.model.id, name: this.model.getName(), @@ -191,11 +175,22 @@ color: this.model.getColor(), avatarPath: this.model.getAvatarPath(), isVerified: this.model.isVerified(), - isKeysPending: !this.model.isFriend(), + isFriendRequestPending: this.model.isPendingFriendRequest(), + isFriend: this.model.isFriend(), isMe: this.model.isMe(), + isClosable: this.model.isClosable(), isBlocked: this.model.isBlocked(), isGroup: !this.model.isPrivate(), isOnline: this.model.isOnline(), + isArchived: this.model.get('isArchived'), + isPublic: this.model.isPublic(), + isRss: this.model.isRss(), + amMod: this.model.isModerator( + window.storage.get('primaryDevicePubKey') + ), + members, + subscriberCount: this.model.get('subscriberCount'), + selectedMessages: this.model.selectedMessages, expirationSettingName, showBackButton: Boolean(this.panels && this.panels.length), timerOptions: Whisper.ExpirationTimerOptions.map(item => ({ @@ -207,6 +202,8 @@ onSetDisappearingMessages: seconds => this.setDisappearingMessages(seconds), onDeleteMessages: () => this.destroyMessages(), + onDeleteSelectedMessages: () => this.deleteSelectedMessages(), + onCloseOverlay: () => this.model.resetMessageSelection(), onDeleteContact: () => this.model.deleteContact(), onResetSession: () => this.endSession(), @@ -216,11 +213,10 @@ this.showSafetyNumber(); }, onShowAllMedia: async () => { - await this.showAllMedia(); this.updateHeader(); }, - onShowGroupMembers: () => { - this.showMembers(); + onShowGroupMembers: async () => { + await this.showMembers(); this.updateHeader(); }, onGoBack: () => { @@ -243,12 +239,92 @@ onCopyPublicKey: () => { this.model.copyPublicKey(); }, + onArchive: () => { + this.unload('archive'); + this.model.setArchived(true); + }, + onMoveToInbox: () => { + this.model.setArchived(false); + }, + + onUpdateGroup: () => { + window.Whisper.events.trigger('updateGroup', this.model); + }, + + onLeaveGroup: () => { + window.Whisper.events.trigger('leaveGroup', this.model); + }, + + onInviteFriends: () => { + window.Whisper.events.trigger('inviteFriends', this.model); + }, + + onAddModerators: () => { + window.Whisper.events.trigger('addModerators', this.model); + }, + + onRemoveModerators: () => { + window.Whisper.events.trigger('removeModerators', this.model); + }, + + onAvatarClick: pubkey => { + if (this.model.isPrivate()) { + window.Whisper.events.trigger('onShowUserDetails', { + userPubKey: pubkey, + }); + } else if (!this.model.isRss()) { + this.showGroupSettings(); + } + }, + }; + }; + const getGroupSettingsProp = () => { + const members = this.model.get('members') || []; + + return { + id: this.model.id, + name: this.model.getName(), + phoneNumber: this.model.getNumber(), + profileName: this.model.getProfileName(), + color: this.model.getColor(), + avatarPath: this.model.getAvatarPath(), + isGroup: !this.model.isPrivate(), + isPublic: this.model.isPublic(), + isRss: this.model.isRss(), + memberCount: members.length, + + timerOptions: Whisper.ExpirationTimerOptions.map(item => ({ + name: item.getName(), + value: item.get('seconds'), + })), + + onSetDisappearingMessages: seconds => + this.setDisappearingMessages(seconds), + + onGoBack: () => { + this.$('.conversation-content-right').hide(); + }, + + onUpdateGroup: () => { + window.Whisper.events.trigger('updateGroup', this.model); + }, + + onLeaveGroup: () => { + window.Whisper.events.trigger('leaveGroup', this.model); + }, + + onInviteFriends: () => { + window.Whisper.events.trigger('inviteFriends', this.model); + }, + onShowLightBox: (lightBoxOptions = {}) => { + this.showChannelLightbox(lightBoxOptions); + }, }; }; this.titleView = new Whisper.ReactWrapperView({ className: 'title-wrapper', Component: window.Signal.Components.ConversationHeader, - props: getHeaderProps(this.model), + props: getHeaderProps(), }); this.updateHeader = () => this.titleView.update(getHeaderProps()); this.listenTo(this.model, 'change', this.updateHeader); @@ -261,6 +337,37 @@ this.$('.discussion-container').append(this.view.el); this.view.render(); + this.memberView = new Whisper.MemberListView({ + el: this.$('.member-list-container'), + onClicked: this.selectMember.bind(this), + }); + + this.showGroupSettings = () => { + if (!this.groupSettings) { + this.groupSettings = new Whisper.ReactWrapperView({ + className: 'group-settings', + Component: window.Signal.Components.SessionChannelSettings, + props: getGroupSettingsProp(this.model), + }); + this.$('.conversation-content-right').append(this.groupSettings.el); + } else { + this.groupSettings.update(getGroupSettingsProp(this.model)); + } + this.$('.conversation-content-right').show(); + }; + + this.hideGroupSettings = () => { + this.$('.conversation-content-right').hide(); + }; + + this.memberView.render(); + + this.bulkEditView = new Whisper.BulkEditView({ + el: this.$('#bulk-edit-view'), + onCancel: this.resetMessageSelection.bind(this), + onDelete: this.deleteSelectedMessages.bind(this), + }); + this.$messageField = this.$('.send-message'); this.onResize = this.forceUpdateMessageFieldSize.bind(this); @@ -284,13 +391,42 @@ this.$emojiPanelContainer = this.$('.emoji-panel-container'); this.model.updateTextInputState(); + + this.selectMember = this.selectMember.bind(this); + + const updateMemberList = async () => { + const allPubKeys = await window.Signal.Data.getPubkeysInPublicConversation( + this.model.id + ); + + const allMembers = await Promise.all( + allPubKeys.map(async pubKey => { + const conv = ConversationController.get(pubKey); + let profileName = 'Anonymous'; + if (conv) { + profileName = await conv.getProfileName(); + } + return { + id: pubKey, + authorPhoneNumber: pubKey, + authorProfileName: profileName, + }; + }) + ); + window.lokiPublicChatAPI.setListOfMembers(allMembers); + }; + + if (this.model.isPublic()) { + updateMemberList(); + setInterval(updateMemberList, 10000); + } }, events: { keydown: 'onKeyDown', - 'submit .send': 'checkUnverifiedSendMessage', - 'input .send-message': 'updateMessageFieldSize', - 'keydown .send-message': 'updateMessageFieldSize', + 'submit .send': 'handleSubmitPressed', + 'input .send-message': 'handleInputEvent', + 'keydown .send-message': 'handleInputEvent', 'keyup .send-message': 'onKeyUp', click: 'onClick', 'click .bottom-bar': 'focusMessageField', @@ -372,7 +508,9 @@ }, onChangePlaceholder(type) { - if (!this.$messageField) return; + if (!this.$messageField) { + return; + } let placeholder; switch (type) { case 'friend-request': @@ -381,6 +519,12 @@ case 'disabled': placeholder = i18n('sendMessageDisabled'); break; + case 'secondary': + placeholder = i18n('sendMessageDisabledSecondary'); + break; + case 'left-group': + placeholder = i18n('sendMessageLeftGroup'); + break; default: placeholder = i18n('sendMessage'); break; @@ -572,12 +716,18 @@ } }, - toggleMicrophone() { - // ALWAYS HIDE until we support audio - this.$('.capture-audio').hide(); + onFriendStatusChange() { + if (this.model.isPrivate() && !this.model.isFriend()) { + this.$('#choose-file').hide(); + } else { + this.$('#choose-file').show(); + } + }, - /* + async toggleMicrophone() { + const allowMicrophone = await window.getMediaPermissions(); if ( + !allowMicrophone || this.$('.send-message').val().length > 0 || this.fileInput.hasFiles() ) { @@ -585,14 +735,6 @@ } else { this.$('.capture-audio').show(); } - */ - }, - toggleLengthWarning() { - if (this.$('.send-message').val().length > 2000) { - this.$('.android-length-warning').show(); - } else { - this.$('.android-length-warning').hide(); - } }, captureAudio(e) { e.preventDefault(); @@ -680,6 +822,17 @@ this.lastActivity = Date.now(); this.model.updateLastMessage(); + this.model.resetMessageSelection(); + + if (this.model.isRss()) { + $('.compose').hide(); + $('.conversation-stack').removeClass('conversation-stack-no-border'); + $('.conversation-stack').addClass('conversation-stack-border'); + } else { + $('.compose').show(); + $('.conversation-stack').removeClass('conversation-stack-border'); + $('.conversation-stack').addClass('conversation-stack-no-border'); + } // const statusPromise = this.throttledGetProfiles(); // // eslint-disable-next-line more/no-then @@ -724,9 +877,7 @@ }, updateScrollDownButton(count) { - if (this.scrollDownButton) { - this.scrollDownButton.increment(count); - } else { + if (!this.scrollDownButton) { this.scrollDownButton = new Whisper.ScrollDownButtonView({ count }); this.scrollDownButton.render(); const container = this.$('.discussion-container'); @@ -782,13 +933,15 @@ const collection = await window.Signal.Data.getMessagesBySentAt(id, { MessageCollection: Whisper.MessageCollection, }); - const messageFromDatabase = collection.find(item => { - const messageAuthor = item.getContact(); + const found = Boolean( + collection.find(item => { + const messageAuthor = item.getContact(); - return messageAuthor && author === messageAuthor.id; - }); + return messageAuthor && author === messageAuthor.id; + }) + ); - if (messageFromDatabase) { + if (found) { const toast = new Whisper.FoundButNotLoadedToast(); toast.$el.appendTo(this.$el); toast.render(); @@ -816,130 +969,6 @@ el[0].scrollIntoView(); }, - async showAllMedia() { - // We fetch more documents than media as they don’t require to be loaded - // into memory right away. Revisit this once we have infinite scrolling: - const DEFAULT_MEDIA_FETCH_COUNT = 50; - const DEFAULT_DOCUMENTS_FETCH_COUNT = 150; - - const conversationId = this.model.get('id'); - const rawMedia = await Signal.Data.getMessagesWithVisualMediaAttachments( - conversationId, - { - limit: DEFAULT_MEDIA_FETCH_COUNT, - MessageCollection: Whisper.MessageCollection, - } - ); - const rawDocuments = await Signal.Data.getMessagesWithFileAttachments( - conversationId, - { - limit: DEFAULT_DOCUMENTS_FETCH_COUNT, - MessageCollection: Whisper.MessageCollection, - } - ); - - // First we upgrade these messages to ensure that they have thumbnails - for (let max = rawMedia.length, i = 0; i < max; i += 1) { - const message = rawMedia[i]; - const { schemaVersion } = message; - - if (schemaVersion < Message.VERSION_NEEDED_FOR_DISPLAY) { - // Yep, we really do want to wait for each of these - // eslint-disable-next-line no-await-in-loop - rawMedia[i] = await upgradeMessageSchema(message); - // eslint-disable-next-line no-await-in-loop - await window.Signal.Data.saveMessage(rawMedia[i], { - Message: Whisper.Message, - }); - } - } - - const media = _.flatten( - rawMedia.map(message => { - const { attachments } = message; - return (attachments || []).map((attachment, index) => { - const { thumbnail } = attachment; - - return { - objectURL: getAbsoluteAttachmentPath(attachment.path), - thumbnailObjectUrl: thumbnail - ? getAbsoluteAttachmentPath(thumbnail.path) - : null, - contentType: attachment.contentType, - index, - attachment, - message, - }; - }); - }) - ); - - // Unlike visual media, only one non-image attachment is supported - const documents = rawDocuments.map(message => { - const attachments = message.attachments || []; - const attachment = attachments[0]; - return { - contentType: attachment.contentType, - index: 0, - attachment, - message, - }; - }); - - const saveAttachment = async ({ attachment, message } = {}) => { - const timestamp = message.received_at; - Signal.Types.Attachment.save({ - attachment, - document, - getAbsolutePath: getAbsoluteAttachmentPath, - timestamp, - }); - }; - - const onItemClick = async ({ message, attachment, type }) => { - switch (type) { - case 'documents': { - saveAttachment({ message, attachment }); - break; - } - - case 'media': { - const selectedIndex = media.findIndex( - mediaMessage => mediaMessage.attachment.path === attachment.path - ); - this.lightboxGalleryView = new Whisper.ReactWrapperView({ - className: 'lightbox-wrapper', - Component: Signal.Components.LightboxGallery, - props: { - media, - onSave: saveAttachment, - selectedIndex, - }, - onClose: () => Signal.Backbone.Views.Lightbox.hide(), - }); - Signal.Backbone.Views.Lightbox.show(this.lightboxGalleryView.el); - break; - } - - default: - throw new TypeError(`Unknown attachment type: '${type}'`); - } - }; - - const view = new Whisper.ReactWrapperView({ - className: 'panel-wrapper', - Component: Signal.Components.MediaGallery, - props: { - documents, - media, - onItemClick, - }, - onClose: () => this.resetPanel(), - }); - - this.listenBack(view); - }, - scrollToBottom() { // If we're above the last seen indicator, we should scroll there instead // Note: if we don't end up at the bottom of the conversation, button won't go away! @@ -1183,13 +1212,12 @@ } }, - showMembers(e, providedMembers, options = {}) { + async showMembers(e, providedMembers, options = {}) { _.defaults(options, { needVerify: false }); - const members = providedMembers || this.model.contactCollection; - + const model = providedMembers || this.model.contactCollection; const view = new Whisper.GroupMemberList({ - model: members, + model, // we pass this in to allow nested panels listenBack: this.listenBack.bind(this), needVerify: options.needVerify, @@ -1199,7 +1227,7 @@ }, forceSend({ contact, message }) { - const dialog = new Whisper.ConfirmationDialogView({ + window.confirmationDialog({ message: i18n('identityKeyErrorOnSend', [ contact.getTitle(), contact.getTitle(), @@ -1220,9 +1248,6 @@ message.resend(contact.id); }, }); - - this.$el.prepend(dialog.el); - dialog.focusCancel(); }, showSafetyNumber(providedModel) { @@ -1248,32 +1273,113 @@ toast.render(); return; } - Signal.Types.Attachment.save({ attachment, document, getAbsolutePath: getAbsoluteAttachmentPath, - timestamp: message.get('sent_at'), + timestamp: message.get ? message.get('sent_at') : message.sent_at, }); }, - deleteMessage(message) { - const dialog = new Whisper.ConfirmationDialogView({ - message: i18n('deleteWarning'), + deleteSelectedMessages() { + const ourPubkey = textsecure.storage.user.getNumber(); + const selected = Array.from(this.model.selectedMessages); + const isModerator = this.model.isModerator(ourPubkey); + const isAllOurs = selected.every( + message => message.attributes.source === message.OUR_NUMBER + ); + + if (!isAllOurs && !isModerator) { + window.pushToast({ + title: i18n('messageDeletionForbidden'), + type: 'error', + id: 'messageDeletionForbidden', + }); + + return; + } + + this.deleteMessages(selected, () => { + this.resetMessageSelection(); + }); + }, + + deleteMessages(messages, onSuccess) { + const multiple = messages.length > 1; + + const warningMessage = (() => { + if (this.model.isPublic()) { + return multiple + ? i18n('deleteMultiplePublicWarning') + : i18n('deletePublicWarning'); + } + return multiple ? i18n('deleteMultipleWarning') : i18n('deleteWarning'); + })(); + + const doDelete = async () => { + let toDeleteLocally; + + if (this.model.isPublic()) { + toDeleteLocally = await this.model.deletePublicMessages(messages); + if (toDeleteLocally.length === 0) { + // Message failed to delete from server, show error? + return; + } + } else { + messages.forEach(m => this.model.messageCollection.remove(m.id)); + toDeleteLocally = messages; + } + + await Promise.all( + toDeleteLocally.map(async m => { + await window.Signal.Data.removeMessage(m.id, { + Message: Whisper.Message, + }); + m.trigger('unload'); + }) + ); + + this.resetPanel(); + this.updateHeader(); + + if (onSuccess) { + onSuccess(); + } + }; + + // Only show a warning when at least one messages was successfully + // saved in on the server + if (!messages.some(m => !m.hasErrors())) { + doDelete(); + return; + } + + window.confirmationDialog({ + message: warningMessage, okText: i18n('delete'), - resolve: () => { - window.Signal.Data.removeMessage(message.id, { - Message: Whisper.Message, - }); - message.trigger('unload'); - this.model.messageCollection.remove(message.id); - this.resetPanel(); - this.updateHeader(); - }, + resolve: doDelete, }); + }, + + deleteMessage(message) { + this.deleteMessages([message]); + }, - this.$el.prepend(dialog.el); - dialog.focusCancel(); + showChannelLightbox({ media, attachment, message }) { + const selectedIndex = media.findIndex( + mediaMessage => mediaMessage.attachment.path === attachment.path + ); + this.lightboxGalleryView = new Whisper.ReactWrapperView({ + className: 'lightbox-wrapper', + Component: Signal.Components.LightboxGallery, + props: { + media, + onSave: () => this.downloadAttachment({ attachment, message }), + selectedIndex, + }, + onClose: () => Signal.Backbone.Views.Lightbox.hide(), + }); + Signal.Backbone.Views.Lightbox.show(this.lightboxGalleryView.el); }, showLightbox({ attachment, message }) { @@ -1288,7 +1394,19 @@ } const attachments = message.get('attachments') || []; - if (attachments.length === 1) { + + const media = attachments + .filter(item => item.thumbnail && !item.pending && !item.error) + .map((item, index) => ({ + objectURL: getAbsoluteAttachmentPath(item.path), + path: item.path, + contentType: item.contentType, + index, + message, + attachment: item, + })); + + if (media.length === 1) { const props = { objectURL: getAbsoluteAttachmentPath(path), contentType, @@ -1299,30 +1417,28 @@ className: 'lightbox-wrapper', Component: Signal.Components.Lightbox, props, - onClose: () => Signal.Backbone.Views.Lightbox.hide(), + onClose: () => { + Signal.Backbone.Views.Lightbox.hide(); + this.stopListening(message); + }, }); + this.listenTo(message, 'expired', () => this.lightboxView.remove()); Signal.Backbone.Views.Lightbox.show(this.lightboxView.el); return; } const selectedIndex = _.findIndex( - attachments, + media, item => attachment.path === item.path ); - const media = attachments.map((item, index) => ({ - objectURL: getAbsoluteAttachmentPath(item.path), - contentType: item.contentType, - index, - message, - attachment: item, - })); const onSave = async (options = {}) => { Signal.Types.Attachment.save({ attachment: options.attachment, document, + index: options.index + 1, getAbsolutePath: getAbsoluteAttachmentPath, - timestamp: options.message.received_at, + timestamp: options.message.get('sent_at'), }); }; @@ -1335,26 +1451,35 @@ className: 'lightbox-wrapper', Component: Signal.Components.LightboxGallery, props, - onClose: () => Signal.Backbone.Views.Lightbox.hide(), + onClose: () => { + Signal.Backbone.Views.Lightbox.hide(); + this.stopListening(message); + }, }); + this.listenTo(message, 'expired', () => + this.lightboxGalleryView.remove() + ); Signal.Backbone.Views.Lightbox.show(this.lightboxGalleryView.el); }, showMessageDetail(message) { + const onClose = () => { + this.stopListening(message, 'change', update); + this.resetPanel(); + this.updateHeader(); + }; + const props = message.getPropsForMessageDetail(); const view = new Whisper.ReactWrapperView({ className: 'message-detail-wrapper', Component: Signal.Components.MessageDetail, props, - onClose: () => { - this.stopListening(message, 'change', update); - this.resetPanel(); - this.updateHeader(); - }, + onClose, }); const update = () => view.update(message.getPropsForMessageDetail()); this.listenTo(message, 'change', update); + this.listenTo(message, 'expired', onClose); // We could listen to all involved contacts, but we'll call that overkill this.listenBack(view); @@ -1394,11 +1519,7 @@ }, async openConversation(number) { - const conversation = await window.ConversationController.getOrCreateAndWait( - number, - 'private' - ); - window.Whisper.events.trigger('showConversation', conversation); + window.Whisper.events.trigger('showConversation', number); }, listenBack(view) { @@ -1439,11 +1560,22 @@ }, destroyMessages() { + const message = this.model.isPublic() + ? i18n('deletePublicConversationConfirmation') + : i18n('deleteConversationConfirmation'); + Whisper.events.trigger('showConfirmationDialog', { - message: i18n('deleteConversationConfirmation'), + message, onOk: async () => { - await this.model.destroyMessages(); - this.remove(); + try { + await this.model.destroyMessages(); + this.unload('delete messages'); + } catch (error) { + window.log.error( + 'destroyMessages: Failed to successfully delete conversation', + error && error.stack ? error.stack : error + ); + } }, }); }, @@ -1467,7 +1599,8 @@ } } - const dialog = new Whisper.ConfirmationDialogView({ + window.confirmationDialog({ + title: i18n('changedSinceVerifiedTitle'), message, okText: i18n('sendAnyway'), resolve: () => { @@ -1477,9 +1610,58 @@ this.focusMessageFieldAndClearDisabled(); }, }); + }, + + stripQuery(text, cursorPos) { + const end = text.slice(cursorPos).search(/[^a-fA-F0-9]/); + const mentionEnd = end === -1 ? text.length : cursorPos + end; + + const stripped = text.substr(0, mentionEnd); + + const mentionStart = stripped.lastIndexOf('@'); + + const query = stripped.substr(mentionStart, mentionEnd - mentionStart); - this.$el.prepend(dialog.el); - dialog.focusCancel(); + return [stripped.substr(0, mentionStart), query, text.substr(mentionEnd)]; + }, + + selectMember(member) { + const cursorPos = this.$messageField[0].selectionStart; + // Note: skipping the middle value here + const [prev, , end] = this.stripQuery( + this.$messageField.val(), + cursorPos + ); + + const handle = this.memberView.addPubkeyMapping( + member.authorProfileName, + member.authorPhoneNumber + ); + + let firstHalf = `${prev}${handle}`; + let newCursorPos = firstHalf.length; + + const needExtraWhitespace = end.length === 0 || /\b/.test(end[0]); + if (needExtraWhitespace) { + firstHalf += ' '; + newCursorPos += 1; + } + + const result = firstHalf + end; + + this.$messageField.val(result); + this.$messageField[0].selectionStart = newCursorPos; + this.$messageField[0].selectionEnd = newCursorPos; + this.$messageField.trigger('input'); + }, + + async handleSubmitPressed(e, options = {}) { + if (this.memberView.membersShown()) { + const member = this.memberView.selectedMember(); + this.selectMember(member); + } else { + await this.checkUnverifiedSendMessage(e, options); + } }, async checkUnverifiedSendMessage(e, options = {}) { @@ -1541,6 +1723,10 @@ } }, + resetMessageSelection() { + this.model.resetMessageSelection(); + }, + toggleEmojiPanel(e) { e.preventDefault(); if (!this.emojiPanel) { @@ -1553,6 +1739,13 @@ if (event.key !== 'Escape') { return; } + + // TODO: this view is not always in focus (e.g. after I've selected a message), + // so need to make Esc more robust + // Perhaps look into ConversationHeader.tsx and add an event listener in there. + // Up and down arrows should scroll + // Alt + up and down should swap between conversations / setting categories + this.model.resetMessageSelection(); this.closeEmojiPanel(); }, openEmojiPanel() { @@ -1661,38 +1854,48 @@ this.closeEmojiPanel(); this.model.clearTypingTimers(); - let toast; - if (extension.expired()) { - toast = new Whisper.ExpiredToast(); + const input = this.$messageField; + + let message = this.memberView.replaceMentions(input.val()); + message = window.Signal.Emoji.replaceColons(message).trim(); + + const toastOptions = { type: 'info' }; + // let it pass if we're still trying to read it or it's false... + if (extension.expiredStatus() === true) { + toastOptions.title = i18n('expiredWarning'); + toastOptions.id = 'expiredWarning'; + } + if (!window.clientClockSynced) { + // Check to see if user has updated their clock to current time + const clockSynced = await window.LokiPublicChatAPI.setClockParams(); + if (clockSynced) { + toastOptions.title = i18n('clockOutOfSync'); + toastOptions.id = 'clockOutOfSync'; + } } if (this.model.isPrivate() && storage.isBlocked(this.model.id)) { - toast = new Whisper.BlockedToast(); + toastOptions.title = i18n('unblockToSend'); + toastOptions.id = 'unblockToSend'; } if (!this.model.isPrivate() && storage.isGroupBlocked(this.model.id)) { - toast = new Whisper.BlockedGroupToast(); + toastOptions.title = i18n('unblockGroupToSend'); + toastOptions.id = 'unblockGroupToSend'; } if (!this.model.isPrivate() && this.model.get('left')) { - toast = new Whisper.LeftGroupToast(); + toastOptions.title = i18n('youLeftTheGroup'); + toastOptions.id = 'youLeftTheGroup'; + } + if (message.length > MAX_MESSAGE_BODY_LENGTH) { + toastOptions.title = i18n('messageBodyTooLong'); + toastOptions.id = 'messageBodyTooLong'; } - if (toast) { - toast.$el.appendTo(this.$el); - toast.render(); + if (toastOptions.title) { + window.pushToast(toastOptions); this.focusMessageFieldAndClearDisabled(); return; } - const input = this.$messageField; - const inputMessage = window.Signal.Emoji.replaceColons( - input.val() - ).trim(); - - // Limit the message to 2000 characters - const message = inputMessage.substring( - 0, - Math.min(2000, inputMessage.length) - ); - try { if (!message.length && !this.fileInput.hasFiles()) { return; @@ -1710,6 +1913,7 @@ ); input.val(''); + this.memberView.deleteMention(); this.setQuoteMessage(null); this.resetLinkPreview(); this.focusMessageFieldAndClearDisabled(); @@ -1732,7 +1936,7 @@ maybeGrabLinkPreview() { // Don't generate link previews if user has turned them off - if (!storage.get('linkPreviews', false)) { + if (!storage.get('link-preview-setting', false)) { return; } // Do nothing if we're offline @@ -1749,6 +1953,7 @@ } const messageText = this.$messageField.val().trim(); + const caretLocation = this.$messageField.get(0).selectionStart; if (!messageText) { this.resetLinkPreview(); @@ -1758,7 +1963,10 @@ return; } - const links = window.Signal.LinkPreviews.findLinks(messageText); + const links = window.Signal.LinkPreviews.findLinks( + messageText, + caretLocation + ); const { currentlyMatchedLink } = this; if (links.includes(currentlyMatchedLink)) { return; @@ -2009,7 +2217,7 @@ getLinkPreview() { // Don't generate link previews if user has turned them off - if (!storage.get('linkPreviews', false)) { + if (!storage.get('link-preview-setting', false)) { return []; } @@ -2039,7 +2247,173 @@ } }, - updateMessageFieldSize(event) { + handleDeleteOrBackspace(event, isDelete) { + const $input = this.$messageField[0]; + const text = this.$messageField.val(); + + // Only handle the case when nothing is selected + if ($input.selectionDirection !== 'none') { + // Note: if this ends up deleting a handle, we should + // (ideally) check if we need to update the mapping in + // `this.memberView`, but that's not vital as we already + // reset it on every 'send' + return; + } + + const mentions = this.memberView.pendingMentions(); + + const _ = window.Lodash; // no underscore.js please + const predicate = isDelete ? _.startsWith : _.endsWith; + + const pos = $input.selectionStart; + const part = isDelete ? text.substr(pos) : text.substr(0, pos); + + const curMention = _.keys(mentions).find(key => predicate(part, key)); + + if (!curMention) { + return; + } + + event.preventDefault(); + + const beforeMention = isDelete + ? text.substr(0, pos) + : text.substr(0, pos - curMention.length); + const afterMention = isDelete + ? text.substr(pos + curMention.length) + : text.substr(pos); + + const resText = beforeMention + afterMention; + // NOTE: this doesn't work well with undo/redo, perhaps + // we should fix it one day + this.$messageField.val(resText); + + const nextPos = isDelete ? pos : pos - curMention.length; + + $input.selectionStart = nextPos; + $input.selectionEnd = nextPos; + + this.memberView.deleteMention(curMention); + }, + + handleLeftRight(event, isLeft) { + // Return next cursor position candidate before we take + // various modifier keys into account + const nextPos = (text, cursorPos, isLeft2, isAltPressed) => { + // If the next char is ' ', skip it if Alt is pressed + let pos = cursorPos; + if (isAltPressed) { + const nextChar = isLeft2 + ? text.substr(pos - 1, 1) + : text.substr(pos, 1); + if (nextChar === ' ') { + pos = isLeft2 ? pos - 1 : pos + 1; + } + } + + const part = isLeft2 ? text.substr(0, pos) : text.substr(pos); + + const mentions = this.memberView.pendingMentions(); + + const predicate = isLeft2 + ? window.Lodash.endsWith + : window.Lodash.startsWith; + + const curMention = _.keys(mentions).find(key => predicate(part, key)); + + const offset = curMention ? curMention.length : 1; + + const resPos = isLeft2 ? Math.max(0, pos - offset) : pos + offset; + + return resPos; + }; + + event.preventDefault(); + + const $input = this.$messageField[0]; + + const posStart = $input.selectionStart; + const posEnd = $input.selectionEnd; + + const text = this.$messageField.val(); + + const posToChange = + $input.selectionDirection === 'forward' ? posEnd : posStart; + + let newPos = nextPos(text, posToChange, isLeft, event.altKey); + + // If command (macos) key is pressed, go to the beginning/end + // (this shouldn't affect Windows, but we should double check that) + if (event.metaKey) { + newPos = isLeft ? 0 : text.length; + } + + // Alt would normally make the cursor go until the next whitespace, + // but we need to take the presence of a mention into account + if (event.altKey || event.ctrlKey) { + const searchFrom = isLeft ? posToChange - 1 : posToChange + 1; + const toSearch = isLeft + ? text.substr(0, searchFrom) + : text.substr(searchFrom); + + // Note: we don't seem to support tabs etc, thus no /\s/ + let nextAltPos = isLeft + ? toSearch.lastIndexOf(' ') + : toSearch.indexOf(' '); + + if (nextAltPos === -1) { + nextAltPos = isLeft ? 0 : text.length; + } else if (isLeft) { + nextAltPos += 1; + } + + if (isLeft) { + newPos = Math.min(newPos, nextAltPos); + } else { + newPos = Math.max(newPos, nextAltPos + searchFrom); + } + } + + // ==== Handle selection business ==== + let newPosStart = newPos; + let newPosEnd = newPos; + + let direction = $input.selectionDirection; + + if (event.shiftKey) { + if (direction === 'none') { + if (isLeft) { + direction = 'backward'; + } else { + direction = 'forward'; + } + } + } else { + direction = 'none'; + } + + if (direction === 'forward') { + newPosStart = posStart; + } else if (direction === 'backward') { + newPosEnd = posEnd; + } + + if (newPosStart === newPosEnd) { + direction = 'none'; + } + + $input.setSelectionRange(newPosStart, newPosEnd, direction); + }, + + // Note: not only input, but keypresses too (rename?) + handleInputEvent(event) { + // Note: schedule the member list handler shortly afterwards, so + // that the input element has time to update its cursor position to + // what the user would expect + if (this.model.get('type') === 'group') { + window.requestAnimationFrame(this.maybeShowMembers.bind(this, event)); + } + const keyCode = event.which || event.keyCode; if ( @@ -2053,8 +2427,56 @@ this.$('.bottom-bar form').submit(); return; } + + const keyPressedLeft = keyCode === 37; + const keyPressedUp = keyCode === 38; + const keyPressedRight = keyCode === 39; + const keyPressedDown = keyCode === 40; + const keyPressedTab = keyCode === 9; + + const preventDefault = keyPressedUp || keyPressedDown || keyPressedTab; + + if (this.memberView.membersShown() && preventDefault) { + if (keyPressedDown) { + this.memberView.selectDown(); + } else if (keyPressedUp) { + this.memberView.selectUp(); + } else if (keyPressedTab) { + // Tab is treated as Enter in this context + this.handleSubmitPressed(); + } + + const $selected = this.$('.member-selected'); + if ($selected.length) { + $selected[0].scrollIntoView({ behavior: 'smooth' }); + } + event.preventDefault(); + return; + } + + if (keyPressedLeft || keyPressedRight) { + this.$messageField.trigger('input'); + this.handleLeftRight(event, keyPressedLeft); + + return; + } + + const keyPressedDelete = keyCode === 46; + const keyPressedBackspace = keyCode === 8; + + if (keyPressedDelete) { + this.handleDeleteOrBackspace(event, true); + } + + if (keyPressedBackspace) { + this.handleDeleteOrBackspace(event, false); + } + + this.updateMessageFieldSize(); + }, + + updateMessageFieldSize() { this.toggleMicrophone(); - this.toggleLengthWarning(); this.view.measureScrollPosition(); window.autosize(this.$messageField); @@ -2078,6 +2500,100 @@ this.view.scrollToBottomIfNeeded(); }, + async maybeShowMembers(event) { + const filterMembers = (caseSensitiveQuery, member) => { + const { authorPhoneNumber, authorProfileName } = member; + + const profileName = authorProfileName + ? authorProfileName.toLowerCase() + : ''; + const query = caseSensitiveQuery.toLowerCase(); + + if (authorPhoneNumber.includes(query) || profileName.includes(query)) { + return true; + } + return false; + }; + + // This is not quite the same as stripQuery + // as this one searches until the current + // cursor position + const getQuery = (srcLine, cursorPos) => { + const input = srcLine.substr(0, cursorPos); + + const atPos = input.lastIndexOf('@'); + if (atPos === -1) { + return null; + } + + // Whitespace is required right before @ unless + // the beginning of line + if (atPos > 0 && /\w/.test(input.substr(atPos - 1, 1))) { + return null; + } + + const query = input.substr(atPos + 1); + + // No whitespaces allowed in a query + if (/\s/.test(query)) { + return null; + } + + return query; + }; + + let allMembers; + + if (this.model.isPublic()) { + // const api = await this.model.getPublicSendData(); + // not quite in the right format tho yet... + // let members = await api.getSubscribers(); + let members = await window.lokiPublicChatAPI.getListOfMembers(); + members = members + .filter(d => !!d) + .filter(d => d.authorProfileName !== 'Anonymous'); + allMembers = _.uniq(members, true, d => d.authorPhoneNumber); + } else { + const members = this.model.get('members'); + if (!members || members.length === 0) { + return; + } + + const privateConvos = window + .getConversations() + .models.filter(d => d.isPrivate()); + const memberConvos = members + .map(m => privateConvos.find(c => c.id === m)) + .filter(c => !!c && c.getLokiProfile()); + + allMembers = memberConvos.map(c => ({ + id: c.id, + authorPhoneNumber: c.id, + authorProfileName: c.getLokiProfile().displayName, + })); + } + + const cursorPos = event.target.selectionStart; + + // can't use pubkeyPattern here, as we are matching incomplete + // pubkeys (including the single @) + const query = getQuery(event.target.value, cursorPos); + + let membersToShow = []; + if (query !== null) { + membersToShow = + query !== '' + ? allMembers.filter(m => filterMembers(query, m)) + : allMembers; + } + + membersToShow = membersToShow.map(m => + _.pick(m, ['authorPhoneNumber', 'authorProfileName', 'id']) + ); + + this.memberView.updateMembers(membersToShow); + }, + forceUpdateMessageFieldSize(event) { if (this.isHidden()) { return; diff --git a/js/views/create_group_dialog_view.js b/js/views/create_group_dialog_view.js new file mode 100644 index 0000000000..c70b4f3819 --- /dev/null +++ b/js/views/create_group_dialog_view.js @@ -0,0 +1,138 @@ +/* global Whisper, i18n, textsecure, _ */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.CreateGroupDialogView = Whisper.View.extend({ + className: 'loki-dialog modal', + initialize() { + this.titleText = i18n('createGroupDialogTitle'); + this.okText = i18n('ok'); + this.cancelText = i18n('cancel'); + this.close = this.close.bind(this); + + const convos = window.getConversations().models; + + let allMembers = convos.filter( + d => !!d && d.isFriend() && d.isPrivate() && !d.isMe() + ); + allMembers = _.uniq(allMembers, true, d => d.id); + + this.membersToShow = allMembers; + + this.$el.focus(); + this.render(); + }, + render() { + this.dialogView = new Whisper.ReactWrapperView({ + className: 'create-group-dialog', + Component: window.Signal.Components.CreateGroupDialog, + props: { + titleText: this.titleText, + okText: this.okText, + cancelText: this.cancelText, + friendList: this.membersToShow, + onClose: this.close, + }, + }); + + this.$el.append(this.dialogView.el); + return this; + }, + close() { + this.remove(); + }, + }); + + Whisper.UpdateGroupDialogView = Whisper.View.extend({ + className: 'loki-dialog modal', + initialize(groupConvo) { + this.groupName = groupConvo.get('name'); + + this.conversation = groupConvo; + this.titleText = `${i18n('updateGroupDialogTitle')}: ${this.groupName}`; + this.okText = i18n('ok'); + this.cancelText = i18n('cancel'); + this.close = this.close.bind(this); + this.onSubmit = this.onSubmit.bind(this); + this.isPublic = groupConvo.isPublic(); + + const ourPK = textsecure.storage.user.getNumber(); + + this.isAdmin = groupConvo.get('groupAdmins').includes(ourPK); + + const convos = window.getConversations().models.filter(d => !!d); + + let existingMembers = groupConvo.get('members') || []; + + // Show a contact if they are our friend or if they are a member + const friendsAndMembers = convos.filter( + d => + (d.isFriend() || existingMembers.includes(d.id)) && + d.isPrivate() && + !d.isMe() + ); + this.friendsAndMembers = _.uniq(friendsAndMembers, true, d => d.id); + + // at least make sure it's an array + if (!Array.isArray(existingMembers)) { + existingMembers = []; + } + + this.existingMembers = existingMembers; + + // public chat settings overrides + if (this.isPublic) { + // fix the title + this.titleText = `${i18n('updatePublicGroupDialogTitle')}: ${ + this.groupName + }`; + // I'd much prefer to integrate mods with groupAdmins + // but lets discuss first... + this.isAdmin = groupConvo.isModerator( + window.storage.get('primaryDevicePubKey') + ); + // zero out friendList for now + this.friendsAndMembers = []; + this.existingMembers = []; + } + + this.$el.focus(); + this.render(); + }, + render() { + this.dialogView = new Whisper.ReactWrapperView({ + className: 'create-group-dialog', + Component: window.Signal.Components.UpdateGroupDialog, + props: { + titleText: this.titleText, + groupName: this.groupName, + okText: this.okText, + isPublic: this.isPublic, + cancelText: this.cancelText, + existingMembers: this.existingMembers, + friendList: this.friendsAndMembers, + isAdmin: this.isAdmin, + onClose: this.close, + onSubmit: this.onSubmit, + }, + }); + + this.$el.append(this.dialogView.el); + return this; + }, + onSubmit(newGroupName, newMembers) { + const ourPK = textsecure.storage.user.getNumber(); + const allMembers = window.Lodash.concat(newMembers, [ourPK]); + const groupId = this.conversation.get('id'); + + window.doUpdateGroup(groupId, newGroupName, allMembers); + }, + close() { + this.remove(); + }, + }); +})(); diff --git a/js/views/device_pairing_dialog_view.js b/js/views/device_pairing_dialog_view.js new file mode 100644 index 0000000000..8cb1ceabeb --- /dev/null +++ b/js/views/device_pairing_dialog_view.js @@ -0,0 +1,36 @@ +/* global Whisper, i18n, */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.DevicePairingDialogView = Whisper.View.extend({ + className: 'loki-dialog device-pairing-dialog modal', + initialize(options) { + this.close = this.close.bind(this); + this.pubKeyToUnpair = options.pubKeyToUnpair; + this.render(); + }, + + render() { + this.dialogView = new Whisper.ReactWrapperView({ + className: 'device-pairing-dialog', + Component: window.Signal.Components.DevicePairingDialog, + props: { + i18n, + onClose: this.close, + pubKeyToUnpair: this.pubKeyToUnpair, + }, + }); + + this.$el.append(this.dialogView.el); + return this; + }, + + close() { + this.remove(); + }, + }); +})(); diff --git a/js/views/device_pairing_words_dialog_view.js b/js/views/device_pairing_words_dialog_view.js new file mode 100644 index 0000000000..afc0ca6b97 --- /dev/null +++ b/js/views/device_pairing_words_dialog_view.js @@ -0,0 +1,35 @@ +/* global Whisper, i18n, textsecure */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.DevicePairingWordsDialogView = Whisper.View.extend({ + className: 'loki-dialog device-pairing-words-dialog modal', + templateName: 'device-pairing-words-dialog', + initialize() { + const pubKey = textsecure.storage.user.getNumber(); + this.secretWords = window.mnemonic + .mn_encode(pubKey.slice(2), 'english') + .split(' ') + .slice(-3) + .join(' '); + this.render(); + }, + events: { + 'click #close': 'close', + }, + render_attributes() { + return { + title: i18n('showPairingWordsTitle'), + closeText: i18n('close'), + secretWords: this.secretWords, + }; + }, + close() { + this.remove(); + }, + }); +})(); diff --git a/js/views/edit_profile_dialog_view.js b/js/views/edit_profile_dialog_view.js new file mode 100644 index 0000000000..b96eabfec5 --- /dev/null +++ b/js/views/edit_profile_dialog_view.js @@ -0,0 +1,53 @@ +/* global i18n, Whisper */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.EditProfileDialogView = Whisper.View.extend({ + className: 'loki-dialog modal', + initialize({ + profileName, + avatarPath, + avatarColor, + pubkey, + onOk, + callback, + }) { + this.close = this.close.bind(this); + + this.callback = callback; + this.profileName = profileName; + this.pubkey = pubkey; + this.avatarPath = avatarPath; + this.avatarColor = avatarColor; + this.onOk = onOk; + + this.$el.focus(); + this.render(); + }, + render() { + this.dialogView = new Whisper.ReactWrapperView({ + className: 'edit-profile-dialog', + Component: window.Signal.Components.EditProfileDialog, + props: { + callback: this.callback, + onOk: this.onOk, + onClose: this.close, + profileName: this.profileName, + pubkey: this.pubkey, + avatarPath: this.avatarPath, + i18n, + }, + }); + + this.$el.append(this.dialogView.el); + return this; + }, + close() { + this.remove(); + }, + }); +})(); diff --git a/js/views/file_input_view.js b/js/views/file_input_view.js index 0251c22831..3d95bc6ccf 100644 --- a/js/views/file_input_view.js +++ b/js/views/file_input_view.js @@ -14,35 +14,6 @@ const { MIME, VisualAttachment } = window.Signal.Types; - Whisper.FileSizeToast = Whisper.ToastView.extend({ - templateName: 'file-size-modal', - render_attributes() { - return { - 'file-size-warning': i18n('fileSizeWarning'), - limit: this.model.limit, - units: this.model.units, - }; - }, - }); - Whisper.UnableToLoadToast = Whisper.ToastView.extend({ - render_attributes() { - return { toastMessage: i18n('unableToLoadAttachment') }; - }, - }); - - Whisper.DangerousFileTypeToast = Whisper.ToastView.extend({ - template: i18n('dangerousFileType'), - }); - Whisper.OneNonImageAtATimeToast = Whisper.ToastView.extend({ - template: i18n('oneNonImageAtATimeToast'), - }); - Whisper.CannotMixImageAndNonImageAttachmentsToast = Whisper.ToastView.extend({ - template: i18n('cannotMixImageAdnNonImageAttachments'), - }); - Whisper.MaxAttachmentsToast = Whisper.ToastView.extend({ - template: i18n('maximumAttachments'), - }); - Whisper.FileInputView = Backbone.View.extend({ tagName: 'span', className: 'file-input', @@ -217,47 +188,56 @@ }, // Show errors - showLoadFailure() { - const toast = new Whisper.UnableToLoadToast(); - toast.$el.insertAfter(this.$el); - toast.render(); + window.pushToast({ + title: i18n('unableToLoadAttachment'), + type: 'error', + id: 'unableToLoadAttachment', + }); }, showDangerousError() { - const toast = new Whisper.DangerousFileTypeToast(); - toast.$el.insertAfter(this.$el); - toast.render(); + window.pushToast({ + title: i18n('dangerousFileType'), + type: 'error', + id: 'dangerousFileType', + }); }, - showFileSizeError({ limit, units, u }) { - const toast = new Whisper.FileSizeToast({ - model: { limit, units: units[u] }, + showFileSizeError() { + window.pushToast({ + title: i18n('fileSizeWarning'), + description: `Max size: ${this.model.limit} ${this.model.units}`, + type: 'error', + id: 'fileSizeWarning', }); - toast.$el.insertAfter(this.$el); - toast.render(); }, showCannotMixError() { - const toast = new Whisper.CannotMixImageAndNonImageAttachmentsToast(); - toast.$el.insertAfter(this.$el); - toast.render(); + window.pushToast({ + title: i18n('cannotMixImageAndNonImageAttachments'), + type: 'error', + id: 'cannotMixImageAndNonImageAttachments', + }); }, showMultipleNonImageError() { - const toast = new Whisper.OneNonImageAtATimeToast(); - toast.$el.insertAfter(this.$el); - toast.render(); + window.pushToast({ + title: i18n('oneNonImageAtATimeToast'), + type: 'error', + id: 'oneNonImageAtATimeToast', + }); }, showMaximumAttachmentsError() { - const toast = new Whisper.MaxAttachmentsToast(); - toast.$el.insertAfter(this.$el); - toast.render(); + window.pushToast({ + title: i18n('maximumAttachments'), + type: 'error', + id: 'maximumAttachments', + }); }, // Housekeeping - addAttachment(attachment) { if (attachment.isVoiceNote && this.attachments.length > 0) { throw new Error('A voice note cannot be sent with other attachments'); @@ -388,7 +368,7 @@ limit /= 1000; u += 1; } while (limit >= 1000 && u < units.length - 1); - this.showFileSizeError({ limit, units, u }); + this.showFileSizeError(); return; } } catch (error) { @@ -523,9 +503,11 @@ return new Promise((resolve, reject) => { const FR = new FileReader(); FR.onload = e => { + const data = e.target.result; resolve({ ...attachment, - data: e.target.result, + data, + size: data.byteLength, }); }; FR.onerror = reject; diff --git a/js/views/group_update_view.js b/js/views/group_update_view.js index f3515647c9..b7e150d5e3 100644 --- a/js/views/group_update_view.js +++ b/js/views/group_update_view.js @@ -6,6 +6,7 @@ window.Whisper = window.Whisper || {}; + // TODO: remove this as unused? Whisper.GroupUpdateView = Backbone.View.extend({ tagName: 'div', className: 'group-update', @@ -18,7 +19,7 @@ const messages = ['Updated the group.']; if (this.model.name) { - messages.push(`Title is now '${this.model.name}'.`); + messages.push(`Group name has been set to '${this.model.name}'.`); } if (this.model.joined) { messages.push(`${this.model.joined.join(', ')} joined the group`); diff --git a/js/views/hint_view.js b/js/views/hint_view.js deleted file mode 100644 index 8d1a54813f..0000000000 --- a/js/views/hint_view.js +++ /dev/null @@ -1,18 +0,0 @@ -/* global Whisper */ - -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - window.Whisper = window.Whisper || {}; - - Whisper.HintView = Whisper.View.extend({ - templateName: 'hint', - initialize(options) { - this.content = options.content; - }, - render_attributes() { - return { content: this.content }; - }, - }); -})(); diff --git a/js/views/inbox_view.js b/js/views/inbox_view.js index 54fd7009f0..d57b6a824f 100644 --- a/js/views/inbox_view.js +++ b/js/views/inbox_view.js @@ -1,12 +1,16 @@ -/* global ConversationController: false */ -/* global extension: false */ -/* global getInboxCollection: false */ -/* global getContactCollection: false */ -/* global i18n: false */ -/* global Whisper: false */ -/* global textsecure: false */ -/* global clipboard: false */ -/* global Signal: false */ +/* + global + $ + ConversationController, + extension, + ConversationController + getConversations, + getInboxCollection, + i18n, + Whisper, + textsecure, + Signal +*/ // eslint-disable-next-line func-names (function() { @@ -18,7 +22,40 @@ className: 'conversation-stack', open(conversation) { const id = `conversation-${conversation.cid}`; - if (id !== this.el.firstChild.id) { + const container = $('#main-view .conversation-stack'); + + // Has been opened since app start, but not focussed + const conversationExists = container.children(`#${id}`).length > 0; + // Is focussed + const conversationOpened = container.children().first().id === id; + + // To limit the size of the DOM for speed + const maxNumConversations = 10; + const numConversations = container + .children() + .not('.conversation.placeholder').length; + const shouldTrimConversations = numConversations > maxNumConversations; + + if (shouldTrimConversations) { + // Removes conversation which has been hidden the longest + container.children()[numConversations - 1].remove(); + } + + if (conversationExists) { + // User opened conversation, move it to top of stack, rather than re-rendering + const conversations = container + .children() + .not('.conversation.placeholder'); + container + .children(`#${id}`) + .first() + .insertBefore(conversations.first()); + conversation.trigger('opened'); + + return; + } + + if (!conversationOpened) { this.$el .first() .find('video, audio') @@ -31,78 +68,42 @@ model: conversation, window: this.model.window, }); + view.view.resetScrollPosition(); + // eslint-disable-next-line prefer-destructuring $el = view.$el; } - $el.prependTo(this.el); + + container.prepend($el); } conversation.trigger('opened'); }, close(conversation) { - const $el = this.$(`#conversation-${conversation.cid}`); + const $el = $(`#conversation-${conversation.cid}`); if ($el && $el.length > 0) { $el.remove(); } }, showToast({ message }) { - const toast = new Whisper.MessageToastView({ - message, + window.pushToast({ + title: message, + type: 'success', }); - toast.$el.appendTo(this.$el); - toast.render(); }, showConfirmationDialog({ title, message, onOk, onCancel }) { - const dialog = new Whisper.ConfirmationDialogView({ + window.confirmationDialog({ title, message, resolve: onOk, reject: onCancel, }); - this.el.append(dialog.el); - }, - }); - - Whisper.FontSizeView = Whisper.View.extend({ - defaultSize: 14, - maxSize: 30, - minSize: 14, - initialize() { - this.currentSize = this.defaultSize; - this.render(); - }, - events: { keydown: 'zoomText' }, - zoomText(e) { - if (!e.ctrlKey) { - return; - } - const keyCode = e.which || e.keyCode; - const maxSize = 22; // if bigger text goes outside send-message textarea - const minSize = 14; - if (keyCode === 189 || keyCode === 109) { - if (this.currentSize > minSize) { - this.currentSize -= 1; - } - } else if (keyCode === 187 || keyCode === 107) { - if (this.currentSize < maxSize) { - this.currentSize += 1; - } - } - this.render(); - }, - render() { - this.$el.css('font-size', `${this.currentSize}px`); }, }); Whisper.AppLoadingScreen = Whisper.View.extend({ templateName: 'app-loading-screen', className: 'app-loading-screen', - updateProgress(count) { - if (count > 0) { - const message = i18n('loadingMessages', count.toString()); - this.$('.message').text(message); - } - }, + updateProgress() {}, render_attributes: { message: i18n('loading'), }, @@ -116,21 +117,16 @@ this.render(); this.$el.attr('tabindex', '1'); - // eslint-disable-next-line no-new - new Whisper.FontSizeView({ el: this.$el }); - - this.mainHeaderView = new Whisper.MainHeaderView({ - el: this.$('.main-header-placeholder'), - items: this.getMainHeaderItems(), - }); - this.onPasswordUpdated(); - this.on('password-updated', () => this.onPasswordUpdated()); - this.conversation_stack = new Whisper.ConversationStack({ el: this.$('.conversation-stack'), model: { window: options.window }, }); + if (!window.storage.get('betaReleaseDisclaimerAccepted')) { + // Beta disclaimer disabled. + // this.showBetaReleaseDisclaimer(); + } + if (!options.initialLoadComplete) { this.appLoadingScreen = new Whisper.AppLoadingScreen(); this.appLoadingScreen.render(); @@ -141,111 +137,132 @@ // Inbox const inboxCollection = getInboxCollection(); + // ConversationCollection + const conversations = getConversations(); + this.listenTo(conversations, 'remove', conversation => { + if (this.conversation_stack) { + this.conversation_stack.close(conversation); + } + }); + this.listenTo(inboxCollection, 'messageError', () => { if (this.networkStatusView) { this.networkStatusView.render(); } }); - this.listenTo(inboxCollection, 'select', this.openConversation); - - this.inboxListView = new Whisper.ConversationListView({ - el: this.$('.inbox'), - collection: inboxCollection, - }).render(); - this.inboxListView.listenTo( - inboxCollection, - 'add change:timestamp change:name change:number change:profileName', - this.inboxListView.updateLocation - ); - - this.inboxListView.listenTo( - inboxCollection, - 'add change:unreadCount', - () => this.updateInboxSectionUnread() - ); - - // Listen to any conversation remove - this.listenTo(inboxCollection, 'remove', this.closeConversation); - - // Friends - const contactCollection = getContactCollection(); - - this.listenTo(contactCollection, 'select', this.openConversation); + this.networkStatusView = new Whisper.NetworkStatusView(); + this.$el + .find('.network-status-container') + .append(this.networkStatusView.render().el); - this.contactListView = new Whisper.ConversationContactListView({ - el: this.$('.friends'), - collection: contactCollection, - }).render(); + extension.expired(expired => { + if (expired) { + const banner = new Whisper.ExpiredAlertBanner().render(); + banner.$el.prependTo(this.$el); + this.$el.addClass('expired'); + } + }); - this.contactListView.listenTo( - contactCollection, - 'add change:timestamp change:name change:number change:profileName', - this.contactListView.updateLocation + // FIXME: Fix this for new react views + this.updateInboxSectionUnread(); + this.setupLeftPane(); + }, + render_attributes: { + welcomeToSession: i18n('welcomeToSession'), + selectAContact: i18n('selectAContact'), + }, + events: { + click: 'onClick', + 'click .section-toggle': 'toggleSection', + }, + setupLeftPane() { + // Here we set up a full redux store with initial state for our LeftPane Root + const convoCollection = getConversations(); + const conversations = convoCollection.map( + conversation => conversation.cachedProps ); - this.listenTo( - contactCollection, - 'remove', - this.contactListView.removeItem - ); + const initialState = { + conversations: { + conversationLookup: Signal.Util.makeLookup(conversations, 'id'), + }, + user: { + regionCode: window.storage.get('regionCode'), + ourNumber: + window.storage.get('primaryDevicePubKey') || + textsecure.storage.user.getNumber(), + isSecondaryDevice: !!window.storage.get('isSecondaryDevice'), + i18n: window.i18n, + }, + }; - // Search - this.searchView = new Whisper.ConversationSearchView({ - el: this.$('.search-results'), - input: this.$('input.search'), + this.store = Signal.State.createStore(initialState); + window.inboxStore = this.store; + this.leftPaneView = new Whisper.ReactWrapperView({ + JSX: Signal.State.Roots.createLeftPane(this.store), + className: 'left-pane-wrapper', }); - this.searchView.listenTo( - ConversationController.getCollection(), - 'remove', - this.searchView.filterContacts + // Enables our redux store to be updated by backbone events in the outside world + const { + conversationAdded, + conversationChanged, + conversationRemoved, + removeAllConversations, + messageExpired, + openConversationExternal, + } = Signal.State.bindActionCreators( + Signal.State.Ducks.conversations.actions, + this.store.dispatch + ); + const { userChanged } = Signal.State.bindActionCreators( + Signal.State.Ducks.user.actions, + this.store.dispatch ); - this.searchView.$el.hide(); - - this.listenTo(this.searchView, 'hide', function toggleVisibility() { - this.searchView.$el.hide(); - this.$('.conversations-list').show(); + this.openConversationAction = openConversationExternal; + + // In the future this listener will be added by the conversation view itself. But + // because we currently have multiple converations open at once, we install just + // one global handler. + // $(document).on('keydown', event => { + // const { ctrlKey, key } = event; + + // We can add Command-E as the Mac shortcut when we add it to our Electron menus: + // https://stackoverflow.com/questions/27380018/when-cmd-key-is-kept-pressed-keyup-is-not-triggered-for-any-other-key + // For now, it will stay as CTRL-E only + // if (key === 'e' && ctrlKey) { + // const state = this.store.getState(); + // const selectedId = state.conversations.selectedConversation; + // const conversation = ConversationController.get(selectedId); + + // if (conversation && !conversation.get('isArchived')) { + // conversation.setArchived(true); + // conversation.trigger('unload'); + // } + // } + // }); + + this.listenTo(convoCollection, 'remove', conversation => { + const { id } = conversation || {}; + conversationRemoved(id); }); - this.listenTo(this.searchView, 'show', function toggleVisibility() { - this.searchView.$el.show(); - this.$('.conversations-list').hide(); + this.listenTo(convoCollection, 'add', conversation => { + const { id, cachedProps } = conversation || {}; + conversationAdded(id, cachedProps); }); - this.listenTo(this.searchView, 'open', this.openConversation); - - this.networkStatusView = new Whisper.NetworkStatusView(); - this.$el - .find('.network-status-container') - .append(this.networkStatusView.render().el); - - extension.windows.onClosed(() => { - this.inboxListView.stopListening(); - this.contactListView.stopListening(); + this.listenTo(convoCollection, 'change', conversation => { + const { id, cachedProps } = conversation || {}; + conversationChanged(id, cachedProps); }); + this.listenTo(convoCollection, 'reset', removeAllConversations); - if (extension.expired()) { - const banner = new Whisper.ExpiredAlertBanner().render(); - banner.$el.prependTo(this.$el); - this.$el.addClass('expired'); - } + Whisper.events.on('messageExpired', messageExpired); + Whisper.events.on('userChanged', userChanged); - this.updateInboxSectionUnread(); - }, - render_attributes() { - return { - welcomeToSignal: i18n('welcomeToSignal'), - selectAContact: i18n('selectAContact'), - searchForPeopleOrGroups: i18n('searchForPeopleOrGroups'), - settings: i18n('settings'), - }; - }, - events: { - click: 'onClick', - 'click #header': 'focusHeader', - 'click .conversation': 'focusConversation', - 'click .section-toggle': 'toggleSection', - 'input input.search': 'filterContacts', + // Finally, add it to the DOM + this.$('.left-pane-placeholder').append(this.leftPaneView.el); }, startConnectionListener() { this.interval = setInterval(() => { @@ -267,11 +284,8 @@ this.onEmpty(); break; default: - window.log.error( - 'Whisper.InboxView::startConnectionListener:', - 'Unknown web socket status:', - status - ); + // We also replicate empty here + this.onEmpty(); break; } }, 1000); @@ -305,21 +319,6 @@ reloadBackgroundPage() { window.location.reload(); }, - filterContacts(e) { - this.searchView.filterContacts(e); - const input = this.$('input.search'); - if (input.val().length > 0) { - input.addClass('active'); - const textDir = window.getComputedStyle(input[0]).direction; - if (textDir === 'ltr') { - input.removeClass('rtl').addClass('ltr'); - } else if (textDir === 'rtl') { - input.removeClass('ltr').addClass('rtl'); - } - } else { - input.removeClass('active'); - } - }, toggleSection(e) { // Expand or collapse this panel const $target = this.$(e.currentTarget); @@ -329,16 +328,24 @@ $next.slideToggle('fast'); $target.toggleClass('section-toggle-visible'); }, - openConversation(conversation) { - this.searchView.hideHints(); + async openConversation(id, messageId) { + // If we call this to create a new conversation, it can only be private + // (group conversations are created elsewhere) + const conversation = await ConversationController.getOrCreateAndWait( + id, + 'private' + ); + + if (this.openConversationAction) { + this.openConversationAction(id, messageId); + } + if (conversation) { - conversation.updateProfile(); - ConversationController.markAsSelected(conversation); - this.conversation_stack.open( - ConversationController.get(conversation.id) - ); - this.focusConversation(); + conversation.updateProfileName(); } + + this.conversation_stack.open(conversation); + this.focusConversation(); }, closeConversation(conversation) { if (conversation) { @@ -353,76 +360,26 @@ this.$('.conversation:first .recorder').trigger('close'); }, updateInboxSectionUnread() { - const $section = this.$('.section-conversations-unread-counter'); - const models = - (this.inboxListView.collection && - this.inboxListView.collection.models) || - []; - const unreadCount = models.reduce( - (count, m) => count + Math.max(0, m.get('unreadCount')), - 0 - ); - $section.text(unreadCount); - if (unreadCount > 0) { - $section.show(); - } else { - $section.hide(); - } + // FIXME: Fix this for new react views + // const $section = this.$('.section-conversations-unread-counter'); + // const models = + // (this.inboxListView.collection && + // this.inboxListView.collection.models) || + // []; + // const unreadCount = models.reduce( + // (count, m) => count + Math.max(0, m.get('unreadCount')), + // 0 + // ); + // $section.text(unreadCount); + // if (unreadCount > 0) { + // $section.show(); + // } else { + // $section.hide(); + // } }, onClick(e) { this.closeRecording(e); }, - getMainHeaderItems() { - return [ - this._mainHeaderItem('copyPublicKey', () => { - const ourNumber = textsecure.storage.user.getNumber(); - clipboard.writeText(ourNumber); - - this.showToastMessageInGutter(i18n('copiedPublicKey')); - }), - this._mainHeaderItem('editDisplayName', () => { - window.Whisper.events.trigger('onEditProfile'); - }), - this._mainHeaderItem('showSeed', () => { - window.Whisper.events.trigger('showSeedDialog'); - }), - ]; - }, - async onPasswordUpdated() { - const hasPassword = await Signal.Data.getPasswordHash(); - const items = this.getMainHeaderItems(); - - const showPasswordDialog = (type, resolve) => - Whisper.events.trigger('showPasswordDialog', { - type, - resolve, - }); - - const passwordItem = (textKey, type) => - this._mainHeaderItem(textKey, () => - showPasswordDialog(type, () => { - this.showToastMessageInGutter(i18n(`${textKey}Success`)); - }) - ); - - if (hasPassword) { - items.push( - passwordItem('changePassword', 'change'), - passwordItem('removePassword', 'remove') - ); - } else { - items.push(passwordItem('setPassword', 'set')); - } - - this.mainHeaderView.updateItems(items); - }, - _mainHeaderItem(textKey, onClick) { - return { - id: textKey, - text: i18n(textKey), - onClick, - }; - }, showToastMessageInGutter(message) { const toast = new Whisper.MessageToastView({ message, @@ -430,11 +387,15 @@ toast.$el.appendTo(this.$('.gutter')); toast.render(); }, + showBetaReleaseDisclaimer() { + const dialog = new Whisper.BetaReleaseDisclaimer(); + this.el.append(dialog.el); + }, }); Whisper.ExpiredAlertBanner = Whisper.View.extend({ templateName: 'expired_alert', - className: 'expiredAlert clearfix', + className: 'expiredAlert', render_attributes() { return { expiredWarning: i18n('expiredWarning'), diff --git a/js/views/invite_friends_dialog_view.js b/js/views/invite_friends_dialog_view.js new file mode 100644 index 0000000000..06d59e9df6 --- /dev/null +++ b/js/views/invite_friends_dialog_view.js @@ -0,0 +1,58 @@ +/* global Whisper */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.InviteFriendsDialogView = Whisper.View.extend({ + className: 'loki-dialog modal', + initialize(convo) { + this.close = this.close.bind(this); + this.submit = this.submit.bind(this); + + const convos = window.getConversations().models; + + const friends = convos.filter( + d => !!d && d.isFriend() && d.isPrivate() && !d.isMe() + ); + + this.friends = friends; + this.chatName = convo.get('name'); + this.chatServer = convo.get('server'); + this.channelId = convo.get('channelId'); + + this.$el.focus(); + this.render(); + }, + render() { + const view = new Whisper.ReactWrapperView({ + className: 'invite-friends-dialog', + Component: window.Signal.Components.InviteFriendsDialog, + props: { + friendList: this.friends, + onSubmit: this.submit, + onClose: this.close, + chatName: this.chatName, + }, + }); + + this.$el.append(view.el); + return this; + }, + close() { + this.remove(); + }, + submit(pubkeys) { + window.sendGroupInvitations( + { + address: this.chatServer, + name: this.chatName, + channelId: this.channelId, + }, + pubkeys + ); + }, + }); +})(); diff --git a/js/views/key_verification_view.js b/js/views/key_verification_view.js index 64a7e87fc6..af4965ccef 100644 --- a/js/views/key_verification_view.js +++ b/js/views/key_verification_view.js @@ -61,16 +61,14 @@ onSafetyNumberChanged() { this.model.getProfiles().then(this.loadKeys.bind(this)); - const dialog = new Whisper.ConfirmationDialogView({ + window.confirmationDialog({ + title: i18n('changedSinceVerifiedTitle'), message: i18n('changedRightAfterVerify', [ this.model.getTitle(), this.model.getTitle(), ]), hideCancel: true, }); - - dialog.$el.insertBefore(this.el); - dialog.focusCancel(); }, toggleVerified() { this.$('button.verify').attr('disabled', true); diff --git a/js/views/main_header_view.js b/js/views/main_header_view.js deleted file mode 100644 index 7a7470e128..0000000000 --- a/js/views/main_header_view.js +++ /dev/null @@ -1,62 +0,0 @@ -/* global Whisper, textsecure, ConversationController, Signal */ - -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - window.Whisper = window.Whisper || {}; - - Whisper.MainHeaderView = Whisper.View.extend({ - templateName: 'main-header-placeholder', - events: { - 'click .main-header-title-wrapper': 'onClick', - 'click .edit-name': 'onEditProfile', - 'click .copy-key': 'onCopyKey', - }, - initialize(options) { - this.ourNumber = textsecure.storage.user.getNumber(); - const me = ConversationController.getOrCreate(this.ourNumber, 'private'); - - this.mainHeaderView = new Whisper.ReactWrapperView({ - className: 'main-header-wrapper', - Component: Signal.Components.MainHeader, - props: me.format(), - }); - const update = () => this.mainHeaderView.update(me.format()); - this.listenTo(me, 'change', update); - - this.render(); - this.$('.main-header-title-wrapper').prepend(this.mainHeaderView.el); - - this.$toggle = this.$('.main-header-content-toggle'); - this.$content = this.$('.main-header-content-wrapper'); - this.$content.hide(); - - this.updateItems(options.items); - }, - updateItems(items) { - this.$content.html(''); - (items || []).forEach(item => { - // Add the item - this.$content.append( - `
${item.text}
` - ); - - // Register its callback - if (item.onClick) { - this.$(`#${item.id}`).click(item.onClick); - } - }); - }, - render_attributes() { - return { - items: this.items, - }; - }, - onClick() { - // Toggle section visibility - this.$content.slideToggle('fast'); - this.$toggle.toggleClass('main-header-content-toggle-visible'); - }, - }); -})(); diff --git a/js/views/member_list_view.js b/js/views/member_list_view.js new file mode 100644 index 0000000000..7c78c0a5c3 --- /dev/null +++ b/js/views/member_list_view.js @@ -0,0 +1,106 @@ +/* global _, Whisper, */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.MemberListView = Whisper.View.extend({ + initialize(options) { + this.member_list = []; + this.memberMapping = {}; + this.selected_idx = 0; + this.onClicked = options.onClicked; + this.render(); + }, + render() { + if (this.memberView) { + this.memberView.remove(); + this.memberView = null; + } + + this.memberView = new Whisper.ReactWrapperView({ + className: 'member-list', + Component: window.Signal.Components.MemberList, + props: { + members: this.member_list, + selected: this.selectedMember(), + onMemberClicked: this.handleMemberClicked.bind(this), + }, + }); + + this.$el.append(this.memberView.el); + return this; + }, + handleMemberClicked(member) { + this.onClicked(member); + }, + updateMembers(members) { + if (!_.isEqual(this.member_list, members)) { + // Whenever the list is updated, we reset the selection + this.selected_idx = 0; + this.member_list = members; + this.render(); + } + }, + replaceMentions(message) { + let result = message; + + // Sort keys from long to short, so we don't have to + // worry about one key being a substring of another + const keys = _.sortBy(_.keys(this.memberMapping), d => -d.length); + + keys.forEach(key => { + const pubkey = this.memberMapping[key]; + result = result.split(key).join(`@${pubkey}`); + }); + + return result; + }, + pendingMentions() { + return this.memberMapping; + }, + deleteMention(mention) { + if (mention) { + delete this.memberMapping[mention]; + } else { + // Delete all mentions if no argument is passed + this.memberMapping = {}; + } + }, + membersShown() { + return this.member_list.length !== 0; + }, + selectUp() { + this.selected_idx = Math.max(this.selected_idx - 1, 0); + this.render(); + }, + selectDown() { + this.selected_idx = Math.min( + this.selected_idx + 1, + this.member_list.length - 1 + ); + this.render(); + }, + selectedMember() { + return this.member_list[this.selected_idx]; + }, + addPubkeyMapping(name, pubkey) { + let handle = `@${name}`; + let chars = 4; + + while ( + _.has(this.memberMapping, handle) && + this.memberMapping[handle] !== pubkey + ) { + const shortenedPubkey = pubkey.substr(pubkey.length - chars); + handle = `@${name}(..${shortenedPubkey})`; + chars += 1; + } + + this.memberMapping[handle] = pubkey; + return handle; + }, + }); +})(); diff --git a/js/views/message_view.js b/js/views/message_view.js index 0897f12cd3..1f82fcdb3a 100644 --- a/js/views/message_view.js +++ b/js/views/message_view.js @@ -44,41 +44,51 @@ getRenderInfo() { const { Components } = window.Signal; - if (this.model.isExpirationTimerUpdate()) { + if (this.model.propsForTimerNotification) { return { Component: Components.TimerNotification, - props: this.model.getPropsForTimerNotification(), + props: this.model.propsForTimerNotification, }; - } else if (this.model.isKeyChange()) { + } else if (this.model.propsForSafetyNumberNotification) { return { Component: Components.SafetyNumberNotification, - props: this.model.getPropsForSafetyNumberNotification(), + props: this.model.propsForSafetyNumberNotification, }; - } else if (this.model.isVerifiedChange()) { + } else if (this.model.propsForVerificationNotification) { return { Component: Components.VerificationNotification, - props: this.model.getPropsForVerificationNotification(), + props: this.model.propsForVerificationNotification, }; - } else if (this.model.isEndSession()) { + } else if (this.model.propsForResetSessionNotification) { return { Component: Components.ResetSessionNotification, - props: this.model.getPropsForResetSessionNotification(), + props: this.model.propsForResetSessionNotification, }; - } else if (this.model.isGroupUpdate()) { + } else if (this.model.propsForGroupNotification) { return { Component: Components.GroupNotification, - props: this.model.getPropsForGroupNotification(), + props: this.model.propsForGroupNotification, }; - } else if (this.model.isFriendRequest()) { + } else if (this.model.isSessionRestoration()) { + return { + Component: Components.ResetSessionNotification, + props: this.model.getPropsForResetSessionNotification(), + }; + } else if (this.model.propsForFriendRequest) { return { Component: Components.FriendRequest, - props: this.model.getPropsForFriendRequest(), + props: this.model.propsForFriendRequest, + }; + } else if (this.model.propsForGroupInvitation) { + return { + Component: Components.GroupInvitation, + props: this.model.propsForGroupInvitation, }; } return { Component: Components.Message, - props: this.model.getPropsForMessage(), + props: this.model.propsForMessage, }; }, render() { diff --git a/js/views/moderators_add_dialog_view.js b/js/views/moderators_add_dialog_view.js new file mode 100644 index 0000000000..df304ca0a1 --- /dev/null +++ b/js/views/moderators_add_dialog_view.js @@ -0,0 +1,66 @@ +/* global Whisper, log */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.AddModeratorsDialogView = Whisper.View.extend({ + className: 'loki-dialog modal', + async initialize(convo) { + this.close = this.close.bind(this); + this.onSubmit = this.onSubmit.bind(this); + + this.chatName = convo.get('name'); + this.chatServer = convo.get('server'); + this.channelId = convo.get('channelId'); + + // get current list of moderators + this.channelAPI = await convo.getPublicSendData(); + const modPubKeys = await this.channelAPI.getModerators(); + const convos = window.getConversations().models; + + // private friends (not you) that aren't already moderators + const friends = convos.filter( + d => + !!d && + d.isFriend() && + d.isPrivate() && + !d.isMe() && + !modPubKeys.includes(d.id) + ); + + this.friends = friends; + + this.$el.focus(); + this.render(); + }, + render() { + const view = new Whisper.ReactWrapperView({ + className: 'add-moderators-dialog', + Component: window.Signal.Components.AddModeratorsDialog, + props: { + friendList: this.friends, + chatName: this.chatName, + onSubmit: this.onSubmit, + onClose: this.close, + }, + }); + + this.$el.append(view.el); + return this; + }, + close() { + this.remove(); + }, + async onSubmit(pubKeys) { + log.info(`asked to add ${pubKeys}`); + const res = await this.channelAPI.serverAPI.addModerators(pubKeys); + if (res !== true) { + // we have errors, deal with them... + // how? + } + }, + }); +})(); diff --git a/js/views/moderators_remove_dialog_view.js b/js/views/moderators_remove_dialog_view.js new file mode 100644 index 0000000000..166d9f9dde --- /dev/null +++ b/js/views/moderators_remove_dialog_view.js @@ -0,0 +1,64 @@ +/* global Whisper */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.RemoveModeratorsDialogView = Whisper.View.extend({ + className: 'loki-dialog modal', + async initialize(convo) { + this.close = this.close.bind(this); + this.onSubmit = this.onSubmit.bind(this); + + this.chatName = convo.get('name'); + this.chatServer = convo.get('server'); + this.channelId = convo.get('channelId'); + + // get current list of moderators + this.channelAPI = await convo.getPublicSendData(); + const modPubKeys = await this.channelAPI.getModerators(); + const convos = window.getConversations().models; + const moderators = modPubKeys + .map( + pubKey => + convos.find(c => c.id === pubKey) || { + id: pubKey, // memberList need a key + authorPhoneNumber: pubKey, + } + ) + .filter(c => !!c); + + this.mods = moderators; + + this.$el.focus(); + this.render(); + }, + render() { + const view = new Whisper.ReactWrapperView({ + className: 'remove-moderators-dialog', + Component: window.Signal.Components.RemoveModeratorsDialog, + props: { + modList: this.mods, + onSubmit: this.onSubmit, + onClose: this.close, + chatName: this.chatName, + }, + }); + + this.$el.append(view.el); + return this; + }, + close() { + this.remove(); + }, + async onSubmit(pubKeys) { + const res = await this.channelAPI.serverAPI.removeModerators(pubKeys); + if (res !== true) { + // we have errors, deal with them... + // how? + } + }, + }); +})(); diff --git a/js/views/network_status_view.js b/js/views/network_status_view.js index 54fa257007..de5034f191 100644 --- a/js/views/network_status_view.js +++ b/js/views/network_status_view.js @@ -6,6 +6,8 @@ window.Whisper = window.Whisper || {}; + const DISCONNECTED_DELAY = 30000; + Whisper.NetworkStatusView = Whisper.View.extend({ className: 'network-status', templateName: 'networkStatus', @@ -27,6 +29,7 @@ this.model = new Backbone.Model(); this.listenTo(this.model, 'change', this.onChange); + this.connectedTimer = null; }, onReconnectTimer() { this.setSocketReconnectInterval(60000); @@ -43,7 +46,7 @@ getSocketStatus() { return window.getSocketStatus(); }, - getNetworkStatus() { + getNetworkStatus(shortCircuit = false) { let message = ''; let instructions = ''; let hasInterruption = false; @@ -55,21 +58,37 @@ case WebSocket.CONNECTING: message = i18n('connecting'); this.setSocketReconnectInterval(null); + window.clearTimeout(this.connectedTimer); + this.connectedTimer = null; break; case WebSocket.OPEN: this.setSocketReconnectInterval(null); + window.clearTimeout(this.connectedTimer); + this.connectedTimer = null; break; case WebSocket.CLOSED: - message = i18n('disconnected'); - instructions = i18n('checkNetworkConnection'); - hasInterruption = true; - break; + // Intentional fallthrough case WebSocket.CLOSING: - default: - message = i18n('disconnected'); - instructions = i18n('checkNetworkConnection'); - hasInterruption = true; + // Intentional fallthrough + default: { + const markOffline = () => { + message = i18n('disconnected'); + instructions = i18n('checkNetworkConnection'); + hasInterruption = true; + }; + if (shortCircuit) { + // Used to skip the timer for testing + markOffline(); + break; + } + if (!this.connectedTimer) { + // Mark offline if disconnected for 30 seconds + this.connectedTimer = window.setTimeout(() => { + markOffline(); + }, DISCONNECTED_DELAY); + } break; + } } if ( diff --git a/js/views/new_group_update_view.js b/js/views/new_group_update_view.js deleted file mode 100644 index c15494e996..0000000000 --- a/js/views/new_group_update_view.js +++ /dev/null @@ -1,99 +0,0 @@ -/* global Whisper, _ */ - -/* eslint-disable more/no-then */ - -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - window.Whisper = window.Whisper || {}; - - Whisper.NewGroupUpdateView = Whisper.View.extend({ - tagName: 'div', - className: 'new-group-update', - templateName: 'new-group-update', - initialize(options) { - this.render(); - this.avatarInput = new Whisper.FileInputView({ - el: this.$('.group-avatar'), - window: options.window, - }); - - this.recipients_view = new Whisper.RecipientsInputView(); - this.listenTo(this.recipients_view.typeahead, 'sync', () => - this.model.contactCollection.models.forEach(model => { - if (this.recipients_view.typeahead.get(model)) { - this.recipients_view.typeahead.remove(model); - } - }) - ); - this.recipients_view.$el.insertBefore(this.$('.container')); - - this.member_list_view = new Whisper.ContactListView({ - collection: this.model.contactCollection, - className: 'members', - }); - this.member_list_view.render(); - this.$('.scrollable').append(this.member_list_view.el); - }, - events: { - 'click .back': 'goBack', - 'click .send': 'send', - 'focusin input.search': 'showResults', - 'focusout input.search': 'hideResults', - }, - hideResults() { - this.$('.results').hide(); - }, - showResults() { - this.$('.results').show(); - }, - goBack() { - this.trigger('back'); - }, - render_attributes() { - return { - name: this.model.getTitle(), - avatar: this.model.getAvatar(), - }; - }, - async send() { - // When we turn this view on again, need to handle avatars in the new way - - // const avatarFile = await this.avatarInput.getThumbnail(); - const now = Date.now(); - const attrs = { - timestamp: now, - active_at: now, - name: this.$('.name').val(), - members: _.union( - this.model.get('members'), - this.recipients_view.recipients.pluck('id') - ), - }; - - // if (avatarFile) { - // attrs.avatar = avatarFile; - // } - - // Because we're no longer using Backbone-integrated saves, we need to manually - // clear the changed fields here so model.changed is accurate. - this.model.changed = {}; - this.model.set(attrs); - const groupUpdate = this.model.changed; - - await window.Signal.Data.updateConversation( - this.model.id, - this.model.attributes, - { Conversation: Whisper.Conversation } - ); - - if (groupUpdate.avatar) { - this.model.trigger('change:avatar'); - } - - this.model.updateGroup(groupUpdate); - this.goBack(); - }, - }); -})(); diff --git a/js/views/nickname_dialog_view.js b/js/views/nickname_dialog_view.js index 2414de0d4c..398ec3c41c 100644 --- a/js/views/nickname_dialog_view.js +++ b/js/views/nickname_dialog_view.js @@ -1,4 +1,4 @@ -/* global Whisper, i18n, _ */ +/* global Whisper, i18n, _, displayNameRegex */ // eslint-disable-next-line func-names (function() { @@ -19,9 +19,6 @@ this.reject = options.reject; this.cancelText = options.cancelText || i18n('cancel'); - this.clear = options.clear; - this.clearText = options.clearText || i18n('clear'); - this.title = options.title; this.render(); @@ -31,22 +28,38 @@ this.$input.focus(); this.validateNickname(); + + const sanitiseNameInput = () => { + const oldVal = this.$input.val(); + this.$input.val(oldVal.replace(displayNameRegex, '')); + }; + + this.$input[0].oninput = () => { + sanitiseNameInput(); + }; + + this.$input[0].onpaste = () => { + // Sanitise data immediately after paste because it's easier + setTimeout(() => { + sanitiseNameInput(); + }); + }; }, events: { keyup: 'onKeyup', 'click .ok': 'ok', 'click .cancel': 'cancel', - 'click .clear': 'clear', change: 'validateNickname', }, validateNickname() { const nickname = this.$input.val(); if (_.isEmpty(nickname)) { - this.$('.clear').hide(); - } else { - this.$('.clear').show(); + this.$('.ok').attr('disabled', 'disabled'); + return false; } + this.$('.ok').removeAttr('disabled'); + return true; }, render_attributes() { return { @@ -54,7 +67,6 @@ showCancel: !this.hideCancel, cancel: this.cancelText, ok: this.okText, - clear: this.clearText, title: this.title, }; }, @@ -72,14 +84,13 @@ this.reject(); } }, - clear() { - this.$input.val('').trigger('change'); - }, onKeyup(event) { - this.validateNickname(); + const valid = this.validateNickname(); switch (event.key) { case 'Enter': - this.ok(); + if (valid) { + this.ok(); + } break; case 'Escape': case 'Esc': diff --git a/js/views/password_dialog_view.js b/js/views/password_dialog_view.js index 57d2afa009..d73f433f2c 100644 --- a/js/views/password_dialog_view.js +++ b/js/views/password_dialog_view.js @@ -1,4 +1,4 @@ -/* global Whisper, i18n, _, Signal, passwordUtil */ +/* global Whisper */ // eslint-disable-next-line func-names (function() { @@ -6,223 +6,39 @@ window.Whisper = window.Whisper || {}; - const PasswordDialogView = Whisper.View.extend({ + Whisper.PasswordDialogView = Whisper.View.extend({ className: 'loki-dialog password-dialog modal', - templateName: 'password-dialog', initialize(options) { - this.type = options.type; - this.resolve = options.resolve; - this.okText = options.okText || i18n('ok'); - - this.reject = options.reject; - this.cancelText = options.cancelText || i18n('cancel'); - - this.title = options.title; + this.close = this.close.bind(this); + this.onOk = this.onOk.bind(this); + this.props = options; this.render(); - this.updateUI(); - }, - events: { - keyup: 'onKeyup', - 'click .ok': 'ok', - 'click .cancel': 'cancel', - }, - render_attributes() { - return { - showCancel: !this.hideCancel, - cancel: this.cancelText, - ok: this.okText, - title: this.title, - }; - }, - async updateUI() { - if (this.disableOkButton()) { - this.$('.ok').prop('disabled', true); - } else { - this.$('.ok').prop('disabled', false); - } - }, - disableOkButton() { - const password = this.$('#password').val(); - return _.isEmpty(password); }, - async validate() { - const password = this.$('#password').val(); - const passwordConfirmation = this.$('#password-confirmation').val(); - const pairValidation = this.validatePasswordPair( - password, - passwordConfirmation - ); - const hashValidation = await this.validatePasswordHash(password); + render() { + this.dialogView = new Whisper.ReactWrapperView({ + className: 'password-dialog-wrapper', + Component: window.Signal.Components.SessionPasswordModal, + props: { + onClose: this.close, + onOk: this.onOk, + ...this.props, + }, + }); - return pairValidation || hashValidation; - }, - async validatePasswordHash(password) { - // Check if the password matches the hash we have stored - const hash = await Signal.Data.getPasswordHash(); - if (hash && !passwordUtil.matchesHash(password, hash)) { - return i18n('invalidPassword'); - } - return null; + this.$el.append(this.dialogView.el); + return this; }, - validatePasswordPair(password, passwordConfirmation) { - if (!_.isEmpty(password)) { - // Check if the password is first valid - const passwordValidation = passwordUtil.validatePassword( - password, - i18n - ); - if (passwordValidation) { - return passwordValidation; - } - // Check if the confirmation password is the same - if ( - !passwordConfirmation || - password.trim() !== passwordConfirmation.trim() - ) { - return i18n('passwordsDoNotMatch'); - } + onOk(action) { + if (this.props.onSuccess) { + this.props.onSuccess(action); } - return null; }, - okPressed() { - const password = this.$('#password').val(); - if (this.type === 'set') { - window.setPassword(password.trim()); - } else if (this.type === 'remove') { - window.setPassword(null, password.trim()); - } - }, - okErrored() { - if (this.type === 'set') { - this.showError(i18n('setPasswordFail')); - } else if (this.type === 'remove') { - this.showError(i18n('removePasswordFail')); - } - }, - async ok() { - const error = await this.validate(); - if (error) { - this.showError(error); - return; - } - // Clear any errors - this.showError(null); - - try { - this.okPressed(); - - this.remove(); - if (this.resolve) { - this.resolve(); - } - } catch (e) { - this.okErrored(); - } - }, - cancel() { + close() { this.remove(); - if (this.reject) { - this.reject(); - } - }, - onKeyup(event) { - this.updateUI(); - switch (event.key) { - case 'Enter': - this.ok(); - break; - case 'Escape': - case 'Esc': - this.cancel(); - break; - default: - return; - } - event.preventDefault(); - }, - focusCancel() { - this.$('.cancel').focus(); - }, - showError(message) { - if (_.isEmpty(message)) { - this.$('.error').text(''); - this.$('.error').hide(); - } else { - this.$('.error').text(`Error: ${message}`); - this.$('.error').show(); - } }, }); - - const ChangePasswordDialogView = PasswordDialogView.extend({ - templateName: 'password-change-dialog', - disableOkButton() { - const oldPassword = this.$('#old-password').val(); - const newPassword = this.$('#new-password').val(); - return _.isEmpty(oldPassword) || _.isEmpty(newPassword); - }, - async validate() { - const oldPassword = this.$('#old-password').val(); - - // Validate the old password - if (!_.isEmpty(oldPassword)) { - const oldPasswordValidation = passwordUtil.validatePassword( - oldPassword, - i18n - ); - if (oldPasswordValidation) { - return oldPasswordValidation; - } - } else { - return i18n('typeInOldPassword'); - } - - const password = this.$('#new-password').val(); - const passwordConfirmation = this.$('#new-password-confirmation').val(); - - const pairValidation = this.validatePasswordPair( - password, - passwordConfirmation - ); - const hashValidation = await this.validatePasswordHash(oldPassword); - - return pairValidation || hashValidation; - }, - okPressed() { - const oldPassword = this.$('#old-password').val(); - const newPassword = this.$('#new-password').val(); - window.setPassword(newPassword.trim(), oldPassword.trim()); - }, - okErrored() { - this.showError(i18n('changePasswordFail')); - }, - }); - - Whisper.getPasswordDialogView = (type, resolve, reject) => { - // This is a differently styled dialog - if (type === 'change') { - return new ChangePasswordDialogView({ - title: i18n('changePassword'), - okTitle: i18n('change'), - resolve, - reject, - }); - } - - // Set and Remove is basically the same UI - const title = - type === 'remove' ? i18n('removePassword') : i18n('setPassword'); - const okTitle = type === 'remove' ? i18n('remove') : i18n('set'); - return new PasswordDialogView({ - title, - okTitle, - type, - resolve, - reject, - }); - }; })(); diff --git a/js/views/password_view.js b/js/views/password_view.js index a2ed42a262..f88ffbafa6 100644 --- a/js/views/password_view.js +++ b/js/views/password_view.js @@ -1,7 +1,4 @@ -/* global i18n: false */ -/* global Whisper: false */ - -/* eslint-disable no-new */ +/* global Whisper */ // eslint-disable-next-line func-names (function() { @@ -9,63 +6,20 @@ window.Whisper = window.Whisper || {}; - const MIN_LOGIN_TRIES = 3; - Whisper.PasswordView = Whisper.View.extend({ - className: 'password full-screen-flow standalone-fullscreen', - templateName: 'password', - events: { - keyup: 'onKeyup', - 'click #unlock-button': 'onLogin', - 'click #reset-button': 'onReset', - }, initialize() { - this.errorCount = 0; this.render(); }, - render_attributes() { - return { - title: i18n('passwordViewTitle'), - buttonText: i18n('unlock'), - resetText: i18n('resetDatabase'), - showReset: this.errorCount >= MIN_LOGIN_TRIES, - }; - }, - onKeyup(event) { - switch (event.key) { - case 'Enter': - this.onLogin(); - break; - default: - return; - } - event.preventDefault(); - }, - async onLogin() { - const passPhrase = this.$('#passPhrase').val(); - const trimmed = passPhrase ? passPhrase.trim() : passPhrase; - this.setError(''); - try { - await window.onLogin(trimmed); - } catch (e) { - // Increment the error counter and show the button if necessary - this.errorCount += 1; - if (this.errorCount >= MIN_LOGIN_TRIES) { - this.render(); - } - this.setError(`Error: ${e}`); - } - }, - setError(string) { - this.$('.error').text(string); - }, - onReset() { - const clearDataView = new window.Whisper.ClearDataView(() => { - window.resetDatabase(); + render() { + this.passwordView = new window.Whisper.ReactWrapperView({ + className: 'password overlay', + Component: window.Signal.Components.SessionPasswordPrompt, + props: {}, }); - clearDataView.render(); - this.$el.append(clearDataView.el); + + this.$el.append(this.passwordView.el); + return this; }, }); })(); diff --git a/js/views/qr_dialog_view.js b/js/views/qr_dialog_view.js new file mode 100644 index 0000000000..6019f87c9b --- /dev/null +++ b/js/views/qr_dialog_view.js @@ -0,0 +1,36 @@ +/* global Whisper */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.QRDialogView = Whisper.View.extend({ + className: 'loki-dialog qr-dialog modal', + initialize(options) { + this.value = options.value || ''; + this.close = this.close.bind(this); + + this.render(); + }, + + render() { + this.dialogView = new Whisper.ReactWrapperView({ + className: 'qr-dialog-wrapper', + Component: window.Signal.Components.SessionQRModal, + props: { + value: this.value, + onClose: this.close, + }, + }); + + this.$el.append(this.dialogView.el); + return this; + }, + + close() { + this.remove(); + }, + }); +})(); diff --git a/js/views/react_wrapper_view.js b/js/views/react_wrapper_view.js index d335d38aa3..a256fff188 100644 --- a/js/views/react_wrapper_view.js +++ b/js/views/react_wrapper_view.js @@ -14,6 +14,7 @@ initialize(options) { const { Component, + JSX, props, onClose, tagName, @@ -28,6 +29,7 @@ this.tagName = tagName; this.className = className; + this.JSX = JSX; this.Component = Component; this.onClose = onClose; this.onInitialRender = onInitialRender; @@ -38,7 +40,9 @@ }, update(props) { const updatedProps = this.augmentProps(props); - const reactElement = React.createElement(this.Component, updatedProps); + const reactElement = this.JSX + ? this.JSX + : React.createElement(this.Component, updatedProps); ReactDOM.render(reactElement, this.el, () => { if (this.hasRendered) { return; @@ -53,16 +57,15 @@ augmentProps(props) { return Object.assign({}, props, { close: () => { - if (this.onClose) { - this.onClose(); - return; - } this.remove(); }, i18n, }); }, remove() { + if (this.onClose) { + this.onClose(); + } ReactDOM.unmountComponentAtNode(this.el); Backbone.View.prototype.remove.call(this); }, diff --git a/js/views/recipients_input_view.js b/js/views/recipients_input_view.js deleted file mode 100644 index af5ea95578..0000000000 --- a/js/views/recipients_input_view.js +++ /dev/null @@ -1,183 +0,0 @@ -/* global Whisper, Backbone, ConversationController */ - -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - window.Whisper = window.Whisper || {}; - - const ContactsTypeahead = Backbone.TypeaheadCollection.extend({ - typeaheadAttributes: [ - 'name', - 'e164_number', - 'national_number', - 'international_number', - ], - model: Whisper.Conversation, - async fetchContacts() { - const models = window.Signal.Data.getAllPrivateConversations({ - ConversationCollection: Whisper.ConversationCollection, - }); - - this.reset(models); - }, - }); - - Whisper.ContactPillView = Whisper.View.extend({ - tagName: 'span', - className: 'recipient', - events: { - 'click .remove': 'removeModel', - }, - templateName: 'contact_pill', - initialize() { - const error = this.model.validate(this.model.attributes); - if (error) { - this.$el.addClass('error'); - } - }, - removeModel() { - this.$el.trigger('remove', { modelId: this.model.id }); - this.remove(); - }, - render_attributes() { - return { name: this.model.getTitle() }; - }, - }); - - Whisper.RecipientListView = Whisper.ListView.extend({ - itemView: Whisper.ContactPillView, - }); - - Whisper.SuggestionView = Whisper.ConversationListItemView.extend({ - className: 'contact-details contact', - templateName: 'contact_name_and_number', - }); - - Whisper.SuggestionListView = Whisper.ConversationListView.extend({ - itemView: Whisper.SuggestionView, - }); - - Whisper.RecipientsInputView = Whisper.View.extend({ - className: 'recipients-input', - templateName: 'recipients-input', - initialize(options) { - if (options) { - this.placeholder = options.placeholder; - } - this.render(); - this.$input = this.$('input.search'); - this.$new_contact = this.$('.new-contact'); - - // Collection of recipients selected for the new message - this.recipients = new Whisper.ConversationCollection([], { - comparator: false, - }); - - // View to display the selected recipients - this.recipients_view = new Whisper.RecipientListView({ - collection: this.recipients, - el: this.$('.recipients'), - }); - - // Collection of contacts to match user input against - this.typeahead = new ContactsTypeahead(); - this.typeahead.fetchContacts(); - - // View to display the matched contacts from typeahead - this.typeahead_view = new Whisper.SuggestionListView({ - collection: new Whisper.ConversationCollection([], { - comparator(m) { - return m.getTitle().toLowerCase(); - }, - }), - }); - this.$('.contacts').append(this.typeahead_view.el); - this.initNewContact(); - this.listenTo(this.typeahead, 'reset', this.filterContacts); - }, - - render_attributes() { - return { placeholder: this.placeholder || 'name or phone number' }; - }, - - events: { - 'input input.search': 'filterContacts', - 'select .new-contact': 'addNewRecipient', - 'select .contacts': 'addRecipient', - 'remove .recipient': 'removeRecipient', - }, - - filterContacts() { - const query = this.$input.val(); - if (query.length) { - if (this.maybeNumber(query)) { - this.new_contact_view.model.set('id', query); - this.new_contact_view.render().$el.show(); - } else { - this.new_contact_view.$el.hide(); - } - this.typeahead_view.collection.reset(this.typeahead.typeahead(query)); - } else { - this.resetTypeahead(); - } - }, - - initNewContact() { - if (this.new_contact_view) { - this.new_contact_view.undelegateEvents(); - this.new_contact_view.$el.hide(); - } - // Creates a view to display a new contact - this.new_contact_view = new Whisper.ConversationListItemView({ - el: this.$new_contact, - model: ConversationController.create({ - type: 'private', - newContact: true, - }), - }).render(); - }, - - addNewRecipient() { - this.recipients.add(this.new_contact_view.model); - this.initNewContact(); - this.resetTypeahead(); - }, - - addRecipient(e, conversation) { - this.recipients.add(this.typeahead.remove(conversation.id)); - this.resetTypeahead(); - }, - - removeRecipient(e, data) { - const model = this.recipients.remove(data.modelId); - if (!model.get('newContact')) { - this.typeahead.add(model); - } - this.filterContacts(); - }, - - reset() { - this.delegateEvents(); - this.typeahead_view.delegateEvents(); - this.recipients_view.delegateEvents(); - this.new_contact_view.delegateEvents(); - this.typeahead.add( - this.recipients.filter(model => !model.get('newContact')) - ); - this.recipients.reset([]); - this.resetTypeahead(); - this.typeahead.fetchContacts(); - }, - - resetTypeahead() { - this.new_contact_view.$el.hide(); - this.$input.val('').focus(); - this.typeahead_view.collection.reset([]); - }, - - maybeNumber(number) { - return number.match(/^\+?[0-9]*$/); - }, - }); -})(); diff --git a/js/views/scroll_down_button_view.js b/js/views/scroll_down_button_view.js index 3f98be35d4..2f8bc60fba 100644 --- a/js/views/scroll_down_button_view.js +++ b/js/views/scroll_down_button_view.js @@ -1,4 +1,4 @@ -/* global Whisper, i18n */ +/* global Whisper */ // eslint-disable-next-line func-names (function() { @@ -7,33 +7,16 @@ window.Whisper = window.Whisper || {}; Whisper.ScrollDownButtonView = Whisper.View.extend({ - className: 'module-scroll-down', - templateName: 'scroll-down-button-view', + initialize() {}, - initialize(options = {}) { - this.count = options.count || 0; - }, - - increment(count = 0) { - this.count += count; - this.render(); - }, - - render_attributes() { - const buttonClass = - this.count > 0 ? 'module-scroll-down__button--new-messages' : ''; - - let moreBelow = i18n('scrollDown'); - if (this.count > 1) { - moreBelow = i18n('messagesBelow'); - } else if (this.count === 1) { - moreBelow = i18n('messageBelow'); - } + render() { + this.scrollButtonView = new Whisper.ReactWrapperView({ + className: 'module-scroll-down', + Component: window.Signal.Components.SessionScrollButton, + }); - return { - buttonClass, - moreBelow, - }; + this.$el.append(this.scrollButtonView.el); + return this; }, }); })(); diff --git a/js/views/seed_dialog_view.js b/js/views/seed_dialog_view.js index 244006bc92..0ddeea06c7 100644 --- a/js/views/seed_dialog_view.js +++ b/js/views/seed_dialog_view.js @@ -1,4 +1,4 @@ -/* global Whisper, i18n, Signal, passwordUtil */ +/* global Whisper */ // eslint-disable-next-line func-names (function() { @@ -8,91 +8,26 @@ Whisper.SeedDialogView = Whisper.View.extend({ className: 'loki-dialog seed-dialog modal', - templateName: 'seed-dialog', - initialize(options = {}) { - this.okText = options.okText || i18n('ok'); - this.cancelText = options.cancelText || i18n('cancel'); - this.confirmText = options.confirmText || i18n('confirm'); - this.copyText = options.copyText || i18n('copySeed'); - this.seed = options.seed || '-'; - + initialize() { + this.close = this.close.bind(this); this.render(); - this.showSeedView(false); - this.initPasswordHash(); - - this.$('#password').bind('keyup', event => this.onKeyup(event)); - }, - events: { - 'click .ok': 'close', - 'click .confirm': 'confirmPassword', - 'click .copy-seed': 'copySeed', - }, - render_attributes() { - return { - passwordViewTitle: i18n('passwordViewTitle'), - seedViewTitle: i18n('seedViewTitle'), - ok: this.okText, - copyText: this.copyText, - confirm: this.confirmText, - cancel: this.cancelText, - }; }, - async initPasswordHash() { - const hash = await Signal.Data.getPasswordHash(); - this.passwordHash = hash; - this.showSeedView(!hash); - }, - showSeedView(show) { - const seedView = this.$('.seedView'); - const passwordView = this.$('.passwordView'); - if (show) { - this.$('.seed').html(this.seed); - seedView.show(); - passwordView.hide(); - } else { - this.$('.seed').html(''); - passwordView.show(); - this.$('#password').focus(); - seedView.hide(); - } - }, - confirmPassword() { - this.$('.error').html(); - const password = this.$('#password').val(); - if ( - this.passwordHash && - !passwordUtil.matchesHash(password, this.passwordHash) - ) { - this.$('.error').html(`Error: ${i18n('invalidPassword')}`); - return; - } - this.showSeedView(true); + render() { + this.dialogView = new Whisper.ReactWrapperView({ + className: 'seed-dialog-wrapper', + Component: window.Signal.Components.SessionSeedModal, + props: { + onClose: this.close, + }, + }); + + this.$el.append(this.dialogView.el); + return this; }, + close() { this.remove(); }, - copySeed() { - window.clipboard.writeText(this.seed); - - const toast = new Whisper.MessageToastView({ - message: i18n('copiedMnemonic'), - }); - toast.$el.appendTo(this.$el); - toast.render(); - }, - onKeyup(event) { - switch (event.key) { - case 'Enter': - this.confirmPassword(); - break; - case 'Escape': - case 'Esc': - this.close(); - break; - default: - break; - } - }, }); })(); diff --git a/js/views/session_confirm_view.js b/js/views/session_confirm_view.js new file mode 100644 index 0000000000..42084513ad --- /dev/null +++ b/js/views/session_confirm_view.js @@ -0,0 +1,57 @@ +/* global Whisper */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.SessionConfirmView = Whisper.View.extend({ + initialize(options) { + this.props = { + title: options.title, + message: options.message, + messageSub: options.messageSub, + onClickOk: this.ok.bind(this), + onClickClose: this.cancel.bind(this), + resolve: options.resolve, + reject: options.reject, + okText: options.okText, + cancelText: options.cancelText, + okTheme: options.okTheme, + closeTheme: options.closeTheme, + hideCancel: options.hideCancel, + }; + }, + + render() { + this.$('.session-confirm-wrapper').remove(); + + this.confirmView = new Whisper.ReactWrapperView({ + className: 'session-confirm-wrapper', + Component: window.Signal.Components.SessionConfirm, + props: this.props, + }); + + this.$el.append(this.confirmView.el); + }, + + ok() { + this.$('.session-confirm-wrapper').remove(); + if (this.props.resolve) { + this.props.resolve(); + } + }, + cancel() { + this.$('.session-confirm-wrapper').remove(); + if (this.props.reject) { + this.props.reject(); + } + }, + onKeyup(event) { + if (event.key === 'Escape' || event.key === 'Esc') { + this.cancel(); + } + }, + }); +})(); diff --git a/js/views/session_dropdown_view.js b/js/views/session_dropdown_view.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/js/views/session_modal_view.js b/js/views/session_modal_view.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/js/views/session_registration_view.js b/js/views/session_registration_view.js new file mode 100644 index 0000000000..b1ec896212 --- /dev/null +++ b/js/views/session_registration_view.js @@ -0,0 +1,128 @@ +/* eslint-disable no-plusplus */ +/* global + Whisper, +*/ + +/* eslint-disable more/no-then */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.SessionRegistrationView = Whisper.View.extend({ + className: 'session-fullscreen', + initialize() { + this.render(); + }, + render() { + this.session_registration_view = new Whisper.ReactWrapperView({ + className: 'session-full-screen-flow session-fullscreen', + Component: window.Signal.Components.SessionRegistrationView, + props: {}, + }); + + this.$el.append(this.session_registration_view.el); + return this; + }, + + log(s) { + window.log.info(s); + this.$('#status').text(s); + }, + displayError(error) { + this.$('#error') + .hide() + .text(error) + .addClass('in') + .fadeIn(); + }, + + showToast(message) { + const toast = new Whisper.MessageToastView({ + message, + }); + toast.$el.appendTo(this.$el); + toast.render(); + }, + }); + + class TextScramble { + constructor(el) { + this.el = el; + this.chars = '0123456789abcdef'; + this.update = this.update.bind(this); + } + + setText(newText) { + const oldText = this.el.value; + const length = Math.max(oldText.length, newText.length); + // eslint-disable-next-line no-return-assign + const promise = new Promise(resolve => (this.resolve = resolve)); + this.queue = []; + + for (let i = 0; i < length; i++) { + const from = oldText[i] || ''; + const to = newText[i] || ''; + const start = Math.floor(Math.random() * 40); + const end = start + Math.floor(Math.random() * 40); + this.queue.push({ + from, + to, + start, + end, + }); + } + + cancelAnimationFrame(this.frameRequest); + this.frame = 0; + this.update(); + return promise; + } + + update() { + let output = ''; + let complete = 0; + + for (let i = 0, n = this.queue.length; i < n; i++) { + const { from, to, start, end } = this.queue[i]; + let { char } = this.queue[i]; + + if (this.frame >= end) { + complete++; + output += to; + } else if (this.frame >= start) { + if (!char || Math.random() < 0.28) { + char = this.randomChar(); + this.queue[i].char = char; + } + output += char; + } else { + output += from; + } + } + + this.el.value = output; + + if (complete === this.queue.length) { + this.resolve(); + } else { + this.frameRequest = requestAnimationFrame(this.update); + this.frame++; + } + } + + randomChar() { + return this.chars[Math.floor(Math.random() * this.chars.length)]; + } + } + window.Session = window.Session || {}; + + window.Session.setNewSessionID = sessionID => { + const el = document.querySelector('.session-id-editable-textarea'); + const fx = new TextScramble(el); + el.value = sessionID; + fx.setText(sessionID); + }; +})(); diff --git a/js/views/session_settings_view.js b/js/views/session_settings_view.js new file mode 100644 index 0000000000..3d6623283a --- /dev/null +++ b/js/views/session_settings_view.js @@ -0,0 +1,28 @@ +/* global i18n, Whisper */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.SessionSettingsView = Whisper.View.extend({ + initialize() { + this.render(); + }, + render() { + this.settingsView = new Whisper.ReactWrapperView({ + className: 'session-settings', + Component: window.Signal.Components.SettingsView, + props: { + i18n, + }, + }); + + this.$el.append(this.settingsView.el); + }, + close() { + this.remove(); + }, + }); +})(); diff --git a/js/views/session_toast_view.js b/js/views/session_toast_view.js new file mode 100644 index 0000000000..bbcb8b67d5 --- /dev/null +++ b/js/views/session_toast_view.js @@ -0,0 +1,75 @@ +/* global Whisper */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.SessionToastView = Whisper.View.extend({ + initialize(options) { + this.props = { + title: options.title, + id: options.id, + description: options.description, + icon: options.icon, + fadeToast: this.fadeToast.bind(this), + closeToast: this.closeToast.bind(this), + }; + }, + + render() { + this.toastView = new Whisper.ReactWrapperView({ + className: 'session-toast-wrapper', + Component: window.Signal.Components.SessionToast, + props: this.props, + }); + + this.$el.append(this.toastView.el); + }, + + update(options) { + this.props.title = options.title; + this.props.id = options.id; + this.props.description = options.description || ''; + this.props.type = options.type || ''; + this.props.icon = options.icon || ''; + this.props.shouldFade = options.shouldFade !== false; + + this.toastView.update(this.props); + + this.showToast(); + + if (this.timer) { + clearTimeout(this.timer); + } + if (this.props.shouldFade) { + this.timer = setTimeout(this.fadeToast.bind(this), 4000); + } + }, + + showToast() { + this.toastView.$el.show(); + }, + + fadeToast() { + this.removeToast(); + this.toastView.$el.fadeOut(500, () => { + this.toastView.remove(); + }); + }, + + closeToast() { + this.removeToast(); + this.toastView.$el.fadeOut(125, () => { + this.toastView.remove(); + }); + }, + + removeToast() { + if (this.props.id) { + window.toasts.delete(this.props.id); + } + }, + }); +})(); diff --git a/js/views/session_toggle_view.js b/js/views/session_toggle_view.js new file mode 100644 index 0000000000..8d0e35a46c --- /dev/null +++ b/js/views/session_toggle_view.js @@ -0,0 +1,31 @@ +/* global Whisper */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.SessionToggleView = Whisper.View.extend({ + initialize(options) { + this.props = { + active: options.active, + }; + }, + + render() { + this.toggleView = new Whisper.ReactWrapperView({ + className: 'session-toggle-wrapper', + Component: window.Signal.Components.SessionToggle, + props: this.props, + }); + + this.$el.append(this.toggleView.el); + }, + + toggle() { + this.props.active = !this.props.active; + this.toggleView.update(this.props); + }, + }); +})(); diff --git a/js/views/settings_view.js b/js/views/settings_view.js index 9cb46c8658..af89d9f954 100644 --- a/js/views/settings_view.js +++ b/js/views/settings_view.js @@ -94,6 +94,25 @@ }, }); + const TypingIndicatorsSettingView = Whisper.View.extend({ + initialize(options) { + this.value = options.value; + this.setFn = options.setFn; + this.populate(); + }, + events: { + change: 'change', + }, + change(e) { + this.value = e.target.checked; + this.setFn(this.value); + window.log.info('typing-indicators-setting changed to', this.value); + }, + populate() { + this.$('input').prop('checked', Boolean(this.value)); + }, + }); + const RadioButtonGroupView = Whisper.View.extend({ initialize(options) { this.name = options.name; @@ -150,12 +169,14 @@ value: window.initialData.spellCheck, setFn: window.setSpellCheck, }); - new CheckboxView({ - el: this.$('.menu-bar-setting'), - name: 'menu-bar-setting', - value: window.initialData.hideMenuBar, - setFn: window.setHideMenuBar, - }); + if (Settings.isHideMenuBarSupported()) { + new CheckboxView({ + el: this.$('.menu-bar-setting'), + name: 'menu-bar-setting', + value: window.initialData.hideMenuBar, + setFn: window.setHideMenuBar, + }); + } new CheckboxView({ el: this.$('.link-preview-setting'), name: 'link-preview-setting', @@ -172,6 +193,11 @@ value: window.initialData.readReceiptSetting, setFn: window.setReadReceiptSetting, }); + new TypingIndicatorsSettingView({ + el: this.$('.typing-indicators-setting'), + value: window.initialData.typingIndicatorsSetting, + setFn: window.setTypingIndicatorsSetting, + }); new MessageTTLSettingView({ el: this.$('.message-ttl-setting'), value: window.initialData.messageTTL, @@ -203,6 +229,7 @@ nameOnly: i18n('nameOnly'), audioNotificationDescription: i18n('audioNotificationDescription'), isAudioNotificationSupported: Settings.isAudioNotificationSupported(), + isHideMenuBarSupported: Settings.isHideMenuBarSupported(), themeLight: i18n('themeLight'), themeDark: i18n('themeDark'), hideMenuBar: i18n('hideMenuBar'), @@ -213,15 +240,17 @@ mediaPermissionsDescription: i18n('mediaPermissionsDescription'), generalHeader: i18n('general'), readReceiptSettingDescription: i18n('readReceiptSettingDescription'), + typingIndicatorsSettingDescription: i18n( + 'typingIndicatorsSettingDescription' + ), messageTTL: i18n('messageTTL'), messageTTLSettingDescription: i18n('messageTTLSettingDescription'), messageTTLSettingWarning: i18n('messageTTLSettingWarning'), spellCheckHeader: i18n('spellCheck'), spellCheckDescription: i18n('spellCheckDescription'), blockedHeader: 'Blocked Users', - linkPreviews: i18n('linkPreviews'), - linkPreviewsDescription: i18n('linkPreviewsDescription'), - linkPreviewsSettingDescription: i18n('linkPreviewsSettingDescription'), + linkPreviews: i18n('linkPreviewsTitle'), + linkPreviewsSettingDescription: i18n('linkPreviewsDescription'), }; }, onClose() { diff --git a/js/views/standalone_registration_view.js b/js/views/standalone_registration_view.js index 54d5706a47..937738dfb0 100644 --- a/js/views/standalone_registration_view.js +++ b/js/views/standalone_registration_view.js @@ -1,4 +1,14 @@ -/* global Whisper, $, getAccountManager, textsecure, i18n, passwordUtil, _ */ +/* global + Whisper, + $, + getAccountManager, + textsecure, + i18n, + passwordUtil, + _, + setTimeout, + displayNameRegex +*/ /* eslint-disable more/no-then */ @@ -8,11 +18,17 @@ window.Whisper = window.Whisper || {}; + const REGISTER_INDEX = 0; + const PROFILE_INDEX = 1; + let currentPageIndex = REGISTER_INDEX; + Whisper.StandaloneRegistrationView = Whisper.View.extend({ templateName: 'standalone', className: 'full-screen-flow standalone-fullscreen', initialize() { this.accountManager = getAccountManager(); + // Clean status in case the app closed unexpectedly + textsecure.storage.remove('secondaryDeviceStatus'); this.render(); @@ -26,15 +42,23 @@ this.$('#error').hide(); this.$('.standalone-mnemonic').hide(); + this.$('.standalone-secondary-device').hide(); this.onGenerateMnemonic(); const options = window.mnemonic.get_languages().map(language => { - const text = language.charAt(0).toUpperCase() + language.slice(1); + const text = language + // Split by whitespace or underscore + .split(/[\s_]+/) + // Capitalise each word + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); return ``; }); this.$('#mnemonic-language').append(options); + this.$('#mnemonic-language').val('english'); this.$('#mnemonic-display-language').append(options); + this.$('#mnemonic-display-language').val('english'); this.$passwordInput = this.$('#password'); this.$passwordConfirmationInput = this.$('#password-confirmation'); @@ -42,17 +66,37 @@ this.registrationParams = {}; this.$pages = this.$('.page'); + this.pairingInterval = null; this.showRegisterPage(); this.onValidatePassword(); + + this.onSecondaryDeviceRegistered = this.onSecondaryDeviceRegistered.bind( + this + ); + + this.$('#display-name').get(0).oninput = () => { + this.sanitiseNameInput(); + }; + + this.$('#display-name').get(0).onpaste = () => { + // Sanitise data immediately after paste because it's easier + setTimeout(() => { + this.sanitiseNameInput(); + }); + }; + this.sanitiseNameInput(); }, events: { + keyup: 'onKeyup', 'validation input.number': 'onValidation', 'click #request-voice': 'requestVoice', 'click #request-sms': 'requestSMSVerification', 'change #code': 'onChangeCode', 'click #register': 'registerWithoutMnemonic', 'click #register-mnemonic': 'registerWithMnemonic', + 'click #register-secondary-device': 'registerSecondaryDevice', + 'click #cancel-secondary-device': 'cancelSecondaryDevice', 'click #back-button': 'onBack', 'click #save-button': 'onSaveProfile', 'change #mnemonic': 'onChangeMnemonic', @@ -63,6 +107,17 @@ 'keyup #password': 'onValidatePassword', 'keyup #password-confirmation': 'onValidatePassword', }, + sanitiseNameInput() { + const oldVal = this.$('#display-name').val(); + const newVal = oldVal.replace(displayNameRegex, ''); + this.$('#display-name').val(newVal); + if (_.isEmpty(newVal)) { + this.$('#save-button').attr('disabled', 'disabled'); + return false; + } + this.$('#save-button').removeAttr('disabled'); + return true; + }, async showPage(pageIndex) { // eslint-disable-next-line func-names this.$pages.each(function(index) { @@ -70,12 +125,13 @@ $(this).hide(); } else { $(this).show(); + currentPageIndex = pageIndex; } }); }, async showRegisterPage() { this.registrationParams = {}; - this.showPage(0); + this.showPage(REGISTER_INDEX); }, async showProfilePage(mnemonic, language) { this.registrationParams = { @@ -85,28 +141,67 @@ this.$passwordInput.val(''); this.$passwordConfirmationInput.val(''); this.onValidatePassword(); - this.showPage(1); + this.showPage(PROFILE_INDEX); + this.$('#display-name').focus(); + }, + onKeyup(event) { + if ( + currentPageIndex !== PROFILE_INDEX && + currentPageIndex !== REGISTER_INDEX + ) { + // Only want enter/escape keys to work on profile page + return; + } + + const validName = this.sanitiseNameInput(); + switch (event.key) { + case 'Enter': + if (event.target.id === 'mnemonic') { + this.registerWithMnemonic(); + } else if (event.target.id === 'primary-pubkey') { + this.registerSecondaryDevice(); + } else if (validName) { + this.onSaveProfile(); + } + break; + case 'Escape': + case 'Esc': + this.onBack(); + break; + default: + } }, async register(mnemonic, language) { // Make sure the password is valid if (this.validatePassword()) { - this.showToast('Invalid password'); + window.pushToast({ + title: i18n('invalidPassword'), + type: 'info', + }); return; } const input = this.trim(this.$passwordInput.val()); + // Ensure we clear the secondary device registration status + textsecure.storage.remove('secondaryDeviceStatus'); + try { + await this.resetRegistration(); + await window.setPassword(input); await this.accountManager.registerSingleDevice( mnemonic, language, - this.$('#display-name').val() + this.trim(this.$('#display-name').val()) ); this.$el.trigger('openInbox'); } catch (e) { if (typeof e === 'string') { - this.showToast(e); + window.pushToast({ + title: e, + type: 'info', + }); } this.log(e); } @@ -116,9 +211,110 @@ const language = this.$('#mnemonic-display-language').val(); this.showProfilePage(mnemonic, language); }, + async onSecondaryDeviceRegistered() { + clearInterval(this.pairingInterval); + // Ensure the left menu is updated + Whisper.events.trigger('userChanged', { isSecondaryDevice: true }); + // will re-run the background initialisation + Whisper.events.trigger('registration_done'); + this.$el.trigger('openInbox'); + }, + async resetRegistration() { + await window.Signal.Data.removeAllIdentityKeys(); + await window.Signal.Data.removeAllPrivateConversations(); + Whisper.Registration.remove(); + // Do not remove all items since they are only set + // at startup. + textsecure.storage.remove('identityKey'); + textsecure.storage.remove('secondaryDeviceStatus'); + window.ConversationController.reset(); + await window.ConversationController.load(); + Whisper.RotateSignedPreKeyListener.stop(Whisper.events); + }, + async cancelSecondaryDevice() { + Whisper.events.off( + 'secondaryDeviceRegistration', + this.onSecondaryDeviceRegistered + ); + this.$('#register-secondary-device') + .removeAttr('disabled') + .text('Link'); + this.$('#cancel-secondary-device').hide(); + this.$('.standalone-secondary-device #pubkey').text(''); + await this.resetRegistration(); + }, + async registerSecondaryDevice() { + if (textsecure.storage.get('secondaryDeviceStatus') === 'ongoing') { + return; + } + await this.resetRegistration(); + textsecure.storage.put('secondaryDeviceStatus', 'ongoing'); + this.$('#register-secondary-device') + .attr('disabled', 'disabled') + .text('Sending...'); + this.$('#cancel-secondary-device').show(); + const mnemonic = this.$('#mnemonic-display').text(); + const language = this.$('#mnemonic-display-language').val(); + const primaryPubKey = this.$('#primary-pubkey').val(); + this.$('.standalone-secondary-device #error').hide(); + // Ensure only one listener + Whisper.events.off( + 'secondaryDeviceRegistration', + this.onSecondaryDeviceRegistered + ); + Whisper.events.once( + 'secondaryDeviceRegistration', + this.onSecondaryDeviceRegistered + ); + const onError = async error => { + this.$('.standalone-secondary-device #error') + .text(error) + .show(); + await this.resetRegistration(); + this.$('#register-secondary-device') + .removeAttr('disabled') + .text('Link'); + this.$('#cancel-secondary-device').hide(); + }; + const c = new Whisper.Conversation({ + id: primaryPubKey, + type: 'private', + }); + const validationError = c.validateNumber(); + if (validationError) { + onError('Invalid public key'); + return; + } + try { + await this.accountManager.registerSingleDevice( + mnemonic, + language, + null + ); + await this.accountManager.requestPairing(primaryPubKey); + const pubkey = textsecure.storage.user.getNumber(); + const words = window.mnemonic.pubkey_to_secret_words(pubkey); + + this.$('.standalone-secondary-device #pubkey').text( + `Here is your secret:\n${words}` + ); + } catch (e) { + onError(e); + } + }, registerWithMnemonic() { const mnemonic = this.$('#mnemonic').val(); const language = this.$('#mnemonic-language').val(); + try { + window.mnemonic.mn_decode(mnemonic, language); + } catch (error) { + this.$('#mnemonic').addClass('error-input'); + this.$('#error').text(error); + this.$('#error').show(); + return; + } + this.$('#error').hide(); + this.$('#mnemonic').removeClass('error-input'); if (!mnemonic) { this.log('Please provide a mnemonic word list'); } else { @@ -147,12 +343,10 @@ }, onCopyMnemonic() { window.clipboard.writeText(this.$('#mnemonic-display').text()); - - const toast = new Whisper.MessageToastView({ - message: i18n('copiedMnemonic'), + window.pushToast({ + title: i18n('copiedMnemonic'), + type: 'info', }); - toast.$el.appendTo(this.$el); - toast.render(); }, log(s) { window.log.info(s); @@ -209,13 +403,22 @@ this.$('#number-container').addClass('invalid'); } }, + toggleSection(e) { + function focusInput() { + const inputs = $(this).find('input'); + if ($(this).is(':visible')) { + if (inputs[0]) { + inputs[0].focus(); + } + } + } // Expand or collapse this panel const $target = this.$(e.currentTarget); const $next = $target.next(); // Toggle section visibility - $next.slideToggle('fast'); + $next.slideToggle('fast', focusInput); $target.toggleClass('section-toggle-visible'); // Hide the other sections @@ -283,12 +486,5 @@ trim(value) { return value ? value.trim() : value; }, - showToast(message) { - const toast = new Whisper.MessageToastView({ - message, - }); - toast.$el.appendTo(this.$el); - toast.render(); - }, }); })(); diff --git a/js/views/timestamp_view.js b/js/views/timestamp_view.js deleted file mode 100644 index c443f5390c..0000000000 --- a/js/views/timestamp_view.js +++ /dev/null @@ -1,129 +0,0 @@ -/* global moment: false */ -/* global Whisper: false */ -/* global extension: false */ -/* global i18n: false */ -/* global _: false */ - -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - window.Whisper = window.Whisper || {}; - - function extendedRelativeTime(number, string) { - return moment.duration(-1 * number, string).humanize(string !== 's'); - } - - const extendedFormats = { - y: 'lll', - M: `${i18n('timestampFormat_M') || 'MMM D'} LT`, - d: 'ddd LT', - }; - - function shortRelativeTime(number, string) { - return moment.duration(number, string).humanize(); - } - const shortFormats = { - y: 'll', - M: i18n('timestampFormat_M') || 'MMM D', - d: 'ddd', - }; - - function getRelativeTimeSpanString(rawTimestamp, options = {}) { - _.defaults(options, { extended: false }); - - const relativeTime = options.extended - ? extendedRelativeTime - : shortRelativeTime; - const formats = options.extended ? extendedFormats : shortFormats; - - // Convert to moment timestamp if it isn't already - const timestamp = moment(rawTimestamp); - const now = moment(); - const timediff = moment.duration(now - timestamp); - - if (timediff.years() > 0) { - return timestamp.format(formats.y); - } else if (timediff.months() > 0 || timediff.days() > 6) { - return timestamp.format(formats.M); - } else if (timediff.days() > 0) { - return timestamp.format(formats.d); - } else if (timediff.hours() >= 1) { - return relativeTime(timediff.hours(), 'h'); - } else if (timediff.minutes() >= 1) { - // Note that humanize seems to jump to '1 hour' as soon as we cross 45 minutes - return relativeTime(timediff.minutes(), 'm'); - } - - return relativeTime(timediff.seconds(), 's'); - } - - Whisper.TimestampView = Whisper.View.extend({ - initialize() { - extension.windows.onClosed(this.clearTimeout.bind(this)); - }, - update() { - this.clearTimeout(); - const millisNow = Date.now(); - let millis = this.$el.data('timestamp'); - if (millis === '') { - return; - } - if (millis >= millisNow) { - millis = millisNow; - } - const result = this.getRelativeTimeSpanString(millis); - this.delay = this.getDelay(millis); - this.$el.text(result); - - const timestamp = moment(millis); - this.$el.attr('title', timestamp.format('llll')); - - if (this.delay) { - if (this.delay < 0) { - this.delay = 1000; - } - this.timeout = setTimeout(this.update.bind(this), this.delay); - } - }, - clearTimeout() { - clearTimeout(this.timeout); - }, - getRelativeTimeSpanString(timestamp) { - return getRelativeTimeSpanString(timestamp); - }, - getDelay(rawTimestamp) { - // Convert to moment timestamp if it isn't already - const timestamp = moment(rawTimestamp); - const now = moment(); - const timediff = moment.duration(now - timestamp); - - if (timediff.years() > 0) { - return null; - } else if (timediff.months() > 0 || timediff.days() > 6) { - return null; - } else if (timediff.days() > 0) { - return moment(timestamp) - .add(timediff.days() + 1, 'd') - .diff(now); - } else if (timediff.hours() >= 1) { - return moment(timestamp) - .add(timediff.hours() + 1, 'h') - .diff(now); - } else if (timediff.minutes() >= 1) { - return moment(timestamp) - .add(timediff.minutes() + 1, 'm') - .diff(now); - } - - return moment(timestamp) - .add(1, 'm') - .diff(now); - }, - }); - Whisper.ExtendedTimestampView = Whisper.TimestampView.extend({ - getRelativeTimeSpanString(timestamp) { - return getRelativeTimeSpanString(timestamp, { extended: true }); - }, - }); -})(); diff --git a/js/views/user_details_dialog_view.js b/js/views/user_details_dialog_view.js new file mode 100644 index 0000000000..98c164691e --- /dev/null +++ b/js/views/user_details_dialog_view.js @@ -0,0 +1,56 @@ +/* global i18n, Whisper */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.UserDetailsDialogView = Whisper.View.extend({ + className: 'loki-dialog modal', + initialize({ + profileName, + avatarPath, + avatarColor, + pubkey, + isRss, + onOk, + onStartConversation, + }) { + this.close = this.close.bind(this); + + this.profileName = profileName; + this.pubkey = pubkey; + this.isRss = isRss; + this.avatarPath = avatarPath; + this.avatarColor = avatarColor; + this.onOk = onOk; + this.onStartConversation = onStartConversation; + + this.$el.focus(); + this.render(); + }, + render() { + this.dialogView = new Whisper.ReactWrapperView({ + className: 'user-details-dialog', + Component: window.Signal.Components.UserDetailsDialog, + props: { + onOk: this.onOk, + onClose: this.close, + onStartConversation: this.onStartConversation, + profileName: this.profileName, + pubkey: this.pubkey, + isRss: this.isRss, + avatarPath: this.avatarPath, + i18n, + }, + }); + + this.$el.append(this.dialogView.el); + return this; + }, + close() { + this.remove(); + }, + }); +})(); diff --git a/js/views/whisper_view.js b/js/views/whisper_view.js index 4ac7c6f7ea..1212774755 100644 --- a/js/views/whisper_view.js +++ b/js/views/whisper_view.js @@ -53,13 +53,12 @@ }, confirm(message, okText) { return new Promise((resolve, reject) => { - const dialog = new Whisper.ConfirmationDialogView({ - message, + window.confirmationDialog({ + title: message, okText, resolve, reject, }); - this.$el.append(dialog.el); }); }, }, diff --git a/libloki/api.js b/libloki/api.js index eab326d434..9b658a07f5 100644 --- a/libloki/api.js +++ b/libloki/api.js @@ -1,33 +1,30 @@ -/* global window, textsecure, log */ +/* global window, textsecure, Whisper, dcodeIO, StringView, ConversationController */ // eslint-disable-next-line func-names (function() { window.libloki = window.libloki || {}; - async function sendFriendRequestAccepted(pubKey) { - return sendEmptyMessage(pubKey, true); + async function sendBackgroundMessage(pubKey) { + return sendOnlineBroadcastMessage(pubKey); } - async function broadcastOnlineStatus() { - const friendKeys = await window.Signal.Data.getPubKeysWithFriendStatus( - window.friends.friendRequestStatusEnum.friends - ); - await Promise.all( - friendKeys.map(async pubKey => { - try { - await sendOnlineBroadcastMessage(pubKey); - } catch (e) { - log.warn(`Failed to send online broadcast message to ${pubKey}`); - } - }) + async function sendOnlineBroadcastMessage(pubKey, isPing = false) { + const authorisation = await window.libloki.storage.getGrantAuthorisationForSecondaryPubKey( + pubKey ); - } + if (authorisation && authorisation.primaryDevicePubKey !== pubKey) { + sendOnlineBroadcastMessage(authorisation.primaryDevicePubKey); + return; + } + const p2pAddress = null; + const p2pPort = null; + // We result loki address message for sending "background" messages + const type = textsecure.protobuf.LokiAddressMessage.Type.HOST_UNREACHABLE; - async function sendOnlineBroadcastMessage(pubKey, isPing = false) { - const myLokiAddress = await window.lokiSnodeAPI.getMyLokiAddress(); const lokiAddressMessage = new textsecure.protobuf.LokiAddressMessage({ - p2pAddress: `http://${myLokiAddress}`, - p2pPort: parseInt(window.localServerPort, 10), + p2pAddress, + p2pPort, + type, }); const content = new textsecure.protobuf.Content({ lokiAddressMessage, @@ -47,52 +44,182 @@ await outgoingMessage.sendToNumber(pubKey); } - async function sendEmptyMessage(pubKey, sendContentMessage = false) { - const options = {}; - // send an empty message. - if (sendContentMessage) { - // The logic downstream will attach the prekeys and our profile. - await textsecure.messaging.sendMessageToNumber( - pubKey, // number - null, // messageText - [], // attachments - null, // quote - [], // preview - Date.now(), // timestamp - null, // expireTimer - null, // profileKey - options + function createPairingAuthorisationProtoMessage({ + primaryDevicePubKey, + secondaryDevicePubKey, + requestSignature, + grantSignature, + }) { + if (!primaryDevicePubKey || !secondaryDevicePubKey || !requestSignature) { + throw new Error( + 'createPairingAuthorisationProtoMessage: pubkeys missing' + ); + } + if (requestSignature.constructor !== ArrayBuffer) { + throw new Error( + 'createPairingAuthorisationProtoMessage expects a signature as ArrayBuffer' + ); + } + if (grantSignature && grantSignature.constructor !== ArrayBuffer) { + throw new Error( + 'createPairingAuthorisationProtoMessage expects a signature as ArrayBuffer' + ); + } + return new textsecure.protobuf.PairingAuthorisationMessage({ + requestSignature: new Uint8Array(requestSignature), + grantSignature: grantSignature ? new Uint8Array(grantSignature) : null, + primaryDevicePubKey, + secondaryDevicePubKey, + }); + } + + function sendUnpairingMessageToSecondary(pubKey) { + const flags = textsecure.protobuf.DataMessage.Flags.UNPAIRING_REQUEST; + const dataMessage = new textsecure.protobuf.DataMessage({ + flags, + }); + const content = new textsecure.protobuf.Content({ + dataMessage, + }); + const options = { messageType: 'device-unpairing' }; + const outgoingMessage = new textsecure.OutgoingMessage( + null, // server + Date.now(), // timestamp, + [pubKey], // numbers + content, // message + true, // silent + () => null, // callback + options + ); + return outgoingMessage.sendToNumber(pubKey); + } + // Serialise as ... + // This is an implementation of the reciprocal of contacts_parser.js + function serialiseByteBuffers(buffers) { + const result = new dcodeIO.ByteBuffer(); + buffers.forEach(buffer => { + // bytebuffer container expands and increments + // offset automatically + result.writeInt32(buffer.limit); + result.append(buffer); + }); + result.limit = result.offset; + result.reset(); + return result; + } + async function createContactSyncProtoMessage(conversations) { + // Extract required contacts information out of conversations + const rawContacts = await Promise.all( + conversations.map(async conversation => { + const profile = conversation.getLokiProfile(); + const number = conversation.getNumber(); + const name = profile + ? profile.displayName + : conversation.getProfileName(); + const status = await conversation.safeGetVerified(); + const protoState = textsecure.storage.protocol.convertVerifiedStatusToProtoState( + status + ); + const verified = new textsecure.protobuf.Verified({ + state: protoState, + destination: number, + identityKey: StringView.hexToArrayBuffer(number), + }); + return { + name, + verified, + number, + nickname: conversation.getNickname(), + blocked: conversation.isBlocked(), + expireTimer: conversation.get('expireTimer'), + }; + }) + ); + // Convert raw contacts to an array of buffers + const contactDetails = rawContacts + .filter(x => x.number !== textsecure.storage.user.getNumber()) + .map(x => new textsecure.protobuf.ContactDetails(x)) + .map(x => x.encode()); + // Serialise array of byteBuffers into 1 byteBuffer + const byteBuffer = serialiseByteBuffers(contactDetails); + const data = new Uint8Array(byteBuffer.toArrayBuffer()); + const contacts = new textsecure.protobuf.SyncMessage.Contacts({ + data, + }); + const syncMessage = new textsecure.protobuf.SyncMessage({ + contacts, + }); + return syncMessage; + } + async function sendPairingAuthorisation(authorisation, recipientPubKey) { + const pairingAuthorisation = createPairingAuthorisationProtoMessage( + authorisation + ); + const ourNumber = textsecure.storage.user.getNumber(); + const ourConversation = await ConversationController.getOrCreateAndWait( + ourNumber, + 'private' + ); + const content = new textsecure.protobuf.Content({ + pairingAuthorisation, + }); + const isGrant = authorisation.primaryDevicePubKey === ourNumber; + if (isGrant) { + // Send profile name to secondary device + const lokiProfile = ourConversation.getLokiProfile(); + // profile.avatar is the path to the local image + // replace with the avatar URL + const avatarPointer = ourConversation.get('avatarPointer'); + lokiProfile.avatar = avatarPointer; + const profile = new textsecure.protobuf.DataMessage.LokiProfile( + lokiProfile + ); + const profileKey = window.storage.get('profileKey'); + const dataMessage = new textsecure.protobuf.DataMessage({ + profile, + profileKey, + }); + // Attach contact list + const conversations = await window.Signal.Data.getConversationsWithFriendStatus( + window.friends.friendRequestStatusEnum.friends, + { ConversationCollection: Whisper.ConversationCollection } ); - } else { - // empty content message - const content = new textsecure.protobuf.Content(); + const syncMessage = await createContactSyncProtoMessage(conversations); + content.syncMessage = syncMessage; + content.dataMessage = dataMessage; + } + // Send + const options = { messageType: 'pairing-request' }; + const p = new Promise((resolve, reject) => { + const timestamp = Date.now(); - // will be called once the transmission succeeded or failed - const callback = res => { - if (res.errors.length > 0) { - res.errors.forEach(error => log.error(error)); - } else { - log.info('empty message sent successfully'); - } - }; - // send an empty message. The logic in ougoing_message will attach the prekeys. const outgoingMessage = new textsecure.OutgoingMessage( null, // server - Date.now(), // timestamp, - [pubKey], // numbers + timestamp, + [recipientPubKey], // numbers content, // message true, // silent - callback, // callback + result => { + // callback + if (result.errors.length > 0) { + reject(result.errors[0]); + } else { + resolve(); + } + }, options ); - await outgoingMessage.sendToNumber(pubKey); - } + outgoingMessage.sendToNumber(recipientPubKey); + }); + return p; } window.libloki.api = { - sendFriendRequestAccepted, - sendEmptyMessage, + sendBackgroundMessage, sendOnlineBroadcastMessage, - broadcastOnlineStatus, + sendPairingAuthorisation, + createPairingAuthorisationProtoMessage, + sendUnpairingMessageToSecondary, + createContactSyncProtoMessage, }; })(); diff --git a/libloki/crypto.js b/libloki/crypto.js index f97a5a3fda..f9888d70e6 100644 --- a/libloki/crypto.js +++ b/libloki/crypto.js @@ -6,6 +6,7 @@ Multibase, TextEncoder, TextDecoder, + crypto, dcodeIO */ @@ -34,8 +35,8 @@ async function DHDecrypt(symmetricKey, ivAndCiphertext) { const iv = ivAndCiphertext.slice(0, IV_LENGTH); - const cipherText = ivAndCiphertext.slice(IV_LENGTH); - return libsignal.crypto.decrypt(symmetricKey, cipherText, iv); + const ciphertext = ivAndCiphertext.slice(IV_LENGTH); + return libsignal.crypto.decrypt(symmetricKey, ciphertext, iv); } class FallBackSessionCipher { @@ -44,6 +45,7 @@ this.pubKey = StringView.hexToArrayBuffer(address.getName()); } + // Should we use ephemeral key pairs here rather than long term keys on each side? async encrypt(plaintext) { const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair(); const myPrivateKey = myKeyPair.privKey; @@ -93,6 +95,7 @@ function decodeSnodeAddressToPubKey(snodeAddress) { const snodeAddressClean = snodeAddress .replace('.snode', '') + .replace('https://', '') .replace('http://', ''); return Multibase.decode(`${base32zCode}${snodeAddressClean}`); } @@ -130,18 +133,18 @@ return this._ephemeralPubKeyHex; } - async decrypt(snodeAddress, ivAndCipherTextBase64) { - const ivAndCipherText = dcodeIO.ByteBuffer.wrap( - ivAndCipherTextBase64, + async decrypt(snodeAddress, ivAndCiphertextBase64) { + const ivAndCiphertext = dcodeIO.ByteBuffer.wrap( + ivAndCiphertextBase64, 'base64' ).toArrayBuffer(); const symmetricKey = await this._getSymmetricKey(snodeAddress); try { - const decrypted = await DHDecrypt(symmetricKey, ivAndCipherText); + const decrypted = await DHDecrypt(symmetricKey, ivAndCiphertext); const decoder = new TextDecoder(); return decoder.decode(decrypted); } catch (e) { - return ivAndCipherText; + return ivAndCiphertext; } } @@ -152,21 +155,177 @@ plainText = textEncoder.encode(plainText); } const symmetricKey = await this._getSymmetricKey(snodeAddress); - const cipherText = await DHEncrypt(symmetricKey, plainText); - return dcodeIO.ByteBuffer.wrap(cipherText).toString('base64'); + const ciphertext = await DHEncrypt(symmetricKey, plainText); + return dcodeIO.ByteBuffer.wrap(ciphertext).toString('base64'); } } + async function generateSignatureForPairing(secondaryPubKey, type) { + const pubKeyArrayBuffer = StringView.hexToArrayBuffer(secondaryPubKey); + // Make sure the signature includes the pairing action (pairing or unpairing) + const len = pubKeyArrayBuffer.byteLength; + const data = new Uint8Array(len + 1); + data.set(new Uint8Array(pubKeyArrayBuffer), 0); + data[len] = type; + + const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair(); + const signature = await libsignal.Curve.async.calculateSignature( + myKeyPair.privKey, + data.buffer + ); + return signature; + } + + async function verifyAuthorisation(authorisation) { + const { + primaryDevicePubKey, + secondaryDevicePubKey, + requestSignature, + grantSignature, + } = authorisation; + const isGrant = !!grantSignature; + if (!primaryDevicePubKey || !secondaryDevicePubKey) { + window.log.warn( + 'Received a pairing request with missing pubkeys. Ignored.' + ); + return false; + } else if (!requestSignature) { + window.log.warn( + 'Received a pairing request with missing request signature. Ignored.' + ); + return false; + } + const verify = async (signature, signatureType) => { + const encoding = typeof signature === 'string' ? 'base64' : undefined; + await this.verifyPairingSignature( + primaryDevicePubKey, + secondaryDevicePubKey, + dcodeIO.ByteBuffer.wrap(signature, encoding).toArrayBuffer(), + signatureType + ); + }; + try { + await verify(requestSignature, PairingType.REQUEST); + } catch (e) { + window.log.warn( + 'Could not verify pairing request authorisation signature. Ignoring message.' + ); + window.log.error(e); + return false; + } + // can't have grant without requestSignature? + if (isGrant) { + try { + await verify(grantSignature, PairingType.GRANT); + } catch (e) { + window.log.warn( + 'Could not verify pairing grant authorisation signature. Ignoring message.' + ); + window.log.error(e); + return false; + } + } + return true; + } + + // FIXME: rename to include the fact it's relative to YOUR device + async function validateAuthorisation(authorisation) { + const { + primaryDevicePubKey, + secondaryDevicePubKey, + grantSignature, + } = authorisation; + const alreadySecondaryDevice = !!window.storage.get('isSecondaryDevice'); + const ourPubKey = textsecure.storage.user.getNumber(); + const isRequest = !grantSignature; + if (isRequest && alreadySecondaryDevice) { + window.log.warn( + 'Received a pairing request while being a secondary device. Ignored.' + ); + return false; + } else if (isRequest && primaryDevicePubKey !== ourPubKey) { + window.log.warn( + 'Received a pairing request addressed to another pubkey. Ignored.' + ); + return false; + } else if (isRequest && secondaryDevicePubKey === ourPubKey) { + window.log.warn('Received a pairing request from ourselves. Ignored.'); + return false; + } + return this.verifyAuthorisation(authorisation); + } + + async function verifyPairingSignature( + primaryDevicePubKey, + secondaryPubKey, + signature, + type + ) { + const secondaryPubKeyArrayBuffer = StringView.hexToArrayBuffer( + secondaryPubKey + ); + const primaryDevicePubKeyArrayBuffer = StringView.hexToArrayBuffer( + primaryDevicePubKey + ); + const len = secondaryPubKeyArrayBuffer.byteLength; + const data = new Uint8Array(len + 1); + // For REQUEST type message, the secondary device signs the primary device pubkey + // For GRANT type message, the primary device signs the secondary device pubkey + let issuer; + if (type === PairingType.GRANT) { + data.set(new Uint8Array(secondaryPubKeyArrayBuffer)); + issuer = primaryDevicePubKeyArrayBuffer; + } else if (type === PairingType.REQUEST) { + data.set(new Uint8Array(primaryDevicePubKeyArrayBuffer)); + issuer = secondaryPubKeyArrayBuffer; + } + data[len] = type; + // Throws for invalid signature + await libsignal.Curve.async.verifySignature(issuer, data.buffer, signature); + } + async function decryptToken({ cipherText64, serverPubKey64 }) { + const ivAndCiphertext = new Uint8Array( + dcodeIO.ByteBuffer.fromBase64(cipherText64).toArrayBuffer() + ); + + const serverPubKey = new Uint8Array( + dcodeIO.ByteBuffer.fromBase64(serverPubKey64).toArrayBuffer() + ); + const { privKey } = await textsecure.storage.protocol.getIdentityKeyPair(); + const symmetricKey = libsignal.Curve.calculateAgreement( + serverPubKey, + privKey + ); + + const token = await DHDecrypt(symmetricKey, ivAndCiphertext); + + const tokenString = dcodeIO.ByteBuffer.wrap(token).toString('utf8'); + return tokenString; + } const snodeCipher = new LokiSnodeChannel(); + const sha512 = data => crypto.subtle.digest('SHA-512', data); + + const PairingType = Object.freeze({ + REQUEST: 1, + GRANT: 2, + }); + window.libloki.crypto = { DHEncrypt, DHDecrypt, FallBackSessionCipher, FallBackDecryptionError, snodeCipher, + decryptToken, + generateSignatureForPairing, + verifyPairingSignature, + verifyAuthorisation, + validateAuthorisation, + PairingType, // for testing _LokiSnodeChannel: LokiSnodeChannel, _decodeSnodeAddressToPubKey: decodeSnodeAddressToPubKey, + sha512, }; })(); diff --git a/libloki/modules/local_loki_server.js b/libloki/modules/local_loki_server.js deleted file mode 100644 index ed13937563..0000000000 --- a/libloki/modules/local_loki_server.js +++ /dev/null @@ -1,112 +0,0 @@ -const http = require('http'); -const EventEmitter = require('events'); - -const STATUS = { - OK: 200, - BAD_REQUEST: 400, - NOT_FOUND: 404, - METHOD_NOT_ALLOWED: 405, - INTERNAL_SERVER_ERROR: 500, -}; - -class LocalLokiServer extends EventEmitter { - /** - * Creates an instance of LocalLokiServer. - * Sends out a `message` event when a new message is received. - */ - constructor() { - super(); - this.server = http.createServer((req, res) => { - let body = []; - - const sendResponse = (statusCode, message = null) => { - const headers = message && { - 'Content-Type': 'text/plain', - }; - res.writeHead(statusCode, headers); - res.end(message); - }; - - if (req.method !== 'POST') { - sendResponse(STATUS.METHOD_NOT_ALLOWED); - return; - } - - // Check endpoints - req - .on('error', () => { - // Internal server error - sendResponse(STATUS.INTERNAL_SERVER_ERROR); - }) - .on('data', chunk => { - body.push(chunk); - }) - .on('end', () => { - try { - body = Buffer.concat(body).toString(); - } catch (e) { - // Internal server error: failed to convert body to string - sendResponse(STATUS.INTERNAL_SERVER_ERROR); - } - - // Check endpoints here - if (req.url === '/v1/storage_rpc') { - try { - const bodyObject = JSON.parse(body); - if (bodyObject.method !== 'store') { - sendResponse(STATUS.NOT_FOUND, 'Invalid endpoint!'); - return; - } - this.emit('message', { - message: bodyObject.params.data, - onSuccess: () => sendResponse(STATUS.OK), - onFailure: () => sendResponse(STATUS.NOT_FOUND), - }); - } catch (e) { - // Bad Request: Failed to decode json - sendResponse(STATUS.BAD_REQUEST, 'Failed to decode JSON'); - } - } else { - sendResponse(STATUS.NOT_FOUND, 'Invalid endpoint!'); - } - }); - }); - } - - async start(port, ip) { - // Close the old server - await this.close(); - - // Start a listening on new server - return new Promise((res, rej) => { - this.server.listen(port, ip, err => { - if (err) { - rej(err); - } else { - res(this.server.address().port); - } - }); - }); - } - - // Async wrapper for http server close - close() { - if (this.server) { - return new Promise(res => { - this.server.close(() => res()); - }); - } - - return Promise.resolve(); - } - - getPort() { - if (this.server.listening) { - return this.server.address().port; - } - - return null; - } -} - -module.exports = LocalLokiServer; diff --git a/libloki/modules/mnemonic.js b/libloki/modules/mnemonic.js index aeb793f8ef..f28a798d44 100644 --- a/libloki/modules/mnemonic.js +++ b/libloki/modules/mnemonic.js @@ -6,6 +6,7 @@ module.exports = { mn_decode, sc_reduce32, get_languages, + pubkey_to_secret_words, }; class MnemonicError extends Error {} @@ -141,36 +142,38 @@ function mn_decode(str, wordset_name) { checksum_word.slice(0, wordset.prefix_len) ) { throw new MnemonicError( - 'Your private key could not be verified, please try again' + 'Your private key could not be verified, please verify the checksum word' ); } } return out; } -var mn_words = { - english: { - prefix_len: 3, - words: require('../../mnemonic_languages/english'), - }, - electrum: { - prefix_len: 0, - words: require('../../mnemonic_languages/electrum'), - }, - spanish: { - prefix_len: 4, - words: require('../../mnemonic_languages/spanish'), - }, - portuguese: { - prefix_len: 4, - words: require('../../mnemonic_languages/portuguese'), - }, - japanese: { - prefix_len: 3, - words: require('../../mnemonic_languages/japanese'), - }, +// Note: the value is the prefix_len +const languages = { + chinese_simplified: 1, + dutch: 4, + electrum: 0, + english: 3, + esperanto: 4, + french: 4, + german: 4, + italian: 4, + japanese: 3, + lojban: 4, + portuguese: 4, + russian: 4, + spanish: 4, }; +let mn_words = {}; +for (let [language, prefix_len] of Object.entries(languages)) { + mn_words[language] = { + prefix_len, + words: require(`../../mnemonic_languages/${language}`), + }; +} + function get_languages() { return Object.keys(mn_words); } @@ -188,3 +191,10 @@ for (var i in mn_words) { } } } + +function pubkey_to_secret_words(pubKey) { + return mn_encode(pubKey.slice(2), 'english') + .split(' ') + .slice(0, 3) + .join(' '); +} diff --git a/libloki/proof-of-work.js b/libloki/proof-of-work.js index 039e2395d7..b537e17af3 100644 --- a/libloki/proof-of-work.js +++ b/libloki/proof-of-work.js @@ -1,8 +1,7 @@ /* global dcodeIO, crypto, JSBI */ const NONCE_LEN = 8; // Modify this value for difficulty scaling -const DEV_NONCE_TRIALS = 10; -const PROD_NONCE_TRIALS = 100; +const FALLBACK_DIFFICULTY = 10; const pow = { // Increment Uint8Array nonce by '_increment' with carrying @@ -47,11 +46,17 @@ const pow = { // Compare two Uint8Arrays, return true if arr1 is > arr2 greaterThan(arr1, arr2) { // Early exit if lengths are not equal. Should never happen - if (arr1.length !== arr2.length) return false; + if (arr1.length !== arr2.length) { + return false; + } for (let i = 0, len = arr1.length; i < len; i += 1) { - if (arr1[i] > arr2[i]) return true; - if (arr1[i] < arr2[i]) return false; + if (arr1[i] > arr2[i]) { + return true; + } + if (arr1[i] < arr2[i]) { + return false; + } } return false; }, @@ -62,8 +67,7 @@ const pow = { ttl, pubKey, data, - development = false, - _nonceTrials = null, + _difficulty = null, increment = 1, startNonce = 0 ) { @@ -74,15 +78,12 @@ const pow = { ).toArrayBuffer() ); - const nonceTrials = - _nonceTrials || (development ? DEV_NONCE_TRIALS : PROD_NONCE_TRIALS); - const target = pow.calcTarget(ttl, payload.length, nonceTrials); + const difficulty = _difficulty || FALLBACK_DIFFICULTY; + const target = pow.calcTarget(ttl, payload.length, difficulty); let nonce = new Uint8Array(NONCE_LEN); nonce = pow.incrementNonce(nonce, startNonce); // initial value - let trialValue = pow.bigIntToUint8Array( - JSBI.BigInt(Number.MAX_SAFE_INTEGER) - ); + let trialValue = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255]); const initialHash = new Uint8Array( await crypto.subtle.digest('SHA-512', payload) ); @@ -103,7 +104,7 @@ const pow = { return pow.bufferToBase64(nonce); }, - calcTarget(ttl, payloadLen, nonceTrials = PROD_NONCE_TRIALS) { + calcTarget(ttl, payloadLen, difficulty = FALLBACK_DIFFICULTY) { // payloadLength + NONCE_LEN const totalLen = JSBI.add(JSBI.BigInt(payloadLen), JSBI.BigInt(NONCE_LEN)); // ttl converted to seconds @@ -119,9 +120,9 @@ const pow = { const innerFrac = JSBI.divide(ttlMult, two16); // totalLen + innerFrac const lenPlusInnerFrac = JSBI.add(totalLen, innerFrac); - // nonceTrials * lenPlusInnerFrac + // difficulty * lenPlusInnerFrac const denominator = JSBI.multiply( - JSBI.BigInt(nonceTrials), + JSBI.BigInt(difficulty), lenPlusInnerFrac ); // 2^64 - 1 diff --git a/libloki/storage.js b/libloki/storage.js index 8d61881bdb..dd3889fba6 100644 --- a/libloki/storage.js +++ b/libloki/storage.js @@ -1,9 +1,13 @@ -/* global window, libsignal, textsecure */ +/* global window, libsignal, textsecure, Signal, + lokiFileServerAPI, ConversationController */ // eslint-disable-next-line func-names (function() { window.libloki = window.libloki || {}; + const timers = {}; + const REFRESH_DELAY = 60 * 1000; + async function getPreKeyBundleForContact(pubKey) { const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair(); const identityKey = myKeyPair.pubKey; @@ -113,11 +117,142 @@ } } + // fetches device mappings from server. + async function getPrimaryDeviceMapping(pubKey) { + if (typeof lokiFileServerAPI === 'undefined') { + // If this is not defined then we are initiating a pairing + return []; + } + const deviceMapping = await lokiFileServerAPI.getUserDeviceMapping(pubKey); + if (!deviceMapping) { + return []; + } + let authorisations = deviceMapping.authorisations || []; + if (deviceMapping.isPrimary === '0') { + const { primaryDevicePubKey } = + authorisations.find( + authorisation => authorisation.secondaryDevicePubKey === pubKey + ) || {}; + if (primaryDevicePubKey) { + // do NOT call getprimaryDeviceMapping recursively + // in case both devices are out of sync and think they are + // each others' secondary pubkey. + const primaryDeviceMapping = await lokiFileServerAPI.getUserDeviceMapping( + primaryDevicePubKey + ); + if (!primaryDeviceMapping) { + return []; + } + ({ authorisations } = primaryDeviceMapping); + } + } + return authorisations || []; + } + + // if the device is a secondary device, + // fetch the device mappings for its primary device + async function saveAllPairingAuthorisationsFor(pubKey) { + // Will be false if there is no timer + const cacheValid = timers[pubKey] > Date.now(); + if (cacheValid) { + return; + } + timers[pubKey] = Date.now() + REFRESH_DELAY; + const authorisations = await getPrimaryDeviceMapping(pubKey); + await Promise.all( + authorisations.map(authorisation => + savePairingAuthorisation(authorisation) + ) + ); + } + + async function savePairingAuthorisation(authorisation) { + // Ensure that we have a conversation for all the devices + const conversation = await ConversationController.getOrCreateAndWait( + authorisation.secondaryDevicePubKey, + 'private' + ); + await conversation.setSecondaryStatus( + true, + authorisation.primaryDevicePubKey + ); + await window.Signal.Data.createOrUpdatePairingAuthorisation(authorisation); + } + + function removePairingAuthorisationForSecondaryPubKey(pubKey) { + return window.Signal.Data.removePairingAuthorisationForSecondaryPubKey( + pubKey + ); + } + + // Transforms signatures from base64 to ArrayBuffer! + async function getGrantAuthorisationForSecondaryPubKey(secondaryPubKey) { + const conversation = ConversationController.get(secondaryPubKey); + if (!conversation || conversation.isPublic() || conversation.isRss()) { + return null; + } + await saveAllPairingAuthorisationsFor(secondaryPubKey); + const authorisation = await window.Signal.Data.getGrantAuthorisationForSecondaryPubKey( + secondaryPubKey + ); + if (!authorisation) { + return null; + } + return { + ...authorisation, + requestSignature: Signal.Crypto.base64ToArrayBuffer( + authorisation.requestSignature + ), + grantSignature: Signal.Crypto.base64ToArrayBuffer( + authorisation.grantSignature + ), + }; + } + + // Transforms signatures from base64 to ArrayBuffer! + async function getAuthorisationForSecondaryPubKey(secondaryPubKey) { + await saveAllPairingAuthorisationsFor(secondaryPubKey); + const authorisation = await window.Signal.Data.getAuthorisationForSecondaryPubKey( + secondaryPubKey + ); + if (!authorisation) { + return null; + } + return { + ...authorisation, + requestSignature: Signal.Crypto.base64ToArrayBuffer( + authorisation.requestSignature + ), + grantSignature: authorisation.grantSignature + ? Signal.Crypto.base64ToArrayBuffer(authorisation.grantSignature) + : null, + }; + } + + function getSecondaryDevicesFor(primaryDevicePubKey) { + return window.Signal.Data.getSecondaryDevicesFor(primaryDevicePubKey); + } + + async function getAllDevicePubKeysForPrimaryPubKey(primaryDevicePubKey) { + await saveAllPairingAuthorisationsFor(primaryDevicePubKey); + const secondaryPubKeys = + (await getSecondaryDevicesFor(primaryDevicePubKey)) || []; + return secondaryPubKeys.concat(primaryDevicePubKey); + } + window.libloki.storage = { getPreKeyBundleForContact, saveContactPreKeyBundle, removeContactPreKeyBundle, verifyFriendRequestAcceptPreKey, + savePairingAuthorisation, + saveAllPairingAuthorisationsFor, + removePairingAuthorisationForSecondaryPubKey, + getGrantAuthorisationForSecondaryPubKey, + getAuthorisationForSecondaryPubKey, + getAllDevicePubKeysForPrimaryPubKey, + getSecondaryDevicesFor, + getPrimaryDeviceMapping, }; // Libloki protocol store diff --git a/libloki/test/metrics.js b/libloki/test/metrics.js index cdd49059d3..6a783823a3 100644 --- a/libloki/test/metrics.js +++ b/libloki/test/metrics.js @@ -3,7 +3,7 @@ let jobId = 0; let currentTrace = 0; let plotlyDiv; const workers = []; -async function run(messageLength, numWorkers = 1, nonceTrials = 100, ttl = 72) { +async function run(messageLength, numWorkers = 1, difficulty = 100, ttl = 72) { const timestamp = Math.floor(Date.now() / 1000); const pubKey = '05ec8635a07a13743516c7c9b3412f3e8252efb7fcaf67eb1615ffba62bebc6802'; @@ -29,7 +29,7 @@ async function run(messageLength, numWorkers = 1, nonceTrials = 100, ttl = 72) { pubKey, data, false, - nonceTrials, + difficulty, increment, index, ]); @@ -50,12 +50,12 @@ async function run(messageLength, numWorkers = 1, nonceTrials = 100, ttl = 72) { async function runPoW({ iteration, - nonceTrials, + difficulty, numWorkers, messageLength = 50, ttl = 72, }) { - const name = `W:${numWorkers} - NT: ${nonceTrials} - L:${messageLength} - TTL:${ttl}`; + const name = `W:${numWorkers} - NT: ${difficulty} - L:${messageLength} - TTL:${ttl}`; Plotly.addTraces(plotlyDiv, { y: [], type: 'box', @@ -64,7 +64,7 @@ async function runPoW({ }); for (let i = 0; i < iteration; i += 1) { // eslint-disable-next-line no-await-in-loop - await run(messageLength, numWorkers, nonceTrials, ttl); + await run(messageLength, numWorkers, difficulty, ttl); } currentTrace += 1; @@ -76,8 +76,9 @@ function randomString(length) { let text = ''; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < length; i += 1) + for (let i = 0; i < length; i += 1) { text += possible.charAt(Math.floor(Math.random() * possible.length)); + } return text; } @@ -86,9 +87,7 @@ function addPoint(duration) { } async function startMessageLengthRun() { const iteration0 = parseFloat(document.getElementById('iteration0').value); - const nonceTrials0 = parseFloat( - document.getElementById('nonceTrials0').value - ); + const difficulty0 = parseFloat(document.getElementById('difficulty0').value); const numWorkers0 = parseFloat(document.getElementById('numWorkers0').value); const messageLengthStart0 = parseFloat( document.getElementById('messageLengthStart0').value @@ -108,7 +107,7 @@ async function startMessageLengthRun() { // eslint-disable-next-line no-await-in-loop await runPoW({ iteration: iteration0, - nonceTrials: nonceTrials0, + difficulty: difficulty0, numWorkers: numWorkers0, messageLength: l, ttl: TTL0, @@ -117,9 +116,7 @@ async function startMessageLengthRun() { } async function startNumWorkerRun() { const iteration1 = parseFloat(document.getElementById('iteration1').value); - const nonceTrials1 = parseFloat( - document.getElementById('nonceTrials1').value - ); + const difficulty1 = parseFloat(document.getElementById('difficulty1').value); const numWorkersStart1 = parseFloat( document.getElementById('numWorkersStart1').value ); @@ -138,34 +135,34 @@ async function startNumWorkerRun() { // eslint-disable-next-line no-await-in-loop await runPoW({ iteration: iteration1, - nonceTrials: nonceTrials1, + difficulty: difficulty1, numWorkers, messageLength: messageLength1, ttl: TTL1, }); } } -async function startNonceTrialsRun() { +async function startDifficultyRun() { const iteration2 = parseFloat(document.getElementById('iteration2').value); const messageLength2 = parseFloat( document.getElementById('messageLength2').value ); const numWorkers2 = parseFloat(document.getElementById('numWorkers2').value); - const nonceTrialsStart2 = parseFloat( - document.getElementById('nonceTrialsStart2').value + const difficultyStart2 = parseFloat( + document.getElementById('difficultyStart2').value ); - const nonceTrialsStop2 = parseFloat( - document.getElementById('nonceTrialsStop2').value + const difficultyStop2 = parseFloat( + document.getElementById('difficultyStop2').value ); - const nonceTrialsStep2 = parseFloat( - document.getElementById('nonceTrialsStep2').value + const difficultyStep2 = parseFloat( + document.getElementById('difficultyStep2').value ); const TTL2 = parseFloat(document.getElementById('TTL2').value); - for (let n = nonceTrialsStart2; n < nonceTrialsStop2; n += nonceTrialsStep2) { + for (let n = difficultyStart2; n < difficultyStop2; n += difficultyStep2) { // eslint-disable-next-line no-await-in-loop await runPoW({ iteration: iteration2, - nonceTrials: n, + difficulty: n, numWorkers: numWorkers2, messageLength: messageLength2, ttl: TTL2, @@ -174,9 +171,7 @@ async function startNonceTrialsRun() { } async function starTTLRun() { const iteration3 = parseFloat(document.getElementById('iteration3').value); - const nonceTrials3 = parseFloat( - document.getElementById('nonceTrials3').value - ); + const difficulty3 = parseFloat(document.getElementById('difficulty3').value); const messageLength3 = parseFloat( document.getElementById('messageLength3').value ); @@ -188,7 +183,7 @@ async function starTTLRun() { // eslint-disable-next-line no-await-in-loop await runPoW({ iteration: iteration3, - nonceTrials: nonceTrials3, + difficulty: difficulty3, numWorkers: numWorkers3, messageLength: messageLength3, ttl, @@ -216,7 +211,7 @@ async function start(index) { await startNumWorkerRun(); break; case 2: - await startNonceTrialsRun(); + await startDifficultyRun(); break; case 3: await starTTLRun(); diff --git a/libloki/test/node/local_loki_server_test.js b/libloki/test/node/local_loki_server_test.js deleted file mode 100644 index ed34b7b5fb..0000000000 --- a/libloki/test/node/local_loki_server_test.js +++ /dev/null @@ -1,85 +0,0 @@ -const axios = require('axios'); -const { assert } = require('chai'); -const LocalLokiServer = require('../../modules/local_loki_server'); - -describe('LocalLokiServer', () => { - before(async () => { - this.server = new LocalLokiServer(); - await this.server.start(8000); - }); - - after(() => { - this.server.close(); - }); - - it('should return 405 if not a POST request', async () => { - try { - await axios.get('http://localhost:8000'); - assert.fail('Got a successful response'); - } catch (error) { - if (error.response) { - assert.equal(405, error.response.status); - return; - } - assert.isNotOk(error, 'Another error was receieved'); - } - }); - - it('should return 404 if no endpoint provided', async () => { - try { - await axios.post('http://localhost:8000', { name: 'Test' }); - assert.fail('Got a successful response'); - } catch (error) { - if (error.response) { - assert.equal(404, error.response.status); - return; - } - assert.isNotOk(error, 'Another error was receieved'); - } - }); - - it('should return 404 and a string if invalid enpoint is provided', async () => { - try { - await axios.post('http://localhost:8000/invalid', { name: 'Test' }); - assert.fail('Got a successful response'); - } catch (error) { - if (error.response) { - assert.equal(404, error.response.status); - assert.equal('Invalid endpoint!', error.response.data); - return; - } - assert.isNotOk(error, 'Another error was receieved'); - } - }); - - describe('/store', async () => { - it('should pass the POSTed data to the callback', async () => { - const server = new LocalLokiServer(); - await server.start(8001); - const messageData = { - method: 'store', - params: { - data: 'This is data', - }, - }; - - const promise = new Promise(res => { - server.on('message', eventData => { - const { message, onSuccess } = eventData; - assert.equal(message, 'This is data'); - onSuccess(); - server.close(); - res(); - }); - }); - - try { - await axios.post('http://localhost:8001/v1/storage_rpc', messageData); - } catch (error) { - assert.isNotOk(error, 'Error occured'); - } - - return promise; - }); - }); -}); diff --git a/libloki/test/node/loki_p2p_api_test.js b/libloki/test/node/loki_p2p_api_test.js deleted file mode 100644 index 5e52ba0195..0000000000 --- a/libloki/test/node/loki_p2p_api_test.js +++ /dev/null @@ -1,92 +0,0 @@ -const { assert } = require('chai'); -const LokiP2pAPI = require('../../../js/modules/loki_p2p_api'); - -describe('LokiP2pAPI', () => { - const usedKey = 'aPubKey'; - const usedAddress = 'anAddress'; - const usedPort = 'aPort'; - - beforeEach(() => { - this.lokiP2pAPI = new LokiP2pAPI(); - }); - - afterEach(() => { - this.lokiP2pAPI.reset(); - }); - - it("Should not emit a pingContact event if that contact doesn't exits", () => { - this.lokiP2pAPI.on('pingContact', () => { - assert.fail(); - }); - this.lokiP2pAPI.pingContact('not stored'); - }); - - it('Should emit an online event if the contact is online', done => { - this.lokiP2pAPI.on('online', pubKey => { - assert.strictEqual(pubKey, usedKey); - done(); - }); - this.lokiP2pAPI.updateContactP2pDetails( - usedKey, - usedAddress, - usedPort, - true - ); - }).timeout(1000); - - it("Should send a pingContact event if the contact isn't online", done => { - this.lokiP2pAPI.on('pingContact', pubKey => { - assert.strictEqual(pubKey, usedKey); - done(); - }); - this.lokiP2pAPI.updateContactP2pDetails( - usedKey, - usedAddress, - usedPort, - false - ); - }).timeout(1000); - - it('Should store a contacts p2p details', () => { - this.lokiP2pAPI.updateContactP2pDetails( - usedKey, - usedAddress, - usedPort, - true - ); - const p2pDetails = this.lokiP2pAPI.getContactP2pDetails(usedKey); - assert.strictEqual(usedAddress, p2pDetails.address); - assert.strictEqual(usedPort, p2pDetails.port); - }); - - it('Should say if a contact is online', () => { - this.lokiP2pAPI.updateContactP2pDetails( - usedKey, - usedAddress, - usedPort, - false - ); - assert.isFalse(this.lokiP2pAPI.isOnline(usedKey)); - this.lokiP2pAPI.updateContactP2pDetails( - usedKey, - usedAddress, - usedPort, - true - ); - assert.isTrue(this.lokiP2pAPI.isOnline(usedKey)); - }); - - it('Should set a contact as offline', () => { - this.lokiP2pAPI.updateContactP2pDetails( - usedKey, - usedAddress, - usedPort, - true - ); - let p2pDetails = this.lokiP2pAPI.getContactP2pDetails(usedKey); - assert.isTrue(p2pDetails.isOnline); - p2pDetails = this.lokiP2pAPI.getContactP2pDetails(usedKey); - this.lokiP2pAPI.setContactOffline(usedKey); - assert.isFalse(p2pDetails.isOnline); - }); -}); diff --git a/libloki/test/snode_channel_test.js b/libloki/test/snode_channel_test.js index 9960dde688..cb535c73e6 100644 --- a/libloki/test/snode_channel_test.js +++ b/libloki/test/snode_channel_test.js @@ -75,7 +75,7 @@ describe('Snode Channel', () => { }); it('should encrypt data correctly', async () => { - // message sent by Loki Messenger + // message sent by Session const snode = await generateSnodeKeysAndAddress(); const messageSent = 'I am Groot'; const textEncoder = new TextEncoder(); @@ -133,7 +133,7 @@ describe('Snode Channel', () => { const encryptedBase64 = dcodeIO.ByteBuffer.wrap(encrypted).toString( 'base64' ); - // message received by Loki Messenger + // message received by Session const decrypted = await channel.decrypt(snode.address, encryptedBase64); assert.strictEqual(messageSent, decrypted); }); diff --git a/libloki/test/storage_test.js b/libloki/test/storage_test.js index 0af07a2bd5..2f7b2d2255 100644 --- a/libloki/test/storage_test.js +++ b/libloki/test/storage_test.js @@ -40,8 +40,9 @@ describe('Storage', () => { testKeyArray.byteLength, newBundle.signedKey.byteLength ); - for (let i = 0; i !== testKeyArray.byteLength; i += 1) + for (let i = 0; i !== testKeyArray.byteLength; i += 1) { assert.strictEqual(testKeyArray[i], newBundle.signedKey[i]); + } }); it('should return the same prekey bundle after creating a contact', async () => { diff --git a/libtextsecure/account_manager.js b/libtextsecure/account_manager.js index 9a2e495cf3..1dfca94170 100644 --- a/libtextsecure/account_manager.js +++ b/libtextsecure/account_manager.js @@ -2,6 +2,8 @@ window, textsecure, libsignal, + libloki, + lokiFileServerAPI, mnemonic, btoa, Signal, @@ -10,9 +12,9 @@ dcodeIO, StringView, log, - storage, Event, - ConversationController + ConversationController, + Whisper */ /* eslint-disable more/no-then */ @@ -123,7 +125,13 @@ let generateKeypair; if (mnemonic) { generateKeypair = () => { - const seedHex = window.mnemonic.mn_decode(mnemonic, mnemonicLanguage); + let seedHex = window.mnemonic.mn_decode(mnemonic, mnemonicLanguage); + // handle shorter than 32 bytes seeds + const privKeyHexLength = 32 * 2; + if (seedHex.length !== privKeyHexLength) { + seedHex = seedHex.concat(seedHex); + seedHex = seedHex.substring(0, privKeyHexLength); + } const privKeyHex = window.mnemonic.sc_reduce32(seedHex); const privKey = dcodeIO.ByteBuffer.wrap( privKeyHex, @@ -373,69 +381,76 @@ }); }); }, - createAccount(identityKeyPair, userAgent, readReceipts) { + async createAccount(identityKeyPair, userAgent, readReceipts) { const signalingKey = libsignal.crypto.getRandomBytes(32 + 20); let password = btoa(getString(libsignal.crypto.getRandomBytes(16))); password = password.substring(0, password.length - 2); const registrationId = libsignal.KeyHelper.generateRegistrationId(); + await Promise.all([ + textsecure.storage.remove('identityKey'), + textsecure.storage.remove('signaling_key'), + textsecure.storage.remove('password'), + textsecure.storage.remove('registrationId'), + textsecure.storage.remove('number_id'), + textsecure.storage.remove('device_name'), + textsecure.storage.remove('userAgent'), + textsecure.storage.remove('read-receipt-setting'), + textsecure.storage.remove('typing-indicators-setting'), + textsecure.storage.remove('regionCode'), + ]); + // update our own identity key, which may have changed // if we're relinking after a reinstall on the master device const pubKeyString = StringView.arrayBufferToHex(identityKeyPair.pubKey); + await textsecure.storage.protocol.saveIdentityWithAttributes( + pubKeyString, + { + id: pubKeyString, + publicKey: identityKeyPair.pubKey, + firstUse: true, + timestamp: Date.now(), + verified: textsecure.storage.protocol.VerifiedStatus.VERIFIED, + nonblockingApproval: true, + } + ); - return Promise.resolve().then(async () => { - await Promise.all([ - textsecure.storage.remove('identityKey'), - textsecure.storage.remove('signaling_key'), - textsecure.storage.remove('password'), - textsecure.storage.remove('registrationId'), - textsecure.storage.remove('number_id'), - textsecure.storage.remove('device_name'), - textsecure.storage.remove('userAgent'), - textsecure.storage.remove('read-receipts-setting'), - ]); - - // update our own identity key, which may have changed - // if we're relinking after a reinstall on the master device - await textsecure.storage.protocol.saveIdentityWithAttributes( - pubKeyString, - { - id: pubKeyString, - publicKey: identityKeyPair.pubKey, - firstUse: true, - timestamp: Date.now(), - verified: textsecure.storage.protocol.VerifiedStatus.VERIFIED, - nonblockingApproval: true, - } - ); + await textsecure.storage.put('identityKey', identityKeyPair); + await textsecure.storage.put('signaling_key', signalingKey); + await textsecure.storage.put('password', password); + await textsecure.storage.put('registrationId', registrationId); + if (userAgent) { + await textsecure.storage.put('userAgent', userAgent); + } - await textsecure.storage.put('identityKey', identityKeyPair); - await textsecure.storage.put('signaling_key', signalingKey); - await textsecure.storage.put('password', password); - await textsecure.storage.put('registrationId', registrationId); - if (userAgent) { - await textsecure.storage.put('userAgent', userAgent); - } + await textsecure.storage.put( + 'read-receipt-setting', + Boolean(readReceipts) + ); - await textsecure.storage.put( - 'read-receipt-setting', - Boolean(readReceipts) - ); + // Enable typing indicators by default + await textsecure.storage.put('typing-indicators-setting', Boolean(true)); - await textsecure.storage.user.setNumberAndDeviceId(pubKeyString, 1); - }); + await textsecure.storage.user.setNumberAndDeviceId(pubKeyString, 1); + await textsecure.storage.put('regionCode', null); }, async clearSessionsAndPreKeys() { const store = textsecure.storage.protocol; window.log.info('clearing all sessions, prekeys, and signed prekeys'); await Promise.all([ - store.clearPreKeyStore(), store.clearContactPreKeysStore(), - store.clearSignedPreKeysStore(), store.clearContactSignedPreKeysStore(), store.clearSessionStore(), ]); + // During secondary device registration we need to keep our prekeys sent + // to other pubkeys + if (textsecure.storage.get('secondaryDeviceStatus') !== 'ongoing') { + await Promise.all([ + store.clearPreKeyStore(), + store.clearSignedPreKeysStore(), + ]); + } }, // Takes the same object returned by generateKeys async confirmKeys(keys) { @@ -516,8 +531,11 @@ }); }, async generateMnemonic(language = 'english') { - const keys = await libsignal.KeyHelper.generateIdentityKeyPair(); - const hex = StringView.arrayBufferToHex(keys.privKey); + // Note: 4 bytes are converted into 3 seed words, so length 12 seed words + // (13 - 1 checksum) are generated using 12 * 4 / 3 = 16 bytes. + const seedSize = 16; + const seed = window.Signal.Crypto.getRandomBytes(seedSize); + const hex = StringView.arrayBufferToHex(seed); return mnemonic.mn_encode(hex, language); }, getCurrentMnemonic() { @@ -526,23 +544,107 @@ saveMnemonic(mnemonic) { return textsecure.storage.put('mnemonic', mnemonic); }, - async registrationDone(number, profileName) { + async registrationDone(number, displayName) { window.log.info('registration done'); + if (!textsecure.storage.get('secondaryDeviceStatus')) { + // We have registered as a primary device + textsecure.storage.put('primaryDevicePubKey', number); + } // Ensure that we always have a conversation for ourself const conversation = await ConversationController.getOrCreateAndWait( number, 'private' ); - - await storage.setProfileName(profileName); - - // Update the conversation if we have it - const newProfile = storage.getLocalProfile(); - await conversation.setProfile(newProfile); + await conversation.setLokiProfile({ displayName }); this.dispatchEvent(new Event('registration')); }, + async requestPairing(primaryDevicePubKey) { + // throws if invalid + this.validatePubKeyHex(primaryDevicePubKey); + // we need a conversation for sending a message + await ConversationController.getOrCreateAndWait( + primaryDevicePubKey, + 'private' + ); + const ourPubKey = textsecure.storage.user.getNumber(); + if (primaryDevicePubKey === ourPubKey) { + throw new Error('Cannot request to pair with ourselves'); + } + const requestSignature = await libloki.crypto.generateSignatureForPairing( + primaryDevicePubKey, + libloki.crypto.PairingType.REQUEST + ); + const authorisation = { + primaryDevicePubKey, + secondaryDevicePubKey: ourPubKey, + requestSignature, + }; + await libloki.api.sendPairingAuthorisation( + authorisation, + primaryDevicePubKey + ); + }, + async authoriseSecondaryDevice(secondaryDevicePubKey) { + const ourPubKey = textsecure.storage.user.getNumber(); + if (secondaryDevicePubKey === ourPubKey) { + throw new Error( + 'Cannot register primary device pubkey as secondary device' + ); + } + + // throws if invalid + this.validatePubKeyHex(secondaryDevicePubKey); + // we need a conversation for sending a message + const secondaryConversation = await ConversationController.getOrCreateAndWait( + secondaryDevicePubKey, + 'private' + ); + const grantSignature = await libloki.crypto.generateSignatureForPairing( + secondaryDevicePubKey, + libloki.crypto.PairingType.GRANT + ); + const existingAuthorisation = await libloki.storage.getAuthorisationForSecondaryPubKey( + secondaryDevicePubKey + ); + if (!existingAuthorisation) { + throw new Error( + 'authoriseSecondaryDevice: request signature missing from database!' + ); + } + const { requestSignature } = existingAuthorisation; + const authorisation = { + primaryDevicePubKey: ourPubKey, + secondaryDevicePubKey, + requestSignature, + grantSignature, + }; + // Update authorisation in database with the new grant signature + await libloki.storage.savePairingAuthorisation(authorisation); + await lokiFileServerAPI.updateOurDeviceMapping(); + await libloki.api.sendPairingAuthorisation( + authorisation, + secondaryDevicePubKey + ); + // Always be friends with secondary devices + await secondaryConversation.setFriendRequestStatus( + window.friends.friendRequestStatusEnum.friends, + { + blockSync: true, + } + ); + }, + validatePubKeyHex(pubKey) { + const c = new Whisper.Conversation({ + id: pubKey, + type: 'private', + }); + const validationError = c.validateNumber(); + if (validationError) { + throw new Error(validationError); + } + }, }); textsecure.AccountManager = AccountManager; })(); diff --git a/libtextsecure/contacts_parser.js b/libtextsecure/contacts_parser.js index 710faf6ae4..4a4c38b691 100644 --- a/libtextsecure/contacts_parser.js +++ b/libtextsecure/contacts_parser.js @@ -14,7 +14,7 @@ ProtoParser.prototype = { if (this.buffer.limit === this.buffer.offset) { return undefined; // eof } - const len = this.buffer.readVarint32(); + const len = this.buffer.readInt32(); const nextBuffer = this.buffer .slice(this.buffer.offset, this.buffer.offset + len) .toArrayBuffer(); diff --git a/libtextsecure/errors.js b/libtextsecure/errors.js index 78f9fcfb1e..7c5fbe050c 100644 --- a/libtextsecure/errors.js +++ b/libtextsecure/errors.js @@ -163,10 +163,10 @@ } inherit(ReplayableError, DNSResolutionError); - function LokiIpError(message, resolutionError) { - this.name = 'LokiIpError'; + function NotFoundError(message, error) { + this.name = 'NotFoundError'; this.message = message; - this.error = resolutionError; + this.error = error; Error.call(this, message); @@ -176,14 +176,12 @@ Error.captureStackTrace(this); } - appendStack(this, resolutionError); + appendStack(this, error); } - function NotFoundError(message, error) { - this.name = 'NotFoundError'; + function SeedNodeError(message) { + this.name = 'SeedNodeError'; this.message = message; - this.error = error; - Error.call(this, message); // Maintains proper stack trace, where our error was thrown (only available on V8) @@ -191,8 +189,6 @@ if (Error.captureStackTrace) { Error.captureStackTrace(this); } - - appendStack(this, error); } function HTTPError(message, response) { @@ -222,6 +218,51 @@ } } + function WrongDifficultyError(newDifficulty) { + this.name = 'WrongDifficultyError'; + this.newDifficulty = newDifficulty; + + Error.call(this, this.name); + + // Maintains proper stack trace, where our error was thrown (only available on V8) + // via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error + if (Error.captureStackTrace) { + Error.captureStackTrace(this); + } + } + + function PublicTokenError(message) { + this.name = 'PublicTokenError'; + + ReplayableError.call(this, { + name: 'PublicTokenError', + message, + }); + } + inherit(ReplayableError, PublicTokenError); + + function TimestampError(message) { + this.name = 'TimeStampError'; + + ReplayableError.call(this, { + name: 'TimestampError', + message, + }); + } + inherit(ReplayableError, TimestampError); + + function PublicChatError(message) { + this.name = 'PublicChatError'; + this.message = message; + Error.call(this, message); + + // Maintains proper stack trace, where our error was thrown (only available on V8) + // via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error + if (Error.captureStackTrace) { + Error.captureStackTrace(this); + } + } + window.textsecure.UnregisteredUserError = UnregisteredUserError; window.textsecure.SendMessageNetworkError = SendMessageNetworkError; window.textsecure.IncomingIdentityKeyError = IncomingIdentityKeyError; @@ -232,9 +273,13 @@ window.textsecure.SignedPreKeyRotationError = SignedPreKeyRotationError; window.textsecure.PoWError = PoWError; window.textsecure.EmptySwarmError = EmptySwarmError; + window.textsecure.SeedNodeError = SeedNodeError; window.textsecure.DNSResolutionError = DNSResolutionError; - window.textsecure.LokiIpError = LokiIpError; window.textsecure.HTTPError = HTTPError; window.textsecure.NotFoundError = NotFoundError; window.textsecure.WrongSwarmError = WrongSwarmError; + window.textsecure.WrongDifficultyError = WrongDifficultyError; + window.textsecure.TimestampError = TimestampError; + window.textsecure.PublicChatError = PublicChatError; + window.textsecure.PublicTokenError = PublicTokenError; })(); diff --git a/libtextsecure/helpers.js b/libtextsecure/helpers.js index ffa4b59dcb..31a6605d01 100644 --- a/libtextsecure/helpers.js +++ b/libtextsecure/helpers.js @@ -15,12 +15,15 @@ const StaticArrayBufferProto = new ArrayBuffer().__proto__; const StaticUint8ArrayProto = new Uint8Array().__proto__; function getString(thing) { if (thing === Object(thing)) { - if (thing.__proto__ === StaticUint8ArrayProto) + if (thing.__proto__ === StaticUint8ArrayProto) { return String.fromCharCode.apply(null, thing); - if (thing.__proto__ === StaticArrayBufferProto) + } + if (thing.__proto__ === StaticArrayBufferProto) { return getString(new Uint8Array(thing)); - if (thing.__proto__ === StaticByteBufferProto) + } + if (thing.__proto__ === StaticByteBufferProto) { return thing.toString('binary'); + } } return thing; } @@ -48,15 +51,19 @@ window.textsecure.utils = (() => { *** JSON'ing Utilities *** ************************* */ function ensureStringed(thing) { - if (getStringable(thing)) return getString(thing); - else if (thing instanceof Array) { + if (getStringable(thing)) { + return getString(thing); + } else if (thing instanceof Array) { const res = []; - for (let i = 0; i < thing.length; i += 1) + for (let i = 0; i < thing.length; i += 1) { res[i] = ensureStringed(thing[i]); + } return res; } else if (thing === Object(thing)) { const res = {}; - for (const key in thing) res[key] = ensureStringed(thing[key]); + for (const key in thing) { + res[key] = ensureStringed(thing[key]); + } return res; } else if (thing === null) { return null; diff --git a/libtextsecure/http-resources.js b/libtextsecure/http-resources.js index e709013495..2e2b42a192 100644 --- a/libtextsecure/http-resources.js +++ b/libtextsecure/http-resources.js @@ -3,8 +3,8 @@ // eslint-disable-next-line func-names (function() { let server; - const SUCCESS_POLL_TIME = 100; - const FAIL_POLL_TIME = 2000; + const EXHAUSTED_SNODES_RETRY_DELAY = 5000; + const NUM_CONCURRENT_CONNECTIONS = 3; function stringToArrayBufferBase64(string) { return dcodeIO.ByteBuffer.wrap(string, 'base64').toArrayBuffer(); @@ -49,6 +49,11 @@ handleRequest = request => request.respond(404, 'Not found'); } let connected = true; + this.calledStop = false; + let resolveStopPolling; + const stopPolling = new Promise(res => { + resolveStopPolling = res; + }); this.handleMessage = (message, options = {}) => { try { @@ -78,28 +83,43 @@ } }; - this.pollServer = async callback => { + this.pollServer = async () => { + // This blocking call will return only when all attempts + // at reaching snodes are exhausted or a DNS error occured try { - await server.retrieveMessages(messages => { - messages.forEach(message => { - const { data } = message; - this.handleMessage(data); - }); - }); - connected = true; - } catch (err) { - window.log.error('Polling error: ', err); - connected = false; + await server.startLongPolling( + NUM_CONCURRENT_CONNECTIONS, + stopPolling, + messages => { + connected = true; + messages.forEach(message => { + const { data } = message; + this.handleMessage(data); + }); + } + ); + } catch (e) { + // we'll try again anyway + } + + if (this.calledStop) { + return; } - const pollTime = connected ? SUCCESS_POLL_TIME : FAIL_POLL_TIME; - callback(connected); + + connected = false; + // Exhausted all our snodes urls, trying again later from scratch setTimeout(() => { - this.pollServer(callback); - }, pollTime); + this.pollServer(); + }, EXHAUSTED_SNODES_RETRY_DELAY); }; this.isConnected = function isConnected() { return connected; }; + + this.close = () => { + this.calledStop = true; + resolveStopPolling(true); + }; }; })(); diff --git a/libtextsecure/key_worker.js b/libtextsecure/key_worker.js index ba8f32155a..96f3c275a3 100644 --- a/libtextsecure/key_worker.js +++ b/libtextsecure/key_worker.js @@ -33,7 +33,9 @@ window.textsecure.storage.impl = { *** Override Storage Routines *** **************************** */ put(key, value) { - if (value === undefined) throw new Error('Tried to store undefined'); + if (value === undefined) { + throw new Error('Tried to store undefined'); + } store[key] = value; postMessage({ method: 'set', key, value }); }, diff --git a/libtextsecure/libsignal-protocol.js b/libtextsecure/libsignal-protocol.js index b5eb93b3e3..a9a47a80b5 100644 --- a/libtextsecure/libsignal-protocol.js +++ b/libtextsecure/libsignal-protocol.js @@ -646,11 +646,11 @@ function allocate(slab, types, allocator, ptr) { assert((ret & 3) == 0); stop = ret + (size & ~3); for (; ptr < stop; ptr += 4) { - HEAP32[((ptr)>>2)]=0; + HEAP32[ptr>>2]=0; } stop = ret + size; while (ptr < stop) { - HEAP8[((ptr++)>>0)]=0; + HEAP8[ptr++>>0]=0; } return ret; } @@ -22848,12 +22848,12 @@ function _memset(ptr, value, num) { } } while ((ptr|0) < (stop4|0)) { - HEAP32[((ptr)>>2)]=value4; + HEAP32[ptr>>2]=value4; ptr = (ptr+4)|0; } } while ((ptr|0) < (stop|0)) { - HEAP8[((ptr)>>0)]=value; + HEAP8[ptr>>0]=value; ptr = (ptr+1)|0; } return (ptr-num)|0; @@ -22898,20 +22898,20 @@ function _memcpy(dest, src, num) { if ((dest&3) == (src&3)) { while (dest & 3) { if ((num|0) == 0) return ret|0; - HEAP8[((dest)>>0)]=((HEAP8[((src)>>0)])|0); + HEAP8[dest>>0]=((HEAP8[src>>0])|0); dest = (dest+1)|0; src = (src+1)|0; num = (num-1)|0; } while ((num|0) >= 4) { - HEAP32[((dest)>>2)]=((HEAP32[((src)>>2)])|0); + HEAP32[dest>>2]=((HEAP32[src>>2])|0); dest = (dest+4)|0; src = (src+4)|0; num = (num-4)|0; } } while ((num|0) > 0) { - HEAP8[((dest)>>0)]=((HEAP8[((src)>>0)])|0); + HEAP8[dest>>0]=((HEAP8[src>>0])|0); dest = (dest+1)|0; src = (src+1)|0; num = (num-1)|0; @@ -36155,7 +36155,11 @@ SessionCipher.prototype = { // using each one at a time. Stop and return the result if we get // a valid result if (sessionList.length === 0) { - return Promise.reject(errors[0]); + var error = errors[0]; + if (!error) { + error = new Error('decryptWithSessionList: list is empty, but no errors in array'); + } + return Promise.reject(error); } var session = sessionList.pop(); @@ -36181,7 +36185,8 @@ SessionCipher.prototype = { var errors = []; return this.decryptWithSessionList(buffer, record.getSessions(), errors).then(function(result) { return this.getRecord(address).then(function(record) { - if (result.session.indexInfo.baseKey !== record.getOpenSession().indexInfo.baseKey) { + var openSession = record.getOpenSession(); + if (!openSession || result.session.indexInfo.baseKey !== openSession.indexInfo.baseKey) { record.archiveCurrentState(); record.promoteState(result.session); } diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index e83ff7509d..6146f86dc6 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -12,10 +12,14 @@ /* global ContactBuffer: false */ /* global GroupBuffer: false */ /* global WebSocketResource: false */ -/* global localLokiServer: false */ -/* global localServerPort: false */ +/* global lokiPublicChatAPI: false */ /* global lokiMessageAPI: false */ -/* global lokiP2pAPI: false */ +/* global feeds: false */ +/* global Whisper: false */ +/* global lokiFileServerAPI: false */ +/* global WebAPI: false */ +/* global ConversationController: false */ +/* global Signal: false */ /* eslint-disable more/no-then */ /* eslint-disable no-unreachable */ @@ -26,6 +30,7 @@ function MessageReceiver(username, password, signalingKey, options = {}) { this.signalingKey = signalingKey; this.username = username; this.password = password; + this.server = WebAPI.connect({ username, password }); if (!options.serverTrustRoot) { throw new Error('Server trust root is required!'); @@ -73,18 +78,17 @@ MessageReceiver.prototype.extend({ this.httpPollingResource = new HttpResource(lokiMessageAPI, { handleRequest: this.handleRequest.bind(this), }); - this.httpPollingResource.pollServer(connected => { - // Emulate receiving an 'empty' websocket messages from the server. - // This is required to update the internal logic that checks - // if we are connected to the server. Without this, for example, - // the loading screen would never disappear if the navigator - // detects internet connectivity but never receives an 'empty' signal. - if (connected) { - this.onEmpty(); - } + this.httpPollingResource.pollServer(); + if (lokiPublicChatAPI) { + lokiPublicChatAPI.on( + 'publicMessage', + this.handleUnencryptedMessage.bind(this) + ); + } + // set up pollers for any RSS feeds + feeds.forEach(feed => { + feed.on('rssMessage', this.handleUnencryptedMessage.bind(this)); }); - localLokiServer.on('message', this.handleP2pMessage.bind(this)); - this.startLocalServer(); // TODO: Rework this socket stuff to work with online messaging const useWebSocket = false; @@ -115,35 +119,29 @@ MessageReceiver.prototype.extend({ // all cached envelopes are processed. this.incoming = [this.pending]; }, - async startLocalServer() { - try { - const myLokiIp = await window.lokiSnodeAPI.getMyLokiIp(); - const myServerPort = await localLokiServer.start( - localServerPort, - myLokiIp + async handleUnencryptedMessage({ message }) { + const isMe = message.source === textsecure.storage.user.getNumber(); + if (!isMe && message.message.profile) { + const conversation = await window.ConversationController.getOrCreateAndWait( + message.source, + 'private' + ); + await this.updateProfile( + conversation, + message.message.profile, + message.message.profileKey ); - window.log.info(`Local Server started at ${myLokiIp}:${myServerPort}`); - libloki.api.broadcastOnlineStatus(); - } catch (e) { - if (e instanceof textsecure.LokiIpError) { - window.log.warn( - 'Failed to get my loki address to bind server to, will retry in 30 seconds' - ); - } else { - window.log.warn( - 'Failed to start local loki server, will retry in 30 seconds' - ); - } - setTimeout(this.startLocalServer.bind(this), 30 * 1000); } + + const ev = new Event('message'); + ev.confirm = function confirmTerm() {}; + ev.data = message; + this.dispatchAndWait(ev); }, - handleP2pMessage({ message, onSuccess, onFailure }) { - const options = { - isP2p: true, - onSuccess, - onFailure, - }; - this.httpPollingResource.handleMessage(message, options); + stopProcessing() { + window.log.info('MessageReceiver: stopProcessing requested'); + this.stoppingProcessing = true; + return this.close(); }, shutdown() { if (this.socket) { @@ -157,15 +155,8 @@ MessageReceiver.prototype.extend({ this.wsr.removeEventListener('close', this._onClose); this.wsr = null; } - - if (localLokiServer) { - localLokiServer.removeListener( - 'message', - this.handleP2pMessage.bind(this) - ); - } }, - close() { + async close() { window.log.info('MessageReceiver.close()'); this.calledClose = true; @@ -175,8 +166,12 @@ MessageReceiver.prototype.extend({ this.wsr.close(3000, 'called close'); } - if (localLokiServer) { - localLokiServer.close(); + if (lokiPublicChatAPI) { + await lokiPublicChatAPI.close(); + } + + if (this.httpPollingResource) { + this.httpPollingResource.close(); } return this.drain(); @@ -188,7 +183,13 @@ MessageReceiver.prototype.extend({ window.log.error('websocket error'); }, dispatchAndWait(event) { - return Promise.all(this.dispatchEvent(event)); + const promise = this.appPromise || Promise.resolve(); + const appJobPromise = Promise.all(this.dispatchEvent(event)); + const job = () => appJobPromise; + + this.appPromise = promise.then(job, job); + + return Promise.resolve(); }, onclose(ev) { window.log.info( @@ -221,7 +222,7 @@ MessageReceiver.prototype.extend({ // }); }, handleRequest(request, options) { - const { isP2p, onSuccess, onFailure } = options; + const { onSuccess, onFailure } = options; this.incoming = this.incoming || []; const lastPromise = _.last(this.incoming); @@ -241,9 +242,6 @@ MessageReceiver.prototype.extend({ const promise = Promise.resolve(request.body.toArrayBuffer()) // textsecure.crypto .then(plaintext => { const envelope = textsecure.protobuf.Envelope.decode(plaintext); - if (isP2p) { - lokiP2pAPI.setContactOnline(envelope.source); - } // After this point, decoding errors are not the server's // fault, and we should handle them gracefully and tell the // user they received an invalid message @@ -253,7 +251,6 @@ MessageReceiver.prototype.extend({ } envelope.id = envelope.serverGuid || window.getGuid(); - envelope.isP2p = isP2p; envelope.serverTimestamp = envelope.serverTimestamp ? envelope.serverTimestamp.toNumber() : null; @@ -312,22 +309,34 @@ MessageReceiver.prototype.extend({ const { incoming } = this; this.incoming = []; - const dispatchEmpty = () => { + const emitEmpty = () => { + window.log.info("MessageReceiver: emitting 'empty' event"); const ev = new Event('empty'); - return this.dispatchAndWait(ev); + this.dispatchAndWait(ev); }; - const queueDispatch = () => { + const waitForApplication = async () => { + window.log.info( + "MessageReceiver: finished processing messages after 'empty', now waiting for application" + ); + const promise = this.appPromise || Promise.resolve(); + this.appPromise = Promise.resolve(); + + // We don't await here because we don't this to gate future message processing + promise.then(emitEmpty, emitEmpty); + }; + + const waitForEmptyQueue = () => { // resetting count to zero so everything queued after this starts over again this.count = 0; - this.addToQueue(dispatchEmpty); + this.addToQueue(waitForApplication); }; // We first wait for all recently-received messages (this.incoming) to be queued, - // then we add a task to emit the 'empty' event to the queue, so all message - // processing is complete by the time it runs. - Promise.all(incoming).then(queueDispatch, queueDispatch); + // then we queue a task to wait for the application to finish its processing, then + // finally we emit the 'empty' event to the queue. + Promise.all(incoming).then(waitForEmptyQueue, waitForEmptyQueue); }, drain() { const { incoming } = this; @@ -464,7 +473,10 @@ MessageReceiver.prototype.extend({ ); await textsecure.storage.unprocessed.remove(item.id); } else { - await textsecure.storage.unprocessed.save({ ...item, attempts }); + await textsecure.storage.unprocessed.updateAttempts( + item.id, + attempts + ); } } catch (error) { window.log.error( @@ -498,30 +510,26 @@ MessageReceiver.prototype.extend({ return null; } - if (item.get('version') === 2) { - item.set({ - source: envelope.source, - sourceDevice: envelope.sourceDevice, - serverTimestamp: envelope.serverTimestamp, - decrypted: await MessageReceiver.arrayBufferToStringBase64(plaintext), - }); + item.source = envelope.source; + item.sourceDevice = envelope.sourceDevice; + item.serverTimestamp = envelope.serverTimestamp; + + if (item.version === 2) { + item.decrypted = await MessageReceiver.arrayBufferToStringBase64( + plaintext + ); } else { - item.set({ - source: envelope.source, - sourceDevice: envelope.sourceDevice, - serverTimestamp: envelope.serverTimestamp, - decrypted: await MessageReceiver.arrayBufferToString(plaintext), - }); + item.decrypted = await MessageReceiver.arrayBufferToString(plaintext); } - return textsecure.storage.unprocessed.save(item.attributes); + return textsecure.storage.unprocessed.addDecryptedData(item.id, item); }, removeFromCache(envelope) { const { id } = envelope; return textsecure.storage.unprocessed.remove(id); }, queueDecryptedEnvelope(envelope, plaintext) { - const { id } = envelope; + const id = this.getEnvelopeId(envelope); window.log.info('queueing decrypted envelope', id); const task = this.handleDecryptedEnvelope.bind(this, envelope, plaintext); @@ -533,9 +541,7 @@ MessageReceiver.prototype.extend({ return promise.catch(error => { window.log.error( - 'queueDecryptedEnvelope error handling envelope', - id, - ':', + `queueDecryptedEnvelope error handling envelope ${id}:`, error && error.stack ? error.stack : error ); }); @@ -572,6 +578,9 @@ MessageReceiver.prototype.extend({ // messages which were successfully decrypted, but application logic didn't finish // processing. handleDecryptedEnvelope(envelope, plaintext) { + if (this.stoppingProcessing) { + return Promise.resolve(); + } // No decryption is required for delivery receipts, so the decrypted field of // the Unprocessed model will never be set @@ -584,6 +593,10 @@ MessageReceiver.prototype.extend({ throw new Error('Received message with no content and no legacyMessage'); }, handleEnvelope(envelope) { + if (this.stoppingProcessing) { + return Promise.resolve(); + } + if (envelope.type === textsecure.protobuf.Envelope.Type.RECEIPT) { return this.onDeliveryReceipt(envelope); } @@ -640,9 +653,10 @@ MessageReceiver.prototype.extend({ return plaintext; }, async decrypt(envelope, ciphertext) { - const { serverTrustRoot } = this; - let promise; + + // We don't have source at this point yet (with sealed sender) + // This needs a massive cleanup! const address = new libsignal.SignalProtocolAddress( envelope.source, envelope.sourceDevice @@ -657,103 +671,51 @@ MessageReceiver.prototype.extend({ options.messageKeysLimit = false; } + // Will become obsolete const sessionCipher = new libsignal.SessionCipher( textsecure.storage.protocol, address, options ); - const secretSessionCipher = new window.Signal.Metadata.SecretSessionCipher( - textsecure.storage.protocol - ); - - const fallBackSessionCipher = new libloki.crypto.FallBackSessionCipher( - address - ); const me = { number: ourNumber, deviceId: parseInt(textsecure.storage.user.getDeviceId(), 10), }; - let conversation; - try { - conversation = await window.ConversationController.getOrCreateAndWait( - envelope.source, - 'private' - ); - } catch (e) { - window.log.info('Error getting conversation: ', envelope.source); - } + // Will become obsolete const getCurrentSessionBaseKey = async () => { const record = await sessionCipher.getRecord(address.toString()); - if (!record) return null; + if (!record) { + return null; + } const openSession = record.getOpenSession(); - if (!openSession) return null; + if (!openSession) { + return null; + } const { baseKey } = openSession.indexInfo; return baseKey; }; + + // Will become obsolete const captureActiveSession = async () => { this.activeSessionBaseKey = await getCurrentSessionBaseKey(sessionCipher); }; - const restoreActiveSession = async () => { - const record = await sessionCipher.getRecord(address.toString()); - if (!record) return; - record.archiveCurrentState(); - const sessionToRestore = record.sessions[this.activeSessionBaseKey]; - record.promoteState(sessionToRestore); - record.updateSessionState(sessionToRestore); - await textsecure.storage.protocol.storeSession( - address.toString(), - record.serialize() - ); - }; - const deleteAllSessionExcept = async sessionBaseKey => { - const record = await sessionCipher.getRecord(address.toString()); - if (!record) return; - const sessionToKeep = record.sessions[sessionBaseKey]; - record.sessions = {}; - record.updateSessionState(sessionToKeep); - await textsecure.storage.protocol.storeSession( - address.toString(), - record.serialize() - ); - }; - let handleSessionReset; - if (conversation.isSessionResetOngoing()) { - handleSessionReset = async result => { - const currentSessionBaseKey = await getCurrentSessionBaseKey( - sessionCipher - ); - if ( - this.activeSessionBaseKey && - currentSessionBaseKey !== this.activeSessionBaseKey - ) { - if (conversation.isSessionResetReceived()) { - await restoreActiveSession(); - } else { - await deleteAllSessionExcept(currentSessionBaseKey); - await conversation.onNewSessionAdopted(); - } - } else if (conversation.isSessionResetReceived()) { - await deleteAllSessionExcept(this.activeSessionBaseKey); - await conversation.onNewSessionAdopted(); - } - return result; - }; - } else { - handleSessionReset = async result => result; - } switch (envelope.type) { case textsecure.protobuf.Envelope.Type.CIPHERTEXT: window.log.info('message from', this.getEnvelopeId(envelope)); promise = captureActiveSession() .then(() => sessionCipher.decryptWhisperMessage(ciphertext)) - .then(this.unpad) - .then(handleSessionReset); + .then(this.unpad); break; case textsecure.protobuf.Envelope.Type.FRIEND_REQUEST: { window.log.info('friend-request message from ', envelope.source); + + const fallBackSessionCipher = new libloki.crypto.FallBackSessionCipher( + address + ); + promise = fallBackSessionCipher .decrypt(ciphertext.toArrayBuffer()) .then(this.unpad); @@ -761,40 +723,38 @@ MessageReceiver.prototype.extend({ } case textsecure.protobuf.Envelope.Type.PREKEY_BUNDLE: window.log.info('prekey message from', this.getEnvelopeId(envelope)); - promise = captureActiveSession(sessionCipher) - .then(async () => { - if (!this.activeSessionBaseKey) { - try { - const buffer = dcodeIO.ByteBuffer.wrap(ciphertext); - await window.libloki.storage.verifyFriendRequestAcceptPreKey( - envelope.source, - buffer - ); - } catch (e) { - await this.removeFromCache(envelope); - throw e; - } + promise = captureActiveSession(sessionCipher).then(async () => { + if (!this.activeSessionBaseKey) { + try { + const buffer = dcodeIO.ByteBuffer.wrap(ciphertext); + await window.libloki.storage.verifyFriendRequestAcceptPreKey( + envelope.source, + buffer + ); + } catch (e) { + await this.removeFromCache(envelope); + throw e; } - return this.decryptPreKeyWhisperMessage( - ciphertext, - sessionCipher, - address - ); - }) - .then(handleSessionReset); + } + return this.decryptPreKeyWhisperMessage( + ciphertext, + sessionCipher, + address + ); + }); break; - case textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER: + case textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER: { window.log.info('received unidentified sender message'); + + const secretSessionCipher = new window.Signal.Metadata.SecretSessionCipher( + textsecure.storage.protocol + ); + promise = secretSessionCipher - .decrypt( - window.Signal.Metadata.createCertificateValidator(serverTrustRoot), - ciphertext.toArrayBuffer(), - Math.min(envelope.serverTimestamp || Date.now(), Date.now()), - me - ) + .decrypt(ciphertext.toArrayBuffer(), me) .then( result => { - const { isMe, sender, content } = result; + const { isMe, sender, content, type } = result; // We need to drop incoming messages from ourself since server can't // do it for us @@ -802,6 +762,13 @@ MessageReceiver.prototype.extend({ return { isMe: true }; } + // We might have substituted the type based on decrypted content + if (type === textsecure.protobuf.Envelope.Type.FRIEND_REQUEST) { + // eslint-disable-next-line no-param-reassign + envelope.type = + textsecure.protobuf.Envelope.Type.FRIEND_REQUEST; + } + if (this.isBlocked(sender.getName())) { window.log.info( 'Dropping blocked message after sealed sender decryption' @@ -822,7 +789,7 @@ MessageReceiver.prototype.extend({ envelope.unidentifiedDeliveryReceived = !originalSource; // Return just the content because that matches the signature of the other - // decrypt methods used above. + // decrypt methods used above. return this.unpad(content); }, error => { @@ -854,31 +821,131 @@ MessageReceiver.prototype.extend({ } ); break; + } default: promise = Promise.reject(new Error('Unknown message type')); } return promise - .then(plaintext => { + .then(async plaintext => { const { isMe, isBlocked } = plaintext || {}; if (isMe || isBlocked) { - return this.removeFromCache(envelope); + this.removeFromCache(envelope); + return null; } - return this.updateCache(envelope, plaintext).then( - () => plaintext, - error => { - window.log.error( - 'decrypt failed to save decrypted message contents to cache:', - error && error.stack ? error.stack : error - ); - return plaintext; - } + let conversation; + try { + conversation = await window.ConversationController.getOrCreateAndWait( + envelope.source, + 'private' + ); + } catch (e) { + window.log.info('Error getting conversation: ', envelope.source); + } + + // lint hates anything after // (so /// is no good) + // *** BEGIN: session reset *** + + // we have address in scope from parent scope + // seems to be the same input parameters + // going to comment out due to lint complaints + /* + const address = new libsignal.SignalProtocolAddress( + envelope.source, + envelope.sourceDevice ); + */ + + const restoreActiveSession = async () => { + const record = await sessionCipher.getRecord(address.toString()); + if (!record) { + return; + } + record.archiveCurrentState(); + + // NOTE: activeSessionBaseKey will be undefined here... + const sessionToRestore = record.sessions[this.activeSessionBaseKey]; + record.promoteState(sessionToRestore); + record.updateSessionState(sessionToRestore); + await textsecure.storage.protocol.storeSession( + address.toString(), + record.serialize() + ); + }; + const deleteAllSessionExcept = async sessionBaseKey => { + const record = await sessionCipher.getRecord(address.toString()); + if (!record) { + return; + } + const sessionToKeep = record.sessions[sessionBaseKey]; + record.sessions = {}; + record.updateSessionState(sessionToKeep); + await textsecure.storage.protocol.storeSession( + address.toString(), + record.serialize() + ); + }; + + if (conversation.isSessionResetOngoing()) { + const currentSessionBaseKey = await getCurrentSessionBaseKey( + sessionCipher + ); + if ( + this.activeSessionBaseKey && + currentSessionBaseKey !== this.activeSessionBaseKey + ) { + if (conversation.isSessionResetReceived()) { + await restoreActiveSession(); + } else { + await deleteAllSessionExcept(currentSessionBaseKey); + await conversation.onNewSessionAdopted(); + } + } else if (conversation.isSessionResetReceived()) { + await deleteAllSessionExcept(this.activeSessionBaseKey); + await conversation.onNewSessionAdopted(); + } + } + + // lint hates anything after // (so /// is no good) + // *** END *** + + // Type here can actually be UNIDENTIFIED_SENDER even if + // the underlying message is FRIEND_REQUEST + if ( + envelope.type !== textsecure.protobuf.Envelope.Type.FRIEND_REQUEST + ) { + // If we got here there is a valid session, which meants friend request + // is complete (if it wasn't already) + if (conversation) { + const isFriendRequestAccept = await conversation.onFriendRequestAccepted(); + if (isFriendRequestAccept) { + await conversation.notifyFriendRequest( + envelope.source, + 'accepted' + ); + } + } + } + + this.updateCache(envelope, plaintext).catch(error => { + window.log.error( + 'decrypt failed to save decrypted message contents to cache:', + error && error.stack ? error.stack : error + ); + }); + + return plaintext; }) .catch(error => { let errorToThrow = error; + const noSession = + error && + (error.message.indexOf('No record for device') === 0 || + error.message.indexOf('decryptWithSessionList: list is empty') === + 0); + if (error && error.message === 'Unknown identity key') { // create an error that the UI will pick up and ask the // user if they want to re-negotiate @@ -888,8 +955,8 @@ MessageReceiver.prototype.extend({ buffer.toArrayBuffer(), error.identityKey ); - } else { - // re-throw + } else if (!noSession) { + // We want to handle "no-session" error, not re-throw it throw error; } const ev = new Event('error'); @@ -920,6 +987,8 @@ MessageReceiver.prototype.extend({ throw e; } }, + // handle a SYNC message for a message + // sent by another device handleSentMessage(envelope, sentContainer, msg) { const { destination, @@ -934,10 +1003,13 @@ MessageReceiver.prototype.extend({ p = this.handleEndSession(destination); } return p.then(() => - this.processDecrypted(envelope, msg, this.number).then(message => { + this.processDecrypted(envelope, msg).then(message => { const groupId = message.group && message.group.id; const isBlocked = this.isGroupBlocked(groupId); - const isMe = envelope.source === textsecure.storage.user.getNumber(); + const primaryDevicePubKey = window.storage.get('primaryDevicePubKey'); + const isMe = + envelope.source === textsecure.storage.user.getNumber() || + envelope.source === primaryDevicePubKey; const isLeavingGroup = Boolean( message.group && message.group.type === textsecure.protobuf.GroupContext.Type.QUIT @@ -952,6 +1024,17 @@ MessageReceiver.prototype.extend({ return this.removeFromCache(envelope); } + // handle profileKey and avatar updates + if (envelope.source === primaryDevicePubKey) { + const { profileKey, profile } = message; + const primaryConversation = ConversationController.get( + primaryDevicePubKey + ); + if (profile) { + this.updateProfile(primaryConversation, profile, profileKey); + } + } + const ev = new Event('sent'); ev.confirm = this.removeFromCache.bind(this, envelope); ev.data = { @@ -968,25 +1051,228 @@ MessageReceiver.prototype.extend({ }) ); }, - async handleLokiAddressMessage(envelope, lokiAddressMessage) { - const { p2pAddress, p2pPort } = lokiAddressMessage; - lokiP2pAPI.updateContactP2pDetails( - envelope.source, - p2pAddress, - p2pPort, - envelope.isP2p + async handleLokiAddressMessage(envelope) { + window.log.warn('Ignoring a Loki address message'); + return this.removeFromCache(envelope); + }, + async handlePairingRequest(envelope, pairingRequest) { + const valid = await libloki.crypto.validateAuthorisation(pairingRequest); + if (valid) { + // Pairing dialog is open and is listening + if (Whisper.events.isListenedTo('devicePairingRequestReceived')) { + await window.libloki.storage.savePairingAuthorisation(pairingRequest); + Whisper.events.trigger( + 'devicePairingRequestReceived', + pairingRequest.secondaryDevicePubKey + ); + } + // Ignore requests if the dialog is closed + } + return this.removeFromCache(envelope); + }, + async handleAuthorisationForSelf( + envelope, + pairingAuthorisation, + { dataMessage, syncMessage } + ) { + const valid = await libloki.crypto.validateAuthorisation( + pairingAuthorisation + ); + const alreadySecondaryDevice = !!window.storage.get('isSecondaryDevice'); + let removedFromCache = false; + if (alreadySecondaryDevice) { + window.log.warn( + 'Received an unexpected pairing authorisation (device is already paired as secondary device). Ignoring.' + ); + } else if (!valid) { + window.log.warn( + 'Received invalid pairing authorisation for self. Could not verify signature. Ignoring.' + ); + } else { + const { primaryDevicePubKey, grantSignature } = pairingAuthorisation; + if (grantSignature) { + // Authorisation received to become a secondary device + window.log.info( + `Received pairing authorisation from ${primaryDevicePubKey}` + ); + // Set current device as secondary. + // This will ensure the authorisation is sent + // along with each friend request. + window.storage.remove('secondaryDeviceStatus'); + window.storage.put('isSecondaryDevice', true); + window.storage.put('primaryDevicePubKey', primaryDevicePubKey); + await libloki.storage.savePairingAuthorisation(pairingAuthorisation); + const primaryConversation = await ConversationController.getOrCreateAndWait( + primaryDevicePubKey, + 'private' + ); + primaryConversation.trigger('change'); + Whisper.events.trigger('secondaryDeviceRegistration'); + // Update profile + if (dataMessage) { + const { profile, profileKey } = dataMessage; + const ourNumber = window.storage.get('primaryDevicePubKey'); + const me = window.ConversationController.get(ourNumber); + if (me) { + this.updateProfile(me, profile, profileKey); + } + } + // Update contact list + if (syncMessage && syncMessage.contacts) { + // This call already removes the envelope from the cache + await this.handleContacts(envelope, syncMessage.contacts); + removedFromCache = true; + } + } else { + window.log.warn('Unimplemented pairing authorisation message type'); + } + } + if (!removedFromCache) { + await this.removeFromCache(envelope); + } + }, + async sendFriendRequestsToSyncContacts(contacts) { + const attachmentPointer = await this.handleAttachment(contacts); + const contactBuffer = new ContactBuffer(attachmentPointer.data); + let contactDetails = contactBuffer.next(); + // Extract just the pubkeys + const friendPubKeys = []; + while (contactDetails !== undefined) { + friendPubKeys.push(contactDetails.number); + contactDetails = contactBuffer.next(); + } + return Promise.all( + friendPubKeys.map(async pubKey => { + const c = await window.ConversationController.getOrCreateAndWait( + pubKey, + 'private' + ); + if (!c) { + return null; + } + const attachments = []; + const quote = null; + const linkPreview = null; + // Send an empty message, the underlying logic will know + // it should send a friend request + return c.sendMessage('', attachments, quote, linkPreview); + }) + ); + }, + async handleAuthorisationForContact(envelope) { + window.log.error( + 'Unexpected pairing request/authorisation received, ignoring.' ); return this.removeFromCache(envelope); }, - handleDataMessage(envelope, msg) { - if (!envelope.isP2p) { - const timestamp = envelope.timestamp.toNumber(); - const now = Date.now(); - const ageInSeconds = (now - timestamp) / 1000; - if (ageInSeconds <= 120) { - lokiP2pAPI.pingContact(envelope.source); + async handlePairingAuthorisationMessage(envelope, content) { + const { pairingAuthorisation } = content; + const { secondaryDevicePubKey, grantSignature } = pairingAuthorisation; + const isGrant = + grantSignature && + secondaryDevicePubKey === textsecure.storage.user.getNumber(); + if (isGrant) { + return this.handleAuthorisationForSelf( + envelope, + pairingAuthorisation, + content + ); + } + return this.handlePairingRequest(envelope, pairingAuthorisation); + }, + + async handleSecondaryDeviceFriendRequest(pubKey, deviceMapping) { + if (!deviceMapping) { + return false; + } + // Only handle secondary pubkeys + if (deviceMapping.isPrimary === '1' || !deviceMapping.authorisations) { + return false; + } + const { authorisations } = deviceMapping; + // Secondary devices should only have 1 authorisation from a primary device + if (authorisations.length !== 1) { + return false; + } + const authorisation = authorisations[0]; + if (!authorisation) { + return false; + } + if (!authorisation.grantSignature) { + return false; + } + const isValid = await libloki.crypto.validateAuthorisation(authorisation); + if (!isValid) { + return false; + } + const correctSender = pubKey === authorisation.secondaryDevicePubKey; + if (!correctSender) { + return false; + } + const { primaryDevicePubKey } = authorisation; + // ensure the primary device is a friend + const c = window.ConversationController.get(primaryDevicePubKey); + if (!c || !c.isFriendWithAnyDevice()) { + return false; + } + await libloki.storage.savePairingAuthorisation(authorisation); + + return true; + }, + + async updateProfile(conversation, profile, profileKey) { + // Retain old values unless changed: + const newProfile = conversation.get('profile') || {}; + + newProfile.displayName = profile.displayName; + + // TODO: may need to allow users to reset their avatars to null + if (profile.avatar) { + const prevPointer = conversation.get('avatarPointer'); + const needsUpdate = + !prevPointer || !_.isEqual(prevPointer, profile.avatar); + + if (needsUpdate) { + conversation.set('avatarPointer', profile.avatar); + conversation.set('profileKey', profileKey); + + const downloaded = await this.downloadAttachment({ + url: profile.avatar, + isRaw: true, + }); + + // null => use jazzicon + let path = null; + if (profileKey) { + // Convert profileKey to ArrayBuffer, if needed + const encoding = typeof profileKey === 'string' ? 'base64' : null; + try { + const profileKeyArrayBuffer = dcodeIO.ByteBuffer.wrap( + profileKey, + encoding + ).toArrayBuffer(); + const decryptedData = await textsecure.crypto.decryptProfile( + downloaded.data, + profileKeyArrayBuffer + ); + const upgraded = await Signal.Migrations.processNewAttachment({ + ...downloaded, + data: decryptedData, + }); + ({ path } = upgraded); + } catch (e) { + window.log.error(`Could not decrypt profile image: ${e}`); + } + } + newProfile.avatar = path; } + } else { + newProfile.avatar = null; } + + await conversation.setLokiProfile(newProfile); + }, + handleDataMessage(envelope, msg) { window.log.info('data message from', this.getEnvelopeId(envelope)); let p = Promise.resolve(); // eslint-disable-next-line no-bitwise @@ -994,76 +1280,141 @@ MessageReceiver.prototype.extend({ p = this.handleEndSession(envelope.source); } return p.then(() => - this.processDecrypted(envelope, msg, envelope.source).then( - async message => { - const groupId = message.group && message.group.id; - const isBlocked = this.isGroupBlocked(groupId); - const isMe = envelope.source === textsecure.storage.user.getNumber(); - const conversation = window.ConversationController.get( - envelope.source - ); - const isLeavingGroup = Boolean( - message.group && - message.group.type === textsecure.protobuf.GroupContext.Type.QUIT - ); - const friendRequest = - envelope.type === textsecure.protobuf.Envelope.Type.FRIEND_REQUEST; - - // Check if we need to update any profile names - if (!isMe && conversation) { - let profile = null; - if (message.profile) { - profile = JSON.parse(message.profile.encodeJSON()); + this.processDecrypted(envelope, msg).then(async message => { + const groupId = message.group && message.group.id; + const isBlocked = this.isGroupBlocked(groupId); + const ourPubKey = textsecure.storage.user.getNumber(); + const isMe = envelope.source === ourPubKey; + const conversation = window.ConversationController.get(envelope.source); + const isLeavingGroup = Boolean( + message.group && + message.group.type === textsecure.protobuf.GroupContext.Type.QUIT + ); + const friendRequest = + envelope.type === textsecure.protobuf.Envelope.Type.FRIEND_REQUEST; + const { UNPAIRING_REQUEST } = textsecure.protobuf.DataMessage.Flags; + // eslint-disable-next-line no-bitwise + const isUnpairingRequest = Boolean(message.flags & UNPAIRING_REQUEST); + + if (!friendRequest && isUnpairingRequest) { + // TODO: move high-level pairing logic to libloki.multidevice.xx + + const unpairingRequestIsLegit = async () => { + const isSecondary = textsecure.storage.get('isSecondaryDevice'); + if (!isSecondary) { + return false; + } + const primaryPubKey = window.storage.get('primaryDevicePubKey'); + // TODO: allow unpairing from any paired device? + if (envelope.source !== primaryPubKey) { + return false; } - // Update the conversation - await conversation.setProfile(profile); - } + const primaryMapping = await lokiFileServerAPI.getUserDeviceMapping( + primaryPubKey + ); - if (friendRequest && isMe) { - window.log.info('refusing to add a friend request to ourselves'); - throw new Error('Cannot add a friend request for ourselves!'); + if (!primaryMapping) { + return false; + } + + // We expect the primary device to have updated its mapping + // before sending the unpairing request + const found = primaryMapping.authorisations.find( + authorisation => authorisation.secondaryDevicePubKey === ourPubKey + ); + + // our pubkey should NOT be in the primary device mapping + return !found; + }; + + const legit = await unpairingRequestIsLegit(); + + this.removeFromCache(envelope); + + if (legit) { + // remove our device mapping annotations from file server + await lokiFileServerAPI.clearOurDeviceMappingAnnotations(); + // Delete the account and restart + try { + await window.Signal.Logs.deleteAll(); + await window.Signal.Data.removeAll(); + await window.Signal.Data.close(); + await window.Signal.Data.removeDB(); + await window.Signal.Data.removeOtherData(); + // TODO generate an empty db with a flag + // to display a message about the unpairing + // after the app restarts + } catch (error) { + window.log.error( + 'Something went wrong deleting all data:', + error && error.stack ? error.stack : error + ); + } + window.restart(); } + } - if (groupId && isBlocked && !(isMe && isLeavingGroup)) { - window.log.warn( - `Message ${this.getEnvelopeId( - envelope - )} ignored; destined for blocked group` + // Check if we need to update any profile names + if (!isMe && conversation) { + if (message.profile) { + await this.updateProfile( + conversation, + message.profile, + message.profileKey ); - return this.removeFromCache(envelope); } + } - if (!message.body) { - // Trigger conversation friend request event for empty message - if (conversation && !message.flags) { - const isFriendRequestAccept = await conversation.onFriendRequestAccepted(); - if (isFriendRequestAccept) { - await conversation.notifyFriendRequest( - envelope.source, - 'accepted' - ); - } + if (friendRequest) { + if (isMe) { + window.log.info('refusing to add a friend request to ourselves'); + throw new Error('Cannot add a friend request for ourselves!'); + } else { + const senderPubKey = envelope.source; + // fetch the device mapping from the server + const deviceMapping = await lokiFileServerAPI.getUserDeviceMapping( + senderPubKey + ); + // auto-accept friend request if the device is paired to one of our friend + const autoAccepted = await this.handleSecondaryDeviceFriendRequest( + senderPubKey, + deviceMapping + ); + if (autoAccepted) { + // sending a message back = accepting friend request + // Directly setting friend request status to skip the pending state + await conversation.setFriendRequestStatus( + window.friends.friendRequestStatusEnum.friends + ); + window.libloki.api.sendBackgroundMessage(envelope.source); + return this.removeFromCache(envelope); } - this.removeFromCache(envelope); - return null; } + } - const ev = new Event('message'); - ev.confirm = this.removeFromCache.bind(this, envelope); - ev.data = { - friendRequest, - source: envelope.source, - sourceDevice: envelope.sourceDevice, - timestamp: envelope.timestamp.toNumber(), - receivedAt: envelope.receivedAt, - unidentifiedDeliveryReceived: envelope.unidentifiedDeliveryReceived, - isP2p: envelope.isP2p, - message, - }; - return this.dispatchAndWait(ev); + if (groupId && isBlocked && !(isMe && isLeavingGroup)) { + window.log.warn( + `Message ${this.getEnvelopeId( + envelope + )} ignored; destined for blocked group` + ); + return this.removeFromCache(envelope); } - ) + + const ev = new Event('message'); + ev.confirm = this.removeFromCache.bind(this, envelope); + ev.data = { + friendRequest, + source: envelope.source, + sourceDevice: envelope.sourceDevice, + timestamp: envelope.timestamp.toNumber(), + receivedAt: envelope.receivedAt, + unidentifiedDeliveryReceived: envelope.unidentifiedDeliveryReceived, + message, + }; + return this.dispatchAndWait(ev); + }) ); }, handleLegacyMessage(envelope) { @@ -1096,28 +1447,39 @@ MessageReceiver.prototype.extend({ async innerHandleContentMessage(envelope, plaintext) { const content = textsecure.protobuf.Content.decode(plaintext); - if (content.preKeyBundleMessage) + if (content.preKeyBundleMessage) { await this.savePreKeyBundleMessage( envelope.source, content.preKeyBundleMessage ); - if (content.lokiAddressMessage) + } + if (content.lokiAddressMessage) { return this.handleLokiAddressMessage( envelope, content.lokiAddressMessage ); - if (content.syncMessage) + } + if (content.pairingAuthorisation) { + return this.handlePairingAuthorisationMessage(envelope, content); + } + if (content.syncMessage) { return this.handleSyncMessage(envelope, content.syncMessage); - if (content.dataMessage) + } + if (content.dataMessage) { return this.handleDataMessage(envelope, content.dataMessage); - if (content.nullMessage) + } + if (content.nullMessage) { return this.handleNullMessage(envelope, content.nullMessage); - if (content.callMessage) + } + if (content.callMessage) { return this.handleCallMessage(envelope, content.callMessage); - if (content.receiptMessage) + } + if (content.receiptMessage) { return this.handleReceiptMessage(envelope, content.receiptMessage); - if (content.typingMessage) + } + if (content.typingMessage) { return this.handleTypingMessage(envelope, content.typingMessage); + } return null; }, @@ -1197,13 +1559,18 @@ MessageReceiver.prototype.extend({ window.log.info('null message from', this.getEnvelopeId(envelope)); this.removeFromCache(envelope); }, - handleSyncMessage(envelope, syncMessage) { - if (envelope.source !== this.number) { - throw new Error('Received sync message from another number'); - } - // eslint-disable-next-line eqeqeq - if (envelope.sourceDevice == this.deviceId) { - throw new Error('Received sync message from our own device'); + async handleSyncMessage(envelope, syncMessage) { + const ourNumber = textsecure.storage.user.getNumber(); + // NOTE: Maybe we should be caching this list? + const ourDevices = await libloki.storage.getAllDevicePubKeysForPrimaryPubKey( + window.storage.get('primaryDevicePubKey') + ); + const validSyncSender = + ourDevices && ourDevices.some(devicePubKey => devicePubKey === ourNumber); + if (!validSyncSender) { + throw new Error( + "Received sync message from a device we aren't paired with" + ); } if (syncMessage.sent) { const sentMessage = syncMessage.sent; @@ -1271,8 +1638,11 @@ MessageReceiver.prototype.extend({ }, handleContacts(envelope, contacts) { window.log.info('contact sync'); - const attachmentPointer = contacts.blob; - return this.handleAttachment(attachmentPointer).then(() => { + // const { blob } = contacts; + + // Note: we do not return here because we don't want to block the next message on + // this attachment download and a lot of processing of that attachment. + this.handleAttachment(contacts).then(attachmentPointer => { const results = []; const contactBuffer = new ContactBuffer(attachmentPointer.data); let contactDetails = contactBuffer.next(); @@ -1295,45 +1665,22 @@ MessageReceiver.prototype.extend({ }, handleGroups(envelope, groups) { window.log.info('group sync'); - const attachmentPointer = groups.blob; - return this.handleAttachment(attachmentPointer).then(() => { + const { blob } = groups; + + // Note: we do not return here because we don't want to block the next message on + // this attachment download and a lot of processing of that attachment. + this.handleAttachment(blob).then(attachmentPointer => { const groupBuffer = new GroupBuffer(attachmentPointer.data); let groupDetails = groupBuffer.next(); const promises = []; while (groupDetails !== undefined) { - const getGroupDetails = details => { - // eslint-disable-next-line no-param-reassign - details.id = details.id.toBinary(); - if (details.active) { - return textsecure.storage.groups - .getGroup(details.id) - .then(existingGroup => { - if (existingGroup === undefined) { - return textsecure.storage.groups.createNewGroup( - details.members, - details.id - ); - } - return textsecure.storage.groups.updateNumbers( - details.id, - details.members - ); - }) - .then(() => details); - } - return Promise.resolve(details); - }; - - const promise = getGroupDetails(groupDetails) - .then(details => { - const ev = new Event('group'); - ev.confirm = this.removeFromCache.bind(this, envelope); - ev.groupDetails = details; - return this.dispatchAndWait(ev); - }) - .catch(e => { - window.log.error('error processing group', e); - }); + groupDetails.id = groupDetails.id.toBinary(); + const ev = new Event('group'); + ev.confirm = this.removeFromCache.bind(this, envelope); + ev.groupDetails = groupDetails; + const promise = this.dispatchAndWait(ev).catch(e => { + window.log.error('error processing group', e); + }); groupDetails = groupBuffer.next(); promises.push(promise); } @@ -1389,34 +1736,49 @@ MessageReceiver.prototype.extend({ isGroupBlocked(groupId) { return textsecure.storage.get('blocked-groups', []).indexOf(groupId) >= 0; }, - handleAttachment(attachment) { - window.log.info('Not handling attachments.'); - return Promise.reject(); - // eslint-disable-next-line no-param-reassign - attachment.id = attachment.id.toString(); - // eslint-disable-next-line no-param-reassign - attachment.key = attachment.key.toArrayBuffer(); - if (attachment.digest) { - // eslint-disable-next-line no-param-reassign - attachment.digest = attachment.digest.toArrayBuffer(); - } - function decryptAttachment(encrypted) { - return textsecure.crypto.decryptAttachment( - encrypted, - attachment.key, - attachment.digest + cleanAttachment(attachment) { + return { + ..._.omit(attachment, 'thumbnail'), + id: attachment.id.toString(), + key: attachment.key ? attachment.key.toString('base64') : null, + digest: attachment.digest ? attachment.digest.toString('base64') : null, + }; + }, + async downloadAttachment(attachment) { + // The attachment id is actually just the absolute url of the attachment + let data = await this.server.getAttachment(attachment.url); + if (!attachment.isRaw) { + const { key, digest, size } = attachment; + + data = await textsecure.crypto.decryptAttachment( + data, + window.Signal.Crypto.base64ToArrayBuffer(key), + window.Signal.Crypto.base64ToArrayBuffer(digest) ); - } - function updateAttachment(data) { - // eslint-disable-next-line no-param-reassign - attachment.data = data; + if (!size || size !== data.byteLength) { + throw new Error( + `downloadAttachment: Size ${size} did not match downloaded attachment size ${ + data.byteLength + }` + ); + } } - return this.server - .getAttachment(attachment.id) - .then(decryptAttachment) - .then(updateAttachment); + return { + ..._.omit(attachment, 'digest', 'key'), + data, + }; + }, + handleAttachment(attachment) { + // window.log.info('Not handling attachments.'); + return Promise.resolve({ + ...attachment, + data: dcodeIO.ByteBuffer.wrap(attachment.data).toArrayBuffer(), // ByteBuffer to ArrayBuffer + }); + + const cleaned = this.cleanAttachment(attachment); + return this.downloadAttachment(cleaned); }, async handleEndSession(number) { window.log.info('got end session'); @@ -1429,11 +1791,6 @@ MessageReceiver.prototype.extend({ window.log.error('Error getting conversation: ', number); } - // Bail early if a session reset is already ongoing - if (conversation.isSessionResetOngoing()) { - return; - } - await Promise.all( deviceIds.map(async deviceId => { const address = new libsignal.SignalProtocolAddress(number, deviceId); @@ -1464,7 +1821,7 @@ MessageReceiver.prototype.extend({ ); await conversation.onSessionResetReceived(); }, - processDecrypted(envelope, decrypted, source) { + processDecrypted(envelope, decrypted) { /* eslint-disable no-bitwise, no-param-reassign */ const FLAGS = textsecure.protobuf.DataMessage.Flags; @@ -1491,6 +1848,12 @@ MessageReceiver.prototype.extend({ } else if (decrypted.flags & FLAGS.PROFILE_KEY_UPDATE) { decrypted.body = null; decrypted.attachments = []; + } else if (decrypted.flags & FLAGS.SESSION_REQUEST) { + // do nothing + } else if (decrypted.flags & FLAGS.SESSION_RESTORE) { + // do nothing + } else if (decrypted.flags & FLAGS.UNPAIRING_REQUEST) { + // do nothing } else if (decrypted.flags !== 0) { throw new Error('Unknown flags in message'); } @@ -1500,71 +1863,24 @@ MessageReceiver.prototype.extend({ if (decrypted.group !== null) { decrypted.group.id = decrypted.group.id.toBinary(); - if ( - decrypted.group.type === textsecure.protobuf.GroupContext.Type.UPDATE - ) { - if (decrypted.group.avatar !== null) { - promises.push(this.handleAttachment(decrypted.group.avatar)); - } + switch (decrypted.group.type) { + case textsecure.protobuf.GroupContext.Type.UPDATE: + decrypted.body = null; + decrypted.attachments = []; + break; + case textsecure.protobuf.GroupContext.Type.QUIT: + decrypted.body = null; + decrypted.attachments = []; + break; + case textsecure.protobuf.GroupContext.Type.DELIVER: + decrypted.group.name = null; + decrypted.group.members = []; + decrypted.group.avatar = null; + break; + default: + this.removeFromCache(envelope); + throw new Error('Unknown group message type'); } - - const storageGroups = textsecure.storage.groups; - - promises.push( - storageGroups.getNumbers(decrypted.group.id).then(existingGroup => { - if (existingGroup === undefined) { - if ( - decrypted.group.type !== - textsecure.protobuf.GroupContext.Type.UPDATE - ) { - decrypted.group.members = [source]; - window.log.warn('Got message for unknown group'); - } - return textsecure.storage.groups.createNewGroup( - decrypted.group.members, - decrypted.group.id - ); - } - const fromIndex = existingGroup.indexOf(source); - - if (fromIndex < 0) { - // TODO: This could be indication of a race... - window.log.warn( - 'Sender was not a member of the group they were sending from' - ); - } - - switch (decrypted.group.type) { - case textsecure.protobuf.GroupContext.Type.UPDATE: - decrypted.body = null; - decrypted.attachments = []; - return textsecure.storage.groups.updateNumbers( - decrypted.group.id, - decrypted.group.members - ); - case textsecure.protobuf.GroupContext.Type.QUIT: - decrypted.body = null; - decrypted.attachments = []; - if (source === this.number) { - return textsecure.storage.groups.deleteGroup( - decrypted.group.id - ); - } - return textsecure.storage.groups.removeNumber( - decrypted.group.id, - source - ); - case textsecure.protobuf.GroupContext.Type.DELIVER: - decrypted.group.name = null; - decrypted.group.members = []; - decrypted.group.avatar = null; - return Promise.resolve(); - default: - this.removeFromCache(envelope); - throw new Error('Unknown group message type'); - } - }) - ); } const attachmentCount = decrypted.attachments.length; @@ -1575,65 +1891,67 @@ MessageReceiver.prototype.extend({ ); } - for (let i = 0; i < attachmentCount; i += 1) { - const attachment = decrypted.attachments[i]; - promises.push(this.handleAttachment(attachment)); - } + // Here we go from binary to string/base64 in all AttachmentPointer digest/key fields - const previewCount = (decrypted.preview || []).length; - for (let i = 0; i < previewCount; i += 1) { - const preview = decrypted.preview[i]; - if (preview.image) { - promises.push(this.handleAttachment(preview.image)); + if ( + decrypted.group && + decrypted.group.type === textsecure.protobuf.GroupContext.Type.UPDATE + ) { + if (decrypted.group.avatar !== null) { + decrypted.group.avatar = this.cleanAttachment(decrypted.group.avatar); } } - if (decrypted.contact && decrypted.contact.length) { - const contacts = decrypted.contact; + decrypted.attachments = (decrypted.attachments || []).map( + this.cleanAttachment.bind(this) + ); + decrypted.preview = (decrypted.preview || []).map(item => { + const { image } = item; - for (let i = 0, max = contacts.length; i < max; i += 1) { - const contact = contacts[i]; - const { avatar } = contact; + if (!image) { + return item; + } - if (avatar && avatar.avatar) { - // We don't want the failure of a thumbnail download to fail the handling of - // this message entirely, like we do for full attachments. - promises.push( - this.handleAttachment(avatar.avatar).catch(error => { - window.log.error( - 'Problem loading avatar for contact', - error && error.stack ? error.stack : error - ); - }) - ); - } + return { + ...item, + image: this.cleanAttachment(image), + }; + }); + decrypted.contact = (decrypted.contact || []).map(item => { + const { avatar } = item; + + if (!avatar || !avatar.avatar) { + return item; } - } + + return { + ...item, + avatar: { + ...item.avatar, + avatar: this.cleanAttachment(item.avatar.avatar), + }, + }; + }); if (decrypted.quote && decrypted.quote.id) { decrypted.quote.id = decrypted.quote.id.toNumber(); } - if (decrypted.quote && decrypted.quote.attachments) { - const { attachments } = decrypted.quote; + if (decrypted.quote) { + decrypted.quote.attachments = (decrypted.quote.attachments || []).map( + item => { + const { thumbnail } = item; - for (let i = 0, max = attachments.length; i < max; i += 1) { - const attachment = attachments[i]; - const { thumbnail } = attachment; + if (!thumbnail) { + return item; + } - if (thumbnail) { - // We don't want the failure of a thumbnail download to fail the handling of - // this message entirely, like we do for full attachments. - promises.push( - this.handleAttachment(thumbnail).catch(error => { - window.log.error( - 'Problem loading thumbnail for quote', - error && error.stack ? error.stack : error - ); - }) - ); + return { + ...item, + thumbnail: this.cleanAttachment(item.thumbnail), + }; } - } + ); } return Promise.all(promises).then(() => decrypted); @@ -1662,11 +1980,19 @@ textsecure.MessageReceiver = function MessageReceiverWrapper( messageReceiver ); this.getStatus = messageReceiver.getStatus.bind(messageReceiver); + this.handleEndSession = messageReceiver.handleEndSession.bind( + messageReceiver + ); this.close = messageReceiver.close.bind(messageReceiver); this.savePreKeyBundleMessage = messageReceiver.savePreKeyBundleMessage.bind( messageReceiver ); + this.downloadAttachment = messageReceiver.downloadAttachment.bind( + messageReceiver + ); + this.stopProcessing = messageReceiver.stopProcessing.bind(messageReceiver); + messageReceiver.connect(); }; diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index f2bb059ef6..5be3b73680 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -6,12 +6,30 @@ libloki, StringView, dcodeIO, - log, lokiMessageAPI, + i18n, */ /* eslint-disable more/no-then */ /* eslint-disable no-unreachable */ +const NUM_SEND_CONNECTIONS = 3; + +const getTTLForType = type => { + switch (type) { + case 'friend-request': + return 4 * 24 * 60 * 60 * 1000; // 4 days for friend request message + case 'device-unpairing': + return 4 * 24 * 60 * 60 * 1000; // 4 days for device unpairing + case 'onlineBroadcast': + return 60 * 1000; // 1 minute for online broadcast message + case 'typing': + return 60 * 1000; // 1 minute for typing indicators + case 'pairing-request': + return 2 * 60 * 1000; // 2 minutes for pairing requests + default: + return (window.getMessageTTL() || 24) * 60 * 60 * 1000; // 1 day default for any other message + } +}; function OutgoingMessage( server, @@ -42,9 +60,24 @@ function OutgoingMessage( this.failoverNumbers = []; this.unidentifiedDeliveries = []; - const { numberInfo, senderCertificate, online, messageType, isPing } = + const { + numberInfo, + senderCertificate, + online, + messageType, + isPing, + isPublic, + publicSendData, + } = options || {}; this.numberInfo = numberInfo; + this.isPublic = isPublic; + this.isGroup = !!( + this.message && + this.message.dataMessage && + this.message.dataMessage.group + ); + this.publicSendData = publicSendData; this.senderCertificate = senderCertificate; this.online = online; this.messageType = messageType || 'outgoing'; @@ -84,19 +117,21 @@ OutgoingMessage.prototype = { this.numberCompleted(); }, reloadDevicesAndSend(number, recurse) { + const ourNumber = textsecure.storage.user.getNumber(); return () => - textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => { - if (deviceIds.length === 0) { - // eslint-disable-next-line no-param-reassign - deviceIds = [1]; - // return this.registerError( - // number, - // 'Got empty device list when loading device keys', - // null - // ); - } - return this.doSendMessage(number, deviceIds, recurse); - }); + libloki.storage + .getAllDevicePubKeysForPrimaryPubKey(number) + // Don't send to ourselves + .then(devicesPubKeys => + devicesPubKeys.filter(pubKey => pubKey !== ourNumber) + ) + .then(devicesPubKeys => { + if (devicesPubKeys.length === 0) { + // eslint-disable-next-line no-param-reassign + devicesPubKeys = [number]; + } + return this.doSendMessage(number, devicesPubKeys, recurse); + }); }, getKeysForNumber(number, updateDevices) { @@ -187,13 +222,16 @@ OutgoingMessage.prototype = { async transmitMessage(number, data, timestamp, ttl = 24 * 60 * 60 * 1000) { const pubKey = number; try { - await lokiMessageAPI.sendMessage( - pubKey, - data, - timestamp, - ttl, - this.isPing - ); + // TODO: Make NUM_CONCURRENT_CONNECTIONS a global constant + const options = { + numConnections: NUM_SEND_CONNECTIONS, + isPing: this.isPing, + }; + options.isPublic = this.isPublic; + if (this.isPublic) { + options.publicSendData = this.publicSendData; + } + await lokiMessageAPI.sendMessage(pubKey, data, timestamp, ttl, options); } catch (e) { if (e.name === 'HTTPError' && (e.code !== 409 && e.code !== 410)) { // 409 and 410 should bubble and be handled by doSendMessage @@ -220,8 +258,7 @@ OutgoingMessage.prototype = { return messagePartCount * 160; }, - convertMessageToText(message) { - const messageBuffer = message.toArrayBuffer(); + convertMessageToText(messageBuffer) { const plaintext = new Uint8Array( this.getPaddedMessageLength(messageBuffer.byteLength + 1) - 1 ); @@ -230,16 +267,19 @@ OutgoingMessage.prototype = { return plaintext; }, - getPlaintext() { - if (!this.plaintext) { - this.plaintext = this.convertMessageToText(this.message); - } - return this.plaintext; + getPlaintext(messageBuffer) { + return this.convertMessageToText(messageBuffer); }, async wrapInWebsocketMessage(outgoingObject) { + const source = + outgoingObject.type === + textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER + ? null + : outgoingObject.ourKey; + const messageEnvelope = new textsecure.protobuf.Envelope({ type: outgoingObject.type, - source: outgoingObject.ourKey, + source, sourceDevice: outgoingObject.sourceDevice, timestamp: this.timestamp, content: outgoingObject.content, @@ -255,125 +295,232 @@ OutgoingMessage.prototype = { request: requestMessage, }); const bytes = new Uint8Array(websocketMessage.encode().toArrayBuffer()); - log.info(bytes.toString()); // print bytes for debugging purposes: can be injected in mock socket server return bytes; }, - doSendMessage(number, deviceIds, recurse) { + doSendMessage(number, devicesPubKeys, recurse) { const ciphers = {}; - - /* Disabled because i'm not sure how senderCertificate works :thinking: - const { numberInfo, senderCertificate } = this; - const info = numberInfo && numberInfo[number] ? numberInfo[number] : {}; - const { accessKey } = info || {}; - - if (accessKey && !senderCertificate) { - return Promise.reject( - new Error( - 'OutgoingMessage.doSendMessage: accessKey was provided, ' + - 'but senderCertificate was not' - ) - ); + if (this.isPublic) { + return this.transmitMessage( + number, + this.message.dataMessage, + this.timestamp, + 0 // ttl + ) + .then(() => { + this.successfulNumbers[this.successfulNumbers.length] = number; + this.numberCompleted(); + }) + .catch(error => { + throw error; + }); } - const sealedSender = Boolean(accessKey && senderCertificate); - - // We don't send to ourselves if unless sealedSender is enabled - const ourNumber = textsecure.storage.user.getNumber(); - const ourDeviceId = textsecure.storage.user.getDeviceId(); - if (number === ourNumber && !sealedSender) { - // eslint-disable-next-line no-param-reassign - deviceIds = _.reject( - deviceIds, - deviceId => - // because we store our own device ID as a string at least sometimes - deviceId === ourDeviceId || deviceId === parseInt(ourDeviceId, 10) - ); - } - */ + this.numbers = devicesPubKeys; return Promise.all( - deviceIds.map(async deviceId => { - const address = new libsignal.SignalProtocolAddress(number, deviceId); + devicesPubKeys.map(async devicePubKey => { + // Session doesn't use the deviceId scheme, it's always 1. + // Instead, there are multiple device public keys. + const deviceId = 1; + const updatedDevices = await this.getStaleDeviceIdsForNumber( + devicePubKey + ); + const keysFound = await this.getKeysForNumber( + devicePubKey, + updatedDevices + ); + let enableFallBackEncryption = !keysFound; + + const address = new libsignal.SignalProtocolAddress( + devicePubKey, + deviceId + ); const ourKey = textsecure.storage.user.getNumber(); const options = {}; - const fallBackCipher = new libloki.crypto.FallBackSessionCipher( - address - ); + + let isMultiDeviceRequest = false; + let thisDeviceMessageType = this.messageType; + if ( + thisDeviceMessageType !== 'pairing-request' && + thisDeviceMessageType !== 'friend-request' + ) { + let conversation; + try { + conversation = ConversationController.get(devicePubKey); + } catch (e) { + // do nothing + } + if ( + conversation && + !conversation.isFriend() && + !conversation.hasReceivedFriendRequest() && + !this.isGroup + ) { + // We want to send an automated friend request if: + // - We aren't already friends + // - We haven't received a friend request from this device + // - We haven't sent a friend request recently + if (conversation.friendRequestTimerIsExpired()) { + isMultiDeviceRequest = true; + thisDeviceMessageType = 'friend-request'; + } else { + // Throttle automated friend requests + this.successfulNumbers.push(devicePubKey); + return null; + } + } + } // Check if we need to attach the preKeys let sessionCipher; - const isFriendRequest = this.messageType === 'friend-request'; + const isFriendRequest = thisDeviceMessageType === 'friend-request'; + enableFallBackEncryption = + enableFallBackEncryption || isFriendRequest || isMultiDeviceRequest; const flags = this.message.dataMessage ? this.message.dataMessage.get_flags() : null; const isEndSession = flags === textsecure.protobuf.DataMessage.Flags.END_SESSION; - if (isFriendRequest || isEndSession) { + const signalCipher = new libsignal.SessionCipher( + textsecure.storage.protocol, + address + ); + if (enableFallBackEncryption || isEndSession) { // Encrypt them with the fallback - const pkb = await libloki.storage.getPreKeyBundleForContact(number); + const pkb = await libloki.storage.getPreKeyBundleForContact( + devicePubKey + ); const preKeyBundleMessage = new textsecure.protobuf.PreKeyBundleMessage( pkb ); this.message.preKeyBundleMessage = preKeyBundleMessage; window.log.info('attaching prekeys to outgoing message'); } - if (isFriendRequest) { - sessionCipher = fallBackCipher; + + let messageBuffer; + if (isMultiDeviceRequest) { + const tempMessage = new textsecure.protobuf.Content(); + const tempDataMessage = new textsecure.protobuf.DataMessage(); + tempDataMessage.body = i18n('secondaryDeviceDefaultFR'); + if (this.message.dataMessage && this.message.dataMessage.profile) { + tempDataMessage.profile = this.message.dataMessage.profile; + } + tempMessage.preKeyBundleMessage = this.message.preKeyBundleMessage; + tempMessage.dataMessage = tempDataMessage; + messageBuffer = tempMessage.toArrayBuffer(); } else { - sessionCipher = new libsignal.SessionCipher( - textsecure.storage.protocol, - address, - options - ); + messageBuffer = this.message.toArrayBuffer(); } - const plaintext = this.getPlaintext(); + + if (enableFallBackEncryption) { + sessionCipher = new libloki.crypto.FallBackSessionCipher(address); + } else { + sessionCipher = signalCipher; + } + const plaintext = this.getPlaintext(messageBuffer); // No limit on message keys if we're communicating with our other devices if (ourKey === number) { options.messageKeysLimit = false; } - ciphers[address.getDeviceId()] = sessionCipher; + let content; + let type; - // Encrypt our plain text - const ciphertext = await sessionCipher.encrypt(plaintext); - if (!this.fallBackEncryption) { - // eslint-disable-next-line no-param-reassign - ciphertext.body = new Uint8Array( - dcodeIO.ByteBuffer.wrap(ciphertext.body, 'binary').toArrayBuffer() + if (window.lokiFeatureFlags.useSealedSender) { + const secretSessionCipher = new window.Signal.Metadata.SecretSessionCipher( + textsecure.storage.protocol ); - } - let ttl; - if (this.messageType === 'friend-request') { - ttl = 4 * 24 * 60 * 60 * 1000; // 4 days for friend request message - } else if (this.messageType === 'onlineBroadcast') { - ttl = 60 * 1000; // 1 minute for online broadcast message + ciphers[address.getDeviceId()] = secretSessionCipher; + + const senderCert = new textsecure.protobuf.SenderCertificate(); + + senderCert.sender = ourKey; + senderCert.senderDevice = deviceId; + + const ciphertext = await secretSessionCipher.encrypt( + address, + senderCert, + plaintext, + sessionCipher + ); + + type = textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER; + content = window.Signal.Crypto.arrayBufferToBase64(ciphertext); } else { - const hours = window.getMessageTTL() || 24; // 1 day default for any other message - ttl = hours * 60 * 60 * 1000; + // TODO: probably remove this branch once + // mobile clients implement sealed sender + ciphers[address.getDeviceId()] = sessionCipher; + + const ciphertext = await sessionCipher.encrypt(plaintext); + if (!enableFallBackEncryption) { + // eslint-disable-next-line no-param-reassign + ciphertext.body = new Uint8Array( + dcodeIO.ByteBuffer.wrap(ciphertext.body, 'binary').toArrayBuffer() + ); + } + + // eslint-disable-next-line prefer-destructuring + type = ciphertext.type; + content = ciphertext.body; } + const ttl = getTTLForType(thisDeviceMessageType); + return { - type: ciphertext.type, // FallBackSessionCipher sets this to FRIEND_REQUEST + type, // FallBackSessionCipher sets this to FRIEND_REQUEST ttl, ourKey, sourceDevice: 1, - destinationRegistrationId: ciphertext.registrationId, - content: ciphertext.body, + content, + pubKey: devicePubKey, }; }) ) .then(async outgoingObjects => { // TODO: handle multiple devices/messages per transmit - const outgoingObject = outgoingObjects[0]; - const socketMessage = await this.wrapInWebsocketMessage(outgoingObject); - await this.transmitMessage( - number, - socketMessage, - this.timestamp, - outgoingObject.ttl - ); - this.successfulNumbers[this.successfulNumbers.length] = number; + const promises = outgoingObjects.map(async outgoingObject => { + if (!outgoingObject) { + return; + } + const destination = outgoingObject.pubKey; + try { + const socketMessage = await this.wrapInWebsocketMessage( + outgoingObject + ); + await this.transmitMessage( + destination, + socketMessage, + this.timestamp, + outgoingObject.ttl + ); + if ( + outgoingObject.type === + textsecure.protobuf.Envelope.Type.FRIEND_REQUEST + ) { + const conversation = ConversationController.get(destination); + if (conversation) { + // Redundant for primary device but marks secondary devices as pending + await conversation.onFriendRequestSent(); + } + } + this.successfulNumbers.push(destination); + } catch (e) { + e.number = destination; + this.errors.push(e); + } + }); + await Promise.all(promises); + // TODO: the retrySend should only send to the devices + // for which the transmission failed. + + // ensure numberCompleted() will execute the callback + this.numbersCompleted += + this.errors.length + this.successfulNumbers.length; + // Absorb errors if message sent to at least 1 device + if (this.successfulNumbers.length > 0) { + this.errors = []; + } this.numberCompleted(); }) .catch(error => { @@ -386,12 +533,13 @@ OutgoingMessage.prototype = { error.name === 'HTTPError' && (error.code === 410 || error.code === 409) ) { - if (!recurse) + if (!recurse) { return this.registerError( number, 'Hit retry limit attempting to reload device list', error ); + } let p; if (error.code === 409) { @@ -428,7 +576,7 @@ OutgoingMessage.prototype = { window.log.error( 'Got "key changed" error from encrypt - no identityKey for application layer', number, - deviceIds + devicesPubKeys ); throw error; } else { @@ -481,36 +629,25 @@ OutgoingMessage.prototype = { } catch (e) { // do nothing } - - return this.getStaleDeviceIdsForNumber(number).then(updateDevices => - this.getKeysForNumber(number, updateDevices) - .then(async keysFound => { - if (!keysFound) { - log.info('Fallback encryption enabled'); - this.fallBackEncryption = true; - } - }) - .then(this.reloadDevicesAndSend(number, true)) - .catch(error => { - conversation.resetPendingSend(); - if (error.message === 'Identity key changed') { - // eslint-disable-next-line no-param-reassign - error = new textsecure.OutgoingIdentityKeyError( - number, - error.originalMessage, - error.timestamp, - error.identityKey - ); - this.registerError(number, 'Identity key changed', error); - } else { - this.registerError( - number, - `Failed to retrieve new device keys for number ${number}`, - error - ); - } - }) - ); + return this.reloadDevicesAndSend(number, true)().catch(error => { + conversation.resetPendingSend(); + if (error.message === 'Identity key changed') { + // eslint-disable-next-line no-param-reassign + error = new textsecure.OutgoingIdentityKeyError( + number, + error.originalMessage, + error.timestamp, + error.identityKey + ); + this.registerError(number, 'Identity key changed', error); + } else { + this.registerError( + number, + `Failed to retrieve new device keys for number ${number}`, + error + ); + } + }); }, }; diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index 882c011ecd..cb1fea2ad4 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -1,4 +1,4 @@ -/* global _, textsecure, WebAPI, libsignal, OutgoingMessage, window */ +/* global _, textsecure, WebAPI, libsignal, OutgoingMessage, window, libloki */ /* eslint-disable more/no-then, no-bitwise */ @@ -27,12 +27,14 @@ function Message(options) { this.expireTimer = options.expireTimer; this.profileKey = options.profileKey; this.profile = options.profile; + this.groupInvitation = options.groupInvitation; + this.sessionRestoration = options.sessionRestoration || false; - if (!(this.recipients instanceof Array) || this.recipients.length < 1) { + if (!(this.recipients instanceof Array)) { throw new Error('Invalid recipient list'); } - if (!this.group && this.recipients.length > 1) { + if (!this.group && this.recipients.length !== 1) { throw new Error('Invalid recipient list for non-group'); } @@ -143,10 +145,35 @@ Message.prototype = { proto.profileKey = this.profileKey; } - if (this.profile && this.profile.name) { - const contact = new textsecure.protobuf.DataMessage.Contact(); - contact.name = this.profile.name; - proto.profile = contact; + // Set the loki profile + if (this.profile) { + const profile = new textsecure.protobuf.DataMessage.LokiProfile(); + if (this.profile.displayName) { + profile.displayName = this.profile.displayName; + } + + const conversation = window.ConversationController.get( + textsecure.storage.user.getNumber() + ); + const avatarPointer = conversation.get('avatarPointer'); + if (avatarPointer) { + profile.avatar = avatarPointer; + } + proto.profile = profile; + } + + if (this.groupInvitation) { + proto.groupInvitation = new textsecure.protobuf.DataMessage.GroupInvitation( + { + serverAddress: this.groupInvitation.serverAddress, + channelId: this.groupInvitation.channelId, + serverName: this.groupInvitation.serverName, + } + ); + } + + if (this.sessionRestoration) { + proto.flags = textsecure.protobuf.DataMessage.Flags.SESSION_RESTORE; } this.dataMessage = proto; @@ -166,7 +193,8 @@ MessageSender.prototype = { constructor: MessageSender, // makeAttachmentPointer :: Attachment -> Promise AttachmentPointerProto - makeAttachmentPointer(attachment) { + async makeAttachmentPointer(attachment, publicServer = null, options = {}) { + const { isRaw = false, isAvatar = false } = options; if (typeof attachment !== 'object' || attachment == null) { return Promise.resolve(undefined); } @@ -183,39 +211,57 @@ MessageSender.prototype = { } const proto = new textsecure.protobuf.AttachmentPointer(); - proto.key = libsignal.crypto.getRandomBytes(64); - - const iv = libsignal.crypto.getRandomBytes(16); - return textsecure.crypto - .encryptAttachment(attachment.data, proto.key, iv) - .then(result => - this.server.putAttachment(result.ciphertext).then(id => { - proto.id = id; - proto.contentType = attachment.contentType; - proto.digest = result.digest; - - if (attachment.size) { - proto.size = attachment.size; - } - if (attachment.fileName) { - proto.fileName = attachment.fileName; - } - if (attachment.flags) { - proto.flags = attachment.flags; - } - if (attachment.width) { - proto.width = attachment.width; - } - if (attachment.height) { - proto.height = attachment.height; - } - if (attachment.caption) { - proto.caption = attachment.caption; - } - - return proto; - }) + let attachmentData; + const server = publicServer || this.server; + + if (publicServer || isRaw) { + attachmentData = attachment.data; + } else { + proto.key = libsignal.crypto.getRandomBytes(64); + const iv = libsignal.crypto.getRandomBytes(16); + const result = await textsecure.crypto.encryptAttachment( + attachment.data, + proto.key, + iv ); + proto.digest = result.digest; + attachmentData = result.ciphertext; + } + + const result = isAvatar + ? await server.putAvatar(attachmentData) + : await server.putAttachment(attachmentData); + + if (!result) { + return Promise.reject( + new Error('Failed to upload data to attachment fileserver') + ); + } + const { url, id } = result; + proto.id = id; + proto.url = url; + proto.contentType = attachment.contentType; + + if (attachment.size) { + proto.size = attachment.size; + } + if (attachment.fileName) { + proto.fileName = attachment.fileName; + } + if (attachment.flags) { + proto.flags = attachment.flags; + } + if (attachment.width) { + proto.width = attachment.width; + } + if (attachment.height) { + proto.height = attachment.height; + } + if (attachment.caption) { + proto.caption = attachment.caption; + } + + return proto; }, queueJobForNumber(number, runJob) { @@ -238,9 +284,11 @@ MessageSender.prototype = { }); }, - uploadAttachments(message) { + uploadAttachments(message, publicServer) { return Promise.all( - message.attachments.map(this.makeAttachmentPointer.bind(this)) + message.attachments.map(attachment => + this.makeAttachmentPointer(attachment, publicServer) + ) ) .then(attachmentPointers => { // eslint-disable-next-line no-param-reassign @@ -255,12 +303,12 @@ MessageSender.prototype = { }); }, - async uploadLinkPreviews(message) { + async uploadLinkPreviews(message, publicServer) { try { const preview = await Promise.all( (message.preview || []).map(async item => ({ ...item, - image: await this.makeAttachmentPointer(item.image), + image: await this.makeAttachmentPointer(item.image, publicServer), })) ); // eslint-disable-next-line no-param-reassign @@ -274,7 +322,7 @@ MessageSender.prototype = { } }, - uploadThumbnails(message) { + uploadThumbnails(message, publicServer) { const makePointer = this.makeAttachmentPointer.bind(this); const { quote } = message; @@ -289,7 +337,7 @@ MessageSender.prototype = { return null; } - return makePointer(thumbnail).then(pointer => { + return makePointer(thumbnail, publicServer).then(pointer => { // eslint-disable-next-line no-param-reassign attachment.attachmentPointer = pointer; }); @@ -306,19 +354,13 @@ MessageSender.prototype = { sendMessage(attrs, options) { const message = new Message(attrs); const silent = false; - - // Remove this when we add support for attachments - message.attachments = []; - message.attachmentPointers = []; - message.preview = []; - if (message.quote) { - message.quote.attachments = []; - } + const publicServer = + options.publicSendData && options.publicSendData.serverAPI; return Promise.all([ - this.uploadAttachments(message), - this.uploadThumbnails(message), - this.uploadLinkPreviews(message), + this.uploadAttachments(message, publicServer), + this.uploadThumbnails(message, publicServer), + this.uploadLinkPreviews(message, publicServer), ]).then( () => new Promise((resolve, reject) => { @@ -367,8 +409,41 @@ MessageSender.prototype = { options ); + const ourNumber = textsecure.storage.user.getNumber(); + numbers.forEach(number => { - this.queueJobForNumber(number, () => outgoing.sendToNumber(number)); + // Note: if we are sending a private group message, we do our best to + // ensure we have signal protocol sessions with every member, but if we + // fail, let's at least send messages to those members with which we do: + const haveSession = _.some( + textsecure.storage.protocol.sessions, + s => s.number === number + ); + + if ( + number === ourNumber || + haveSession || + options.isPublic || + options.messageType === 'friend-request' + ) { + this.queueJobForNumber(number, () => outgoing.sendToNumber(number)); + } else { + window.log.error(`No session for number: ${number}`); + // If it was a message to a group then we need to send a session request + if (outgoing.isGroup) { + this.sendMessageToNumber( + number, + '(If you see this message, you must be using an out-of-date client)', + [], + undefined, + [], + Date.now(), + undefined, + undefined, + { messageType: 'friend-request', sessionRequest: true } + ); + } + } }); }, @@ -426,7 +501,7 @@ MessageSender.prototype = { return syncMessage; }, - sendSyncMessage( + async sendSyncMessage( encodedDataMessage, timestamp, destination, @@ -435,10 +510,16 @@ MessageSender.prototype = { unidentifiedDeliveries = [], options ) { - const myNumber = textsecure.storage.user.getNumber(); - const myDevice = textsecure.storage.user.getDeviceId(); - if (myDevice === 1 || myDevice === '1') { - return Promise.resolve(); + const primaryDeviceKey = + window.storage.get('primaryDevicePubKey') || + textsecure.storage.user.getNumber(); + const allOurDevices = (await libloki.storage.getAllDevicePubKeysForPrimaryPubKey( + primaryDeviceKey + )) + // Don't send to ourselves + .filter(pubKey => pubKey !== textsecure.storage.user.getNumber()); + if (allOurDevices.length === 0) { + return null; } const dataMessage = textsecure.protobuf.DataMessage.decode( @@ -481,7 +562,7 @@ MessageSender.prototype = { const silent = true; return this.sendIndividualProto( - myNumber, + primaryDeviceKey, contentMessage, Date.now(), silent, @@ -501,6 +582,15 @@ MessageSender.prototype = { return this.server.getAvatar(path); }, + uploadAvatar(attachment) { + // isRaw is true since the data is already encrypted + // and doesn't need to be encrypted again + return this.makeAttachmentPointer(attachment, null, { + isRaw: true, + isAvatar: true, + }); + }, + sendRequestConfigurationSyncMessage(options) { const myNumber = textsecure.storage.user.getNumber(); const myDevice = textsecure.storage.user.getDeviceId(); @@ -549,6 +639,38 @@ MessageSender.prototype = { return Promise.resolve(); }, + async sendContactSyncMessage(contactConversation) { + const primaryDeviceKey = window.storage.get('primaryDevicePubKey'); + const allOurDevices = (await libloki.storage.getAllDevicePubKeysForPrimaryPubKey( + primaryDeviceKey + )) + // Don't send to ourselves + .filter(pubKey => pubKey !== textsecure.storage.user.getNumber()); + if ( + allOurDevices.includes(contactConversation.id) || + !primaryDeviceKey || + allOurDevices.length === 0 + ) { + // If we havn't got a primaryDeviceKey then we are in the middle of pairing + return Promise.resolve(); + } + + const syncMessage = await libloki.api.createContactSyncProtoMessage([ + contactConversation, + ]); + const contentMessage = new textsecure.protobuf.Content(); + contentMessage.syncMessage = syncMessage; + + const silent = true; + return this.sendIndividualProto( + primaryDeviceKey, + contentMessage, + Date.now(), + silent, + {} // options + ); + }, + sendRequestContactSyncMessage(options) { const myNumber = textsecure.storage.user.getNumber(); const myDevice = textsecure.storage.user.getDeviceId(); @@ -575,12 +697,13 @@ MessageSender.prototype = { async sendTypingMessage(options = {}, sendOptions = {}) { const ACTION_ENUM = textsecure.protobuf.TypingMessage.Action; - const { recipientId, groupId, isTyping, timestamp } = options; + const { recipientId, groupId, groupNumbers, isTyping, timestamp } = options; // We don't want to send typing messages to our other devices, but we will // in the group case. const myNumber = textsecure.storage.user.getNumber(); - if (recipientId && myNumber === recipientId) { + const primaryDevicePubkey = window.storage.get('primaryDevicePubKey'); + if (recipientId && primaryDevicePubkey === recipientId) { return null; } @@ -589,7 +712,7 @@ MessageSender.prototype = { } const recipients = groupId - ? _.without(await textsecure.storage.groups.getNumbers(groupId), myNumber) + ? _.without(groupNumbers, myNumber) : [recipientId]; const groupIdBuffer = groupId ? window.Signal.Crypto.fromEncodedBinaryToArrayBuffer(groupId) @@ -749,7 +872,13 @@ MessageSender.prototype = { const me = textsecure.storage.user.getNumber(); const numbers = providedNumbers.filter(number => number !== me); if (numbers.length === 0) { - return Promise.reject(new Error('No other members in the group')); + return Promise.resolve({ + successfulNumbers: [], + failoverNumbers: [], + errors: [], + unidentifiedDeliveries: [], + dataMessage: proto.toArrayBuffer(), + }); } return new Promise((resolve, reject) => { @@ -774,7 +903,57 @@ MessageSender.prototype = { }); }, - sendMessageToNumber( + async getMessageProto( + number, + body, + attachments, + quote, + preview, + timestamp, + expireTimer, + profileKey, + flags + ) { + const attributes = { + recipients: [number], + body, + timestamp, + attachments, + quote, + preview, + expireTimer, + profileKey, + flags, + }; + + return this.getMessageProtoObj(attributes); + }, + + async getMessageProtoObj(attributes) { + const message = new Message(attributes); + await Promise.all([ + this.uploadAttachments(message), + this.uploadThumbnails(message), + this.uploadLinkPreviews(message), + ]); + + return message.toArrayBuffer(); + }, + + getOurProfile() { + try { + // Secondary devices have their profile stored + // in their primary device's conversation + const ourNumber = window.storage.get('primaryDevicePubKey'); + const conversation = window.ConversationController.get(ourNumber); + return conversation.getLokiProfile(); + } catch (e) { + window.log.error(`Failed to get our profile: ${e}`); + return null; + } + }, + + async sendMessageToNumber( number, messageText, attachments, @@ -785,7 +964,14 @@ MessageSender.prototype = { profileKey, options ) { - const profile = textsecure.storage.impl.getLocalProfile(); + const profile = this.getOurProfile(); + + const flags = options.sessionRequest + ? textsecure.protobuf.DataMessage.Flags.SESSION_REQUEST + : undefined; + + const { groupInvitation, sessionRestoration } = options; + return this.sendMessage( { recipients: [number], @@ -798,6 +984,9 @@ MessageSender.prototype = { expireTimer, profileKey, profile, + flags, + groupInvitation, + sessionRestoration, }, options ); @@ -843,7 +1032,7 @@ MessageSender.prototype = { ) ); - const sendToContact = deleteAllSessions(number) + const sendToContactPromise = deleteAllSessions(number) .catch(logError('resetSession/deleteAllSessions1 error:')) .then(() => { window.log.info( @@ -863,8 +1052,14 @@ MessageSender.prototype = { ) ); + const myNumber = textsecure.storage.user.getNumber(); + // We already sent the reset session to our other devices in the code above! + if (number === myNumber) { + return sendToContactPromise; + } + const buffer = proto.toArrayBuffer(); - const sendSync = this.sendSyncMessage( + const sendSyncPromise = this.sendSyncMessage( buffer, timestamp, number, @@ -874,12 +1069,13 @@ MessageSender.prototype = { options ).catch(logError('resetSession/sendSync error:')); - return Promise.all([sendToContact, sendSync]); + return Promise.all([sendToContact, sendSyncPromise]); */ }, - sendMessageToGroup( + async sendMessageToGroup( groupId, + groupNumbers, messageText, attachments, quote, @@ -889,183 +1085,138 @@ MessageSender.prototype = { profileKey, options ) { - return textsecure.storage.groups.getNumbers(groupId).then(targetNumbers => { - if (targetNumbers === undefined) { - return Promise.reject(new Error('Unknown Group')); - } - - const me = textsecure.storage.user.getNumber(); - const numbers = targetNumbers.filter(number => number !== me); - if (numbers.length === 0) { - return Promise.reject(new Error('No other members in the group')); - } - - return this.sendMessage( - { - recipients: numbers, - body: messageText, - timestamp, - attachments, - quote, - preview, - needsSync: true, - expireTimer, - profileKey, - group: { - id: groupId, - type: textsecure.protobuf.GroupContext.Type.DELIVER, - }, - }, - options - ); - }); - }, - - createGroup(targetNumbers, name, avatar, options) { - const proto = new textsecure.protobuf.DataMessage(); - proto.group = new textsecure.protobuf.GroupContext(); - - return textsecure.storage.groups - .createNewGroup(targetNumbers) - .then(group => { - proto.group.id = stringToArrayBuffer(group.id); - const { numbers } = group; - - proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; - proto.group.members = numbers; - proto.group.name = name; + const me = textsecure.storage.user.getNumber(); + let numbers = groupNumbers.filter(number => number !== me); + if (options.isPublic) { + numbers = [groupId]; + } + const profile = this.getOurProfile(); + const attrs = { + recipients: numbers, + body: messageText, + timestamp, + attachments, + quote, + preview, + needsSync: true, + expireTimer, + profileKey, + profile, + group: { + id: groupId, + type: textsecure.protobuf.GroupContext.Type.DELIVER, + }, + }; - return this.makeAttachmentPointer(avatar).then(attachment => { - proto.group.avatar = attachment; - return this.sendGroupProto(numbers, proto, Date.now(), options).then( - () => proto.group.id - ); - }); + if (numbers.length === 0) { + return Promise.resolve({ + successfulNumbers: [], + failoverNumbers: [], + errors: [], + unidentifiedDeliveries: [], + dataMessage: await this.getMessageProtoObj(attrs), }); + } + + return this.sendMessage(attrs, options); }, - updateGroup(groupId, name, avatar, targetNumbers, options) { + updateGroup(groupId, name, avatar, members, recipients, options) { const proto = new textsecure.protobuf.DataMessage(); proto.group = new textsecure.protobuf.GroupContext(); proto.group.id = stringToArrayBuffer(groupId); proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; proto.group.name = name; + proto.group.members = members; - return textsecure.storage.groups - .addNumbers(groupId, targetNumbers) - .then(numbers => { - if (numbers === undefined) { - return Promise.reject(new Error('Unknown Group')); - } - proto.group.members = numbers; + const ourPK = textsecure.storage.user.getNumber(); + proto.group.admins = [ourPK]; - return this.makeAttachmentPointer(avatar).then(attachment => { - proto.group.avatar = attachment; - return this.sendGroupProto(numbers, proto, Date.now(), options).then( - () => proto.group.id - ); - }); - }); + return this.makeAttachmentPointer(avatar).then(attachment => { + proto.group.avatar = attachment; + // TODO: re-enable this once we have attachments + proto.group.avatar = null; + return this.sendGroupProto(recipients, proto, Date.now(), options).then( + () => proto.group.id + ); + }); }, - addNumberToGroup(groupId, number, options) { + addNumberToGroup(groupId, newNumbers, options) { const proto = new textsecure.protobuf.DataMessage(); proto.group = new textsecure.protobuf.GroupContext(); proto.group.id = stringToArrayBuffer(groupId); proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; - - return textsecure.storage.groups - .addNumbers(groupId, [number]) - .then(numbers => { - if (numbers === undefined) - return Promise.reject(new Error('Unknown Group')); - proto.group.members = numbers; - - return this.sendGroupProto(numbers, proto, Date.now(), options); - }); + proto.group.members = newNumbers; + return this.sendGroupProto(newNumbers, proto, Date.now(), options); }, - setGroupName(groupId, name, options) { + setGroupName(groupId, name, groupNumbers, options) { const proto = new textsecure.protobuf.DataMessage(); proto.group = new textsecure.protobuf.GroupContext(); proto.group.id = stringToArrayBuffer(groupId); proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; proto.group.name = name; + proto.group.members = groupNumbers; - return textsecure.storage.groups.getNumbers(groupId).then(numbers => { - if (numbers === undefined) - return Promise.reject(new Error('Unknown Group')); - proto.group.members = numbers; - - return this.sendGroupProto(numbers, proto, Date.now(), options); - }); + return this.sendGroupProto(groupNumbers, proto, Date.now(), options); }, - setGroupAvatar(groupId, avatar, options) { + setGroupAvatar(groupId, avatar, groupNumbers, options) { const proto = new textsecure.protobuf.DataMessage(); proto.group = new textsecure.protobuf.GroupContext(); proto.group.id = stringToArrayBuffer(groupId); proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE; + proto.group.members = groupNumbers; - return textsecure.storage.groups.getNumbers(groupId).then(numbers => { - if (numbers === undefined) - return Promise.reject(new Error('Unknown Group')); - proto.group.members = numbers; - - return this.makeAttachmentPointer(avatar).then(attachment => { - proto.group.avatar = attachment; - return this.sendGroupProto(numbers, proto, Date.now(), options); - }); + return this.makeAttachmentPointer(avatar).then(attachment => { + proto.group.avatar = attachment; + return this.sendGroupProto(groupNumbers, proto, Date.now(), options); }); }, - leaveGroup(groupId, options) { + leaveGroup(groupId, groupNumbers, options) { const proto = new textsecure.protobuf.DataMessage(); proto.group = new textsecure.protobuf.GroupContext(); proto.group.id = stringToArrayBuffer(groupId); proto.group.type = textsecure.protobuf.GroupContext.Type.QUIT; - - return textsecure.storage.groups.getNumbers(groupId).then(numbers => { - if (numbers === undefined) - return Promise.reject(new Error('Unknown Group')); - return textsecure.storage.groups - .deleteGroup(groupId) - .then(() => this.sendGroupProto(numbers, proto, Date.now(), options)); - }); + return this.sendGroupProto(groupNumbers, proto, Date.now(), options); }, - sendExpirationTimerUpdateToGroup( + async sendExpirationTimerUpdateToGroup( groupId, + groupNumbers, expireTimer, timestamp, profileKey, options ) { - return textsecure.storage.groups.getNumbers(groupId).then(targetNumbers => { - if (targetNumbers === undefined) - return Promise.reject(new Error('Unknown Group')); - - const me = textsecure.storage.user.getNumber(); - const numbers = targetNumbers.filter(number => number !== me); - if (numbers.length === 0) { - return Promise.reject(new Error('No other members in the group')); - } - return this.sendMessage( - { - recipients: numbers, - timestamp, - needsSync: true, - expireTimer, - profileKey, - flags: textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE, - group: { - id: groupId, - type: textsecure.protobuf.GroupContext.Type.DELIVER, - }, - }, - options - ); - }); + const me = textsecure.storage.user.getNumber(); + const numbers = groupNumbers.filter(number => number !== me); + const attrs = { + recipients: numbers, + timestamp, + needsSync: true, + expireTimer, + profileKey, + flags: textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE, + group: { + id: groupId, + type: textsecure.protobuf.GroupContext.Type.DELIVER, + }, + }; + + if (numbers.length === 0) { + return Promise.resolve({ + successfulNumbers: [], + failoverNumbers: [], + errors: [], + unidentifiedDeliveries: [], + dataMessage: await this.getMessageProtoObj(attrs), + }); + } + + return this.sendMessage(attrs, options); }, sendExpirationTimerUpdateToNumber( number, @@ -1096,13 +1247,8 @@ MessageSender.prototype = { window.textsecure = window.textsecure || {}; -textsecure.MessageSender = function MessageSenderWrapper( - url, - username, - password, - cdnUrl -) { - const sender = new MessageSender(url, username, password, cdnUrl); +textsecure.MessageSender = function MessageSenderWrapper(username, password) { + const sender = new MessageSender(username, password); this.sendExpirationTimerUpdateToNumber = sender.sendExpirationTimerUpdateToNumber.bind( sender @@ -1116,6 +1262,7 @@ textsecure.MessageSender = function MessageSenderWrapper( this.sendRequestContactSyncMessage = sender.sendRequestContactSyncMessage.bind( sender ); + this.sendContactSyncMessage = sender.sendContactSyncMessage.bind(sender); this.sendRequestConfigurationSyncMessage = sender.sendRequestConfigurationSyncMessage.bind( sender ); @@ -1124,7 +1271,6 @@ textsecure.MessageSender = function MessageSenderWrapper( this.resetSession = sender.resetSession.bind(sender); this.sendMessageToGroup = sender.sendMessageToGroup.bind(sender); this.sendTypingMessage = sender.sendTypingMessage.bind(sender); - this.createGroup = sender.createGroup.bind(sender); this.updateGroup = sender.updateGroup.bind(sender); this.addNumberToGroup = sender.addNumberToGroup.bind(sender); this.setGroupName = sender.setGroupName.bind(sender); @@ -1133,12 +1279,14 @@ textsecure.MessageSender = function MessageSenderWrapper( this.sendSyncMessage = sender.sendSyncMessage.bind(sender); this.getProfile = sender.getProfile.bind(sender); this.getAvatar = sender.getAvatar.bind(sender); + this.uploadAvatar = sender.uploadAvatar.bind(sender); this.syncReadMessages = sender.syncReadMessages.bind(sender); this.syncVerification = sender.syncVerification.bind(sender); this.sendDeliveryReceipt = sender.sendDeliveryReceipt.bind(sender); this.sendReadReceipts = sender.sendReadReceipts.bind(sender); this.makeProxiedRequest = sender.makeProxiedRequest.bind(sender); this.getProxiedSize = sender.getProxiedSize.bind(sender); + this.getMessageProto = sender.getMessageProto.bind(sender); }; textsecure.MessageSender.prototype = { diff --git a/libtextsecure/storage.js b/libtextsecure/storage.js index dff324cbf3..d59b83e276 100644 --- a/libtextsecure/storage.js +++ b/libtextsecure/storage.js @@ -14,13 +14,17 @@ *** Base Storage Routines *** **************************** */ put(key, value) { - if (value === undefined) throw new Error('Tried to store undefined'); + if (value === undefined) { + throw new Error('Tried to store undefined'); + } localStorage.setItem(`${key}`, textsecure.utils.jsonThing(value)); }, get(key, defaultValue) { const value = localStorage.getItem(`${key}`); - if (value === null) return defaultValue; + if (value === null) { + return defaultValue; + } return JSON.parse(value); }, diff --git a/libtextsecure/storage/groups.js b/libtextsecure/storage/groups.js deleted file mode 100644 index 67460c15e2..0000000000 --- a/libtextsecure/storage/groups.js +++ /dev/null @@ -1,160 +0,0 @@ -/* global window, getString, libsignal, textsecure */ - -/* eslint-disable more/no-then */ - -// eslint-disable-next-line func-names -(function() { - /** ******************* - *** Group Storage *** - ******************** */ - window.textsecure = window.textsecure || {}; - window.textsecure.storage = window.textsecure.storage || {}; - - // create a random group id that we haven't seen before. - function generateNewGroupId() { - const groupId = getString(libsignal.crypto.getRandomBytes(16)); - return textsecure.storage.protocol.getGroup(groupId).then(group => { - if (group === undefined) { - return groupId; - } - window.log.warn('group id collision'); // probably a bad sign. - return generateNewGroupId(); - }); - } - - window.textsecure.storage.groups = { - createNewGroup(numbers, groupId) { - return new Promise(resolve => { - if (groupId !== undefined) { - resolve( - textsecure.storage.protocol.getGroup(groupId).then(group => { - if (group !== undefined) { - throw new Error('Tried to recreate group'); - } - }) - ); - } else { - resolve( - generateNewGroupId().then(newGroupId => { - // eslint-disable-next-line no-param-reassign - groupId = newGroupId; - }) - ); - } - }).then(() => { - const me = textsecure.storage.user.getNumber(); - let haveMe = false; - const finalNumbers = []; - // eslint-disable-next-line no-restricted-syntax, guard-for-in - for (const i in numbers) { - const number = numbers[i]; - if (!textsecure.utils.isNumberSane(number)) - throw new Error('Invalid number in group'); - if (number === me) haveMe = true; - if (finalNumbers.indexOf(number) < 0) finalNumbers.push(number); - } - - if (!haveMe) finalNumbers.push(me); - - const groupObject = { - numbers: finalNumbers, - numberRegistrationIds: {}, - }; - // eslint-disable-next-line no-restricted-syntax, guard-for-in - for (const i in finalNumbers) { - groupObject.numberRegistrationIds[finalNumbers[i]] = {}; - } - - return textsecure.storage.protocol - .putGroup(groupId, groupObject) - .then(() => ({ id: groupId, numbers: finalNumbers })); - }); - }, - - getNumbers(groupId) { - return textsecure.storage.protocol.getGroup(groupId).then(group => { - if (!group) { - return undefined; - } - - return group.numbers; - }); - }, - - removeNumber(groupId, number) { - return textsecure.storage.protocol.getGroup(groupId).then(group => { - if (group === undefined) return undefined; - - const me = textsecure.storage.user.getNumber(); - if (number === me) - throw new Error( - 'Cannot remove ourselves from a group, leave the group instead' - ); - - const i = group.numbers.indexOf(number); - if (i > -1) { - group.numbers.splice(i, 1); - // eslint-disable-next-line no-param-reassign - delete group.numberRegistrationIds[number]; - return textsecure.storage.protocol - .putGroup(groupId, group) - .then(() => group.numbers); - } - - return group.numbers; - }); - }, - - addNumbers(groupId, numbers) { - return textsecure.storage.protocol.getGroup(groupId).then(group => { - if (group === undefined) return undefined; - - // eslint-disable-next-line no-restricted-syntax, guard-for-in - for (const i in numbers) { - const number = numbers[i]; - if (!textsecure.utils.isNumberSane(number)) - throw new Error('Invalid number in set to add to group'); - if (group.numbers.indexOf(number) < 0) { - group.numbers.push(number); - // eslint-disable-next-line no-param-reassign - group.numberRegistrationIds[number] = {}; - } - } - - return textsecure.storage.protocol - .putGroup(groupId, group) - .then(() => group.numbers); - }); - }, - - deleteGroup(groupId) { - return textsecure.storage.protocol.removeGroup(groupId); - }, - - getGroup(groupId) { - return textsecure.storage.protocol.getGroup(groupId).then(group => { - if (group === undefined) return undefined; - - return { id: groupId, numbers: group.numbers }; - }); - }, - - updateNumbers(groupId, numbers) { - return textsecure.storage.protocol.getGroup(groupId).then(group => { - if (group === undefined) - throw new Error('Tried to update numbers for unknown group'); - - if ( - numbers.filter(textsecure.utils.isNumberSane).length < numbers.length - ) - throw new Error('Invalid number in new group members'); - - const added = numbers.filter( - number => group.numbers.indexOf(number) < 0 - ); - - return textsecure.storage.groups.addNumbers(groupId, added); - }); - }, - }; -})(); diff --git a/libtextsecure/storage/unprocessed.js b/libtextsecure/storage/unprocessed.js index 91bc0e255e..ac113362f7 100644 --- a/libtextsecure/storage/unprocessed.js +++ b/libtextsecure/storage/unprocessed.js @@ -21,8 +21,14 @@ add(data) { return textsecure.storage.protocol.addUnprocessed(data); }, - save(data) { - return textsecure.storage.protocol.saveUnprocessed(data); + updateAttempts(id, attempts) { + return textsecure.storage.protocol.updateUnprocessedAttempts( + id, + attempts + ); + }, + addDecryptedData(id, data) { + return textsecure.storage.protocol.updateUnprocessedWithData(id, data); }, remove(id) { return textsecure.storage.protocol.removeUnprocessed(id); diff --git a/libtextsecure/storage/user.js b/libtextsecure/storage/user.js index b6330acf87..3e17b80af7 100644 --- a/libtextsecure/storage/user.js +++ b/libtextsecure/storage/user.js @@ -18,13 +18,17 @@ getNumber() { const numberId = textsecure.storage.get('number_id'); - if (numberId === undefined) return undefined; + if (numberId === undefined) { + return undefined; + } return textsecure.utils.unencodeNumber(numberId)[0]; }, getDeviceId() { const numberId = textsecure.storage.get('number_id'); - if (numberId === undefined) return undefined; + if (numberId === undefined) { + return undefined; + } return textsecure.utils.unencodeNumber(numberId)[1]; }, diff --git a/libtextsecure/test/_test.js b/libtextsecure/test/_test.js index b18a48ddae..a77e132e93 100644 --- a/libtextsecure/test/_test.js +++ b/libtextsecure/test/_test.js @@ -51,8 +51,9 @@ window.assertEqualArrayBuffers = (ab1, ab2) => { window.hexToArrayBuffer = str => { const ret = new ArrayBuffer(str.length / 2); const array = new Uint8Array(ret); - for (let i = 0; i < str.length / 2; i += 1) + for (let i = 0; i < str.length / 2; i += 1) { array[i] = parseInt(str.substr(i * 2, 2), 16); + } return ret; }; diff --git a/libtextsecure/test/contacts_parser_test.js b/libtextsecure/test/contacts_parser_test.js index be09e2bfd0..16f70254c2 100644 --- a/libtextsecure/test/contacts_parser_test.js +++ b/libtextsecure/test/contacts_parser_test.js @@ -18,7 +18,7 @@ describe('ContactBuffer', () => { const contactInfoBuffer = contactInfo.encode().toArrayBuffer(); for (let i = 0; i < 3; i += 1) { - buffer.writeVarint32(contactInfoBuffer.byteLength); + buffer.writeInt32(contactInfoBuffer.byteLength); buffer.append(contactInfoBuffer); buffer.append(avatarBuffer.clone()); } @@ -69,7 +69,7 @@ describe('GroupBuffer', () => { const groupInfoBuffer = groupInfo.encode().toArrayBuffer(); for (let i = 0; i < 3; i += 1) { - buffer.writeVarint32(groupInfoBuffer.byteLength); + buffer.writeInt32(groupInfoBuffer.byteLength); buffer.append(groupInfoBuffer); buffer.append(avatarBuffer.clone()); } diff --git a/libtextsecure/test/fake_web_api.js b/libtextsecure/test/fake_web_api.js index 74959fe30d..29e2a932f4 100644 --- a/libtextsecure/test/fake_web_api.js +++ b/libtextsecure/test/fake_web_api.js @@ -42,8 +42,9 @@ const fakeAPI = { msg.timestamp === undefined || msg.relay !== undefined || msg.destination !== undefined - ) + ) { throw new Error('Invalid message'); + } messagesSentMap[ `${destination}.${messageArray[i].destinationDeviceId}` diff --git a/libtextsecure/test/in_memory_signal_protocol_store.js b/libtextsecure/test/in_memory_signal_protocol_store.js index dcdb0d01ae..a594a6d79f 100644 --- a/libtextsecure/test/in_memory_signal_protocol_store.js +++ b/libtextsecure/test/in_memory_signal_protocol_store.js @@ -16,21 +16,24 @@ SignalProtocolStore.prototype = { value === undefined || key === null || value === null - ) + ) { throw new Error('Tried to store undefined/null'); + } this.store[key] = value; }, get(key, defaultValue) { - if (key === null || key === undefined) + if (key === null || key === undefined) { throw new Error('Tried to get value for undefined/null key'); + } if (key in this.store) { return this.store[key]; } return defaultValue; }, remove(key) { - if (key === null || key === undefined) + if (key === null || key === undefined) { throw new Error('Tried to remove value for undefined/null key'); + } delete this.store[key]; }, @@ -48,15 +51,17 @@ SignalProtocolStore.prototype = { return Promise.resolve(identityKey === trusted); }, loadIdentityKey(identifier) { - if (identifier === null || identifier === undefined) + if (identifier === null || identifier === undefined) { throw new Error('Tried to get identity key for undefined/null key'); + } return new Promise(resolve => { resolve(this.get(`identityKey${identifier}`)); }); }, saveIdentity(identifier, identityKey) { - if (identifier === null || identifier === undefined) + if (identifier === null || identifier === undefined) { throw new Error('Tried to put identity key for undefined/null key'); + } return new Promise(resolve => { const existing = this.get(`identityKey${identifier}`); this.put(`identityKey${identifier}`, identityKey); @@ -166,7 +171,9 @@ SignalProtocolStore.prototype = { async loadPreKeyForContact(contactPubKey) { return new Promise(resolve => { const key = this.get(`25519KeypreKey${contactPubKey}`); - if (!key) resolve(undefined); + if (!key) { + resolve(undefined); + } resolve({ pubKey: key.publicKey, privKey: key.privateKey, diff --git a/main.js b/main.js index fa45787694..2eb625db86 100644 --- a/main.js +++ b/main.js @@ -14,6 +14,7 @@ const packageJson = require('./package.json'); const GlobalErrors = require('./app/global_errors'); GlobalErrors.addHandler(); +const electronLocalshortcut = require('electron-localshortcut'); const getRealPath = pify(fs.realpath); const { @@ -63,8 +64,11 @@ const appInstance = config.util.getEnv('NODE_APP_INSTANCE') || 0; // data directory has been set. const attachments = require('./app/attachments'); const attachmentChannel = require('./app/attachment_channel'); -// TODO: remove or restore when appropriate -// const autoUpdate = require('./app/auto_update'); + +// TODO: Enable when needed +// const updater = require('./ts/updater/index'); +const updater = null; + const createTrayIcon = require('./app/tray_icon'); const ephemeralConfig = require('./app/ephemeral_config'); const logging = require('./app/logging'); @@ -101,21 +105,25 @@ function showWindow() { if (!process.mas) { console.log('making app single instance'); - const shouldQuit = app.makeSingleInstance(() => { - // Someone tried to run a second instance, we should focus our window - if (mainWindow) { - if (mainWindow.isMinimized()) { - mainWindow.restore(); - } - - showWindow(); + const gotLock = app.requestSingleInstanceLock(); + if (!gotLock) { + // Don't allow second instance if we are in prod + if (appInstance === 0) { + console.log('quitting; we are the second instance'); + app.exit(); } - return true; - }); + } else { + app.on('second-instance', () => { + // Someone tried to run a second instance, we should focus our window + if (mainWindow) { + if (mainWindow.isMinimized()) { + mainWindow.restore(); + } - if (appInstance === 0 && shouldQuit) { - console.log('quitting; we are the second instance'); - app.exit(); + showWindow(); + } + return true; + }); } } @@ -147,8 +155,9 @@ function prepareURL(pathSegments, moreKeys) { serverUrl: config.get('serverUrl'), localUrl: config.get('localUrl'), cdnUrl: config.get('cdnUrl'), - snodeServerPort: config.get('snodeServerPort'), localServerPort: config.get('localServerPort'), + defaultPoWDifficulty: config.get('defaultPoWDifficulty'), + seedNodeList: JSON.stringify(config.get('seedNodeList')), certificateAuthority: config.get('certificateAuthority'), environment: config.environment, node_version: process.versions.node, @@ -158,6 +167,7 @@ function prepareURL(pathSegments, moreKeys) { contentProxyUrl: config.contentProxyUrl, importMode: importMode ? true : undefined, // for stringify() serverTrustRoot: config.get('serverTrustRoot'), + defaultFileServer: config.get('defaultFileServer'), ...moreKeys, }, }); @@ -177,9 +187,9 @@ function captureClicks(window) { } const DEFAULT_WIDTH = 800; -const DEFAULT_HEIGHT = 710; -const MIN_WIDTH = 640; -const MIN_HEIGHT = 360; +const DEFAULT_HEIGHT = 720; +const MIN_WIDTH = 1125; +const MIN_HEIGHT = 750; const BOUNDS_BUFFER = 100; function isVisible(window, bounds) { @@ -220,11 +230,11 @@ function createWindow() { webPreferences: { nodeIntegration: false, nodeIntegrationInWorker: false, - // sandbox: true, + contextIsolation: false, preload: path.join(__dirname, 'preload.js'), nativeWindowOpen: true, }, - icon: path.join(__dirname, 'images', 'icon_256.png'), + icon: path.join(__dirname, 'images', 'session', 'icon_64.png'), }, _.pick(windowConfig, [ 'maximized', @@ -273,6 +283,15 @@ function createWindow() { // Create the browser window. mainWindow = new BrowserWindow(windowOptions); + // Disable system main menu + mainWindow.setMenu(null); + + electronLocalshortcut.register(mainWindow, 'f5', () => { + mainWindow.reload(); + }); + electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => { + mainWindow.reload(); + }); function captureAndSaveWindowStats() { if (!mainWindow) { @@ -384,15 +403,32 @@ function createWindow() { // when you should delete the corresponding element. mainWindow = null; }); - - ipc.on('show-window', () => { - showWindow(); - }); } +ipc.on('show-window', () => { + showWindow(); +}); + +let updatesStarted = false; +ipc.on('ready-for-updates', async () => { + if (updatesStarted || !updater) { + return; + } + updatesStarted = true; + + try { + await updater.start(getMainWindow, locale.messages, logger); + } catch (error) { + logger.error( + 'Error starting update checks:', + error && error.stack ? error.stack : error + ); + } +}); + function openReleaseNotes() { shell.openExternal( - `https://github.com/loki-project/loki-messenger/releases/tag/v${app.getVersion()}` + `https://github.com/loki-project/loki-messenger/releases/tag/${app.getVersion()}` ); } @@ -515,8 +551,8 @@ function showAbout() { webPreferences: { nodeIntegration: false, nodeIntegrationInWorker: false, + contextIsolation: false, preload: path.join(__dirname, 'about_preload.js'), - // sandbox: true, nativeWindowOpen: true, }, parent: mainWindow, @@ -561,8 +597,8 @@ async function showSettingsWindow() { webPreferences: { nodeIntegration: false, nodeIntegrationInWorker: false, + contextIsolation: false, preload: path.join(__dirname, 'settings_preload.js'), - // sandbox: true, nativeWindowOpen: true, }, parent: mainWindow, @@ -606,8 +642,8 @@ async function showDebugLogWindow() { webPreferences: { nodeIntegration: false, nodeIntegrationInWorker: false, + contextIsolation: false, preload: path.join(__dirname, 'debug_log_preload.js'), - // sandbox: true, nativeWindowOpen: true, }, parent: mainWindow, @@ -654,8 +690,8 @@ async function showPermissionsPopupWindow() { webPreferences: { nodeIntegration: false, nodeIntegrationInWorker: false, + contextIsolation: false, preload: path.join(__dirname, 'permissions_popup_preload.js'), - // sandbox: true, nativeWindowOpen: true, }, parent: mainWindow, @@ -707,22 +743,13 @@ app.on('ready', async () => { installPermissionsHandler({ session, userConfig }); - let loggingSetupError; - try { - await logging.initialize(); - } catch (error) { - loggingSetupError = error; - } - - if (loggingSetupError) { - console.error('Problem setting up logging', loggingSetupError.stack); - } - + await logging.initialize(); logger = logging.getLogger(); logger.info('app ready'); + logger.info(`starting version ${packageJson.version}`); if (!locale) { - const appLocale = process.env.NODE_ENV === 'test' ? 'en' : app.getLocale(); + const appLocale = process.env.NODE_ENV === 'test' ? 'en' : 'en'; // app.getLocale(); // FIXME reenable once we have translated our files locale = loadLocale({ appLocale, logger }); } @@ -730,10 +757,11 @@ app.on('ready', async () => { // Try to show the main window with the default key // If that fails then show the password window - try { - await showMainWindow(key); - } catch (e) { + const dbHasPassword = userConfig.get('dbHasPassword'); + if (dbHasPassword) { showPasswordWindow(); + } else { + await showMainWindow(key); } }); @@ -763,10 +791,15 @@ async function removeDB() { } } -async function showMainWindow(sqlKey) { +async function showMainWindow(sqlKey, passwordAttempt = false) { const userDataPath = await getRealPath(app.getPath('userData')); - await sql.initialize({ configDir: userDataPath, key: sqlKey }); + await sql.initialize({ + configDir: userDataPath, + key: sqlKey, + messages: locale.messages, + passwordAttempt, + }); await sqlChannels.initialize(); try { @@ -801,9 +834,6 @@ async function showMainWindow(sqlKey) { ready = true; - // TODO: remove or restore when appropriate - // autoUpdate.initialize(getMainWindow, locale.messages); - createWindow(); if (usingTrayIcon) { @@ -879,6 +909,7 @@ app.on('before-quit', () => { readyForShutdown: mainWindow ? mainWindow.readyForShutdown : null, shouldQuit: windowState.shouldQuit(), }); + windowState.markShouldQuit(); }); @@ -991,7 +1022,8 @@ ipc.on('password-window-login', async (event, passPhrase) => { event.sender.send('password-window-login-response', e); try { - await showMainWindow(passPhrase); + const passwordAttempt = true; + await showMainWindow(passPhrase, passwordAttempt); sendResponse(); if (passwordWindow) { passwordWindow.close(); @@ -1023,10 +1055,12 @@ ipc.on('set-password', async (event, passPhrase, oldPhrase) => { const defaultKey = getDefaultSQLKey(); await sql.setSQLPassword(defaultKey); await sql.removePasswordHash(); + userConfig.set('dbHasPassword', false); } else { await sql.setSQLPassword(passPhrase); const newHash = passwordUtil.generateHash(passPhrase); await sql.savePasswordHash(newHash); + userConfig.set('dbHasPassword', true); } sendResponse(); @@ -1086,6 +1120,10 @@ installSettingsSetter('message-ttl'); installSettingsGetter('read-receipt-setting'); installSettingsSetter('read-receipt-setting'); + +installSettingsGetter('typing-indicators-setting'); +installSettingsSetter('typing-indicators-setting'); + installSettingsGetter('notification-setting'); installSettingsSetter('notification-setting'); installSettingsGetter('audio-notification'); @@ -1112,6 +1150,9 @@ ipc.on('set-media-permissions', (event, value) => { installPermissionsHandler({ session, userConfig }); event.sender.send('set-success-media-permissions', null); + if (mainWindow && mainWindow.webContents) { + mainWindow.webContents.send('mediaPermissionsChanged'); + } }); ipc.on('on-unblock-number', (event, number) => { diff --git a/mnemonic_languages/chinese_simplified.json b/mnemonic_languages/chinese_simplified.json new file mode 100644 index 0000000000..9df98962ba --- /dev/null +++ b/mnemonic_languages/chinese_simplified.json @@ -0,0 +1 @@ +["的","一","是","在","不","了","有","和","人","这","中","大","为","上","个","国","我","以","要","他","时","来","用","们","生","到","作","地","于","出","就","分","对","成","会","可","主","发","年","动","同","工","也","能","下","过","子","说","产","种","面","而","方","后","多","定","行","学","法","所","民","得","经","十","三","之","进","着","等","部","度","家","电","力","里","如","水","化","高","自","二","理","起","小","物","现","实","加","量","都","两","体","制","机","当","使","点","从","业","本","去","把","性","好","应","开","它","合","还","因","由","其","些","然","前","外","天","政","四","日","那","社","义","事","平","形","相","全","表","间","样","与","关","各","重","新","线","内","数","正","心","反","你","明","看","原","又","么","利","比","或","但","质","气","第","向","道","命","此","变","条","只","没","结","解","问","意","建","月","公","无","系","军","很","情","者","最","立","代","想","已","通","并","提","直","题","党","程","展","五","果","料","象","员","革","位","入","常","文","总","次","品","式","活","设","及","管","特","件","长","求","老","头","基","资","边","流","路","级","少","图","山","统","接","知","较","将","组","见","计","别","她","手","角","期","根","论","运","农","指","几","九","区","强","放","决","西","被","干","做","必","战","先","回","则","任","取","据","处","队","南","给","色","光","门","即","保","治","北","造","百","规","热","领","七","海","口","东","导","器","压","志","世","金","增","争","济","阶","油","思","术","极","交","受","联","什","认","六","共","权","收","证","改","清","美","再","采","转","更","单","风","切","打","白","教","速","花","带","安","场","身","车","例","真","务","具","万","每","目","至","达","走","积","示","议","声","报","斗","完","类","八","离","华","名","确","才","科","张","信","马","节","话","米","整","空","元","况","今","集","温","传","土","许","步","群","广","石","记","需","段","研","界","拉","林","律","叫","且","究","观","越","织","装","影","算","低","持","音","众","书","布","复","容","儿","须","际","商","非","验","连","断","深","难","近","矿","千","周","委","素","技","备","半","办","青","省","列","习","响","约","支","般","史","感","劳","便","团","往","酸","历","市","克","何","除","消","构","府","称","太","准","精","值","号","率","族","维","划","选","标","写","存","候","毛","亲","快","效","斯","院","查","江","型","眼","王","按","格","养","易","置","派","层","片","始","却","专","状","育","厂","京","识","适","属","圆","包","火","住","调","满","县","局","照","参","红","细","引","听","该","铁","价","严","首","底","液","官","德","随","病","苏","失","尔","死","讲","配","女","黄","推","显","谈","罪","神","艺","呢","席","含","企","望","密","批","营","项","防","举","球","英","氧","势","告","李","台","落","木","帮","轮","破","亚","师","围","注","远","字","材","排","供","河","态","封","另","施","减","树","溶","怎","止","案","言","士","均","武","固","叶","鱼","波","视","仅","费","紧","爱","左","章","早","朝","害","续","轻","服","试","食","充","兵","源","判","护","司","足","某","练","差","致","板","田","降","黑","犯","负","击","范","继","兴","似","余","坚","曲","输","修","故","城","夫","够","送","笔","船","占","右","财","吃","富","春","职","觉","汉","画","功","巴","跟","虽","杂","飞","检","吸","助","升","阳","互","初","创","抗","考","投","坏","策","古","径","换","未","跑","留","钢","曾","端","责","站","简","述","钱","副","尽","帝","射","草","冲","承","独","令","限","阿","宣","环","双","请","超","微","让","控","州","良","轴","找","否","纪","益","依","优","顶","础","载","倒","房","突","坐","粉","敌","略","客","袁","冷","胜","绝","析","块","剂","测","丝","协","诉","念","陈","仍","罗","盐","友","洋","错","苦","夜","刑","移","频","逐","靠","混","母","短","皮","终","聚","汽","村","云","哪","既","距","卫","停","烈","央","察","烧","迅","境","若","印","洲","刻","括","激","孔","搞","甚","室","待","核","校","散","侵","吧","甲","游","久","菜","味","旧","模","湖","货","损","预","阻","毫","普","稳","乙","妈","植","息","扩","银","语","挥","酒","守","拿","序","纸","医","缺","雨","吗","针","刘","啊","急","唱","误","训","愿","审","附","获","茶","鲜","粮","斤","孩","脱","硫","肥","善","龙","演","父","渐","血","欢","械","掌","歌","沙","刚","攻","谓","盾","讨","晚","粒","乱","燃","矛","乎","杀","药","宁","鲁","贵","钟","煤","读","班","伯","香","介","迫","句","丰","培","握","兰","担","弦","蛋","沉","假","穿","执","答","乐","谁","顺","烟","缩","征","脸","喜","松","脚","困","异","免","背","星","福","买","染","井","概","慢","怕","磁","倍","祖","皇","促","静","补","评","翻","肉","践","尼","衣","宽","扬","棉","希","伤","操","垂","秋","宜","氢","套","督","振","架","亮","末","宪","庆","编","牛","触","映","雷","销","诗","座","居","抓","裂","胞","呼","娘","景","威","绿","晶","厚","盟","衡","鸡","孙","延","危","胶","屋","乡","临","陆","顾","掉","呀","灯","岁","措","束","耐","剧","玉","赵","跳","哥","季","课","凯","胡","额","款","绍","卷","齐","伟","蒸","殖","永","宗","苗","川","炉","岩","弱","零","杨","奏","沿","露","杆","探","滑","镇","饭","浓","航","怀","赶","库","夺","伊","灵","税","途","灭","赛","归","召","鼓","播","盘","裁","险","康","唯","录","菌","纯","借","糖","盖","横","符","私","努","堂","域","枪","润","幅","哈","竟","熟","虫","泽","脑","壤","碳","欧","遍","侧","寨","敢","彻","虑","斜","薄","庭","纳","弹","饲","伸","折","麦","湿","暗","荷","瓦","塞","床","筑","恶","户","访","塔","奇","透","梁","刀","旋","迹","卡","氯","遇","份","毒","泥","退","洗","摆","灰","彩","卖","耗","夏","择","忙","铜","献","硬","予","繁","圈","雪","函","亦","抽","篇","阵","阴","丁","尺","追","堆","雄","迎","泛","爸","楼","避","谋","吨","野","猪","旗","累","偏","典","馆","索","秦","脂","潮","爷","豆","忽","托","惊","塑","遗","愈","朱","替","纤","粗","倾","尚","痛","楚","谢","奋","购","磨","君","池","旁","碎","骨","监","捕","弟","暴","割","贯","殊","释","词","亡","壁","顿","宝","午","尘","闻","揭","炮","残","冬","桥","妇","警","综","招","吴","付","浮","遭","徐","您","摇","谷","赞","箱","隔","订","男","吹","园","纷","唐","败","宋","玻","巨","耕","坦","荣","闭","湾","键","凡","驻","锅","救","恩","剥","凝","碱","齿","截","炼","麻","纺","禁","废","盛","版","缓","净","睛","昌","婚","涉","筒","嘴","插","岸","朗","庄","街","藏","姑","贸","腐","奴","啦","惯","乘","伙","恢","匀","纱","扎","辩","耳","彪","臣","亿","璃","抵","脉","秀","萨","俄","网","舞","店","喷","纵","寸","汗","挂","洪","贺","闪","柬","爆","烯","津","稻","墙","软","勇","像","滚","厘","蒙","芳","肯","坡","柱","荡","腿","仪","旅","尾","轧","冰","贡","登","黎","削","钻","勒","逃","障","氨","郭","峰","币","港","伏","轨","亩","毕","擦","莫","刺","浪","秘","援","株","健","售","股","岛","甘","泡","睡","童","铸","汤","阀","休","汇","舍","牧","绕","炸","哲","磷","绩","朋","淡","尖","启","陷","柴","呈","徒","颜","泪","稍","忘","泵","蓝","拖","洞","授","镜","辛","壮","锋","贫","虚","弯","摩","泰","幼","廷","尊","窗","纲","弄","隶","疑","氏","宫","姐","震","瑞","怪","尤","琴","循","描","膜","违","夹","腰","缘","珠","穷","森","枝","竹","沟","催","绳","忆","邦","剩","幸","浆","栏","拥","牙","贮","礼","滤","钠","纹","罢","拍","咱","喊","袖","埃","勤","罚","焦","潜","伍","墨","欲","缝","姓","刊","饱","仿","奖","铝","鬼","丽","跨","默","挖","链","扫","喝","袋","炭","污","幕","诸","弧","励","梅","奶","洁","灾","舟","鉴","苯","讼","抱","毁","懂","寒","智","埔","寄","届","跃","渡","挑","丹","艰","贝","碰","拔","爹","戴","码","梦","芽","熔","赤","渔","哭","敬","颗","奔","铅","仲","虎","稀","妹","乏","珍","申","桌","遵","允","隆","螺","仓","魏","锐","晓","氮","兼","隐","碍","赫","拨","忠","肃","缸","牵","抢","博","巧","壳","兄","杜","讯","诚","碧","祥","柯","页","巡","矩","悲","灌","龄","伦","票","寻","桂","铺","圣","恐","恰","郑","趣","抬","荒","腾","贴","柔","滴","猛","阔","辆","妻","填","撤","储","签","闹","扰","紫","砂","递","戏","吊","陶","伐","喂","疗","瓶","婆","抚","臂","摸","忍","虾","蜡","邻","胸","巩","挤","偶","弃","槽","劲","乳","邓","吉","仁","烂","砖","租","乌","舰","伴","瓜","浅","丙","暂","燥","橡","柳","迷","暖","牌","秧","胆","详","簧","踏","瓷","谱","呆","宾","糊","洛","辉","愤","竞","隙","怒","粘","乃","绪","肩","籍","敏","涂","熙","皆","侦","悬","掘","享","纠","醒","狂","锁","淀","恨","牲","霸","爬","赏","逆","玩","陵","祝","秒","浙","貌"] \ No newline at end of file diff --git a/mnemonic_languages/dutch.json b/mnemonic_languages/dutch.json new file mode 100644 index 0000000000..0d77e802b9 --- /dev/null +++ b/mnemonic_languages/dutch.json @@ -0,0 +1 @@ +["aalglad","aalscholver","aambeeld","aangeef","aanlandig","aanvaard","aanwakker","aapmens","aarten","abdicatie","abnormaal","abrikoos","accu","acuut","adjudant","admiraal","advies","afbidding","afdracht","affaire","affiche","afgang","afkick","afknap","aflees","afmijner","afname","afpreekt","afrader","afspeel","aftocht","aftrek","afzijdig","ahornboom","aktetas","akzo","alchemist","alcohol","aldaar","alexander","alfabet","alfredo","alice","alikruik","allrisk","altsax","alufolie","alziend","amai","ambacht","ambieer","amina","amnestie","amok","ampul","amuzikaal","angela","aniek","antje","antwerpen","anya","aorta","apache","apekool","appelaar","arganolie","argeloos","armoede","arrenslee","artritis","arubaan","asbak","ascii","asgrauw","asjes","asml","aspunt","asurn","asveld","aterling","atomair","atrium","atsma","atypisch","auping","aura","avifauna","axiaal","azoriaan","azteek","azuur","bachelor","badderen","badhotel","badmantel","badsteden","balie","ballans","balvers","bamibal","banneling","barracuda","basaal","batelaan","batje","beambte","bedlamp","bedwelmd","befaamd","begierd","begraaf","behield","beijaard","bejaagd","bekaaid","beks","bektas","belaad","belboei","belderbos","beloerd","beluchten","bemiddeld","benadeeld","benijd","berechten","beroemd","besef","besseling","best","betichten","bevind","bevochten","bevraagd","bewust","bidplaats","biefstuk","biemans","biezen","bijbaan","bijeenkom","bijfiguur","bijkaart","bijlage","bijpaard","bijtgaar","bijweg","bimmel","binck","bint","biobak","biotisch","biseks","bistro","bitter","bitumen","bizar","blad","bleken","blender","bleu","blief","blijven","blozen","bock","boef","boei","boks","bolder","bolus","bolvormig","bomaanval","bombarde","bomma","bomtapijt","bookmaker","boos","borg","bosbes","boshuizen","bosloop","botanicus","bougie","bovag","boxspring","braad","brasem","brevet","brigade","brinckman","bruid","budget","buffel","buks","bulgaar","buma","butaan","butler","buuf","cactus","cafeetje","camcorder","cannabis","canyon","capoeira","capsule","carkit","casanova","catalaan","ceintuur","celdeling","celplasma","cement","censeren","ceramisch","cerberus","cerebraal","cesium","cirkel","citeer","civiel","claxon","clenbuterol","clicheren","clijsen","coalitie","coassistentschap","coaxiaal","codetaal","cofinanciering","cognac","coltrui","comfort","commandant","condensaat","confectie","conifeer","convector","copier","corfu","correct","coup","couvert","creatie","credit","crematie","cricket","croupier","cruciaal","cruijff","cuisine","culemborg","culinair","curve","cyrano","dactylus","dading","dagblind","dagje","daglicht","dagprijs","dagranden","dakdekker","dakpark","dakterras","dalgrond","dambord","damkat","damlengte","damman","danenberg","debbie","decibel","defect","deformeer","degelijk","degradant","dejonghe","dekken","deppen","derek","derf","derhalve","detineren","devalueer","diaken","dicht","dictaat","dief","digitaal","dijbreuk","dijkmans","dimbaar","dinsdag","diode","dirigeer","disbalans","dobermann","doenbaar","doerak","dogma","dokhaven","dokwerker","doling","dolphijn","dolven","dombo","dooraderd","dopeling","doping","draderig","drama","drenkbak","dreumes","drol","drug","duaal","dublin","duplicaat","durven","dusdanig","dutchbat","dutje","dutten","duur","duwwerk","dwaal","dweil","dwing","dyslexie","ecostroom","ecotaks","educatie","eeckhout","eede","eemland","eencellig","eeneiig","eenruiter","eenwinter","eerenberg","eerrover","eersel","eetmaal","efteling","egaal","egtberts","eickhoff","eidooier","eiland","eind","eisden","ekster","elburg","elevatie","elfkoppig","elfrink","elftal","elimineer","elleboog","elma","elodie","elsa","embleem","embolie","emoe","emonds","emplooi","enduro","enfin","engageer","entourage","entstof","epileer","episch","eppo","erasmus","erboven","erebaan","erelijst","ereronden","ereteken","erfhuis","erfwet","erger","erica","ermitage","erna","ernie","erts","ertussen","eruitzien","ervaar","erven","erwt","esbeek","escort","esdoorn","essing","etage","eter","ethanol","ethicus","etholoog","eufonisch","eurocent","evacuatie","exact","examen","executant","exen","exit","exogeen","exotherm","expeditie","expletief","expres","extase","extinctie","faal","faam","fabel","facultair","fakir","fakkel","faliekant","fallisch","famke","fanclub","fase","fatsoen","fauna","federaal","feedback","feest","feilbaar","feitelijk","felblauw","figurante","fiod","fitheid","fixeer","flap","fleece","fleur","flexibel","flits","flos","flow","fluweel","foezelen","fokkelman","fokpaard","fokvee","folder","follikel","folmer","folteraar","fooi","foolen","forfait","forint","formule","fornuis","fosfaat","foxtrot","foyer","fragiel","frater","freak","freddie","fregat","freon","frijnen","fructose","frunniken","fuiven","funshop","furieus","fysica","gadget","galder","galei","galg","galvlieg","galzuur","ganesh","gaswet","gaza","gazelle","geaaid","gebiecht","gebufferd","gedijd","geef","geflanst","gefreesd","gegaan","gegijzeld","gegniffel","gegraaid","gehikt","gehobbeld","gehucht","geiser","geiten","gekaakt","gekheid","gekijf","gekmakend","gekocht","gekskap","gekte","gelubberd","gemiddeld","geordend","gepoederd","gepuft","gerda","gerijpt","geseald","geshockt","gesierd","geslaagd","gesnaaid","getracht","getwijfel","geuit","gevecht","gevlagd","gewicht","gezaagd","gezocht","ghanees","giebelen","giechel","giepmans","gips","giraal","gistachtig","gitaar","glaasje","gletsjer","gleuf","glibberen","glijbaan","gloren","gluipen","gluren","gluur","gnoe","goddelijk","godgans","godschalk","godzalig","goeierd","gogme","goklustig","gokwereld","gonggrijp","gonje","goor","grabbel","graf","graveer","grif","grolleman","grom","groosman","grubben","gruijs","grut","guacamole","guido","guppy","haazen","hachelijk","haex","haiku","hakhout","hakken","hanegem","hans","hanteer","harrie","hazebroek","hedonist","heil","heineken","hekhuis","hekman","helbig","helga","helwegen","hengelaar","herkansen","hermafrodiet","hertaald","hiaat","hikspoors","hitachi","hitparade","hobo","hoeve","holocaust","hond","honnepon","hoogacht","hotelbed","hufter","hugo","huilbier","hulk","humus","huwbaar","huwelijk","hype","iconisch","idema","ideogram","idolaat","ietje","ijker","ijkheid","ijklijn","ijkmaat","ijkwezen","ijmuiden","ijsbox","ijsdag","ijselijk","ijskoud","ilse","immuun","impliceer","impuls","inbijten","inbuigen","indijken","induceer","indy","infecteer","inhaak","inkijk","inluiden","inmijnen","inoefenen","inpolder","inrijden","inslaan","invitatie","inwaaien","ionisch","isaac","isolatie","isotherm","isra","italiaan","ivoor","jacobs","jakob","jammen","jampot","jarig","jehova","jenever","jezus","joana","jobdienst","josua","joule","juich","jurk","juut","kaas","kabelaar","kabinet","kagenaar","kajuit","kalebas","kalm","kanjer","kapucijn","karregat","kart","katvanger","katwijk","kegelaar","keiachtig","keizer","kenletter","kerdijk","keus","kevlar","kezen","kickback","kieviet","kijken","kikvors","kilheid","kilobit","kilsdonk","kipschnitzel","kissebis","klad","klagelijk","klak","klapbaar","klaver","klene","klets","klijnhout","klit","klok","klonen","klotefilm","kluif","klumper","klus","knabbel","knagen","knaven","kneedbaar","knmi","knul","knus","kokhals","komiek","komkommer","kompaan","komrij","komvormig","koning","kopbal","kopklep","kopnagel","koppejan","koptekst","kopwand","koraal","kosmisch","kostbaar","kram","kraneveld","kras","kreling","krengen","kribbe","krik","kruid","krulbol","kuijper","kuipbank","kuit","kuiven","kutsmoes","kuub","kwak","kwatong","kwetsbaar","kwezelaar","kwijnen","kwik","kwinkslag","kwitantie","lading","lakbeits","lakken","laklaag","lakmoes","lakwijk","lamheid","lamp","lamsbout","lapmiddel","larve","laser","latijn","latuw","lawaai","laxeerpil","lebberen","ledeboer","leefbaar","leeman","lefdoekje","lefhebber","legboor","legsel","leguaan","leiplaat","lekdicht","lekrijden","leksteen","lenen","leraar","lesbienne","leugenaar","leut","lexicaal","lezing","lieten","liggeld","lijdzaam","lijk","lijmstang","lijnschip","likdoorn","likken","liksteen","limburg","link","linoleum","lipbloem","lipman","lispelen","lissabon","litanie","liturgie","lochem","loempia","loesje","logheid","lonen","lonneke","loom","loos","losbaar","loslaten","losplaats","loting","lotnummer","lots","louie","lourdes","louter","lowbudget","luijten","luikenaar","luilak","luipaard","luizenbos","lulkoek","lumen","lunzen","lurven","lutjeboer","luttel","lutz","luuk","luwte","luyendijk","lyceum","lynx","maakbaar","magdalena","malheid","manchet","manfred","manhaftig","mank","mantel","marion","marxist","masmeijer","massaal","matsen","matverf","matze","maude","mayonaise","mechanica","meifeest","melodie","meppelink","midvoor","midweeks","midzomer","miezel","mijnraad","minus","mirck","mirte","mispakken","misraden","miswassen","mitella","moker","molecule","mombakkes","moonen","mopperaar","moraal","morgana","mormel","mosselaar","motregen","mouw","mufheid","mutueel","muzelman","naaidoos","naald","nadeel","nadruk","nagy","nahon","naima","nairobi","napalm","napels","napijn","napoleon","narigheid","narratief","naseizoen","nasibal","navigatie","nawijn","negatief","nekletsel","nekwervel","neolatijn","neonataal","neptunus","nerd","nest","neuzelaar","nihiliste","nijenhuis","nijging","nijhoff","nijl","nijptang","nippel","nokkenas","noordam","noren","normaal","nottelman","notulant","nout","nuance","nuchter","nudorp","nulde","nullijn","nulmeting","nunspeet","nylon","obelisk","object","oblie","obsceen","occlusie","oceaan","ochtend","ockhuizen","oerdom","oergezond","oerlaag","oester","okhuijsen","olifant","olijfboer","omaans","ombudsman","omdat","omdijken","omdoen","omgebouwd","omkeer","omkomen","ommegaand","ommuren","omroep","omruil","omslaan","omsmeden","omvaar","onaardig","onedel","onenig","onheilig","onrecht","onroerend","ontcijfer","onthaal","ontvallen","ontzadeld","onzacht","onzin","onzuiver","oogappel","ooibos","ooievaar","ooit","oorarts","oorhanger","oorijzer","oorklep","oorschelp","oorworm","oorzaak","opdagen","opdien","opdweilen","opel","opgebaard","opinie","opjutten","opkijken","opklaar","opkuisen","opkwam","opnaaien","opossum","opsieren","opsmeer","optreden","opvijzel","opvlammen","opwind","oraal","orchidee","orkest","ossuarium","ostendorf","oublie","oudachtig","oudbakken","oudnoors","oudshoorn","oudtante","oven","over","oxidant","pablo","pacht","paktafel","pakzadel","paljas","panharing","papfles","paprika","parochie","paus","pauze","paviljoen","peek","pegel","peigeren","pekela","pendant","penibel","pepmiddel","peptalk","periferie","perron","pessarium","peter","petfles","petgat","peuk","pfeifer","picknick","pief","pieneman","pijlkruid","pijnacker","pijpelink","pikdonker","pikeer","pilaar","pionier","pipet","piscine","pissebed","pitchen","pixel","plamuren","plan","plausibel","plegen","plempen","pleonasme","plezant","podoloog","pofmouw","pokdalig","ponywagen","popachtig","popidool","porren","positie","potten","pralen","prezen","prijzen","privaat","proef","prooi","prozawerk","pruik","prul","publiceer","puck","puilen","pukkelig","pulveren","pupil","puppy","purmerend","pustjens","putemmer","puzzelaar","queenie","quiche","raam","raar","raat","raes","ralf","rally","ramona","ramselaar","ranonkel","rapen","rapunzel","rarekiek","rarigheid","rattenhol","ravage","reactie","recreant","redacteur","redster","reewild","regie","reijnders","rein","replica","revanche","rigide","rijbaan","rijdansen","rijgen","rijkdom","rijles","rijnwijn","rijpma","rijstafel","rijtaak","rijzwepen","rioleer","ripdeal","riphagen","riskant","rits","rivaal","robbedoes","robot","rockact","rodijk","rogier","rohypnol","rollaag","rolpaal","roltafel","roof","roon","roppen","rosbief","rosharig","rosielle","rotan","rotleven","rotten","rotvaart","royaal","royeer","rubato","ruby","ruche","rudge","ruggetje","rugnummer","rugpijn","rugtitel","rugzak","ruilbaar","ruis","ruit","rukwind","rulijs","rumoeren","rumsdorp","rumtaart","runnen","russchen","ruwkruid","saboteer","saksisch","salade","salpeter","sambabal","samsam","satelliet","satineer","saus","scampi","scarabee","scenario","schobben","schubben","scout","secessie","secondair","seculair","sediment","seeland","settelen","setwinst","sheriff","shiatsu","siciliaan","sidderaal","sigma","sijben","silvana","simkaart","sinds","situatie","sjaak","sjardijn","sjezen","sjor","skinhead","skylab","slamixen","sleijpen","slijkerig","slordig","slowaak","sluieren","smadelijk","smiecht","smoel","smos","smukken","snackcar","snavel","sneaker","sneu","snijdbaar","snit","snorder","soapbox","soetekouw","soigneren","sojaboon","solo","solvabel","somber","sommatie","soort","soppen","sopraan","soundbar","spanen","spawater","spijgat","spinaal","spionage","spiraal","spleet","splijt","spoed","sporen","spul","spuug","spuw","stalen","standaard","star","stefan","stencil","stijf","stil","stip","stopdas","stoten","stoven","straat","strobbe","strubbel","stucadoor","stuif","stukadoor","subhoofd","subregent","sudoku","sukade","sulfaat","surinaams","suus","syfilis","symboliek","sympathie","synagoge","synchroon","synergie","systeem","taanderij","tabak","tachtig","tackelen","taiwanees","talman","tamheid","tangaslip","taps","tarkan","tarwe","tasman","tatjana","taxameter","teil","teisman","telbaar","telco","telganger","telstar","tenant","tepel","terzet","testament","ticket","tiesinga","tijdelijk","tika","tiksel","tilleman","timbaal","tinsteen","tiplijn","tippelaar","tjirpen","toezeggen","tolbaas","tolgeld","tolhek","tolo","tolpoort","toltarief","tolvrij","tomaat","tondeuse","toog","tooi","toonbaar","toos","topclub","toppen","toptalent","topvrouw","toque","torment","tornado","tosti","totdat","toucheer","toulouse","tournedos","tout","trabant","tragedie","trailer","traject","traktaat","trauma","tray","trechter","tred","tref","treur","troebel","tros","trucage","truffel","tsaar","tucht","tuenter","tuitelig","tukje","tuktuk","tulp","tuma","tureluurs","twijfel","twitteren","tyfoon","typograaf","ugandees","uiachtig","uier","uisnipper","ultiem","unitair","uranium","urbaan","urendag","ursula","uurcirkel","uurglas","uzelf","vaat","vakantie","vakleraar","valbijl","valpartij","valreep","valuatie","vanmiddag","vanonder","varaan","varken","vaten","veenbes","veeteler","velgrem","vellekoop","velvet","veneberg","venlo","vent","venusberg","venw","veredeld","verf","verhaaf","vermaak","vernaaid","verraad","vers","veruit","verzaagd","vetachtig","vetlok","vetmesten","veto","vetrek","vetstaart","vetten","veurink","viaduct","vibrafoon","vicariaat","vieux","vieveen","vijfvoud","villa","vilt","vimmetje","vindbaar","vips","virtueel","visdieven","visee","visie","vlaag","vleugel","vmbo","vocht","voesenek","voicemail","voip","volg","vork","vorselaar","voyeur","vracht","vrekkig","vreten","vrije","vrozen","vrucht","vucht","vugt","vulkaan","vulmiddel","vulva","vuren","waas","wacht","wadvogel","wafel","waffel","walhalla","walnoot","walraven","wals","walvis","wandaad","wanen","wanmolen","want","warklomp","warm","wasachtig","wasteil","watt","webhandel","weblog","webpagina","webzine","wedereis","wedstrijd","weeda","weert","wegmaaien","wegscheer","wekelijks","wekken","wekroep","wektoon","weldaad","welwater","wendbaar","wenkbrauw","wens","wentelaar","wervel","wesseling","wetboek","wetmatig","whirlpool","wijbrands","wijdbeens","wijk","wijnbes","wijting","wild","wimpelen","wingebied","winplaats","winter","winzucht","wipstaart","wisgerhof","withaar","witmaker","wokkel","wolf","wonenden","woning","worden","worp","wortel","wrat","wrijf","wringen","yoghurt","ypsilon","zaaijer","zaak","zacharias","zakelijk","zakkam","zakwater","zalf","zalig","zaniken","zebracode","zeeblauw","zeef","zeegaand","zeeuw","zege","zegje","zeil","zesbaans","zesenhalf","zeskantig","zesmaal","zetbaas","zetpil","zeulen","ziezo","zigzag","zijaltaar","zijbeuk","zijlijn","zijmuur","zijn","zijwaarts","zijzelf","zilt","zimmerman","zinledig","zinnelijk","zionist","zitdag","zitruimte","zitzak","zoal","zodoende","zoekbots","zoem","zoiets","zojuist","zondaar","zotskap","zottebol","zucht","zuivel","zulk","zult","zuster","zuur","zweedijk","zwendel","zwepen","zwiep","zwijmel","zworen"] \ No newline at end of file diff --git a/mnemonic_languages/esperanto.json b/mnemonic_languages/esperanto.json new file mode 100644 index 0000000000..1d6941a39b --- /dev/null +++ b/mnemonic_languages/esperanto.json @@ -0,0 +1 @@ +["abako","abdiki","abelo","abituriento","ablativo","abnorma","abonantoj","abrikoto","absoluta","abunda","acetono","acida","adapti","adekvata","adheri","adicii","adjektivo","administri","adolesko","adreso","adstringa","adulto","advokato","adzo","aeroplano","aferulo","afgana","afiksi","aflaba","aforismo","afranki","aftozo","afusto","agavo","agento","agiti","aglo","agmaniero","agnoski","agordo","agrabla","agtipo","agutio","aikido","ailanto","aina","ajatolo","ajgenvaloro","ajlobulbo","ajnlitera","ajuto","ajzi","akademio","akcepti","akeo","akiri","aklamado","akmeo","akno","akompani","akrobato","akselo","aktiva","akurata","akvofalo","alarmo","albumo","alcedo","aldoni","aleo","alfabeto","algo","alhasti","aligatoro","alkoholo","almozo","alnomo","alojo","alpinisto","alrigardi","alskribi","alta","alumeto","alveni","alzaca","amaso","ambasado","amdeklaro","amebo","amfibio","amhara","amiko","amkanto","amletero","amnestio","amoranto","amplekso","amrakonto","amsterdama","amuzi","ananaso","androido","anekdoto","anfrakto","angulo","anheli","animo","anjono","ankro","anonci","anpriskribo","ansero","antikva","anuitato","aorto","aparta","aperti","apika","aplikado","apneo","apogi","aprobi","apsido","apterigo","apudesto","araneo","arbo","ardeco","aresti","argilo","aristokrato","arko","arlekeno","armi","arniko","aromo","arpio","arsenalo","artisto","aruba","arvorto","asaio","asbesto","ascendi","asekuri","asfalto","asisti","askalono","asocio","aspekti","astro","asulo","atakonto","atendi","atingi","atleto","atmosfero","atomo","atropino","atuto","avataro","aventuro","aviadilo","avokado","azaleo","azbuko","azenino","azilpetanto","azoto","azteka","babili","bacilo","badmintono","bagatelo","bahama","bajoneto","baki","balai","bambuo","bani","baobabo","bapti","baro","bastono","batilo","bavara","bazalto","beata","bebofono","bedo","begonio","behaviorismo","bejlo","bekero","belarto","bemolo","benko","bereto","besto","betulo","bevelo","bezoni","biaso","biblioteko","biciklo","bidaro","bieno","bifsteko","bigamiulo","bijekcio","bikino","bildo","bimetalismo","bindi","biografio","birdo","biskvito","bitlibro","bivako","bizara","bjalistoka","blanka","bleki","blinda","blovi","blua","boato","bobsledo","bocvanano","bodisatvo","bofratino","bogefratoj","bohema","boji","bokalo","boli","bombono","bona","bopatrino","bordo","bosko","botelo","bovido","brakpleno","bretaro","brikmuro","broso","brulema","bubalo","buctrapi","budo","bufedo","bugio","bujabeso","buklo","buldozo","bumerango","bunta","burokrataro","busbileto","butero","buzuko","caro","cebo","ceceo","cedro","cefalo","cejana","cekumo","celebri","cemento","cent","cepo","certa","cetera","cezio","ciano","cibeto","cico","cidro","cifero","cigaredo","ciklo","cilindro","cimbalo","cinamo","cipreso","cirkonstanco","cisterno","citrono","ciumi","civilizado","colo","congo","cunamo","cvana","dabi","daco","dadaismo","dafodilo","dago","daimio","dajmono","daktilo","dalio","damo","danki","darmo","datumoj","dazipo","deadmoni","debeto","decidi","dedukti","deerigi","defendi","degeli","dehaki","deirpunkto","deklaracio","delikata","demandi","dento","dependi","derivi","desegni","detrui","devi","deziri","dialogo","dicentro","didaktika","dieto","diferenci","digesti","diino","dikfingro","diligenta","dimensio","dinamo","diodo","diplomo","direkte","diskuti","diurno","diversa","dizajno","dobrogitaro","docento","dogano","dojeno","doktoro","dolori","domego","donaci","dopado","dormi","dosierujo","dotita","dozeno","drato","dresi","drinki","droni","druido","duaranga","dubi","ducent","dudek","duelo","dufoje","dugongo","duhufa","duilo","dujare","dukato","duloka","dumtempe","dungi","duobla","dupiedulo","dura","dusenca","dutaga","duuma","duvalvuloj","duzo","ebena","eblecoj","ebono","ebria","eburo","ecaro","ecigi","ecoj","edelvejso","editoro","edro","eduki","edzino","efektiva","efiki","efloreski","egala","egeco","egiptologo","eglefino","egoista","egreto","ejakuli","ejlo","ekarto","ekbruligi","ekceli","ekde","ekesti","ekfirmao","ekgliti","ekhavi","ekipi","ekkapti","eklezio","ekmalsati","ekonomio","ekpluvi","ekrano","ekster","ektiri","ekumeno","ekvilibro","ekzemplo","elasta","elbalai","elcento","eldoni","elektro","elfari","elgliti","elhaki","elipso","elkovi","ellasi","elmeti","elnutri","elokventa","elparoli","elrevigi","elstari","elteni","eluzita","elvoki","elzasa","emajlo","embaraso","emerito","emfazo","eminenta","emocio","empiria","emulsio","enarkivigi","enboteligi","enciklopedio","endorfino","energio","enfermi","engluti","enhavo","enigmo","enjekcio","enketi","enlanda","enmeti","enorma","enplanti","enradiki","enspezo","entrepreni","enui","envolvi","enzimo","eono","eosto","epitafo","epoko","epriskribebla","epsilono","erari","erbio","erco","erekti","ergonomia","erikejo","ermito","erotika","erpilo","erupcio","esameno","escepti","esenco","eskapi","esotera","esperi","estonto","etapo","etendi","etfingro","etikedo","etlitero","etmakleristo","etnika","etoso","etradio","etskala","etullernejo","evakui","evento","eviti","evolui","ezoko","fabriko","facila","fadeno","fagoto","fajro","fakto","fali","familio","fanatiko","farbo","fasko","fatala","favora","fazeolo","febro","federacio","feino","fekunda","felo","femuro","fenestro","fermi","festi","fetora","fezo","fiasko","fibro","fidela","fiera","fifama","figuro","fiherbo","fiinsekto","fiksa","filmo","fimensa","finalo","fiolo","fiparoli","firmao","fisko","fitingo","fiuzanto","fivorto","fiziko","fjordo","flago","flegi","flirti","floro","flugi","fobio","foceno","foirejo","fojfoje","fokuso","folio","fomenti","fonto","formulo","fosforo","fotografi","fratino","fremda","friti","frosto","frua","ftizo","fuelo","fugo","fuksia","fulmilo","fumanto","fundamento","fuorto","furioza","fusilo","futbalo","fuzio","gabardino","gado","gaela","gafo","gagato","gaja","gaki","galanta","gamao","ganto","gapulo","gardi","gasto","gavio","gazeto","geamantoj","gebani","geedzeco","gefratoj","geheno","gejsero","geko","gelateno","gemisto","geniulo","geografio","gepardo","geranio","gestolingvo","geto","geumo","gibono","giganta","gildo","gimnastiko","ginekologo","gipsi","girlando","gistfungo","gitaro","glazuro","glebo","gliti","globo","gluti","gnafalio","gnejso","gnomo","gnuo","gobio","godetio","goeleto","gojo","golfludejo","gombo","gondolo","gorilo","gospelo","gotika","granda","greno","griza","groto","grupo","guano","gubernatoro","gudrotuko","gufo","gujavo","guldeno","gumi","gupio","guruo","gusto","guto","guvernistino","gvardio","gverilo","gvidanto","habitato","hadito","hafnio","hagiografio","haitiano","hajlo","hakbloko","halti","hamstro","hangaro","hapalo","haro","hasta","hati","havebla","hazardo","hebrea","hedero","hegemonio","hejmo","hektaro","helpi","hemisfero","heni","hepato","herbo","hesa","heterogena","heziti","hiacinto","hibrida","hidrogeno","hieroglifo","higieno","hihii","hilumo","himno","hindino","hiperteksto","hirundo","historio","hobio","hojli","hokeo","hologramo","homido","honesta","hopi","horizonto","hospitalo","hotelo","huadi","hubo","hufumo","hugenoto","hukero","huligano","humana","hundo","huoj","hupilo","hurai","husaro","hutuo","huzo","iafoje","iagrade","iamaniere","iarelate","iaspeca","ibekso","ibiso","idaro","ideala","idiomo","idolo","iele","igluo","ignori","iguamo","igvano","ikono","iksodo","ikto","iliaflanke","ilkomputilo","ilobreto","ilremedo","ilumini","imagi","imitado","imperio","imuna","incidento","industrio","inerta","infano","ingenra","inhali","iniciati","injekti","inklino","inokuli","insekto","inteligenta","inundi","inviti","ioma","ionosfero","iperito","ipomeo","irana","irejo","irigacio","ironio","isato","islamo","istempo","itinero","itrio","iuloke","iumaniere","iutempe","izolita","jado","jaguaro","jakto","jama","januaro","japano","jarringo","jazo","jenoj","jesulo","jetavio","jezuito","jodli","joviala","juano","jubileo","judismo","jufto","juki","julio","juneca","jupo","juristo","juste","juvelo","kabineto","kadrato","kafo","kahelo","kajako","kakao","kalkuli","kampo","kanti","kapitalo","karaktero","kaserolo","katapulto","kaverna","kazino","kebabo","kefiro","keglo","kejlo","kekso","kelka","kemio","kerno","kesto","kiamaniere","kibuco","kidnapi","kielo","kikero","kilogramo","kimono","kinejo","kiosko","kirurgo","kisi","kitelo","kivio","klavaro","klerulo","klini","klopodi","klubo","knabo","knedi","koalo","kobalto","kodigi","kofro","kohera","koincidi","kojoto","kokoso","koloro","komenci","kontrakto","kopio","korekte","kosti","kotono","kovri","krajono","kredi","krii","krom","kruco","ksantino","ksenono","ksilofono","ksosa","kubuto","kudri","kuglo","kuiri","kuko","kulero","kumuluso","kuneco","kupro","kuri","kuseno","kutimo","kuvo","kuzino","kvalito","kverko","kvin","kvoto","labori","laculo","ladbotelo","lafo","laguno","laikino","laktobovino","lampolumo","landkarto","laosa","lapono","larmoguto","lastjare","latitudo","lavejo","lazanjo","leciono","ledosako","leganto","lekcio","lemura","lentuga","leopardo","leporo","lerni","lesivo","letero","levilo","lezi","liano","libera","liceo","lieno","lifto","ligilo","likvoro","lila","limono","lingvo","lipo","lirika","listo","literatura","liveri","lobio","logika","lojala","lokalo","longa","lordo","lotado","loza","luanto","lubriki","lucida","ludema","luigi","lukso","luli","lumbilda","lunde","lupago","lustro","lutilo","luzerno","maato","maceri","madono","mafiano","magazeno","mahometano","maizo","majstro","maketo","malgranda","mamo","mandareno","maorio","mapigi","marini","masko","mateno","mazuto","meandro","meblo","mecenato","medialo","mefito","megafono","mejlo","mekanika","melodia","membro","mendi","mergi","mespilo","metoda","mevo","mezuri","miaflanke","micelio","mielo","migdalo","mikrofilmo","militi","mimiko","mineralo","miopa","miri","mistera","mitralo","mizeri","mjelo","mnemoniko","mobilizi","mocio","moderna","mohajro","mokadi","molaro","momento","monero","mopso","mordi","moskito","motoro","movimento","mozaiko","mueli","mukozo","muldi","mumio","munti","muro","muskolo","mutacio","muzikisto","nabo","nacio","nadlo","nafto","naiva","najbaro","nanometro","napo","narciso","naski","naturo","navigi","naztruo","neatendite","nebulo","necesa","nedankinde","neebla","nefari","negoco","nehavi","neimagebla","nektaro","nelonga","nematura","nenia","neordinara","nepra","nervuro","nesto","nete","neulo","nevino","nifo","nigra","nihilisto","nikotino","nilono","nimfeo","nitrogeno","nivelo","nobla","nocio","nodozo","nokto","nomkarto","norda","nostalgio","notbloko","novico","nuanco","nuboza","nuda","nugato","nuklea","nuligi","numero","nuntempe","nupto","nura","nutri","oazo","obei","objekto","oblikva","obolo","observi","obtuza","obuso","oceano","odekolono","odori","oferti","oficiala","ofsajdo","ofte","ogivo","ogro","ojstredoj","okaze","okcidenta","okro","oksido","oktobro","okulo","oldulo","oleo","olivo","omaro","ombro","omego","omikrono","omleto","omnibuso","onagro","ondo","oneco","onidire","onklino","onlajna","onomatopeo","ontologio","opaka","operacii","opinii","oportuna","opresi","optimisto","oratoro","orbito","ordinara","orelo","orfino","organizi","orienta","orkestro","orlo","orminejo","ornami","ortangulo","orumi","oscedi","osmozo","ostocerbo","ovalo","ovingo","ovoblanko","ovri","ovulado","ozono","pacama","padeli","pafilo","pagigi","pajlo","paketo","palaco","pampelmo","pantalono","papero","paroli","pasejo","patro","pavimo","peco","pedalo","peklita","pelikano","pensiono","peplomo","pesilo","petanto","pezoforto","piano","picejo","piede","pigmento","pikema","pilkoludo","pimento","pinglo","pioniro","pipromento","pirato","pistolo","pitoreska","piulo","pivoti","pizango","planko","plektita","plibonigi","ploradi","plurlingva","pobo","podio","poeto","pogranda","pohora","pokalo","politekniko","pomarbo","ponevosto","populara","porcelana","postkompreno","poteto","poviga","pozitiva","prapatroj","precize","pridemandi","probable","pruntanto","psalmo","psikologio","psoriazo","pterido","publiko","pudro","pufo","pugnobato","pulovero","pumpi","punkto","pupo","pureo","puso","putrema","puzlo","rabate","racionala","radiko","rafinado","raguo","rajto","rakonti","ralio","rampi","rando","rapida","rastruma","ratifiki","raviolo","razeno","reakcio","rebildo","recepto","redakti","reenigi","reformi","regiono","rehavi","reinspekti","rejesi","reklamo","relativa","rememori","renkonti","reorganizado","reprezenti","respondi","retumilo","reuzebla","revidi","rezulti","rialo","ribeli","ricevi","ridiga","rifuginto","rigardi","rikolti","rilati","rimarki","rinocero","ripozi","riski","ritmo","rivero","rizokampo","roboto","rododendro","rojo","rokmuziko","rolvorto","romantika","ronroni","rosino","rotondo","rovero","rozeto","rubando","rudimenta","rufa","rugbeo","ruino","ruleto","rumoro","runo","rupio","rura","rustimuna","ruzulo","sabato","sadismo","safario","sagaca","sakfluto","salti","samtage","sandalo","sapejo","sarongo","satelito","savano","sbiro","sciado","seanco","sebo","sedativo","segligno","sekretario","selektiva","semajno","senpeza","separeo","servilo","sesangulo","setli","seurigi","severa","sezono","sfagno","sfero","sfinkso","siatempe","siblado","sidejo","siesto","sifono","signalo","siklo","silenti","simpla","sinjoro","siropo","sistemo","situacio","siverto","sizifa","skatolo","skemo","skianto","sklavo","skorpio","skribisto","skulpti","skvamo","slango","sledeto","sliparo","smeraldo","smirgi","smokingo","smuto","snoba","snufegi","sobra","sociano","sodakvo","sofo","soifi","sojlo","soklo","soldato","somero","sonilo","sopiri","sorto","soulo","soveto","sparkado","speciala","spiri","splito","sporto","sprita","spuro","stabila","stelfiguro","stimulo","stomako","strato","studanto","subgrupo","suden","suferanta","sugesti","suito","sukero","sulko","sume","sunlumo","super","surskribeto","suspekti","suturo","svati","svenfali","svingi","svopo","tabako","taglumo","tajloro","taksimetro","talento","tamen","tanko","taoismo","tapioko","tarifo","tasko","tatui","taverno","teatro","tedlaboro","tegmento","tehoro","teknika","telefono","tempo","tenisejo","teorie","teraso","testudo","tetablo","teujo","tezo","tialo","tibio","tielnomata","tifono","tigro","tikli","timida","tinkturo","tiom","tiparo","tirkesto","titolo","tiutempe","tizano","tobogano","tofeo","togo","toksa","tolerema","tombolo","tondri","topografio","tordeti","tosti","totalo","traduko","tredi","triangulo","tropika","trumpeto","tualeto","tubisto","tufgrebo","tuja","tukano","tulipo","tumulto","tunelo","turisto","tusi","tutmonda","tvisto","udono","uesto","ukazo","ukelelo","ulcero","ulmo","ultimato","ululi","umbiliko","unco","ungego","uniformo","unkti","unukolora","uragano","urbano","uretro","urino","ursido","uskleco","usonigi","utero","utila","utopia","uverturo","uzadi","uzeblo","uzino","uzkutimo","uzofini","uzurpi","uzvaloro","vadejo","vafleto","vagono","vahabismo","vajco","vakcino","valoro","vampiro","vangharoj","vaporo","varma","vasta","vato","vazaro","veaspekta","vedismo","vegetalo","vehiklo","vejno","vekita","velstango","vemieno","vendi","vepro","verando","vespero","veturi","veziko","viando","vibri","vico","videbla","vifio","vigla","viktimo","vila","vimeno","vintro","violo","vippuno","virtuala","viskoza","vitro","viveca","viziti","vobli","vodko","vojeto","vokegi","volbo","vomema","vono","vortaro","vosto","voti","vrako","vringi","vualo","vulkano","vundo","vuvuzelo","zamenhofa","zapi","zebro","zefiro","zeloto","zenismo","zeolito","zepelino","zeto","zigzagi","zinko","zipo","zirkonio","zodiako","zoeto","zombio","zono","zoologio","zorgi","zukino","zumilo"] \ No newline at end of file diff --git a/mnemonic_languages/french.json b/mnemonic_languages/french.json new file mode 100644 index 0000000000..73b7c9b262 --- /dev/null +++ b/mnemonic_languages/french.json @@ -0,0 +1 @@ +["abandon","abattre","aboi","abolir","aborder","abri","absence","absolu","abuser","acacia","acajou","accent","accord","accrocher","accuser","acerbe","achat","acheter","acide","acier","acquis","acte","action","adage","adepte","adieu","admettre","admis","adorer","adresser","aduler","affaire","affirmer","afin","agacer","agent","agir","agiter","agonie","agrafe","agrume","aider","aigle","aigre","aile","ailleurs","aimant","aimer","ainsi","aise","ajouter","alarme","album","alcool","alerte","algue","alibi","aller","allumer","alors","amande","amener","amie","amorcer","amour","ample","amuser","ananas","ancien","anglais","angoisse","animal","anneau","annoncer","apercevoir","apparence","appel","apporter","apprendre","appuyer","arbre","arcade","arceau","arche","ardeur","argent","argile","aride","arme","armure","arracher","arriver","article","asile","aspect","assaut","assez","assister","assurer","astre","astuce","atlas","atroce","attacher","attente","attirer","aube","aucun","audace","auparavant","auquel","aurore","aussi","autant","auteur","autoroute","autre","aval","avant","avec","avenir","averse","aveu","avide","avion","avis","avoir","avouer","avril","azote","azur","badge","bagage","bague","bain","baisser","balai","balcon","balise","balle","bambou","banane","banc","bandage","banjo","banlieue","bannir","banque","baobab","barbe","barque","barrer","bassine","bataille","bateau","battre","baver","bavoir","bazar","beau","beige","berger","besoin","beurre","biais","biceps","bidule","bien","bijou","bilan","billet","blanc","blason","bleu","bloc","blond","bocal","boire","boiserie","boiter","bonbon","bondir","bonheur","bordure","borgne","borner","bosse","bouche","bouder","bouger","boule","bourse","bout","boxe","brader","braise","branche","braquer","bras","brave","brebis","brevet","brider","briller","brin","brique","briser","broche","broder","bronze","brosser","brouter","bruit","brute","budget","buffet","bulle","bureau","buriner","buste","buter","butiner","cabas","cabinet","cabri","cacao","cacher","cadeau","cadre","cage","caisse","caler","calme","camarade","camion","campagne","canal","canif","capable","capot","carat","caresser","carie","carpe","cartel","casier","casque","casserole","cause","cavale","cave","ceci","cela","celui","cendre","cent","cependant","cercle","cerise","cerner","certes","cerveau","cesser","chacun","chair","chaleur","chamois","chanson","chaque","charge","chasse","chat","chaud","chef","chemin","cheveu","chez","chicane","chien","chiffre","chiner","chiot","chlore","choc","choix","chose","chou","chute","cibler","cidre","ciel","cigale","cinq","cintre","cirage","cirque","ciseau","citation","citer","citron","civet","clairon","clan","classe","clavier","clef","climat","cloche","cloner","clore","clos","clou","club","cobra","cocon","coiffer","coin","colline","colon","combat","comme","compte","conclure","conduire","confier","connu","conseil","contre","convenir","copier","cordial","cornet","corps","cosmos","coton","couche","coude","couler","coupure","cour","couteau","couvrir","crabe","crainte","crampe","cran","creuser","crever","crier","crime","crin","crise","crochet","croix","cruel","cuisine","cuite","culot","culte","cumul","cure","curieux","cuve","dame","danger","dans","davantage","debout","dedans","dehors","delta","demain","demeurer","demi","dense","dent","depuis","dernier","descendre","dessus","destin","dette","deuil","deux","devant","devenir","devin","devoir","dicton","dieu","difficile","digestion","digue","diluer","dimanche","dinde","diode","dire","diriger","discours","disposer","distance","divan","divers","docile","docteur","dodu","dogme","doigt","dominer","donation","donjon","donner","dopage","dorer","dormir","doseur","douane","double","douche","douleur","doute","doux","douzaine","draguer","drame","drap","dresser","droit","duel","dune","duper","durant","durcir","durer","eaux","effacer","effet","effort","effrayant","elle","embrasser","emmener","emparer","empire","employer","emporter","enclos","encore","endive","endormir","endroit","enduit","enfant","enfermer","enfin","enfler","enfoncer","enfuir","engager","engin","enjeu","enlever","ennemi","ennui","ensemble","ensuite","entamer","entendre","entier","entourer","entre","envelopper","envie","envoyer","erreur","escalier","espace","espoir","esprit","essai","essor","essuyer","estimer","exact","examiner","excuse","exemple","exiger","exil","exister","exode","expliquer","exposer","exprimer","extase","fable","facette","facile","fade","faible","faim","faire","fait","falloir","famille","faner","farce","farine","fatigue","faucon","faune","faute","faux","faveur","favori","faxer","feinter","femme","fendre","fente","ferme","festin","feuille","feutre","fiable","fibre","ficher","fier","figer","figure","filet","fille","filmer","fils","filtre","final","finesse","finir","fiole","firme","fixe","flacon","flair","flamme","flan","flaque","fleur","flocon","flore","flot","flou","fluide","fluor","flux","focus","foin","foire","foison","folie","fonction","fondre","fonte","force","forer","forger","forme","fort","fosse","fouet","fouine","foule","four","foyer","frais","franc","frapper","freiner","frimer","friser","frite","froid","froncer","fruit","fugue","fuir","fuite","fumer","fureur","furieux","fuser","fusil","futile","futur","gagner","gain","gala","galet","galop","gamme","gant","garage","garde","garer","gauche","gaufre","gaule","gaver","gazon","geler","genou","genre","gens","gercer","germer","geste","gibier","gicler","gilet","girafe","givre","glace","glisser","globe","gloire","gluant","gober","golf","gommer","gorge","gosier","goutte","grain","gramme","grand","gras","grave","gredin","griffure","griller","gris","gronder","gros","grotte","groupe","grue","guerrier","guetter","guider","guise","habiter","hache","haie","haine","halte","hamac","hanche","hangar","hanter","haras","hareng","harpe","hasard","hausse","haut","havre","herbe","heure","hibou","hier","histoire","hiver","hochet","homme","honneur","honte","horde","horizon","hormone","houle","housse","hublot","huile","huit","humain","humble","humide","humour","hurler","idole","igloo","ignorer","illusion","image","immense","immobile","imposer","impression","incapable","inconnu","index","indiquer","infime","injure","inox","inspirer","instant","intention","intime","inutile","inventer","inviter","iode","iris","issue","ivre","jade","jadis","jamais","jambe","janvier","jardin","jauge","jaunisse","jeter","jeton","jeudi","jeune","joie","joindre","joli","joueur","journal","judo","juge","juillet","juin","jument","jungle","jupe","jupon","jurer","juron","jury","jusque","juste","kayak","ketchup","kilo","kiwi","koala","label","lacet","lacune","laine","laisse","lait","lame","lancer","lande","laque","lard","largeur","larme","larve","lasso","laver","lendemain","lentement","lequel","lettre","leur","lever","levure","liane","libre","lien","lier","lieutenant","ligne","ligoter","liguer","limace","limer","limite","lingot","lion","lire","lisser","litre","livre","lobe","local","logis","loin","loisir","long","loque","lors","lotus","louer","loup","lourd","louve","loyer","lubie","lucide","lueur","luge","luire","lundi","lune","lustre","lutin","lutte","luxe","machine","madame","magie","magnifique","magot","maigre","main","mairie","maison","malade","malheur","malin","manche","manger","manier","manoir","manquer","marche","mardi","marge","mariage","marquer","mars","masque","masse","matin","mauvais","meilleur","melon","membre","menacer","mener","mensonge","mentir","menu","merci","merlu","mesure","mettre","meuble","meunier","meute","miche","micro","midi","miel","miette","mieux","milieu","mille","mimer","mince","mineur","ministre","minute","mirage","miroir","miser","mite","mixte","mobile","mode","module","moins","mois","moment","momie","monde","monsieur","monter","moquer","moral","morceau","mordre","morose","morse","mortier","morue","motif","motte","moudre","moule","mourir","mousse","mouton","mouvement","moyen","muer","muette","mugir","muguet","mulot","multiple","munir","muret","muse","musique","muter","nacre","nager","nain","naissance","narine","narrer","naseau","nasse","nation","nature","naval","navet","naviguer","navrer","neige","nerf","nerveux","neuf","neutre","neuve","neveu","niche","nier","niveau","noble","noce","nocif","noir","nomade","nombre","nommer","nord","norme","notaire","notice","notre","nouer","nougat","nourrir","nous","nouveau","novice","noyade","noyer","nuage","nuance","nuire","nuit","nulle","nuque","oasis","objet","obliger","obscur","observer","obtenir","obus","occasion","occuper","ocre","octet","odeur","odorat","offense","officier","offrir","ogive","oiseau","olive","ombre","onctueux","onduler","ongle","onze","opter","option","orageux","oral","orange","orbite","ordinaire","ordre","oreille","organe","orgie","orgueil","orient","origan","orner","orteil","ortie","oser","osselet","otage","otarie","ouate","oublier","ouest","ours","outil","outre","ouvert","ouvrir","ovale","ozone","pacte","page","paille","pain","paire","paix","palace","palissade","palmier","palpiter","panda","panneau","papa","papier","paquet","parc","pardi","parfois","parler","parmi","parole","partir","parvenir","passer","pastel","patin","patron","paume","pause","pauvre","paver","pavot","payer","pays","peau","peigne","peinture","pelage","pelote","pencher","pendre","penser","pente","percer","perdu","perle","permettre","personne","perte","peser","pesticide","petit","peuple","peur","phase","photo","phrase","piano","pied","pierre","pieu","pile","pilier","pilote","pilule","piment","pincer","pinson","pinte","pion","piquer","pirate","pire","piste","piton","pitre","pivot","pizza","placer","plage","plaire","plan","plaque","plat","plein","pleurer","pliage","plier","plonger","plot","pluie","plume","plus","pneu","poche","podium","poids","poil","point","poire","poison","poitrine","poivre","police","pollen","pomme","pompier","poncer","pondre","pont","portion","poser","position","possible","poste","potage","potin","pouce","poudre","poulet","poumon","poupe","pour","pousser","poutre","pouvoir","prairie","premier","prendre","presque","preuve","prier","primeur","prince","prison","priver","prix","prochain","produire","profond","proie","projet","promener","prononcer","propre","prose","prouver","prune","public","puce","pudeur","puiser","pull","pulpe","puma","punir","purge","putois","quand","quartier","quasi","quatre","quel","question","queue","quiche","quille","quinze","quitter","quoi","rabais","raboter","race","racheter","racine","racler","raconter","radar","radio","rafale","rage","ragot","raideur","raie","rail","raison","ramasser","ramener","rampe","rance","rang","rapace","rapide","rapport","rarement","rasage","raser","rasoir","rassurer","rater","ratio","rature","ravage","ravir","rayer","rayon","rebond","recevoir","recherche","record","reculer","redevenir","refuser","regard","regretter","rein","rejeter","rejoindre","relation","relever","religion","remarquer","remettre","remise","remonter","remplir","remuer","rencontre","rendre","renier","renoncer","rentrer","renverser","repas","repli","reposer","reproche","requin","respect","ressembler","reste","retard","retenir","retirer","retour","retrouver","revenir","revoir","revue","rhume","ricaner","riche","rideau","ridicule","rien","rigide","rincer","rire","risquer","rituel","rivage","rive","robe","robot","robuste","rocade","roche","rodeur","rogner","roman","rompre","ronce","rondeur","ronger","roque","rose","rosir","rotation","rotule","roue","rouge","rouler","route","ruban","rubis","ruche","rude","ruelle","ruer","rugby","rugir","ruine","rumeur","rural","ruse","rustre","sable","sabot","sabre","sacre","sage","saint","saisir","salade","salive","salle","salon","salto","salut","salve","samba","sandale","sanguin","sapin","sarcasme","satisfaire","sauce","sauf","sauge","saule","sauna","sauter","sauver","savoir","science","scoop","score","second","secret","secte","seigneur","sein","seize","selle","selon","semaine","sembler","semer","semis","sensuel","sentir","sept","serpe","serrer","sertir","service","seuil","seulement","short","sien","sigle","signal","silence","silo","simple","singe","sinon","sinus","sioux","sirop","site","situation","skier","snob","sobre","social","socle","sodium","soigner","soir","soixante","soja","solaire","soldat","soleil","solide","solo","solvant","sombre","somme","somnoler","sondage","songeur","sonner","sorte","sosie","sottise","souci","soudain","souffrir","souhaiter","soulever","soumettre","soupe","sourd","soustraire","soutenir","souvent","soyeux","spectacle","sport","stade","stagiaire","stand","star","statue","stock","stop","store","style","suave","subir","sucre","suer","suffire","suie","suite","suivre","sujet","sulfite","supposer","surf","surprendre","surtout","surveiller","tabac","table","tabou","tache","tacler","tacot","tact","taie","taille","taire","talon","talus","tandis","tango","tanin","tant","taper","tapis","tard","tarif","tarot","tarte","tasse","taureau","taux","taverne","taxer","taxi","tellement","temple","tendre","tenir","tenter","tenu","terme","ternir","terre","test","texte","thym","tibia","tiers","tige","tipi","tique","tirer","tissu","titre","toast","toge","toile","toiser","toiture","tomber","tome","tonne","tonte","toque","torse","tortue","totem","toucher","toujours","tour","tousser","tout","toux","trace","train","trame","tranquille","travail","trembler","trente","tribu","trier","trio","tripe","triste","troc","trois","tromper","tronc","trop","trotter","trouer","truc","truite","tuba","tuer","tuile","turbo","tutu","tuyau","type","union","unique","unir","unisson","untel","urne","usage","user","usiner","usure","utile","vache","vague","vaincre","valeur","valoir","valser","valve","vampire","vaseux","vaste","veau","veille","veine","velours","velu","vendre","venir","vent","venue","verbe","verdict","version","vertige","verve","veste","veto","vexer","vice","victime","vide","vieil","vieux","vigie","vigne","ville","vingt","violent","virer","virus","visage","viser","visite","visuel","vitamine","vitrine","vivant","vivre","vocal","vodka","vogue","voici","voile","voir","voisin","voiture","volaille","volcan","voler","volt","votant","votre","vouer","vouloir","vous","voyage","voyou","vrac","vrai","yacht","yeti","yeux","yoga","zeste","zinc","zone","zoom"] \ No newline at end of file diff --git a/mnemonic_languages/german.json b/mnemonic_languages/german.json new file mode 100644 index 0000000000..6d0bb9dae3 --- /dev/null +++ b/mnemonic_languages/german.json @@ -0,0 +1 @@ +["Abakus","Abart","abbilden","Abbruch","Abdrift","Abendrot","Abfahrt","abfeuern","Abflug","abfragen","Abglanz","abhärten","abheben","Abhilfe","Abitur","Abkehr","Ablauf","ablecken","Ablösung","Abnehmer","abnutzen","Abonnent","Abrasion","Abrede","abrüsten","Absicht","Absprung","Abstand","absuchen","Abteil","Abundanz","abwarten","Abwurf","Abzug","Achse","Achtung","Acker","Aderlass","Adler","Admiral","Adresse","Affe","Affront","Afrika","Aggregat","Agilität","ähneln","Ahnung","Ahorn","Akazie","Akkord","Akrobat","Aktfoto","Aktivist","Albatros","Alchimie","Alemanne","Alibi","Alkohol","Allee","Allüre","Almosen","Almweide","Aloe","Alpaka","Alpental","Alphabet","Alpinist","Alraune","Altbier","Alter","Altflöte","Altruist","Alublech","Aludose","Amateur","Amazonas","Ameise","Amnesie","Amok","Ampel","Amphibie","Ampulle","Amsel","Amulett","Anakonda","Analogie","Ananas","Anarchie","Anatomie","Anbau","Anbeginn","anbieten","Anblick","ändern","andocken","Andrang","anecken","Anflug","Anfrage","Anführer","Angebot","Angler","Anhalter","Anhöhe","Animator","Anis","Anker","ankleben","Ankunft","Anlage","anlocken","Anmut","Annahme","Anomalie","Anonymus","Anorak","anpeilen","Anrecht","Anruf","Ansage","Anschein","Ansicht","Ansporn","Anteil","Antlitz","Antrag","Antwort","Anwohner","Aorta","Apfel","Appetit","Applaus","Aquarium","Arbeit","Arche","Argument","Arktis","Armband","Aroma","Asche","Askese","Asphalt","Asteroid","Ästhetik","Astronom","Atelier","Athlet","Atlantik","Atmung","Audienz","aufatmen","Auffahrt","aufholen","aufregen","Aufsatz","Auftritt","Aufwand","Augapfel","Auktion","Ausbruch","Ausflug","Ausgabe","Aushilfe","Ausland","Ausnahme","Aussage","Autobahn","Avocado","Axthieb","Bach","backen","Badesee","Bahnhof","Balance","Balkon","Ballett","Balsam","Banane","Bandage","Bankett","Barbar","Barde","Barett","Bargeld","Barkasse","Barriere","Bart","Bass","Bastler","Batterie","Bauch","Bauer","Bauholz","Baujahr","Baum","Baustahl","Bauteil","Bauweise","Bazar","beachten","Beatmung","beben","Becher","Becken","bedanken","beeilen","beenden","Beere","befinden","Befreier","Begabung","Begierde","begrüßen","Beiboot","Beichte","Beifall","Beigabe","Beil","Beispiel","Beitrag","beizen","bekommen","beladen","Beleg","bellen","belohnen","Bemalung","Bengel","Benutzer","Benzin","beraten","Bereich","Bergluft","Bericht","Bescheid","Besitz","besorgen","Bestand","Besuch","betanken","beten","betören","Bett","Beule","Beute","Bewegung","bewirken","Bewohner","bezahlen","Bezug","biegen","Biene","Bierzelt","bieten","Bikini","Bildung","Billard","binden","Biobauer","Biologe","Bionik","Biotop","Birke","Bison","Bitte","Biwak","Bizeps","blasen","Blatt","Blauwal","Blende","Blick","Blitz","Blockade","Blödelei","Blondine","Blues","Blume","Blut","Bodensee","Bogen","Boje","Bollwerk","Bonbon","Bonus","Boot","Bordarzt","Börse","Böschung","Boudoir","Boxkampf","Boykott","Brahms","Brandung","Brauerei","Brecher","Breitaxt","Bremse","brennen","Brett","Brief","Brigade","Brillanz","bringen","brodeln","Brosche","Brötchen","Brücke","Brunnen","Brüste","Brutofen","Buch","Büffel","Bugwelle","Bühne","Buletten","Bullauge","Bumerang","bummeln","Buntglas","Bürde","Burgherr","Bursche","Busen","Buslinie","Bussard","Butangas","Butter","Cabrio","campen","Captain","Cartoon","Cello","Chalet","Charisma","Chefarzt","Chiffon","Chipsatz","Chirurg","Chor","Chronik","Chuzpe","Clubhaus","Cockpit","Codewort","Cognac","Coladose","Computer","Coupon","Cousin","Cracking","Crash","Curry","Dach","Dackel","daddeln","daliegen","Dame","Dammbau","Dämon","Dampflok","Dank","Darm","Datei","Datsche","Datteln","Datum","Dauer","Daunen","Deckel","Decoder","Defekt","Degen","Dehnung","Deiche","Dekade","Dekor","Delfin","Demut","denken","Deponie","Design","Desktop","Dessert","Detail","Detektiv","Dezibel","Diadem","Diagnose","Dialekt","Diamant","Dichter","Dickicht","Diesel","Diktat","Diplom","Direktor","Dirne","Diskurs","Distanz","Docht","Dohle","Dolch","Domäne","Donner","Dorade","Dorf","Dörrobst","Dorsch","Dossier","Dozent","Drachen","Draht","Drama","Drang","Drehbuch","Dreieck","Dressur","Drittel","Drossel","Druck","Duell","Duft","Düne","Dünung","dürfen","Duschbad","Düsenjet","Dynamik","Ebbe","Echolot","Echse","Eckball","Edding","Edelweiß","Eden","Edition","Efeu","Effekte","Egoismus","Ehre","Eiablage","Eiche","Eidechse","Eidotter","Eierkopf","Eigelb","Eiland","Eilbote","Eimer","einatmen","Einband","Eindruck","Einfall","Eingang","Einkauf","einladen","Einöde","Einrad","Eintopf","Einwurf","Einzug","Eisbär","Eisen","Eishöhle","Eismeer","Eiweiß","Ekstase","Elan","Elch","Elefant","Eleganz","Element","Elfe","Elite","Elixier","Ellbogen","Eloquenz","Emigrant","Emission","Emotion","Empathie","Empfang","Endzeit","Energie","Engpass","Enkel","Enklave","Ente","entheben","Entität","entladen","Entwurf","Episode","Epoche","erachten","Erbauer","erblühen","Erdbeere","Erde","Erdgas","Erdkunde","Erdnuss","Erdöl","Erdteil","Ereignis","Eremit","erfahren","Erfolg","erfreuen","erfüllen","Ergebnis","erhitzen","erkalten","erkennen","erleben","Erlösung","ernähren","erneuern","Ernte","Eroberer","eröffnen","Erosion","Erotik","Erpel","erraten","Erreger","erröten","Ersatz","Erstflug","Ertrag","Eruption","erwarten","erwidern","Erzbau","Erzeuger","erziehen","Esel","Eskimo","Eskorte","Espe","Espresso","essen","Etage","Etappe","Etat","Ethik","Etikett","Etüde","Eule","Euphorie","Europa","Everest","Examen","Exil","Exodus","Extrakt","Fabel","Fabrik","Fachmann","Fackel","Faden","Fagott","Fahne","Faible","Fairness","Fakt","Fakultät","Falke","Fallobst","Fälscher","Faltboot","Familie","Fanclub","Fanfare","Fangarm","Fantasie","Farbe","Farmhaus","Farn","Fasan","Faser","Fassung","fasten","Faulheit","Fauna","Faust","Favorit","Faxgerät","Fazit","fechten","Federboa","Fehler","Feier","Feige","feilen","Feinripp","Feldbett","Felge","Fellpony","Felswand","Ferien","Ferkel","Fernweh","Ferse","Fest","Fettnapf","Feuer","Fiasko","Fichte","Fiktion","Film","Filter","Filz","Finanzen","Findling","Finger","Fink","Finnwal","Fisch","Fitness","Fixpunkt","Fixstern","Fjord","Flachbau","Flagge","Flamenco","Flanke","Flasche","Flaute","Fleck","Flegel","flehen","Fleisch","fliegen","Flinte","Flirt","Flocke","Floh","Floskel","Floß","Flöte","Flugzeug","Flunder","Flusstal","Flutung","Fockmast","Fohlen","Föhnlage","Fokus","folgen","Foliant","Folklore","Fontäne","Förde","Forelle","Format","Forscher","Fortgang","Forum","Fotograf","Frachter","Fragment","Fraktion","fräsen","Frauenpo","Freak","Fregatte","Freiheit","Freude","Frieden","Frohsinn","Frosch","Frucht","Frühjahr","Fuchs","Fügung","fühlen","Füller","Fundbüro","Funkboje","Funzel","Furnier","Fürsorge","Fusel","Fußbad","Futteral","Gabelung","gackern","Gage","gähnen","Galaxie","Galeere","Galopp","Gameboy","Gamsbart","Gandhi","Gang","Garage","Gardine","Garküche","Garten","Gasthaus","Gattung","gaukeln","Gazelle","Gebäck","Gebirge","Gebräu","Geburt","Gedanke","Gedeck","Gedicht","Gefahr","Gefieder","Geflügel","Gefühl","Gegend","Gehirn","Gehöft","Gehweg","Geige","Geist","Gelage","Geld","Gelenk","Gelübde","Gemälde","Gemeinde","Gemüse","genesen","Genuss","Gepäck","Geranie","Gericht","Germane","Geruch","Gesang","Geschenk","Gesetz","Gesindel","Gesöff","Gespan","Gestade","Gesuch","Getier","Getränk","Getümmel","Gewand","Geweih","Gewitter","Gewölbe","Geysir","Giftzahn","Gipfel","Giraffe","Gitarre","glänzen","Glasauge","Glatze","Gleis","Globus","Glück","glühen","Glutofen","Goldzahn","Gondel","gönnen","Gottheit","graben","Grafik","Grashalm","Graugans","greifen","Grenze","grillen","Groschen","Grotte","Grube","Grünalge","Gruppe","gruseln","Gulasch","Gummibär","Gurgel","Gürtel","Güterzug","Haarband","Habicht","hacken","hadern","Hafen","Hagel","Hähnchen","Haifisch","Haken","Halbaffe","Halsader","halten","Halunke","Handbuch","Hanf","Harfe","Harnisch","härten","Harz","Hasenohr","Haube","hauchen","Haupt","Haut","Havarie","Hebamme","hecheln","Heck","Hedonist","Heiler","Heimat","Heizung","Hektik","Held","helfen","Helium","Hemd","hemmen","Hengst","Herd","Hering","Herkunft","Hermelin","Herrchen","Herzdame","Heulboje","Hexe","Hilfe","Himbeere","Himmel","Hingabe","hinhören","Hinweis","Hirsch","Hirte","Hitzkopf","Hobel","Hochform","Hocker","hoffen","Hofhund","Hofnarr","Höhenzug","Hohlraum","Hölle","Holzboot","Honig","Honorar","horchen","Hörprobe","Höschen","Hotel","Hubraum","Hufeisen","Hügel","huldigen","Hülle","Humbug","Hummer","Humor","Hund","Hunger","Hupe","Hürde","Hurrikan","Hydrant","Hypnose","Ibis","Idee","Idiot","Igel","Illusion","Imitat","impfen","Import","Inferno","Ingwer","Inhalte","Inland","Insekt","Ironie","Irrfahrt","Irrtum","Isolator","Istwert","Jacke","Jade","Jagdhund","Jäger","Jaguar","Jahr","Jähzorn","Jazzfest","Jetpilot","jobben","Jochbein","jodeln","Jodsalz","Jolle","Journal","Jubel","Junge","Junimond","Jupiter","Jutesack","Juwel","Kabarett","Kabine","Kabuff","Käfer","Kaffee","Kahlkopf","Kaimauer","Kajüte","Kaktus","Kaliber","Kaltluft","Kamel","kämmen","Kampagne","Kanal","Känguru","Kanister","Kanone","Kante","Kanu","kapern","Kapitän","Kapuze","Karneval","Karotte","Käsebrot","Kasper","Kastanie","Katalog","Kathode","Katze","kaufen","Kaugummi","Kauz","Kehle","Keilerei","Keksdose","Kellner","Keramik","Kerze","Kessel","Kette","keuchen","kichern","Kielboot","Kindheit","Kinnbart","Kinosaal","Kiosk","Kissen","Klammer","Klang","Klapprad","Klartext","kleben","Klee","Kleinod","Klima","Klingel","Klippe","Klischee","Kloster","Klugheit","Klüngel","kneten","Knie","Knöchel","knüpfen","Kobold","Kochbuch","Kohlrabi","Koje","Kokosöl","Kolibri","Kolumne","Kombüse","Komiker","kommen","Konto","Konzept","Kopfkino","Kordhose","Korken","Korsett","Kosename","Krabbe","Krach","Kraft","Krähe","Kralle","Krapfen","Krater","kraulen","Kreuz","Krokodil","Kröte","Kugel","Kuhhirt","Kühnheit","Künstler","Kurort","Kurve","Kurzfilm","kuscheln","küssen","Kutter","Labor","lachen","Lackaffe","Ladeluke","Lagune","Laib","Lakritze","Lammfell","Land","Langmut","Lappalie","Last","Laterne","Latzhose","Laubsäge","laufen","Laune","Lausbub","Lavasee","Leben","Leder","Leerlauf","Lehm","Lehrer","leihen","Lektüre","Lenker","Lerche","Leseecke","Leuchter","Lexikon","Libelle","Libido","Licht","Liebe","liefern","Liftboy","Limonade","Lineal","Linoleum","List","Liveband","Lobrede","locken","Löffel","Logbuch","Logik","Lohn","Loipe","Lokal","Lorbeer","Lösung","löten","Lottofee","Löwe","Luchs","Luder","Luftpost","Luke","Lümmel","Lunge","lutschen","Luxus","Macht","Magazin","Magier","Magnet","mähen","Mahlzeit","Mahnmal","Maibaum","Maisbrei","Makel","malen","Mammut","Maniküre","Mantel","Marathon","Marder","Marine","Marke","Marmor","Märzluft","Maske","Maßanzug","Maßkrug","Mastkorb","Material","Matratze","Mauerbau","Maulkorb","Mäuschen","Mäzen","Medium","Meinung","melden","Melodie","Mensch","Merkmal","Messe","Metall","Meteor","Methode","Metzger","Mieze","Milchkuh","Mimose","Minirock","Minute","mischen","Missetat","mitgehen","Mittag","Mixtape","Möbel","Modul","mögen","Möhre","Molch","Moment","Monat","Mondflug","Monitor","Monokini","Monster","Monument","Moorhuhn","Moos","Möpse","Moral","Mörtel","Motiv","Motorrad","Möwe","Mühe","Mulatte","Müller","Mumie","Mund","Münze","Muschel","Muster","Mythos","Nabel","Nachtzug","Nackedei","Nagel","Nähe","Nähnadel","Namen","Narbe","Narwal","Nasenbär","Natur","Nebel","necken","Neffe","Neigung","Nektar","Nenner","Neptun","Nerz","Nessel","Nestbau","Netz","Neubau","Neuerung","Neugier","nicken","Niere","Nilpferd","nisten","Nocke","Nomade","Nordmeer","Notdurft","Notstand","Notwehr","Nudismus","Nuss","Nutzhanf","Oase","Obdach","Oberarzt","Objekt","Oboe","Obsthain","Ochse","Odyssee","Ofenholz","öffnen","Ohnmacht","Ohrfeige","Ohrwurm","Ökologie","Oktave","Ölberg","Olive","Ölkrise","Omelett","Onkel","Oper","Optiker","Orange","Orchidee","ordnen","Orgasmus","Orkan","Ortskern","Ortung","Ostasien","Ozean","Paarlauf","Packeis","paddeln","Paket","Palast","Pandabär","Panik","Panorama","Panther","Papagei","Papier","Paprika","Paradies","Parka","Parodie","Partner","Passant","Patent","Patzer","Pause","Pavian","Pedal","Pegel","peilen","Perle","Person","Pfad","Pfau","Pferd","Pfleger","Physik","Pier","Pilotwal","Pinzette","Piste","Plakat","Plankton","Platin","Plombe","plündern","Pobacke","Pokal","polieren","Popmusik","Porträt","Posaune","Postamt","Pottwal","Pracht","Pranke","Preis","Primat","Prinzip","Protest","Proviant","Prüfung","Pubertät","Pudding","Pullover","Pulsader","Punkt","Pute","Putsch","Puzzle","Python","quaken","Qualle","Quark","Quellsee","Querkopf","Quitte","Quote","Rabauke","Rache","Radclub","Radhose","Radio","Radtour","Rahmen","Rampe","Randlage","Ranzen","Rapsöl","Raserei","rasten","Rasur","Rätsel","Raubtier","Raumzeit","Rausch","Reaktor","Realität","Rebell","Rede","Reetdach","Regatta","Regen","Rehkitz","Reifen","Reim","Reise","Reizung","Rekord","Relevanz","Rennboot","Respekt","Restmüll","retten","Reue","Revolte","Rhetorik","Rhythmus","Richtung","Riegel","Rindvieh","Rippchen","Ritter","Robbe","Roboter","Rockband","Rohdaten","Roller","Roman","röntgen","Rose","Rosskur","Rost","Rotahorn","Rotglut","Rotznase","Rubrik","Rückweg","Rufmord","Ruhe","Ruine","Rumpf","Runde","Rüstung","rütteln","Saaltür","Saatguts","Säbel","Sachbuch","Sack","Saft","sagen","Sahneeis","Salat","Salbe","Salz","Sammlung","Samt","Sandbank","Sanftmut","Sardine","Satire","Sattel","Satzbau","Sauerei","Saum","Säure","Schall","Scheitel","Schiff","Schlager","Schmied","Schnee","Scholle","Schrank","Schulbus","Schwan","Seeadler","Seefahrt","Seehund","Seeufer","segeln","Sehnerv","Seide","Seilzug","Senf","Sessel","Seufzer","Sexgott","Sichtung","Signal","Silber","singen","Sinn","Sirup","Sitzbank","Skandal","Skikurs","Skipper","Skizze","Smaragd","Socke","Sohn","Sommer","Songtext","Sorte","Spagat","Spannung","Spargel","Specht","Speiseöl","Spiegel","Sport","spülen","Stadtbus","Stall","Stärke","Stativ","staunen","Stern","Stiftung","Stollen","Strömung","Sturm","Substanz","Südalpen","Sumpf","surfen","Tabak","Tafel","Tagebau","takeln","Taktung","Talsohle","Tand","Tanzbär","Tapir","Tarantel","Tarnname","Tasse","Tatnacht","Tatsache","Tatze","Taube","tauchen","Taufpate","Taumel","Teelicht","Teich","teilen","Tempo","Tenor","Terrasse","Testflug","Theater","Thermik","ticken","Tiefflug","Tierart","Tigerhai","Tinte","Tischler","toben","Toleranz","Tölpel","Tonband","Topf","Topmodel","Torbogen","Torlinie","Torte","Tourist","Tragesel","trampeln","Trapez","Traum","treffen","Trennung","Treue","Trick","trimmen","Trödel","Trost","Trumpf","tüfteln","Turban","Turm","Übermut","Ufer","Uhrwerk","umarmen","Umbau","Umfeld","Umgang","Umsturz","Unart","Unfug","Unimog","Unruhe","Unwucht","Uranerz","Urlaub","Urmensch","Utopie","Vakuum","Valuta","Vandale","Vase","Vektor","Ventil","Verb","Verdeck","Verfall","Vergaser","verhexen","Verlag","Vers","Vesper","Vieh","Viereck","Vinyl","Virus","Vitrine","Vollblut","Vorbote","Vorrat","Vorsicht","Vulkan","Wachstum","Wade","Wagemut","Wahlen","Wahrheit","Wald","Walhai","Wallach","Walnuss","Walzer","wandeln","Wanze","wärmen","Warnruf","Wäsche","Wasser","Weberei","wechseln","Wegegeld","wehren","Weiher","Weinglas","Weißbier","Weitwurf","Welle","Weltall","Werkbank","Werwolf","Wetter","wiehern","Wildgans","Wind","Wohl","Wohnort","Wolf","Wollust","Wortlaut","Wrack","Wunder","Wurfaxt","Wurst","Yacht","Yeti","Zacke","Zahl","zähmen","Zahnfee","Zäpfchen","Zaster","Zaumzeug","Zebra","zeigen","Zeitlupe","Zellkern","Zeltdach","Zensor","Zerfall","Zeug","Ziege","Zielfoto","Zimteis","Zobel","Zollhund","Zombie","Zöpfe","Zucht","Zufahrt","Zugfahrt","Zugvogel","Zündung","Zweck","Zyklop"] \ No newline at end of file diff --git a/mnemonic_languages/italian.json b/mnemonic_languages/italian.json new file mode 100644 index 0000000000..42a79850e4 --- /dev/null +++ b/mnemonic_languages/italian.json @@ -0,0 +1 @@ +["abbinare","abbonato","abisso","abitare","abominio","accadere","accesso","acciaio","accordo","accumulo","acido","acqua","acrobata","acustico","adattare","addetto","addio","addome","adeguato","aderire","adorare","adottare","adozione","adulto","aereo","aerobica","affare","affetto","affidare","affogato","affronto","africano","afrodite","agenzia","aggancio","aggeggio","aggiunta","agio","agire","agitare","aglio","agnello","agosto","aiutare","albero","albo","alce","alchimia","alcool","alfabeto","algebra","alimento","allarme","alleanza","allievo","alloggio","alluce","alpi","alterare","altro","aluminio","amante","amarezza","ambiente","ambrosia","america","amico","ammalare","ammirare","amnesia","amnistia","amore","ampliare","amputare","analisi","anamnesi","ananas","anarchia","anatra","anca","ancorato","andare","androide","aneddoto","anello","angelo","angolino","anguilla","anidride","anima","annegare","anno","annuncio","anomalia","antenna","anticipo","aperto","apostolo","appalto","appello","appiglio","applauso","appoggio","appurare","aprile","aquila","arabo","arachidi","aragosta","arancia","arbitrio","archivio","arco","argento","argilla","aria","ariete","arma","armonia","aroma","arrivare","arrosto","arsenale","arte","artiglio","asfalto","asfissia","asino","asparagi","aspirina","assalire","assegno","assolto","assurdo","asta","astratto","atlante","atletica","atomo","atropina","attacco","attesa","attico","atto","attrarre","auguri","aula","aumento","aurora","auspicio","autista","auto","autunno","avanzare","avarizia","avere","aviatore","avido","avorio","avvenire","avviso","avvocato","azienda","azione","azzardo","azzurro","babbuino","bacio","badante","baffi","bagaglio","bagliore","bagno","balcone","balena","ballare","balordo","balsamo","bambola","bancomat","banda","barato","barba","barista","barriera","basette","basilico","bassista","bastare","battello","bavaglio","beccare","beduino","bellezza","bene","benzina","berretto","bestia","bevitore","bianco","bibbia","biberon","bibita","bici","bidone","bilancia","biliardo","binario","binocolo","biologia","biondina","biopsia","biossido","birbante","birra","biscotto","bisogno","bistecca","bivio","blindare","bloccare","bocca","bollire","bombola","bonifico","borghese","borsa","bottino","botulino","braccio","bradipo","branco","bravo","bresaola","bretelle","brevetto","briciola","brigante","brillare","brindare","brivido","broccoli","brontolo","bruciare","brufolo","bucare","buddista","budino","bufera","buffo","bugiardo","buio","buono","burrone","bussola","bustina","buttare","cabernet","cabina","cacao","cacciare","cactus","cadavere","caffe","calamari","calcio","caldaia","calmare","calunnia","calvario","calzone","cambiare","camera","camion","cammello","campana","canarino","cancello","candore","cane","canguro","cannone","canoa","cantare","canzone","caos","capanna","capello","capire","capo","capperi","capra","capsula","caraffa","carbone","carciofo","cardigan","carenza","caricare","carota","carrello","carta","casa","cascare","caserma","cashmere","casino","cassetta","castello","catalogo","catena","catorcio","cattivo","causa","cauzione","cavallo","caverna","caviglia","cavo","cazzotto","celibato","cemento","cenare","centrale","ceramica","cercare","ceretta","cerniera","certezza","cervello","cessione","cestino","cetriolo","chiave","chiedere","chilo","chimera","chiodo","chirurgo","chitarra","chiudere","ciabatta","ciao","cibo","ciccia","cicerone","ciclone","cicogna","cielo","cifra","cigno","ciliegia","cimitero","cinema","cinque","cintura","ciondolo","ciotola","cipolla","cippato","circuito","cisterna","citofono","ciuccio","civetta","civico","clausola","cliente","clima","clinica","cobra","coccole","cocktail","cocomero","codice","coesione","cogliere","cognome","colla","colomba","colpire","coltello","comando","comitato","commedia","comodino","compagna","comune","concerto","condotto","conforto","congiura","coniglio","consegna","conto","convegno","coperta","copia","coprire","corazza","corda","corleone","cornice","corona","corpo","corrente","corsa","cortesia","corvo","coso","costume","cotone","cottura","cozza","crampo","cratere","cravatta","creare","credere","crema","crescere","crimine","criterio","croce","crollare","cronaca","crostata","croupier","cubetto","cucciolo","cucina","cultura","cuoco","cuore","cupido","cupola","cura","curva","cuscino","custode","danzare","data","decennio","decidere","decollo","dedicare","dedurre","definire","delegare","delfino","delitto","demone","dentista","denuncia","deposito","derivare","deserto","designer","destino","detonare","dettagli","diagnosi","dialogo","diamante","diario","diavolo","dicembre","difesa","digerire","digitare","diluvio","dinamica","dipinto","diploma","diramare","dire","dirigere","dirupo","discesa","disdetta","disegno","disporre","dissenso","distacco","dito","ditta","diva","divenire","dividere","divorare","docente","dolcetto","dolore","domatore","domenica","dominare","donatore","donna","dorato","dormire","dorso","dosaggio","dottore","dovere","download","dragone","dramma","dubbio","dubitare","duetto","durata","ebbrezza","eccesso","eccitare","eclissi","economia","edera","edificio","editore","edizione","educare","effetto","egitto","egiziano","elastico","elefante","eleggere","elemento","elenco","elezione","elmetto","elogio","embrione","emergere","emettere","eminenza","emisfero","emozione","empatia","energia","enfasi","enigma","entrare","enzima","epidemia","epilogo","episodio","epoca","equivoco","erba","erede","eroe","erotico","errore","eruzione","esaltare","esame","esaudire","eseguire","esempio","esigere","esistere","esito","esperto","espresso","essere","estasi","esterno","estrarre","eterno","etica","euforico","europa","evacuare","evasione","evento","evidenza","evitare","evolvere","fabbrica","facciata","fagiano","fagotto","falco","fame","famiglia","fanale","fango","fantasia","farfalla","farmacia","faro","fase","fastidio","faticare","fatto","favola","febbre","femmina","femore","fenomeno","fermata","feromoni","ferrari","fessura","festa","fiaba","fiamma","fianco","fiat","fibbia","fidare","fieno","figa","figlio","figura","filetto","filmato","filosofo","filtrare","finanza","finestra","fingere","finire","finta","finzione","fiocco","fioraio","firewall","firmare","fisico","fissare","fittizio","fiume","flacone","flagello","flirtare","flusso","focaccia","foglio","fognario","follia","fonderia","fontana","forbici","forcella","foresta","forgiare","formare","fornace","foro","fortuna","forzare","fosforo","fotoni","fracasso","fragola","frantumi","fratello","frazione","freccia","freddo","frenare","fresco","friggere","frittata","frivolo","frizione","fronte","frullato","frumento","frusta","frutto","fucile","fuggire","fulmine","fumare","funzione","fuoco","furbizia","furgone","furia","furore","fusibile","fuso","futuro","gabbiano","galassia","gallina","gamba","gancio","garanzia","garofano","gasolio","gatto","gazebo","gazzetta","gelato","gemelli","generare","genitori","gennaio","geologia","germania","gestire","gettare","ghepardo","ghiaccio","giaccone","giaguaro","giallo","giappone","giardino","gigante","gioco","gioiello","giorno","giovane","giraffa","giudizio","giurare","giusto","globo","gloria","glucosio","gnocca","gocciola","godere","gomito","gomma","gonfiare","gorilla","governo","gradire","graffiti","granchio","grappolo","grasso","grattare","gridare","grissino","grondaia","grugnito","gruppo","guadagno","guaio","guancia","guardare","gufo","guidare","guscio","gusto","icona","idea","identico","idolo","idoneo","idrante","idrogeno","igiene","ignoto","imbarco","immagine","immobile","imparare","impedire","impianto","importo","impresa","impulso","incanto","incendio","incidere","incontro","incrocia","incubo","indagare","indice","indotto","infanzia","inferno","infinito","infranto","ingerire","inglese","ingoiare","ingresso","iniziare","innesco","insalata","inserire","insicuro","insonnia","insulto","interno","introiti","invasori","inverno","invito","invocare","ipnosi","ipocrita","ipotesi","ironia","irrigare","iscritto","isola","ispirare","isterico","istinto","istruire","italiano","jazz","labbra","labrador","ladro","lago","lamento","lampone","lancetta","lanterna","lapide","larva","lasagne","lasciare","lastra","latte","laurea","lavagna","lavorare","leccare","legare","leggere","lenzuolo","leone","lepre","letargo","lettera","levare","levitare","lezione","liberare","libidine","libro","licenza","lievito","limite","lince","lingua","liquore","lire","listino","litigare","litro","locale","lottare","lucciola","lucidare","luglio","luna","macchina","madama","madre","maestro","maggio","magico","maglione","magnolia","mago","maialino","maionese","malattia","male","malloppo","mancare","mandorla","mangiare","manico","manopola","mansarda","mantello","manubrio","manzo","mappa","mare","margine","marinaio","marmotta","marocco","martello","marzo","maschera","matrice","maturare","mazzetta","meandri","medaglia","medico","medusa","megafono","melone","membrana","menta","mercato","meritare","merluzzo","mese","mestiere","metafora","meteo","metodo","mettere","miele","miglio","miliardo","mimetica","minatore","minuto","miracolo","mirtillo","missile","mistero","misura","mito","mobile","moda","moderare","moglie","molecola","molle","momento","moneta","mongolia","monologo","montagna","morale","morbillo","mordere","mosaico","mosca","mostro","motivare","moto","mulino","mulo","muovere","muraglia","muscolo","museo","musica","mutande","nascere","nastro","natale","natura","nave","navigare","negare","negozio","nemico","nero","nervo","nessuno","nettare","neutroni","neve","nevicare","nicotina","nido","nipote","nocciola","noleggio","nome","nonno","norvegia","notare","notizia","nove","nucleo","nuda","nuotare","nutrire","obbligo","occhio","occupare","oceano","odissea","odore","offerta","officina","offrire","oggetto","oggi","olfatto","olio","oliva","ombelico","ombrello","omuncolo","ondata","onore","opera","opinione","opuscolo","opzione","orario","orbita","orchidea","ordine","orecchio","orgasmo","orgoglio","origine","orologio","oroscopo","orso","oscurare","ospedale","ospite","ossigeno","ostacolo","ostriche","ottenere","ottimo","ottobre","ovest","pacco","pace","pacifico","padella","pagare","pagina","pagnotta","palazzo","palestra","palpebre","pancetta","panfilo","panino","pannello","panorama","papa","paperino","paradiso","parcella","parente","parlare","parodia","parrucca","partire","passare","pasta","patata","patente","patogeno","patriota","pausa","pazienza","peccare","pecora","pedalare","pelare","pena","pendenza","penisola","pennello","pensare","pentirsi","percorso","perdono","perfetto","perizoma","perla","permesso","persona","pesare","pesce","peso","petardo","petrolio","pezzo","piacere","pianeta","piastra","piatto","piazza","piccolo","piede","piegare","pietra","pigiama","pigliare","pigrizia","pilastro","pilota","pinguino","pioggia","piombo","pionieri","piovra","pipa","pirata","pirolisi","piscina","pisolino","pista","pitone","piumino","pizza","plastica","platino","poesia","poiana","polaroid","polenta","polimero","pollo","polmone","polpetta","poltrona","pomodoro","pompa","popolo","porco","porta","porzione","possesso","postino","potassio","potere","poverino","pranzo","prato","prefisso","prelievo","premio","prendere","prestare","pretesa","prezzo","primario","privacy","problema","processo","prodotto","profeta","progetto","promessa","pronto","proposta","proroga","prossimo","proteina","prova","prudenza","pubblico","pudore","pugilato","pulire","pulsante","puntare","pupazzo","puzzle","quaderno","qualcuno","quarzo","quercia","quintale","rabbia","racconto","radice","raffica","ragazza","ragione","rammento","ramo","rana","randagio","rapace","rapinare","rapporto","rasatura","ravioli","reagire","realista","reattore","reazione","recitare","recluso","record","recupero","redigere","regalare","regina","regola","relatore","reliquia","remare","rendere","reparto","resina","resto","rete","retorica","rettile","revocare","riaprire","ribadire","ribelle","ricambio","ricetta","richiamo","ricordo","ridurre","riempire","riferire","riflesso","righello","rilancio","rilevare","rilievo","rimanere","rimborso","rinforzo","rinuncia","riparo","ripetere","riposare","ripulire","risalita","riscatto","riserva","riso","rispetto","ritaglio","ritmo","ritorno","ritratto","rituale","riunione","riuscire","riva","robotica","rondine","rosa","rospo","rosso","rotonda","rotta","roulotte","rubare","rubrica","ruffiano","rumore","ruota","ruscello","sabbia","sacco","saggio","sale","salire","salmone","salto","salutare","salvia","sangue","sanzioni","sapere","sapienza","sarcasmo","sardine","sartoria","sbalzo","sbarcare","sberla","sborsare","scadenza","scafo","scala","scambio","scappare","scarpa","scatola","scelta","scena","sceriffo","scheggia","schiuma","sciarpa","scienza","scimmia","sciopero","scivolo","sclerare","scolpire","sconto","scopa","scordare","scossa","scrivere","scrupolo","scuderia","scultore","scuola","scusare","sdraiare","secolo","sedativo","sedere","sedia","segare","segreto","seguire","semaforo","seme","senape","seno","sentiero","separare","sepolcro","sequenza","serata","serpente","servizio","sesso","seta","settore","sfamare","sfera","sfidare","sfiorare","sfogare","sgabello","sicuro","siepe","sigaro","silenzio","silicone","simbiosi","simpatia","simulare","sinapsi","sindrome","sinergia","sinonimo","sintonia","sirena","siringa","sistema","sito","smalto","smentire","smontare","soccorso","socio","soffitto","software","soggetto","sogliola","sognare","soldi","sole","sollievo","solo","sommario","sondare","sonno","sorpresa","sorriso","sospiro","sostegno","sovrano","spaccare","spada","spagnolo","spalla","sparire","spavento","spazio","specchio","spedire","spegnere","spendere","speranza","spessore","spezzare","spiaggia","spiccare","spiegare","spiffero","spingere","sponda","sporcare","spostare","spremuta","spugna","spumante","spuntare","squadra","squillo","staccare","stadio","stagione","stallone","stampa","stancare","starnuto","statura","stella","stendere","sterzo","stilista","stimolo","stinco","stiva","stoffa","storia","strada","stregone","striscia","studiare","stufa","stupendo","subire","successo","sudare","suono","superare","supporto","surfista","sussurro","svelto","svenire","sviluppo","svolta","svuotare","tabacco","tabella","tabu","tacchino","tacere","taglio","talento","tangente","tappeto","tartufo","tassello","tastiera","tavolo","tazza","teatro","tedesco","telaio","telefono","tema","temere","tempo","tendenza","tenebre","tensione","tentare","teologia","teorema","termica","terrazzo","teschio","tesi","tesoro","tessera","testa","thriller","tifoso","tigre","timbrare","timido","tinta","tirare","tisana","titano","titolo","toccare","togliere","topolino","torcia","torrente","tovaglia","traffico","tragitto","training","tramonto","transito","trapezio","trasloco","trattore","trazione","treccia","tregua","treno","triciclo","tridente","trilogia","tromba","troncare","trota","trovare","trucco","tubo","tulipano","tumulto","tunisia","tuono","turista","tuta","tutelare","tutore","ubriaco","uccello","udienza","udito","uffa","umanoide","umore","unghia","unguento","unicorno","unione","universo","uomo","uragano","uranio","urlare","uscire","utente","utilizzo","vacanza","vacca","vaglio","vagonata","valle","valore","valutare","valvola","vampiro","vaniglia","vanto","vapore","variante","vasca","vaselina","vassoio","vedere","vegetale","veglia","veicolo","vela","veleno","velivolo","velluto","vendere","venerare","venire","vento","veranda","verbo","verdura","vergine","verifica","vernice","vero","verruca","versare","vertebra","vescica","vespaio","vestito","vesuvio","veterano","vetro","vetta","viadotto","viaggio","vibrare","vicenda","vichingo","vietare","vigilare","vigneto","villa","vincere","violino","vipera","virgola","virtuoso","visita","vita","vitello","vittima","vivavoce","vivere","viziato","voglia","volare","volpe","volto","volume","vongole","voragine","vortice","votare","vulcano","vuotare","zabaione","zaffiro","zainetto","zampa","zanzara","zattera","zavorra","zenzero","zero","zingaro","zittire","zoccolo","zolfo","zombie","zucchero"] \ No newline at end of file diff --git a/mnemonic_languages/lojban.json b/mnemonic_languages/lojban.json new file mode 100644 index 0000000000..ccd5bdd7f0 --- /dev/null +++ b/mnemonic_languages/lojban.json @@ -0,0 +1 @@ +["backi","bacru","badna","badri","bajra","bakfu","bakni","bakri","baktu","balji","balni","balre","balvi","bambu","bancu","bandu","banfi","bangu","banli","banro","banxa","banzu","bapli","barda","bargu","barja","barna","bartu","basfa","basna","basti","batci","batke","bavmi","baxso","bebna","bekpi","bemro","bende","bengo","benji","benre","benzo","bergu","bersa","berti","besna","besto","betfu","betri","bevri","bidju","bifce","bikla","bilga","bilma","bilni","bindo","binra","binxo","birje","birka","birti","bisli","bitmu","bitni","blabi","blaci","blanu","bliku","bloti","bolci","bongu","boske","botpi","boxfo","boxna","bradi","brano","bratu","brazo","bredi","bridi","brife","briju","brito","brivo","broda","bruna","budjo","bukpu","bumru","bunda","bunre","burcu","burna","cabna","cabra","cacra","cadga","cadzu","cafne","cagna","cakla","calku","calse","canci","cando","cange","canja","canko","canlu","canpa","canre","canti","carce","carfu","carmi","carna","cartu","carvi","casnu","catke","catlu","catni","catra","caxno","cecla","cecmu","cedra","cenba","censa","centi","cerda","cerni","certu","cevni","cfale","cfari","cfika","cfila","cfine","cfipu","ciblu","cicna","cidja","cidni","cidro","cifnu","cigla","cikna","cikre","ciksi","cilce","cilfu","cilmo","cilre","cilta","cimde","cimni","cinba","cindu","cinfo","cinje","cinki","cinla","cinmo","cinri","cinse","cinta","cinza","cipni","cipra","cirko","cirla","ciska","cisma","cisni","ciste","citka","citno","citri","citsi","civla","cizra","ckabu","ckafi","ckaji","ckana","ckape","ckasu","ckeji","ckiku","ckilu","ckini","ckire","ckule","ckunu","cladu","clani","claxu","cletu","clika","clinu","clira","clite","cliva","clupa","cmaci","cmalu","cmana","cmavo","cmene","cmeta","cmevo","cmila","cmima","cmoni","cnano","cnebo","cnemu","cnici","cnino","cnisa","cnita","cokcu","condi","conka","corci","cortu","cpacu","cpana","cpare","cpedu","cpina","cradi","crane","creka","crepu","cribe","crida","crino","cripu","crisa","critu","ctaru","ctebi","cteki","ctile","ctino","ctuca","cukla","cukre","cukta","culno","cumki","cumla","cunmi","cunso","cuntu","cupra","curmi","curnu","curve","cusku","cusna","cutci","cutne","cuxna","dacru","dacti","dadjo","dakfu","dakli","damba","damri","dandu","danfu","danlu","danmo","danre","dansu","danti","daplu","dapma","darca","dargu","darlu","darno","darsi","darxi","daski","dasni","daspo","dasri","datka","datni","datro","decti","degji","dejni","dekpu","dekto","delno","dembi","denci","denmi","denpa","dertu","derxi","desku","detri","dicma","dicra","didni","digno","dikca","diklo","dikni","dilcu","dilma","dilnu","dimna","dindi","dinju","dinko","dinso","dirba","dirce","dirgo","disko","ditcu","divzi","dizlo","djacu","djedi","djica","djine","djuno","donri","dotco","draci","drani","drata","drudi","dugri","dukse","dukti","dunda","dunja","dunku","dunli","dunra","dutso","dzena","dzipo","facki","fadni","fagri","falnu","famti","fancu","fange","fanmo","fanri","fanta","fanva","fanza","fapro","farka","farlu","farna","farvi","fasnu","fatci","fatne","fatri","febvi","fegli","femti","fendi","fengu","fenki","fenra","fenso","fepni","fepri","ferti","festi","fetsi","figre","filso","finpe","finti","firca","fisli","fizbu","flaci","flalu","flani","flecu","flese","fliba","flira","foldi","fonmo","fonxa","forca","forse","fraso","frati","fraxu","frica","friko","frili","frinu","friti","frumu","fukpi","fulta","funca","fusra","fuzme","gacri","gadri","galfi","galtu","galxe","ganlo","ganra","ganse","ganti","ganxo","ganzu","gapci","gapru","garna","gasnu","gaspo","gasta","genja","gento","genxu","gerku","gerna","gidva","gigdo","ginka","girzu","gismu","glare","gleki","gletu","glico","glife","glosa","gluta","gocti","gomsi","gotro","gradu","grafu","grake","grana","grasu","grava","greku","grusi","grute","gubni","gugde","gugle","gumri","gundi","gunka","gunma","gunro","gunse","gunta","gurni","guska","gusni","gusta","gutci","gutra","guzme","jabre","jadni","jakne","jalge","jalna","jalra","jamfu","jamna","janbe","janco","janli","jansu","janta","jarbu","jarco","jarki","jaspu","jatna","javni","jbama","jbari","jbena","jbera","jbini","jdari","jdice","jdika","jdima","jdini","jduli","jecta","jeftu","jegvo","jelca","jemna","jenca","jendu","jenmi","jensi","jerna","jersi","jerxo","jesni","jetce","jetnu","jgalu","jganu","jgari","jgena","jgina","jgira","jgita","jibni","jibri","jicla","jicmu","jijnu","jikca","jikfi","jikni","jikru","jilka","jilra","jimca","jimpe","jimte","jinci","jinda","jinga","jinku","jinme","jinru","jinsa","jinto","jinvi","jinzi","jipci","jipno","jirna","jisra","jitfa","jitro","jivbu","jivna","jmaji","jmifa","jmina","jmive","jonse","jordo","jorne","jubme","judri","jufra","jukni","jukpa","julne","julro","jundi","jungo","junla","junri","junta","jurme","jursa","jutsi","juxre","jvinu","jviso","kabri","kacma","kadno","kafke","kagni","kajde","kajna","kakne","kakpa","kalci","kalri","kalsa","kalte","kamju","kamni","kampu","kamre","kanba","kancu","kandi","kanji","kanla","kanpe","kanro","kansa","kantu","kanxe","karbi","karce","karda","kargu","karli","karni","katci","katna","kavbu","kazra","kecti","kekli","kelci","kelvo","kenka","kenra","kensa","kerfa","kerlo","kesri","ketco","ketsu","kevna","kibro","kicne","kijno","kilto","kinda","kinli","kisto","klaji","klaku","klama","klani","klesi","kliki","klina","kliru","kliti","klupe","kluza","kobli","kogno","kojna","kokso","kolme","komcu","konju","korbi","korcu","korka","korvo","kosmu","kosta","krali","kramu","krasi","krati","krefu","krici","krili","krinu","krixa","kruca","kruji","kruvi","kubli","kucli","kufra","kukte","kulnu","kumfa","kumte","kunra","kunti","kurfa","kurji","kurki","kuspe","kusru","labno","lacni","lacpu","lacri","ladru","lafti","lakne","lakse","laldo","lalxu","lamji","lanbi","lanci","landa","lanka","lanli","lanme","lante","lanxe","lanzu","larcu","larva","lasna","lastu","latmo","latna","lazni","lebna","lelxe","lenga","lenjo","lenku","lerci","lerfu","libjo","lidne","lifri","lijda","limfa","limna","lince","lindi","linga","linji","linsi","linto","lisri","liste","litce","litki","litru","livga","livla","logji","loglo","lojbo","loldi","lorxu","lubno","lujvo","luksi","lumci","lunbe","lunra","lunsa","luska","lusto","mabla","mabru","macnu","majga","makcu","makfa","maksi","malsi","mamta","manci","manfo","mango","manku","manri","mansa","manti","mapku","mapni","mapra","mapti","marbi","marce","marde","margu","marji","marna","marxa","masno","masti","matci","matli","matne","matra","mavji","maxri","mebri","megdo","mekso","melbi","meljo","melmi","menli","menre","mensi","mentu","merko","merli","metfo","mexno","midju","mifra","mikce","mikri","milti","milxe","minde","minji","minli","minra","mintu","mipri","mirli","misno","misro","mitre","mixre","mlana","mlatu","mleca","mledi","mluni","mogle","mokca","moklu","molki","molro","morji","morko","morna","morsi","mosra","mraji","mrilu","mruli","mucti","mudri","mugle","mukti","mulno","munje","mupli","murse","murta","muslo","mutce","muvdu","muzga","nabmi","nakni","nalci","namcu","nanba","nanca","nandu","nanla","nanmu","nanvi","narge","narju","natfe","natmi","natsi","navni","naxle","nazbi","nejni","nelci","nenri","nerde","nibli","nicfa","nicte","nikle","nilce","nimre","ninja","ninmu","nirna","nitcu","nivji","nixli","nobli","norgo","notci","nudle","nukni","nunmu","nupre","nurma","nusna","nutka","nutli","nuzba","nuzlo","pacna","pagbu","pagre","pajni","palci","palku","palma","palne","palpi","palta","pambe","pamga","panci","pandi","panje","panka","panlo","panpi","panra","pante","panzi","papri","parbi","pardu","parji","pastu","patfu","patlu","patxu","paznu","pelji","pelxu","pemci","penbi","pencu","pendo","penmi","pensi","pentu","perli","pesxu","petso","pevna","pezli","picti","pijne","pikci","pikta","pilda","pilji","pilka","pilno","pimlu","pinca","pindi","pinfu","pinji","pinka","pinsi","pinta","pinxe","pipno","pixra","plana","platu","pleji","plibu","plini","plipe","plise","plita","plixa","pluja","pluka","pluta","pocli","polje","polno","ponjo","ponse","poplu","porpi","porsi","porto","prali","prami","prane","preja","prenu","preri","preti","prije","prina","pritu","proga","prosa","pruce","pruni","pruri","pruxi","pulce","pulji","pulni","punji","punli","pupsu","purci","purdi","purmo","racli","ractu","radno","rafsi","ragbi","ragve","rakle","rakso","raktu","ralci","ralju","ralte","randa","rango","ranji","ranmi","ransu","ranti","ranxi","rapli","rarna","ratcu","ratni","rebla","rectu","rekto","remna","renro","renvi","respa","rexsa","ricfu","rigni","rijno","rilti","rimni","rinci","rindo","rinju","rinka","rinsa","rirci","rirni","rirxe","rismi","risna","ritli","rivbi","rokci","romge","romlo","ronte","ropno","rorci","rotsu","rozgu","ruble","rufsu","runme","runta","rupnu","rusko","rutni","sabji","sabnu","sacki","saclu","sadjo","sakci","sakli","sakta","salci","salpo","salri","salta","samcu","sampu","sanbu","sance","sanga","sanji","sanli","sanmi","sanso","santa","sarcu","sarji","sarlu","sarni","sarxe","saske","satci","satre","savru","sazri","sefsi","sefta","sekre","selci","selfu","semto","senci","sengi","senpi","senta","senva","sepli","serti","sesre","setca","sevzi","sfani","sfasa","sfofa","sfubu","sibli","siclu","sicni","sicpi","sidbo","sidju","sigja","sigma","sikta","silka","silna","simlu","simsa","simxu","since","sinma","sinso","sinxa","sipna","sirji","sirxo","sisku","sisti","sitna","sivni","skaci","skami","skapi","skari","skicu","skiji","skina","skori","skoto","skuba","skuro","slabu","slaka","slami","slanu","slari","slasi","sligu","slilu","sliri","slovo","sluji","sluni","smacu","smadi","smaji","smaka","smani","smela","smoka","smuci","smuni","smusu","snada","snanu","snidu","snime","snipa","snuji","snura","snuti","sobde","sodna","sodva","softo","solji","solri","sombo","sonci","sorcu","sorgu","sorni","sorta","sovda","spaji","spali","spano","spati","speni","spero","spisa","spita","spofu","spoja","spuda","sputu","sraji","sraku","sralo","srana","srasu","srera","srito","sruma","sruri","stace","stagi","staku","stali","stani","stapa","stasu","stati","steba","steci","stedu","stela","stero","stici","stidi","stika","stizu","stodi","stuna","stura","stuzi","sucta","sudga","sufti","suksa","sumji","sumne","sumti","sunga","sunla","surla","sutra","tabno","tabra","tadji","tadni","tagji","taksi","talsa","tamca","tamji","tamne","tanbo","tance","tanjo","tanko","tanru","tansi","tanxe","tapla","tarbi","tarci","tarla","tarmi","tarti","taske","tasmi","tasta","tatpi","tatru","tavla","taxfu","tcaci","tcadu","tcana","tcati","tcaxe","tcena","tcese","tcica","tcidu","tcika","tcila","tcima","tcini","tcita","temci","temse","tende","tenfa","tengu","terdi","terpa","terto","tifri","tigni","tigra","tikpa","tilju","tinbe","tinci","tinsa","tirna","tirse","tirxu","tisna","titla","tivni","tixnu","toknu","toldi","tonga","tordu","torni","torso","traji","trano","trati","trene","tricu","trina","trixe","troci","tsaba","tsali","tsani","tsapi","tsiju","tsina","tsuku","tubnu","tubra","tugni","tujli","tumla","tunba","tunka","tunlo","tunta","tuple","turko","turni","tutci","tutle","tutra","vacri","vajni","valsi","vamji","vamtu","vanbi","vanci","vanju","vasru","vasxu","vecnu","vedli","venfu","vensa","vente","vepre","verba","vibna","vidni","vidru","vifne","vikmi","viknu","vimcu","vindu","vinji","vinta","vipsi","virnu","viska","vitci","vitke","vitno","vlagi","vlile","vlina","vlipa","vofli","voksa","volve","vorme","vraga","vreji","vreta","vrici","vrude","vrusi","vubla","vujnu","vukna","vukro","xabju","xadba","xadji","xadni","xagji","xagri","xajmi","xaksu","xalbo","xalka","xalni","xamgu","xampo","xamsi","xance","xango","xanka","xanri","xansa","xanto","xarci","xarju","xarnu","xasli","xasne","xatra","xatsi","xazdo","xebni","xebro","xecto","xedja","xekri","xelso","xendo","xenru","xexso","xigzo","xindo","xinmo","xirma","xislu","xispo","xlali","xlura","xorbo","xorlo","xotli","xrabo","xrani","xriso","xrotu","xruba","xruki","xrula","xruti","xukmi","xulta","xunre","xurdo","xusra","xutla","zabna","zajba","zalvi","zanru","zarci","zargu","zasni","zasti","zbabu","zbani","zbasu","zbepi","zdani","zdile","zekri","zenba","zepti","zetro","zevla","zgadi","zgana","zgike","zifre","zinki","zirpu","zivle","zmadu","zmiku","zucna","zukte","zumri","zungi","zunle","zunti","zutse","zvati","zviki","jbobau","jbopre","karsna","cabdei","zunsna","gendra","glibau","nintadni","pavyseljirna","vlaste","selbri","latro'a","zdakemkulgu'a","mriste","selsku","fu'ivla","tolmo'i","snavei","xagmau","retsku","ckupau","skudji","smudra","prulamdei","vokta'a","tinju'i","jefyfa'o","bavlamdei","kinzga","jbocre","jbovla","xauzma","selkei","xuncku","spusku","jbogu'e","pampe'o","bripre","jbosnu","zi'evla","gimste","tolzdi","velski","samselpla","cnegau","velcki","selja'e","fasybau","zanfri","reisku","favgau","jbota'a","rejgau","malgli","zilkai","keidji","tersu'i","jbofi'e","cnima'o","mulgau","ningau","ponbau","mrobi'o","rarbau","zmanei","famyma'o","vacysai","jetmlu","jbonunsla","nunpe'i","fa'orma'o","crezenzu'e","jbojbe","cmicu'a","zilcmi","tolcando","zukcfu","depybu'i","mencre","matmau","nunctu","selma'o","titnanba","naldra","jvajvo","nunsnu","nerkla","cimjvo","muvgau","zipcpi","runbau","faumlu","terbri","balcu'e","dragau","smuvelcki","piksku","selpli","bregau","zvafa'i","ci'izra","noltruti'u","samtci","snaxa'a"] \ No newline at end of file diff --git a/mnemonic_languages/russian.json b/mnemonic_languages/russian.json new file mode 100644 index 0000000000..9b01b4fa5f --- /dev/null +++ b/mnemonic_languages/russian.json @@ -0,0 +1 @@ +["абажур","абзац","абонент","абрикос","абсурд","авангард","август","авиация","авоська","автор","агат","агент","агитатор","агнец","агония","агрегат","адвокат","адмирал","адрес","ажиотаж","азарт","азбука","азот","аист","айсберг","академия","аквариум","аккорд","акробат","аксиома","актер","акула","акция","алгоритм","алебарда","аллея","алмаз","алтарь","алфавит","алхимик","алый","альбом","алюминий","амбар","аметист","амнезия","ампула","амфора","анализ","ангел","анекдот","анимация","анкета","аномалия","ансамбль","антенна","апатия","апельсин","апофеоз","аппарат","апрель","аптека","арабский","арбуз","аргумент","арест","ария","арка","армия","аромат","арсенал","артист","архив","аршин","асбест","аскетизм","аспект","ассорти","астроном","асфальт","атака","ателье","атлас","атом","атрибут","аудитор","аукцион","аура","афера","афиша","ахинея","ацетон","аэропорт","бабушка","багаж","бадья","база","баклажан","балкон","бампер","банк","барон","бассейн","батарея","бахрома","башня","баян","бегство","бедро","бездна","бекон","белый","бензин","берег","беседа","бетонный","биатлон","библия","бивень","бигуди","бидон","бизнес","бикини","билет","бинокль","биология","биржа","бисер","битва","бицепс","благо","бледный","близкий","блок","блуждать","блюдо","бляха","бобер","богатый","бодрый","боевой","бокал","большой","борьба","босой","ботинок","боцман","бочка","боярин","брать","бревно","бригада","бросать","брызги","брюки","бублик","бугор","будущее","буква","бульвар","бумага","бунт","бурный","бусы","бутылка","буфет","бухта","бушлат","бывалый","быль","быстрый","быть","бюджет","бюро","бюст","вагон","важный","ваза","вакцина","валюта","вампир","ванная","вариант","вассал","вата","вафля","вахта","вдова","вдыхать","ведущий","веер","вежливый","везти","веко","великий","вена","верить","веселый","ветер","вечер","вешать","вещь","веяние","взаимный","взбучка","взвод","взгляд","вздыхать","взлетать","взмах","взнос","взор","взрыв","взывать","взятка","вибрация","визит","вилка","вино","вирус","висеть","витрина","вихрь","вишневый","включать","вкус","власть","влечь","влияние","влюблять","внешний","внимание","внук","внятный","вода","воевать","вождь","воздух","войти","вокзал","волос","вопрос","ворота","восток","впадать","впускать","врач","время","вручать","всадник","всеобщий","вспышка","встреча","вторник","вулкан","вурдалак","входить","въезд","выбор","вывод","выгодный","выделять","выезжать","выживать","вызывать","выигрыш","вылезать","выносить","выпивать","высокий","выходить","вычет","вышка","выяснять","вязать","вялый","гавань","гадать","газета","гаишник","галстук","гамма","гарантия","гастроли","гвардия","гвоздь","гектар","гель","генерал","геолог","герой","гешефт","гибель","гигант","гильза","гимн","гипотеза","гитара","глаз","глина","глоток","глубокий","глыба","глядеть","гнать","гнев","гнить","гном","гнуть","говорить","годовой","голова","гонка","город","гость","готовый","граница","грех","гриб","громкий","группа","грызть","грязный","губа","гудеть","гулять","гуманный","густой","гуща","давать","далекий","дама","данные","дарить","дать","дача","дверь","движение","двор","дебют","девушка","дедушка","дежурный","дезертир","действие","декабрь","дело","демократ","день","депутат","держать","десяток","детский","дефицит","дешевый","деятель","джаз","джинсы","джунгли","диалог","диван","диета","дизайн","дикий","динамика","диплом","директор","диск","дитя","дичь","длинный","дневник","добрый","доверие","договор","дождь","доза","документ","должен","домашний","допрос","дорога","доход","доцент","дочь","дощатый","драка","древний","дрожать","друг","дрянь","дубовый","дуга","дудка","дукат","дуло","думать","дупло","дурак","дуть","духи","душа","дуэт","дымить","дыня","дыра","дыханье","дышать","дьявол","дюжина","дюйм","дюна","дядя","дятел","егерь","единый","едкий","ежевика","ежик","езда","елка","емкость","ерунда","ехать","жадный","жажда","жалеть","жанр","жара","жать","жгучий","ждать","жевать","желание","жемчуг","женщина","жертва","жесткий","жечь","живой","жидкость","жизнь","жилье","жирный","житель","журнал","жюри","забывать","завод","загадка","задача","зажечь","зайти","закон","замечать","занимать","западный","зарплата","засыпать","затрата","захват","зацепка","зачет","защита","заявка","звать","звезда","звонить","звук","здание","здешний","здоровье","зебра","зевать","зеленый","земля","зенит","зеркало","зефир","зигзаг","зима","зиять","злак","злой","змея","знать","зной","зодчий","золотой","зомби","зона","зоопарк","зоркий","зрачок","зрение","зритель","зубной","зыбкий","зять","игла","иголка","играть","идея","идиот","идол","идти","иерархия","избрать","известие","изгонять","издание","излагать","изменять","износ","изоляция","изрядный","изучать","изымать","изящный","икона","икра","иллюзия","имбирь","иметь","имидж","иммунный","империя","инвестор","индивид","инерция","инженер","иномарка","институт","интерес","инфекция","инцидент","ипподром","ирис","ирония","искать","история","исходить","исчезать","итог","июль","июнь","кабинет","кавалер","кадр","казарма","кайф","кактус","калитка","камень","канал","капитан","картина","касса","катер","кафе","качество","каша","каюта","квартира","квинтет","квота","кедр","кекс","кенгуру","кепка","керосин","кетчуп","кефир","кибитка","кивнуть","кидать","километр","кино","киоск","кипеть","кирпич","кисть","китаец","класс","клетка","клиент","клоун","клуб","клык","ключ","клятва","книга","кнопка","кнут","князь","кобура","ковер","коготь","кодекс","кожа","козел","койка","коктейль","колено","компания","конец","копейка","короткий","костюм","котел","кофе","кошка","красный","кресло","кричать","кровь","крупный","крыша","крючок","кубок","кувшин","кудрявый","кузов","кукла","культура","кумир","купить","курс","кусок","кухня","куча","кушать","кювет","лабиринт","лавка","лагерь","ладонь","лазерный","лайнер","лакей","лампа","ландшафт","лапа","ларек","ласковый","лауреат","лачуга","лаять","лгать","лебедь","левый","легкий","ледяной","лежать","лекция","лента","лепесток","лесной","лето","лечь","леший","лживый","либерал","ливень","лига","лидер","ликовать","лиловый","лимон","линия","липа","лирика","лист","литр","лифт","лихой","лицо","личный","лишний","лобовой","ловить","логика","лодка","ложка","лозунг","локоть","ломать","лоно","лопата","лорд","лось","лоток","лохматый","лошадь","лужа","лукавый","луна","лупить","лучший","лыжный","лысый","львиный","льгота","льдина","любить","людской","люстра","лютый","лягушка","магазин","мадам","мазать","майор","максимум","мальчик","манера","март","масса","мать","мафия","махать","мачта","машина","маэстро","маяк","мгла","мебель","медведь","мелкий","мемуары","менять","мера","место","метод","механизм","мечтать","мешать","миграция","мизинец","микрофон","миллион","минута","мировой","миссия","митинг","мишень","младший","мнение","мнимый","могила","модель","мозг","мойка","мокрый","молодой","момент","монах","море","мост","мотор","мохнатый","мочь","мошенник","мощный","мрачный","мстить","мудрый","мужчина","музыка","мука","мумия","мундир","муравей","мусор","мутный","муфта","муха","мучить","мушкетер","мыло","мысль","мыть","мычать","мышь","мэтр","мюзикл","мягкий","мякиш","мясо","мятый","мячик","набор","навык","нагрузка","надежда","наемный","нажать","называть","наивный","накрыть","налог","намерен","наносить","написать","народ","натура","наука","нация","начать","небо","невеста","негодяй","неделя","нежный","незнание","нелепый","немалый","неправда","нервный","нести","нефть","нехватка","нечистый","неясный","нива","нижний","низкий","никель","нирвана","нить","ничья","ниша","нищий","новый","нога","ножницы","ноздря","ноль","номер","норма","нота","ночь","ноша","ноябрь","нрав","нужный","нутро","нынешний","нырнуть","ныть","нюанс","нюхать","няня","оазис","обаяние","обвинять","обгонять","обещать","обжигать","обзор","обида","область","обмен","обнимать","оборона","образ","обучение","обходить","обширный","общий","объект","обычный","обязать","овальный","овес","овощи","овраг","овца","овчарка","огненный","огонь","огромный","огурец","одежда","одинокий","одобрить","ожидать","ожог","озарение","озеро","означать","оказать","океан","оклад","окно","округ","октябрь","окурок","олень","опасный","операция","описать","оплата","опора","оппонент","опрос","оптимизм","опускать","опыт","орать","орбита","орган","орден","орел","оригинал","оркестр","орнамент","оружие","осадок","освещать","осень","осина","осколок","осмотр","основной","особый","осуждать","отбор","отвечать","отдать","отец","отзыв","открытие","отмечать","относить","отпуск","отрасль","отставка","оттенок","отходить","отчет","отъезд","офицер","охапка","охота","охрана","оценка","очаг","очередь","очищать","очки","ошейник","ошибка","ощущение","павильон","падать","паек","пакет","палец","память","панель","папка","партия","паспорт","патрон","пауза","пафос","пахнуть","пациент","пачка","пашня","певец","педагог","пейзаж","пельмень","пенсия","пепел","период","песня","петля","пехота","печать","пешеход","пещера","пианист","пиво","пиджак","пиковый","пилот","пионер","пирог","писать","пить","пицца","пишущий","пища","план","плечо","плита","плохой","плыть","плюс","пляж","победа","повод","погода","подумать","поехать","пожимать","позиция","поиск","покой","получать","помнить","пони","поощрять","попадать","порядок","пост","поток","похожий","поцелуй","почва","пощечина","поэт","пояснить","право","предмет","проблема","пруд","прыгать","прямой","психолог","птица","публика","пугать","пудра","пузырь","пуля","пункт","пурга","пустой","путь","пухлый","пучок","пушистый","пчела","пшеница","пыль","пытка","пыхтеть","пышный","пьеса","пьяный","пятно","работа","равный","радость","развитие","район","ракета","рамка","ранний","рапорт","рассказ","раунд","рация","рвать","реальный","ребенок","реветь","регион","редакция","реестр","режим","резкий","рейтинг","река","религия","ремонт","рента","реплика","ресурс","реформа","рецепт","речь","решение","ржавый","рисунок","ритм","рифма","робкий","ровный","рогатый","родитель","рождение","розовый","роковой","роль","роман","ронять","рост","рота","роща","рояль","рубль","ругать","руда","ружье","руины","рука","руль","румяный","русский","ручка","рыба","рывок","рыдать","рыжий","рынок","рысь","рыть","рыхлый","рыцарь","рычаг","рюкзак","рюмка","рябой","рядовой","сабля","садовый","сажать","салон","самолет","сани","сапог","сарай","сатира","сауна","сахар","сбегать","сбивать","сбор","сбыт","свадьба","свет","свидание","свобода","связь","сгорать","сдвигать","сеанс","северный","сегмент","седой","сезон","сейф","секунда","сельский","семья","сентябрь","сердце","сеть","сечение","сеять","сигнал","сидеть","сизый","сила","символ","синий","сирота","система","ситуация","сиять","сказать","скважина","скелет","скидка","склад","скорый","скрывать","скучный","слава","слеза","слияние","слово","случай","слышать","слюна","смех","смирение","смотреть","смутный","смысл","смятение","снаряд","снег","снижение","сносить","снять","событие","совет","согласие","сожалеть","сойти","сокол","солнце","сомнение","сонный","сообщать","соперник","сорт","состав","сотня","соус","социолог","сочинять","союз","спать","спешить","спина","сплошной","способ","спутник","средство","срок","срывать","стать","ствол","стена","стихи","сторона","страна","студент","стыд","субъект","сувенир","сугроб","судьба","суета","суждение","сукно","сулить","сумма","сунуть","супруг","суровый","сустав","суть","сухой","суша","существо","сфера","схема","сцена","счастье","счет","считать","сшивать","съезд","сынок","сыпать","сырье","сытый","сыщик","сюжет","сюрприз","таблица","таежный","таинство","тайна","такси","талант","таможня","танец","тарелка","таскать","тахта","тачка","таять","тварь","твердый","творить","театр","тезис","текст","тело","тема","тень","теория","теплый","терять","тесный","тетя","техника","течение","тигр","типичный","тираж","титул","тихий","тишина","ткань","товарищ","толпа","тонкий","топливо","торговля","тоска","точка","тощий","традиция","тревога","трибуна","трогать","труд","трюк","тряпка","туалет","тугой","туловище","туман","тундра","тупой","турнир","тусклый","туфля","туча","туша","тыкать","тысяча","тьма","тюльпан","тюрьма","тяга","тяжелый","тянуть","убеждать","убирать","убогий","убыток","уважение","уверять","увлекать","угнать","угол","угроза","удар","удивлять","удобный","уезд","ужас","ужин","узел","узкий","узнавать","узор","уйма","уклон","укол","уксус","улетать","улица","улучшать","улыбка","уметь","умиление","умный","умолять","умысел","унижать","уносить","уныние","упасть","уплата","упор","упрекать","упускать","уран","урна","уровень","усадьба","усердие","усилие","ускорять","условие","усмешка","уснуть","успеть","усыпать","утешать","утка","уточнять","утро","утюг","уходить","уцелеть","участие","ученый","учитель","ушко","ущерб","уютный","уяснять","фабрика","фаворит","фаза","файл","факт","фамилия","фантазия","фара","фасад","февраль","фельдшер","феномен","ферма","фигура","физика","фильм","финал","фирма","фишка","флаг","флейта","флот","фокус","фольклор","фонд","форма","фото","фраза","фреска","фронт","фрукт","функция","фуражка","футбол","фыркать","халат","хамство","хаос","характер","хата","хватать","хвост","хижина","хилый","химия","хирург","хитрый","хищник","хлам","хлеб","хлопать","хмурый","ходить","хозяин","хоккей","холодный","хороший","хотеть","хохотать","храм","хрен","хриплый","хроника","хрупкий","художник","хулиган","хутор","царь","цвет","цель","цемент","центр","цепь","церковь","цикл","цилиндр","циничный","цирк","цистерна","цитата","цифра","цыпленок","чадо","чайник","часть","чашка","человек","чемодан","чепуха","черный","честь","четкий","чехол","чиновник","число","читать","членство","чреватый","чтение","чувство","чугунный","чудо","чужой","чукча","чулок","чума","чуткий","чучело","чушь","шаблон","шагать","шайка","шакал","шалаш","шампунь","шанс","шапка","шарик","шасси","шатер","шахта","шашлык","швейный","швырять","шевелить","шедевр","шейка","шелковый","шептать","шерсть","шестерка","шикарный","шинель","шипеть","широкий","шить","шишка","шкаф","школа","шкура","шланг","шлем","шлюпка","шляпа","шнур","шоколад","шорох","шоссе","шофер","шпага","шпион","шприц","шрам","шрифт","штаб","штора","штраф","штука","штык","шуба","шуметь","шуршать","шутка","щадить","щедрый","щека","щель","щенок","щепка","щетка","щука","эволюция","эгоизм","экзамен","экипаж","экономия","экран","эксперт","элемент","элита","эмблема","эмигрант","эмоция","энергия","эпизод","эпоха","эскиз","эссе","эстрада","этап","этика","этюд","эфир","эффект","эшелон","юбилей","юбка","южный","юмор","юноша","юрист","яблоко","явление","ягода","ядерный","ядовитый","ядро","язва","язык","яйцо","якорь","январь","японец","яркий","ярмарка","ярость","ярус","ясный","яхта","ячейка","ящик"] \ No newline at end of file diff --git a/package.json b/package.json index cc6ab60341..34d44bb26c 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { - "name": "loki-messenger-desktop", - "productName": "Loki Messenger", + "name": "session-messenger-desktop", + "productName": "Session", "description": "Private messaging from your desktop", - "repository": "https://github.com/sloki-project/loki-messenger.git", - "version": "0.0.0-beta1", + "repository": "https://github.com/loki-project/loki-messenger.git", + "version": "1.0.0", "license": "GPL-3.0", "author": { "name": "Loki Project", @@ -13,12 +13,19 @@ "scripts": { "postinstall": "electron-builder install-app-deps && rimraf node_modules/dtrace-provider", "start": "electron .", - "start-multi": "NODE_APP_INSTANCE=1 electron .", + "start-multi": "cross-env NODE_APP_INSTANCE=1 electron .", + "start-multi2": "cross-env NODE_APP_INSTANCE=2 electron .", + "start-prod": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod LOKI_DEV=1 electron .", + "start-prod-multi": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod1 LOKI_DEV=1 electron .", + "start-swarm-test": "cross-env NODE_ENV=swarm-testing NODE_APP_INSTANCE=test1 LOKI_DEV=1 electron .", + "start-swarm-test-2": "cross-env NODE_ENV=swarm-testing2 NODE_APP_INSTANCE=test2 LOKI_DEV=1 electron .", "grunt": "grunt", "icon-gen": "electron-icon-maker --input=images/icon_1024.png --output=./build", "generate": "yarn icon-gen && yarn grunt", - "build": "build --config.extraMetadata.environment=$SIGNAL_ENV", - "build-release": "SIGNAL_ENV=production npm run build -- --config.directories.output=release", + "build": "electron-builder --config.extraMetadata.environment=$SIGNAL_ENV", + "build-release": "cross-env SIGNAL_ENV=production npm run build -- --config.directories.output=release", + "make:linux:x64:appimage": "electron-builder build --linux appimage --x64", + "sign-release": "node ts/updater/generateSignature.js", "build-module-protobuf": "pbjs --target static-module --wrap commonjs --out ts/protobuf/compiled.js protos/*.proto && pbts --out ts/protobuf/compiled.d.ts ts/protobuf/compiled.js", "clean-module-protobuf": "rm -f ts/protobuf/compiled.d.ts ts/protobuf/compiled.js", "build-protobuf": "yarn build-module-protobuf", @@ -31,24 +38,32 @@ "test-lib-view": "NODE_ENV=test-lib yarn run start", "test-loki-view": "NODE_ENV=test-loki yarn run start", "test-electron": "yarn grunt test", - "test-node": "mocha --recursive test/app test/modules ts/test libloki/test/node", + "test-node": "mocha --recursive --exit test/app test/modules ts/test libloki/test/node", "test-node-coverage": "nyc --reporter=lcov --reporter=text mocha --recursive test/app test/modules ts/test libloki/test/node", - "test-node-coverage-html": "nyc --reporter=lcov --reporter=html mocha --recursive test/app test/modules ts/test libloki/test/node", - "eslint": "eslint .", + "test-node-coverage-html": "nyc --reporter=lcov --reporter=html mocha --recursive test/a/* */pp test/modules ts/test libloki/test/node", + "eslint": "eslint --cache .", + "eslint-full": "eslint .", "lint": "yarn format --list-different && yarn lint-windows", + "lint-full": "yarn format-full --list-different; yarn lint-windows-full", + "dev-lint": "yarn format --list-different; yarn lint-windows", "lint-windows": "yarn eslint && yarn tslint", + "lint-windows-full": "yarn eslint-full && yarn tslint", "lint-deps": "node ts/util/lint/linter.js", "tslint": "tslint --format stylish --project .", - "format": "prettier --write \"*.{css,js,json,md,scss,ts,tsx}\" \"./**/*.{css,js,json,md,scss,ts,tsx}\"", + "format": "prettier --write `git ls-files --modified *.{css,js,json,md,scss,ts,tsx}` `git ls-files --modified ./**/*.{css,js,json,md,scss,ts,tsx}`", + "format-full": "prettier --write \"*.{css,js,json,md,scss,ts,tsx}\" \"./**/*.{css,js,json,md,scss,ts,tsx}\"", "transpile": "tsc", - "clean-transpile": "rimraf ts/**/*.js ts/*.js", + "clean-transpile": "rimraf ts/**/*.js && rimraf ts/*.js", "open-coverage": "open coverage/lcov-report/index.html", "styleguide": "styleguidist server", - "pow-metrics": "node metrics_app.js localhost 9000" + "pow-metrics": "node metrics_app.js localhost 9000", + "ready": "yarn clean-transpile && yarn grunt && yarn lint-full && yarn test-node && yarn test-electron && yarn lint-deps" }, "dependencies": { - "@journeyapps/sqlcipher": "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#36149a4b03ccf11ec18b9205e1bfd9056015cf07", + "@journeyapps/sqlcipher": "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#2e28733b61640556b0272a3bfc78b0357daf71e6", "@sindresorhus/is": "0.8.0", + "@types/dompurify": "^2.0.0", + "@types/rc-slider": "^8.6.5", "backbone": "1.3.3", "blob-util": "1.3.0", "blueimp-canvas-to-blob": "3.14.0", @@ -56,26 +71,30 @@ "buffer-crc32": "0.2.13", "bunyan": "1.8.12", "classnames": "2.2.5", + "color": "^3.1.2", "config": "1.28.1", + "cross-env": "^6.0.3", + "dompurify": "^2.0.7", + "electron-context-menu": "^0.15.0", "electron-editor-context-menu": "1.1.1", "electron-is-dev": "0.3.0", - "electron-updater": "4.0.6", + "electron-localshortcut": "^3.2.1", "emoji-datasource": "4.0.0", "emoji-datasource-apple": "4.0.0", "emoji-js": "3.4.0", "emoji-panel": "https://github.com/scottnonnenberg-signal/emoji-panel.git#v0.5.5", "filesize": "3.6.1", "firstline": "1.2.1", - "form-data": "2.3.2", + "form-data": "^3.0.0", "fs-extra": "5.0.0", "glob": "7.1.2", - "google-libphonenumber": "3.0.7", + "google-libphonenumber": "3.2.2", "got": "8.2.0", "he": "1.2.0", - "identicon.js": "2.3.3", "intl-tel-input": "12.1.15", "jquery": "3.3.1", "js-sha512": "0.8.0", + "js-yaml": "3.13.0", "jsbn": "1.1.0", "libsodium-wrappers": "^0.7.4", "linkify-it": "2.0.3", @@ -88,15 +107,24 @@ "node-sass": "4.9.3", "os-locale": "2.1.0", "pify": "3.0.0", - "protobufjs": "~6.8.6", + "protobufjs": "6.8.6", "proxy-agent": "3.0.3", - "react": "16.2.0", - "react-contextmenu": "2.10.0", - "react-dom": "16.2.0", + "rc-slider": "^8.7.1", + "react": "16.8.3", + "react-contextmenu": "2.11.0", + "react-dom": "16.8.3", + "react-portal": "^4.2.0", + "react-qr-svg": "^2.2.1", + "react-redux": "6.0.1", + "react-virtualized": "9.21.0", "read-last-lines": "1.3.0", + "redux": "4.0.1", + "redux-logger": "3.0.6", + "redux-promise-middleware": "6.1.0", + "reselect": "4.0.0", "rimraf": "2.6.2", "semver": "5.4.1", - "spellchecker": "3.4.4", + "spellchecker": "3.5.1", "tar": "4.4.8", "testcheck": "1.0.0-rc.2", "tmp": "0.0.33", @@ -108,25 +136,39 @@ "devDependencies": { "@types/chai": "4.1.2", "@types/classnames": "2.2.3", + "@types/color": "^3.0.0", + "@types/config": "0.0.34", "@types/filesize": "3.6.0", + "@types/fs-extra": "5.0.5", "@types/google-libphonenumber": "7.4.14", - "@types/jquery": "3.3.1", + "@types/got": "9.4.1", + "@types/jquery": "3.3.29", + "@types/js-yaml": "3.12.0", "@types/linkify-it": "2.0.3", "@types/lodash": "4.14.106", + "@types/mkdirp": "0.5.2", "@types/mocha": "5.0.0", + "@types/pify": "3.0.2", "@types/qs": "6.5.1", - "@types/react": "16.3.1", - "@types/react-dom": "16.0.4", + "@types/react": "16.8.5", + "@types/react-dom": "16.8.2", + "@types/react-portal": "^4.0.2", + "@types/react-redux": "7.0.1", + "@types/react-virtualized": "9.18.12", + "@types/redux-logger": "3.0.7", + "@types/rimraf": "2.0.2", "@types/semver": "5.5.0", "@types/sinon": "4.3.1", + "@types/uuid": "3.4.4", "arraybuffer-loader": "1.0.3", "asar": "0.14.0", - "axios": "0.18.0", "bower": "1.8.2", "chai": "4.1.2", - "electron": "3.0.14", - "electron-builder": "20.38.5", + "dashdash": "1.14.1", + "electron": "4.1.2", + "electron-builder": "21.2.0", "electron-icon-maker": "0.0.3", + "electron-notarize": "^0.2.0", "eslint": "4.14.0", "eslint-config-airbnb-base": "12.1.0", "eslint-config-prettier": "2.9.0", @@ -153,31 +195,33 @@ "sinon": "4.4.2", "spectron": "5.0.0", "ts-loader": "4.1.0", - "tslint": "5.9.1", - "tslint-microsoft-contrib": "5.0.3", - "tslint-react": "3.5.1", - "typescript": "2.8.1", + "tslint": "5.13.0", + "tslint-microsoft-contrib": "6.0.0", + "tslint-react": "3.6.0", + "typescript": "3.3.3333", "webpack": "4.4.1" }, "engines": { "node": "10.13.0" }, "build": { - "appId": "org.loki.messenger-desktop", + "appId": "com.loki-project.messenger-desktop", + "afterSign": "build/notarize.js", "mac": { "artifactName": "${name}-mac-${version}.${ext}", "category": "public.app-category.social-networking", "icon": "build/icons/mac/icon.icns", - "publish": [ - { - "provider": "generic", - "url": "https://updates.signal.org/desktop" - } - ], "target": [ - "zip" + "dmg" ], - "bundleVersion": "1" + "bundleVersion": "1", + "hardenedRuntime": true, + "gatekeeperAssess": false, + "entitlements": "build/entitlements.mac.plist", + "entitlementsInherit": "build/entitlements.mac.plist" + }, + "dmg": { + "sign": false }, "win": { "asarUnpack": "node_modules/spellchecker/vendor/hunspell_dictionaries", @@ -187,7 +231,7 @@ "publish": [ { "provider": "generic", - "url": "https://updates.signal.org/desktop" + "url": "https://getsession.org/" } ], "target": [ @@ -200,7 +244,7 @@ "linux": { "category": "Network", "desktop": { - "StartupWMClass": "Loki Messenger" + "StartupWMClass": "Session" }, "asarUnpack": "node_modules/spellchecker/vendor/hunspell_dictionaries", "target": [ @@ -210,8 +254,6 @@ }, "deb": { "depends": [ - "gconf2", - "gconf-service", "libnotify4", "libappindicator1", "libxtst6", diff --git a/password.html b/password.html index eba253535a..05478651e2 100644 --- a/password.html +++ b/password.html @@ -16,74 +16,17 @@ - - - + + -
- -
- - - -
-
+
diff --git a/password_preload.js b/password_preload.js index 9253aa2858..1144878eac 100644 --- a/password_preload.js +++ b/password_preload.js @@ -8,11 +8,21 @@ const config = url.parse(window.location.toString(), true).query; const { locale } = config; const localeMessages = ipcRenderer.sendSync('locale-data'); +window.React = require('react'); +window.ReactDOM = require('react-dom'); + window.theme = config.theme; window.i18n = i18n.setup(locale, localeMessages); +window.getEnvironment = () => config.environment; +window.getVersion = () => config.version; +window.getAppInstance = () => config.appInstance; + // So far we're only using this for Signal.Types const Signal = require('./js/modules/signal'); +const electron = require('electron'); + +const ipc = electron.ipcRenderer; window.Signal = Signal.setup({ Attachments: null, @@ -20,16 +30,32 @@ window.Signal = Signal.setup({ getRegionCode: () => null, }); -window.getEnvironment = () => config.environment; -window.getVersion = () => config.version; -window.getAppInstance = () => config.appInstance; +window.Signal.Logs = require('./js/modules/logs'); + +window.CONSTANTS = { + MAX_LOGIN_TRIES: 3, + MAX_PASSWORD_LENGTH: 32, + MAX_USERNAME_LENGTH: 20, +}; window.passwordUtil = require('./app/password_util'); +window.Signal.Logs = require('./js/modules/logs'); window.resetDatabase = () => { window.log.info('reset database'); ipcRenderer.send('resetDatabase'); }; + +window.restart = () => { + window.log.info('restart'); + ipc.send('restart'); +}; + +window.clearLocalData = async () => { + window.resetDatabase(); + window.restart(); +}; + window.onLogin = passPhrase => new Promise((resolve, reject) => { ipcRenderer.once('password-window-login-response', (event, error) => { diff --git a/preload.js b/preload.js index c2052acf98..8fb3936d1d 100644 --- a/preload.js +++ b/preload.js @@ -11,6 +11,7 @@ const { app } = electron.remote; const { clipboard } = electron; window.PROTO_ROOT = 'protos'; +const appConfig = require('./app/config'); const config = require('url').parse(window.location.toString(), true).query; let title = config.name; @@ -21,8 +22,17 @@ if (config.appInstance) { title += ` - ${config.appInstance}`; } +window.Lodash = require('lodash'); + +// Regex to match all characters which are *not* supported in display names +window.displayNameRegex = /[^\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC _0-9]*/g; + +window.semver = semver; +window.platform = process.platform; +window.getDefaultPoWDifficulty = () => config.defaultPoWDifficulty; window.getTitle = () => title; window.getEnvironment = () => config.environment; +window.isDev = () => config.environment === 'development'; window.getAppInstance = () => config.appInstance; window.getVersion = () => config.version; window.isImportMode = () => config.importMode; @@ -33,6 +43,10 @@ window.getHostName = () => config.hostname; window.getServerTrustRoot = () => config.serverTrustRoot; window.isBehindProxy = () => Boolean(config.proxyUrl); window.JobQueue = JobQueue; +window.getStoragePubKey = key => + window.isDev() ? key.substring(0, key.length - 2) : key; +window.getDefaultFileServer = () => config.defaultFileServer; +window.initialisedAPI = false; window.isBeforeVersion = (toCheck, baseVersion) => { try { @@ -46,6 +60,22 @@ window.isBeforeVersion = (toCheck, baseVersion) => { } }; +window.CONSTANTS = { + MAX_LOGIN_TRIES: 3, + MAX_PASSWORD_LENGTH: 32, + MAX_USERNAME_LENGTH: 20, + MAX_GROUP_NAME_LENGTH: 64, + DEFAULT_PUBLIC_CHAT_URL: appConfig.get('defaultPublicChatServer'), + MAX_CONNECTION_DURATION: 5000, +}; + +window.versionInfo = { + environment: window.getEnvironment(), + version: window.getVersion(), + commitHash: window.getCommitHash(), + appInstance: window.getAppInstance(), +}; + window.wrapDeferred = deferredToPromise; const ipc = electron.ipcRenderer; @@ -66,6 +96,8 @@ window.setPassword = (passPhrase, oldPhrase) => ipc.send('set-password', passPhrase, oldPhrase); }); +window.passwordUtil = require('./app/password_util'); + // We never do these in our code, so we'll prevent it everywhere window.open = () => null; // eslint-disable-next-line no-eval, no-multi-assign @@ -91,15 +123,20 @@ window.restart = () => { ipc.send('restart'); }; -window.setMediaPermissions = enabled => - ipc.send('set-media-permissions', enabled); -window.getMediaPermissions = () => ipc.sendSync('get-media-permissions'); +window.resetDatabase = () => { + window.log.info('reset database'); + ipc.send('resetDatabase'); +}; // Events for updating block number states across different windows. // In this case we need these to update the blocked number // collection on the main window from the settings window. window.onUnblockNumber = number => ipc.send('on-unblock-number', number); +ipc.on('mediaPermissionsChanged', () => { + Whisper.events.trigger('mediaPermissionsChanged'); +}); + ipc.on('on-unblock-number', (event, number) => { // Unblock the number if (window.BlockedNumberController) { @@ -121,6 +158,7 @@ ipc.on('on-unblock-number', (event, number) => { }); window.closeAbout = () => ipc.send('close-about'); +window.readyForUpdates = () => ipc.send('ready-for-updates'); window.updateTrayIcon = unreadCount => ipc.send('update-tray-icon', unreadCount); @@ -155,6 +193,29 @@ ipc.on('remove-dark-overlay', () => { } }); +window.getSettingValue = (settingID, comparisonValue = null) => { + // Comparison value allows you to pull boolean values from any type. + // Eg. window.getSettingValue('theme', 'light') + // returns 'false' when the value is 'dark'. + + if (settingID === 'media-permissions') { + let permissionValue; + // eslint-disable-next-line more/no-then + window.getMediaPermissions().then(value => { + permissionValue = value; + }); + + return permissionValue; + } + + const settingVal = window.storage.get(settingID); + return comparisonValue ? !!settingVal === comparisonValue : settingVal; +}; + +window.setSettingValue = (settingID, value) => { + window.storage.put(settingID, value); +}; + installGetter('device-name', 'getDeviceName'); installGetter('theme-setting', 'getThemeSetting'); @@ -169,6 +230,10 @@ installSetter('message-ttl', 'setMessageTTL'); installGetter('read-receipt-setting', 'getReadReceiptSetting'); installSetter('read-receipt-setting', 'setReadReceiptSetting'); + +installGetter('typing-indicators-setting', 'getTypingIndicatorsSetting'); +installSetter('typing-indicators-setting', 'setTypingIndicatorsSetting'); + installGetter('notification-setting', 'getNotificationSetting'); installSetter('notification-setting', 'setNotificationSetting'); installGetter('audio-notification', 'getAudioNotification'); @@ -180,6 +245,9 @@ installSetter('link-preview-setting', 'setLinkPreviewSetting'); installGetter('spell-check', 'getSpellCheck'); installSetter('spell-check', 'setSpellCheck'); +installGetter('media-permissions', 'getMediaPermissions'); +installGetter('media-permissions', 'setMediaPermissions'); + window.getMediaPermissions = () => new Promise((resolve, reject) => { ipc.once('get-success-media-permissions', (_event, error, value) => { @@ -289,27 +357,25 @@ window.WebAPI = initializeWebAPI({ proxyUrl: config.proxyUrl, }); +window.seedNodeList = JSON.parse(config.seedNodeList); const LokiSnodeAPI = require('./js/modules/loki_snode_api'); window.lokiSnodeAPI = new LokiSnodeAPI({ serverUrl: config.serverUrl, localUrl: config.localUrl, - snodeServerPort: config.snodeServerPort, }); -window.LokiP2pAPI = require('./js/modules/loki_p2p_api'); +window.LokiMessageAPI = require('./js/modules/loki_message_api'); -const LokiMessageAPI = require('./js/modules/loki_message_api'); +window.LokiPublicChatAPI = require('./js/modules/loki_public_chat_api'); -window.lokiMessageAPI = new LokiMessageAPI({ - url: config.serverUrl, - snodeServerPort: config.snodeServerPort, -}); +window.LokiAppDotNetServerAPI = require('./js/modules/loki_app_dot_net_api'); + +window.LokiFileServerAPI = require('./js/modules/loki_file_server_api'); -const LocalLokiServer = require('./libloki/modules/local_loki_server'); +window.LokiRssAPI = require('./js/modules/loki_rss_api'); window.localServerPort = config.localServerPort; -window.localLokiServer = new LocalLokiServer(); window.mnemonic = require('./libloki/modules/mnemonic'); const WorkerInterface = require('./js/modules/util_worker_interface'); @@ -336,7 +402,6 @@ window.libphonenumber.PhoneNumberFormat = require('google-libphonenumber').Phone window.loadImage = require('blueimp-load-image'); window.getGuid = require('uuid/v4'); window.profileImages = require('./app/profile_images'); -window.passwordUtil = require('./app/password_util'); window.React = require('react'); window.ReactDOM = require('react-dom'); @@ -378,6 +443,30 @@ window.Signal.Backup = require('./js/modules/backup'); window.Signal.Debug = require('./js/modules/debug'); window.Signal.Logs = require('./js/modules/logs'); +// Add right-click listener for selected text and urls +const contextMenu = require('electron-context-menu'); + +const isQR = params => + params.mediaType === 'image' && params.titleText === 'Scan me!'; + +// QR saving doesn't work so we just disable it +contextMenu({ + showInspectElement: false, + shouldShowMenu: (event, params) => { + const isRegular = + params.mediaType === 'none' && (params.linkURL || params.selectionText); + return Boolean(!params.isEditable && (isQR(params) || isRegular)); + }, + menu: (actions, params) => { + // If it's not a QR then show the default options + if (!isQR(params)) { + return actions; + } + + return [actions.copyImage()]; + }, +}); + // We pull this in last, because the native module involved appears to be sensitive to // /tmp mounted as noexec on Linux. require('./js/spell_check'); @@ -398,3 +487,24 @@ if (config.environment === 'test') { }; /* eslint-enable global-require, import/no-extraneous-dependencies */ } + +window.shortenPubkey = pubkey => `(...${pubkey.substring(pubkey.length - 6)})`; + +window.pubkeyPattern = /@[a-fA-F0-9]{64,66}\b/g; + +// Limited due to the proof-of-work requirement +window.SMALL_GROUP_SIZE_LIMIT = 10; + +// TODO: activate SealedSender once it is ready on all platforms +window.lokiFeatureFlags = { + multiDeviceUnpairing: true, + privateGroupChats: true, + useSnodeProxy: true, + useSealedSender: true, +}; + +// eslint-disable-next-line no-extend-native,func-names +Promise.prototype.ignore = function() { + // eslint-disable-next-line more/no-then + this.then(() => {}); +}; diff --git a/prepare_beta_build.js b/prepare_beta_build.js index dfd41a3ac4..13e297297d 100644 --- a/prepare_beta_build.js +++ b/prepare_beta_build.js @@ -27,16 +27,16 @@ const PRODUCTION_NAME = 'loki-messenger-desktop'; const BETA_NAME = 'loki-messenger-desktop-beta'; const PRODUCT_NAME_PATH = 'productName'; -const PRODUCTION_PRODUCT_NAME = 'Loki Messenger'; -const BETA_PRODUCT_NAME = 'Loki Messenger Beta'; +const PRODUCTION_PRODUCT_NAME = 'Session'; +const BETA_PRODUCT_NAME = 'Session Beta'; const APP_ID_PATH = 'build.appId'; -const PRODUCTION_APP_ID = 'org.loki.messenger-desktop'; -const BETA_APP_ID = 'org.loki.messenger-desktop-beta'; +const PRODUCTION_APP_ID = 'com.loki-project.messenger-desktop'; +const BETA_APP_ID = 'com.loki-project.messenger-desktop-beta'; const STARTUP_WM_CLASS_PATH = 'build.linux.desktop.StartupWMClass'; -const PRODUCTION_STARTUP_WM_CLASS = 'Loki Messenger'; -const BETA_STARTUP_WM_CLASS = 'Loki Messenger Beta'; +const PRODUCTION_STARTUP_WM_CLASS = 'Session'; +const BETA_STARTUP_WM_CLASS = 'Session Beta'; // ------- diff --git a/protos/SignalService.proto b/protos/SignalService.proto index cc2660060f..41f6ed5abf 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -36,11 +36,24 @@ message Content { optional TypingMessage typingMessage = 6; optional PreKeyBundleMessage preKeyBundleMessage = 101; optional LokiAddressMessage lokiAddressMessage = 102; + optional PairingAuthorisationMessage pairingAuthorisation = 103; } message LokiAddressMessage { + enum Type { + HOST_REACHABLE = 0; + HOST_UNREACHABLE = 1; + } optional string p2pAddress = 1; optional uint32 p2pPort = 2; + optional Type type = 3; +} + +message PairingAuthorisationMessage { + optional string primaryDevicePubKey = 1; + optional string secondaryDevicePubKey = 2; + optional bytes requestSignature = 3; + optional bytes grantSignature = 4; } message PreKeyBundleMessage { @@ -89,9 +102,12 @@ message CallMessage { message DataMessage { enum Flags { - END_SESSION = 1; - EXPIRATION_TIMER_UPDATE = 2; - PROFILE_KEY_UPDATE = 4; + END_SESSION = 1; + EXPIRATION_TIMER_UPDATE = 2; + PROFILE_KEY_UPDATE = 4; + SESSION_RESTORE = 64; + UNPAIRING_REQUEST = 128; + SESSION_REQUEST = 256; } message Quote { @@ -180,6 +196,18 @@ message DataMessage { optional AttachmentPointer image = 3; } + // Loki: A custom message for our profile + message LokiProfile { + optional string displayName = 1; + optional string avatar = 2; + } + + message GroupInvitation { + optional string serverAddress = 1; + optional uint32 channelId = 2; + optional string serverName = 3; + } + optional string body = 1; repeated AttachmentPointer attachments = 2; optional GroupContext group = 3; @@ -190,7 +218,8 @@ message DataMessage { optional Quote quote = 8; repeated Contact contact = 9; repeated Preview preview = 10; - optional Contact profile = 101; // Loki: The profile of the current user + optional LokiProfile profile = 101; // Loki: The profile of the current user + optional GroupInvitation groupInvitation = 102; // Loki: Invitation to a public chat } message NullMessage { @@ -248,6 +277,7 @@ message SyncMessage { message Contacts { optional AttachmentPointer blob = 1; optional bool complete = 2 [default = false]; + optional bytes data = 101; } message Groups { @@ -310,6 +340,7 @@ message AttachmentPointer { optional uint32 width = 9; optional uint32 height = 10; optional string caption = 11; + optional string url = 101; } message GroupContext { @@ -325,6 +356,7 @@ message GroupContext { optional string name = 3; repeated string members = 4; optional AttachmentPointer avatar = 5; + repeated string admins = 6; } message ContactDetails { @@ -341,6 +373,7 @@ message ContactDetails { optional bytes profileKey = 6; optional bool blocked = 7; optional uint32 expireTimer = 8; + optional string nickname = 101; } message GroupDetails { diff --git a/protos/UnidentifiedDelivery.proto b/protos/UnidentifiedDelivery.proto index da9295aa6b..9b49b8c9ba 100644 --- a/protos/UnidentifiedDelivery.proto +++ b/protos/UnidentifiedDelivery.proto @@ -13,17 +13,10 @@ message ServerCertificate { optional bytes signature = 2; } +// This should perhaps be renamed to something like `SenderInfo` message SenderCertificate { - message Certificate { - optional string sender = 1; - optional uint32 senderDevice = 2; - optional fixed64 expires = 3; - optional bytes identityKey = 4; - optional ServerCertificate signer = 5; - } - - optional bytes certificate = 1; - optional bytes signature = 2; + optional string sender = 1; + optional uint32 senderDevice = 2; } message UnidentifiedSenderMessage { @@ -32,6 +25,7 @@ message UnidentifiedSenderMessage { enum Type { PREKEY_MESSAGE = 1; MESSAGE = 2; + LOKI_FRIEND_REQUEST = 3; } optional Type type = 1; diff --git a/settings.html b/settings.html index cca9fea8d9..bd10df867b 100644 --- a/settings.html +++ b/settings.html @@ -60,10 +60,12 @@

{{ theme }}


+ {{ #isHideMenuBarSupported }} + {{ /isHideMenuBarSupported }}

{{ notifications }}

@@ -98,15 +100,11 @@

{{ generalHeader }}

+ -
-

{{ linkPreviews }}

-
{{ linkPreviewsDescription }}
-
-

{{ permissions }}

@@ -118,6 +116,10 @@

{{ permissions }}

+
+ + +

diff --git a/settings_preload.js b/settings_preload.js index 8fdb3c1ee7..97f94bc8a8 100644 --- a/settings_preload.js +++ b/settings_preload.js @@ -11,6 +11,10 @@ const localeMessages = ipcRenderer.sendSync('locale-data'); window.theme = config.theme; window.i18n = i18n.setup(locale, localeMessages); +window.getEnvironment = () => config.environment; +window.getVersion = () => config.version; +window.getAppInstance = () => config.appInstance; + // So far we're only using this for Signal.Types const Signal = require('./js/modules/signal'); @@ -20,10 +24,6 @@ window.Signal = Signal.setup({ getRegionCode: () => null, }); -window.getEnvironment = () => config.environment; -window.getVersion = () => config.version; -window.getAppInstance = () => config.appInstance; - window.closeSettings = () => ipcRenderer.send('close-settings'); // Events for updating block number states across different windows. @@ -47,6 +47,10 @@ window.setMessageTTL = makeSetter('message-ttl'); window.getReadReceiptSetting = makeGetter('read-receipt-setting'); window.setReadReceiptSetting = makeSetter('read-receipt-setting'); + +window.getTypingIndicatorsSetting = makeGetter('typing-indicators-setting'); +window.setTypingIndicatorsSetting = makeSetter('typing-indicators-setting'); + window.getNotificationSetting = makeGetter('notification-setting'); window.setNotificationSetting = makeSetter('notification-setting'); window.getAudioNotification = makeGetter('audio-notification'); diff --git a/styleguide.config.js b/styleguide.config.js index 07157d8686..c58cf7d82a 100644 --- a/styleguide.config.js +++ b/styleguide.config.js @@ -9,22 +9,22 @@ module.exports = { { name: 'Components', description: '', - components: 'ts/components/*.tsx', + components: 'ts/components/[^_]*.tsx', }, { name: 'Conversation', description: 'Everything necessary to render a conversation', - components: 'ts/components/conversation/*.tsx', + components: 'ts/components/conversation/[^_]*.tsx', }, { name: 'Media Gallery', description: 'Display media and documents in a conversation', - components: 'ts/components/conversation/media-gallery/*.tsx', + components: 'ts/components/conversation/media-gallery/[^_]*.tsx', }, { name: 'Utility', description: 'Utility components used across the application', - components: 'ts/components/utility/*.tsx', + components: 'ts/components/utility/[^_]*.tsx', }, { name: 'Test', diff --git a/stylesheets/_conversation.scss b/stylesheets/_conversation.scss index 2f1fee424a..2f879d9c3c 100644 --- a/stylesheets/_conversation.scss +++ b/stylesheets/_conversation.scss @@ -136,13 +136,8 @@ .message-list { list-style: none; - .message-wrapper { - margin-left: 16px; - margin-right: 16px; - } - li { - margin-bottom: 10px; + margin-bottom: 2px; &::after { visibility: hidden; @@ -155,12 +150,168 @@ } } -.group { - .message-container, - .message-list { - .message-wrapper { - margin-left: 44px; +.module-message__check-box { + color: rgb(97, 97, 97); + font-size: 20px; + padding: 4px; + user-select: none; + display: inline; +} + +.check-box-container { + // background-color: blue; + align-items: center; + flex-direction: row; + display: inline-flex; +} + +.check-box-selected { + opacity: 1; +} + +.loki-message-wrapper { + .react-contextmenu-wrapper { + display: inline-flex; + width: 100%; + } +} + +.loki-message-wrapper { + padding-left: 16px; + padding-right: 16px; +} + +.public-chat-message-wrapper { + padding-left: 10px; + padding-right: 10px; +} + +.loki-message-wrapper { + display: flow-root; + padding-bottom: 4px; + padding-top: 4px; +} + +.group-invitation-container { + display: flex; + flex-direction: column; +} + +.group-invitation { + background-color: #f4f4f0; + display: inline-block; + margin: 4px 16px; + padding: 4px; + + border: solid; + border-width: 0.5px; + border-radius: 4px; + border-color: #e0e0e0; + + align-self: flex-start; + + box-shadow: none; + + .title { + margin: 6px; + color: darkslategray; + font-variant-caps: all-small-caps; + user-select: none; + } + + .contents { + display: flex; + align-items: center; + margin: 6px; + + .invite-group-avatar { + height: 48px; + width: 48px; } + + .group-details { + display: inline-flex; + flex-direction: column; + + padding: 8px; + + .group-name { + font-weight: lighter; + padding-bottom: 4px; + } + + .group-address { + color: grey; + } + } + + .join-btn { + background-color: #00f782; + color: white; + padding: 6px 10px; + margin-left: 6px; + border-radius: 2px; + box-shadow: none; + user-select: none; + cursor: pointer; + transition: 0.25s; + + &:hover { + background-color: #00d672; + } + } + } +} + +.dark-theme { + .group-invitation { + background-color: #242424; + border-color: #303030; + box-shadow: none; + + .title { + color: lightgrey; + } + } +} + +.invitation-outgoing { + align-self: flex-end; +} + +.bulk-edit-container { + display: flex; + border-top: solid; + border-width: 0.8px; + border-color: #80808090; + + &.hidden { + display: none; + } + + .delete-button { + color: orangered; + padding: 18px; + // This makes sure the message counter is right in the center + width: 80px; + margin-right: -80px; + user-select: none; + } + + .cancel-button { + padding: 18px; + width: 80px; + margin-left: -80px; + user-select: none; + } + + .message-counter { + color: darkgrey; + display: flex; + align-items: center; + user-select: none; + margin-left: auto; + margin-right: auto; } } @@ -190,6 +341,22 @@ margin-bottom: -5px; } +.conversation-stack-border { + border-bottom: solid; + border-color: white; + border-width: 4px; +} + +.conversation-stack-no-border { + border-bottom: none; +} + +.dark-theme { + .conversation-stack-border { + border-color: black; + } +} + .bottom-bar .preview-wrapper { margin-top: 3px; margin-left: 37px; @@ -202,9 +369,6 @@ $button-width: 36px; form.active { - textarea { - border: solid 1px $blue; - } } form.send { @@ -294,10 +458,6 @@ resize: none; font-size: 1em; font-family: inherit; - - &[disabled='disabled'] { - background: $color-light-35; - } } .capture-audio { float: right; @@ -316,10 +476,7 @@ bottom: 62px; text-align: center; - padding-left: 16px; - padding-right: 16px; - padding-top: 8px; - padding-bottom: 8px; + padding: 8px 16px; border-radius: 4px; z-index: 100; @@ -386,6 +543,7 @@ border: 1px solid $color-loki-green; color: white; outline: none; + user-select: none; &:hover, &:disabled { @@ -415,14 +573,6 @@ h4 { margin-top: 8px; margin-bottom: 16px; - white-space: -moz-pre-wrap; /* Mozilla */ - white-space: -hp-pre-wrap; /* HP printers */ - white-space: -o-pre-wrap; /* Opera 7 */ - white-space: -pre-wrap; /* Opera 4-6 */ - white-space: pre-wrap; /* CSS 2.1 */ - white-space: pre-line; /* CSS 3 (and 2.1 as well, actually) */ - word-wrap: break-word; /* IE */ - word-break: break-all; } } @@ -521,7 +671,7 @@ .module-last-seen-indicator__bar { background-color: $color-light-60; width: 100%; - height: 4px; + height: 2px; } .module-last-seen-indicator__text { diff --git a/stylesheets/_emoji.scss b/stylesheets/_emoji.scss index 2ade390c66..08397e0206 100644 --- a/stylesheets/_emoji.scss +++ b/stylesheets/_emoji.scss @@ -82,16 +82,13 @@ img.emoji.jumbo { } button.emoji { - width: 36px; - height: 36px; padding: 0; opacity: 0.5; border: none; background: transparent; - margin-top: 3px; + margin: 0px 10px 0px 15px; &:before { - margin-top: 4px; content: ''; display: inline-block; width: $button-height; diff --git a/stylesheets/_global.scss b/stylesheets/_global.scss index 9ce93c6d11..8c8a04c8ad 100644 --- a/stylesheets/_global.scss +++ b/stylesheets/_global.scss @@ -69,6 +69,15 @@ audio { button { cursor: pointer; font-size: inherit; + + &[disabled='disabled'] { + &, + &:hover { + opacity: 0.5; + box-shadow: none; + cursor: default; + } + } } button.grey { border-radius: $border-radius; @@ -143,63 +152,6 @@ a { } } -.dropoff { - outline: solid 1px $blue; -} - -$avatar-size: 48px; - -.avatar { - display: inline-block; - height: $avatar-size; - width: $avatar-size; - border-radius: 50%; - background-size: cover; - vertical-align: middle; - text-align: center; - line-height: $avatar-size; - overflow-x: hidden; - text-overflow: ellipsis; - color: $color-white; - font-size: 18px; - background-color: $grey; -} - -.group-info-input { - background: $color-white; - - .group-avatar { - display: inline-block; - padding: 2px 0px 0px 2px; - } - - .file-input .thumbnail, - .thumbnail .avatar, - img { - height: 54px; - width: 54px; - border-radius: (54px / 2); - } - - .thumbnail:after { - content: ''; - position: absolute; - height: 0; - width: 0; - bottom: 0; - right: 0; - border-bottom: 10px solid $grey; - border-left: 10px solid transparent; - } - - input.name { - padding: 0.5em; - border: solid 1px #ccc; - border-width: 0 0 1px 0; - width: calc(100% - 84px); - } -} - .group-member-list, .new-group-update { .summary { @@ -257,124 +209,6 @@ $unread-badge-size: 21px; } } -$new-contact-left-margin: 16px; -.new-contact .avatar { - margin-left: $new-contact-left-margin; -} - -// Still used for the contact search view -.contact-details { - color: $color-gray-05; - $left-margin: 12px; - - vertical-align: middle; - display: inline-block; - margin: 0 0 0 $left-margin; - width: calc( - 100% - #{$avatar-size} - #{$new-contact-left-margin} - #{$left-margin} - #{( - 4/14 - ) + em} - ); - text-align: left; - - p { - overflow-x: hidden; - overflow-y: hidden; - height: 1.2em; - text-overflow: ellipsis; - } - - .name { - display: block; - margin: 0; - font-size: 1em; - text-overflow: ellipsis; - overflow-x: hidden; - text-align: left; - } - - .number { - color: $color-dark-30; - font-size: $font-size-small; - } - - &.clickable { - cursor: pointer; - } - - .verified-icon { - @include color-svg('../images/verified-check.svg', $grey); - display: inline-block; - width: 1.25em; - height: 1.25em; - vertical-align: text-bottom; - } - - .body-wrapper { - overflow-x: hidden; - overflow-y: hidden; - text-overflow: ellipsis; - } -} - -.recipients-input { - position: relative; - - .recipients-container { - background-color: white; - padding: 2px; - border-bottom: 1px solid #f2f2f2; - line-height: 24px; - } - - .recipient { - display: inline-block; - margin: 0 2px 2px 0; - padding: 0 5px; - border-radius: 10px; - background-color: $blue; - color: white; - - &.error { - background-color: #f00; - } - - .remove { - margin-left: 5px; - padding: 0 2px; - } - } - - .results { - position: absolute; - z-index: 10; - margin: 0 0 0 20px; - width: calc(100% - 30px); - max-width: 300px; - max-height: 55px * 3; - overflow-y: auto; - box-shadow: 0px 0px 1px rgba(#aaa, 0.8); - - .contact { - cursor: pointer; - } - } -} - -.attachment-preview { - display: inline-block; - position: relative; - img { - max-width: 100%; - } -} -.new-conversation .recipients-input .recipients::before { - content: 'To: '; -} -.new-group-update .recipients-input .recipients::before { - content: 'Add: '; -} - $loading-height: 16px; .loading { @@ -438,10 +272,6 @@ $loading-height: 16px; } } -.inbox { - position: relative; -} - @keyframes loading { 50% { transform: scale(1); @@ -893,6 +723,12 @@ $loading-height: 16px; .button { background: $color-loki-green-gradient; border-radius: 100px; + + &:disabled, + &:disabled:hover { + background: $color-loki-dark-gray; + cursor: default; + } } #mnemonic-display { @@ -938,7 +774,7 @@ $loading-height: 16px; .select-container { position: relative; display: block; - width: 9em; + width: 13em; line-height: 2.8; background: white; overflow: hidden; @@ -985,7 +821,8 @@ $loading-height: 16px; } } - .password-inputs { + .password-inputs, + .standalone-mnemonic-inputs { display: flex; flex-direction: column; justify-content: center; @@ -1036,13 +873,20 @@ $loading-height: 16px; outline: none; } -.text-security .inbox { - .name, - .body, - .last-message, - .sender, - .conversation-title, - .number { - -webkit-text-security: square; +.inbox { + position: relative; +} + +.qr-dialog { + .content { + width: 300px !important; + max-width: none !important; + min-width: auto !important; + } + + #qr { + display: flex; + justify-content: center; + margin-bottom: 1em; } } diff --git a/stylesheets/_index.scss b/stylesheets/_index.scss index f63abd9bac..f5156f5dcc 100644 --- a/stylesheets/_index.scss +++ b/stylesheets/_index.scss @@ -1,3 +1,7 @@ +.conversation-stack { + position: relative; +} + .conversation-stack, .new-conversation, .inbox, @@ -6,6 +10,61 @@ overflow: hidden; } +.edit-profile-dialog, +.user-details-dialog { + .content { + max-width: 100% !important; + } + + .buttons { + margin: 8px; + } + + .profile-name { + font-size: larger; + text-align: center; + } + + .title-text { + font-size: large; + text-align: center; + } + + .message { + font-style: italic; + color: $grey; + font-size: 12px; + margin-bottom: 16px; + } + + .module-avatar { + display: block; + margin-bottom: 1em; + } + + .avatar-center { + display: flex; + justify-content: center; + } + + .avatar-center-inner { + display: flex; + } + + .upload-btn-background { + background-color: #ffffff70; + align-self: center; + margin-left: -24px; + margin-top: 40px; + z-index: 1; + border-radius: 8px; + } + + .input-file { + display: none; + } +} + .expired { .conversation-stack, .gutter { @@ -182,12 +241,13 @@ h4.section-toggle, } } -.underneathIdentityWrapper { - position: absolute; - top: 0; - bottom: 0; - left: 300px; - right: 0; +.left-pane-placeholder { + flex-grow: 1; + display: flex; +} + +.left-pane-wrapper { + flex: 1; } .conversation-stack { diff --git a/stylesheets/_ios.scss b/stylesheets/_ios.scss index 323070a4e4..cf3ba988c4 100644 --- a/stylesheets/_ios.scss +++ b/stylesheets/_ios.scss @@ -136,6 +136,20 @@ background-color: $color-gray-60; } + .module-spinner__circle--incoming { + background-color: $color-white-04; + } + .module-spinner__arc--incoming { + background-color: $color-gray-60; + } + + .module-spinner__circle--outgoing { + background-color: $color-white-04; + } + .module-spinner__arc--outgoing { + background-color: $color-white; + } + &.dark-theme { // _modules @@ -303,5 +317,31 @@ .module-embedded-contact__contact-method--incoming { color: $color-gray-25; } + + .module-spinner__circle--incoming { + background-color: $color-white-04; + } + .module-spinner__arc--incoming { + background-color: $color-gray-25; + } + .module-spinner__circle--small-incoming { + background-color: $color-white-04; + } + .module-spinner__arc--small-incoming { + background-color: $color-gray-25; + } + + .module-spinner__circle--outgoing { + background-color: $color-white-04; + } + .module-spinner__arc--outgoing { + background-color: $color-gray-05; + } + .module-spinner__circle--small-outgoing { + background-color: $color-white-04; + } + .module-spinner__arc--small-outgoing { + background-color: $color-gray-05; + } } } diff --git a/stylesheets/_mentions.scss b/stylesheets/_mentions.scss new file mode 100644 index 0000000000..0f8ae10b27 --- /dev/null +++ b/stylesheets/_mentions.scss @@ -0,0 +1,213 @@ +.leave-group-dialog { + .content { + max-width: 100% !important; + } + + .titleText { + font-size: large; + text-align: center; + margin: 2px; + } + + .ok { + background-color: orangered; + min-width: 70px; + border: none; + + &:hover { + background-color: red; + } + } + + .cancel { + border: none; + min-width: 70px; + } +} + +.member-preview { + margin-left: 10px; +} + +/* remove scroll bars */ +.loki-dialog .add-moderators-dialog .content { + padding: 1.1em; +} + +.invite-friends-dialog, +.add-moderators-dialog, +.remove-moderators-dialog, +.create-group-dialog { + .content { + max-width: 100% !important; + } + + .buttons { + margin: 8px; + } + + .group-name { + font-size: larger; + } + + .titleText { + font-size: large; + text-align: center; + } +} + +.create-group-dialog, +.add-moderators-dialog, +.remove-moderators-dialog, +.invite-friends-dialog { + .no-friends { + text-align: center; + } + + .hidden { + display: none; + } +} + +.create-group-dialog, +.add-moderators-dialog, +.remove-moderators-dialog, +.edit-profile-dialog { + .error-message { + text-align: center; + color: red; + display: block; + user-select: none; + } + + .error-faded { + opacity: 0; + margin-top: -20px; + transition: all 100ms linear; + } + + .error-shown { + opacity: 1; + transition: all 250ms linear; + } +} + +.friend-selection-list { + max-height: 240px; + overflow-y: scroll; + margin: 4px; + + .check-mark { + float: right; + text-align: center; + color: darkslategrey; + margin: 4px; + min-width: 20px; + } + + .invisible { + visibility: hidden; + } + + .existing-member { + color: green; + } + + .existing-member-kicked { + color: red; + } +} + +.dark-theme { + .friend-selection-list { + .check-mark { + color: rgb(230, 230, 230); + } + } +} + +.member-list-container { + margin: 0; + padding: 0; + + max-height: 240px; + overflow-y: scroll; + + .check-mark { + display: none; + } +} + +.member-list-container, +.create-group-dialog, +.add-moderators-dialog, +.remove-moderators-dialog, +.invite-friends-dialog { + .member-item { + padding: 4px; + user-select: none; + + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + + .name-part { + font-weight: 300; + margin-left: 12px; + } + + .pubkey-part { + margin-left: 10px; + opacity: 0.6; + } + } +} + +.mention-profile-name { + color: rgb(194, 244, 255); + background-color: rgb(66, 121, 150); + text-decoration: underline; + border-radius: 4px; + margin: 2px; + padding: 2px; + user-select: none; +} + +.mention-profile-name-us { + background-color: rgba(255, 197, 50, 1); + color: black; +} + +.message-highlighted { + border-radius: $message-container-border-radius; + background-color: rgba(255, 197, 50, 0.2); +} + +.module-conversation-list-item--mentioned-us { + border-left: 4px solid #ffb000 !important; +} + +.at-symbol { + background-color: #ffb000; + + color: $color-black; + text-align: center; + + padding-top: 1px; + padding-left: 3px; + padding-right: 3px; + + position: absolute; + right: -6px; + top: 12px; + + font-weight: 300; + font-size: 11px; + letter-spacing: 0.25px; + + height: 16px; + min-width: 16px; + border-radius: 8px; + + box-shadow: 0px 0px 0px 1px $color-dark-85; +} diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index 10c856f2d1..097920c68e 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -5,10 +5,20 @@ display: flex; flex-direction: column; align-items: flex-start; - margin-right: 8px; overflow-x: hidden; } +.create-group-button { + background-color: #383c46; + color: #ffffff; + margin: 4px; + padding: 4px; +} + +.create-group-button:focus { + outline: 0; +} + .module-contact-name span { text-overflow: ellipsis; overflow-x: hidden; @@ -17,10 +27,14 @@ user-select: none; } -.module-contact-name__profile-number { +.module-contact-name__profile-number.italic { font-style: italic; } +.module-contact-name.compact { + display: block; +} + // Module: Message .module-message { @@ -84,9 +98,21 @@ right: 100%; } -.module-message__buttons__download { +.module-message__buttons__upload { height: 24px; width: 24px; + transform: rotate(180deg); + display: inline-block; + cursor: pointer; + @include color-svg('../images/download.svg', $color-light-45); + &:hover { + @include color-svg('../images/download.svg', $color-gray-90); + } +} + +.module-message__buttons__download { + min-height: 24px; + min-width: 24px; display: inline-block; cursor: pointer; @include color-svg('../images/download.svg', $color-light-45); @@ -103,8 +129,8 @@ } .module-message__buttons__reply { - height: 24px; - width: 24px; + min-height: 24px; + min-width: 24px; display: inline-block; cursor: pointer; @include color-svg('../images/reply.svg', $color-light-45); @@ -164,7 +190,7 @@ .module-message__container { position: relative; display: inline-block; - border-radius: 16px; + border-radius: $message-container-border-radius; padding-right: 12px; padding-left: 12px; padding-top: 10px; @@ -219,7 +245,6 @@ // Entirely to ensure that images are centered if they aren't full width of bubble text-align: center; position: relative; - cursor: pointer; margin-left: -12px; margin-right: -12px; @@ -266,6 +291,7 @@ .module-message__generic-attachment { display: flex; flex-direction: row; + align-items: center; } .module-message__generic-attachment--with-content-below { @@ -279,6 +305,10 @@ .module-message__generic-attachment__icon-container { position: relative; } +.module-message__generic-attachment__spinner-container { + padding-left: 4px; + padding-right: 4px; +} .module-message__generic-attachment__icon { background: url('../images/file-gradient.svg') no-repeat center; @@ -440,7 +470,7 @@ line-height: 16px; text-transform: uppercase; } - +.module-conversation__user, .module-message__author { color: $color-white; font-size: 13px; @@ -452,7 +482,7 @@ white-space: nowrap; text-overflow: ellipsis; } - +.module-conversation__user__profile-name, .module-message__author__profile-name { font-style: italic; } @@ -490,8 +520,20 @@ display: flex; flex-direction: row; align-items: center; - margin-top: 3px; + margin-top: 5px; margin-bottom: -3px; + + span { + opacity: 0.5; + transition: 0.25s; + + &:not(.module-message__metadata__badge--separator):hover { + opacity: 1; + } + } + .module-message__metadata__badge--separator { + margin-top: -2px; + } } // With an image and no caption, this section needs to be on top of the image overlay @@ -507,25 +549,28 @@ } .module-message__metadata__date, -.module-message__metadata__p2p { +.module-message__metadata__badge { font-size: 11px; line-height: 16px; letter-spacing: 0.3px; color: $color-gray-60; text-transform: uppercase; + user-select: none; } + +.module-message__metadata__badge { + font-weight: bold; +} + .module-message__metadata__date--incoming, -.module-message__metadata__p2p--incoming { +.module-message__metadata__badge--incoming { color: $color-white-08; } + .module-message__metadata__date--with-image-no-caption { color: $color-white; } -.module-message__metadata__p2p { - font-weight: bold; -} - .module-message__metadata__spacer { flex-grow: 1; } @@ -594,9 +639,9 @@ } .module-message__author-avatar { - position: absolute; - bottom: 0px; - right: calc(100% + 4px); + flex-direction: column-reverse; + display: inline-flex; + padding-right: 4px; } .module-message__typing-container { @@ -993,7 +1038,7 @@ cursor: pointer; display: flex; flex-direction: row; - align-items: stretch; + align-items: center; } .module-embedded-contact--with-content-above { @@ -1004,6 +1049,11 @@ padding-bottom: 4px; } +.module-embedded-contact__spinner-container { + padding-left: 5px; + padding-right: 5px; +} + .module-embedded-contact__text-container { flex-grow: 1; margin-left: 8px; @@ -1185,11 +1235,17 @@ } .module-group-notification__change { - margin-top: 10px; + background-color: #212121; + width: 90%; + max-width: 700px; + margin: 10px auto; + padding: 5px 20px; + border-radius: 4px; } .module-group-notification__contact { - font-weight: 300; + font-family: 'SF Pro Text'; + font-weight: bold; } // Module: Reset Session Notification @@ -1383,8 +1439,6 @@ // Module: Conversation Header .module-conversation-header { - padding-left: 16px; - padding-right: 16px; display: flex; flex-direction: row; align-items: center; @@ -1418,6 +1472,10 @@ height: 48px; } +.module-conversation-header__title-text { + color: darkgrey; +} + .module-conversation-header__title-flex { margin-left: auto; margin-right: auto; @@ -1429,12 +1487,13 @@ } .module-conversation-header__avatar { + cursor: pointer; min-width: 28px; user-select: none; } .module-conversation-header__title { - margin-left: 8px; + margin-left: 6px; min-width: 0; font-size: 16px; @@ -1442,8 +1501,8 @@ font-weight: 300; color: $color-gray-90; - // width of avatar (28px) and our 8px left margin - max-width: calc(100% - 36px); + // width of avatar (28px) and our 6px left margin + max-width: calc(100% - 34px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -1695,6 +1754,7 @@ // Module: Document List Item .module-document-list-item { + padding-right: 10px; width: 100%; height: 72px; } @@ -1843,10 +1903,6 @@ .module-avatar { background-color: $color-dark-85; } - - .module-contact-name { - margin-right: 0px; - } } .module-conversation-list-item--has-unread { @@ -1885,7 +1941,7 @@ position: absolute; right: -6px; - top: 6px; + top: -6px; font-weight: 300; font-size: 11px; @@ -2047,6 +2103,25 @@ transform: translate(-50%, -50%); } +.module-avatar__icon--crown-wrapper { + position: relative; + bottom: -38px; + right: -16px; + height: 21px; + width: 21px; + transform: translate(25%, 25%); + padding: 9%; + background-color: $color-white; + border-radius: 50%; + filter: drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.3)); +} + +.module-avatar__icon--crown { + @include color-svg('../images/crown.svg', #ffb000); + height: 100%; + width: 100%; +} + .module-avatar__icon--group { @include color-svg('../images/profile-group.svg', $color-white); } @@ -2141,6 +2216,33 @@ width: 42px; } +.module-avatar--300 { + height: 300px; + width: 300px; + + img { + height: 300px; + width: 300px; + } +} + +.module-avatar__label--300 { + width: 300px; + font-size: 150px; + line-height: 302px; +} + +.module-avatar__icon--300 { + height: 158px; + width: 158px; +} + +.module-avatar__icon--note-to-self { + width: 70%; + height: 70%; + @include color-svg('../images/note-28.svg', $color-white); +} + .module-avatar--no-image { background-color: $color-conversation-grey; } @@ -2184,7 +2286,16 @@ // Module: Main Header -.main-header-title-wrapper { +.module-main-header { + display: flex; + flex-direction: column; + border-bottom: 1px solid $color-dark-90; + color: $color-dark-05; +} + +.module-main-header__title { + height: 55px; + padding-left: 16px; flex: 1; flex-direction: row; display: flex; @@ -2196,10 +2307,20 @@ } } -.main-header-content-wrapper { +.module-main-header__menu { color: $color-dark-05; + overflow: hidden; - div { + .accordian { + margin-top: -100%; + transition: margin-top 0.35s ease-out; + + &.expanded { + margin-top: 0; + } + } + + .menu-item { padding: 12px; background-color: $color-dark-90; user-select: none; @@ -2211,21 +2332,7 @@ } } -.main-header-wrapper { - overflow-x: hidden; - flex: 1; -} - -.module-main-header { - height: $header-height; - padding-left: 16px; - - display: flex; - flex-direction: row; - align-items: center; -} - -.main-header-content-toggle { +.module-main-header-content-toggle { width: 3em; line-height: 3em; font-weight: bold; @@ -2246,18 +2353,10 @@ } } -.main-header-content-toggle-visible::after { +.module-main-header-content-toggle-visible::after { transform: rotate(180deg); } -.module-main-header__app-name { - font-size: 16px; - line-height: 24px; - font-weight: 300; - margin-left: 32px; - color: $color-dark-05; -} - .module-main-header__contact-name { font-weight: 300; margin-left: 12px; @@ -2266,6 +2365,77 @@ flex: 1; } +.module-main-header__search { + margin: 8px; + position: relative; +} + +.module-main-header__search__icon { + background-color: $color-light-35; +} + +.module-main-header__search__input { + color: $color-dark-05; + background-color: $color-gray-95; + border: 1px solid $color-light-60; + padding: 0 26px 0 30px; + margin-left: 8px; + margin-right: 8px; + outline: 0; + height: 32px; + width: calc(100% - 16px); + outline-offset: -2px; + font-size: 14px; + line-height: 18px; + font-weight: normal; + + position: relative; + border-radius: 4px; + + &:focus { + outline: solid 1px $blue; + } + + &::placeholder { + color: $color-gray-45; + } +} + +.module-main-header__search__icon { + content: ''; + display: inline-block; + width: 18px; + height: 26px; + background-color: $color-light-35; + position: absolute; + left: 14px; + top: 3px; + + cursor: text; + @include color-svg('../images/search.svg', $color-gray-60); +} + +.module-main-header__search__cancel-icon { + position: absolute; + right: 16px; + top: 9px; + height: 14px; + width: 14px; + cursor: pointer; + @include color-svg('../images/x-16.svg', $color-gray-60); +} + +.module-main-header__search__copy-from-clipboard { + position: absolute; + right: 16px; + top: 5px; + width: 20px; + height: 22px; + opacity: 0.8; + cursor: pointer; + @include color-svg('../images/icon-paste.svg', $color-gray-60); +} + // Module: Image .module-image { @@ -2321,6 +2491,13 @@ background-color: $color-black-02; } +.module-image__loading-placeholder { + display: inline-flex; + flex-direction: row; + align-items: center; + background-color: $color-black-015; +} + .module-image__image { object-fit: cover; // redundant with attachment-container, but we get cursor flashing on move otherwise @@ -2438,7 +2615,7 @@ .module-typing-animation { display: inline-flex; - flex-directin: row; + flex-direction: row; align-items: center; height: 8px; @@ -2845,6 +3022,412 @@ @include color-svg('../images/x-16.svg', $color-gray-60); } +// Module: Spinner + +.module-spinner__container { + margin-left: auto; + margin-right: auto; + position: relative; + height: 56px; + width: 56px; +} + +.module-spinner__circle { + position: absolute; + top: 0; + left: 0; + + @include color-svg('../images/spinner-track-56.svg', $color-white-04); + z-index: 2; + height: 56px; + width: 56px; +} +.module-spinner__arc { + position: absolute; + top: 0; + left: 0; + + @include color-svg('../images/spinner-56.svg', $color-gray-60); + z-index: 3; + height: 56px; + width: 56px; + + animation: spinner-arc-animation 1000ms linear infinite; +} + +@keyframes spinner-arc-animation { + 0% { + transform: rotate(0deg); + } + 50% { + transform: rotate(180deg); + } + 100% { + transform: rotate(360deg); + } +} + +// In these --small and --mini sizes, we're exploding our @color-svg mixin so we don't +// have to duplicate our background colors for the dark/ios/size matrix. + +.module-spinner__container--small { + height: 24px; + width: 24px; +} +.module-spinner__circle--small { + -webkit-mask: url('../images/spinner-track-24.svg') no-repeat center; + -webkit-mask-size: 100%; + height: 24px; + width: 24px; +} +.module-spinner__arc--small { + -webkit-mask: url('../images/spinner-24.svg') no-repeat center; + -webkit-mask-size: 100%; + height: 24px; + width: 24px; +} + +.module-spinner__container--mini { + height: 14px; + width: 14px; +} +.module-spinner__circle--mini { + -webkit-mask: url('../images/spinner-track-24.svg') no-repeat center; + -webkit-mask-size: 100%; + height: 14px; + width: 14px; +} +.module-spinner__arc--mini { + -webkit-mask: url('../images/spinner-24.svg') no-repeat center; + -webkit-mask-size: 100%; + height: 14px; + width: 14px; +} + +.module-spinner__circle--incoming { + background-color: $color-white-04; +} +.module-spinner__arc--incoming { + background-color: $color-white; +} + +// Module: Highlighted Message Body + +.module-message-body__highlight { + font-weight: bold; +} + +// Module: Search Results + +.module-search-results { + overflow-y: auto; + max-height: 100%; + color: white; +} + +.module-search-results__conversations-header { + height: 36px; + line-height: 36px; + + margin-left: 16px; + + font-size: 14px; + font-weight: 300; + letter-spacing: 0; +} + +.module-search-results__no-results { + margin-top: 27px; + width: 100%; + text-align: center; +} + +.module-search-results__contacts-header { + height: 36px; + line-height: 36px; + + margin-left: 16px; + + font-size: 14px; + font-weight: 300; + letter-spacing: 0; +} + +.module-search-results__messages-header { + height: 36px; + line-height: 36px; + + margin-left: 16px; + + font-size: 14px; + font-weight: 300; + letter-spacing: 0; +} + +// Module: Message Search Result + +.module-message-search-result { + padding: 8px; + padding-left: 16px; + padding-right: 16px; + min-height: 64px; + max-width: 300px; + + display: flex; + flex-direction: row; + align-items: flex-start; + + cursor: pointer; + &:hover { + background-color: $color-gray-05; + } +} + +.module-message-search-result--is-selected { + background-color: $color-gray-05; +} + +.module-message-search-result__text { + flex-grow: 1; + margin-left: 12px; + // parent - 48px (for avatar) - 16px (our right margin) + max-width: calc(100% - 64px); + + display: inline-flex; + flex-direction: column; + align-items: stretch; +} + +.module-message-search-result__header { + display: flex; + flex-direction: row; + align-items: center; +} + +.module-message-search-result__header__from { + flex-grow: 1; + flex-shrink: 1; + font-size: 14px; + line-height: 18px; + + overflow-x: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + color: $color-gray-90; +} + +.module-message-search-result__header__timestamp { + flex-shrink: 0; + margin-left: 6px; + + font-size: 11px; + line-height: 16px; + letter-spacing: 0.3px; + + overflow-x: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + text-transform: uppercase; + + color: $color-gray-60; +} + +.module-message-search-result__header__name { + font-weight: 300; +} +.module-mesages-search-result__header__group { + font-weight: 300; +} + +.module-message-search-result__body { + margin-top: 1px; + flex-grow: 1; + flex-shrink: 1; + + font-size: 13px; + + color: $color-gray-60; + + max-height: 3.6em; + + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + + // Note: -webkit-line-clamp doesn't work for RTL text, and it forces you to use + // ... as the truncation indicator. That's not a solution that works well for + // all languages. More resources: + // - http://hackingui.com/front-end/a-pure-css-solution-for-multiline-text-truncation/ + // - https://medium.com/mofed/css-line-clamp-the-good-the-bad-and-the-straight-up-broken-865413f16e5 +} + +// Module: Left Pane + +.module-left-pane { + border-right: 1px solid $color-dark-90; + + display: inline-flex; + flex-direction: column; + width: 380px; + height: 100%; +} + +.module-left-pane__header { + flex-shrink: 0; + flex-grow: 0; +} + +.module-left-pane__tabs { + color: $color-dark-05; + background-color: $color-dark-75; + display: flex; + flex-direction: row; + + .tab { + width: 50%; + padding: 16px; + text-align: center; + cursor: pointer; + + &:hover { + background-color: $color-dark-72; + } + } + + .tab.selected { + background-color: #383c46; + } +} + +.module-left-pane__archive-header { + height: 48px; + width: 100%; + + display: inline-flex; + flex-direction: row; + align-items: center; + + border-bottom: 1px solid $color-gray-15; +} + +.module-left-pane__to-inbox-button { + margin-left: 2px; + + width: 35px; + height: 35px; + + cursor: pointer; + @include color-svg('../images/back.svg', $color-gray-60); +} + +.module-left-pane__archive-header-text { + color: $color-gray-90; + font-size: 16px; + font-weight: 300px; +} + +.module-left-pane__archive-helper-text { + flex-grow: 0; + flex-shrink: 0; + + padding: 1em; + font-size: 12px; + color: $color-gray-60; + background-color: $color-gray-05; +} + +.module-left-pane__list { + flex-grow: 1; + flex-shrink: 1; + overflow-y: auto; + overflow-x: hidden; +} + +.module-left-pane__virtual-list { + outline: none; +} + +.module-left-pane__archived-button { + font-size: 14px; + height: 64px; + line-height: 64px; + text-align: center; + font-weight: 300; + color: $color-gray-60; + + cursor: pointer; + &:hover { + background-color: $color-gray-05; + } +} + +.module-left-pane__archived-button__archived-count { + font-size: 12px; + font-weight: 300; + color: $color-gray-60; + background-color: $color-gray-05; + padding: 6px; + padding-top: 1px; + padding-bottom: 1px; + border-radius: 10px; +} + +// Module: Start New Conversation + +.module-start-new-conversation { + display: flex; + flex-direction: row; + align-items: center; + cursor: pointer; + padding: 8px 16px; + opacity: 0.7; + + &.valid { + opacity: 1; + } +} + +.module-start-new-conversation__avatar { + display: inline-block; + height: 48px; + width: 48px; + border-radius: 50%; + background-size: cover; + vertical-align: middle; + text-align: center; + line-height: 48px; + overflow-x: hidden; + text-overflow: ellipsis; + color: #ffffff; + font-size: 18px; + background-color: #616161; +} + +.module-start-new-conversation__content { + overflow: hidden; + margin-left: 12px; + flex: 1; +} + +.module-start-new-conversation__number { + margin: 0; + font-size: 1em; + text-overflow: ellipsis; + overflow-x: hidden; + text-align: left; + font-weight: 300; +} + +.module-start-new-conversation__text { + margin-top: 3px; + font-style: italic; + color: $color-gray-60; + font-size: 13px; +} + // Third-party module: react-contextmenu .react-contextmenu { diff --git a/stylesheets/_recorder.scss b/stylesheets/_recorder.scss index a6b2370cdb..b76a1985e5 100644 --- a/stylesheets/_recorder.scss +++ b/stylesheets/_recorder.scss @@ -17,7 +17,6 @@ } &:before { - margin-top: 4px; content: ''; display: inline-block; height: 24px; diff --git a/stylesheets/_session-slider.scss b/stylesheets/_session-slider.scss new file mode 100644 index 0000000000..85aa3f3f90 --- /dev/null +++ b/stylesheets/_session-slider.scss @@ -0,0 +1,268 @@ +.slider { + &-wrapper { + display: flex; + align-items: center; + margin: 20px 10px; + } + + &-info { + display: block; + margin-left: 20px; + text-align: center; + + p { + white-space: nowrap; + } + } +} + +.rc-slider { + position: relative; + height: 14px; + padding: 5px 0; + width: 100%; + border-radius: 6px; + -ms-touch-action: none; + touch-action: none; + box-sizing: border-box; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +.rc-slider * { + box-sizing: border-box; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +.rc-slider-rail { + position: absolute; + width: 100%; + background-color: $session-shade-6; + height: 8px; + border-radius: 6px; + margin: 0px 0px 0px -1px; +} +.rc-slider-handle { + transition: $session-transition-duration; + position: absolute; + width: 6px; + height: 25px; + cursor: pointer; + cursor: -webkit-grab; + margin-top: -9.5px; + margin-left: -2.3px; + cursor: grab; + border-radius: 4px; + /* border: solid 2px rgba(0, 247, 130, 0.8); */ + background-color: rgba(0, 247, 130, 1); + -ms-touch-action: pan-x; + touch-action: pan-x; +} +.rc-slider-handle:focus { + border-color: $session-color-green; + box-shadow: 0 0 0 5px rgba($session-color-white, 0.2); + outline: none; +} +.rc-slider-handle-click-focused:focus { + border-color: rgba($session-color-white, 0.2); + box-shadow: unset; +} +.rc-slider-handle:hover { + border-color: $session-color-green; +} +.rc-slider-handle:active { + border-color: $session-color-green; + box-shadow: 0 0 5px rgba($session-color-white, 0.2); + cursor: -webkit-grabbing; + cursor: grabbing; +} +.rc-slider-mark { + position: absolute; + top: 18px; + left: 0; + width: 100%; + font-size: 12px; +} +.rc-slider-mark-text { + position: absolute; + display: inline-block; + vertical-align: middle; + text-align: center; + cursor: pointer; + color: #999; +} +.rc-slider-mark-text-active { + color: #666; +} +.rc-slider-step { + position: absolute; + width: 100%; + height: 4px; + top: -4px; + background: transparent; +} +.rc-slider-dot { + position: absolute; + bottom: -2px; + margin-left: -4px; + width: 3px; + height: 6px; + background-color: $session-shade-6; + cursor: pointer; + border-radius: 2px; + vertical-align: middle; +} +.rc-slider-dot:first-child { + margin-left: -2px; +} +.rc-slider-dot:last-child { + margin-right: -2px; +} +.rc-slider-dot-active { + border-color: #96dbfa; +} +.rc-slider-dot-reverse { + margin-left: 0; + margin-right: -4px; +} +.rc-slider-disabled { + background-color: #e9e9e9; +} +.rc-slider-disabled .rc-slider-handle, +.rc-slider-disabled .rc-slider-dot { + border-color: #ccc; + box-shadow: none; + background-color: #fff; + cursor: not-allowed; +} +.rc-slider-disabled .rc-slider-mark-text, +.rc-slider-disabled .rc-slider-dot { + cursor: not-allowed !important; +} +.rc-slider-vertical { + width: 14px; + height: 100%; + padding: 0 5px; +} +.rc-slider-vertical .rc-slider-rail { + height: 100%; + width: 4px; +} +.rc-slider-vertical .rc-slider-handle { + margin-left: -5px; + -ms-touch-action: pan-y; + touch-action: pan-y; +} +.rc-slider-vertical .rc-slider-mark { + top: 0; + left: 18px; + height: 100%; +} +.rc-slider-vertical .rc-slider-step { + height: 100%; + width: 4px; +} +.rc-slider-vertical .rc-slider-dot { + left: 2px; + margin-bottom: -4px; +} +.rc-slider-vertical .rc-slider-dot:first-child { + margin-bottom: -4px; +} +.rc-slider-vertical .rc-slider-dot:last-child { + margin-bottom: -4px; +} +.rc-slider-tooltip-zoom-down-enter, +.rc-slider-tooltip-zoom-down-appear { + animation-duration: 0.3s; + animation-fill-mode: both; + display: block !important; + animation-play-state: paused; +} +.rc-slider-tooltip-zoom-down-leave { + animation-duration: 0.3s; + animation-fill-mode: both; + display: block !important; + animation-play-state: paused; +} +.rc-slider-tooltip-zoom-down-enter.rc-slider-tooltip-zoom-down-enter-active, +.rc-slider-tooltip-zoom-down-appear.rc-slider-tooltip-zoom-down-appear-active { + animation-name: rcSliderTooltipZoomDownIn; + animation-play-state: running; +} +.rc-slider-tooltip-zoom-down-leave.rc-slider-tooltip-zoom-down-leave-active { + animation-name: rcSliderTooltipZoomDownOut; + animation-play-state: running; +} +.rc-slider-tooltip-zoom-down-enter, +.rc-slider-tooltip-zoom-down-appear { + transform: scale(0, 0); + animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); +} +.rc-slider-tooltip-zoom-down-leave { + animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); +} +@keyframes rcSliderTooltipZoomDownIn { + 0% { + opacity: 0; + transform-origin: 50% 100%; + transform: scale(0, 0); + } + 100% { + transform-origin: 50% 100%; + transform: scale(1, 1); + } +} +@keyframes rcSliderTooltipZoomDownOut { + 0% { + transform-origin: 50% 100%; + transform: scale(1, 1); + } + 100% { + opacity: 0; + transform-origin: 50% 100%; + transform: scale(0, 0); + } +} +.rc-slider-tooltip { + position: absolute; + left: -9999px; + top: -9999px; + visibility: visible; + box-sizing: border-box; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +.rc-slider-tooltip * { + box-sizing: border-box; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +.rc-slider-tooltip-hidden { + display: none; +} +.rc-slider-tooltip-placement-top { + padding: 4px 0 8px 0; +} +.rc-slider-tooltip-inner { + padding: 6px 2px; + min-width: 24px; + height: 24px; + font-size: 12px; + line-height: 1; + color: #fff; + text-align: center; + text-decoration: none; + background-color: #6c6c6c; + border-radius: 6px; + box-shadow: 0 0 4px #d9d9d9; +} +.rc-slider-tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.rc-slider-tooltip-placement-top .rc-slider-tooltip-arrow { + bottom: 4px; + left: 50%; + margin-left: -4px; + border-width: 4px 4px 0; + border-top-color: #6c6c6c; +} diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss new file mode 100644 index 0000000000..5781ced1e2 --- /dev/null +++ b/stylesheets/_session.scss @@ -0,0 +1,1763 @@ +@font-face { + font-family: 'SpaceMono'; + src: url('../fonts/SpaceMono-Regular.ttf') format('truetype'); +} + +@font-face { + font-family: 'SpaceMono'; + src: url('../fonts/SpaceMono-Bold.ttf') format('truetype'); + font-weight: bold; +} + +@font-face { + font-family: 'SpaceMono'; + src: url('../fonts/SpaceMono-Italic.ttf') format('truetype'); + font-style: italic; +} + +@font-face { + font-family: 'SpaceMono'; + src: url('../fonts/SpaceMono-BoldItalic.ttf') format('truetype'); + font-weight: bold; + font-style: italic; +} +@font-face { + font-family: 'Wasa'; + src: url('../fonts/Wasa-Bold.otf') format('opentype'); + font-weight: bold; +} +@font-face { + font-family: 'SF Pro Text'; + src: url('../fonts/SFProText-Regular.ttf') format('truetype'); +} +@font-face { + font-family: 'SF Pro Display'; + src: url('../fonts/SFProDisplay-Regular.otf') format('opentype'); +} +@keyframes fadein { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +// Session Colors +$session-font-family: 'Wasa'; + +$session-color-green: #00f782; +$session-color-green-alt-1: #00f480; +$session-color-green-alt-2: #00fd73; +$session-color-green-alt-3: #00f782; + +$session-shade-1: #0c0c0c; +$session-shade-2: #161616; +$session-shade-3: #191818; +$session-shade-4: #1b1b1b; +$session-shade-5: #222325; +$session-shade-6: #232323; +$session-shade-7: #2e2e2e; +$session-shade-8: #2f2f2f; +$session-shade-9: #313131; +$session-shade-10: #3e3e3e; +$session-shade-11: #3f3f3f; +$session-shade-12: #3f4146; +$session-shade-13: #474646; +$session-shade-14: #535865; +$session-shade-15: #5b6c72; +$session-shade-16: #979797; +$session-shade-17: #a0a0a0; +$session-shade-18: #141414; + +$session-opaque-dark-1: rgba(0, 0, 0, 0.25); +$session-opaque-dark-2: rgba(0, 0, 0, 0.37); +$session-opaque-dark-3: rgba(0, 0, 0, 0.5); + +$session-color-white: #fff; +$session-color-dark-grey: #353535; +$session-color-black: #000; +$session-color-danger: #ff453a; +$session-color-primary: $session-shade-13; +$session-color-secondary: $session-shade-6; +$session-background-overlay: #212121; +$session-background: #121212; + +$session-color-info: $session-shade-11; +$session-color-success: #35d388; +$session-color-error: #edd422; +$session-color-warning: $session-shade-17; +$session-color-warning-alt: #ff9d00; + +$session-color-light-grey: #a0a0a0; + +$session-shadow-opacity: 0.15; +$session-overlay-opacity: 0.3; + +$session-margin-xs: 5px; +$session-margin-sm: 10px; +$session-margin-md: 15px; +$session-margin-lg: 20px; + +$session-font-xs: 11px; +$session-font-sm: 13px; +$session-font-md: 15px; +$session-font-lg: 18px; +$session-font-xl: 24px; + +$session-font-h1: 30px; +$session-font-h2: 24px; +$session-font-h3: 20px; +$session-font-h4: 16px; + +$session-search-input-height: 34px; +$main-view-header-height: 85px; +$session-left-pane-width: 300px; +$session-left-pane-sections-container-width: 80px; + +div.spacer-sm { + height: $session-margin-sm; +} +div.spacer-md { + height: $session-margin-md; +} +div.spacer-lg { + height: $session-margin-lg; +} + +@mixin session-filter-color-green { + filter: brightness(0) saturate(100%) invert(67%) sepia(69%) saturate(2367%) + hue-rotate(101deg) brightness(107%) contrast(101%); +} + +@mixin session-color-subtle($color) { + color: rgba($color, 0.6); +} + +.text-subtle { + opacity: 0.6; +} + +.text-soft { + opacity: 0.4; +} + +.text-center { + text-align: center; +} + +.fullwidth { + width: 100%; +} + +input, +textarea { + caret-color: $session-color-green !important; +} + +@mixin text-highlight($color) { + background-color: $color; + padding: $session-margin-xs $session-margin-sm; + border-radius: 3px; + display: inline-block; +} + +@mixin session-dark-background { + background-color: $session-background; +} +@mixin session-dark-background-lighter { + background-color: $session-background-overlay; +} +@mixin session-dark-background-hover { + background-color: $session-shade-7; +} + +$session-transition-duration: 0.25s; +$session-fadein-duration: 0.1s; + +$session-icon-size-sm: 15px; +$session-icon-size-md: 20px; +$session-icon-size-lg: 30px; + +$session-modal-size-sm: 220px; +$session-modal-size-md: 400px; +$session-modal-size-lg: 650px; + +$session-conversation-header-height: 60px; + +@mixin fontWasaBold { + font-weight: bold; + font-family: $session-font-family; +} + +a, +div, +span, +label { + user-select: none; + + &::selection { + background: $session-shade-17; + } +} + +$session-gradient-green: linear-gradient( + 270deg, + rgba($session-color-green-alt-1, 1), + rgba($session-color-green-alt-1, 0.6) +); +$session-gradient-black: linear-gradient( + 90deg, + rgba($session-shade-3, 1), + rgba($session-shade-4, 0.6) +); + +$session-dark-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.37); + +$session_message-container-border-radius: 5px; + +.shadowed { + opacity: $session-shadow-opacity; +} +.overlayed { + opacity: $session-overlay-opacity; + pointer-events: none; +} +.overlay { + display: block !important; + z-index: 1; +} + +#main-view { + height: 100vh; + display: flex; + flex-grow: 1; + + .conversation-stack { + display: block; + width: 100%; + } +} + +.button-group > div { + display: inline-flex; + margin-left: 5px; + margin-right: 5px; +} + +.session-button { + @mixin transparent-background($textAndBorderColor) { + background-color: Transparent; + background-repeat: no-repeat; + overflow: hidden; + outline: none; + color: $textAndBorderColor; + border: 2px solid $textAndBorderColor; + } + + width: auto; + display: flex; + justify-content: center; + font-weight: 700; + user-select: none; + white-space: nowrap; + cursor: pointer; + transition: $session-transition-duration; + background-color: rgba(0, 0, 0, 0); + + &.disabled { + cursor: default; + } + + &.default, + &.square, + &.brand { + color: $session-color-white; + + &:not(.disabled):hover { + filter: brightness(90%); + } + + &.green, + &.white, + &.primary, + &.secondary, + &.success, + &.danger, + &.warning { + &.disabled { + filter: brightness(60%); + } + } + + &.green { + background-color: $session-color-green; + } + + &.white { + background-color: $session-color-white; + } + &.primary { + background-color: $session-color-primary; + } + &.secondary { + background-color: $session-color-secondary; + } + &.success { + background-color: $session-color-success; + } + &.danger { + background-color: $session-color-danger; + } + &.warning { + background-color: $session-color-warning; + } + } + + &.brand-outline, + &.default-outline, + &.square-outline { + border: none; + + &.green { + @include transparent-background($session-color-green); + } + &.white { + @include transparent-background($session-color-white); + } + &.primary { + @include transparent-background($session-color-primary); + } + &.secondary { + @include transparent-background($session-color-secondary); + } + &.danger { + @include transparent-background($session-color-danger); + } + &.warning { + @include transparent-background($session-color-warning-alt); + } + &.warning, + &.danger, + &.secondary, + &.primary, + &.white, + &.green { + &.disabled { + filter: brightness(60%); + + &:hover { + filter: brightness(60%); + } + } + } + } + + &.brand { + min-width: 165px; + height: 45px; + line-height: 40px; + padding: 0px $session-margin-lg; + font-size: $session-font-md; + font-family: $session-font-family; + border-radius: 500px; + + &:not(.disabled):hover { + color: $session-color-white; + border-color: $session-color-white; + } + } + + &.default, + &.square, + &.default-outline, + &.square-outline { + border-radius: 2px; + height: 33px; + padding: 0px 18px; + line-height: 33px; + font-size: $session-font-sm; + } + + &.square, + &.square-outline { + border-radius: 0px; + } + + & > *:hover:not(svg) { + filter: brightness(80%); + } +} + +.session-label { + color: $session-color-white; + padding: $session-margin-sm; + width: 100%; + border-radius: 2px; + text-align: center; + + &.primary { + background-color: $session-color-primary; + } + &.secondary { + background-color: $session-color-secondary; + } + &.success { + background-color: $session-color-success; + } + &.danger { + background-color: $session-color-danger; + } + &.warning { + background-color: $session-color-warning-alt; + } +} + +@mixin set-icon-margin($size) { + margin: $size / 3; +} +.session-icon { + &.padded-left { + @include set-icon-margin($session-icon-size-md); + } +} +.session-icon-button { + cursor: pointer; + display: inline-block; + position: relative; + opacity: 0.4; + transform: translateZ(0); + + &:hover { + opacity: 1; + } + transition: opacity $session-transition-duration; + + &.no-opacity { + opacity: 1; + } + + &.small.padded { + @include set-icon-margin($session-icon-size-sm); + } + + &.medium.padded { + @include set-icon-margin($session-icon-size-md); + } + + &.large.padded { + @include set-icon-margin($session-icon-size-lg); + } + + .notification-count { + position: absolute; + font-size: $session-font-xs; + font-family: $session-font-family; + top: 20px; + right: 20px; + width: 20px; + height: 20px; + padding: 3px; + border-radius: 50%; + font-weight: 700; + background: red; + color: $session-color-white; + text-align: center; + opacity: 1; + } +} + +.session-icon { + fill: $session-color-white; +} + +$session-separator-element-border: 1px solid $session-shade-6; +$session-element-border-green: 4px solid $session-color-green; + +/* CONVERSATION AND MESSAGES */ + +@mixin standard-icon-button() { + color: $session-color-white; + opacity: 0.6; + + &:hover { + opacity: 1; + } +} + +.module-conversation-header__title-flex, +.module-conversation-header__title { + font-family: Wasa; + width: 100%; + display: flex; + font-size: $session-font-md; + + &-text { + @include session-color-subtle($session-color-white); + font-family: 'SF Pro Text'; + font-weight: 300; + font-size: $session-font-xs; + } + + .module-contact-name { + width: 100%; + } + + .module-contact-name__profile-number { + text-align: center; + } +} + +.module-conversation-header__title { + flex-direction: column; +} +.module-conversation-header__title-flex { + flex-direction: row; +} +.module-conversation__user__profile-name, +.module-message__author__profile-name { + font-style: normal; +} + +.module-message__author-avatar { + display: inline-flex; + margin-right: 20px; + padding-top: 5px; +} + +.module-message__container { + border-radius: $session_message-container-border-radius; +} +label { + user-select: none; +} + +.module-message__attachment-container, +.module-image--curved-bottom-right, +.module-image--curved-bottom-left { + border-top-left-radius: 0px; + border-top-right-radius: 0px; + border-bottom-left-radius: $session_message-container-border-radius; + border-bottom-right-radius: $session_message-container-border-radius; +} + +.conversation-header { + .module-avatar img { + box-shadow: 0px 0px 5px 0px rgba(255, 255, 255, 0.2); + } + + .search-icon { + margin-right: 10px; + } + + .session-icon-button { + @include standard-icon-button(); + } +} + +.module-conversation-header { + position: relative; + padding: 0px $session-margin-lg 0px $session-margin-sm; +} + +.title-wrapper { + position: relative; +} + +.module-left-pane-overlay { + h3 { + margin-bottom: 6px; + } +} +.message-selection-overlay { + display: none; + position: absolute; + right: $session-margin-sm; + left: $session-margin-md; + + .close-button { + float: left; + margin: 17px 0px 0px 0px; + } +} +.message-selection-overlay div[role='button'] { + display: inline-block; +} + +.message-selection-overlay .button-group { + float: right; + margin-top: 13.5px; +} + +.hidden { + visibility: hidden; +} + +.session-button div[role='button'] { + cursor: pointer; +} + +#session-toast-container { + position: fixed; + right: $session-margin-lg; + bottom: $session-margin-lg; + + z-index: 10000; +} + +.session-toast { + position: relative; + padding: $session-margin-md $session-margin-md; + background-color: rgba($session-shade-6, 0.8); + margin-bottom: $session-margin-md; + display: flex; + flex-direction: row; + justify-content: flex-start; + + .toast-icon, + .toast-info { + display: flex; + flex-direction: column; + justify-content: center; + } + + .toast-icon { + padding-right: $session-icon-size-md; + } + .toast-info { + margin-right: $session-icon-size-sm + $session-icon-size-sm; + width: 350px; + + &-container { + display: block; + } + } + + .title, + .description { + text-overflow: ellipsis; + } + + .title { + font-size: $session-font-md; + line-height: $session-font-sm; + margin-bottom: $session-margin-sm; + padding-top: 0px; + color: $session-color-white; + padding-top: 0px; + } + + .description { + font-size: $session-font-xs; + @include session-color-subtle($session-color-white); + } + + .toast-close { + @include session-color-subtle($session-color-white); + position: absolute; + top: $session-margin-md; + right: $session-margin-md; + + &:hover { + color: $session-color-white; + } + } + + @mixin set-toast-theme($color) { + border-left: 4px solid $color; + } + &.info { + @include set-toast-theme($session-color-info); + } + &.success { + @include set-toast-theme($session-color-success); + } + &.warning { + @include set-toast-theme($session-color-warning-alt); + } + &.error { + @include set-toast-theme($session-color-error); + } +} + +.session-modal { + animation: fadein $session-transition-duration; + z-index: 150; + min-width: 300px; + + box-sizing: border-box; + max-height: 70vh; + max-width: 70vw; + background-color: $session-shade-4; + border: 1px solid $session-shade-8; + + &__header { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + padding: $session-margin-lg; + + font-family: 'Wasa'; + text-align: center; + line-height: 18px; + font-size: $session-font-md; + font-weight: 700; + + &.reverse { + flex-direction: row-reverse; + + .session-modal__header__close > div { + float: right; + } + + .session-modal__header__icons > div { + float: left; + padding-left: 0px; + padding-right: 10px; + } + } + + &__icons, + &__close { + width: 60px; + } + &__icons { + float: right; + } + &__close > div { + float: left; + } + &__icons > div { + float: right; + padding-left: 10px; + } + } + + &__body { + padding: 0px $session-margin-lg $session-margin-lg $session-margin-lg; + font-family: 'Wasa'; + line-height: $session-font-md; + font-size: $session-font-sm; + + .message { + text-align: center; + } + + .session-id-editable { + width: 30vw; + max-width: 700px; + } + } + + &__centered { + display: flex; + flex-direction: column; + align-items: center; + } + + &__button-group { + display: flex; + justify-content: flex-end; + + .session-button { + margin: $session-margin-xs; + } + + &__center { + display: flex; + justify-content: center; + } + } + + &__text-highlight { + @include text-highlight($session-color-green); + + color: black; + + font-family: monospace; + font-style: normal; + font-size: $session-font-xs; + } +} + +.session-confirm { + &-wrapper { + .session-modal__body .session-modal__centered { + margin: $session-margin-lg; + } + } + + &-main-message { + font-size: $session-font-md; + } + &-sub-message { + margin-top: 5px; + } +} + +.session-toggle { + width: 51px; + height: 31px; + border: 1.5px solid #e5e5ea; + border-radius: 16px; + position: relative; + + cursor: pointer; + background-color: rgba(0, 0, 0, 0); + + .knob { + position: absolute; + top: 0.5px; + left: 0.5px; + height: 27px; + width: 27px; + border-radius: 28px; + background-color: $session-color-white; + box-shadow: 0 0 3px 1px rgba(0, 0, 0, 0.05), 0 3px 1px 0 rgba(0, 0, 0, 0.05), + 0 2px 2px 0 rgba(0, 0, 0, 0.1), 0 3px 3px 0 rgba(0, 0, 0, 0.05); + + transition: transform 0.25s ease, background-color 0.25s ease; + } + + &.active { + background-color: $session-color-green; + border-color: $session-color-green; + + .knob { + transform: translateX(20px); + } + } +} + +.react-contextmenu { + padding: 0px; + margin: 0px; + + border: none !important; + border-radius: 0px; +} + +.react-contextmenu-item { + display: flex; + align-items: center; + + height: 25px; + padding: $session-margin-md $session-margin-sm; + + background-color: $session-shade-4; + + color: $session-color-white; + font-family: 'Wasa'; + font-size: $session-font-sm; + line-height: $session-icon-size-sm; + font-weight: 700; + + &--active, + &--selected { + background-color: $session-shade-7 !important; + } + &:active, + &:visited, + &:focus { + outline: none; + } +} + +.session-dropdown { + position: absolute; + top: 50px; + left: 50px; + display: inline-block; + + ul { + display: block; + list-style: none; + padding: 0px; + margin: 0px; + + li { + cursor: pointer; + + height: 25px; + padding-right: 7px; + background-color: $session-shade-4; + + color: $session-color-white; + font-family: 'Wasa'; + font-size: $session-font-xs; + line-height: $session-icon-size-sm; + font-weight: 700; + + display: flex; + align-items: center; + + .session-icon { + margin-left: 6px; + } + .item-content { + margin-left: 6px; + } + + &.active, + &:hover { + background-color: $session-shade-7; + } + + &.danger { + color: $session-color-danger; + } + } + } +} + +.edit-profile-dialog { + .session-modal__header__title { + font-size: $session-font-lg; + } + + .session-modal { + width: $session-modal-size-md; + + &__header { + height: 68.45px; + } + } + + .avatar-center-inner { + position: relative; + + .module-avatar { + box-shadow: 0 0 23px 0 rgba($session-color-black, 0.78); + } + + .qr-view-button { + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + position: absolute; + right: -3px; + height: 26px; + width: 26px; + border-radius: 50%; + padding-top: 3px; + background-color: $session-color-white; + transition: $session-transition-duration; + + &:hover { + filter: brightness(90%); + } + + .session-icon-button { + opacity: 1; + } + } + } + + .image-upload-section { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + cursor: pointer; + width: 80px; + height: 80px; + border-radius: 100%; + background-color: rgba($session-color-black, 0.72); + box-shadow: 0px 0px 3px 0.5px rgba(0, 0, 0, 0.75); + opacity: 0; + transition: $session-transition-duration; + + &:after { + content: 'Edit'; + } + + &:hover { + opacity: 1; + } + } + + .session-id-section { + display: flex; + align-items: center; + flex-direction: column; + + .panel-text-divider { + margin-top: 35px; + margin-bottom: 35px; + } + + &-display { + text-align: center; + word-break: break-all; + font-size: $session-font-md; + padding: 0px $session-margin-lg; + font-family: 'SF Pro Text'; + font-weight: 100; + color: rgba($session-color-white, 0.8); + font-size: $session-font-md; + padding: 0px $session-margin-sm; + } + + .session-button { + width: 148px; + } + } + + .profile-name { + display: flex; + justify-content: center; + margin-top: $session-margin-lg; + + &-input { + height: 38px; + width: 142px; + border-radius: 5px; + text-align: center; + font-size: $session-font-md; + background-color: $session-shade-5 !important; + } + + &-uneditable { + display: flex; + align-items: center; + justify-content: center; + margin-left: $session-margin-lg; + + p { + font-size: $session-font-md; + padding: 0px $session-margin-sm; + } + } + } +} + +.qr-image { + display: flex; + justify-content: center; + + svg { + width: 135px; + height: 135px; + border-radius: 3px; + padding: $session-margin-xs; + background-color: $session-color-white; + } +} + +.conversation-loader { + position: absolute; + top: 50%; + left: 50%; + margin: -40px 0 0 -40px; + + & > div { + display: block; + } +} + +.session-loader { + display: inline-block; + position: relative; + width: 80px; + height: 80px; + + div { + position: absolute; + top: 33px; + width: 13px; + height: 13px; + border-radius: 50%; + background: $session-color-green; + animation-timing-function: cubic-bezier(0, 1, 1, 0); + } + div:nth-child(1) { + left: 8px; + animation: session-loader1 0.6s infinite; + } + div:nth-child(2) { + left: 8px; + animation: session-loader2 0.6s infinite; + } + div:nth-child(3) { + left: 32px; + animation: session-loader2 0.6s infinite; + } + div:nth-child(4) { + left: 56px; + animation: session-loader3 0.6s infinite; + } + @keyframes session-loader1 { + 0% { + transform: scale(0); + } + 100% { + transform: scale(1); + } + } + @keyframes session-loader3 { + 0% { + transform: scale(1); + } + 100% { + transform: scale(0); + } + } + @keyframes session-loader2 { + 0% { + transform: translate(0, 0); + } + 100% { + transform: translate(24px, 0); + } + } +} + +.session-settings { + width: 100%; + height: 100vh; + display: flex; + flex-direction: column; + + &-list { + overflow-y: scroll; + } + + &-header { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + background-color: $session-shade-6; + height: $main-view-header-height; + + &-title { + line-height: $main-view-header-height; + font-weight: bold; + font-size: $session-font-lg; + text-align: center; + flex-grow: 1; + } + + .session-button, + .session-icon-button { + margin-right: $session-margin-lg; + } + } + + &-item { + font-size: $session-font-md; + color: $session-color-white; + background-color: $session-shade-1; + + padding: $session-margin-lg; + margin-bottom: 20px; + border-bottom: 1px solid $session-shade-5; + + &.inline { + display: flex; + align-items: center; + justify-content: space-between; + } + + &__info { + padding-right: $session-margin-lg; + } + + &__title { + line-height: 1.7; + font-size: $session-font-lg; + font-weight: bold; + } + + &__description { + font-family: 'SF Pro Text'; + font-size: $session-font-sm; + font-weight: 100; + max-width: 700px; + @include session-color-subtle($session-color-white); + } + + &__content { + label { + @include session-color-subtle($session-color-white); + } + } + } + + &-view { + flex-grow: 1; + display: flex; + flex-direction: column; + justify-content: space-between; + } + + &__version-info { + display: flex; + justify-content: space-between; + + padding: $session-margin-sm $session-margin-md; + background-color: $session-shade-5; + font-size: $session-font-xs; + + span { + opacity: 0.4; + transition: $session-transition-duration; + + &:hover { + opacity: 1; + } + } + } + + &__password-lock { + display: flex; + align-items: center; + justify-content: center; + flex-grow: 1; + + &-box { + padding: 45px 60px; + display: flex; + flex-direction: column; + align-items: center; + + max-width: 90%; + width: 600px; + + background-color: $session-shade-4; + border: 1px solid $session-shade-8; + border-radius: 5px; + + h3 { + padding: 0px; + margin-bottom: $session-margin-lg; + } + + input { + width: 100%; + color: $session-color-white; + background-color: $session-shade-7; + padding: $session-margin-xs $session-margin-md; + margin-bottom: $session-margin-lg; + outline: none; + border: none; + border-radius: 2px; + text-align: center; + font-size: $session-font-xl; + letter-spacing: 5px; + font-family: 'SF Pro Text'; + } + } + } +} + +#qr svg, +.qr-image svg { + width: $session-modal-size-sm; + height: $session-modal-size-sm; + padding: $session-margin-xs; + background-color: $session-color-white; + border-radius: 3px; +} + +.qr-dialog { + &__description { + max-width: $session-modal-size-sm; + text-align: center; + margin: 0 auto; + } +} + +.seed-dialog { + &__description { + max-width: $session-modal-size-lg; + } +} + +.messages li { + transition: $session-transition-duration !important; +} + +.discussion-container { + .module-message { + font-family: 'SF Pro Text'; + border-radius: 5px; + } +} + +.bottom-bar { + .compose { + min-height: 60px; + max-height: 200px; + } + + .send-message-container { + display: flex; + flex-grow: 1; + min-height: 60px; + margin-bottom: -5px; + } + + textarea.send-message { + background-color: $session-shade-4; + border: none; + margin: 0px; + padding: 0px $session-margin-lg; + font-size: $session-font-md; + line-height: 1.3em; + align-self: center; + + @include session-color-subtle($session-color-white); + } +} + +.bottom-bar form { + &.send { + background-color: $session-shade-4; + } + + &.active textarea { + border: none; + } +} + +.dark-theme .bottom-bar .send-message[disabled='disabled'] { + background: $session-shade-4 !important; +} + +.session-radio-group fieldset { + border: none; + margin-left: $session-margin-sm; + margin-top: $session-margin-sm; + + .session-radio { + padding: $session-margin-xs 0px; + } +} + +.session-radio { + input[type='radio'] { + border: 0; + opacity: 0; + padding: 0; + position: absolute; + cursor: pointer; + } + + label { + margin-right: 1em; + } + + label:before { + content: ''; + display: inline-block; + width: 0.5em; + height: 0.5em; + margin-right: 0.8em; + border-radius: 100%; + vertical-align: -3px; + border: 2px solid rgba($session-color-white, 0.6); + padding: 0.2em; + background-color: transparent; + background-clip: content-box; + } + + input:hover + label:before { + border-color: $session-color-white; + } + + input:checked + label:before { + background-color: $session-color-white; + border-color: $session-color-white; + } +} + +.session-id-editable { + padding: $session-margin-lg; + + textarea { + width: 30vh; + } +} +.session-id-editable textarea { + resize: none; + overflow: hidden; + user-select: all; +} + +input { + user-select: text; +} + +.dark-theme .invite-friends-dialog .member-item { + height: 64px; + background-color: $session-shade-4; + border: $session-separator-element-border; + display: flex; + align-items: center; + transition: $session-transition-duration; + + .module-avatar, + .name-part { + margin: auto $session-margin-md; + } + + .check-mark { + margin: auto $session-margin-xs; + } + + .name-part { + flex-grow: 1; + } +} + +.dark-theme .modal .content { + background-color: $session-shade-4; +} + +.loki-dialog button { + border: none; +} + +.friend-selection-list { + width: 40vh; +} + +.session-beta-disclaimer { + .session-modal { + width: 600px; + + &__header { + font-size: $session-font-lg; + } + + &__body > div:first-child { + padding: $session-margin-lg; + } + + .session-confirm-main-message { + opacity: 0.8; + margin-bottom: $session-margin-xs; + } + + .session-confirm-sub-message { + text-align: center; + } + } +} + +.session-confirm-wrapper { + position: absolute; + height: 100%; + width: 100%; + display: flex; + + .session-modal { + margin: auto auto; + } +} + +.module-scroll-down { + animation: fadein $session-fadein-duration; + bottom: 15px; + + .session-icon-button { + display: flex; + justify-content: center; + align-items: center; + height: 40px; + width: 40px; + border-radius: 50%; + opacity: 1; + background-color: $session-shade-8; + + svg path { + transition: $session-transition-duration; + opacity: 0.6; + } + + &:hover svg path { + opacity: 1; + } + } +} + +/* Memberlist */ +.member-list-container .member { + &-item { + font-family: 'SF Pro Text'; + padding: $session-margin-sm $session-margin-md; + background-color: $session-shade-5; + + &:hover:not(.member-selected) { + background-color: $session-shade-7; + } + } + + &-selected { + background-color: $session-shade-8; + } +} + +.standalone { + .button { + background: $session-color-green; + } + + #reset-button { + color: $session-color-green; + } + + .inputs { + input { + color: $color-dark-05; + background-color: $color-dark-70; + border-color: $color-dark-55; + + &:focus { + outline: none; + } + } + } +} + +.clear-data, +.password-prompt { + &-wrapper { + display: flex; + justify-content: center; + align-items: center; + + background-color: $session-color-black; + + width: 100%; + height: 100%; + + padding: 3 * $session-margin-lg; + } + + &-error-section { + width: 100%; + color: $session-color-white; + margin: -$session-margin-sm 0px 2 * $session-margin-lg 0px; + + .session-label { + &.primary { + background-color: rgba($session-color-primary, 0.3); + } + padding: $session-margin-xs $session-margin-sm; + font-size: $session-font-xs; + text-align: center; + } + } + + &-container { + font-family: 'SF Pro Text'; + color: $session-color-white; + display: inline-flex; + flex-direction: column; + align-items: center; + justify-content: center; + + width: 600px; + min-width: 420px; + padding: 3 * $session-margin-lg 2 * $session-margin-lg; + box-sizing: border-box; + background-color: $session-shade-4; + border: 1px solid $session-shade-8; + border-radius: 2px; + + .warning-info-area, + .password-info-area { + display: inline-flex; + justify-content: center; + align-items: center; + + h1 { + color: $session-color-white; + } + svg { + margin-right: $session-margin-lg; + } + } + + p, + input { + margin: $session-margin-lg 0px; + } + + .button-group { + display: inline-flex; + } + + #password-prompt-input { + width: 100%; + color: #fff; + background-color: #2e2e2e; + margin-top: 2 * $session-margin-lg; + padding: $session-margin-xs $session-margin-lg; + outline: none; + border: none; + border-radius: 2px; + text-align: center; + font-size: 24px; + letter-spacing: 5px; + font-family: 'SF Pro Text'; + } + } +} + +.onboarding-message-section { + display: flex; + flex-grow: 1; + align-items: flex-start; + position: relative; + padding: $session-margin-lg 2 * $session-margin-lg; + background: linear-gradient( + 180deg, + rgba(29, 28, 28, 1) 0%, + rgba(18, 18, 18, 1) 100% + ); + + &__exit { + position: absolute; + top: $session-margin-lg; + left: $session-margin-lg; + } + + &__container { + display: flex; + flex-grow: 1; + flex-direction: column; + align-items: center; + justify-content: space-between; + padding-top: 3 * $session-margin-lg; + + height: 400px; + + text-align: center; + font-family: 'SF Pro Text'; + } + + &__title h1 { + color: $session-color-white; + font-size: $session-font-h1; + font-weight: bold; + margin-bottom: 2 * $session-margin-lg; + } + + &__icons img { + width: 180px; + margin-bottom: 2 * $session-margin-lg; + } + + &__info { + font-family: 'Wasa'; + margin-bottom: 2 * $session-margin-lg; + + &--title { + font-size: $session-font-h3; + line-height: $session-font-h2; + margin-bottom: $session-margin-md; + } + + &--subtitle { + font-family: 'SF Pro Text'; + font-weight: 300; + line-height: $session-font-md; + opacity: 0.8; + } + } + + &__spinner-container { + display: flex; + flex-grow: 1; + align-items: center; + justify-content: center; + } + + &__buttons { + width: 100%; + + .session-button:first-child { + margin-bottom: $session-margin-md; + } + } +} + +.group-member-list { + &__container { + padding: 2px 0px; + width: 100%; + max-height: 400px; + overflow-y: auto; + box-shadow: inset 0px 14px 7px -15px $session-color-dark-grey, + inset 0px -14px 7px -15px $session-color-dark-grey; + } + + &__selection { + height: 100%; + display: flex; + flex-direction: column; + } + + &__no-contacts { + font-family: 'SpaceMono'; + text-align: center; + padding: 20px; + } +} +.create-group-name-input { + .session-id-editable { + height: 60px !important; + + textarea { + padding-bottom: 0px !important; + } + + &-disabled { + border: 1px solid $session-color-dark-grey !important; + } + } +} + +.session-member-item { + cursor: pointer; + font-family: 'SF Pro Text'; + padding: 0px $session-margin-sm; + height: 50px; + display: flex; + justify-content: space-between; + transition: $session-transition-duration; + + &.selected { + background-color: $session-shade-4; + } + + &__checkmark { + opacity: 0; + transition: $session-transition-duration; + + &.selected { + opacity: 1; + } + } + + &__info, + &__checkmark { + display: flex; + align-items: center; + } + + &__name { + font-weight: bold; + margin-left: $session-margin-md; + } + &__pubkey { + margin-left: 5px; + opacity: 0.8; + } +} diff --git a/stylesheets/_session_group_panel.scss b/stylesheets/_session_group_panel.scss new file mode 100644 index 0000000000..9cf26d536d --- /dev/null +++ b/stylesheets/_session_group_panel.scss @@ -0,0 +1,146 @@ +.group-settings { + display: flex; + flex-direction: column; + height: 100vh; + width: 22vw; + flex-shrink: 0; + border: $session-separator-element-border; + background-color: $session-shade-4; + align-items: center; + + &-header { + margin-top: $session-margin-lg; + width: -webkit-fill-available; + display: flex; + flex-direction: row; + flex-shrink: 0; + + .module-avatar { + margin: auto; + } + + .session-icon-button { + margin: 0 $session-margin-md; + } + } + + h2 { + word-break: break-word; + } + + .description { + margin: $session-margin-md 0; + border: $session-separator-element-border; + background-color: $session-shade-1; + min-height: 4rem; + width: inherit; + color: $session-color-white; + text-align: center; + display: none; + } + + &-item { + display: flex; + align-items: center; + background-color: $session-shade-1; + min-height: 3rem; + font-size: 0.8rem; + color: $session-color-white; + width: -webkit-fill-available; + padding: 0 $session-margin-md; + border-bottom: $session-separator-element-border; + border-top: $session-separator-element-border; + transition: $session-transition-duration; + cursor: pointer; + + &:hover { + @include session-dark-background-hover; + } + } + + // no double border (top and bottom) between two elements + &-item + &-item { + border-top: none; + } + + // bottom button + .session-button.square-outline.danger { + margin-top: auto; + width: 100%; + border: none; + height: 3.5rem; + background-color: black; + flex-shrink: 0; + align-items: center; + } + + .module-empty-state { + text-align: center; + } + + .module-attachment-section__items { + &-media { + display: grid; + grid-template-columns: repeat(3, 1fr); + width: 100%; + } + + &-documents { + width: 100%; + } + } + + .module-media { + &-gallery { + &__tab-container { + padding-top: 1rem; + } + + &__tab { + color: white; + font-weight: bold; + font-size: 0.9rem; + padding: 0.6rem; + opacity: 0.8; + + &--active { + border-bottom: none; + opacity: 1; + + &:after { + content: ''; /* This is necessary for the pseudo element to work. */ + display: block; + margin: 0 auto; + width: 70%; + padding-top: 0.5rem; + border-bottom: $session-element-border-green; + } + } + } + + &__content { + padding: $session-margin-xs; + + .module-media-grid-item__image, + .module-media-grid-item { + height: calc( + 22vw / 3.5 + ); //.group-settings is 22vw and we want three rows with some space so divide it by 3.5 + width: calc( + 22vw / 3.5 + ); //.group-settings is 22vw and we want three rows with some space so divide it by 3.5 + margin: auto; + } + } + } + } +} + +.conversation-content { + display: flex; + height: inherit; + + &-left { + flex-grow: 1; + } +} diff --git a/stylesheets/_session_left_pane.scss b/stylesheets/_session_left_pane.scss new file mode 100644 index 0000000000..8024524e27 --- /dev/null +++ b/stylesheets/_session_left_pane.scss @@ -0,0 +1,633 @@ +$session-compose-margin: 20px; + +.module-conversation-list-item { + transition: $session-transition-duration; + @at-root .light-theme #{&} { + @include session-dark-background-lighter; + &:hover { + @include session-dark-background-hover; + } + } + @at-root .dark-theme #{&} { + @include session-dark-background-lighter; + &:hover { + @include session-dark-background-hover; + } + } + + &--is-selected { + @at-root .light-theme #{&} { + @include session-dark-background-hover; + &:hover { + @include session-dark-background-hover; + } + } + @at-root .dark-theme #{&} { + @include session-dark-background-hover; + &:hover { + @include session-dark-background-hover; + } + } + .module-conversation__user__profile-number, + .module-conversation__user__profile-name, + .module-conversation-list-item__message__text { + @at-root .light-theme #{&} { + color: $session-color-black; + } + @at-root .dark-theme #{&} { + color: $session-color-white; + } + } + } +} +.gutter { + width: 380px !important; + background: none !important; + padding-right: 5px !important; +} + +.module-conversation { + &-list-item { + &--has-unread { + border-left: $session-element-border-green !important; + } + + &__unread-count { + @at-root .light-theme #{&} { + color: $session-color-black !important; + background-color: $session-color-white !important; + } + @at-root .dark-theme #{&} { + color: $session-color-white !important; + background-color: $session-shade-10 !important; + } + + position: static !important; + font-weight: 700 !important; + box-shadow: none !important; + margin: 0 !important; + } + + &__header__date, + &__header__date--has-unread { + flex-grow: 1 !important; + text-align: end !important; + } + + &__message__text { + color: $session-shade-17; + &--has-unread { + @at-root .light-theme #{&} { + color: $session-shade-4 !important; + } + @at-root .dark-theme #{&} { + color: $session-color-white !important; + } + } + } + + &__header__name { + flex-grow: 0 !important; + padding-right: 5px !important; + + @at-root .light-theme #{&} { + color: $session-color-black; + } + @at-root .dark-theme #{&} { + color: $session-shade-17; + } + } + + &__header__name--with-unread .module-conversation__user__profile-number, + &__header__name--with-unread .module-conversation__user__profile-name { + @at-root .light-theme #{&} { + color: $session-color-black; + } + @at-root .dark-theme #{&} { + color: $session-color-white; + } + } + } + + &__user__profile { + &-number, + &-name { + @include fontWasaBold(); + font-size: 15px; + + @at-root .light-theme #{&} { + color: $session-color-white; + } + @at-root .dark-theme #{&} { + color: $session-shade-17; + } + } + } +} + +.inbox { + @at-root .light-theme #{&} { + color: $session-color-black; + } + @at-root .dark-theme #{&} { + @include session-dark-background; + } +} + +.module-left-pane { + border-right: none !important; + width: $session-left-pane-width; + position: relative; + height: -webkit-fill-available; + + @at-root .light-theme #{&} { + background: $session-color-white !important; + } + @at-root .dark-theme #{&} { + background: $session-shade-4 !important; + } + + &-session { + display: flex; + } + + &__sections-container { + height: -webkit-fill-available; + width: $session-left-pane-sections-container-width; + display: inline-flex; + flex-direction: column; + + .module-avatar, + .session-icon-button { + cursor: pointer; + padding: 30px; + + &:last-child { + margin: auto auto 0px auto; + /* Hide theme icon until light theme is ready */ + display: none; + } + + &:first-child { + padding: 0; + margin: 30px auto; + } + } + } + + &__header { + display: flex; + flex-direction: row; + padding: 15px 7px 14px 0px; + height: 63px; + + @at-root .light-theme #{&} { + background-color: $session-color-white; + } + @at-root .dark-theme #{&} { + background-color: $session-shade-3; + } + + .session-button { + margin-left: auto; + @include fontWasaBold(); + } + + &-buttons { + margin-bottom: $session-margin-sm; + display: inline-flex; + width: 100%; + + .session-button { + flex: 1; + } + } + } + + &__title { + cursor: pointer; + padding-right: $session-margin-sm; + padding-left: $session-margin-sm; + transition: $session-transition-duration; + } + + &__list { + height: -webkit-fill-available; + &-popup { + width: -webkit-fill-available; + height: -webkit-fill-available; + position: absolute; + } + } + + &-compose { + @at-root .light-theme #{&} { + background: $session-color-white; + } + @at-root .dark-theme #{&} { + @include session-dark-background-lighter; + } + } + + &-overlay { + background: linear-gradient(180deg, #171717 0%, $session-background 100%); + box-shadow: 0 0 100px 0 rgba(0, 0, 0, 0.5); + display: flex; + flex-direction: column; + align-items: center; + height: -webkit-fill-available; + .session-icon .exit { + padding: 13px; + } + + h3, + h2 { + text-align: center; + } + + h3 { + padding-top: 22px; + position: relative; + + .green-border { + position: absolute; + color: $session-color-green; + background-color: $session-color-green; + height: 5px; + left: -10px; + right: -10px; + margin-top: 7px; + border: none; + z-index: 1; + } + } + + h4 { + text-transform: uppercase; + } + + .white-border { + width: $session-left-pane-width; + position: relative; + height: 1px; + opacity: 0.3; + margin-top: 2px; + margin-bottom: 40px; + } + + .exit { + margin-top: 10px; + margin-left: 13px; + align-self: flex-start; + } + + .session-search-input { + margin: 10px $session-compose-margin 0 $session-compose-margin; + width: -webkit-fill-available; + } + + .module-search-results { + width: -webkit-fill-available; + } + + .session-description-long { + font-size: $session-font-sm; + line-height: $session-font-h3; + margin: 0px 20px; + font-family: 'SF Pro Display'; + } + + .session-id-editable { + textarea::-webkit-inner-spin-button { + margin: 0px 20px; + width: -webkit-fill-available; + flex-shrink: 0; + } + } + + .session-id-editable-disabled { + border: none; + } + + .session-button { + width: fit-content; + margin-top: 1rem; + margin-bottom: 3rem; + flex-shrink: 0; + } + } +} +.module-search-results { + flex-grow: 1; +} + +.module-conversations-list-content { + overflow-x: hidden; + display: flex; + flex-direction: column; + flex-grow: 1; +} + +.session-left-pane-section-content { + display: flex; + flex-direction: column; + flex-grow: 1; +} + +.user-search-dropdown { + width: 100%; + flex-grow: 1; + overflow-y: auto; +} + +.session-search-input { + height: $session-search-input-height; + width: 100%; + margin-right: 1px; + margin-bottom: 10px; + display: inline-flex; + flex-shrink: 0; + + @at-root .light-theme #{&} { + color: $session-color-black; + background: $session-color-white; + } + @at-root .dark-theme #{&} { + color: $session-color-white; + background: $session-shade-4; + } + + .session-icon-button { + margin: auto 10px; + } + + input { + width: inherit; + height: inherit; + border: none; + flex-grow: 1; + font-size: $session-font-sm; + font-family: 'SF Pro Text'; + + &:focus { + outline: none !important; + } + + @at-root .light-theme #{&} { + color: $session-color-black; + background: $session-color-white; + } + @at-root .dark-theme #{&} { + color: $session-color-white; + background: $session-shade-4; + } + } +} + +.session-full-logo { + width: 250px; + height: 250px; +} + +.app-loading-screen { + @include session-dark-background; +} + +@mixin bottom-buttons() { + display: flex; + flex-direction: row; + position: absolute; + bottom: 0px; + width: 100%; + + @at-root .light-theme #{&} { + background-color: $session-color-white; + } + @at-root .dark-theme #{&} { + background-color: $session-shade-2; + } + + .session-button.square-outline.square.green, + .session-button.square-outline.square.white, + .session-button.square-outline.square.danger { + flex-grow: 1; + height: 50px; + line-height: 50px; + + @at-root .light-theme #{&} { + border: 1px solid $session-shade-15; + } + @at-root .dark-theme #{&} { + border: 1px solid $session-shade-6; + } + } +} + +.contacts-dropdown { + width: -webkit-fill-available; + + &-row { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding: 8px 20px; + margin: 0px $session-compose-margin; + + @at-root .light-theme #{&} { + background: $session-shade-15; + color: $session-color-black; + } + @at-root .dark-theme #{&} { + background: $session-shade-4; + color: $session-color-light-grey; + } + + &-selected, + &:hover { + font-weight: bold; + + @at-root .light-theme #{&} { + color: $session-color-black; + background: $session-color-white; + } + @at-root .dark-theme #{&} { + color: $session-color-white; + background: $session-shade-8; + } + } + } +} + +.contact-notification-count-bubble { + background: $session-color-danger; + line-height: 30px; + width: 30px; + height: 30px; + font-size: 14px; + margin-left: auto; + text-align: center; + border-radius: 50%; + font-weight: bold; + cursor: pointer; + transition: $session-transition-duration; + + color: $session-color-white; + &:hover { + filter: brightness(80%); + } +} + +.left-pane-contact { + &-section, + &-content { + display: flex; + flex-direction: column; + flex-grow: 1; + + .module-conversation-list-item { + &--has-friend-request { + &:first-child { + border-top: none !important; + } + + border: 1px solid $session-shade-8 !important; + + .module-conversation__user__profile-number, + .module-conversation__user__profile-name { + font-size: 13px !important; + } + } + + &__buttons { + display: flex; + + .session-button { + font-size: 11px; + padding: 6px; + height: auto; + margin: 0px; + line-height: 14px; + } + } + } + } + + &-content { + @include session-dark-background-lighter; + } + + &-bottom-buttons { + @include bottom-buttons(); + } +} + +.left-pane-setting { + &-bottom-buttons { + @include bottom-buttons(); + } + + &-content, + &-section { + display: flex; + flex-direction: column; + } + + &-category-list-item { + @include session-dark-background; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + height: 74px; + line-height: 1.4; + padding: 0px 12px; + flex-shrink: 0; + cursor: pointer; + transition: $session-transition-duration !important; + + & > div { + display: block; + } + + &.active { + background-color: $session-shade-9; + } + + &:hover { + background-color: $session-shade-7; + } + + &__buttons { + display: flex; + + .session-button { + font-size: 11px; + padding: 6px; + height: auto; + margin: 0px; + line-height: 14px; + } + } + } + + &-input-group { + display: inline-flex; + } + + &-input-button .session-button.square { + display: flex; + justify-content: center; + align-items: center; + + height: $session-search-input-height; + width: $session-search-input-height; + + padding: 0px; + margin: 0px; + + svg { + transition: $session-transition-duration; + } + + &:hover { + svg { + fill: $session-color-green; + } + } + .session-button.square-outline.square.green, + .session-button.square-outline.square.white { + flex-grow: 1; + border: 1px solid $session-shade-8; + height: 50px; + line-height: 50px; + } + } +} + +.friend-request-title { + font-weight: bold; + font-size: 13px; + padding: 11px; + border-right: 1px solid $session-shade-8 !important; + border-top: 1px solid $session-shade-8 !important; + border-left: 1px solid $session-shade-8 !important; + @include session-dark-background-lighter; +} + +.panel-text-divider { + width: 100%; + text-align: center; + border-bottom: 1px solid $session-color-dark-grey; + line-height: 0.1em; + margin: 50px 0 50px; + + span { + padding: 5px 10px; + border-radius: 50px; + background-color: $session-background; + color: $session-color-light-grey; + border: 1px solid $session-color-dark-grey; + font-family: 'SF Pro Text'; + font-size: $session-font-sm; + } +} diff --git a/stylesheets/_session_signin.scss b/stylesheets/_session_signin.scss new file mode 100644 index 0000000000..66b5fd5c2e --- /dev/null +++ b/stylesheets/_session_signin.scss @@ -0,0 +1,262 @@ +.session { + &-fullscreen { + @include session-dark-background; + overflow-y: auto; + height: 100%; + } + + &-content { + width: 100%; + height: 100%; + display: flex; + align-items: center; + + &-accent { + flex-grow: 1; + padding-left: 20px; + + &-text { + color: $session-color-white; + font-family: $session-font-family; + + .title { + font-size: 100px; + font-weight: 700; + line-height: 120px; + } + } + } + + &-registration { + height: 45%; + padding-right: 128px; + } + + &-close-button { + position: absolute; + top: 17px; + left: 20px; + } + + &-session-button { + position: absolute; + top: 17px; + right: 20px; + + img { + width: 30px; + } + } + } + + &-registration { + &-container { + display: flex; + flex-direction: column; + width: 289px; + + .session-button { + width: 100%; + } + } + + &__content { + width: 100%; + padding-top: 20px; + } + + &__sections { + display: flex; + flex-grow: 1; + flex-direction: column; + } + + &__tab-container { + display: flex; + flex-grow: 0; + flex-shrink: 0; + cursor: pointer; + width: 289px; + height: 30px; + left: 0; + right: 0; + + margin-left: auto; + margin-right: auto; + color: $session-color-white; + } + + &__tab { + @include fontWasaBold(); + width: 100%; + padding-bottom: 10px; + background-color: transparent; + text-align: center; + color: $session-color-white; + border-bottom: 2px solid $session-color-dark-grey; + transition: border-color $session-transition-duration linear; + line-height: 17px; + font-size: 15px; + + &--active { + border-bottom: $session-element-border-green; + } + } + + @mixin registration-label-mixin { + color: $session-color-white; + font-weight: bold; + padding: 12px; + } + + &__welcome-session { + @include registration-label-mixin; + font-size: 14px; + font-weight: 700; + line-height: 14px; + padding-top: 2em; + text-align: center; + } + + &__unique-session-id { + @include registration-label-mixin; + padding-top: 3em; + text-align: center; + } + + &__entry-fields { + margin: 0px; + padding-bottom: 30px; + } + } + + &-input-floating-label-show-hide { + padding-right: 30px; + } + + &-input-with-label-container { + height: 46.5px; + width: 280px; + color: $session-color-white; + padding: 2px 0 2px 0; + transition: opacity $session-transition-duration; + opacity: 1; + position: relative; + + label { + line-height: 14px; + opacity: 0; + color: #737373; + font-size: 10px; + line-height: 11px; + position: absolute; + top: 0px; + } + + &.filled { + opacity: 1; + } + + &.error { + color: red; + } + + input { + border: none; + outline: 0; + height: 14px; + width: 280px; + background: transparent; + color: $session-color-white; + font-size: 12px; + line-height: 14px; + position: absolute; + top: 50%; + transform: translateY(-50%); + } + + hr { + border: 1px solid $session-color-light-grey; + width: 100%; + position: absolute; + bottom: 0px; + } + + .session-icon-button { + position: absolute; + top: 50%; + transform: translateY(-50%); + right: 0px; + } + } + + &-terms-conditions-agreement { + padding-top: 10px; + color: $session-color-light-grey; + text-align: center; + font-size: 12px; + + a { + white-space: nowrap; + font-weight: bold; + text-decoration: none; + color: $session-color-light-grey; + transition: $session-transition-duration; + + &:visited &:link { + color: $session-color-light-grey; + } + + &:hover { + color: $session-color-white; + } + } + } + + &-description-long, + &-signin-device-pairing-header { + padding-top: 10px; + padding-bottom: 10px; + color: $session-color-light-grey; + text-align: center; + font-size: 12px; + line-height: 20px; + } + + &-id-editable { + display: flex; + align-items: center; + justify-content: center; + height: 94px; + padding: 20px; + border-radius: 8px; + border: 2px solid #353535; + margin-bottom: 20px; + + textarea { + width: 100%; + outline: 0; + border: none; + background: transparent; + color: #fff; + font-size: 15px; + line-height: 18px; + text-align: center; + overflow-wrap: break-word; + padding: 20px 5px 20px 5px; + display: inline-block; + font-family: 'SpaceMono'; + user-select: all; + } + } +} + +[contenteditable='true']:empty::before { + content: attr(placeholder); + color: $session-color-light-grey; + font-size: 13px; +} + +.registration-content-centered { + text-align: center; +} diff --git a/stylesheets/_session_theme.scss b/stylesheets/_session_theme.scss new file mode 100644 index 0000000000..1dffb58179 --- /dev/null +++ b/stylesheets/_session_theme.scss @@ -0,0 +1,147 @@ +// Messages + +.discussion-container { + @at-root .light-theme #{&} { + background-color: $session-color-white; + } + @at-root .dark-theme #{&} { + background-color: $session-shade-2; + } +} + +.conversation { + background: none !important; +} +.module-conversation-header { + @at-root .light-theme #{&} { + background-color: $session-color-white; + border-bottom: none; + } + @at-root .dark-theme #{&} { + background-color: $session-shade-4; + border-bottom: none; + } +} + +.module-message { + &__author, + &__metadata__badge, + &__metadata__date--incoming, + &__metadata__date--outgoing { + @at-root .light-theme #{&} { + @include session-color-subtle($session-color-black); + } + @at-root .dark-theme #{&} { + @include session-color-subtle($session-color-white); + } + } + + &__container--incoming { + @at-root .light-theme #{&} { + @include session-color-subtle($session-color-white); + } + @at-root .dark-theme #{&} { + background-color: $session-shade-8; + } + } + + &__container--outgoing { + @at-root .light-theme #{&} { + background-color: $session-color-white; + } + @at-root .dark-theme #{&} { + background-color: $session-shade-11; + } + } + + &__container { + transition: background-color 0.25s; + box-shadow: 0px 0px 8px 2px rgba(0, 0, 0, 0.69); + } +} + +.message { + &-highlighted { + border-radius: 0; + + @at-root .light-theme #{&} { + background-color: $session-shade-5; + } + @at-root .dark-theme #{&} { + background-color: $session-color-white; + } + } + + &-selected { + .module-message { + &__container { + background-image: $session-gradient-green; + box-shadow: $session-dark-shadow; + } + + &__author { + color: $session-color-white; + } + } + } + + &-read-receipt-container { + margin-left: 5px; + } +} + +.inbox { + background: linear-gradient(180deg, #171717 0%, $session-background 100%); +} + +.conversation { + background: none !important; +} + +.discussion-container { + background-color: #141414; +} + +@mixin session-h-title { + @include fontWasaBold(); +} + +h1 { + @include session-h-title; + color: $session-shade-16; + font-size: 25px; + margin: 0; + + &.active { + color: $session-color-white; + } +} + +h2 { + @include session-h-title; + color: $session-color-white; + font-size: 22px; + text-align: center; +} + +h3 { + @include session-h-title; + color: $session-color-white; + font-size: 18px; + padding-top: 22px; +} + +h4 { + @include session-h-title; + color: $session-color-white; + font-size: 17px; + text-align: center; +} + +.dark-theme .network-status-container .network-status { + background-color: rgba($session-color-green, 0.6); + + .network-status-message { + color: $session-color-white; + } +} diff --git a/stylesheets/_theme_dark.scss b/stylesheets/_theme_dark.scss index 89e8dce6bf..39de2faadd 100644 --- a/stylesheets/_theme_dark.scss +++ b/stylesheets/_theme_dark.scss @@ -1,5 +1,4 @@ // Don't forget to handle the background of the popup windows! - body.dark-theme { background-color: $color-black; color: $color-gray-05; @@ -35,15 +34,7 @@ body.dark-theme { } .bottom-bar { - form.active { - textarea { - border: solid 1px $blue; - } - } - form.send { - background-color: $color-gray-95; - &.video-attachment { .outer { .play.icon { @@ -54,13 +45,10 @@ body.dark-theme { } .send-message { - background-color: $color-dark-85; - color: $color-dark-05; - border: 1px solid $color-light-60; outline: 0; &[disabled='disabled'] { - background: $color-light-90; + cursor: not-allowed; } } } @@ -102,7 +90,6 @@ body.dark-theme { button { background-color: $color-dark-85; border-radius: $border-radius; - border: 1px solid $color-dark-60; color: $color-dark-05; &:hover { @@ -129,7 +116,7 @@ body.dark-theme { } .module-last-seen-indicator__bar { - background-color: $color-dark-30; + background-color: #353535; } .module-last-seen-indicator__text { @@ -201,6 +188,7 @@ body.dark-theme { button.emoji { &:before { + margin-top: 7px; @include color-svg('../images/smile.svg', $color-dark-30); } } @@ -309,12 +297,24 @@ body.dark-theme { } } + /* why can't I access $session-color values here? */ .expiredAlert { - background: #f3f3a7; + background: #28f587; + color: black; + /* biggest we can make the font without wrapping the current text at minimum app width */ + font-family: 'Wasa'; + font-size: 20px; + height: 60px; + + span { + line-height: 36px; + } button { + font-size: 14px; + height: 36px; color: white; - background: $blue; + background: #474646; } } @@ -724,7 +724,7 @@ body.dark-theme { .module-message__link-preview__location { color: $color-gray-25; } - + .module-conversation__user, .module-message__author { color: $color-white; } @@ -1137,6 +1137,10 @@ body.dark-theme { color: $color-dark-05; } + .module-conversation-header__note-to-self { + color: $color-dark-05; + } + .module-conversation-header__title__verified-icon { @include color-svg('../images/verified-check.svg', $color-dark-05); } @@ -1309,11 +1313,19 @@ body.dark-theme { } .module-avatar__icon--group { - @include color-svg('../images/profile-group.svg', $color-gray-05); + background-color: $color-gray-05; } .module-avatar__icon--direct { - @include color-svg('../images/profile-individual.svg', $color-gray-05); + background-color: $color-gray-05; + } + + .module-avatar__icon--note-to-self { + background-color: $color-gray-05; + } + + .module-avatar__icon--crown-wrapper { + background-color: $color-gray-75; } .module-avatar--no-image { @@ -1359,16 +1371,48 @@ body.dark-theme { // Module: Main Header - .module-main-header__app-name { - color: $color-dark-05; + .module-main-header { + border-bottom: 1px solid $color-gray-75; + } + + .module-main-header__search__input { + background-color: $color-gray-95; + border-radius: 14px; + border: solid 1px $color-gray-75; + color: $color-gray-05; + + &::placeholder { + color: $color-gray-45; + } + + &:focus { + border: solid 1px blue; + outline: none; + } + } + + .module-main-header__search__icon { + @include color-svg('../images/search.svg', $color-gray-25); + } + + .module-main-header__search__cancel-icon { + @include color-svg('../images/x-16.svg', $color-gray-25); } // Module: Image + .module-image { + background-color: $color-black; + } + .module-image__border-overlay { box-shadow: inset 0px 0px 0px 1px $color-white-015; } + .module-image__loading-placeholder { + background-color: $color-white-015; + } + // Module: Image Grid // Module: Typing Animation @@ -1388,7 +1432,7 @@ body.dark-theme { } .module-attachments__close-button { - @include color-svg('../images/x.svg', $color-gray-45); + @include color-svg('../images/x-16.svg', $color-gray-45); } // Module: Staged Generic Attachment @@ -1440,6 +1484,152 @@ body.dark-theme { @include color-svg('../images/x-16.svg', $color-gray-25); } + // Module: Spinner + + .module-spinner__circle { + background-color: $color-white-04; + } + .module-spinner__arc { + background-color: $color-gray-05; + } + + .module-spinner__circle--incoming { + background-color: $color-white-04; + } + .module-spinner__arc--incoming { + background-color: $color-gray-05; + } + + .module-spinner__circle--outgoing { + background-color: $color-white-04; + } + .module-spinner__arc--outgoing { + background-color: $color-gray-05; + } + + // Module: Caption Editor + + .module-caption-editor { + background-color: $color-black; + } + + .module-caption-editor__close-button { + @include color-svg('../images/x-16.svg', $color-white); + } + + .module-caption-editor__media-container { + background-color: $color-black; + } + + .module-caption-editor__caption-input { + border: 1px solid $color-white; + color: $color-white; + background-color: $color-black; + + &::placeholder { + color: $color-white-07; + } + &:focus { + border: 1px solid $color-signal-blue; + outline: none; + } + } + + .module-caption-editor__save-button { + background-color: $color-signal-blue; + color: $color-white; + } + + // Module: Highlighted Message Body + + // Module: Search Results + + .module-search-results__conversations-header { + color: $color-gray-05; + } + .module-search-results__contacts-header { + color: $color-gray-05; + } + .module-search-results__messages-header { + color: $color-gray-05; + } + + // Module: Message Search Result + + .module-message-search-result { + &:hover { + background-color: $color-dark-70; + } + } + + .module-message-search-result--is-selected { + background-color: $color-dark-70; + } + + .module-message-search-result__header__from { + color: $color-gray-05; + } + + .module-message-search-result__header__timestamp { + color: $color-gray-25; + } + + .module-message-search-result__body { + color: $color-gray-05; + } + + // Module: Left Pane + + .module-left-pane { + background-color: $color-dark-85; + border-right: 1px solid $color-gray-75; + } + + .module-left-pane__archive-header { + border-bottom: 1px solid $color-gray-75; + } + + .module-left-pane__to-inbox-button { + background-color: $color-gray-25; + } + + .module-left-pane__archive-header-text { + color: $color-gray-05; + } + + .module-left-pane__archive-helper-text { + color: $color-gray-25; + background-color: $color-gray-75; + } + + .module-left-pane__archived-button { + color: $color-gray-25; + &:hover { + background-color: $color-gray-75; + } + } + + .module-left-pane__archived-button__archived-count { + color: $color-gray-25; + background-color: $color-gray-75; + } + + // Module: Start New Conversation + + .module-start-new-conversation { + &:hover { + background-color: $color-dark-70; + } + } + + .module-start-new-conversation__number { + color: $color-gray-05; + } + + .module-start-new-conversation__text { + color: $color-gray-45; + } + // Third-party module: react-contextmenu .react-contextmenu { diff --git a/stylesheets/_variables.scss b/stylesheets/_variables.scss index 2f589ae874..a3643e9ae0 100644 --- a/stylesheets/_variables.scss +++ b/stylesheets/_variables.scss @@ -22,8 +22,13 @@ font-weight: bold; } -$roboto: Roboto, 'Helvetica Neue', Arial, Helvetica, sans-serif; -$roboto-light: Roboto-Light, 'Helvetica Neue', Arial, Helvetica, sans-serif; +$roboto: Roboto, 'Helvetica Neue', 'Source Sans Pro', 'Source Han Sans SC', + 'Source Han Sans CN', 'Hiragino Sans GB', 'Hiragino Kaku Gothic', + 'Microsoft Yahei UI', Helvetica, Arial, sans-serif; +$roboto-light: Roboto-Light, 'Helvetica Neue', 'Source Sans Pro Light', + 'Source Han Sans SC Light', 'Source Han Sans CN Light', + 'Hiragino Sans GB Light', 'Hiragino Kaku Gothic Light', + 'Microsoft Yahei UI Light', Helvetica, Arial, sans-serif; // Loki colors $color-loki-light-gray: #e9e9e9; @@ -109,6 +114,7 @@ $color-white-08: rgba($color-white, 0.8); $color-white-085: rgba($color-white, 0.85); $color-light-02: #f9fafa; $color-light-10: #eeefef; +$color-light-20: #c1c5cd; $color-light-35: #a4a6a9; $color-light-45: #8b8e91; $color-light-60: #62656a; @@ -124,6 +130,7 @@ $color-dark-75: #292c33; $color-dark-85: #1a1c20; $color-dark-90: #121417; $color-black-008: rgba($color-black, 0.08); +$color-black-005: rgba($color-black, 0.05); $color-black-008-no-tranparency: #ededed; $color-black-016-no-tranparency: #d9d9d9; $color-black-012: rgba($color-black, 0.12); @@ -192,6 +199,7 @@ $header-height: 55px; $button-height: 24px; $border-radius: 5px; +$message-container-border-radius: 16px; $font-size: 14px; $font-size-small: (13/14) + em; diff --git a/stylesheets/manifest.scss b/stylesheets/manifest.scss index b5c70eecea..d4657b295c 100644 --- a/stylesheets/manifest.scss +++ b/stylesheets/manifest.scss @@ -10,6 +10,7 @@ @import 'lightbox'; @import 'recorder'; @import 'emoji'; +@import 'mentions'; @import 'settings'; @import 'password'; @@ -21,8 +22,15 @@ @import 'ios'; @import 'theme_dark'; -// New CSS +// Session @import 'modules'; +@import 'session'; +@import 'session_signin'; +@import 'session_theme'; +@import 'session_left_pane'; +@import 'session_group_panel'; + +@import 'session-slider'; // Installer @import 'options'; diff --git a/test/_test.js b/test/_test.js index f55ff8bc2c..6e1e03ae23 100644 --- a/test/_test.js +++ b/test/_test.js @@ -1,4 +1,4 @@ -/* global chai, Whisper */ +/* global chai, Whisper, _, Backbone */ mocha.setup('bdd'); window.assert = chai.assert; @@ -74,10 +74,13 @@ function deleteIndexedDB() { before(async () => { await deleteIndexedDB(); await window.Signal.Data.removeAll(); + await window.storage.fetch(); }); window.clearDatabase = async () => { await window.Signal.Data.removeAll(); + await window.storage.fetch(); }; -window.lokiP2pAPI = new window.LokiP2pAPI('ourKey'); +window.Whisper = window.Whisper || {}; +window.Whisper.events = _.clone(Backbone.Events); diff --git a/test/app/fixtures/menu-mac-os-setup.json b/test/app/fixtures/menu-mac-os-setup.json index 103a86a8a0..861511f8a0 100644 --- a/test/app/fixtures/menu-mac-os-setup.json +++ b/test/app/fixtures/menu-mac-os-setup.json @@ -1,18 +1,14 @@ [ { + "label": "Session", "submenu": [ { - "label": "About Loki Messenger", + "label": "About Session", "click": null }, { "type": "separator" }, - { - "label": "Preferences…", - "accelerator": "CommandOrControl+,", - "click": null - }, { "type": "separator" }, @@ -32,7 +28,7 @@ "type": "separator" }, { - "label": "Quit Loki Messenger", + "label": "Quit Session", "role": "quit" } ] @@ -114,6 +110,7 @@ "role": "resetzoom" }, { + "accelerator": "Command+=", "label": "Zoom In", "role": "zoomin" }, diff --git a/test/app/fixtures/menu-mac-os.json b/test/app/fixtures/menu-mac-os.json index 9a32a57dd2..b2ea56aaf3 100644 --- a/test/app/fixtures/menu-mac-os.json +++ b/test/app/fixtures/menu-mac-os.json @@ -1,18 +1,14 @@ [ { + "label": "Session", "submenu": [ { - "label": "About Loki Messenger", + "label": "About Session", "click": null }, { "type": "separator" }, - { - "label": "Preferences…", - "accelerator": "CommandOrControl+,", - "click": null - }, { "type": "separator" }, @@ -32,7 +28,7 @@ "type": "separator" }, { - "label": "Quit Loki Messenger", + "label": "Quit Session", "role": "quit" } ] @@ -101,6 +97,7 @@ "role": "resetzoom" }, { + "accelerator": "Command+=", "label": "Zoom In", "role": "zoomin" }, diff --git a/test/app/fixtures/menu-windows-linux-setup.json b/test/app/fixtures/menu-windows-linux-setup.json index f3b9058c14..e72303d391 100644 --- a/test/app/fixtures/menu-windows-linux-setup.json +++ b/test/app/fixtures/menu-windows-linux-setup.json @@ -13,15 +13,11 @@ { "type": "separator" }, - { - "label": "Preferences…", - "click": null - }, { "type": "separator" }, { - "label": "Quit Loki Messenger", + "label": "Quit Session", "role": "quit" } ] @@ -74,6 +70,7 @@ "role": "resetzoom" }, { + "accelerator": "Control+Plus", "label": "Zoom In", "role": "zoomin" }, @@ -137,7 +134,7 @@ "type": "separator" }, { - "label": "About Loki Messenger", + "label": "About Session", "click": null } ] diff --git a/test/app/fixtures/menu-windows-linux.json b/test/app/fixtures/menu-windows-linux.json index 675bca0053..aa6a1171d5 100644 --- a/test/app/fixtures/menu-windows-linux.json +++ b/test/app/fixtures/menu-windows-linux.json @@ -2,15 +2,11 @@ { "label": "&File", "submenu": [ - { - "label": "Preferences…", - "click": null - }, { "type": "separator" }, { - "label": "Quit Loki Messenger", + "label": "Quit Session", "role": "quit" } ] @@ -63,6 +59,7 @@ "role": "resetzoom" }, { + "accelerator": "Control+Plus", "label": "Zoom In", "role": "zoomin" }, @@ -126,7 +123,7 @@ "type": "separator" }, { - "label": "About Loki Messenger", + "label": "About Session", "click": null } ] diff --git a/test/backup_test.js b/test/backup_test.js index fedb16e13e..f5146cc096 100644 --- a/test/backup_test.js +++ b/test/backup_test.js @@ -574,6 +574,8 @@ describe('Backup', () => { 'profileAvatar', 'swarmNodes', 'friendRequestStatus', + 'groupAdmins', + 'isKickedFromGroup', 'unlockTimestamp', 'sessionResetStatus', 'isOnline', diff --git a/test/conversation_controller_test.js b/test/conversation_controller_test.js deleted file mode 100644 index b8c01d99cf..0000000000 --- a/test/conversation_controller_test.js +++ /dev/null @@ -1,48 +0,0 @@ -/* global Whisper */ - -'use strict'; - -describe('ConversationController', () => { - it('sorts conversations based on timestamp then by intl-friendly title', () => { - const collection = window.getInboxCollection(); - collection.reset([]); - - collection.add( - new Whisper.Conversation({ - name: 'No timestamp', - }) - ); - collection.add( - new Whisper.Conversation({ - name: 'B', - timestamp: 20, - }) - ); - collection.add( - new Whisper.Conversation({ - name: 'C', - timestamp: 20, - }) - ); - collection.add( - new Whisper.Conversation({ - name: 'Á', - timestamp: 20, - }) - ); - collection.add( - new Whisper.Conversation({ - name: 'First!', - timestamp: 30, - }) - ); - - assert.strictEqual(collection.at('0').get('name'), 'First!'); - assert.strictEqual(collection.at('1').get('name'), 'Á'); - assert.strictEqual(collection.at('2').get('name'), 'B'); - assert.strictEqual(collection.at('3').get('name'), 'C'); - assert.strictEqual(collection.at('4').get('name'), 'No timestamp'); - - collection.reset([]); - }); -}); diff --git a/test/index.html b/test/index.html index c541e717b4..a5bc6a0464 100644 --- a/test/index.html +++ b/test/index.html @@ -16,72 +16,50 @@
+ + + + + - + + - + + + + - - - - - - - - + + + + + - - - - - - - + - - + + @@ -363,12 +501,12 @@

{{ message }}

- + @@ -380,47 +518,45 @@

{{ message }}

- - - - - - - + + - - + + - + + + + + + + - - - diff --git a/test/keychange_listener_test.js b/test/keychange_listener_test.js index 08da00bf2b..43a8c759bc 100644 --- a/test/keychange_listener_test.js +++ b/test/keychange_listener_test.js @@ -10,8 +10,9 @@ describe('KeyChangeListener', () => { const newKey = libsignal.crypto.getRandomBytes(33); let store; - beforeEach(() => { + beforeEach(async () => { store = new SignalProtocolStore(); + await store.hydrateCaches(); Whisper.KeyChangeListener.init(store); return store.saveIdentity(address.toString(), oldKey); }); diff --git a/test/metadata/SecretSessionCipher_test.js b/test/metadata/SecretSessionCipher_test.js index 6fb7beea31..25c4df7ea8 100644 --- a/test/metadata/SecretSessionCipher_test.js +++ b/test/metadata/SecretSessionCipher_test.js @@ -43,13 +43,15 @@ InMemorySignalProtocolStore.prototype = { value === undefined || key === null || value === null - ) + ) { throw new Error('Tried to store undefined/null'); + } this.store[key] = value; }, get(key, defaultValue) { - if (key === null || key === undefined) + if (key === null || key === undefined) { throw new Error('Tried to get value for undefined/null key'); + } if (key in this.store) { return this.store[key]; } @@ -57,8 +59,9 @@ InMemorySignalProtocolStore.prototype = { return defaultValue; }, remove(key) { - if (key === null || key === undefined) + if (key === null || key === undefined) { throw new Error('Tried to remove value for undefined/null key'); + } delete this.store[key]; }, diff --git a/test/models/conversations_test.js b/test/models/conversations_test.js index 51bb11aab2..97d0ed1bdb 100644 --- a/test/models/conversations_test.js +++ b/test/models/conversations_test.js @@ -177,56 +177,4 @@ describe('Conversation', () => { assert.notOk(longId.isValid()); }); }); - - describe('Conversation search', () => { - let convo; - - before(clearDatabase); - - beforeEach(async () => { - convo = new Whisper.ConversationCollection().add({ - id: - '771d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab', - type: 'private', - name: 'John Doe', - }); - await window.Signal.Data.saveConversation(convo.attributes, { - Conversation: Whisper.Conversation, - }); - }); - - afterEach(clearDatabase); - - async function testSearch(queries) { - await Promise.all( - queries.map(async query => { - const collection = new Whisper.ConversationCollection(); - await collection.search(query); - - assert.isDefined( - collection.get(convo.id), - `no result for "${query}"` - ); - }) - ); - } - it('matches by partial keys', () => { - return testSearch([ - '1', - 'd11', - 'fc3d74115c33225', - 'd01e56d9bfc3d74115c33225a632321b509ac17a13fde', - '1d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab', - ]); - }); - // TODO: Re-enable once we have nickanme functionality - // it('matches by name', () => { - // return testSearch(['John', 'Doe', 'john', 'doe', 'John Doe', 'john doe']); - // }); - it('does not match +', async () => { - const collection = new Whisper.ConversationCollection(); - await collection.search('+'); - assert.isUndefined(collection.get(convo.id), 'got result for "+"'); - }); - }); }); diff --git a/test/models/messages_test.js b/test/models/messages_test.js index 70efb27086..7b0f7ece0e 100644 --- a/test/models/messages_test.js +++ b/test/models/messages_test.js @@ -101,7 +101,7 @@ describe('MessageCollection', () => { message = messages.add({ group_update: { name: 'blerg' } }); assert.equal( message.getDescription(), - "Title is now 'blerg'", + "Group name has been set to 'blerg'", 'Returns a single notice if only group_updates.name changes.' ); @@ -126,7 +126,7 @@ describe('MessageCollection', () => { }); assert.equal( message.getDescription(), - "Title is now 'blerg', Bob joined the group", + "Group name has been set to 'blerg', Bob joined the group", 'Notes when there are multiple changes to group_updates properties.' ); diff --git a/test/models/profile_test.js b/test/models/profile_test.js deleted file mode 100644 index 11112f62cf..0000000000 --- a/test/models/profile_test.js +++ /dev/null @@ -1,120 +0,0 @@ -/* global storage */ - -/* eslint no-await-in-loop: 0 */ - -'use strict'; - -const PROFILE_ID = 'local-profile'; - -describe('Profile', () => { - beforeEach(async () => { - await clearDatabase(); - await storage.remove(PROFILE_ID); - }); - - describe('getLocalProfile', () => { - it('returns the local profile', async () => { - const values = [null, 'hello', { a: 'b' }]; - for (let i = 0; i < values.length; i += 1) { - await storage.put(PROFILE_ID, values[i]); - assert.strictEqual(values[i], storage.getLocalProfile()); - } - }); - }); - - describe('saveLocalProfile', () => { - it('saves a profile', async () => { - const values = [null, 'hello', { a: 'b' }]; - for (let i = 0; i < values.length; i += 1) { - await storage.saveLocalProfile(values[i]); - assert.strictEqual(values[i], storage.get(PROFILE_ID)); - } - }); - }); - - describe('removeLocalProfile', () => { - it('removes a profile', async () => { - await storage.saveLocalProfile('a'); - assert.strictEqual('a', storage.getLocalProfile()); - - await storage.removeLocalProfile(); - assert.strictEqual(null, storage.getLocalProfile()); - }); - }); - - describe('setProfileName', () => { - it('throws if a name is not a string', async () => { - const values = [0, { a: 'b' }, [1, 2]]; - for (let i = 0; i < values.length; i += 1) { - try { - await storage.setProfileName(values[i]); - assert.fail( - `setProfileName did not throw an error for ${typeof values[i]}` - ); - } catch (e) { - assert.throws(() => { - throw e; - }, 'Name must be a string!'); - } - } - }); - - it('does not throw if we pass a string or null', async () => { - const values = [null, '1']; - for (let i = 0; i < values.length; i += 1) { - try { - await storage.setProfileName(values[i]); - } catch (e) { - assert.fail('setProfileName threw an error'); - } - } - }); - - it('saves the display name', async () => { - await storage.setProfileName('hi there!'); - - const expected = { - displayName: 'hi there!', - }; - const profile = storage.getLocalProfile(); - assert.exists(profile.name); - assert.deepEqual(expected, profile.name); - }); - - it('saves the display name without overwriting the other profile properties', async () => { - const profile = { title: 'hello' }; - await storage.put(PROFILE_ID, profile); - await storage.setProfileName('hi there!'); - - const expected = { - ...profile, - name: { - displayName: 'hi there!', - }, - }; - assert.deepEqual(expected, storage.getLocalProfile()); - }); - - it('trims the display name', async () => { - await storage.setProfileName(' in middle '); - const profile = storage.getLocalProfile(); - const name = { - displayName: 'in middle', - }; - assert.deepEqual(name, profile.name); - }); - - it('unsets the name property if it is empty', async () => { - const profile = { - name: { - displayName: 'HI THERE!', - }, - }; - await storage.put(PROFILE_ID, profile); - assert.exists(storage.getLocalProfile().name); - - await storage.setProfileName(''); - assert.notExists(storage.getLocalProfile().name); - }); - }); -}); diff --git a/test/modules/link_previews_test.js b/test/modules/link_previews_test.js index 327f98b6d0..f88c371e64 100644 --- a/test/modules/link_previews_test.js +++ b/test/modules/link_previews_test.js @@ -1,9 +1,11 @@ const { assert } = require('chai'); const { + findLinks, getTitleMetaTag, getImageMetaTag, isLinkInWhitelist, + isLinkSneaky, isMediaLinkInWhitelist, } = require('../../js/modules/link_previews'); @@ -190,7 +192,7 @@ describe('Link previews', () => { ); }); - it('returns html-decoded tag contents from Instagram', () => { + it('returns html-decoded tag contents from Imgur', () => { const imgur = ` @@ -209,6 +211,50 @@ describe('Link previews', () => { ); }); + it('returns html-decoded tag contents from Giphy', () => { + const giphy = ` + + + + + + + + + `; + + assert.strictEqual( + 'I Cant Hear You Kobe Bryant GIF - Find & Share on GIPHY', + getTitleMetaTag(giphy) + ); + assert.strictEqual( + 'https://media.giphy.com/media/3o7qE8mq5bT9FQj7j2/giphy.gif', + getImageMetaTag(giphy) + ); + }); + + it('returns html-decoded tag contents from Tenor', () => { + const tenor = ` + + + + + + + + + `; + + assert.strictEqual( + 'Hopping Jumping GIF - Hopping Jumping Bird - Discover & Share GIFs', + getTitleMetaTag(tenor) + ); + assert.strictEqual( + 'https://media1.tenor.com/images/3772949a5b042e626d259f313fd1e9b8/tenor.gif?itemid=14834517', + getImageMetaTag(tenor) + ); + }); + it('returns only the first tag', () => { const html = ` @@ -227,5 +273,118 @@ describe('Link previews', () => { getTitleMetaTag(html) ); }); + + it('converts image url protocol http to https', () => { + const html = ` + + `; + + assert.strictEqual( + 'https://giphygifs.s3.amazonaws.com/media/APcFiiTrG0x2/200.gif', + getImageMetaTag(html) + ); + }); + }); + + describe('#findLinks', () => { + it('returns all links if no caretLocation is provided', () => { + const text = + 'Check out this link: https://github.com/signalapp/Signal-Desktop\nAnd this one too: https://github.com/signalapp/Signal-Android'; + + const expected = [ + 'https://github.com/signalapp/Signal-Desktop', + 'https://github.com/signalapp/Signal-Android', + ]; + + const actual = findLinks(text); + assert.deepEqual(expected, actual); + }); + + it('includes all links if cursor is not in a link', () => { + const text = + 'Check out this link: https://github.com/signalapp/Signal-Desktop\nAnd this one too: https://github.com/signalapp/Signal-Android'; + const caretLocation = 10; + + const expected = [ + 'https://github.com/signalapp/Signal-Desktop', + 'https://github.com/signalapp/Signal-Android', + ]; + + const actual = findLinks(text, caretLocation); + assert.deepEqual(expected, actual); + }); + + it('excludes a link not at the end if the caret is inside of it', () => { + const text = + 'Check out this link: https://github.com/signalapp/Signal-Desktop\nAnd this one too: https://github.com/signalapp/Signal-Android'; + const caretLocation = 30; + + const expected = ['https://github.com/signalapp/Signal-Android']; + + const actual = findLinks(text, caretLocation); + assert.deepEqual(expected, actual); + }); + + it('excludes a link not at the end if the caret is at its end', () => { + const text = + 'Check out this link: https://github.com/signalapp/Signal-Desktop\nAnd this one too: https://github.com/signalapp/Signal-Android'; + const caretLocation = 64; + + const expected = ['https://github.com/signalapp/Signal-Android']; + + const actual = findLinks(text, caretLocation); + assert.deepEqual(expected, actual); + }); + + it('excludes a link at the end of the caret is inside of it', () => { + const text = + 'Check out this link: https://github.com/signalapp/Signal-Desktop\nAnd this one too: https://github.com/signalapp/Signal-Android'; + const caretLocation = 100; + + const expected = ['https://github.com/signalapp/Signal-Desktop']; + + const actual = findLinks(text, caretLocation); + assert.deepEqual(expected, actual); + }); + + it('includes link at the end if cursor is at its end', () => { + const text = + 'Check out this link: https://github.com/signalapp/Signal-Desktop\nAnd this one too: https://github.com/signalapp/Signal-Android'; + const caretLocation = text.length; + + const expected = [ + 'https://github.com/signalapp/Signal-Desktop', + 'https://github.com/signalapp/Signal-Android', + ]; + + const actual = findLinks(text, caretLocation); + assert.deepEqual(expected, actual); + }); + }); + + describe('#isLinkSneaky', () => { + it('returns false for all-latin domain', () => { + const link = 'https://www.amazon.com'; + const actual = isLinkSneaky(link); + assert.strictEqual(actual, false); + }); + + it('returns true for Latin + Cyrillic domain', () => { + const link = 'https://www.aмazon.com'; + const actual = isLinkSneaky(link); + assert.strictEqual(actual, true); + }); + + it('returns true for Latin + Greek domain', () => { + const link = 'https://www.αpple.com'; + const actual = isLinkSneaky(link); + assert.strictEqual(actual, true); + }); + + it('returns true for Latin + High Greek domain', () => { + const link = `https://www.apple${String.fromCodePoint(0x101a0)}.com`; + const actual = isLinkSneaky(link); + assert.strictEqual(actual, true); + }); }); }); diff --git a/test/modules/types/attachment_test.js b/test/modules/types/attachment_test.js index 7c54a33e11..f55aacef01 100644 --- a/test/modules/types/attachment_test.js +++ b/test/modules/types/attachment_test.js @@ -12,13 +12,11 @@ describe('Attachment', () => { it('should sanitize left-to-right order override character', async () => { const input = { contentType: 'image/jpeg', - data: null, fileName: 'test\u202Dfig.exe', size: 1111, }; const expected = { contentType: 'image/jpeg', - data: null, fileName: 'test\uFFFDfig.exe', size: 1111, }; @@ -30,13 +28,11 @@ describe('Attachment', () => { it('should sanitize right-to-left order override character', async () => { const input = { contentType: 'image/jpeg', - data: null, fileName: 'test\u202Efig.exe', size: 1111, }; const expected = { contentType: 'image/jpeg', - data: null, fileName: 'test\uFFFDfig.exe', size: 1111, }; @@ -48,13 +44,11 @@ describe('Attachment', () => { it('should sanitize multiple override characters', async () => { const input = { contentType: 'image/jpeg', - data: null, fileName: 'test\u202e\u202dlol\u202efig.exe', size: 1111, }; const expected = { contentType: 'image/jpeg', - data: null, fileName: 'test\uFFFD\uFFFDlol\uFFFDfig.exe', size: 1111, }; @@ -72,7 +66,6 @@ describe('Attachment', () => { fileName => { const input = { contentType: 'image/jpeg', - data: null, fileName, size: 1111, }; @@ -131,7 +124,6 @@ describe('Attachment', () => { it('should remove existing schema version', () => { const input = { contentType: 'image/jpeg', - data: null, fileName: 'foo.jpg', size: 1111, schemaVersion: 1, @@ -139,7 +131,6 @@ describe('Attachment', () => { const expected = { contentType: 'image/jpeg', - data: null, fileName: 'foo.jpg', size: 1111, }; diff --git a/test/modules/types/contact_test.js b/test/modules/types/contact_test.js index e9732be10f..c835f38d9c 100644 --- a/test/modules/types/contact_test.js +++ b/test/modules/types/contact_test.js @@ -42,6 +42,7 @@ describe('Contact', () => { assert.deepEqual(result, message.contact[0]); }); + // LOKI: Phone number stays the same it('turns phone numbers to e164 format', async () => { const upgradeAttachment = sinon .stub() @@ -71,7 +72,7 @@ describe('Contact', () => { number: [ { type: 1, - value: '+12025550099', + value: '(202) 555-0099', }, ], }; diff --git a/test/modules/types/message_test.js b/test/modules/types/message_test.js index 09333f377b..0231c8b686 100644 --- a/test/modules/types/message_test.js +++ b/test/modules/types/message_test.js @@ -575,17 +575,22 @@ describe('Message', () => { body: 'hey there!', quote: { text: 'hey!', - attachments: [], + attachments: [ + { + fileName: 'manifesto.txt', + contentType: 'text/plain', + }, + ], }, }; const result = await upgradeVersion(message, { logger }); assert.deepEqual(result, message); }); - it('eliminates thumbnails with no data field', async () => { + it('does not eliminate thumbnails with missing data field', async () => { const upgradeAttachment = sinon .stub() - .throws(new Error("Shouldn't be called")); + .returns({ fileName: 'processed!' }); const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment); const message = { @@ -597,7 +602,7 @@ describe('Message', () => { fileName: 'cat.gif', contentType: 'image/gif', thumbnail: { - fileName: 'failed to download!', + fileName: 'not yet downloaded!', }, }, ], @@ -611,6 +616,9 @@ describe('Message', () => { { contentType: 'image/gif', fileName: 'cat.gif', + thumbnail: { + fileName: 'processed!', + }, }, ], }, diff --git a/test/storage_test.js b/test/storage_test.js index ac3bc142f9..7c4f458076 100644 --- a/test/storage_test.js +++ b/test/storage_test.js @@ -10,6 +10,7 @@ describe('SignalProtocolStore', () => { before(done => { store = textsecure.storage.protocol; + store.hydrateCaches(); identityKey = { pubKey: libsignal.crypto.getRandomBytes(33), privKey: libsignal.crypto.getRandomBytes(32), @@ -88,6 +89,7 @@ describe('SignalProtocolStore', () => { verified: store.VerifiedStatus.DEFAULT, }); + await store.hydrateCaches(); await store.saveIdentity(identifier, newIdentity); }); it('marks the key not firstUse', async () => { @@ -109,6 +111,7 @@ describe('SignalProtocolStore', () => { nonblockingApproval: false, verified: store.VerifiedStatus.DEFAULT, }); + await store.hydrateCaches(); await store.saveIdentity(identifier, newIdentity); }); @@ -127,6 +130,8 @@ describe('SignalProtocolStore', () => { nonblockingApproval: false, verified: store.VerifiedStatus.VERIFIED, }); + + await store.hydrateCaches(); await store.saveIdentity(identifier, newIdentity); }); it('sets the new key to unverified', async () => { @@ -149,6 +154,7 @@ describe('SignalProtocolStore', () => { verified: store.VerifiedStatus.UNVERIFIED, }); + await store.hydrateCaches(); await store.saveIdentity(identifier, newIdentity); }); it('sets the new key to unverified', async () => { @@ -170,12 +176,14 @@ describe('SignalProtocolStore', () => { nonblockingApproval: false, verified: store.VerifiedStatus.DEFAULT, }); + await store.hydrateCaches(); }); describe('If it is marked firstUse', () => { before(async () => { const identity = await window.Signal.Data.getIdentityKeyById(number); identity.firstUse = true; await window.Signal.Data.createOrUpdateIdentityKey(identity); + await store.hydrateCaches(); }); it('nothing changes', async () => { await store.saveIdentity(identifier, testKey.pubKey, true); @@ -190,6 +198,7 @@ describe('SignalProtocolStore', () => { const identity = await window.Signal.Data.getIdentityKeyById(number); identity.firstUse = false; await window.Signal.Data.createOrUpdateIdentityKey(identity); + await store.hydrateCaches(); }); describe('If nonblocking approval is required', () => { let now; @@ -200,6 +209,7 @@ describe('SignalProtocolStore', () => { ); identity.timestamp = now; await window.Signal.Data.createOrUpdateIdentityKey(identity); + await store.hydrateCaches(); }); it('sets non-blocking approval', async () => { await store.saveIdentity(identifier, testKey.pubKey, true); @@ -313,6 +323,7 @@ describe('SignalProtocolStore', () => { verified: store.VerifiedStatus.DEFAULT, nonblockingApproval: false, }); + await store.hydrateCaches(); } describe('with no public key argument', () => { before(saveRecordDefault); @@ -372,6 +383,7 @@ describe('SignalProtocolStore', () => { describe('when there is no existing record', () => { before(async () => { await window.Signal.Data.removeIdentityKeyById(number); + await store.hydrateCaches(); }); it('does nothing', async () => { @@ -405,6 +417,7 @@ describe('SignalProtocolStore', () => { verified: store.VerifiedStatus.VERIFIED, nonblockingApproval: false, }); + await store.hydrateCaches(); }); it('does not save the new identity (because this is a less secure state)', async () => { @@ -436,6 +449,7 @@ describe('SignalProtocolStore', () => { verified: store.VerifiedStatus.VERIFIED, nonblockingApproval: false, }); + await store.hydrateCaches(); }); it('updates the verified status', async () => { @@ -464,6 +478,7 @@ describe('SignalProtocolStore', () => { verified: store.VerifiedStatus.DEFAULT, nonblockingApproval: false, }); + await store.hydrateCaches(); }); it('does not hang', async () => { @@ -482,6 +497,7 @@ describe('SignalProtocolStore', () => { describe('when there is no existing record', () => { before(async () => { await window.Signal.Data.removeIdentityKeyById(number); + await store.hydrateCaches(); }); it('saves the new identity and marks it verified', async () => { @@ -512,6 +528,7 @@ describe('SignalProtocolStore', () => { verified: store.VerifiedStatus.VERIFIED, nonblockingApproval: false, }); + await store.hydrateCaches(); }); it('saves the new identity and marks it UNVERIFIED', async () => { @@ -543,6 +560,7 @@ describe('SignalProtocolStore', () => { verified: store.VerifiedStatus.DEFAULT, nonblockingApproval: false, }); + await store.hydrateCaches(); }); it('updates the verified status', async () => { @@ -573,6 +591,7 @@ describe('SignalProtocolStore', () => { verified: store.VerifiedStatus.UNVERIFIED, nonblockingApproval: false, }); + await store.hydrateCaches(); }); it('does not hang', async () => { @@ -591,6 +610,7 @@ describe('SignalProtocolStore', () => { describe('when there is no existing record', () => { before(async () => { await window.Signal.Data.removeIdentityKeyById(number); + await store.hydrateCaches(); }); it('saves the new identity and marks it verified', async () => { @@ -617,6 +637,7 @@ describe('SignalProtocolStore', () => { verified: store.VerifiedStatus.VERIFIED, nonblockingApproval: false, }); + await store.hydrateCaches(); }); it('saves the new identity and marks it VERIFIED', async () => { @@ -648,6 +669,7 @@ describe('SignalProtocolStore', () => { verified: store.VerifiedStatus.UNVERIFIED, nonblockingApproval: false, }); + await store.hydrateCaches(); }); it('saves the identity and marks it verified', async () => { @@ -678,6 +700,7 @@ describe('SignalProtocolStore', () => { verified: store.VerifiedStatus.VERIFIED, nonblockingApproval: false, }); + await store.hydrateCaches(); }); it('does not hang', async () => { @@ -705,6 +728,7 @@ describe('SignalProtocolStore', () => { nonblockingApproval: false, }); + await store.hydrateCaches(); const untrusted = await store.isUntrusted(number); assert.strictEqual(untrusted, false); }); @@ -718,6 +742,7 @@ describe('SignalProtocolStore', () => { firstUse: false, nonblockingApproval: true, }); + await store.hydrateCaches(); const untrusted = await store.isUntrusted(number); assert.strictEqual(untrusted, false); @@ -732,6 +757,7 @@ describe('SignalProtocolStore', () => { firstUse: true, nonblockingApproval: false, }); + await store.hydrateCaches(); const untrusted = await store.isUntrusted(number); assert.strictEqual(untrusted, false); @@ -746,6 +772,8 @@ describe('SignalProtocolStore', () => { firstUse: false, nonblockingApproval: false, }); + await store.hydrateCaches(); + const untrusted = await store.isUntrusted(number); assert.strictEqual(untrusted, true); }); @@ -968,36 +996,36 @@ describe('SignalProtocolStore', () => { assert.strictEqual(items.length, 0); }); - it('adds two and gets them back', async () => { + it('adds three and gets them back', async () => { await Promise.all([ - store.addUnprocessed({ id: 2, name: 'second', timestamp: 2 }), - store.addUnprocessed({ id: 3, name: 'third', timestamp: 3 }), - store.addUnprocessed({ id: 1, name: 'first', timestamp: 1 }), + store.addUnprocessed({ id: 2, envelope: 'second', timestamp: 2 }), + store.addUnprocessed({ id: 3, envelope: 'third', timestamp: 3 }), + store.addUnprocessed({ id: 1, envelope: 'first', timestamp: 1 }), ]); const items = await store.getAllUnprocessed(); assert.strictEqual(items.length, 3); // they are in the proper order because the collection comparator is 'timestamp' - assert.strictEqual(items[0].name, 'first'); - assert.strictEqual(items[1].name, 'second'); - assert.strictEqual(items[2].name, 'third'); + assert.strictEqual(items[0].envelope, 'first'); + assert.strictEqual(items[1].envelope, 'second'); + assert.strictEqual(items[2].envelope, 'third'); }); it('saveUnprocessed successfully updates item', async () => { const id = 1; - await store.addUnprocessed({ id, name: 'first', timestamp: 1 }); - await store.saveUnprocessed({ id, name: 'updated', timestamp: 1 }); + await store.addUnprocessed({ id, envelope: 'first', timestamp: 1 }); + await store.updateUnprocessedWithData(id, { decrypted: 'updated' }); const items = await store.getAllUnprocessed(); assert.strictEqual(items.length, 1); - assert.strictEqual(items[0].name, 'updated'); + assert.strictEqual(items[0].decrypted, 'updated'); assert.strictEqual(items[0].timestamp, 1); }); it('removeUnprocessed successfully deletes item', async () => { const id = 1; - await store.addUnprocessed({ id, name: 'first', timestamp: 1 }); + await store.addUnprocessed({ id, envelope: 'first', timestamp: 1 }); await store.removeUnprocessed(id); const items = await store.getAllUnprocessed(); diff --git a/test/views/attachment_view_test.js b/test/views/attachment_view_test.js deleted file mode 100644 index 99d8d1f3e1..0000000000 --- a/test/views/attachment_view_test.js +++ /dev/null @@ -1,59 +0,0 @@ -/* global assert, storage, Whisper */ - -'use strict'; - -describe('AttachmentView', () => { - let convo; - - before(async () => { - await clearDatabase(); - - convo = new Whisper.Conversation({ id: 'foo' }); - convo.messageCollection.add({ - conversationId: convo.id, - body: 'hello world', - type: 'outgoing', - source: '+14158675309', - received_at: Date.now(), - }); - - await storage.put('number_id', '+18088888888.1'); - }); - - describe('with arbitrary files', () => { - it('should render a file view', () => { - const attachment = { - contentType: 'unused', - size: 1232, - }; - const view = new Whisper.AttachmentView({ model: attachment }).render(); - assert.match(view.el.innerHTML, /fileView/); - }); - it('should display the filename if present', () => { - const attachment = { - fileName: 'foo.txt', - contentType: 'unused', - size: 1232, - }; - const view = new Whisper.AttachmentView({ model: attachment }).render(); - assert.match(view.el.innerHTML, /foo.txt/); - }); - it('should render a file size', () => { - const attachment = { - size: 1232, - contentType: 'unused', - }; - const view = new Whisper.AttachmentView({ model: attachment }).render(); - assert.match(view.el.innerHTML, /1.2 KB/); - }); - }); - it('should render an image for images', () => { - const now = new Date().getTime(); - const attachment = { contentType: 'image/png', data: 'grumpy cat' }; - const view = new Whisper.AttachmentView({ - model: attachment, - timestamp: now, - }).render(); - assert.equal(view.el.firstChild.tagName, 'IMG'); - }); -}); diff --git a/test/views/group_update_view_test.js b/test/views/group_update_view_test.js deleted file mode 100644 index 45c9a7440c..0000000000 --- a/test/views/group_update_view_test.js +++ /dev/null @@ -1,24 +0,0 @@ -/* global Whisper */ - -describe('GroupUpdateView', () => { - it('should show new group members', () => { - const view = new Whisper.GroupUpdateView({ - model: { joined: ['Alice', 'Bob'] }, - }).render(); - assert.match(view.$el.text(), /Alice.*Bob.*joined the group/); - }); - - it('should note updates to the title', () => { - const view = new Whisper.GroupUpdateView({ - model: { name: 'New name' }, - }).render(); - assert.match(view.$el.text(), /Title is now 'New name'/); - }); - - it('should say "Updated the group"', () => { - const view = new Whisper.GroupUpdateView({ - model: { avatar: 'New avatar' }, - }).render(); - assert.match(view.$el.text(), /Updated the group/); - }); -}); diff --git a/test/views/inbox_view_test.js b/test/views/inbox_view_test.js index 1c1147dd8c..dea9363997 100644 --- a/test/views/inbox_view_test.js +++ b/test/views/inbox_view_test.js @@ -13,6 +13,11 @@ describe('InboxView', () => { }; storage.put('identityKey', identityKey); await ConversationController.load(); + await textsecure.storage.user.setNumberAndDeviceId( + '18005554444', + 1, + 'Home Office' + ); await ConversationController.getOrCreateAndWait( textsecure.storage.user.getNumber(), 'private' diff --git a/test/views/network_status_view_test.js b/test/views/network_status_view_test.js index a3daa7883e..85cb64c687 100644 --- a/test/views/network_status_view_test.js +++ b/test/views/network_status_view_test.js @@ -153,7 +153,8 @@ describe('NetworkStatusView', () => { it('should be interrupted', () => { socketStatus = socketStatusVal; networkStatusView.update(); - const status = networkStatusView.getNetworkStatus(); + const shortCircuit = true; + const status = networkStatusView.getNetworkStatus(shortCircuit); assert(status.hasInterruption); }); }); diff --git a/test/views/scroll_down_button_view_test.js b/test/views/scroll_down_button_view_test.js index 27ff41072f..df099a6b60 100644 --- a/test/views/scroll_down_button_view_test.js +++ b/test/views/scroll_down_button_view_test.js @@ -1,35 +1,12 @@ /* global Whisper */ describe('ScrollDownButtonView', () => { - it('renders with count = 0', () => { + it('renders ', () => { const view = new Whisper.ScrollDownButtonView(); view.render(); - assert.equal(view.count, 0); - assert.match(view.$el.html(), /Scroll to bottom/); - }); - - it('renders with count = 1', () => { - const view = new Whisper.ScrollDownButtonView({ count: 1 }); - view.render(); - assert.equal(view.count, 1); - assert.match(view.$el.html(), /New message below/); - }); - - it('renders with count = 2', () => { - const view = new Whisper.ScrollDownButtonView({ count: 2 }); - view.render(); - assert.equal(view.count, 2); - - assert.match(view.$el.html(), /New messages below/); - }); - - it('increments count and re-renders', () => { - const view = new Whisper.ScrollDownButtonView(); - view.render(); - assert.equal(view.count, 0); - assert.notMatch(view.$el.html(), /New message below/); - view.increment(1); - assert.equal(view.count, 1); - assert.match(view.$el.html(), /New message below/); + assert.match( + view.$el.html(), + /
{ - it('should be ordered newest to oldest', () => { - // Timestamps - const today = new Date(); - const tomorrow = new Date(); - tomorrow.setDate(today.getDate() + 1); - - // Add threads - Whisper.Threads.add({ timestamp: today }); - Whisper.Threads.add({ timestamp: tomorrow }); - - const { models } = Whisper.Threads; - const firstTimestamp = models[0].get('timestamp').getTime(); - const secondTimestamp = models[1].get('timestamp').getTime(); - - // Compare timestamps - assert(firstTimestamp > secondTimestamp); - }); -}); diff --git a/test/views/timestamp_view_test.js b/test/views/timestamp_view_test.js deleted file mode 100644 index 2e22d734fa..0000000000 --- a/test/views/timestamp_view_test.js +++ /dev/null @@ -1,139 +0,0 @@ -/* global moment, Whisper */ - -'use strict'; - -describe('TimestampView', () => { - it('formats long-ago timestamps correctly', () => { - const timestamp = Date.now(); - const briefView = new Whisper.TimestampView({ brief: true }).render(); - const extendedView = new Whisper.ExtendedTimestampView().render(); - - // Helper functions to check absolute and relative timestamps - - // Helper to check an absolute TS for an exact match - const check = (view, ts, expected) => { - const result = view.getRelativeTimeSpanString(ts); - assert.strictEqual(result, expected); - }; - - // Helper to check relative times for an exact match against both views - const checkDiff = (secAgo, expectedBrief, expectedExtended) => { - check(briefView, timestamp - secAgo * 1000, expectedBrief); - check(extendedView, timestamp - secAgo * 1000, expectedExtended); - }; - - // Helper to check an absolute TS for an exact match against both views - const checkAbs = (ts, expectedBrief, expectedExtended) => { - if (!expectedExtended) { - // eslint-disable-next-line no-param-reassign - expectedExtended = expectedBrief; - } - check(briefView, ts, expectedBrief); - check(extendedView, ts, expectedExtended); - }; - - // Helper to check an absolute TS for a match at the beginning against - const checkStartsWith = (view, ts, expected) => { - const result = view.getRelativeTimeSpanString(ts); - const regexp = new RegExp(`^${expected}`); - assert.match(result, regexp); - }; - - // check integer timestamp, JS Date object and moment object - checkAbs(timestamp, 'now', 'now'); - checkAbs(new Date(), 'now', 'now'); - checkAbs(moment(), 'now', 'now'); - - // check recent timestamps - checkDiff(30, 'now', 'now'); // 30 seconds - checkDiff(40 * 60, '40 minutes', '40 minutes ago'); - checkDiff(60 * 60, '1 hour', '1 hour ago'); - checkDiff(125 * 60, '2 hours', '2 hours ago'); - - // set to third of month to avoid problems on the 29th/30th/31st - const lastMonth = moment() - .subtract(1, 'month') - .date(3); - const months = [ - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'May', - 'Jun', - 'Jul', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec', - ]; - check(briefView, lastMonth, `${months[lastMonth.month()]} 3`); - checkStartsWith(extendedView, lastMonth, `${months[lastMonth.month()]} 3`); - - // subtract 26 hours to be safe in case of DST stuff - const yesterday = new Date(timestamp - 26 * 60 * 60 * 1000); - const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - check(briefView, yesterday, daysOfWeek[yesterday.getDay()]); - checkStartsWith(extendedView, yesterday, daysOfWeek[yesterday.getDay()]); - - // Check something long ago - // months are zero-indexed in JS for some reason - check(briefView, new Date(2012, 4, 5, 17, 30, 0), 'May 5, 2012'); - checkStartsWith( - extendedView, - new Date(2012, 4, 5, 17, 30, 0), - 'May 5, 2012' - ); - }); - - describe('updates within a minute reasonable intervals', () => { - let view; - beforeEach(() => { - view = new Whisper.TimestampView(); - }); - afterEach(() => { - clearTimeout(view.timeout); - }); - - it('updates timestamps this minute within a minute', () => { - const now = Date.now(); - view.$el.attr('data-timestamp', now - 1000); - view.update(); - assert.isAbove(view.delay, 0); // non zero - assert.isBelow(view.delay, 60 * 1000); // < minute - }); - - it('updates timestamps from this hour within a minute', () => { - const now = Date.now(); - view.$el.attr('data-timestamp', now - 1000 - 1000 * 60 * 5); // 5 minutes and 1 sec ago - view.update(); - assert.isAbove(view.delay, 0); // non zero - assert.isBelow(view.delay, 60 * 1000); // minute - }); - - it('updates timestamps from today within an hour', () => { - const now = Date.now(); - view.$el.attr('data-timestamp', now - 1000 - 1000 * 60 * 60 * 5); // 5 hours and 1 sec ago - view.update(); - assert.isAbove(view.delay, 60 * 1000); // minute - assert.isBelow(view.delay, 60 * 60 * 1000); // hour - }); - - it('updates timestamps from this week within a day', () => { - const now = Date.now(); - view.$el.attr('data-timestamp', now - 1000 - 6 * 24 * 60 * 60 * 1000); // 6 days and 1 sec ago - view.update(); - assert.isAbove(view.delay, 60 * 60 * 1000); // hour - assert.isBelow(view.delay, 36 * 60 * 60 * 1000); // day and a half - }); - - it('does not updates very old timestamps', () => { - const now = Date.now(); - // return falsey value for long ago dates that don't update - view.$el.attr('data-timestamp', now - 8 * 24 * 60 * 60 * 1000); - view.update(); - assert.notOk(view.delay); - }); - }); -}); diff --git a/ts/backbone/views/Lightbox.ts b/ts/backbone/views/Lightbox.ts index 011fec5131..04da210036 100644 --- a/ts/backbone/views/Lightbox.ts +++ b/ts/backbone/views/Lightbox.ts @@ -2,7 +2,7 @@ export const show = (element: HTMLElement): void => { const container: HTMLDivElement | null = document.querySelector( '.lightbox-container' ); - if (container === null) { + if (!container) { throw new TypeError("'.lightbox-container' is required"); } // tslint:disable-next-line:no-inner-html @@ -15,7 +15,7 @@ export const hide = (): void => { const container: HTMLDivElement | null = document.querySelector( '.lightbox-container' ); - if (container === null) { + if (!container) { return; } // tslint:disable-next-line:no-inner-html diff --git a/ts/components/Avatar.md b/ts/components/Avatar.md index a6e1146b4d..94a7120beb 100644 --- a/ts/components/Avatar.md +++ b/ts/components/Avatar.md @@ -63,6 +63,45 @@ ``` +### Note to self + +```jsx + + + + + + +``` + ### All colors ```jsx diff --git a/ts/components/Avatar.tsx b/ts/components/Avatar.tsx index a4f3e2ff91..6adb05d16e 100644 --- a/ts/components/Avatar.tsx +++ b/ts/components/Avatar.tsx @@ -1,33 +1,38 @@ import React from 'react'; import classNames from 'classnames'; +import { JazzIcon } from './JazzIcon'; import { getInitials } from '../util/getInitials'; -import { Localizer } from '../types/Util'; +import { LocalizerType } from '../types/Util'; interface Props { avatarPath?: string; color?: string; conversationType: 'group' | 'direct'; - i18n: Localizer; + noteToSelf?: boolean; name?: string; phoneNumber?: string; profileName?: string; size: number; borderColor?: string; borderWidth?: number; + i18n?: LocalizerType; + onAvatarClick?: () => void; } interface State { imageBroken: boolean; } -export class Avatar extends React.Component { +export class Avatar extends React.PureComponent { public handleImageErrorBound: () => void; + public onAvatarClickBound: (e: any) => void; public constructor(props: Props) { super(props); this.handleImageErrorBound = this.handleImageError.bind(this); + this.onAvatarClickBound = this.onAvatarClick.bind(this); this.state = { imageBroken: false, @@ -42,10 +47,25 @@ export class Avatar extends React.Component { }); } + public renderIdenticon() { + const { phoneNumber, borderColor, borderWidth, size } = this.props; + + if (!phoneNumber) { + return this.renderNoImage(); + } + + const borderStyle = this.getBorderStyle(borderColor, borderWidth); + + // Generate the seed + const hash = phoneNumber.substring(0, 12); + const seed = parseInt(hash, 16) || 1234; + + return ; + } + public renderImage() { const { avatarPath, - i18n, name, phoneNumber, profileName, @@ -53,9 +73,8 @@ export class Avatar extends React.Component { borderWidth, } = this.props; const { imageBroken } = this.state; - const hasImage = avatarPath && !imageBroken; - if (!hasImage) { + if (!avatarPath || imageBroken) { return null; } @@ -69,7 +88,7 @@ export class Avatar extends React.Component { {i18n('contactAvatarAlt', ); @@ -79,6 +98,7 @@ export class Avatar extends React.Component { const { conversationType, name, + noteToSelf, size, borderColor, borderWidth, @@ -87,6 +107,18 @@ export class Avatar extends React.Component { const initials = getInitials(name); const isGroup = conversationType === 'group'; + if (noteToSelf) { + return ( +
+ ); + } + const borderStyle = this.getBorderStyle(borderColor, borderWidth); if (!isGroup && initials) { @@ -116,12 +148,26 @@ export class Avatar extends React.Component { } public render() { - const { avatarPath, color, size } = this.props; + const { + avatarPath, + color, + size, + noteToSelf, + conversationType, + } = this.props; const { imageBroken } = this.state; - const hasImage = avatarPath && !imageBroken; - - if (size !== 28 && size !== 36 && size !== 48 && size !== 80) { + // If it's a direct conversation then we must have an identicon + const hasAvatar = avatarPath || conversationType === 'direct'; + const hasImage = !noteToSelf && hasAvatar && !imageBroken; + + if ( + size !== 28 && + size !== 36 && + size !== 48 && + size !== 80 && + size !== 300 + ) { throw new Error(`Size ${size} is not supported!`); } @@ -133,21 +179,45 @@ export class Avatar extends React.Component { hasImage ? 'module-avatar--with-image' : 'module-avatar--no-image', !hasImage ? `module-avatar--${color}` : null )} + onClick={e => { + this.onAvatarClickBound(e); + }} + role="button" > - {hasImage ? this.renderImage() : this.renderNoImage()} + {hasImage ? this.renderAvatarOrIdenticon() : this.renderNoImage()}
); } - private getBorderStyle(color?: string, width?: number) { - const borderWidth = typeof width === 'number' ? width : 3; + private onAvatarClick(e: any) { + if (this.props.onAvatarClick) { + e.stopPropagation(); + this.props.onAvatarClick(); + } + } + + private renderAvatarOrIdenticon() { + const { avatarPath, conversationType } = this.props; + + // If it's a direct conversation then we must have an identicon + const hasAvatar = avatarPath || conversationType === 'direct'; + + return hasAvatar && avatarPath + ? this.renderImage() + : this.renderIdenticon(); + } + + private getBorderStyle(_color?: string, _width?: number) { + //const borderWidth = typeof width === 'number' ? width : 3; - return color + // no border at all for now + return undefined; + /* return color ? { borderColor: color, borderStyle: 'solid', borderWidth: borderWidth, } - : undefined; + : undefined; */ } } diff --git a/ts/components/CaptionEditor.tsx b/ts/components/CaptionEditor.tsx index 1902a51c04..309c154b99 100644 --- a/ts/components/CaptionEditor.tsx +++ b/ts/components/CaptionEditor.tsx @@ -3,13 +3,13 @@ import React from 'react'; import * as GoogleChrome from '../util/GoogleChrome'; -import { AttachmentType } from './conversation/types'; +import { AttachmentType } from '../types/Attachment'; -import { Localizer } from '../types/Util'; +import { LocalizerType } from '../types/Util'; interface Props { attachment: AttachmentType; - i18n: Localizer; + i18n: LocalizerType; url: string; caption?: string; onSave?: (caption: string) => void; @@ -21,15 +21,15 @@ interface State { } export class CaptionEditor extends React.Component { - private handleKeyUpBound: ( + private readonly handleKeyUpBound: ( event: React.KeyboardEvent ) => void; - private setFocusBound: () => void; - // TypeScript doesn't like our React.Ref typing here, so we omit it - private captureRefBound: () => void; - private onChangeBound: () => void; - private onSaveBound: () => void; - private inputRef: React.Ref | null; + private readonly setFocusBound: () => void; + private readonly onChangeBound: ( + event: React.FormEvent + ) => void; + private readonly onSaveBound: () => void; + private readonly inputRef: React.RefObject; constructor(props: Props) { super(props); @@ -41,10 +41,16 @@ export class CaptionEditor extends React.Component { this.handleKeyUpBound = this.handleKeyUp.bind(this); this.setFocusBound = this.setFocus.bind(this); - this.captureRefBound = this.captureRef.bind(this); this.onChangeBound = this.onChange.bind(this); this.onSaveBound = this.onSave.bind(this); - this.inputRef = null; + this.inputRef = React.createRef(); + } + + public componentDidMount() { + // Forcing focus after a delay due to some focus contention with ConversationView + setTimeout(() => { + this.setFocus(); + }, 200); } public handleKeyUp(event: React.KeyboardEvent) { @@ -61,21 +67,11 @@ export class CaptionEditor extends React.Component { } public setFocus() { - if (this.inputRef) { - // @ts-ignore - this.inputRef.focus(); + if (this.inputRef.current) { + this.inputRef.current.focus(); } } - public captureRef(ref: React.Ref) { - this.inputRef = ref; - - // Forcing focus after a delay due to some focus contention with ConversationView - setTimeout(() => { - this.setFocus(); - }, 200); - } - public onSave() { const { onSave } = this.props; const { caption } = this.state; @@ -124,6 +120,7 @@ export class CaptionEditor extends React.Component { public render() { const { i18n, close } = this.props; const { caption } = this.state; + const onKeyUp = close ? this.handleKeyUpBound : undefined; return (
{
{caption ? ( diff --git a/ts/components/ConfirmDialog.tsx b/ts/components/ConfirmDialog.tsx new file mode 100644 index 0000000000..77f9026807 --- /dev/null +++ b/ts/components/ConfirmDialog.tsx @@ -0,0 +1,45 @@ +import React from 'react'; + +import { SessionModal } from './session/SessionModal'; +import { SessionButton } from './session/SessionButton'; + +interface Props { + titleText: string; + messageText: string; + okText: string; + cancelText: string; + onConfirm: any; + onClose: any; +} + +export class ConfirmDialog extends React.Component { + constructor(props: any) { + super(props); + } + + public render() { + return ( + null} + onOk={() => null} + > +
+

{this.props.messageText}

+
+ +
+ + + +
+ + ); + } +} diff --git a/ts/components/ContactListItem.tsx b/ts/components/ContactListItem.tsx index 936ba4b4c2..ebffc0b322 100644 --- a/ts/components/ContactListItem.tsx +++ b/ts/components/ContactListItem.tsx @@ -4,17 +4,17 @@ import classNames from 'classnames'; import { Avatar } from './Avatar'; import { Emojify } from './conversation/Emojify'; -import { Localizer } from '../types/Util'; +import { LocalizerType } from '../types/Util'; interface Props { phoneNumber: string; isMe?: boolean; name?: string; - color?: string; + color: string; verified: boolean; profileName?: string; avatarPath?: string; - i18n: Localizer; + i18n: LocalizerType; onClick?: () => void; } @@ -38,7 +38,7 @@ export class ContactListItem extends React.Component { name={name} phoneNumber={phoneNumber} profileName={profileName} - size={48} + size={36} /> ); } diff --git a/ts/components/ConversationListItem.md b/ts/components/ConversationListItem.md index 77c0994d48..c4ad525f99 100644 --- a/ts/components/ConversationListItem.md +++ b/ts/components/ConversationListItem.md @@ -3,6 +3,7 @@ ```jsx console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> @@ -23,6 +24,7 @@ ```jsx console.log('onClick')} + onClick={result => console.log('onClick', result)} + i18n={util.i18n} + /> + +``` + +#### Conversation with yourself + +```jsx + + console.log('onClick', result)} i18n={util.i18n} /> @@ -44,6 +68,7 @@
console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} />
@@ -118,17 +147,19 @@
console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} />
console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} />
@@ -152,6 +183,7 @@
console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} />
@@ -193,6 +227,7 @@ ```jsx console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> @@ -214,23 +249,25 @@ We don't want Jumbomoji or links.
console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} />
@@ -245,6 +282,7 @@ We only show one line.
console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} />
@@ -326,6 +369,7 @@ On platforms that show scrollbars all the time, this is true all the time.
console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} />
@@ -357,43 +402,47 @@ On platforms that show scrollbars all the time, this is true all the time.
console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} />
@@ -406,26 +455,29 @@ On platforms that show scrollbars all the time, this is true all the time.
console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} /> console.log('onClick')} + onClick={result => console.log('onClick', result)} i18n={util.i18n} />
diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx index e38cf6d547..0a3868527f 100644 --- a/ts/components/ConversationListItem.tsx +++ b/ts/components/ConversationListItem.tsx @@ -1,5 +1,8 @@ import React from 'react'; import classNames from 'classnames'; +import { isEmpty } from 'lodash'; +import { ContextMenu, ContextMenuTrigger, MenuItem } from 'react-contextmenu'; +import { Portal } from 'react-portal'; import { Avatar } from './Avatar'; import { MessageBody } from './conversation/MessageBody'; @@ -7,34 +10,49 @@ import { Timestamp } from './conversation/Timestamp'; import { ContactName } from './conversation/ContactName'; import { TypingAnimation } from './conversation/TypingAnimation'; -import { Colors, Localizer } from '../types/Util'; -import { ContextMenu, ContextMenuTrigger, MenuItem } from 'react-contextmenu'; +import { Colors, LocalizerType } from '../types/Util'; +import { SessionButton, SessionButtonColor } from './session/SessionButton'; -interface Props { +export type PropsData = { + id: string; phoneNumber: string; + color?: string; profileName?: string; name?: string; - color?: string; - conversationType: 'group' | 'direct'; + type: 'group' | 'direct'; avatarPath?: string; + isMe: boolean; + isPublic?: boolean; + isRss?: boolean; + isClosable?: boolean; lastUpdated: number; unreadCount: number; + mentionedUs: boolean; isSelected: boolean; isTyping: boolean; lastMessage?: { status: 'sending' | 'sent' | 'delivered' | 'read' | 'error'; text: string; + isRss: boolean; }; - showFriendRequestIndicator?: boolean; - isBlocked: boolean; - isOnline: boolean; - isMe: boolean; - hasNickname: boolean; - i18n: Localizer; - onClick?: () => void; + isPendingFriendRequest?: boolean; + hasReceivedFriendRequest?: boolean; + hasSentFriendRequest?: boolean; + isBlocked?: boolean; + isOnline?: boolean; + hasNickname?: boolean; + isFriend?: boolean; + isSecondary?: boolean; + isGroupInvitation?: boolean; +}; + +type PropsHousekeeping = { + i18n: LocalizerType; + style?: Object; + onClick?: (id: string) => void; onDeleteMessages?: () => void; onDeleteContact?: () => void; onBlockContact?: () => void; @@ -42,48 +60,64 @@ interface Props { onClearNickname?: () => void; onCopyPublicKey?: () => void; onUnblockContact?: () => void; -} + acceptFriendRequest?: () => void; + declineFriendRequest?: () => void; +}; + +type Props = PropsData & PropsHousekeeping; -export class ConversationListItem extends React.Component { +export class ConversationListItem extends React.PureComponent { public renderAvatar() { const { avatarPath, color, - conversationType, + type, i18n, + isMe, name, phoneNumber, profileName, isOnline, + isPendingFriendRequest, + hasSentFriendRequest, } = this.props; - const borderColor = isOnline ? Colors.ONLINE : Colors.OFFLINE; + let borderColor; + if (!(isPendingFriendRequest && !hasSentFriendRequest)) { + borderColor = isOnline ? Colors.ONLINE : Colors.OFFLINE; + } + const iconSize = isPendingFriendRequest && !hasSentFriendRequest ? 28 : 36; return (
- {this.renderUnread()}
); } public renderUnread() { - const { unreadCount } = this.props; + const { unreadCount, mentionedUs } = this.props; if (unreadCount > 0) { + const atSymbol = mentionedUs ?

@

: null; + return ( -
- {unreadCount} +
+

+ {unreadCount} +

+ {atSymbol}
); } @@ -95,10 +129,10 @@ export class ConversationListItem extends React.Component { const { unreadCount, i18n, + isMe, lastUpdated, - name, - phoneNumber, - profileName, + isFriend, + hasReceivedFriendRequest, } = this.props; return ( @@ -111,28 +145,28 @@ export class ConversationListItem extends React.Component { : null )} > - -
-
0 - ? 'module-conversation-list-item__header__date--has-unread' - : null - )} - > - + {isMe ? i18n('noteToSelf') : this.renderUser()}
+ {hasReceivedFriendRequest || this.renderUnread()} + {isFriend && ( +
0 + ? 'module-conversation-list-item__header__date--has-unread' + : null + )} + > + {!hasReceivedFriendRequest && ( + + )} +
+ )}
); } @@ -142,6 +176,9 @@ export class ConversationListItem extends React.Component { i18n, isBlocked, isMe, + isClosable, + isRss, + isPublic, hasNickname, onDeleteContact, onDeleteMessages, @@ -157,32 +194,63 @@ export class ConversationListItem extends React.Component { return ( - {!isMe ? ( + {!isPublic && !isRss && !isMe ? ( {blockTitle} ) : null} - {!isMe ? ( + {!isPublic && !isRss && !isMe ? ( {i18n('changeNickname')} ) : null} - {!isMe && hasNickname ? ( + {!isPublic && !isRss && !isMe && hasNickname ? ( {i18n('clearNickname')} ) : null} - {i18n('copyPublicKey')} + {!isPublic && !isRss ? ( + {i18n('copyPublicKey')} + ) : null} {i18n('deleteMessages')} - {!isMe ? ( - {i18n('deleteContact')} + {!isMe && isClosable ? ( + !isPublic ? ( + + {i18n('deleteContact')} + + ) : ( + + {i18n('deletePublicChannel')} + + ) ) : null} ); } public renderMessage() { - const { lastMessage, isTyping, unreadCount, i18n } = this.props; + const { + lastMessage, + isTyping, + unreadCount, + i18n, + isPendingFriendRequest, + } = this.props; if (!lastMessage && !isTyping) { return null; } + let text = lastMessage && lastMessage.text ? lastMessage.text : ''; + + // if coming from Rss feed + if (lastMessage && lastMessage.isRss) { + // strip any HTML + text = text.replace(/<[^>]*>?/gm, ''); + } + + if (isPendingFriendRequest) { + text = text.replace('Friend Request: ', ''); + } + + if (isEmpty(text)) { + return null; + } return (
@@ -198,7 +266,7 @@ export class ConversationListItem extends React.Component { ) : ( { ); } + public renderFriendRequestButtons() { + const { acceptFriendRequest, declineFriendRequest } = this.props; + + return ( +
+ + +
+ ); + } + public render() { const { phoneNumber, unreadCount, onClick, + id, isSelected, - showFriendRequestIndicator, + hasReceivedFriendRequest, isBlocked, + style, + mentionedUs, } = this.props; const triggerId = `${phoneNumber}-ctxmenu-${Date.now()}`; @@ -236,14 +325,22 @@ export class ConversationListItem extends React.Component {
{ + if (onClick) { + onClick(id); + } + }} + style={style} className={classNames( 'module-conversation-list-item', unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null, + unreadCount > 0 && mentionedUs + ? 'module-conversation-list-item--mentioned-us' + : null, isSelected ? 'module-conversation-list-item--is-selected' : null, - showFriendRequestIndicator + hasReceivedFriendRequest ? 'module-conversation-list-item--has-friend-request' : null, isBlocked ? 'module-conversation-list-item--is-blocked' : null @@ -254,9 +351,31 @@ export class ConversationListItem extends React.Component { {this.renderHeader()} {this.renderMessage()}
+ {hasReceivedFriendRequest && this.renderFriendRequestButtons()}
- {this.renderContextMenu(triggerId)} + {this.renderContextMenu(triggerId)} +
+ ); + } + + private renderUser() { + const { name, phoneNumber, profileName } = this.props; + + const shortenedPubkey = window.shortenPubkey(phoneNumber); + + const displayedPubkey = profileName ? shortenedPubkey : phoneNumber; + + return ( +
+
); } diff --git a/ts/components/ConversationLoadingScreen.tsx b/ts/components/ConversationLoadingScreen.tsx new file mode 100644 index 0000000000..677ffb8108 --- /dev/null +++ b/ts/components/ConversationLoadingScreen.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +import { SessionSpinner } from './session/SessionSpinner'; + +export class ConversationLoadingScreen extends React.PureComponent { + constructor(props: any) { + super(props); + } + + public render() { + return ( +
+ +
+ ); + } +} diff --git a/ts/components/DevicePairingDialog.tsx b/ts/components/DevicePairingDialog.tsx new file mode 100644 index 0000000000..9af2f2ce53 --- /dev/null +++ b/ts/components/DevicePairingDialog.tsx @@ -0,0 +1,387 @@ +import React, { ChangeEvent } from 'react'; +import { QRCode } from 'react-qr-svg'; + +import { SessionModal } from './session/SessionModal'; +import { SessionButton, SessionButtonColor } from './session/SessionButton'; +import { SessionSpinner } from './session/SessionSpinner'; + +interface Props { + onClose: any; + pubKeyToUnpair: string | undefined; +} + +interface State { + currentPubKey: string | undefined; + accepted: boolean; + pubKeyRequests: Array; + currentView: 'filterRequestView' | 'qrcodeView' | 'unpairDeviceView'; + errors: any; + loading: boolean; + deviceAlias: string | undefined; +} + +export class DevicePairingDialog extends React.Component { + constructor(props: any) { + super(props); + + this.closeDialog = this.closeDialog.bind(this); + this.onKeyUp = this.onKeyUp.bind(this); + this.stopReceivingRequests = this.stopReceivingRequests.bind(this); + this.startReceivingRequests = this.startReceivingRequests.bind(this); + this.skipDevice = this.skipDevice.bind(this); + this.allowDevice = this.allowDevice.bind(this); + this.validateSecondaryDevice = this.validateSecondaryDevice.bind(this); + this.handleUpdateDeviceAlias = this.handleUpdateDeviceAlias.bind(this); + this.triggerUnpairDevice = this.triggerUnpairDevice.bind(this); + + this.state = { + currentPubKey: undefined, + accepted: false, + pubKeyRequests: Array(), + currentView: props.pubKeyToUnpair ? 'unpairDeviceView' : 'qrcodeView', + loading: false, + errors: undefined, + deviceAlias: 'Unnamed Device', + }; + } + + public componentWillMount() { + if (this.state.currentView === 'qrcodeView') { + this.startReceivingRequests(); + } + } + + public componentWillUnmount() { + this.closeDialog(); + } + + public renderErrors() { + const { errors } = this.state; + + return ( + <> + {errors && ( + <> +
+
{errors}
+ + )} + + ); + } + + public renderFilterRequestsView() { + const { currentPubKey, accepted, deviceAlias } = this.state; + let secretWords: undefined; + if (currentPubKey) { + secretWords = window.mnemonic.pubkey_to_secret_words(currentPubKey); + } + + if (accepted) { + return ( + null} + onClose={this.closeDialog} + > +
+ {this.renderErrors()} + +
+ +
+ +
+
+ ); + } + + return ( + null} + onClose={this.closeDialog} + > +
+ {this.renderErrors()} + +
{secretWords}
+
+ + +
+
+
+ ); + } + + public renderQrCodeView() { + const requestReceived = this.hasReceivedRequests(); + const title = window.i18n('pairingDevice'); + + return ( + null} onClose={this.closeDialog}> +
+ {this.renderErrors()} +

{window.i18n('waitingForDeviceToRegister')}

+ + {window.i18n('pairNewDevicePrompt')} + +
+ +
+ +
+ +
+
+ {!requestReceived ? ( + + ) : ( +
+ +
+ )} +
+
+ + ); + } + + public renderUnpairDeviceView() { + const { pubKeyToUnpair } = this.props; + const secretWords = window.mnemonic.pubkey_to_secret_words(pubKeyToUnpair); + const conv = window.ConversationController.get(pubKeyToUnpair); + let description; + + if (conv && conv.getNickname()) { + description = `${conv.getNickname()}: ${window.shortenPubkey( + pubKeyToUnpair + )} ${secretWords}`; + } else { + description = `${window.shortenPubkey(pubKeyToUnpair)} ${secretWords}`; + } + + return ( + null} + onClose={this.closeDialog} + > +
+ {this.renderErrors()} +

+ {window.i18n('confirmUnpairingTitle')} +
+ {description} +

+
+
+ + +
+
+ + ); + } + + public render() { + const { currentView } = this.state; + const renderQrCodeView = currentView === 'qrcodeView'; + const renderFilterRequestView = currentView === 'filterRequestView'; + const renderUnpairDeviceView = currentView === 'unpairDeviceView'; + + return ( + <> + {renderQrCodeView && this.renderQrCodeView()} + {renderFilterRequestView && this.renderFilterRequestsView()} + {renderUnpairDeviceView && this.renderUnpairDeviceView()} + + ); + } + + private reset() { + this.setState({ + currentPubKey: undefined, + accepted: false, + pubKeyRequests: Array(), + currentView: 'filterRequestView', + deviceAlias: 'Unnamed Device', + }); + } + + private startReceivingRequests() { + this.reset(); + window.Whisper.events.on( + 'devicePairingRequestReceived', + (pubKey: string) => { + this.requestReceived(pubKey); + } + ); + this.setState({ currentView: 'qrcodeView' }); + } + + private stopReceivingRequests() { + this.setState({ currentView: 'filterRequestView' }); + window.Whisper.events.off('devicePairingRequestReceived'); + } + + private requestReceived(secondaryDevicePubKey: string | EventHandlerNonNull) { + // FIFO: push at the front of the array with unshift() + this.state.pubKeyRequests.unshift(secondaryDevicePubKey); + window.pushToast({ + title: window.i18n('gotPairingRequest'), + description: `${window.shortenPubkey( + secondaryDevicePubKey + )} ${window.i18n( + 'showPairingWordsTitle' + )}: ${window.mnemonic.pubkey_to_secret_words(secondaryDevicePubKey)}`, + }); + if (!this.state.currentPubKey) { + this.nextPubKey(); + } + } + + private allowDevice() { + this.setState({ + accepted: true, + }); + } + + private transmissionCB(errors: any) { + if (!errors) { + this.setState({ + errors: null, + }); + this.closeDialog(); + window.pushToast({ + title: window.i18n('devicePairedSuccessfully'), + }); + const conv = window.ConversationController.get(this.state.currentPubKey); + if (conv) { + conv.setNickname(this.state.deviceAlias); + } + + return; + } + + this.setState({ + errors: errors, + }); + } + + private skipDevice() { + window.Whisper.events.trigger( + 'devicePairingRequestRejected', + this.state.currentPubKey + ); + + const hasNext = this.state.pubKeyRequests.length > 0; + this.nextPubKey(); + if (!hasNext) { + this.startReceivingRequests(); + } + this.setState({ + currentView: hasNext ? 'filterRequestView' : 'qrcodeView', + }); + } + + private nextPubKey() { + // FIFO: pop at the back of the array using pop() + this.setState({ + currentPubKey: this.state.pubKeyRequests.pop(), + }); + } + + private onKeyUp(event: any) { + switch (event.key) { + case 'Esc': + case 'Escape': + this.closeDialog(); + break; + default: + } + } + + private validateSecondaryDevice() { + this.setState({ loading: true }); + window.Whisper.events.trigger( + 'devicePairingRequestAccepted', + this.state.currentPubKey, + (errors: any) => { + this.transmissionCB(errors); + window.Whisper.events.trigger('refreshLinkedDeviceList'); + + return true; + } + ); + } + + private hasReceivedRequests() { + return this.state.currentPubKey || this.state.pubKeyRequests.length > 0; + } + + private closeDialog() { + window.removeEventListener('keyup', this.onKeyUp); + this.stopReceivingRequests(); + window.Whisper.events.off('devicePairingRequestReceived'); + if (this.state.currentPubKey && !this.state.accepted) { + window.Whisper.events.trigger( + 'devicePairingRequestRejected', + this.state.currentPubKey + ); + } + this.props.onClose(); + } + + private handleUpdateDeviceAlias(value: ChangeEvent) { + const trimmed = value.target.value.trim(); + if (!!trimmed) { + this.setState({ deviceAlias: trimmed }); + } else { + this.setState({ deviceAlias: undefined }); + } + } + + private triggerUnpairDevice() { + window.Whisper.events.trigger( + 'deviceUnpairingRequested', + this.props.pubKeyToUnpair + ); + window.pushToast({ + title: window.i18n('deviceUnpaired'), + }); + this.closeDialog(); + } +} diff --git a/ts/components/EditProfileDialog.tsx b/ts/components/EditProfileDialog.tsx new file mode 100644 index 0000000000..555986cdc5 --- /dev/null +++ b/ts/components/EditProfileDialog.tsx @@ -0,0 +1,340 @@ +import React from 'react'; +import { QRCode } from 'react-qr-svg'; + +import { Avatar } from './Avatar'; + +import { + SessionButton, + SessionButtonColor, + SessionButtonType, +} from './session/SessionButton'; + +import { + SessionIconButton, + SessionIconSize, + SessionIconType, +} from './session/icon'; +import { SessionModal } from './session/SessionModal'; + +declare global { + interface Window { + displayNameRegex: any; + } +} + +interface Props { + callback: any; + i18n: any; + profileName: string; + avatarPath: string; + avatarColor: string; + pubkey: string; + onClose: any; + onOk: any; +} + +interface State { + profileName: string; + setProfileName: string; + avatar: string; + mode: 'default' | 'edit' | 'qr'; +} + +export class EditProfileDialog extends React.Component { + private readonly inputEl: any; + + constructor(props: any) { + super(props); + + this.onNameEdited = this.onNameEdited.bind(this); + this.closeDialog = this.closeDialog.bind(this); + this.onClickOK = this.onClickOK.bind(this); + this.onKeyUp = this.onKeyUp.bind(this); + this.onFileSelected = this.onFileSelected.bind(this); + this.fireInputEvent = this.fireInputEvent.bind(this); + + this.state = { + profileName: this.props.profileName, + setProfileName: this.props.profileName, + avatar: this.props.avatarPath, + mode: 'default', + }; + + this.inputEl = React.createRef(); + + window.addEventListener('keyup', this.onKeyUp); + } + + public render() { + const i18n = this.props.i18n; + + const viewDefault = this.state.mode === 'default'; + const viewEdit = this.state.mode === 'edit'; + const viewQR = this.state.mode === 'qr'; + const sessionID = window.textsecure.storage.user.getNumber(); + + const backButton = + viewEdit || viewQR + ? [ + { + iconType: SessionIconType.Chevron, + iconRotation: 90, + onClick: () => { + this.setState({ mode: 'default' }); + }, + }, + ] + : undefined; + + return ( + +
+ + {viewQR && this.renderQRView(sessionID)} + {viewDefault && this.renderDefaultView()} + {viewEdit && this.renderEditView()} + +
+
+ {window.i18n('yourSessionID')} +
+

{sessionID}

+ +
+ + {viewDefault || viewQR ? ( + { + this.copySessionID(sessionID); + }} + /> + ) : ( + + )} + +
+
+ + ); + } + + private renderProfileHeader() { + return ( + <> +
+
+ {this.renderAvatar()} +
+ +
+ { + this.setState({ mode: 'qr' }); + }} + /> +
+
+
+ + ); + } + + private fireInputEvent() { + this.setState({ mode: 'edit' }, () => { + const el = this.inputEl.current; + if (el) { + el.click(); + } + }); + } + + private renderDefaultView() { + return ( + <> + {this.renderProfileHeader()} + +
+

{this.state.setProfileName}

+ { + this.setState({ mode: 'edit' }); + }} + /> +
+ + ); + } + + private renderEditView() { + const placeholderText = window.i18n('displayName'); + + return ( + <> + {this.renderProfileHeader()} +
+ +
+ + ); + } + + private renderQRView(sessionID: string) { + const bgColor = '#FFFFFF'; + const fgColor = '#1B1B1B'; + + return ( +
+ +
+ ); + } + + private onFileSelected() { + const file = this.inputEl.current.files[0]; + const url = window.URL.createObjectURL(file); + + this.setState({ + avatar: url, + }); + } + + private renderAvatar() { + const avatarPath = this.state.avatar; + const color = this.props.avatarColor; + + return ( + + ); + } + + private onNameEdited(event: any) { + event.persist(); + + const newName = event.target.value.replace(window.displayNameRegex, ''); + + this.setState(state => { + return { + ...state, + profileName: newName, + }; + }); + } + + private onKeyUp(event: any) { + switch (event.key) { + case 'Enter': + if (this.state.mode === 'edit') { + this.onClickOK(); + } + break; + case 'Esc': + case 'Escape': + this.closeDialog(); + break; + default: + } + } + + private copySessionID(sessionID: string) { + window.clipboard.writeText(sessionID); + + window.pushToast({ + title: window.i18n('copiedSessionID'), + type: 'success', + id: 'copiedSessionID', + }); + } + + private onClickOK() { + const newName = this.state.profileName.trim(); + + if ( + newName.length === 0 || + newName.length > window.CONSTANTS.MAX_USERNAME_LENGTH + ) { + return; + } + + const avatar = + this.inputEl && + this.inputEl.current && + this.inputEl.current.files && + this.inputEl.current.files.length > 0 + ? this.inputEl.current.files[0] + : null; + + this.props.onOk(newName, avatar); + + this.setState( + { + mode: 'default', + setProfileName: this.state.profileName, + }, + () => { + // Update settinngs in dialog complete; + // now callback to reloadactions panel avatar + this.props.callback(this.state.avatar); + } + ); + } + + private closeDialog() { + window.removeEventListener('keyup', this.onKeyUp); + + this.props.onClose(); + } +} diff --git a/ts/components/Intl.tsx b/ts/components/Intl.tsx index 00389469e5..d5a353fd82 100644 --- a/ts/components/Intl.tsx +++ b/ts/components/Intl.tsx @@ -1,15 +1,15 @@ import React from 'react'; -import { Localizer, RenderTextCallback } from '../types/Util'; +import { LocalizerType, RenderTextCallbackType } from '../types/Util'; type FullJSX = Array | JSX.Element | string; interface Props { /** The translation string id */ id: string; - i18n: Localizer; + i18n: LocalizerType; components?: Array; - renderText?: RenderTextCallback; + renderText?: RenderTextCallbackType; } export class Intl extends React.Component { @@ -17,7 +17,7 @@ export class Intl extends React.Component { renderText: ({ text }) => text, }; - public getComponent(index: number): FullJSX | null { + public getComponent(index: number): FullJSX | undefined { const { id, components } = this.props; if (!components || !components.length || components.length <= index) { @@ -26,7 +26,7 @@ export class Intl extends React.Component { `Error: Intl missing provided components for id ${id}, index ${index}` ); - return null; + return; } return components[index]; diff --git a/ts/components/JazzIcon/JazzIcon.tsx b/ts/components/JazzIcon/JazzIcon.tsx new file mode 100644 index 0000000000..c9fd2fcece --- /dev/null +++ b/ts/components/JazzIcon/JazzIcon.tsx @@ -0,0 +1,166 @@ +// Modified from https://github.com/redlanta/react-jazzicon + +import React from 'react'; +import Color from 'color'; +import { Paper } from './Paper'; +import { RNG } from './RNG'; + +const defaultColors = [ + '#01888c', // teal + '#fc7500', // bright orange + '#034f5d', // dark teal + '#E784BA', // light pink + '#81C8B6', // bright green + '#c7144c', // raspberry + '#f3c100', // goldenrod + '#1598f2', // lightning blue + '#2465e1', // sail blue + '#f19e02', // gold +]; + +const isColor = (str: string) => /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(str); +const isColors = (arr: Array) => { + if (!Array.isArray(arr)) { + return false; + } + + if (arr.every(value => typeof value === 'string' && isColor(value))) { + return true; + } + + return false; +}; + +interface Props { + diameter: number; + seed: number; + paperStyles?: Object; + svgStyles?: Object; + shapeCount?: number; + wobble?: number; + colors?: Array; +} + +// tslint:disable-next-line no-http-string +const svgns = 'http://www.w3.org/2000/svg'; +const shapeCount = 4; +const wobble = 30; + +export class JazzIcon extends React.PureComponent { + public render() { + const { + colors: customColors, + diameter, + paperStyles, + seed, + svgStyles, + } = this.props; + + const generator = new RNG(seed); + + const colors = customColors || defaultColors; + + const newColours = this.hueShift( + this.colorsForIcon(colors).slice(), + generator + ); + const shapesArr = Array(shapeCount).fill(null); + const shuffledColours = this.shuffleArray(newColours, generator); + + return ( + + + {shapesArr.map((_, i) => + this.genShape( + shuffledColours[i + 1], + diameter, + i, + shapeCount - 1, + generator + ) + )} + + + ); + } + + private hueShift(colors: Array, generator: RNG) { + const amount = generator.random() * 30 - wobble / 2; + + return colors.map(hex => + Color(hex) + .rotate(amount) + .hex() + ); + } + + private genShape( + colour: string, + diameter: number, + i: number, + total: number, + generator: RNG + ) { + const center = diameter / 2; + const firstRot = generator.random(); + const angle = Math.PI * 2 * firstRot; + const velocity = + diameter / total * generator.random() + i * diameter / total; + const tx = Math.cos(angle) * velocity; + const ty = Math.sin(angle) * velocity; + const translate = `translate(${tx} ${ty})`; + + // Third random is a shape rotation on top of all of that. + const secondRot = generator.random(); + const rot = firstRot * 360 + secondRot * 180; + const rotate = `rotate(${rot.toFixed(1)} ${center} ${center})`; + const transform = `${translate} ${rotate}`; + + return ( + + ); + } + + private colorsForIcon(arr: Array) { + if (isColors(arr)) { + return arr; + } + + return defaultColors; + } + + private shuffleArray(array: Array, generator: RNG) { + let currentIndex = array.length; + const newArray = [...array]; + + // While there remain elements to shuffle... + while (currentIndex > 0) { + // Pick a remaining element... + const randomIndex = generator.next() % currentIndex; + currentIndex -= 1; + // And swap it with the current element. + const temporaryValue = newArray[currentIndex]; + newArray[currentIndex] = newArray[randomIndex]; + newArray[randomIndex] = temporaryValue; + } + + return newArray; + } +} diff --git a/ts/components/JazzIcon/Paper.tsx b/ts/components/JazzIcon/Paper.tsx new file mode 100644 index 0000000000..ee37916372 --- /dev/null +++ b/ts/components/JazzIcon/Paper.tsx @@ -0,0 +1,25 @@ +import React from 'react'; + +const styles = { + borderRadius: '50%', + display: 'inline-block', + margin: 0, + overflow: 'hidden', + padding: 0, +}; + +// @ts-ignore +export const Paper = ({ children, color, diameter, style: styleOverrides }) => ( +
+ {children} +
+); diff --git a/ts/components/JazzIcon/RNG.tsx b/ts/components/JazzIcon/RNG.tsx new file mode 100644 index 0000000000..b6c18ac6bb --- /dev/null +++ b/ts/components/JazzIcon/RNG.tsx @@ -0,0 +1,21 @@ +export class RNG { + private _seed: number; + constructor(seed: number) { + this._seed = seed % 2147483647; + if (this._seed <= 0) { + this._seed += 2147483646; + } + } + + public next() { + return (this._seed = (this._seed * 16807) % 2147483647); + } + + public nextFloat() { + return (this.next() - 1) / 2147483646; + } + + public random() { + return this.nextFloat(); + } +} diff --git a/ts/components/JazzIcon/index.tsx b/ts/components/JazzIcon/index.tsx new file mode 100644 index 0000000000..204774dc64 --- /dev/null +++ b/ts/components/JazzIcon/index.tsx @@ -0,0 +1,2 @@ +import { JazzIcon } from './JazzIcon'; +export { JazzIcon }; diff --git a/ts/components/LeftPane.md b/ts/components/LeftPane.md new file mode 100644 index 0000000000..11fb6c9787 --- /dev/null +++ b/ts/components/LeftPane.md @@ -0,0 +1,240 @@ +#### With search results + +```jsx +window.searchResults = {}; +window.searchResults.conversations = [ + { + id: 'convo1', + name: 'Everyone 🌆', + conversationType: 'group', + phoneNumber: '(202) 555-0011', + avatarPath: util.landscapeGreenObjectUrl, + lastUpdated: Date.now() - 5 * 60 * 1000, + lastMessage: { + text: 'The rabbit hopped silently in the night.', + status: 'sent', + }, + }, + { + id: 'convo2', + name: 'Everyone Else 🔥', + conversationType: 'direct', + phoneNumber: '(202) 555-0012', + avatarPath: util.landscapePurpleObjectUrl, + lastUpdated: Date.now() - 5 * 60 * 1000, + lastMessage: { + text: "What's going on?", + status: 'error', + }, + }, + { + id: 'convo3', + name: 'John the Turtle', + conversationType: 'direct', + phoneNumber: '(202) 555-0021', + lastUpdated: Date.now() - 24 * 60 * 60 * 1000, + lastMessage: { + text: 'I dunno', + }, + }, + { + id: 'convo4', + name: 'The Fly', + conversationType: 'direct', + phoneNumber: '(202) 555-0022', + avatarPath: util.pngObjectUrl, + lastUpdated: Date.now(), + lastMessage: { + text: 'Gimme!', + }, + }, +]; + +window.searchResults.contacts = [ + { + id: 'contact1', + name: 'The one Everyone', + conversationType: 'direct', + phoneNumber: '(202) 555-0013', + avatarPath: util.gifObjectUrl, + }, + { + id: 'contact2', + e: 'No likey everyone', + conversationType: 'direct', + phoneNumber: '(202) 555-0014', + color: 'red', + }, +]; + +window.searchResults.messages = [ + { + from: { + isMe: true, + avatarPath: util.gifObjectUrl, + }, + to: { + name: 'Mr. Fire 🔥', + phoneNumber: '(202) 555-0015', + }, + id: '1-guid-guid-guid-guid-guid', + conversationId: '(202) 555-0015', + receivedAt: Date.now() - 5 * 60 * 1000, + snippet: '<>Everyone<>! Get in!', + }, + { + from: { + name: 'Jon ❄️', + phoneNumber: '(202) 555-0016', + color: 'green', + }, + to: { + isMe: true, + }, + id: '2-guid-guid-guid-guid-guid', + conversationId: '(202) 555-0016', + snippet: 'Why is <>everyone<> so frustrated?', + receivedAt: Date.now() - 20 * 60 * 1000, + }, + { + from: { + name: 'Someone', + phoneNumber: '(202) 555-0011', + color: 'green', + avatarPath: util.pngObjectUrl, + }, + to: { + name: "Y'all 🌆", + }, + id: '3-guid-guid-guid-guid-guid', + conversationId: 'EveryoneGroupID', + snippet: 'Hello, <>everyone<>! Woohooo!', + receivedAt: Date.now() - 24 * 60 * 1000, + }, + { + from: { + isMe: true, + avatarPath: util.gifObjectUrl, + }, + to: { + name: "Y'all 🌆", + }, + id: '4-guid-guid-guid-guid-guid', + conversationId: 'EveryoneGroupID', + snippet: 'Well, <>everyone<>, happy new year!', + receivedAt: Date.now() - 24 * 60 * 1000, + }, +]; + + + + console.log('startNewConversation', query, options) + } + openConversationInternal={(id, messageId) => + console.log('openConversation', id, messageId) + } + showArchivedConversations={() => console.log('showArchivedConversations')} + showInbox={() => console.log('showInbox')} + renderMainHeader={() => ( + console.log('search', result)} + updateSearch={result => console.log('updateSearch', result)} + clearSearch={result => console.log('clearSearch', result)} + i18n={util.i18n} + /> + )} + i18n={util.i18n} + /> +; +``` + +#### With just conversations + +```jsx + + + console.log('startNewConversation', query, options) + } + openConversationInternal={(id, messageId) => + console.log('openConversation', id, messageId) + } + showArchivedConversations={() => console.log('showArchivedConversations')} + showInbox={() => console.log('showInbox')} + renderMainHeader={() => ( + console.log('search', result)} + updateSearch={result => console.log('updateSearch', result)} + clearSearch={result => console.log('clearSearch', result)} + i18n={util.i18n} + /> + )} + i18n={util.i18n} + /> + +``` + +#### Showing inbox, with some archived + +```jsx + + + console.log('startNewConversation', query, options) + } + openConversationInternal={(id, messageId) => + console.log('openConversation', id, messageId) + } + showArchivedConversations={() => console.log('showArchivedConversations')} + showInbox={() => console.log('showInbox')} + renderMainHeader={() => ( + console.log('search', result)} + updateSearch={result => console.log('updateSearch', result)} + clearSearch={result => console.log('clearSearch', result)} + i18n={util.i18n} + /> + )} + i18n={util.i18n} + /> + +``` + +#### Showing archived conversations + +```jsx + + + console.log('startNewConversation', query, options) + } + openConversationInternal={(id, messageId) => + console.log('openConversation', id, messageId) + } + showArchivedConversations={() => console.log('showArchivedConversations')} + showInbox={() => console.log('showInbox')} + renderMainHeader={() => ( + console.log('search', result)} + updateSearch={result => console.log('updateSearch', result)} + clearSearch={result => console.log('clearSearch', result)} + i18n={util.i18n} + /> + )} + i18n={util.i18n} + /> + +``` diff --git a/ts/components/LeftPane.tsx b/ts/components/LeftPane.tsx new file mode 100644 index 0000000000..babd60a8ea --- /dev/null +++ b/ts/components/LeftPane.tsx @@ -0,0 +1,203 @@ +import React from 'react'; + +import { ActionsPanel, SectionType } from './session/ActionsPanel'; +import { LeftPaneMessageSection } from './session/LeftPaneMessageSection'; + +import { PropsData as ConversationListItemPropsType } from './ConversationListItem'; +import { PropsData as SearchResultsProps } from './SearchResults'; +import { SearchOptions } from '../types/Search'; +import { LeftPaneSectionHeader } from './session/LeftPaneSectionHeader'; + +import { ConversationType } from '../state/ducks/conversations'; +import { LeftPaneContactSection } from './session/LeftPaneContactSection'; +import { LeftPaneSettingSection } from './session/LeftPaneSettingSection'; +import { LeftPaneChannelSection } from './session/LeftPaneChannelSection'; + +// from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5 +export type RowRendererParamsType = { + index: number; + isScrolling: boolean; + isVisible: boolean; + key: string; + parent: Object; + style: Object; +}; + +interface State { + selectedSection: SectionType; +} + +interface Props { + conversations: Array; + friends: Array; + sentFriendsRequest: Array; + receivedFriendsRequest: Array; + unreadMessageCount: number; + receivedFriendRequestCount: number; + searchResults?: SearchResultsProps; + searchTerm: string; + isSecondaryDevice: boolean; + + openConversationInternal: (id: string, messageId?: string) => void; + updateSearchTerm: (searchTerm: string) => void; + search: (query: string, options: SearchOptions) => void; + clearSearch: () => void; +} + +export class LeftPane extends React.Component { + public state = { + selectedSection: SectionType.Message, + }; + + public constructor(props: any) { + super(props); + this.handleSectionSelected = this.handleSectionSelected.bind(this); + } + + // this static function is set here to be used by all subsections (message, contacts,...) to render their headers + public static RENDER_HEADER( + labels: Array, + onTabSelected?: any, + buttonLabel?: string, + buttonClicked?: any, + notificationCount?: number + ): JSX.Element { + return ( + + ); + } + + public handleSectionSelected(section: SectionType) { + this.props.clearSearch(); + this.setState({ selectedSection: section }); + } + + public render(): JSX.Element { + return ( +
+ +
{this.renderSection()}
+
+ ); + } + + private renderSection(): JSX.Element | undefined { + switch (this.state.selectedSection) { + case SectionType.Message: + return this.renderMessageSection(); + case SectionType.Contact: + return this.renderContactSection(); + case SectionType.Channel: + return this.renderChannelSection(); + case SectionType.Settings: + return this.renderSettingSection(); + case SectionType.Moon: + return window.toggleTheme(); + default: + return undefined; + } + } + + private renderMessageSection() { + const { + openConversationInternal, + conversations, + searchResults, + searchTerm, + isSecondaryDevice, + updateSearchTerm, + search, + clearSearch, + } = this.props; + + return ( + + ); + } + + private renderContactSection() { + const { + openConversationInternal, + friends, + sentFriendsRequest, + receivedFriendsRequest, + conversations, + searchResults, + searchTerm, + isSecondaryDevice, + updateSearchTerm, + search, + clearSearch, + receivedFriendRequestCount, + } = this.props; + + return ( + + ); + } + + private renderSettingSection() { + return ; + } + + private renderChannelSection() { + const { + openConversationInternal, + conversations, + searchResults, + searchTerm, + isSecondaryDevice, + updateSearchTerm, + search, + clearSearch, + } = this.props; + + return ( + + ); + } +} diff --git a/ts/components/Lightbox.tsx b/ts/components/Lightbox.tsx index 05cda90b03..1f46fac2b5 100644 --- a/ts/components/Lightbox.tsx +++ b/ts/components/Lightbox.tsx @@ -8,7 +8,7 @@ import is from '@sindresorhus/is'; import * as GoogleChrome from '../util/GoogleChrome'; import * as MIME from '../types/MIME'; -import { Localizer } from '../types/Util'; +import { LocalizerType } from '../types/Util'; const Colors = { TEXT_SECONDARY: '#bbb', @@ -26,7 +26,7 @@ const colorSVG = (url: string, color: string) => { interface Props { close: () => void; contentType: MIME.MIMEType | undefined; - i18n: Localizer; + i18n: LocalizerType; objectURL: string; caption?: string; onNext?: () => void; @@ -164,17 +164,17 @@ const Icon = ({ ); export class Lightbox extends React.Component { - private containerRef: HTMLDivElement | null = null; - private videoRef: HTMLVideoElement | null = null; - - private captureVideoBound: (element: HTMLVideoElement) => void; - private playVideoBound: () => void; + private readonly containerRef: React.RefObject; + private readonly videoRef: React.RefObject; + private readonly playVideoBound: () => void; constructor(props: Props) { super(props); - this.captureVideoBound = this.captureVideo.bind(this); this.playVideoBound = this.playVideo.bind(this); + + this.videoRef = React.createRef(); + this.containerRef = React.createRef(); } public componentDidMount() { @@ -189,20 +189,21 @@ export class Lightbox extends React.Component { document.removeEventListener('keyup', this.onKeyUp, useCapture); } - public captureVideo(element: HTMLVideoElement) { - this.videoRef = element; - } - public playVideo() { if (!this.videoRef) { return; } - if (this.videoRef.paused) { + const { current } = this.videoRef; + if (!current) { + return; + } + + if (current.paused) { // tslint:disable-next-line no-floating-promises - this.videoRef.play(); + current.play(); } else { - this.videoRef.pause(); + current.pause(); } } @@ -221,7 +222,7 @@ export class Lightbox extends React.Component {
@@ -259,14 +260,14 @@ export class Lightbox extends React.Component { ); } - private renderObject = ({ + private readonly renderObject = ({ objectURL, contentType, i18n, }: { objectURL: string; contentType: MIME.MIMEType; - i18n: Localizer; + i18n: LocalizerType; }) => { const isImageTypeSupported = GoogleChrome.isImageTypeSupported(contentType); if (isImageTypeSupported) { @@ -285,7 +286,7 @@ export class Lightbox extends React.Component { return (