mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-02-07 16:13:23 +08:00
Work on Google Play V2 Accounts
This commit is contained in:
parent
0cbaf28aae
commit
c79abc6a74
106
.efrocachemap
106
.efrocachemap
@ -420,28 +420,28 @@
|
|||||||
"assets/build/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/60/ad/38269b7f1c7dc20cb9a506cd0681",
|
"assets/build/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/60/ad/38269b7f1c7dc20cb9a506cd0681",
|
||||||
"assets/build/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/72/85/d6fc4d16b7081d91fba2850b5b10",
|
"assets/build/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/72/85/d6fc4d16b7081d91fba2850b5b10",
|
||||||
"assets/build/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/e9/ae/1d674d0c086eaa0bd1c3b1db0505",
|
"assets/build/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/e9/ae/1d674d0c086eaa0bd1c3b1db0505",
|
||||||
"assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/25/11/cfcf4238fb55433521e26cce0d10",
|
"assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/89/ec/d472036fbb09f310891761beb39a",
|
||||||
"assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/e2/24/5e7ea9ca5c9de4d3b7a28e53564d",
|
"assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/b0/05/e530acaba539f040ce61e22561dc",
|
||||||
"assets/build/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/61/03/89070ca765e06da3a419a579f503",
|
"assets/build/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/61/03/89070ca765e06da3a419a579f503",
|
||||||
"assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/ea/22/bb0950095686a71030c67ac74b3b",
|
"assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/1f/7f/af259ba9b41556e5e667ad4c646d",
|
||||||
"assets/build/ba_data/data/languages/chinesetraditional.json": "https://files.ballistica.net/cache/ba1/3c/22/78a56fc40426ab19ad4e76924b78",
|
"assets/build/ba_data/data/languages/chinesetraditional.json": "https://files.ballistica.net/cache/ba1/3c/22/78a56fc40426ab19ad4e76924b78",
|
||||||
"assets/build/ba_data/data/languages/croatian.json": "https://files.ballistica.net/cache/ba1/c9/73/01a1343af814131b1ee96af0b687",
|
"assets/build/ba_data/data/languages/croatian.json": "https://files.ballistica.net/cache/ba1/c9/73/01a1343af814131b1ee96af0b687",
|
||||||
"assets/build/ba_data/data/languages/czech.json": "https://files.ballistica.net/cache/ba1/61/20/01291c2cb72b22f204730c0d7574",
|
"assets/build/ba_data/data/languages/czech.json": "https://files.ballistica.net/cache/ba1/61/20/01291c2cb72b22f204730c0d7574",
|
||||||
"assets/build/ba_data/data/languages/danish.json": "https://files.ballistica.net/cache/ba1/6a/fa/fcf4a804beaff927b0f12c179eaa",
|
"assets/build/ba_data/data/languages/danish.json": "https://files.ballistica.net/cache/ba1/6a/fa/fcf4a804beaff927b0f12c179eaa",
|
||||||
"assets/build/ba_data/data/languages/dutch.json": "https://files.ballistica.net/cache/ba1/68/93/da8e9874f41a786edf52ba4ccaad",
|
"assets/build/ba_data/data/languages/dutch.json": "https://files.ballistica.net/cache/ba1/68/93/da8e9874f41a786edf52ba4ccaad",
|
||||||
"assets/build/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/df/08/29edc91f648c34c3aa7960e04c13",
|
"assets/build/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/a0/1d/5fbc922d01521142c2a347b1b024",
|
||||||
"assets/build/ba_data/data/languages/esperanto.json": "https://files.ballistica.net/cache/ba1/4c/c7/0184b8178869d1a3827a1bfcd5bb",
|
"assets/build/ba_data/data/languages/esperanto.json": "https://files.ballistica.net/cache/ba1/4c/c7/0184b8178869d1a3827a1bfcd5bb",
|
||||||
"assets/build/ba_data/data/languages/filipino.json": "https://files.ballistica.net/cache/ba1/c7/2e/e0520f58206da01b829e02ff4576",
|
"assets/build/ba_data/data/languages/filipino.json": "https://files.ballistica.net/cache/ba1/c7/2e/e0520f58206da01b829e02ff4576",
|
||||||
"assets/build/ba_data/data/languages/french.json": "https://files.ballistica.net/cache/ba1/e8/84/6c9f123e9a0d82fc595c8f55ac7c",
|
"assets/build/ba_data/data/languages/french.json": "https://files.ballistica.net/cache/ba1/e8/84/6c9f123e9a0d82fc595c8f55ac7c",
|
||||||
"assets/build/ba_data/data/languages/german.json": "https://files.ballistica.net/cache/ba1/8a/09/3e0fa9e44913b53f4dab195d3fae",
|
"assets/build/ba_data/data/languages/german.json": "https://files.ballistica.net/cache/ba1/8a/09/3e0fa9e44913b53f4dab195d3fae",
|
||||||
"assets/build/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/08/15/13981ce51e1e9f974357a9e0a59c",
|
"assets/build/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/5f/51/c15d74d2fe4e88ee1e3db0986500",
|
||||||
"assets/build/ba_data/data/languages/greek.json": "https://files.ballistica.net/cache/ba1/aa/da/dfc8d710af960d7300c7090faeab",
|
"assets/build/ba_data/data/languages/greek.json": "https://files.ballistica.net/cache/ba1/aa/da/dfc8d710af960d7300c7090faeab",
|
||||||
"assets/build/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/91/98/42701cd595c2f70b7484614a8f49",
|
"assets/build/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/09/55/b50104638f60636af2263877bb7f",
|
||||||
"assets/build/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/d8/f2/aa16bc336bd7660cc86c3264bfc4",
|
"assets/build/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/d8/f2/aa16bc336bd7660cc86c3264bfc4",
|
||||||
"assets/build/ba_data/data/languages/indonesian.json": "https://files.ballistica.net/cache/ba1/0d/b4/e225e3838c4b5d9381dcc4594517",
|
"assets/build/ba_data/data/languages/indonesian.json": "https://files.ballistica.net/cache/ba1/3a/6b/34714586cb4e9f1b12f8ae54cac8",
|
||||||
"assets/build/ba_data/data/languages/italian.json": "https://files.ballistica.net/cache/ba1/12/62/862228b229057877e89fb195d41d",
|
"assets/build/ba_data/data/languages/italian.json": "https://files.ballistica.net/cache/ba1/12/62/862228b229057877e89fb195d41d",
|
||||||
"assets/build/ba_data/data/languages/korean.json": "https://files.ballistica.net/cache/ba1/7c/38/d4a44c481757d355836f292ede48",
|
"assets/build/ba_data/data/languages/korean.json": "https://files.ballistica.net/cache/ba1/7c/38/d4a44c481757d355836f292ede48",
|
||||||
"assets/build/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/df/b1/b2c9ebaad5e873ebedd365726d3d",
|
"assets/build/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/10/13/1228836444f7557211f0058ef9bd",
|
||||||
"assets/build/ba_data/data/languages/polish.json": "https://files.ballistica.net/cache/ba1/19/e9/59c891b1fb85f3ba9f19283c233d",
|
"assets/build/ba_data/data/languages/polish.json": "https://files.ballistica.net/cache/ba1/19/e9/59c891b1fb85f3ba9f19283c233d",
|
||||||
"assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/61/5b/847c03407d1c3a85866833323676",
|
"assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/61/5b/847c03407d1c3a85866833323676",
|
||||||
"assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/d7/06/9d70642d0a4d1e3b1c2149d7a17c",
|
"assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/d7/06/9d70642d0a4d1e3b1c2149d7a17c",
|
||||||
@ -4003,51 +4003,51 @@
|
|||||||
"assets/src/ba_data/python/ba/_generated/__init__.py": "https://files.ballistica.net/cache/ba1/ee/e8/cad05aa531c7faf7ff7b96db7f6e",
|
"assets/src/ba_data/python/ba/_generated/__init__.py": "https://files.ballistica.net/cache/ba1/ee/e8/cad05aa531c7faf7ff7b96db7f6e",
|
||||||
"assets/src/ba_data/python/ba/_generated/enums.py": "https://files.ballistica.net/cache/ba1/1c/77/ac670a5118abdf8a7687af0e159b",
|
"assets/src/ba_data/python/ba/_generated/enums.py": "https://files.ballistica.net/cache/ba1/1c/77/ac670a5118abdf8a7687af0e159b",
|
||||||
"ballisticacore-windows/Generic/BallisticaCore.ico": "https://files.ballistica.net/cache/ba1/89/c0/e32c7d2a35dc9aef57cc73b0911a",
|
"ballisticacore-windows/Generic/BallisticaCore.ico": "https://files.ballistica.net/cache/ba1/89/c0/e32c7d2a35dc9aef57cc73b0911a",
|
||||||
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/5d/0f/3592238512deb417b34428b91e09",
|
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/20/fc/441aca31b3c8d5e9dcd0d71f6634",
|
||||||
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/96/68/f540e5eefc5fc496f6531693f635",
|
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/76/42/ebdcc0eda90030c41496c53885b4",
|
||||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a2/3e/3c403004cc2f9e6902bc21ed36e2",
|
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/2a/c3/c1694a56da0e053d4dff2f203684",
|
||||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/fb/63/67f2fbaadfd5dd9604c33d3be317",
|
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/0e/b8/9bbb04ec920dab1e32cbb752ab12",
|
||||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/a5/ec/375c414801b769b64c1848ee0331",
|
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/63/9a/85a2953ff4282728fac8bc9b92aa",
|
||||||
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/99/a6/8b421200970caea159fa12be513f",
|
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/da/6b/53941679695bef614fe94906f8c7",
|
||||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e3/64/45e980a2cabf12cb97d621fa4b66",
|
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/91/5b/5b4871e3e18e2cf046f8411ac41c",
|
||||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/67/5a/a6455e835d5fa38e66ce2e06c712",
|
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b1/e8/b471efa43d7bb2be88ec4882d1ec",
|
||||||
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ea/01/04ab6a0bc93eefa1523312420297",
|
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/20/30/0567f04d0ed9662d84907d73ccff",
|
||||||
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/b6/61/bff5c094a4bc66cbcd3727422687",
|
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/61/3e/3bd9d4b36f8e7977902f5830eef7",
|
||||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/2f/d1/780a1948216e46af050245f96d70",
|
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6f/55/f9e2afacd2d1e55622d42102f987",
|
||||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/19/56/b64bef7218b736ceb7cf07a4073c",
|
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/38/78/c94ca8ab70e99a64fe7d9b77b7fd",
|
||||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f2/e7/7c9bd7adb70893d23b1bb7df31f8",
|
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/81/3e/ce018d8ffb1ce8b795461f177926",
|
||||||
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/9d/f7/3aef724ad3f821687c13edf1b7e0",
|
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/4f/5e/2b753f317848edb49a1228b48669",
|
||||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/67/41/9e4ab9f852b1cd8f386f2de5a7d3",
|
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e6/09/5fb14bb97f99c12509d986482a9e",
|
||||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4d/ed/39635f875286955f76eb90729998",
|
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/31/9d/c21fd262d8e8bf7357982d3919ab",
|
||||||
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/e6/24/5a90398d108b1887d532cf525682",
|
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/b4/03/1a4dd6757d0f02f536b01446aa70",
|
||||||
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/db/ab/7c9126758b5903ea15cd40bad187",
|
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a7/e5/ed98da5d3d1d101498cdb78a3e66",
|
||||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/aa/7e/f1f9ccfb744c00d97cba806bbe0f",
|
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/8e/be/4c06b72d6cfe9d7c918e49e5dade",
|
||||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/99/fe/2ddc2fec5207e1a465acd307bc24",
|
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/d6/e0/7e696374bf682d8de0fa761fa280",
|
||||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/24/40/c2fb8cdbea5b58242c3b51b52d14",
|
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ab/36/ab405201f69f1396c7393d272145",
|
||||||
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/20/4e/60e18e1b04c88e1cdd4a0836d710",
|
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b1/f7/4d23bfb3b40e3abf944d03b293fb",
|
||||||
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a4/1e/dbb500fc6af85da634aeeb81af3e",
|
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ea/32/46199c5adad2591f3335cc3788a9",
|
||||||
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e1/4a/75c95d1e0a44e833b866f5a02cbd",
|
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b8/9d/10dd3fee2be55f0fb05160208cc3",
|
||||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/58/f2/5997aa7d771a67246ba55be7538d",
|
"build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d3/0c/dfd26af11b14dea706d03d32bc39",
|
||||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/57/21/b34020e41aae91cd276efc77a9d2",
|
"build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/02/80/b5fde15cc6e79f9b243b1e6c7035",
|
||||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/08/c3/cb7fbc13bc7aa3026f4363f6f1d6",
|
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f6/07/5cee0f1e1e5362d84f1af9ed2503",
|
||||||
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f3/54/5c6f3bfaa7feb59faf2a44945d38",
|
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3c/66/432193098a405483f64a78ad3139",
|
||||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ad/93/21d395043d633d70eef84502fd9f",
|
"build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d3/56/804cd3f543b05891f4f7d85ca457",
|
||||||
"build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/33/83/67d6444edb230cfc3d9ad7c030be",
|
"build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2a/95/62f8f30e3829d95358f603344aca",
|
||||||
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e6/81/aff0232cd83ec62aa0ec10716568",
|
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/bd/c1/db89136718610fbab8ebeb29e62c",
|
||||||
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/5c/96/aa9f7ad96fa1f70ff80dbfaa6f7e",
|
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/09/2a/71b05acd74b7f18af9b6eb0de3a3",
|
||||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/aa/56/194ab27e57149fb6b253bf8d754d",
|
"build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d2/37/08d73f46704304b24b38bf93b046",
|
||||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7e/1d/f330b35ca15bd654333a778b100d",
|
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d7/17/6220f7a35fbddb711c605903f491",
|
||||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/9a/5b/bcf80b7e94eb7bb1e949c41c58f8",
|
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c0/73/ae5ad1b4170b2bba3f31a95a9b3d",
|
||||||
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/5e/bc/1a9f73dc64f3ffcc489aa58b6251",
|
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2f/d2/8aa3dc377561ad74448c7e3f5a44",
|
||||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/ea/4f/8e568645129f921392345e4b81a3",
|
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/8e/ff/159b3a7f288f6fc7bd5fb7091a52",
|
||||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/e6/0a/43f0c01db3245e332a6cbb2c04a6",
|
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/2d/68/dc944091445ecff5ff5173acb17f",
|
||||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/7a/33/215327a641ff1d522d3554ec2577",
|
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/18/17/f5fe60e049a1bece575f6df1c4b2",
|
||||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/46/59/69428486eb6bb6ba752b37a31db1",
|
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/09/43/a868bb9683aac2a9bec62c8f7c89",
|
||||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/97/d3/716990aa94a4378f91afe137c773",
|
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/1d/47/d08b1b7383d837271fd1089f3b98",
|
||||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/c4/fd/7532b2a98bf8c1b0ea68485a9d8e",
|
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/ca/f1/f82e547b5f72cfb2c082f88523f8",
|
||||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/0d/06/a6b7a7ca3834c8a05b538c5f1934",
|
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/7b/5e/81fbec9eebd1137251df2dcc2953",
|
||||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/46/54/fdb879571df92b8636e880f66207",
|
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/ce/7f/f34ea80406a491e5c71080ea9639",
|
||||||
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/c0/32/b7907e3859a5c5013a3d97b6b523",
|
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/87/8c/22c79120b24ffa5d790bb6f72120",
|
||||||
"src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/2d/4f/f4fe67827f36cd59cd5193333a02",
|
"src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/2d/4f/f4fe67827f36cd59cd5193333a02",
|
||||||
"src/ballistica/generated/python_embedded/bootstrap_monolithic.inc": "https://files.ballistica.net/cache/ba1/ef/c1/aa5f1aa10af89f5c0b1e616355fd"
|
"src/ballistica/generated/python_embedded/bootstrap_monolithic.inc": "https://files.ballistica.net/cache/ba1/ef/c1/aa5f1aa10af89f5c0b1e616355fd"
|
||||||
}
|
}
|
||||||
3
.idea/dictionaries/ericf.xml
generated
3
.idea/dictionaries/ericf.xml
generated
@ -65,6 +65,7 @@
|
|||||||
<w>aiomain</w>
|
<w>aiomain</w>
|
||||||
<w>alarmsound</w>
|
<w>alarmsound</w>
|
||||||
<w>alibaba</w>
|
<w>alibaba</w>
|
||||||
|
<w>alibname</w>
|
||||||
<w>allerrors</w>
|
<w>allerrors</w>
|
||||||
<w>allobjc</w>
|
<w>allobjc</w>
|
||||||
<w>allobjs</w>
|
<w>allobjs</w>
|
||||||
@ -1103,6 +1104,7 @@
|
|||||||
<w>googlevr</w>
|
<w>googlevr</w>
|
||||||
<w>goosey</w>
|
<w>goosey</w>
|
||||||
<w>gotresponse</w>
|
<w>gotresponse</w>
|
||||||
|
<w>gpgs</w>
|
||||||
<w>gpio</w>
|
<w>gpio</w>
|
||||||
<w>gprev</w>
|
<w>gprev</w>
|
||||||
<w>gpsui</w>
|
<w>gpsui</w>
|
||||||
@ -1866,6 +1868,7 @@
|
|||||||
<w>pentry</w>
|
<w>pentry</w>
|
||||||
<w>perma</w>
|
<w>perma</w>
|
||||||
<w>perrdetail</w>
|
<w>perrdetail</w>
|
||||||
|
<w>phandle</w>
|
||||||
<w>phasers</w>
|
<w>phasers</w>
|
||||||
<w>phasescriptexecution</w>
|
<w>phasescriptexecution</w>
|
||||||
<w>phello</w>
|
<w>phello</w>
|
||||||
|
|||||||
@ -1,4 +1,10 @@
|
|||||||
### 1.7.14 (build 20921, api 7, 2022-11-03)
|
### 1.7.14 (build 20928, api 7, 2022-11-15)
|
||||||
|
- Android Google Play logins now provide V2 accounts with access to all V2 features such as a globally-unique account tag, cloud-console, and workspaces. They should still retain their V1 data as well.
|
||||||
|
- V2 accounts now have a 'Manage Account' button in the app account window which will sign you into a browser with your current account.
|
||||||
|
- Removed Google App Invite functionality which has been deprecated for a while now. Google Play users can still get tickets by sharing the app via codes (same as other platforms).
|
||||||
|
- Updated Android root-detection library to the latest version. Please holler if you are getting new false 'your device is rooted' errors when trying to play tournaments or anything like that.
|
||||||
|
- Removed a few obsolete internal functions: `_ba.is_ouya_build()`, `_ba.android_media_scan_file()`.
|
||||||
|
- Renaming some methods/data to disambiguate 'login' vs 'sign-in', both in the app and on ballistica.net. Those two terms are somewhat ambiguous and interchangeable in English and can either be a verb or a noun. I'd like to keep things clear in Ballistica by always using 'sign-in' for the verb form and 'login' for the noun. For example: 'You can now sign in to your account using your Google Play login'.
|
||||||
|
|
||||||
### 1.7.13 (build 20919, api 7, 2022-11-03)
|
### 1.7.13 (build 20919, api 7, 2022-11-03)
|
||||||
- Android target-sdk has been updated to 33 (Android 13). Please holler if anything seems broken or is behaving differently than before on Android.
|
- Android target-sdk has been updated to 33 (Android 13). Please holler if anything seems broken or is behaving differently than before on Android.
|
||||||
|
|||||||
@ -40,6 +40,7 @@
|
|||||||
"ba_data/python/ba/__pycache__/_language.cpython-310.opt-1.pyc",
|
"ba_data/python/ba/__pycache__/_language.cpython-310.opt-1.pyc",
|
||||||
"ba_data/python/ba/__pycache__/_level.cpython-310.opt-1.pyc",
|
"ba_data/python/ba/__pycache__/_level.cpython-310.opt-1.pyc",
|
||||||
"ba_data/python/ba/__pycache__/_lobby.cpython-310.opt-1.pyc",
|
"ba_data/python/ba/__pycache__/_lobby.cpython-310.opt-1.pyc",
|
||||||
|
"ba_data/python/ba/__pycache__/_login.cpython-310.opt-1.pyc",
|
||||||
"ba_data/python/ba/__pycache__/_map.cpython-310.opt-1.pyc",
|
"ba_data/python/ba/__pycache__/_map.cpython-310.opt-1.pyc",
|
||||||
"ba_data/python/ba/__pycache__/_math.cpython-310.opt-1.pyc",
|
"ba_data/python/ba/__pycache__/_math.cpython-310.opt-1.pyc",
|
||||||
"ba_data/python/ba/__pycache__/_messages.cpython-310.opt-1.pyc",
|
"ba_data/python/ba/__pycache__/_messages.cpython-310.opt-1.pyc",
|
||||||
@ -110,6 +111,7 @@
|
|||||||
"ba_data/python/ba/_language.py",
|
"ba_data/python/ba/_language.py",
|
||||||
"ba_data/python/ba/_level.py",
|
"ba_data/python/ba/_level.py",
|
||||||
"ba_data/python/ba/_lobby.py",
|
"ba_data/python/ba/_lobby.py",
|
||||||
|
"ba_data/python/ba/_login.py",
|
||||||
"ba_data/python/ba/_map.py",
|
"ba_data/python/ba/_map.py",
|
||||||
"ba_data/python/ba/_math.py",
|
"ba_data/python/ba/_math.py",
|
||||||
"ba_data/python/ba/_messages.py",
|
"ba_data/python/ba/_messages.py",
|
||||||
@ -147,6 +149,7 @@
|
|||||||
"ba_data/python/bacommon/__pycache__/bacloud.cpython-310.opt-1.pyc",
|
"ba_data/python/bacommon/__pycache__/bacloud.cpython-310.opt-1.pyc",
|
||||||
"ba_data/python/bacommon/__pycache__/build.cpython-310.opt-1.pyc",
|
"ba_data/python/bacommon/__pycache__/build.cpython-310.opt-1.pyc",
|
||||||
"ba_data/python/bacommon/__pycache__/cloud.cpython-310.opt-1.pyc",
|
"ba_data/python/bacommon/__pycache__/cloud.cpython-310.opt-1.pyc",
|
||||||
|
"ba_data/python/bacommon/__pycache__/login.cpython-310.opt-1.pyc",
|
||||||
"ba_data/python/bacommon/__pycache__/net.cpython-310.opt-1.pyc",
|
"ba_data/python/bacommon/__pycache__/net.cpython-310.opt-1.pyc",
|
||||||
"ba_data/python/bacommon/__pycache__/servermanager.cpython-310.opt-1.pyc",
|
"ba_data/python/bacommon/__pycache__/servermanager.cpython-310.opt-1.pyc",
|
||||||
"ba_data/python/bacommon/__pycache__/transfer.cpython-310.opt-1.pyc",
|
"ba_data/python/bacommon/__pycache__/transfer.cpython-310.opt-1.pyc",
|
||||||
@ -154,6 +157,7 @@
|
|||||||
"ba_data/python/bacommon/bacloud.py",
|
"ba_data/python/bacommon/bacloud.py",
|
||||||
"ba_data/python/bacommon/build.py",
|
"ba_data/python/bacommon/build.py",
|
||||||
"ba_data/python/bacommon/cloud.py",
|
"ba_data/python/bacommon/cloud.py",
|
||||||
|
"ba_data/python/bacommon/login.py",
|
||||||
"ba_data/python/bacommon/net.py",
|
"ba_data/python/bacommon/net.py",
|
||||||
"ba_data/python/bacommon/servermanager.py",
|
"ba_data/python/bacommon/servermanager.py",
|
||||||
"ba_data/python/bacommon/transfer.py",
|
"ba_data/python/bacommon/transfer.py",
|
||||||
@ -358,12 +362,12 @@
|
|||||||
"ba_data/python/bastd/ui/account/__pycache__/link.cpython-310.opt-1.pyc",
|
"ba_data/python/bastd/ui/account/__pycache__/link.cpython-310.opt-1.pyc",
|
||||||
"ba_data/python/bastd/ui/account/__pycache__/settings.cpython-310.opt-1.pyc",
|
"ba_data/python/bastd/ui/account/__pycache__/settings.cpython-310.opt-1.pyc",
|
||||||
"ba_data/python/bastd/ui/account/__pycache__/unlink.cpython-310.opt-1.pyc",
|
"ba_data/python/bastd/ui/account/__pycache__/unlink.cpython-310.opt-1.pyc",
|
||||||
"ba_data/python/bastd/ui/account/__pycache__/v2.cpython-310.opt-1.pyc",
|
"ba_data/python/bastd/ui/account/__pycache__/v2proxy.cpython-310.opt-1.pyc",
|
||||||
"ba_data/python/bastd/ui/account/__pycache__/viewer.cpython-310.opt-1.pyc",
|
"ba_data/python/bastd/ui/account/__pycache__/viewer.cpython-310.opt-1.pyc",
|
||||||
"ba_data/python/bastd/ui/account/link.py",
|
"ba_data/python/bastd/ui/account/link.py",
|
||||||
"ba_data/python/bastd/ui/account/settings.py",
|
"ba_data/python/bastd/ui/account/settings.py",
|
||||||
"ba_data/python/bastd/ui/account/unlink.py",
|
"ba_data/python/bastd/ui/account/unlink.py",
|
||||||
"ba_data/python/bastd/ui/account/v2.py",
|
"ba_data/python/bastd/ui/account/v2proxy.py",
|
||||||
"ba_data/python/bastd/ui/account/viewer.py",
|
"ba_data/python/bastd/ui/account/viewer.py",
|
||||||
"ba_data/python/bastd/ui/achievements.py",
|
"ba_data/python/bastd/ui/achievements.py",
|
||||||
"ba_data/python/bastd/ui/appinvite.py",
|
"ba_data/python/bastd/ui/appinvite.py",
|
||||||
|
|||||||
@ -174,6 +174,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
|
|||||||
build/ba_data/python/ba/_language.py \
|
build/ba_data/python/ba/_language.py \
|
||||||
build/ba_data/python/ba/_level.py \
|
build/ba_data/python/ba/_level.py \
|
||||||
build/ba_data/python/ba/_lobby.py \
|
build/ba_data/python/ba/_lobby.py \
|
||||||
|
build/ba_data/python/ba/_login.py \
|
||||||
build/ba_data/python/ba/_map.py \
|
build/ba_data/python/ba/_map.py \
|
||||||
build/ba_data/python/ba/_math.py \
|
build/ba_data/python/ba/_math.py \
|
||||||
build/ba_data/python/ba/_messages.py \
|
build/ba_data/python/ba/_messages.py \
|
||||||
@ -286,7 +287,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
|
|||||||
build/ba_data/python/bastd/ui/account/link.py \
|
build/ba_data/python/bastd/ui/account/link.py \
|
||||||
build/ba_data/python/bastd/ui/account/settings.py \
|
build/ba_data/python/bastd/ui/account/settings.py \
|
||||||
build/ba_data/python/bastd/ui/account/unlink.py \
|
build/ba_data/python/bastd/ui/account/unlink.py \
|
||||||
build/ba_data/python/bastd/ui/account/v2.py \
|
build/ba_data/python/bastd/ui/account/v2proxy.py \
|
||||||
build/ba_data/python/bastd/ui/account/viewer.py \
|
build/ba_data/python/bastd/ui/account/viewer.py \
|
||||||
build/ba_data/python/bastd/ui/achievements.py \
|
build/ba_data/python/bastd/ui/achievements.py \
|
||||||
build/ba_data/python/bastd/ui/appinvite.py \
|
build/ba_data/python/bastd/ui/appinvite.py \
|
||||||
@ -425,6 +426,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
|
|||||||
build/ba_data/python/ba/__pycache__/_language.cpython-310.opt-1.pyc \
|
build/ba_data/python/ba/__pycache__/_language.cpython-310.opt-1.pyc \
|
||||||
build/ba_data/python/ba/__pycache__/_level.cpython-310.opt-1.pyc \
|
build/ba_data/python/ba/__pycache__/_level.cpython-310.opt-1.pyc \
|
||||||
build/ba_data/python/ba/__pycache__/_lobby.cpython-310.opt-1.pyc \
|
build/ba_data/python/ba/__pycache__/_lobby.cpython-310.opt-1.pyc \
|
||||||
|
build/ba_data/python/ba/__pycache__/_login.cpython-310.opt-1.pyc \
|
||||||
build/ba_data/python/ba/__pycache__/_map.cpython-310.opt-1.pyc \
|
build/ba_data/python/ba/__pycache__/_map.cpython-310.opt-1.pyc \
|
||||||
build/ba_data/python/ba/__pycache__/_math.cpython-310.opt-1.pyc \
|
build/ba_data/python/ba/__pycache__/_math.cpython-310.opt-1.pyc \
|
||||||
build/ba_data/python/ba/__pycache__/_messages.cpython-310.opt-1.pyc \
|
build/ba_data/python/ba/__pycache__/_messages.cpython-310.opt-1.pyc \
|
||||||
@ -537,7 +539,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
|
|||||||
build/ba_data/python/bastd/ui/account/__pycache__/link.cpython-310.opt-1.pyc \
|
build/ba_data/python/bastd/ui/account/__pycache__/link.cpython-310.opt-1.pyc \
|
||||||
build/ba_data/python/bastd/ui/account/__pycache__/settings.cpython-310.opt-1.pyc \
|
build/ba_data/python/bastd/ui/account/__pycache__/settings.cpython-310.opt-1.pyc \
|
||||||
build/ba_data/python/bastd/ui/account/__pycache__/unlink.cpython-310.opt-1.pyc \
|
build/ba_data/python/bastd/ui/account/__pycache__/unlink.cpython-310.opt-1.pyc \
|
||||||
build/ba_data/python/bastd/ui/account/__pycache__/v2.cpython-310.opt-1.pyc \
|
build/ba_data/python/bastd/ui/account/__pycache__/v2proxy.cpython-310.opt-1.pyc \
|
||||||
build/ba_data/python/bastd/ui/account/__pycache__/viewer.cpython-310.opt-1.pyc \
|
build/ba_data/python/bastd/ui/account/__pycache__/viewer.cpython-310.opt-1.pyc \
|
||||||
build/ba_data/python/bastd/ui/__pycache__/achievements.cpython-310.opt-1.pyc \
|
build/ba_data/python/bastd/ui/__pycache__/achievements.cpython-310.opt-1.pyc \
|
||||||
build/ba_data/python/bastd/ui/__pycache__/appinvite.cpython-310.opt-1.pyc \
|
build/ba_data/python/bastd/ui/__pycache__/appinvite.cpython-310.opt-1.pyc \
|
||||||
@ -654,6 +656,7 @@ SCRIPT_TARGETS_PY_PUBLIC_TOOLS = \
|
|||||||
build/ba_data/python/bacommon/bacloud.py \
|
build/ba_data/python/bacommon/bacloud.py \
|
||||||
build/ba_data/python/bacommon/build.py \
|
build/ba_data/python/bacommon/build.py \
|
||||||
build/ba_data/python/bacommon/cloud.py \
|
build/ba_data/python/bacommon/cloud.py \
|
||||||
|
build/ba_data/python/bacommon/login.py \
|
||||||
build/ba_data/python/bacommon/net.py \
|
build/ba_data/python/bacommon/net.py \
|
||||||
build/ba_data/python/bacommon/servermanager.py \
|
build/ba_data/python/bacommon/servermanager.py \
|
||||||
build/ba_data/python/bacommon/transfer.py \
|
build/ba_data/python/bacommon/transfer.py \
|
||||||
@ -686,6 +689,7 @@ SCRIPT_TARGETS_PYC_PUBLIC_TOOLS = \
|
|||||||
build/ba_data/python/bacommon/__pycache__/bacloud.cpython-310.opt-1.pyc \
|
build/ba_data/python/bacommon/__pycache__/bacloud.cpython-310.opt-1.pyc \
|
||||||
build/ba_data/python/bacommon/__pycache__/build.cpython-310.opt-1.pyc \
|
build/ba_data/python/bacommon/__pycache__/build.cpython-310.opt-1.pyc \
|
||||||
build/ba_data/python/bacommon/__pycache__/cloud.cpython-310.opt-1.pyc \
|
build/ba_data/python/bacommon/__pycache__/cloud.cpython-310.opt-1.pyc \
|
||||||
|
build/ba_data/python/bacommon/__pycache__/login.cpython-310.opt-1.pyc \
|
||||||
build/ba_data/python/bacommon/__pycache__/net.cpython-310.opt-1.pyc \
|
build/ba_data/python/bacommon/__pycache__/net.cpython-310.opt-1.pyc \
|
||||||
build/ba_data/python/bacommon/__pycache__/servermanager.cpython-310.opt-1.pyc \
|
build/ba_data/python/bacommon/__pycache__/servermanager.cpython-310.opt-1.pyc \
|
||||||
build/ba_data/python/bacommon/__pycache__/transfer.cpython-310.opt-1.pyc \
|
build/ba_data/python/bacommon/__pycache__/transfer.cpython-310.opt-1.pyc \
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
144047512702782890975548019773691278470
|
90228160524159856290785480177185335343
|
||||||
@ -1227,16 +1227,6 @@ def android_get_external_files_dir() -> str:
|
|||||||
return str()
|
return str()
|
||||||
|
|
||||||
|
|
||||||
def android_media_scan_file(file_name: str) -> None:
|
|
||||||
|
|
||||||
"""(internal)
|
|
||||||
|
|
||||||
Refreshes Android MTP Index for a file; use this to get file
|
|
||||||
modifications to be reflected in Android File Transfer.
|
|
||||||
"""
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def android_show_wifi_settings() -> None:
|
def android_show_wifi_settings() -> None:
|
||||||
|
|
||||||
"""(internal)"""
|
"""(internal)"""
|
||||||
@ -2406,15 +2396,6 @@ def is_os_playing_music() -> bool:
|
|||||||
return bool()
|
return bool()
|
||||||
|
|
||||||
|
|
||||||
def is_ouya_build() -> bool:
|
|
||||||
|
|
||||||
"""(internal)
|
|
||||||
|
|
||||||
Returns whether we're running the ouya-specific version
|
|
||||||
"""
|
|
||||||
return bool()
|
|
||||||
|
|
||||||
|
|
||||||
def is_party_icon_visible() -> bool:
|
def is_party_icon_visible() -> bool:
|
||||||
|
|
||||||
"""(internal)"""
|
"""(internal)"""
|
||||||
@ -2448,6 +2429,12 @@ def lock_all_input() -> None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def login_adapter_get_sign_in_token(login_type: str, attempt_id: int) -> None:
|
||||||
|
|
||||||
|
"""(internal)"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def mac_music_app_get_library_source() -> None:
|
def mac_music_app_get_library_source() -> None:
|
||||||
|
|
||||||
"""(internal)"""
|
"""(internal)"""
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import _ba
|
import _ba
|
||||||
@ -11,6 +12,9 @@ import _ba
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from bacommon.login import LoginType
|
||||||
|
from ba._login import LoginAdapter
|
||||||
|
|
||||||
|
|
||||||
class AccountV2Subsystem:
|
class AccountV2Subsystem:
|
||||||
"""Subsystem for modern account handling in the app.
|
"""Subsystem for modern account handling in the app.
|
||||||
@ -21,6 +25,7 @@ class AccountV2Subsystem:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
from bacommon.login import LoginType
|
||||||
|
|
||||||
# Whether or not everything related to an initial login
|
# Whether or not everything related to an initial login
|
||||||
# (or lack thereof) has completed. This includes things like
|
# (or lack thereof) has completed. This includes things like
|
||||||
@ -30,9 +35,24 @@ class AccountV2Subsystem:
|
|||||||
|
|
||||||
self._kicked_off_workspace_load = False
|
self._kicked_off_workspace_load = False
|
||||||
|
|
||||||
|
self.login_adapters: dict[LoginType, LoginAdapter] = {}
|
||||||
|
|
||||||
|
self._implicit_signed_in_adapter: LoginAdapter | None = None
|
||||||
|
self._auto_signed_in = False
|
||||||
|
|
||||||
|
if _ba.app.platform == 'android' and _ba.app.subplatform == 'google':
|
||||||
|
from ba._login import LoginAdapterGPGS
|
||||||
|
|
||||||
|
self.login_adapters[LoginType.GPGS] = LoginAdapterGPGS(
|
||||||
|
LoginType.GPGS
|
||||||
|
)
|
||||||
|
|
||||||
def on_app_launch(self) -> None:
|
def on_app_launch(self) -> None:
|
||||||
"""Should be called at standard on_app_launch time."""
|
"""Should be called at standard on_app_launch time."""
|
||||||
|
|
||||||
|
for adapter in self.login_adapters.values():
|
||||||
|
adapter.on_app_launch()
|
||||||
|
|
||||||
def set_primary_credentials(self, credentials: str | None) -> None:
|
def set_primary_credentials(self, credentials: str | None) -> None:
|
||||||
"""Set credentials for the primary app account."""
|
"""Set credentials for the primary app account."""
|
||||||
raise RuntimeError('This should be overridden.')
|
raise RuntimeError('This should be overridden.')
|
||||||
@ -49,7 +69,7 @@ class AccountV2Subsystem:
|
|||||||
@property
|
@property
|
||||||
def primary(self) -> AccountV2Handle | None:
|
def primary(self) -> AccountV2Handle | None:
|
||||||
"""The primary account for the app, or None if not logged in."""
|
"""The primary account for the app, or None if not logged in."""
|
||||||
return None
|
return self.do_get_primary()
|
||||||
|
|
||||||
def do_get_primary(self) -> AccountV2Handle | None:
|
def do_get_primary(self) -> AccountV2Handle | None:
|
||||||
"""Internal - should be overridden by subclass."""
|
"""Internal - should be overridden by subclass."""
|
||||||
@ -60,9 +80,11 @@ class AccountV2Subsystem:
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Callback run after the primary account changes.
|
"""Callback run after the primary account changes.
|
||||||
|
|
||||||
Will be called with None on log-outs or when new credentials
|
Will be called with None on log-outs and when new credentials
|
||||||
are set but have not yet been verified.
|
are set but have not yet been verified.
|
||||||
"""
|
"""
|
||||||
|
assert _ba.in_logic_thread()
|
||||||
|
|
||||||
# Currently don't do anything special on sign-outs.
|
# Currently don't do anything special on sign-outs.
|
||||||
if account is None:
|
if account is None:
|
||||||
return
|
return
|
||||||
@ -99,6 +121,30 @@ class AccountV2Subsystem:
|
|||||||
self._initial_login_completed = True
|
self._initial_login_completed = True
|
||||||
_ba.app.on_initial_login_completed()
|
_ba.app.on_initial_login_completed()
|
||||||
|
|
||||||
|
def on_active_logins_changed(self, logins: dict[LoginType, str]) -> None:
|
||||||
|
"""Should be called when logins for the active account change."""
|
||||||
|
|
||||||
|
for adapter in self.login_adapters.values():
|
||||||
|
adapter.set_active_logins(logins)
|
||||||
|
|
||||||
|
def on_implicit_login(
|
||||||
|
self, login_type: LoginType, login_id: str, display_name: str
|
||||||
|
) -> None:
|
||||||
|
"""An implicit login happened."""
|
||||||
|
from ba._login import LoginAdapter
|
||||||
|
|
||||||
|
with _ba.Context('ui'):
|
||||||
|
self.login_adapters[login_type].set_implicit_login_state(
|
||||||
|
LoginAdapter.ImplicitLoginState(
|
||||||
|
login_id=login_id, display_name=display_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_implicit_logout(self, login_type: LoginType) -> None:
|
||||||
|
"""An implicit logout happened."""
|
||||||
|
with _ba.Context('ui'):
|
||||||
|
self.login_adapters[login_type].set_implicit_login_state(None)
|
||||||
|
|
||||||
def on_no_initial_primary_account(self) -> None:
|
def on_no_initial_primary_account(self) -> None:
|
||||||
"""Callback run if the app has no primary account after launch.
|
"""Callback run if the app has no primary account after launch.
|
||||||
|
|
||||||
@ -110,6 +156,63 @@ class AccountV2Subsystem:
|
|||||||
self._initial_login_completed = True
|
self._initial_login_completed = True
|
||||||
_ba.app.on_initial_login_completed()
|
_ba.app.on_initial_login_completed()
|
||||||
|
|
||||||
|
def on_implicit_login_state_changed(
|
||||||
|
self,
|
||||||
|
login_type: LoginType,
|
||||||
|
state: LoginAdapter.ImplicitLoginState | None,
|
||||||
|
) -> None:
|
||||||
|
"""Called when implicit login state changes.
|
||||||
|
|
||||||
|
Logins that tend to sign themselves in/out in the background are
|
||||||
|
considered implicit. We may choose to honor or ignore their states,
|
||||||
|
allowing the user to opt for other login types even if the default
|
||||||
|
implicit one can't be explicitly logged out or otherwise controlled.
|
||||||
|
"""
|
||||||
|
assert _ba.in_logic_thread()
|
||||||
|
|
||||||
|
# Store which (if any) adapter is currently implicitly signed in.
|
||||||
|
if state is None:
|
||||||
|
self._implicit_signed_in_adapter = None
|
||||||
|
else:
|
||||||
|
self._implicit_signed_in_adapter = self.login_adapters[login_type]
|
||||||
|
|
||||||
|
# We may want to auto-sign-in based on this new state.
|
||||||
|
self._update_auto_sign_in()
|
||||||
|
|
||||||
|
def on_cloud_connectivity_changed(self, connected: bool) -> None:
|
||||||
|
"""Should be called with cloud connectivity changes."""
|
||||||
|
del connected # Unused.
|
||||||
|
assert _ba.in_logic_thread()
|
||||||
|
|
||||||
|
# We may want to auto-sign-in based on this new state.
|
||||||
|
self._update_auto_sign_in()
|
||||||
|
|
||||||
|
def _update_auto_sign_in(self) -> None:
|
||||||
|
from ba._internal import get_v1_account_state
|
||||||
|
|
||||||
|
# We attempt auto-sign-in only once.
|
||||||
|
if self._auto_signed_in:
|
||||||
|
return
|
||||||
|
|
||||||
|
# If we're not currently signed in, we have connectivity, and
|
||||||
|
# we have an available implicit adapter, do an auto-sign-in.
|
||||||
|
connected = _ba.app.cloud.is_connected()
|
||||||
|
signed_in_v1 = get_v1_account_state() == 'signed_in'
|
||||||
|
signed_in_v2 = _ba.app.accounts_v2.have_primary_credentials()
|
||||||
|
if (
|
||||||
|
connected
|
||||||
|
and not signed_in_v1
|
||||||
|
and not signed_in_v2
|
||||||
|
and self._implicit_signed_in_adapter is not None
|
||||||
|
):
|
||||||
|
self._auto_signed_in = True
|
||||||
|
self._implicit_signed_in_adapter.sign_in(self._on_sign_in_completed)
|
||||||
|
|
||||||
|
def _on_sign_in_completed(
|
||||||
|
self, result: LoginAdapter.SignInResult | Exception
|
||||||
|
) -> None:
|
||||||
|
logging.debug('GOT SIGN-IN COMPLETED WITH %s', result)
|
||||||
|
|
||||||
def _on_set_active_workspace_completed(self) -> None:
|
def _on_set_active_workspace_completed(self) -> None:
|
||||||
if not self._initial_login_completed:
|
if not self._initial_login_completed:
|
||||||
self._initial_login_completed = True
|
self._initial_login_completed = True
|
||||||
@ -129,8 +232,17 @@ class AccountV2Handle:
|
|||||||
self.workspacename: str | None = None
|
self.workspacename: str | None = None
|
||||||
self.workspaceid: str | None = None
|
self.workspaceid: str | None = None
|
||||||
|
|
||||||
|
# Login types and their display-names associated with this account.
|
||||||
|
self.logins: dict[LoginType, str] = {}
|
||||||
|
|
||||||
def __enter__(self) -> None:
|
def __enter__(self) -> None:
|
||||||
"""Support for "with" statement."""
|
"""Support for "with" statement.
|
||||||
|
|
||||||
|
This allows cloud messages to be sent on our behalf.
|
||||||
|
"""
|
||||||
|
|
||||||
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> Any:
|
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> Any:
|
||||||
"""Support for "with" statement."""
|
"""Support for "with" statement.
|
||||||
|
|
||||||
|
This allows cloud messages to be sent on our behalf.
|
||||||
|
"""
|
||||||
|
|||||||
@ -47,7 +47,7 @@ def bootstrap() -> None:
|
|||||||
|
|
||||||
# Give a soft warning if we're being used with a different binary
|
# Give a soft warning if we're being used with a different binary
|
||||||
# version than we expect.
|
# version than we expect.
|
||||||
expected_build = 20921
|
expected_build = 20928
|
||||||
running_build: int = env['build_number']
|
running_build: int = env['build_number']
|
||||||
if running_build != expected_build:
|
if running_build != expected_build:
|
||||||
print(
|
print(
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
from typing import TYPE_CHECKING, overload
|
from typing import TYPE_CHECKING, overload
|
||||||
|
|
||||||
import _ba
|
import _ba
|
||||||
@ -14,6 +15,8 @@ if TYPE_CHECKING:
|
|||||||
from efro.message import Message, Response
|
from efro.message import Message, Response
|
||||||
import bacommon.cloud
|
import bacommon.cloud
|
||||||
|
|
||||||
|
DEBUG_LOG = False
|
||||||
|
|
||||||
# TODO: Should make it possible to define a protocol in bacommon.cloud and
|
# TODO: Should make it possible to define a protocol in bacommon.cloud and
|
||||||
# autogenerate this. That would give us type safety between this and
|
# autogenerate this. That would give us type safety between this and
|
||||||
# internal protocols.
|
# internal protocols.
|
||||||
@ -30,6 +33,15 @@ class CloudSubsystem:
|
|||||||
"""
|
"""
|
||||||
return False # Needs to be overridden
|
return False # Needs to be overridden
|
||||||
|
|
||||||
|
def on_connectivity_changed(self, connected: bool) -> None:
|
||||||
|
"""Called when cloud connectivity state changes."""
|
||||||
|
if DEBUG_LOG:
|
||||||
|
logging.debug('CloudSubsystem: Connectivity is now %s.', connected)
|
||||||
|
|
||||||
|
# Inform things that use this.
|
||||||
|
# (TODO: should generalize this into some sort of registration system)
|
||||||
|
_ba.app.accounts_v2.on_cloud_connectivity_changed(connected)
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def send_message_cb(
|
def send_message_cb(
|
||||||
self,
|
self,
|
||||||
@ -66,6 +78,26 @@ class CloudSubsystem:
|
|||||||
) -> None:
|
) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def send_message_cb(
|
||||||
|
self,
|
||||||
|
msg: bacommon.cloud.SignInMessage,
|
||||||
|
on_response: Callable[
|
||||||
|
[bacommon.cloud.SignInResponse | Exception], None
|
||||||
|
],
|
||||||
|
) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def send_message_cb(
|
||||||
|
self,
|
||||||
|
msg: bacommon.cloud.ManageAccountMessage,
|
||||||
|
on_response: Callable[
|
||||||
|
[bacommon.cloud.ManageAccountResponse | Exception], None
|
||||||
|
],
|
||||||
|
) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
def send_message_cb(
|
def send_message_cb(
|
||||||
self,
|
self,
|
||||||
msg: Message,
|
msg: Message,
|
||||||
@ -110,7 +142,6 @@ class CloudSubsystem:
|
|||||||
def cloud_console_exec(code: str) -> None:
|
def cloud_console_exec(code: str) -> None:
|
||||||
"""Called by the cloud console to run code in the logic thread."""
|
"""Called by the cloud console to run code in the logic thread."""
|
||||||
import sys
|
import sys
|
||||||
import logging
|
|
||||||
import __main__
|
import __main__
|
||||||
from ba._generated.enums import TimeType
|
from ba._generated.enums import TimeType
|
||||||
|
|
||||||
|
|||||||
@ -278,7 +278,7 @@ def show_damage_count(
|
|||||||
|
|
||||||
|
|
||||||
def timestring(
|
def timestring(
|
||||||
timeval: float,
|
timeval: float | int,
|
||||||
centi: bool = True,
|
centi: bool = True,
|
||||||
timeformat: ba.TimeFormat = TimeFormat.SECONDS,
|
timeformat: ba.TimeFormat = TimeFormat.SECONDS,
|
||||||
suppress_format_warning: bool = False,
|
suppress_format_warning: bool = False,
|
||||||
|
|||||||
@ -247,6 +247,14 @@ def google_play_purchases_not_available_message() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def google_play_services_not_available_message() -> None:
|
||||||
|
from ba._language import Lstr
|
||||||
|
|
||||||
|
_ba.screenmessage(
|
||||||
|
Lstr(resource='googlePlayServicesNotAvailableText'), color=(1, 0, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def empty_call() -> None:
|
def empty_call() -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -424,3 +432,39 @@ def hash_strings(inputs: list[str]) -> str:
|
|||||||
def have_account_v2_credentials() -> bool:
|
def have_account_v2_credentials() -> bool:
|
||||||
"""Do we have primary account-v2 credentials set?"""
|
"""Do we have primary account-v2 credentials set?"""
|
||||||
return _ba.app.accounts_v2.have_primary_credentials()
|
return _ba.app.accounts_v2.have_primary_credentials()
|
||||||
|
|
||||||
|
|
||||||
|
def implicit_login(
|
||||||
|
login_type_str: str, login_id: str, display_name: str
|
||||||
|
) -> None:
|
||||||
|
"""An implicit login happened."""
|
||||||
|
from bacommon.login import LoginType
|
||||||
|
|
||||||
|
_ba.app.accounts_v2.on_implicit_login(
|
||||||
|
login_type=LoginType(login_type_str),
|
||||||
|
login_id=login_id,
|
||||||
|
display_name=display_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def implicit_logout(login_type_str: str) -> None:
|
||||||
|
"""An implicit logout happened."""
|
||||||
|
from bacommon.login import LoginType
|
||||||
|
|
||||||
|
_ba.app.accounts_v2.on_implicit_logout(login_type=LoginType(login_type_str))
|
||||||
|
|
||||||
|
|
||||||
|
def login_adapter_get_sign_in_token_response(
|
||||||
|
login_type_str: str, attempt_id_str: str, result_str: str
|
||||||
|
) -> None:
|
||||||
|
"""Login adapter do-sign-in completed."""
|
||||||
|
from bacommon.login import LoginType
|
||||||
|
from ba._login import LoginAdapterGPGS
|
||||||
|
|
||||||
|
login_type = LoginType(login_type_str)
|
||||||
|
attempt_id = int(attempt_id_str)
|
||||||
|
result = None if result_str == '' else result_str
|
||||||
|
with _ba.Context('ui'):
|
||||||
|
adapter = _ba.app.accounts_v2.login_adapters[login_type]
|
||||||
|
assert isinstance(adapter, LoginAdapterGPGS)
|
||||||
|
adapter.on_sign_in_complete(attempt_id=attempt_id, result=result)
|
||||||
|
|||||||
@ -321,7 +321,9 @@ def get_v1_account_state() -> str:
|
|||||||
"""(internal)"""
|
"""(internal)"""
|
||||||
if HAVE_INTERNAL:
|
if HAVE_INTERNAL:
|
||||||
return _bainternal.get_v1_account_state()
|
return _bainternal.get_v1_account_state()
|
||||||
raise _no_bainternal_error()
|
|
||||||
|
# Without internal present just consider ourself always signed out.
|
||||||
|
return 'signed_out'
|
||||||
|
|
||||||
|
|
||||||
def get_v1_account_display_string(full: bool = True) -> str:
|
def get_v1_account_display_string(full: bool = True) -> str:
|
||||||
|
|||||||
298
assets/src/ba_data/python/ba/_login.py
Normal file
298
assets/src/ba_data/python/ba/_login.py
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
# Released under the MIT License. See LICENSE for details.
|
||||||
|
#
|
||||||
|
"""Login related functionality."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING, final
|
||||||
|
|
||||||
|
import _ba
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
from bacommon.login import LoginType
|
||||||
|
|
||||||
|
DEBUG_LOG = True
|
||||||
|
|
||||||
|
|
||||||
|
class LoginAdapter:
|
||||||
|
"""Allows using implicit login types in an explicit way.
|
||||||
|
|
||||||
|
Some login types such as Google Play Game Services or Game Center are
|
||||||
|
basically always present and often do not provide a way to log out
|
||||||
|
from within a running app, so this adapter exists to use them in a
|
||||||
|
flexible manner by 'attaching' and 'detaching' from an always-present
|
||||||
|
login, allowing for its use alongside other login types. It also
|
||||||
|
provides common functionality for server-side account verification and
|
||||||
|
other handy bits.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SignInResult:
|
||||||
|
"""Describes the final result of a sign-in attempt."""
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ImplicitLoginState:
|
||||||
|
"""Describes the current state of an implicit login."""
|
||||||
|
|
||||||
|
login_id: str
|
||||||
|
display_name: str
|
||||||
|
|
||||||
|
def __init__(self, login_type: LoginType):
|
||||||
|
assert _ba.in_logic_thread()
|
||||||
|
self.login_type = login_type
|
||||||
|
self._implicit_login_state: LoginAdapter.ImplicitLoginState | None = (
|
||||||
|
None
|
||||||
|
)
|
||||||
|
self._on_app_launch_called = False
|
||||||
|
self._implicit_login_state_dirty = False
|
||||||
|
self._back_end_active = False
|
||||||
|
|
||||||
|
# Which login of our type (if any) is associated with the
|
||||||
|
# current active primary account.
|
||||||
|
self._active_login_id: str | None = None
|
||||||
|
|
||||||
|
def on_app_launch(self) -> None:
|
||||||
|
"""Should be called for each adapter in on_app_launch."""
|
||||||
|
|
||||||
|
assert not self._on_app_launch_called
|
||||||
|
self._on_app_launch_called = True
|
||||||
|
|
||||||
|
# Any implicit state we received up until now needs to be pushed
|
||||||
|
# to the app account subsystem.
|
||||||
|
self._update_implicit_login_state()
|
||||||
|
|
||||||
|
def set_implicit_login_state(
|
||||||
|
self, state: ImplicitLoginState | None
|
||||||
|
) -> None:
|
||||||
|
"""Keep the adapter informed of implicit login states.
|
||||||
|
|
||||||
|
This should be called by the adapter back-end when an account
|
||||||
|
of their associated type gets logged in or out.
|
||||||
|
"""
|
||||||
|
assert _ba.in_logic_thread()
|
||||||
|
|
||||||
|
# Ignore redundant sets.
|
||||||
|
if state == self._implicit_login_state:
|
||||||
|
return
|
||||||
|
|
||||||
|
if DEBUG_LOG:
|
||||||
|
if state is None:
|
||||||
|
logging.debug(
|
||||||
|
'LoginAdapter: %s implicit state changed;'
|
||||||
|
' now signed out.',
|
||||||
|
self.login_type.name,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logging.debug(
|
||||||
|
'LoginAdapter: %s implicit state changed;'
|
||||||
|
' now signed in as %s.',
|
||||||
|
self.login_type.name,
|
||||||
|
state.display_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._implicit_login_state = state
|
||||||
|
self._implicit_login_state_dirty = True
|
||||||
|
|
||||||
|
# (possibly) push it to the app for handling.
|
||||||
|
self._update_implicit_login_state()
|
||||||
|
|
||||||
|
def set_active_logins(self, logins: dict[LoginType, str]) -> None:
|
||||||
|
"""Keep the adapter informed of actively used logins.
|
||||||
|
|
||||||
|
This should be called by the app's account subsystem to
|
||||||
|
keep adapters up to date on the full set of logins attached
|
||||||
|
to the currently-in-use account.
|
||||||
|
Note that the logins dict passed in should be immutable as
|
||||||
|
only a reference to it is stored, not a copy.
|
||||||
|
"""
|
||||||
|
assert _ba.in_logic_thread()
|
||||||
|
if DEBUG_LOG:
|
||||||
|
logging.debug(
|
||||||
|
'LoginAdapter: %s adapter got active logins %s.',
|
||||||
|
self.login_type.name,
|
||||||
|
logins,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._active_login_id = logins.get(self.login_type)
|
||||||
|
self._update_back_end_active()
|
||||||
|
|
||||||
|
def on_back_end_active_change(self, active: bool) -> None:
|
||||||
|
"""Called when active state for the back-end is (possibly) changing.
|
||||||
|
|
||||||
|
Meant to be overridden by subclasses.
|
||||||
|
Being active means that the implicit login provided by the back-end
|
||||||
|
is actually being used by the app. It should therefore register
|
||||||
|
unlocked achievements, leaderboard scores, allow viewing native
|
||||||
|
UIs, etc. When not active it should ignore everything and behave
|
||||||
|
as if logged out, even if it technically is still logged in.
|
||||||
|
"""
|
||||||
|
assert _ba.in_logic_thread()
|
||||||
|
del active # Unused.
|
||||||
|
|
||||||
|
@final
|
||||||
|
def sign_in(
|
||||||
|
self, result_cb: Callable[[SignInResult | Exception], None]
|
||||||
|
) -> None:
|
||||||
|
"""Attempt an explicit sign in via this adapter.
|
||||||
|
|
||||||
|
This can be called even if the back-end is not implicitly signed in;
|
||||||
|
the adapter will attempt to sign in if possible. An exception will
|
||||||
|
be returned if the sign-in attempt fails.
|
||||||
|
"""
|
||||||
|
assert _ba.in_logic_thread()
|
||||||
|
from ba._general import Call
|
||||||
|
|
||||||
|
del result_cb # Unused.
|
||||||
|
|
||||||
|
if DEBUG_LOG:
|
||||||
|
logging.debug(
|
||||||
|
'LoginAdapter: %s adapter sign_in() called;'
|
||||||
|
' fetching sign-in-token...',
|
||||||
|
self.login_type.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _got_sign_in_token_result(result: str | None) -> None:
|
||||||
|
import bacommon.cloud
|
||||||
|
|
||||||
|
# Failed to get a sign-in-token.
|
||||||
|
if result is None:
|
||||||
|
if DEBUG_LOG:
|
||||||
|
logging.debug(
|
||||||
|
'LoginAdapter: %s adapter sign-in-token fetch failed;'
|
||||||
|
' aborting sign-in.',
|
||||||
|
self.login_type.name,
|
||||||
|
)
|
||||||
|
_ba.pushcall(
|
||||||
|
Call(result_cb, RuntimeError('fetch-sign-in-token failed'))
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Got a sign-in token! Now pass it to the cloud which will use
|
||||||
|
# it to verify our identity and give us app credentials on
|
||||||
|
# success.
|
||||||
|
if DEBUG_LOG:
|
||||||
|
logging.debug(
|
||||||
|
'LoginAdapter: %s adapter sign-in-token fetch succeeded;'
|
||||||
|
' passing to cloud for verification...',
|
||||||
|
self.login_type.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _got_sign_in_response(
|
||||||
|
response: bacommon.cloud.SignInResponse | Exception,
|
||||||
|
) -> None:
|
||||||
|
from ba._language import Lstr
|
||||||
|
|
||||||
|
if DEBUG_LOG:
|
||||||
|
logging.debug(
|
||||||
|
'LoginAdapter: %s adapter got sign-in response %s...',
|
||||||
|
self.login_type.name,
|
||||||
|
response,
|
||||||
|
)
|
||||||
|
if isinstance(response, Exception):
|
||||||
|
_ba.screenmessage(
|
||||||
|
Lstr(resource='errorText'), color=(1, 0, 0)
|
||||||
|
)
|
||||||
|
_ba.playsound(_ba.getsound('error'))
|
||||||
|
else:
|
||||||
|
_ba.app.accounts_v2.set_primary_credentials(
|
||||||
|
response.credentials
|
||||||
|
)
|
||||||
|
|
||||||
|
_ba.app.cloud.send_message_cb(
|
||||||
|
bacommon.cloud.SignInMessage(self.login_type, result),
|
||||||
|
on_response=_got_sign_in_response,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.get_sign_in_token(completion_cb=_got_sign_in_token_result)
|
||||||
|
|
||||||
|
def get_sign_in_token(
|
||||||
|
self, completion_cb: Callable[[str | None], None]
|
||||||
|
) -> None:
|
||||||
|
"""Get a sign-in token from the adapter back end.
|
||||||
|
|
||||||
|
This token is then passed to the master-server to complete the
|
||||||
|
login process.
|
||||||
|
The adapter can use this opportunity to bring up account creation
|
||||||
|
UI, call its internal sign_in function, etc. as needed.
|
||||||
|
The provided completion_cb should then be called with either a token
|
||||||
|
or None if sign in failed or was cancelled.
|
||||||
|
"""
|
||||||
|
from ba._general import Call
|
||||||
|
|
||||||
|
# Default implementation simply fails immediately.
|
||||||
|
_ba.pushcall(Call(completion_cb, None))
|
||||||
|
|
||||||
|
def _update_implicit_login_state(self) -> None:
|
||||||
|
# If we've received an implicit login state, schedule it to be
|
||||||
|
# sent along to the app. We wait until on-app-launch has been
|
||||||
|
# called so that account-client-v2 has had a chance to load
|
||||||
|
# any existing state so it can properly respond to this.
|
||||||
|
if self._implicit_login_state_dirty and self._on_app_launch_called:
|
||||||
|
from ba._general import Call
|
||||||
|
|
||||||
|
if DEBUG_LOG:
|
||||||
|
logging.debug(
|
||||||
|
'LoginAdapter: %s adapter sending'
|
||||||
|
' implicit-state-changed to app.',
|
||||||
|
self.login_type.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
_ba.pushcall(
|
||||||
|
Call(
|
||||||
|
_ba.app.accounts_v2.on_implicit_login_state_changed,
|
||||||
|
self.login_type,
|
||||||
|
self._implicit_login_state,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self._implicit_login_state_dirty = False
|
||||||
|
|
||||||
|
def _update_back_end_active(self) -> None:
|
||||||
|
was_active = self._back_end_active
|
||||||
|
if self._implicit_login_state is None:
|
||||||
|
is_active = False
|
||||||
|
else:
|
||||||
|
is_active = (
|
||||||
|
self._implicit_login_state.login_id == self._active_login_id
|
||||||
|
)
|
||||||
|
if was_active != is_active:
|
||||||
|
if DEBUG_LOG:
|
||||||
|
logging.debug(
|
||||||
|
'LoginAdapter: %s adapter back-end-active is now %s.',
|
||||||
|
self.login_type.name,
|
||||||
|
is_active,
|
||||||
|
)
|
||||||
|
self.on_back_end_active_change(is_active)
|
||||||
|
self._back_end_active = is_active
|
||||||
|
|
||||||
|
|
||||||
|
class LoginAdapterGPGS(LoginAdapter):
|
||||||
|
"""Google Play Game Services adapter."""
|
||||||
|
|
||||||
|
def __init__(self, login_type: LoginType):
|
||||||
|
super().__init__(login_type)
|
||||||
|
|
||||||
|
# Store int ids for in-flight attempts since they may go through
|
||||||
|
# various platform layers and back.
|
||||||
|
self._sign_in_attempt_num = 123
|
||||||
|
self._sign_in_attempts: dict[int, Callable[[str | None], None]] = {}
|
||||||
|
|
||||||
|
def get_sign_in_token(
|
||||||
|
self, completion_cb: Callable[[str | None], None]
|
||||||
|
) -> None:
|
||||||
|
attempt_id = self._sign_in_attempt_num
|
||||||
|
self._sign_in_attempts[attempt_id] = completion_cb
|
||||||
|
self._sign_in_attempt_num += 1
|
||||||
|
_ba.login_adapter_get_sign_in_token(self.login_type.value, attempt_id)
|
||||||
|
|
||||||
|
def on_sign_in_complete(self, attempt_id: int, result: str | None) -> None:
|
||||||
|
"""Called by the native layer on a completed attempt."""
|
||||||
|
assert _ba.in_logic_thread()
|
||||||
|
if attempt_id not in self._sign_in_attempts:
|
||||||
|
logging.exception('sign-in attempt_id %d not found', attempt_id)
|
||||||
|
return
|
||||||
|
callback = self._sign_in_attempts.pop(attempt_id)
|
||||||
|
callback(result)
|
||||||
@ -80,6 +80,7 @@ from _ba import (
|
|||||||
get_replays_dir,
|
get_replays_dir,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ba._login import LoginAdapter
|
||||||
from ba._map import (
|
from ba._map import (
|
||||||
get_map_class,
|
get_map_class,
|
||||||
register_map,
|
register_map,
|
||||||
@ -178,6 +179,7 @@ from ba._internal import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
'LoginAdapter',
|
||||||
'show_online_score_ui',
|
'show_online_score_ui',
|
||||||
'set_ui_input_device',
|
'set_ui_input_device',
|
||||||
'is_party_icon_visible',
|
'is_party_icon_visible',
|
||||||
@ -247,7 +249,6 @@ __all__ = [
|
|||||||
'set_telnet_access_enabled',
|
'set_telnet_access_enabled',
|
||||||
'new_replay_session',
|
'new_replay_session',
|
||||||
'get_replays_dir',
|
'get_replays_dir',
|
||||||
# DIVIDER
|
|
||||||
'get_unowned_maps',
|
'get_unowned_maps',
|
||||||
'get_unowned_game_types',
|
'get_unowned_game_types',
|
||||||
'get_map_class',
|
'get_map_class',
|
||||||
|
|||||||
@ -87,7 +87,7 @@ def show_user_scripts() -> None:
|
|||||||
' See settings/advanced'
|
' See settings/advanced'
|
||||||
' in the game for more info.'
|
' in the game for more info.'
|
||||||
)
|
)
|
||||||
_ba.android_media_scan_file(file_name)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
from ba import _error
|
from ba import _error
|
||||||
|
|
||||||
|
|||||||
@ -6,13 +6,16 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
import logging
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import bacommon.cloud
|
||||||
|
from bacommon.login import LoginType
|
||||||
import ba
|
import ba
|
||||||
import ba.internal
|
import ba.internal
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
pass
|
from ba.internal import LoginAdapter
|
||||||
|
|
||||||
|
|
||||||
class AccountSettingsWindow(ba.Window):
|
class AccountSettingsWindow(ba.Window):
|
||||||
@ -27,7 +30,7 @@ class AccountSettingsWindow(ba.Window):
|
|||||||
):
|
):
|
||||||
# pylint: disable=too-many-statements
|
# pylint: disable=too-many-statements
|
||||||
|
|
||||||
self._sign_in_v2_button: ba.Widget | None = None
|
self._sign_in_v2_proxy_button: ba.Widget | None = None
|
||||||
self._sign_in_device_button: ba.Widget | None = None
|
self._sign_in_device_button: ba.Widget | None = None
|
||||||
|
|
||||||
self._close_once_signed_in = close_once_signed_in
|
self._close_once_signed_in = close_once_signed_in
|
||||||
@ -92,16 +95,15 @@ class AccountSettingsWindow(ba.Window):
|
|||||||
# Determine which sign-in/sign-out buttons we should show.
|
# Determine which sign-in/sign-out buttons we should show.
|
||||||
self._show_sign_in_buttons: list[str] = []
|
self._show_sign_in_buttons: list[str] = []
|
||||||
|
|
||||||
if app.platform == 'android' and app.subplatform == 'google':
|
if LoginType.GPGS in ba.app.accounts_v2.login_adapters:
|
||||||
self._show_sign_in_buttons.append('Google Play')
|
self._show_sign_in_buttons.append('Google Play')
|
||||||
|
|
||||||
# Local accounts are generally always available with a few key
|
# Always want to show our web-based v2 login option.
|
||||||
# exceptions.
|
self._show_sign_in_buttons.append('V2Proxy')
|
||||||
self._show_sign_in_buttons.append('Local')
|
|
||||||
|
|
||||||
# Ditto with shiny new V2 ones.
|
# Legacy v1 device accounts are currently always available
|
||||||
if bool(True):
|
# (though we need to start phasing them out at some point).
|
||||||
self._show_sign_in_buttons.append('V2')
|
self._show_sign_in_buttons.append('Local')
|
||||||
|
|
||||||
top_extra = 15 if uiscale is ba.UIScale.SMALL else 0
|
top_extra = 15 if uiscale is ba.UIScale.SMALL else 0
|
||||||
super().__init__(
|
super().__init__(
|
||||||
@ -255,8 +257,9 @@ class AccountSettingsWindow(ba.Window):
|
|||||||
account_state == 'signed_out'
|
account_state == 'signed_out'
|
||||||
and 'Local' in self._show_sign_in_buttons
|
and 'Local' in self._show_sign_in_buttons
|
||||||
)
|
)
|
||||||
show_v2_sign_in_button = (
|
show_v2_proxy_sign_in_button = (
|
||||||
account_state == 'signed_out' and 'V2' in self._show_sign_in_buttons
|
account_state == 'signed_out'
|
||||||
|
and 'V2Proxy' in self._show_sign_in_buttons
|
||||||
)
|
)
|
||||||
sign_in_button_space = 70.0
|
sign_in_button_space = 70.0
|
||||||
|
|
||||||
@ -275,9 +278,7 @@ class AccountSettingsWindow(ba.Window):
|
|||||||
|
|
||||||
show_achievements_button = self._signed_in and account_type in (
|
show_achievements_button = self._signed_in and account_type in (
|
||||||
'Google Play',
|
'Google Play',
|
||||||
'Alibaba',
|
|
||||||
'Local',
|
'Local',
|
||||||
'OUYA',
|
|
||||||
'V2',
|
'V2',
|
||||||
)
|
)
|
||||||
achievements_button_space = 60.0
|
achievements_button_space = 60.0
|
||||||
@ -299,9 +300,7 @@ class AccountSettingsWindow(ba.Window):
|
|||||||
show_reset_progress_button = False
|
show_reset_progress_button = False
|
||||||
reset_progress_button_space = 70.0
|
reset_progress_button_space = 70.0
|
||||||
|
|
||||||
show_manage_v2_account_button = (
|
show_manage_v2_account_button = self._signed_in and account_type == 'V2'
|
||||||
self._signed_in and account_type == 'V2' and bool(False)
|
|
||||||
) # Disabled for now.
|
|
||||||
manage_v2_account_button_space = 100.0
|
manage_v2_account_button_space = 100.0
|
||||||
|
|
||||||
show_player_profiles_button = self._signed_in
|
show_player_profiles_button = self._signed_in
|
||||||
@ -327,11 +326,11 @@ class AccountSettingsWindow(ba.Window):
|
|||||||
]
|
]
|
||||||
sign_out_button_space = 70.0
|
sign_out_button_space = 70.0
|
||||||
|
|
||||||
show_cancel_v2_sign_in_button = (
|
show_cancel_sign_in_button = (
|
||||||
account_state == 'signing_in'
|
account_state == 'signing_in'
|
||||||
and ba.app.accounts_v2.have_primary_credentials()
|
and ba.app.accounts_v2.have_primary_credentials()
|
||||||
)
|
)
|
||||||
cancel_v2_sign_in_button_space = 70.0
|
cancel_sign_in_button_space = 70.0
|
||||||
|
|
||||||
if self._subcontainer is not None:
|
if self._subcontainer is not None:
|
||||||
self._subcontainer.delete()
|
self._subcontainer.delete()
|
||||||
@ -346,7 +345,7 @@ class AccountSettingsWindow(ba.Window):
|
|||||||
self._sub_height += sign_in_button_space
|
self._sub_height += sign_in_button_space
|
||||||
if show_device_sign_in_button:
|
if show_device_sign_in_button:
|
||||||
self._sub_height += sign_in_button_space
|
self._sub_height += sign_in_button_space
|
||||||
if show_v2_sign_in_button:
|
if show_v2_proxy_sign_in_button:
|
||||||
self._sub_height += sign_in_button_space
|
self._sub_height += sign_in_button_space
|
||||||
if show_game_service_button:
|
if show_game_service_button:
|
||||||
self._sub_height += game_service_button_space
|
self._sub_height += game_service_button_space
|
||||||
@ -376,8 +375,8 @@ class AccountSettingsWindow(ba.Window):
|
|||||||
self._sub_height += unlink_accounts_button_space
|
self._sub_height += unlink_accounts_button_space
|
||||||
if show_sign_out_button:
|
if show_sign_out_button:
|
||||||
self._sub_height += sign_out_button_space
|
self._sub_height += sign_out_button_space
|
||||||
if show_cancel_v2_sign_in_button:
|
if show_cancel_sign_in_button:
|
||||||
self._sub_height += cancel_v2_sign_in_button_space
|
self._sub_height += cancel_sign_in_button_space
|
||||||
self._subcontainer = ba.containerwidget(
|
self._subcontainer = ba.containerwidget(
|
||||||
parent=self._scrollwidget,
|
parent=self._scrollwidget,
|
||||||
size=(self._sub_width, self._sub_height),
|
size=(self._sub_width, self._sub_height),
|
||||||
@ -528,7 +527,7 @@ class AccountSettingsWindow(ba.Window):
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
on_activate_call=lambda: self._sign_in_press('Google Play'),
|
on_activate_call=lambda: self._sign_in_press(LoginType.GPGS),
|
||||||
)
|
)
|
||||||
if first_selectable is None:
|
if first_selectable is None:
|
||||||
first_selectable = btn
|
first_selectable = btn
|
||||||
@ -541,16 +540,16 @@ class AccountSettingsWindow(ba.Window):
|
|||||||
ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
|
ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
|
||||||
self._sign_in_text = None
|
self._sign_in_text = None
|
||||||
|
|
||||||
if show_v2_sign_in_button:
|
if show_v2_proxy_sign_in_button:
|
||||||
button_width = 350
|
button_width = 350
|
||||||
v -= sign_in_button_space
|
v -= sign_in_button_space
|
||||||
self._sign_in_v2_button = btn = ba.buttonwidget(
|
self._sign_in_v2_proxy_button = btn = ba.buttonwidget(
|
||||||
parent=self._subcontainer,
|
parent=self._subcontainer,
|
||||||
position=((self._sub_width - button_width) * 0.5, v - 20),
|
position=((self._sub_width - button_width) * 0.5, v - 20),
|
||||||
autoselect=True,
|
autoselect=True,
|
||||||
size=(button_width, 60),
|
size=(button_width, 60),
|
||||||
label='',
|
label='',
|
||||||
on_activate_call=self._v2_sign_in_press,
|
on_activate_call=self._v2_proxy_sign_in_press,
|
||||||
)
|
)
|
||||||
ba.textwidget(
|
ba.textwidget(
|
||||||
parent=self._subcontainer,
|
parent=self._subcontainer,
|
||||||
@ -663,9 +662,7 @@ class AccountSettingsWindow(ba.Window):
|
|||||||
color=(0.55, 0.5, 0.6),
|
color=(0.55, 0.5, 0.6),
|
||||||
icon=ba.gettexture('settingsIcon'),
|
icon=ba.gettexture('settingsIcon'),
|
||||||
textcolor=(0.75, 0.7, 0.8),
|
textcolor=(0.75, 0.7, 0.8),
|
||||||
on_activate_call=lambda: ba.open_url(
|
on_activate_call=ba.WeakCall(self._on_manage_account_press),
|
||||||
'https://ballistica.net/accountsettings'
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
if first_selectable is None:
|
if first_selectable is None:
|
||||||
first_selectable = btn
|
first_selectable = btn
|
||||||
@ -1005,9 +1002,9 @@ class AccountSettingsWindow(ba.Window):
|
|||||||
)
|
)
|
||||||
ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15)
|
ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15)
|
||||||
|
|
||||||
if show_cancel_v2_sign_in_button:
|
if show_cancel_sign_in_button:
|
||||||
v -= cancel_v2_sign_in_button_space
|
v -= cancel_sign_in_button_space
|
||||||
self._cancel_v2_sign_in_button = btn = ba.buttonwidget(
|
self._cancel_sign_in_button = btn = ba.buttonwidget(
|
||||||
parent=self._subcontainer,
|
parent=self._subcontainer,
|
||||||
position=((self._sub_width - button_width) * 0.5, v),
|
position=((self._sub_width - button_width) * 0.5, v),
|
||||||
size=(button_width, 60),
|
size=(button_width, 60),
|
||||||
@ -1015,7 +1012,7 @@ class AccountSettingsWindow(ba.Window):
|
|||||||
color=(0.55, 0.5, 0.6),
|
color=(0.55, 0.5, 0.6),
|
||||||
textcolor=(0.75, 0.7, 0.8),
|
textcolor=(0.75, 0.7, 0.8),
|
||||||
autoselect=True,
|
autoselect=True,
|
||||||
on_activate_call=self._cancel_v2_sign_in_press,
|
on_activate_call=self._cancel_sign_in_press,
|
||||||
)
|
)
|
||||||
if first_selectable is None:
|
if first_selectable is None:
|
||||||
first_selectable = btn
|
first_selectable = btn
|
||||||
@ -1066,6 +1063,33 @@ class AccountSettingsWindow(ba.Window):
|
|||||||
account_type,
|
account_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _on_manage_account_press(self) -> None:
|
||||||
|
ba.screenmessage(ba.Lstr(resource='oneMomentText'))
|
||||||
|
|
||||||
|
# We expect to have a v2 account signed in if we get here.
|
||||||
|
if ba.app.accounts_v2.primary is None:
|
||||||
|
logging.exception(
|
||||||
|
'got manage-account press without v2 account present'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
with ba.app.accounts_v2.primary:
|
||||||
|
ba.app.cloud.send_message_cb(
|
||||||
|
bacommon.cloud.ManageAccountMessage(),
|
||||||
|
on_response=ba.WeakCall(self._on_manage_account_response),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _on_manage_account_response(
|
||||||
|
self, response: bacommon.cloud.ManageAccountResponse | Exception
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
if isinstance(response, Exception) or response.url is None:
|
||||||
|
ba.screenmessage(ba.Lstr(resource='errorText'), color=(1, 0, 0))
|
||||||
|
ba.playsound(ba.getsound('error'))
|
||||||
|
return
|
||||||
|
|
||||||
|
ba.open_url(response.url)
|
||||||
|
|
||||||
def _on_leaderboards_press(self) -> None:
|
def _on_leaderboards_press(self) -> None:
|
||||||
ba.timer(
|
ba.timer(
|
||||||
0.15,
|
0.15,
|
||||||
@ -1214,7 +1238,7 @@ class AccountSettingsWindow(ba.Window):
|
|||||||
origin_widget=self._player_profiles_button
|
origin_widget=self._player_profiles_button
|
||||||
)
|
)
|
||||||
|
|
||||||
def _cancel_v2_sign_in_press(self) -> None:
|
def _cancel_sign_in_press(self) -> None:
|
||||||
# Just say we don't wanna be signed in anymore.
|
# Just say we don't wanna be signed in anymore.
|
||||||
ba.app.accounts_v2.set_primary_credentials(None)
|
ba.app.accounts_v2.set_primary_credentials(None)
|
||||||
|
|
||||||
@ -1242,25 +1266,42 @@ class AccountSettingsWindow(ba.Window):
|
|||||||
# Speed UI updates along.
|
# Speed UI updates along.
|
||||||
ba.timer(0.1, ba.WeakCall(self._update), timetype=ba.TimeType.REAL)
|
ba.timer(0.1, ba.WeakCall(self._update), timetype=ba.TimeType.REAL)
|
||||||
|
|
||||||
def _sign_in_press(
|
def _sign_in_press(self, login_type: str | LoginType) -> None:
|
||||||
self, account_type: str, show_test_warning: bool = True
|
|
||||||
|
# V1 login types are strings.
|
||||||
|
if isinstance(login_type, str):
|
||||||
|
ba.internal.sign_in_v1(login_type)
|
||||||
|
|
||||||
|
# Make note of the type account we're *wanting*
|
||||||
|
# to be signed in with.
|
||||||
|
cfg = ba.app.config
|
||||||
|
cfg['Auto Account State'] = login_type
|
||||||
|
cfg.commit()
|
||||||
|
self._needs_refresh = True
|
||||||
|
ba.timer(0.1, ba.WeakCall(self._update), timetype=ba.TimeType.REAL)
|
||||||
|
return
|
||||||
|
|
||||||
|
# V2 login sign-in buttons generally go through adapters.
|
||||||
|
adapter = ba.app.accounts_v2.login_adapters.get(login_type)
|
||||||
|
if adapter is not None:
|
||||||
|
adapter.sign_in(
|
||||||
|
result_cb=ba.WeakCall(self._on_adapter_sign_in_result)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ba.screenmessage(f'Unsupported login_type: {login_type.name}')
|
||||||
|
|
||||||
|
def _on_adapter_sign_in_result(
|
||||||
|
self, result: LoginAdapter.SignInResult | Exception
|
||||||
) -> None:
|
) -> None:
|
||||||
del show_test_warning # unused
|
ba.screenmessage('GOT SIGN IN RESULT')
|
||||||
ba.internal.sign_in_v1(account_type)
|
logging.debug('GOT SIGN IN RESULT %s', result)
|
||||||
|
|
||||||
# Make note of the type account we're *wanting* to be signed in with.
|
def _v2_proxy_sign_in_press(self) -> None:
|
||||||
cfg = ba.app.config
|
|
||||||
cfg['Auto Account State'] = account_type
|
|
||||||
cfg.commit()
|
|
||||||
self._needs_refresh = True
|
|
||||||
ba.timer(0.1, ba.WeakCall(self._update), timetype=ba.TimeType.REAL)
|
|
||||||
|
|
||||||
def _v2_sign_in_press(self) -> None:
|
|
||||||
# pylint: disable=cyclic-import
|
# pylint: disable=cyclic-import
|
||||||
from bastd.ui.account.v2 import V2SignInWindow
|
from bastd.ui.account.v2proxy import V2ProxySignInWindow
|
||||||
|
|
||||||
assert self._sign_in_v2_button is not None
|
assert self._sign_in_v2_proxy_button is not None
|
||||||
V2SignInWindow(origin_widget=self._sign_in_v2_button)
|
V2ProxySignInWindow(origin_widget=self._sign_in_v2_proxy_button)
|
||||||
|
|
||||||
def _reset_progress(self) -> None:
|
def _reset_progress(self) -> None:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -19,7 +19,7 @@ if TYPE_CHECKING:
|
|||||||
STATUS_CHECK_INTERVAL_SECONDS = 2.0
|
STATUS_CHECK_INTERVAL_SECONDS = 2.0
|
||||||
|
|
||||||
|
|
||||||
class V2SignInWindow(ba.Window):
|
class V2ProxySignInWindow(ba.Window):
|
||||||
"""A window allowing signing in to a v2 account."""
|
"""A window allowing signing in to a v2 account."""
|
||||||
|
|
||||||
def __init__(self, origin_widget: ba.Widget):
|
def __init__(self, origin_widget: ba.Widget):
|
||||||
@ -413,6 +413,9 @@ def handle_app_invites_press(force_code: bool = False) -> None:
|
|||||||
and ba.internal.get_v1_account_misc_read_val('enableAppInvites', False)
|
and ba.internal.get_v1_account_misc_read_val('enableAppInvites', False)
|
||||||
and not app.on_tv
|
and not app.on_tv
|
||||||
)
|
)
|
||||||
|
# Update: google's app invites are deprecated.
|
||||||
|
do_app_invites = False
|
||||||
|
|
||||||
if force_code:
|
if force_code:
|
||||||
do_app_invites = False
|
do_app_invites = False
|
||||||
|
|
||||||
|
|||||||
@ -44,6 +44,7 @@ class GatherTab:
|
|||||||
The tab should create and return a container widget covering the
|
The tab should create and return a container widget covering the
|
||||||
specified region.
|
specified region.
|
||||||
"""
|
"""
|
||||||
|
raise RuntimeError('Should not get here.')
|
||||||
|
|
||||||
def on_deactivate(self) -> None:
|
def on_deactivate(self) -> None:
|
||||||
"""Called when the tab will no longer be the active one."""
|
"""Called when the tab will no longer be the active one."""
|
||||||
|
|||||||
@ -63,7 +63,7 @@ class PlaylistBrowserWindow(ba.Window):
|
|||||||
)
|
)
|
||||||
|
|
||||||
uiscale = ba.app.ui.uiscale
|
uiscale = ba.app.ui.uiscale
|
||||||
self._width = 900 if uiscale is ba.UIScale.SMALL else 800
|
self._width = 900.0 if uiscale is ba.UIScale.SMALL else 800.0
|
||||||
x_inset = 50 if uiscale is ba.UIScale.SMALL else 0
|
x_inset = 50 if uiscale is ba.UIScale.SMALL else 0
|
||||||
self._height = (
|
self._height = (
|
||||||
480
|
480
|
||||||
@ -365,7 +365,7 @@ class PlaylistBrowserWindow(ba.Window):
|
|||||||
|
|
||||||
self._sub_width = self._scroll_width
|
self._sub_width = self._scroll_width
|
||||||
self._sub_height = (
|
self._sub_height = (
|
||||||
40 + rows * (button_height + 2 * button_buffer_v) + 90
|
40.0 + rows * (button_height + 2 * button_buffer_v) + 90
|
||||||
)
|
)
|
||||||
assert self._sub_width is not None
|
assert self._sub_width is not None
|
||||||
assert self._sub_height is not None
|
assert self._sub_height is not None
|
||||||
|
|||||||
@ -42,6 +42,7 @@
|
|||||||
<w>airborn</w>
|
<w>airborn</w>
|
||||||
<w>alext</w>
|
<w>alext</w>
|
||||||
<w>alibaba</w>
|
<w>alibaba</w>
|
||||||
|
<w>alibname</w>
|
||||||
<w>allerrors</w>
|
<w>allerrors</w>
|
||||||
<w>allobjc</w>
|
<w>allobjc</w>
|
||||||
<w>allobjs</w>
|
<w>allobjs</w>
|
||||||
@ -1005,6 +1006,7 @@
|
|||||||
<w>pflag</w>
|
<w>pflag</w>
|
||||||
<w>pflags</w>
|
<w>pflags</w>
|
||||||
<w>pgmout</w>
|
<w>pgmout</w>
|
||||||
|
<w>phandle</w>
|
||||||
<w>phasescriptexecution</w>
|
<w>phasescriptexecution</w>
|
||||||
<w>piplist</w>
|
<w>piplist</w>
|
||||||
<w>pipvers</w>
|
<w>pipvers</w>
|
||||||
|
|||||||
@ -50,11 +50,6 @@ void StressTest::Update() {
|
|||||||
"time,averageFps,nodes,models,collide_models,textures,sounds,"
|
"time,averageFps,nodes,models,collide_models,textures,sounds,"
|
||||||
"pssMem,sharedDirtyMem,privateDirtyMem\n");
|
"pssMem,sharedDirtyMem,privateDirtyMem\n");
|
||||||
fflush(stress_test_stats_file_);
|
fflush(stress_test_stats_file_);
|
||||||
if (g_buildconfig.ostype_android()) {
|
|
||||||
// On android, let the OS know we've added or removed a file
|
|
||||||
// (limit to android or we'll get an unimplemented warning).
|
|
||||||
g_platform->AndroidRefreshFile(f_name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (stress_test_stats_file_ != nullptr) {
|
if (stress_test_stats_file_ != nullptr) {
|
||||||
|
|||||||
@ -32,7 +32,7 @@
|
|||||||
namespace ballistica {
|
namespace ballistica {
|
||||||
|
|
||||||
// These are set automatically via script; don't modify them here.
|
// These are set automatically via script; don't modify them here.
|
||||||
const int kAppBuildNumber = 20921;
|
const int kAppBuildNumber = 20928;
|
||||||
const char* kAppVersion = "1.7.14";
|
const char* kAppVersion = "1.7.14";
|
||||||
|
|
||||||
// Our standalone globals.
|
// Our standalone globals.
|
||||||
|
|||||||
@ -155,17 +155,19 @@ static auto print_number(cJSON* item) -> char* {
|
|||||||
double d = item->valuedouble;
|
double d = item->valuedouble;
|
||||||
if (fabs(((double)item->valueint) - d) <= DBL_EPSILON && d <= INT_MAX
|
if (fabs(((double)item->valueint) - d) <= DBL_EPSILON && d <= INT_MAX
|
||||||
&& d >= INT_MIN) {
|
&& d >= INT_MIN) {
|
||||||
str = (char*)cJSON_malloc(21); /* 2^64+1 can be represented in 21 chars. */
|
size_t sz{21};
|
||||||
if (str) sprintf(str, "%d", item->valueint);
|
str = (char*)cJSON_malloc(sz); /* 2^64+1 can be represented in 21 chars. */
|
||||||
|
if (str) snprintf(str, sz, "%d", item->valueint);
|
||||||
} else {
|
} else {
|
||||||
str = (char*)cJSON_malloc(64); /* This is a nice tradeoff. */
|
size_t sz{64};
|
||||||
|
str = (char*)cJSON_malloc(sz); /* This is a nice tradeoff. */
|
||||||
if (str) {
|
if (str) {
|
||||||
if (fabs(floor(d) - d) <= DBL_EPSILON && fabs(d) < 1.0e60)
|
if (fabs(floor(d) - d) <= DBL_EPSILON && fabs(d) < 1.0e60)
|
||||||
sprintf(str, "%.0f", d);
|
snprintf(str, sz, "%.0f", d);
|
||||||
else if (fabs(d) < 1.0e-6 || fabs(d) > 1.0e9)
|
else if (fabs(d) < 1.0e-6 || fabs(d) > 1.0e9)
|
||||||
sprintf(str, "%e", d);
|
snprintf(str, sz, "%e", d);
|
||||||
else
|
else
|
||||||
sprintf(str, "%f", d);
|
snprintf(str, sz, "%f", d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
@ -379,7 +381,7 @@ static auto print_string_ptr(const char* str) -> char* {
|
|||||||
*ptr2++ = 't';
|
*ptr2++ = 't';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
sprintf(ptr2, "u%04x", token);
|
snprintf(ptr2, 20, "u%04x", token);
|
||||||
ptr2 += 5;
|
ptr2 += 5;
|
||||||
break; /* escape and print */
|
break; /* escape and print */
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,6 +34,7 @@
|
|||||||
#include "ballistica/platform/sdl/sdl_app.h"
|
#include "ballistica/platform/sdl/sdl_app.h"
|
||||||
#include "ballistica/platform/stdio_console.h"
|
#include "ballistica/platform/stdio_console.h"
|
||||||
#include "ballistica/python/python.h"
|
#include "ballistica/python/python.h"
|
||||||
|
#include "ballistica/python/python_sys.h"
|
||||||
|
|
||||||
#if BA_HEADLESS_BUILD
|
#if BA_HEADLESS_BUILD
|
||||||
#include "ballistica/app/app_flavor_headless.h"
|
#include "ballistica/app/app_flavor_headless.h"
|
||||||
@ -183,6 +184,18 @@ auto Platform::GetLegacyDeviceUUID() -> const std::string& {
|
|||||||
return legacy_device_uuid_;
|
return legacy_device_uuid_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Platform::LoginAdapterGetSignInToken(const std::string& login_type,
|
||||||
|
int attempt_id) -> void {
|
||||||
|
// Default implementation simply calls completion callback immediately.
|
||||||
|
g_logic->thread()->PushCall([login_type, attempt_id] {
|
||||||
|
PythonRef args(Py_BuildValue("(sss)", login_type.c_str(),
|
||||||
|
std::to_string(attempt_id).c_str(), ""),
|
||||||
|
PythonRef::kSteal);
|
||||||
|
g_python->obj(Python::ObjID::kLoginAdapterGetSignInTokenResponseCall)
|
||||||
|
.Call(args);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
auto Platform::GetDeviceV1AccountUUIDPrefix() -> std::string {
|
auto Platform::GetDeviceV1AccountUUIDPrefix() -> std::string {
|
||||||
Log(LogLevel::kError, "GetDeviceV1AccountUUIDPrefix() unimplemented");
|
Log(LogLevel::kError, "GetDeviceV1AccountUUIDPrefix() unimplemented");
|
||||||
return "u";
|
return "u";
|
||||||
@ -1030,10 +1043,6 @@ void Platform::SubmitAnalyticsCounts() {}
|
|||||||
|
|
||||||
void Platform::SetPlatformMiscReadVals(const std::string& vals) {}
|
void Platform::SetPlatformMiscReadVals(const std::string& vals) {}
|
||||||
|
|
||||||
void Platform::AndroidRefreshFile(const std::string& file) {
|
|
||||||
Log(LogLevel::kError, "AndroidRefreshFile() unimplemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Platform::ShowAd(const std::string& purpose) {
|
void Platform::ShowAd(const std::string& purpose) {
|
||||||
Log(LogLevel::kError, "ShowAd() unimplemented");
|
Log(LogLevel::kError, "ShowAd() unimplemented");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -267,7 +267,6 @@ class Platform {
|
|||||||
virtual auto AndroidShowAppInvite(const std::string& title,
|
virtual auto AndroidShowAppInvite(const std::string& title,
|
||||||
const std::string& message,
|
const std::string& message,
|
||||||
const std::string& code) -> void;
|
const std::string& code) -> void;
|
||||||
virtual auto AndroidRefreshFile(const std::string& file) -> void;
|
|
||||||
virtual auto AndroidShowWifiSettings() -> void;
|
virtual auto AndroidShowWifiSettings() -> void;
|
||||||
virtual auto AndroidGetExternalFilesDir() -> std::string;
|
virtual auto AndroidGetExternalFilesDir() -> std::string;
|
||||||
|
|
||||||
@ -337,6 +336,10 @@ class Platform {
|
|||||||
/// Return the prefix to use for device-account ids on this platform.
|
/// Return the prefix to use for device-account ids on this platform.
|
||||||
virtual auto GetDeviceV1AccountUUIDPrefix() -> std::string;
|
virtual auto GetDeviceV1AccountUUIDPrefix() -> std::string;
|
||||||
|
|
||||||
|
/// Called when a Python LoginAdapter is requesting an explicit sign-in.
|
||||||
|
virtual auto LoginAdapterGetSignInToken(const std::string& login_type,
|
||||||
|
int attempt_id) -> void;
|
||||||
|
|
||||||
#pragma mark MUSIC PLAYBACK ----------------------------------------------------
|
#pragma mark MUSIC PLAYBACK ----------------------------------------------------
|
||||||
|
|
||||||
// FIXME: currently these are wired up on Android; need to generalize
|
// FIXME: currently these are wired up on Android; need to generalize
|
||||||
|
|||||||
@ -622,6 +622,22 @@ auto PySetAnalyticsScreen(PyObject* self, PyObject* args, PyObject* keywds)
|
|||||||
BA_PYTHON_CATCH;
|
BA_PYTHON_CATCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto PyLoginAdapterGetSignInToken(PyObject* self, PyObject* args,
|
||||||
|
PyObject* keywds) -> PyObject* {
|
||||||
|
BA_PYTHON_TRY;
|
||||||
|
const char* login_type;
|
||||||
|
int attempt_id;
|
||||||
|
static const char* kwlist[] = {"login_type", "attempt_id", nullptr};
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, keywds, "si",
|
||||||
|
const_cast<char**>(kwlist), &login_type,
|
||||||
|
&attempt_id)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
g_platform->LoginAdapterGetSignInToken(login_type, attempt_id);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
BA_PYTHON_CATCH;
|
||||||
|
}
|
||||||
|
|
||||||
auto PySetInternalLanguageKeys(PyObject* self, PyObject* args) -> PyObject* {
|
auto PySetInternalLanguageKeys(PyObject* self, PyObject* args) -> PyObject* {
|
||||||
BA_PYTHON_TRY;
|
BA_PYTHON_TRY;
|
||||||
PyObject* list_obj;
|
PyObject* list_obj;
|
||||||
@ -659,26 +675,6 @@ auto PySetInternalLanguageKeys(PyObject* self, PyObject* args) -> PyObject* {
|
|||||||
BA_PYTHON_CATCH;
|
BA_PYTHON_CATCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PyIsOuyaBuild(PyObject* self, PyObject* args) -> PyObject* {
|
|
||||||
BA_PYTHON_TRY;
|
|
||||||
Py_RETURN_FALSE;
|
|
||||||
BA_PYTHON_CATCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto PyAndroidMediaScanFile(PyObject* self, PyObject* args, PyObject* keywds)
|
|
||||||
-> PyObject* {
|
|
||||||
BA_PYTHON_TRY;
|
|
||||||
const char* file_name;
|
|
||||||
static const char* kwlist[] = {"file_name", nullptr};
|
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
|
|
||||||
const_cast<char**>(kwlist), &file_name)) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
g_platform->AndroidRefreshFile(file_name);
|
|
||||||
Py_RETURN_NONE;
|
|
||||||
BA_PYTHON_CATCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma clang diagnostic push
|
#pragma clang diagnostic push
|
||||||
#pragma ide diagnostic ignored "ConstantFunctionResult"
|
#pragma ide diagnostic ignored "ConstantFunctionResult"
|
||||||
|
|
||||||
@ -822,15 +818,6 @@ auto PythonMethodsSystem::GetMethods() -> std::vector<PyMethodDef> {
|
|||||||
"\n"
|
"\n"
|
||||||
"(internal)"},
|
"(internal)"},
|
||||||
|
|
||||||
{"android_media_scan_file", (PyCFunction)PyAndroidMediaScanFile,
|
|
||||||
METH_VARARGS | METH_KEYWORDS,
|
|
||||||
"android_media_scan_file(file_name: str) -> None\n"
|
|
||||||
"\n"
|
|
||||||
"(internal)\n"
|
|
||||||
"\n"
|
|
||||||
"Refreshes Android MTP Index for a file; use this to get file\n"
|
|
||||||
"modifications to be reflected in Android File Transfer."},
|
|
||||||
|
|
||||||
{"android_get_external_files_dir",
|
{"android_get_external_files_dir",
|
||||||
(PyCFunction)PyAndroidGetExternalFilesDir, METH_VARARGS | METH_KEYWORDS,
|
(PyCFunction)PyAndroidGetExternalFilesDir, METH_VARARGS | METH_KEYWORDS,
|
||||||
"android_get_external_files_dir() -> str\n"
|
"android_get_external_files_dir() -> str\n"
|
||||||
@ -847,13 +834,6 @@ auto PythonMethodsSystem::GetMethods() -> std::vector<PyMethodDef> {
|
|||||||
"\n"
|
"\n"
|
||||||
"(internal)"},
|
"(internal)"},
|
||||||
|
|
||||||
{"is_ouya_build", PyIsOuyaBuild, METH_VARARGS,
|
|
||||||
"is_ouya_build() -> bool\n"
|
|
||||||
"\n"
|
|
||||||
"(internal)\n"
|
|
||||||
"\n"
|
|
||||||
"Returns whether we're running the ouya-specific version"},
|
|
||||||
|
|
||||||
{"set_internal_language_keys", PySetInternalLanguageKeys, METH_VARARGS,
|
{"set_internal_language_keys", PySetInternalLanguageKeys, METH_VARARGS,
|
||||||
"set_internal_language_keys(listobj: list[tuple[str, str]],\n"
|
"set_internal_language_keys(listobj: list[tuple[str, str]],\n"
|
||||||
" random_names_list: list[tuple[str, str]]) -> None\n"
|
" random_names_list: list[tuple[str, str]]) -> None\n"
|
||||||
@ -872,6 +852,13 @@ auto PythonMethodsSystem::GetMethods() -> std::vector<PyMethodDef> {
|
|||||||
"'screen' should be a string description of an app location\n"
|
"'screen' should be a string description of an app location\n"
|
||||||
"('Main Menu', etc.)"},
|
"('Main Menu', etc.)"},
|
||||||
|
|
||||||
|
{"login_adapter_get_sign_in_token",
|
||||||
|
(PyCFunction)PyLoginAdapterGetSignInToken, METH_VARARGS | METH_KEYWORDS,
|
||||||
|
"login_adapter_get_sign_in_token(login_type: str, attempt_id: int)"
|
||||||
|
" -> None\n"
|
||||||
|
"\n"
|
||||||
|
"(internal)"},
|
||||||
|
|
||||||
{"submit_analytics_counts", (PyCFunction)PySubmitAnalyticsCounts,
|
{"submit_analytics_counts", (PyCFunction)PySubmitAnalyticsCounts,
|
||||||
METH_VARARGS | METH_KEYWORDS,
|
METH_VARARGS | METH_KEYWORDS,
|
||||||
"submit_analytics_counts() -> None\n"
|
"submit_analytics_counts() -> None\n"
|
||||||
|
|||||||
@ -318,6 +318,7 @@ class Python {
|
|||||||
kSetLastAdNetworkCall,
|
kSetLastAdNetworkCall,
|
||||||
kNoGameCircleMessageCall,
|
kNoGameCircleMessageCall,
|
||||||
kGooglePlayPurchasesNotAvailableMessageCall,
|
kGooglePlayPurchasesNotAvailableMessageCall,
|
||||||
|
kGooglePlayServicesNotAvailableMessageCall,
|
||||||
kEmptyCall,
|
kEmptyCall,
|
||||||
kLevelIconPressCall,
|
kLevelIconPressCall,
|
||||||
kTrophyIconPressCall,
|
kTrophyIconPressCall,
|
||||||
@ -365,6 +366,9 @@ class Python {
|
|||||||
kLoggingWarningCall,
|
kLoggingWarningCall,
|
||||||
kLoggingErrorCall,
|
kLoggingErrorCall,
|
||||||
kLoggingCriticalCall,
|
kLoggingCriticalCall,
|
||||||
|
kImplicitLoginCall,
|
||||||
|
kImplicitLogoutCall,
|
||||||
|
kLoginAdapterGetSignInTokenResponseCall,
|
||||||
kLast // Sentinel; must be at end.
|
kLast // Sentinel; must be at end.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -66,6 +66,7 @@ def get_binding_values() -> tuple[Any, ...]:
|
|||||||
_hooks.set_last_ad_network, # kSetLastAdNetworkCall
|
_hooks.set_last_ad_network, # kSetLastAdNetworkCall
|
||||||
_hooks.no_game_circle_message, # kNoGameCircleMessageCall
|
_hooks.no_game_circle_message, # kNoGameCircleMessageCall
|
||||||
_hooks.google_play_purchases_not_available_message, # kGooglePlayPurchasesNotAvailableMessageCall
|
_hooks.google_play_purchases_not_available_message, # kGooglePlayPurchasesNotAvailableMessageCall
|
||||||
|
_hooks.google_play_services_not_available_message, # kGooglePlayServicesNotAvailableMessageCall
|
||||||
_hooks.empty_call, # kEmptyCall
|
_hooks.empty_call, # kEmptyCall
|
||||||
_hooks.level_icon_press, # kLevelIconPressCall
|
_hooks.level_icon_press, # kLevelIconPressCall
|
||||||
_hooks.trophy_icon_press, # kTrophyIconPressCall
|
_hooks.trophy_icon_press, # kTrophyIconPressCall
|
||||||
@ -142,4 +143,7 @@ def get_binding_values() -> tuple[Any, ...]:
|
|||||||
logging.warning, # kLoggingWarningCall
|
logging.warning, # kLoggingWarningCall
|
||||||
logging.error, # kLoggingErrorCall
|
logging.error, # kLoggingErrorCall
|
||||||
logging.critical, # kLoggingCriticalCall
|
logging.critical, # kLoggingCriticalCall
|
||||||
|
_hooks.implicit_login, # kImplicitLoginCall
|
||||||
|
_hooks.implicit_logout, # kImplicitLogoutCall
|
||||||
|
_hooks.login_adapter_get_sign_in_token_response, # kLoginAdapterGetSignInTokenResponseCall
|
||||||
) # yapf: disable
|
) # yapf: disable
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from enum import Enum
|
|||||||
from efro.message import Message, Response
|
from efro.message import Message, Response
|
||||||
from efro.dataclassio import ioprepped, IOAttrs
|
from efro.dataclassio import ioprepped, IOAttrs
|
||||||
from bacommon.transfer import DirectoryManifest
|
from bacommon.transfer import DirectoryManifest
|
||||||
|
from bacommon.login import LoginType
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
pass
|
pass
|
||||||
@ -154,3 +155,42 @@ class WorkspaceFetchResponse(Response):
|
|||||||
] = field(default_factory=dict)
|
] = field(default_factory=dict)
|
||||||
|
|
||||||
done: Annotated[bool, IOAttrs('d')] = False
|
done: Annotated[bool, IOAttrs('d')] = False
|
||||||
|
|
||||||
|
|
||||||
|
@ioprepped
|
||||||
|
@dataclass
|
||||||
|
class SignInMessage(Message):
|
||||||
|
"""Can I sign in please?"""
|
||||||
|
|
||||||
|
login_type: Annotated[LoginType, IOAttrs('l')]
|
||||||
|
sign_in_token: Annotated[str, IOAttrs('t')]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_response_types(cls) -> list[type[Response] | None]:
|
||||||
|
return [SignInResponse]
|
||||||
|
|
||||||
|
|
||||||
|
@ioprepped
|
||||||
|
@dataclass
|
||||||
|
class SignInResponse(Response):
|
||||||
|
"""Here's that sign-in result you asked for, boss."""
|
||||||
|
|
||||||
|
credentials: Annotated[str | None, IOAttrs('c')]
|
||||||
|
|
||||||
|
|
||||||
|
@ioprepped
|
||||||
|
@dataclass
|
||||||
|
class ManageAccountMessage(Message):
|
||||||
|
"""Message asking for a manage-account url."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_response_types(cls) -> list[type[Response] | None]:
|
||||||
|
return [ManageAccountResponse]
|
||||||
|
|
||||||
|
|
||||||
|
@ioprepped
|
||||||
|
@dataclass
|
||||||
|
class ManageAccountResponse(Response):
|
||||||
|
"""Here's that sign-in result you asked for, boss."""
|
||||||
|
|
||||||
|
url: Annotated[str | None, IOAttrs('u')]
|
||||||
|
|||||||
21
tools/bacommon/login.py
Normal file
21
tools/bacommon/login.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Released under the MIT License. See LICENSE for details.
|
||||||
|
#
|
||||||
|
"""Functionality related to cloud based assets."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LoginType(Enum):
|
||||||
|
"""Types of logins available."""
|
||||||
|
|
||||||
|
# Email/password
|
||||||
|
EMAIL = 'email'
|
||||||
|
|
||||||
|
# Google Play Game Services
|
||||||
|
GPGS = 'gpgs'
|
||||||
@ -334,11 +334,11 @@ def gen_fulltest_buildfile_linux() -> None:
|
|||||||
batools.build.gen_fulltest_buildfile_linux()
|
batools.build.gen_fulltest_buildfile_linux()
|
||||||
|
|
||||||
|
|
||||||
def python_version_build_base() -> None:
|
def python_version_android_base() -> None:
|
||||||
"""Print built Python base version."""
|
"""Print built Python base version."""
|
||||||
from efrotools.pybuild import PY_VER
|
from efrotools.pybuild import PY_VER_ANDROID
|
||||||
|
|
||||||
print(PY_VER, end='')
|
print(PY_VER_ANDROID, end='')
|
||||||
|
|
||||||
|
|
||||||
def python_version_android() -> None:
|
def python_version_android() -> None:
|
||||||
@ -444,7 +444,25 @@ def python_gather() -> None:
|
|||||||
from efrotools import pybuild
|
from efrotools import pybuild
|
||||||
|
|
||||||
os.chdir(PROJROOT)
|
os.chdir(PROJROOT)
|
||||||
pybuild.gather()
|
pybuild.gather(do_android=True, do_apple=True)
|
||||||
|
|
||||||
|
|
||||||
|
def python_gather_android() -> None:
|
||||||
|
"""python_gather but only android bits."""
|
||||||
|
import os
|
||||||
|
from efrotools import pybuild
|
||||||
|
|
||||||
|
os.chdir(PROJROOT)
|
||||||
|
pybuild.gather(do_android=True, do_apple=False)
|
||||||
|
|
||||||
|
|
||||||
|
def python_gather_apple() -> None:
|
||||||
|
"""python_gather but only apple bits."""
|
||||||
|
import os
|
||||||
|
from efrotools import pybuild
|
||||||
|
|
||||||
|
os.chdir(PROJROOT)
|
||||||
|
pybuild.gather(do_android=False, do_apple=True)
|
||||||
|
|
||||||
|
|
||||||
def python_winprune() -> None:
|
def python_winprune() -> None:
|
||||||
|
|||||||
@ -188,6 +188,7 @@ def is_asyncio_streams_communication_error(exc: BaseException) -> bool:
|
|||||||
firewall/connectivity issues, etc. These issues can often be safely
|
firewall/connectivity issues, etc. These issues can often be safely
|
||||||
ignored or presented to the user as general 'connection-lost' events.
|
ignored or presented to the user as general 'connection-lost' events.
|
||||||
"""
|
"""
|
||||||
|
# pylint: disable=too-many-return-statements
|
||||||
import ssl
|
import ssl
|
||||||
|
|
||||||
if isinstance(
|
if isinstance(
|
||||||
@ -227,4 +228,8 @@ def is_asyncio_streams_communication_error(exc: BaseException) -> bool:
|
|||||||
if 'SSL: WRONG_VERSION_NUMBER' in excstr:
|
if 'SSL: WRONG_VERSION_NUMBER' in excstr:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# And seeing this very rarely; assuming its just data corruption?
|
||||||
|
if 'SSL: DECRYPTION_FAILED_OR_BAD_RECORD_MAC' in excstr:
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|||||||
@ -273,13 +273,14 @@ class DispatchMethodWrapper(Generic[ArgT, RetT]):
|
|||||||
"""Type-aware standin for the dispatch func returned by dispatchmethod."""
|
"""Type-aware standin for the dispatch func returned by dispatchmethod."""
|
||||||
|
|
||||||
def __call__(self, arg: ArgT) -> RetT:
|
def __call__(self, arg: ArgT) -> RetT:
|
||||||
pass
|
raise RuntimeError('Should not get here')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def register(
|
def register(
|
||||||
func: Callable[[Any, Any], RetT]
|
func: Callable[[Any, Any], RetT]
|
||||||
) -> Callable[[Any, Any], RetT]:
|
) -> Callable[[Any, Any], RetT]:
|
||||||
"""Register a new dispatch handler for this dispatch-method."""
|
"""Register a new dispatch handler for this dispatch-method."""
|
||||||
|
raise RuntimeError('Should not get here')
|
||||||
|
|
||||||
registry: dict[Any, Callable]
|
registry: dict[Any, Callable]
|
||||||
|
|
||||||
|
|||||||
@ -14,9 +14,10 @@ if TYPE_CHECKING:
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
# Python version we build here (not necessarily same as we use in repo).
|
# Python version we build here (not necessarily same as we use in repo).
|
||||||
PY_VER = '3.10'
|
PY_VER_ANDROID = '3.11'
|
||||||
PY_VER_EXACT_ANDROID = '3.10.7'
|
PY_VER_EXACT_ANDROID = '3.11.0'
|
||||||
PY_VER_EXACT_APPLE = '3.10.7'
|
PY_VER_APPLE = '3.10'
|
||||||
|
PY_VER_EXACT_APPLE = '3.10.5'
|
||||||
|
|
||||||
ANDROID_PYTHON_REPO = 'https://github.com/GRRedWings/python3-android'
|
ANDROID_PYTHON_REPO = 'https://github.com/GRRedWings/python3-android'
|
||||||
|
|
||||||
@ -91,7 +92,7 @@ def build_apple(arch: str, debug: bool = False) -> None:
|
|||||||
# broke in the underlying build even on old commits so keeping it
|
# broke in the underlying build even on old commits so keeping it
|
||||||
# locked for now...
|
# locked for now...
|
||||||
# run('git checkout bf1ed73d0d5ff46862ba69dd5eb2ffaeff6f19b6')
|
# run('git checkout bf1ed73d0d5ff46862ba69dd5eb2ffaeff6f19b6')
|
||||||
subprocess.run(['git', 'checkout', PY_VER], check=True)
|
subprocess.run(['git', 'checkout', PY_VER_APPLE], check=True)
|
||||||
|
|
||||||
txt = readfile('Makefile')
|
txt = readfile('Makefile')
|
||||||
|
|
||||||
@ -240,7 +241,7 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
|
|||||||
# after it is extracted.
|
# after it is extracted.
|
||||||
ftxt = readfile('build.sh')
|
ftxt = readfile('build.sh')
|
||||||
|
|
||||||
ftxt = replace_exact(ftxt, 'PYVER=3.10.5', f'PYVER={PY_VER_EXACT_ANDROID}')
|
ftxt = replace_exact(ftxt, 'PYVER=3.11.0', f'PYVER={PY_VER_EXACT_ANDROID}')
|
||||||
ftxt = replace_exact(
|
ftxt = replace_exact(
|
||||||
ftxt,
|
ftxt,
|
||||||
' popd\n',
|
' popd\n',
|
||||||
@ -252,7 +253,10 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
|
|||||||
# Ok; let 'er rip!
|
# Ok; let 'er rip!
|
||||||
exargs = ' --with-pydebug' if debug else ''
|
exargs = ' --with-pydebug' if debug else ''
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
f'ARCH={arch} ANDROID_API=21 ./build.sh{exargs}', shell=True, check=True
|
f'ARCH={arch} ANDROID_API=21 ./build.sh{exargs} --without-ensurepip'
|
||||||
|
f' --with-build-python=/home/ubuntu/.py311/bin/python3.11',
|
||||||
|
shell=True,
|
||||||
|
check=True,
|
||||||
)
|
)
|
||||||
print('python build complete! (android/' + arch + ')')
|
print('python build complete! (android/' + arch + ')')
|
||||||
|
|
||||||
@ -302,30 +306,34 @@ def android_patch_ssl() -> None:
|
|||||||
)
|
)
|
||||||
writefile(fname, txt)
|
writefile(fname, txt)
|
||||||
|
|
||||||
# Getting a lot of crashes in _armv7_tick, which seems to be a
|
# Update: looks like this might have been disabled by default for
|
||||||
# somewhat known issue with certain arm7 devices. Sounds like
|
# newer SSL builds used by 3.11+
|
||||||
# there are no major downsides to disabling this feature, so doing that.
|
if bool(False):
|
||||||
# (Sounds like its possible to somehow disable it through an env var
|
# Getting a lot of crashes in _armv7_tick, which seems to be a
|
||||||
# but let's just be sure and #ifdef it out in the source.
|
# somewhat known issue with certain arm7 devices. Sounds like
|
||||||
# see https://github.com/openssl/openssl/issues/17465
|
# there are no major downsides to disabling this feature, so doing that.
|
||||||
fname = 'crypto/armcap.c'
|
# (Sounds like its possible to somehow disable it through an env var
|
||||||
txt = readfile(fname)
|
# but let's just be sure and #ifdef it out in the source.
|
||||||
txt = replace_exact(
|
# see https://github.com/openssl/openssl/issues/17465
|
||||||
txt,
|
fname = 'crypto/armcap.c'
|
||||||
' /* Things that getauxval didn\'t tell us */\n'
|
txt = readfile(fname)
|
||||||
' if (sigsetjmp(ill_jmp, 1) == 0) {\n'
|
txt = replace_exact(
|
||||||
' _armv7_tick();\n'
|
txt,
|
||||||
' OPENSSL_armcap_P |= ARMV7_TICK;\n'
|
' /* Things that getauxval didn\'t tell us */\n'
|
||||||
' }\n',
|
' if (sigsetjmp(ill_jmp, 1) == 0) {\n'
|
||||||
'# if 0 // ericf disabled; causing crashes on some android devices.\n'
|
' _armv7_tick();\n'
|
||||||
' /* Things that getauxval didn\'t tell us */\n'
|
' OPENSSL_armcap_P |= ARMV7_TICK;\n'
|
||||||
' if (sigsetjmp(ill_jmp, 1) == 0) {\n'
|
' }\n',
|
||||||
' _armv7_tick();\n'
|
'# if 0 // ericf disabled; causing crashes'
|
||||||
' OPENSSL_armcap_P |= ARMV7_TICK;\n'
|
' on some android devices.\n'
|
||||||
' }\n'
|
' /* Things that getauxval didn\'t tell us */\n'
|
||||||
'# endif // 0\n',
|
' if (sigsetjmp(ill_jmp, 1) == 0) {\n'
|
||||||
)
|
' _armv7_tick();\n'
|
||||||
writefile(fname, txt)
|
' OPENSSL_armcap_P |= ARMV7_TICK;\n'
|
||||||
|
' }\n'
|
||||||
|
'# endif // 0\n',
|
||||||
|
)
|
||||||
|
writefile(fname, txt)
|
||||||
|
|
||||||
|
|
||||||
def _patch_py_ssl() -> None:
|
def _patch_py_ssl() -> None:
|
||||||
@ -373,6 +381,7 @@ def _patch_setup_file(platform: str, arch: str, slc: str) -> None:
|
|||||||
if platform == 'android':
|
if platform == 'android':
|
||||||
prefix = '$(srcdir)/Android/sysroot/usr'
|
prefix = '$(srcdir)/Android/sysroot/usr'
|
||||||
uuid_ex = f' -L{prefix}/lib -luuid'
|
uuid_ex = f' -L{prefix}/lib -luuid'
|
||||||
|
uuid_ex += f' -I{prefix}/include/uuid' # Testing
|
||||||
zlib_ex = f' -I{prefix}/include -L{prefix}/lib -lz'
|
zlib_ex = f' -I{prefix}/include -L{prefix}/lib -lz'
|
||||||
bz2_ex = f' -I{prefix}/include -L{prefix}/lib -lbz2'
|
bz2_ex = f' -I{prefix}/include -L{prefix}/lib -lbz2'
|
||||||
ssl_ex = f' -DUSE_SSL -I{prefix}/include -L{prefix}/lib -lssl -lcrypto'
|
ssl_ex = f' -DUSE_SSL -I{prefix}/include -L{prefix}/lib -lssl -lcrypto'
|
||||||
@ -552,7 +561,7 @@ def _patch_setup_file(platform: str, arch: str, slc: str) -> None:
|
|||||||
|
|
||||||
ftxt += (
|
ftxt += (
|
||||||
f'_sqlite3'
|
f'_sqlite3'
|
||||||
f' _sqlite/cache.c'
|
f' _sqlite/blob.c'
|
||||||
f' _sqlite/connection.c'
|
f' _sqlite/connection.c'
|
||||||
f' _sqlite/cursor.c'
|
f' _sqlite/cursor.c'
|
||||||
f' _sqlite/microprotocols.c'
|
f' _sqlite/microprotocols.c'
|
||||||
@ -642,7 +651,7 @@ def winprune() -> None:
|
|||||||
print('Win-prune successful.')
|
print('Win-prune successful.')
|
||||||
|
|
||||||
|
|
||||||
def gather() -> None:
|
def gather(do_android: bool, do_apple: bool) -> None:
|
||||||
"""Gather per-platform python headers, libs, and modules together.
|
"""Gather per-platform python headers, libs, and modules together.
|
||||||
|
|
||||||
This assumes all embeddable py builds have been run successfully,
|
This assumes all embeddable py builds have been run successfully,
|
||||||
@ -651,8 +660,6 @@ def gather() -> None:
|
|||||||
# pylint: disable=too-many-locals
|
# pylint: disable=too-many-locals
|
||||||
# pylint: disable=too-many-statements
|
# pylint: disable=too-many-statements
|
||||||
|
|
||||||
do_android = True
|
|
||||||
|
|
||||||
# First off, clear out any existing output.
|
# First off, clear out any existing output.
|
||||||
existing_dirs = [
|
existing_dirs = [
|
||||||
os.path.join('src/external', d)
|
os.path.join('src/external', d)
|
||||||
@ -675,7 +682,7 @@ def gather() -> None:
|
|||||||
debug = buildtype == 'debug'
|
debug = buildtype == 'debug'
|
||||||
bsuffix = '_debug' if buildtype == 'debug' else ''
|
bsuffix = '_debug' if buildtype == 'debug' else ''
|
||||||
bsuffix2 = '-debug' if buildtype == 'debug' else ''
|
bsuffix2 = '-debug' if buildtype == 'debug' else ''
|
||||||
libname = 'python' + PY_VER + ('d' if debug else '')
|
alibname = 'python' + PY_VER_ANDROID + ('d' if debug else '')
|
||||||
|
|
||||||
bases = {
|
bases = {
|
||||||
'mac': f'build/python_apple_mac{bsuffix}/build/macOS',
|
'mac': f'build/python_apple_mac{bsuffix}/build/macOS',
|
||||||
@ -756,9 +763,9 @@ def gather() -> None:
|
|||||||
{
|
{
|
||||||
'name': 'android_arm',
|
'name': 'android_arm',
|
||||||
'group': 'android',
|
'group': 'android',
|
||||||
'headers': bases['android_arm'] + f'/usr/include/{libname}',
|
'headers': bases['android_arm'] + f'/usr/include/{alibname}',
|
||||||
'libs': [
|
'libs': [
|
||||||
bases['android_arm'] + f'/usr/lib/lib{libname}.a',
|
bases['android_arm'] + f'/usr/lib/lib{alibname}.a',
|
||||||
bases2['android_arm'] + '/usr/lib/libssl.a',
|
bases2['android_arm'] + '/usr/lib/libssl.a',
|
||||||
bases2['android_arm'] + '/usr/lib/libcrypto.a',
|
bases2['android_arm'] + '/usr/lib/libcrypto.a',
|
||||||
bases2['android_arm'] + '/usr/lib/liblzma.a',
|
bases2['android_arm'] + '/usr/lib/liblzma.a',
|
||||||
@ -767,14 +774,16 @@ def gather() -> None:
|
|||||||
bases2['android_arm'] + '/usr/lib/libuuid.a',
|
bases2['android_arm'] + '/usr/lib/libuuid.a',
|
||||||
],
|
],
|
||||||
'libinst': 'android_armeabi-v7a',
|
'libinst': 'android_armeabi-v7a',
|
||||||
'pylib': (bases['android_arm'] + '/usr/lib/python' + PY_VER),
|
'pylib': (
|
||||||
|
bases['android_arm'] + '/usr/lib/python' + PY_VER_ANDROID
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'android_arm64',
|
'name': 'android_arm64',
|
||||||
'group': 'android',
|
'group': 'android',
|
||||||
'headers': bases['android_arm64'] + f'/usr/include/{libname}',
|
'headers': bases['android_arm64'] + f'/usr/include/{alibname}',
|
||||||
'libs': [
|
'libs': [
|
||||||
bases['android_arm64'] + f'/usr/lib/lib{libname}.a',
|
bases['android_arm64'] + f'/usr/lib/lib{alibname}.a',
|
||||||
bases2['android_arm64'] + '/usr/lib/libssl.a',
|
bases2['android_arm64'] + '/usr/lib/libssl.a',
|
||||||
bases2['android_arm64'] + '/usr/lib/libcrypto.a',
|
bases2['android_arm64'] + '/usr/lib/libcrypto.a',
|
||||||
bases2['android_arm64'] + '/usr/lib/liblzma.a',
|
bases2['android_arm64'] + '/usr/lib/liblzma.a',
|
||||||
@ -787,9 +796,9 @@ def gather() -> None:
|
|||||||
{
|
{
|
||||||
'name': 'android_x86',
|
'name': 'android_x86',
|
||||||
'group': 'android',
|
'group': 'android',
|
||||||
'headers': bases['android_x86'] + f'/usr/include/{libname}',
|
'headers': bases['android_x86'] + f'/usr/include/{alibname}',
|
||||||
'libs': [
|
'libs': [
|
||||||
bases['android_x86'] + f'/usr/lib/lib{libname}.a',
|
bases['android_x86'] + f'/usr/lib/lib{alibname}.a',
|
||||||
bases2['android_x86'] + '/usr/lib/libssl.a',
|
bases2['android_x86'] + '/usr/lib/libssl.a',
|
||||||
bases2['android_x86'] + '/usr/lib/libcrypto.a',
|
bases2['android_x86'] + '/usr/lib/libcrypto.a',
|
||||||
bases2['android_x86'] + '/usr/lib/liblzma.a',
|
bases2['android_x86'] + '/usr/lib/liblzma.a',
|
||||||
@ -802,9 +811,9 @@ def gather() -> None:
|
|||||||
{
|
{
|
||||||
'name': 'android_x86_64',
|
'name': 'android_x86_64',
|
||||||
'group': 'android',
|
'group': 'android',
|
||||||
'headers': bases['android_x86_64'] + f'/usr/include/{libname}',
|
'headers': bases['android_x86_64'] + f'/usr/include/{alibname}',
|
||||||
'libs': [
|
'libs': [
|
||||||
bases['android_x86_64'] + f'/usr/lib/lib{libname}.a',
|
bases['android_x86_64'] + f'/usr/lib/lib{alibname}.a',
|
||||||
bases2['android_x86_64'] + '/usr/lib/libssl.a',
|
bases2['android_x86_64'] + '/usr/lib/libssl.a',
|
||||||
bases2['android_x86_64'] + '/usr/lib/libcrypto.a',
|
bases2['android_x86_64'] + '/usr/lib/libcrypto.a',
|
||||||
bases2['android_x86_64'] + '/usr/lib/liblzma.a',
|
bases2['android_x86_64'] + '/usr/lib/liblzma.a',
|
||||||
@ -820,6 +829,8 @@ def gather() -> None:
|
|||||||
grp = build['group']
|
grp = build['group']
|
||||||
if not do_android and grp == 'android':
|
if not do_android and grp == 'android':
|
||||||
continue
|
continue
|
||||||
|
if not do_apple and grp == 'apple':
|
||||||
|
continue
|
||||||
builddir = f'src/external/python-{grp}{bsuffix2}'
|
builddir = f'src/external/python-{grp}{bsuffix2}'
|
||||||
header_dst = os.path.join(builddir, 'include')
|
header_dst = os.path.join(builddir, 'include')
|
||||||
lib_dst = os.path.join(builddir, 'lib')
|
lib_dst = os.path.join(builddir, 'lib')
|
||||||
|
|||||||
@ -67,7 +67,7 @@ from batools.pcommand import (
|
|||||||
python_version_android,
|
python_version_android,
|
||||||
python_version_apple,
|
python_version_apple,
|
||||||
python_build_apple,
|
python_build_apple,
|
||||||
python_version_build_base,
|
python_version_android_base,
|
||||||
python_build_apple_debug,
|
python_build_apple_debug,
|
||||||
python_build_android,
|
python_build_android,
|
||||||
python_build_android_debug,
|
python_build_android_debug,
|
||||||
@ -75,6 +75,8 @@ from batools.pcommand import (
|
|||||||
python_android_patch_ssl,
|
python_android_patch_ssl,
|
||||||
python_apple_patch,
|
python_apple_patch,
|
||||||
python_gather,
|
python_gather,
|
||||||
|
python_gather_apple,
|
||||||
|
python_gather_android,
|
||||||
python_winprune,
|
python_winprune,
|
||||||
capitalize,
|
capitalize,
|
||||||
upper,
|
upper,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user