1
0
Fork 0

Compare commits

...

843 Commits
master ... idac

Author SHA1 Message Date
Dniel97 0eefb2c592
idac: fixed in store battle bug 2024-04-15 20:22:45 +02:00
Dniel97 2b014178c8
Merge remote-tracking branch 'origin/develop' into idac 2024-04-13 23:09:51 +02:00
Hay1tsme 69c76f1116 Merge pull request 'chore[docs/game_specific_info.md]: Remove --game flag from dbutils.py lines.' (#124) from Vanilla/artemis:develop into develop
Reviewed-on: Hay1tsme/artemis#124
2024-04-13 20:22:31 +00:00
Vanilla 201630f1d5
chore[docs/game_specific_info.md]: Remove --game flag from dbutils.py lines. 2024-04-11 17:26:14 +08:00
Hay1tsme ed4031feca wacca: block unregistered serials if set 2024-04-08 17:07:18 -04:00
Midorica 8b5825bec4 fixed documentation revision for alembic changes & changelog update 2024-04-08 09:47:56 -04:00
beerpsi d939755574 [mai2] Support maimai DX International (#118)
Reviewed-on: Hay1tsme/artemis#118
Co-authored-by: beerpsi <beerpsi@duck.com>
Co-committed-by: beerpsi <beerpsi@duck.com>
2024-04-07 19:12:12 +00:00
topty 0c1c24148d fix: mai2 DX reboot time from config (#120)
Reviewed-on: Hay1tsme/artemis#120
Co-authored-by: topty <topty@noreply.gitea.tendokyu.moe>
Co-committed-by: topty <topty@noreply.gitea.tendokyu.moe>
2024-04-07 19:11:35 +00:00
Hay1tsme 926713431d Merge branch 'develop' of https://gitea.tendokyu.moe/Hay1tsme/artemis into develop 2024-04-02 16:43:16 -04:00
Hay1tsme 1072a9d63b database: fix create_revision_auto 2024-04-02 16:43:13 -04:00
Dniel97 9379172791
idac: battle gift event, tips, QoL improvements added 2024-04-01 20:19:37 +02:00
Dniel97 c741c052e9
Merge branch 'develop' into idac 2024-04-01 20:09:34 +02:00
Hay1tsme 7e709db0cc fix get_ip_addr 2024-03-29 20:13:15 -04:00
Hay1tsme 10582cc1fc ongeki: fix frontend profiles not loading due to missing await 2024-03-29 20:09:22 -04:00
Dniel97 9692aea87b
idac: hotfix profile import 2024-03-26 22:44:50 +01:00
Dniel97 bfebe69a74
Merge branch 'develop' into idac 2024-03-26 22:42:16 +01:00
Hay1tsme 87c7c91e3a add ability to add users, cards, arcades and cabs on the webui 2024-03-23 20:01:32 -04:00
Midorica de2e2349e6 Merge pull request 'maimai DX BUDDiES support' (#117) from Dniel97/artemis:mai2_buddies into develop
Reviewed-on: Hay1tsme/artemis#117
2024-03-22 19:13:39 +00:00
Dniel97 3613f4dbd2
mai2: BUDDiES support added 2024-03-20 21:42:38 +01:00
Dniel97 3cd3910b0d
Merge branch 'develop' into fork_develop 2024-03-20 20:37:32 +01:00
Midorica 942b636b3e cxb: fixing rev s1 support 2024-03-18 22:33:39 -04:00
zaphkito 75bf8f4cb7 add python-multipart to requirements.txt (#112)
because of frontend now required python-multipart in requestments

Reviewed-on: Hay1tsme/artemis#112
Co-authored-by: zaphkito <zaphkito@noreply.gitea.tendokyu.moe>
Co-committed-by: zaphkito <zaphkito@noreply.gitea.tendokyu.moe>
2024-03-14 14:48:06 +00:00
beerpsi 40a0817009 CHUNITHM & O.N.G.E.K.I.: Handle userRatingBase*List (#113)
These tables are not used by the game, but are useful for anyone wanting to develop a web UI showing what the player's rating consists of. As such, instead of storing them in JSON columns, I've split them out, one row per each entry.

Reviewed-on: Hay1tsme/artemis#113
Co-authored-by: beerpsi <beerpsi@duck.com>
Co-committed-by: beerpsi <beerpsi@duck.com>
2024-03-14 14:44:32 +00:00
Hay1tsme 346f82a32a wacca: move allowed_stages into __init__ for s 2024-03-12 14:20:36 -04:00
Hay1tsme f71591e622 wacca: fix S stage up stages 2024-03-12 14:17:26 -04:00
Dniel97 7066651549
Merge branch 'develop' into fork_develop 2024-03-12 18:34:04 +01:00
Dniel97 a1a43130bf
idac: bugfixes 2024-03-12 18:20:32 +01:00
Dniel97 88b3cfc750
idac: fixed frontend 2024-03-12 17:48:12 +01:00
Dniel97 a0fba8c3a4
Merge branch 'develop' into idac 2024-03-12 15:29:05 +01:00
Hay1tsme 84fa139357 idac: add missing await, fixes #111 2024-03-12 10:14:03 -04:00
Dniel97 752bb3f244
Merge branch 'develop' into idac 2024-03-12 14:33:25 +01:00
Hay1tsme b63c2c2d12 dbutils: add autocreate option to revision 2024-03-11 21:24:57 -04:00
Hay1tsme 6557cac55b ongeki: add support for bright memory act 2 final 2024-03-07 15:20:02 -05:00
Hay1tsme c4deff9d1c add database downgrading 2024-03-04 00:50:51 -05:00
Midorica bc2b883fa9 sao: quick fix to the documentation 2024-03-03 14:23:19 -05:00
Midorica 86f419278d sao: adjust documentation 2024-03-03 12:25:25 -05:00
Midorica 3fe8bc8c62 sao: fixing default party 2024-03-02 19:44:16 -05:00
Midorica 3557578bcd sao: quick fixes to profile and rewards 2024-03-02 19:38:34 -05:00
Hay1tsme 6f654f8ba9 mai2: remove redundant method (fixes #103) 2024-03-02 18:00:14 -05:00
Hay1tsme 3559d56ccf cm: add awaits (fixes #104) 2024-03-02 17:55:41 -05:00
Hay1tsme e69149632f frontend: fix namechange and password change failing 2024-03-02 17:52:01 -05:00
Hay1tsme bd97428166 frontend: fix account and owner creation (fixes #108) 2024-03-02 17:48:48 -05:00
Hay1tsme 84c059ed75 idac: add missing await to handle_user_updatespecialmoderesult_request (closes #109) 2024-03-02 17:28:19 -05:00
zaphkito 91f49f52cc fix coroutine CardMakerReader.read_*** was never awaited (#105)
Reviewed-on: Hay1tsme/artemis#105
Co-authored-by: zaphkito <zaphkito@noreply.gitea.tendokyu.moe>
Co-committed-by: zaphkito <zaphkito@noreply.gitea.tendokyu.moe>
2024-03-02 22:25:54 +00:00
Hay1tsme f708b8ea4f wacca: fix lily handle_user_status_update_request 2024-02-27 20:42:20 -05:00
Midorica b83399cef4 cxb: hotfix to classic rev 2024-02-26 18:35:34 -05:00
Hay1tsme 43d0edf036 docs: add note about memcached 2024-02-26 00:18:31 -05:00
Hay1tsme 4363b8321b mucha: UTC_SERVER_TIME -> SERVER_TIME_UTC 2024-02-24 16:49:02 -05:00
Hay1tsme b7f56c20a7 wacca: fix version string printing 2024-02-17 00:21:37 -05:00
Hay1tsme b462a2720a fix database config field protocol 2024-02-14 16:56:40 -05:00
Hay1tsme 89c2b324dc aimedb: add last login time update to register endpoints 2024-02-12 15:58:02 -05:00
Hay1tsme 4149944d56 aimedb: update card last login time properly 2024-02-12 15:53:28 -05:00
Hay1tsme d4e00781c8 ongeki: Fix error loading user music (#100) 2024-02-12 11:40:24 -05:00
Hay1tsme 50f42f850a idac: fix database issues 2024-02-09 10:48:39 -05:00
Hay1tsme d38207cdc3 fix billing server not working on some games 2024-02-09 09:56:38 -05:00
Midorica b0f603cbd8 Merge pull request 'SAO: Adding assets for the reader & edited game specific info' (#99) from Midorica/artemis:develop into develop
Reviewed-on: Hay1tsme/artemis#99
2024-02-02 20:16:04 +00:00
Midorica 864b749f20 sao: adding reader assets 2024-02-02 15:10:36 -05:00
Hay1tsme 9844b7ebdb add license 2024-01-29 02:06:59 -05:00
Hay1tsme 77dfec0483 docs: inf 2024-01-25 16:29:53 -05:00
Hay1tsme c7aa3f2479 docs: i suck at typing 2024-01-25 16:09:34 -05:00
Hay1tsme 9f67079614 docs: fix typos 2024-01-25 15:24:21 -05:00
Hay1tsme d2177bdbed docs: add windows install guide 2024-01-25 15:12:26 -05:00
Hay1tsme ce5888227e idz: fix userdb 2024-01-23 11:31:09 -05:00
Hay1tsme 83b981917e sao: add logout_ticket_unpurchased, get_quest_hierarchy_progress_degrees_ranking_list and get_quest_popular_hero_log_ranking_list 2024-01-22 17:42:58 -05:00
Hay1tsme 3a710c671e sao: fix data issues 2024-01-22 16:45:09 -05:00
Hay1tsme 348edef0f5 idac: add missing await 2024-01-22 16:35:43 -05:00
Hay1tsme 60e9cd2edd sao: fix bad find and replace 2024-01-22 16:30:32 -05:00
Hay1tsme 404496191d mucha: remove print statement 2024-01-22 16:25:51 -05:00
Hay1tsme 8864b2625d fix proxy ports 2024-01-22 16:21:49 -05:00
Hay1tsme 200bfe7d84 mucha: oops 2024-01-22 15:55:10 -05:00
Hay1tsme 6ed80ecbab fix ongeki and sao 2024-01-22 15:49:06 -05:00
Hay1tsme 9a021d4005 mucha: add shop info 2024-01-22 15:32:43 -05:00
Hay1tsme 66c9548dd2 mucha: move to starlette 2024-01-22 15:03:59 -05:00
Hay1tsme 029be55cc0 mucha: fix handlers 2024-01-22 14:59:53 -05:00
Hay1tsme 5c9570d421 allnet: fix placeid formatting 2024-01-22 14:47:36 -05:00
Hay1tsme 8d416d7633 allnet: add exception handling if get_allnet_info fails 2024-01-22 14:44:21 -05:00
Hay1tsme a15ad77f20 csb: fix get_allnet_info 2024-01-22 14:42:13 -05:00
Hay1tsme 384924335c allnet: populate title registry when launching standalone 2024-01-22 14:42:02 -05:00
Midorica 58c7d3f22d readme: fixing typo for maimai 2024-01-18 14:28:32 -05:00
Midorica 1cced47c1d Merge pull request 'Modifying readme to add game version names' (#95) from Midorica/artemis:develop into develop
Reviewed-on: Hay1tsme/artemis#95
2024-01-18 18:36:29 +00:00
Midorica 4b2bce9d12 fixing the order of the games in the readme 2024-01-18 13:35:34 -05:00
Midorica 220ba8ba54 modifying readme for game versions 2024-01-18 13:13:36 -05:00
Hay1tsme 1c3f52974d cardmaker: add missing await (thanks galexion!) 2024-01-16 19:19:25 -05:00
Hay1tsme de013766f6 cardmaker: fix method not allowed 2024-01-16 02:47:59 -05:00
Hay1tsme a4262b9285 update install guides 2024-01-14 18:40:14 -05:00
Hay1tsme f41eb907ef update prod.md 2024-01-14 17:46:52 -05:00
Hay1tsme a3b1d07c73 update nginx example config 2024-01-14 17:42:58 -05:00
Hay1tsme 7cf8b4fd5f aimedb: allow listen address override 2024-01-14 16:48:41 -05:00
Hay1tsme e36989e7cc billing: fix overzealous find and replace 2024-01-14 16:43:25 -05:00
Hay1tsme 1003651fb4 add alembic to requirements.txt 2024-01-14 03:34:45 -05:00
Hay1tsme 97e5c29cf5 frontend: utf16 -> utf8 2024-01-13 17:15:02 -05:00
Hay1tsme cdc1b302f2 docs: fix inaccurate config description 2024-01-13 01:34:57 -05:00
Hay1tsme ff26485c93 update config documentation 2024-01-13 01:32:19 -05:00
Hay1tsme e80b0671c2 frontend: add `charset=utf16` 2024-01-12 18:03:44 -05:00
Hay1tsme ae09c2ad48 chuni: add frontend 2024-01-12 18:03:11 -05:00
UncleJim 74a671aff5 bugfixing to course info saving method (#9)
Reviewed-on: Dniel97/artemis#9
Co-authored-by: UncleJim <unclejim@noreply.gitea.tendokyu.moe>
Co-committed-by: UncleJim <unclejim@noreply.gitea.tendokyu.moe>
2024-01-12 14:32:44 +00:00
Hay1tsme 9a7d5bc689 fix logging 2024-01-11 21:01:51 -05:00
Hay1tsme fc3f0900b3 dbutils: add create-revision command 2024-01-11 20:48:27 -05:00
Hay1tsme 19fd008598 fix legacy upgrade script 2024-01-11 20:39:29 -05:00
Hay1tsme ba7cb07208 update default config value 2024-01-11 20:34:53 -05:00
Hay1tsme 31969221e2 sao: change handle_c12a to async 2024-01-11 20:25:30 -05:00
Hay1tsme df4c667adf Merge branch 'starlette_cleanup' into develop 2024-01-11 20:24:59 -05:00
Hay1tsme 72bac55353 update docs 2024-01-11 12:36:34 -05:00
Hay1tsme bb752563cc aimedb: add enable toggle 2024-01-11 12:25:35 -05:00
Hay1tsme a676e42a59 fix old value being in config 2024-01-11 12:16:45 -05:00
Hay1tsme 6dd46a5aa7 fix default config typo 2024-01-11 12:09:19 -05:00
Hay1tsme fb36cc9b07 configuration changes, update docs 2024-01-11 12:08:22 -05:00
Hay1tsme 3bfc9cc845 sao: stub give_free_ticket 2024-01-10 12:51:40 -05:00
Hay1tsme 06e7288cad ongeki: fix frontend page 2024-01-09 21:16:22 -05:00
Hay1tsme d01ceab92f idac: remove hanging "'s" on frontend if the person viewing the page doesn't have a profile 2024-01-09 21:03:58 -05:00
Hay1tsme 0a56207e90 frontend: fix cab list on arcade page 2024-01-09 20:08:10 -05:00
Hay1tsme d672edb266 fix searching for an arcade by serial 2024-01-09 19:45:39 -05:00
Hay1tsme 805b8f5b3e allnet: fix error parsing dli files 2024-01-09 19:10:54 -05:00
Hay1tsme 8a99f94c93 aimedb: fix felica lookups failing 2024-01-09 19:10:39 -05:00
Hay1tsme 5e6efbd092 wacca: fix network BAD 2024-01-09 19:10:24 -05:00
Hay1tsme be0e407ebe wacca: fix hash 2024-01-09 18:49:43 -05:00
Hay1tsme c680c2d4e9 readd get_title_port_ssl 2024-01-09 17:49:18 -05:00
Hay1tsme 261d09aaef dbutils: add legacy migration 2024-01-09 17:11:49 -05:00
Hay1tsme 2c1958eb04 fix SDDT v5 rollback script 2024-01-09 16:23:08 -05:00
Hay1tsme 37304500a5 fix SDDT v5 rollback and v6 upgrade scripts 2024-01-09 16:22:43 -05:00
Hay1tsme e6dad1cb34 ongeki: fix v6 upgrade script 2024-01-09 16:08:48 -05:00
Hay1tsme 2d95e29f3c remove unused imports 2024-01-09 15:59:58 -05:00
Hay1tsme f8c77e69ed remove unused table 2024-01-09 15:56:49 -05:00
Hay1tsme 9dab26b122 port frontend changes from different project 2024-01-09 15:54:34 -05:00
Hay1tsme e27ac4b81f fix dockerfile 2024-01-09 14:46:43 -05:00
Hay1tsme 05586df08a move to async database 2024-01-09 14:42:17 -05:00
Hay1tsme edd3ce8ead move to alembic 2024-01-09 13:57:59 -05:00
Hay1tsme 07cbbcc377 idac: fix matching 2024-01-09 12:52:53 -05:00
Hay1tsme 4bc77a8ef6 cxb: impl JSONResponseNoASCII 2024-01-09 12:44:48 -05:00
Hay1tsme 1c22c6bec0 idac: add echo server 2024-01-09 12:41:47 -05:00
Hay1tsme 7b49a8ab58 add JSONResponseNoASCII 2024-01-09 12:41:32 -05:00
Hay1tsme f65aa4d60a idz: add socket servers 2024-01-09 12:21:31 -05:00
Hay1tsme 4b9db8be3b add uvicorn to requirements 2024-01-09 11:49:22 -05:00
Hay1tsme 5c124a7d61 chuni: remove semicolon 2024-01-09 11:48:28 -05:00
Hay1tsme b0536e45ed chuni: remove semicolon 2024-01-09 11:43:43 -05:00
Hay1tsme 08c831ff8c add missing credit to changelog 2024-01-09 10:35:50 -05:00
Hay1tsme 0c3924a8f0 aimedb: fix lookup with banned or locked access code 2024-01-09 10:33:48 -05:00
zaphkito 4550cb2af5 delete "ADD config config" (#83)
this is because everywhere about docker build write to create and use configs/config folder, not config, if we follow docker installation guide, there will not exist config folder in artemis root. Even if the folder exists, the folder will not be mapped inside the container, then we will get a build error about config not exist
in docker-compose.yml
```
  app:
    hostname: ma.app
    build: .
    volumes:
      - ./aime:/app/aime
      - ./configs/config:/app/config
```

in INSTALL_DOCKER.md

- Create 'aime', 'configs', 'AimeDB', and 'logs' folder in ARTEMiS root folder (where all source files exist)
- Inside configs folder, create 'config' folder, and copy all .yaml files from example_config to config (thats all files without nginx_example.conf)
- Edit .yaml files inside configs/config to suit your server needs
- Edit core.yaml inside configs/config

I checked this file history
in d1259509ad, phantomlan already delete this, but don't know why it come back between 040742a9a4 and 565dc38e9a, maybe git bug? However, now delete it again

Reviewed-on: Hay1tsme/artemis#83
Co-authored-by: zaphkito <zaphkito@noreply.gitea.tendokyu.moe>
Co-committed-by: zaphkito <zaphkito@noreply.gitea.tendokyu.moe>
2024-01-09 15:31:03 +00:00
Hay1tsme 4933ad72f2 adb: change warning to debug 2024-01-09 03:17:23 -05:00
Hay1tsme cc68b7f6c6 adb: fix timeout issue 2024-01-09 03:16:31 -05:00
Hay1tsme 14fa0f5e8e begin move 2024-01-09 03:07:04 -05:00
Hay1tsme b056ff218d Update changelog 2024-01-08 21:25:45 -05:00
Hay1tsme 01591a0456 chuni: revert changes to udpHolePunchUri and reflectorUri 2024-01-08 21:23:10 -05:00
Hay1tsme b4b40869c1 chuni: add roomId to handle_get_matching_state_api_request, update changelog 2024-01-08 20:21:27 -05:00
Hay1tsme c9dfbc77c4 Merge pull request 'Fix issue in handle_get_user_music_api_request for Chunithm where songs would not always return all scores' (#92) from Kumubou/artemis:develop into develop
Reviewed-on: Hay1tsme/artemis#92
2024-01-09 01:20:46 +00:00
Hay1tsme 1b3e43b918 chuni: Imrpove `GetGameSetting` request handling for different versions 2024-01-08 18:30:03 -05:00
Hay1tsme f5c77f04fa update changelog 2024-01-08 18:23:15 -05:00
Hay1tsme 27bf51f9f8 chuni: add matching config, stun turn stuff 2024-01-08 18:22:09 -05:00
Hay1tsme 343424e26a update changelog 2024-01-08 18:00:06 -05:00
Dniel97 7639c2ef74
idac: updated scripts + bugfixes, BACKUP DATABASE! 2024-01-08 18:36:46 +01:00
Dniel97 9ee58cd444
idac: bugfixes 2024-01-08 16:51:59 +01:00
Dniel97 e08acf8ee4
idac: added basic update/rollback scripts 2024-01-08 16:45:43 +01:00
Dniel97 62937584bc
Merge branch 'online' into idac 2024-01-08 16:42:46 +01:00
UncleJim c0078b252f Update titles/idac/const.py 2024-01-08 10:21:30 +00:00
UncleJim 4c1603db09 Update titles/idac/season2.py 2024-01-08 03:10:34 +00:00
UncleJim 3dd23d4b53 Added past round event data for season 1 2024-01-08 03:09:17 +00:00
UncleJim a883450e5c oops 2024-01-08 02:39:31 +00:00
UncleJim acc3a63030 removed excess columns 2024-01-08 02:38:35 +00:00
UncleJim 3426a37d2d fix typo 2024-01-06 14:05:28 +00:00
UncleJim f225b732f2 implement new vs_info saving method 2024-01-06 14:04:47 +00:00
UncleJim a42993aca7 implement new vs_info saving method 2024-01-06 14:01:14 +00:00
UncleJim 39031757f4 Implmented course_info query method 2024-01-06 08:52:35 +00:00
UncleJim a2975fc979 hotfix 2024-01-05 04:07:01 +00:00
UncleJim b1026fdec0 splited course_info from idac_user_vs_info table 2024-01-05 04:04:31 +00:00
Kumubou 5f33b2d3e4 Fix issue in handle_get_user_music_api_request where songs would not always return all scores 2024-01-03 22:05:24 +00:00
Dniel97 0b38778c19
Merge branch 'develop' into idac 2024-01-03 18:30:42 +01:00
Midorica 19baf05d7b cxb: fixing get_allnet_info 2023-12-29 13:59:53 -05:00
Midorica 32419faf01 Update docs/game_specific_info.md
Removing unused option details for cxb
2023-12-29 03:20:44 +00:00
Midorica bf7d709b49 chuni: fixing the network encryption for Sun Plus 2023-12-15 22:22:36 -05:00
Midorica 37b1f41b44 fixing the upgrade & downgrade script for chunithm 2023-12-15 22:21:05 -05:00
Midorica 7b8611cee3 Merge pull request 'Chunithm Sun Plus, Int support' (#78) from EmmyHeart/artemis-sp:develop into develop
Reviewed-on: Hay1tsme/artemis#78
2023-12-16 03:20:02 +00:00
EmmyHeart d0f8568e17 Fixed Rival Music API not returning everything due to iteration issues 2023-12-13 23:28:00 +00:00
EmmyHeart fd0fefa48b Added Sun Plus to example config
my bad
2023-12-13 12:28:28 +00:00
EmmyHeart b5462276f5 Last remnant. Goodbye, rank scaling. 2023-12-13 07:35:07 +00:00
EmmyHeart 626ea58f75 Updated game specific info for Sun Plus
Also removed some old info about rank scaling, since it got YEETed
2023-12-13 07:34:37 +00:00
EmmyHeart 40e6e42c87 Updated readme.md 2023-12-13 07:33:01 +00:00
EmmyHeart a2d238857c Fixed CM compatibility with new Chuni URL structure 2023-12-13 06:37:30 +00:00
EmmyHeart a6b315185d Incremented schema version 2023-12-13 06:35:55 +00:00
EmmyHeart 9b9d1f664d Rollback for Sun Plus changed
should hopefully never need to use this
2023-12-13 06:34:32 +00:00
EmmyHeart b6d1f2cd3a Updated playlog and best tables for Sun Plus
Lord have mercy, this is my first schema update
2023-12-13 06:33:06 +00:00
EmmyHeart 0060122613 Recognize Chunithm Int code 2023-12-13 06:09:02 +00:00
EmmyHeart 97963adf38 Updated matchnaming URLs to use game codes 2023-12-13 06:07:40 +00:00
EmmyHeart 1bfe3584b1 Updated matchmaking URLs to reflect game codes 2023-12-13 06:06:16 +00:00
EmmyHeart ec4ae98beb Updated matchnaming URLs to utilize game codes 2023-12-13 06:05:46 +00:00
EmmyHeart a0dc8421eb Updated schema for changes in Sun Plus
isClear and isSuccess are now integers to represent different clear types, although more research is needed into what those types mean.
2023-12-13 06:04:21 +00:00
EmmyHeart ed731e7407 Added Sun Plus support 2023-12-13 06:02:32 +00:00
EmmyHeart 15204f8d8a Added Sun Plus support, and Int workaround
Int versions of Chunithm use nearly the same endpoints, just with C3Exp at the end. We can treat them the same as the regular versions of the game for now by simply removing the C3Exp portion of the endpoints and running our current logic. Note that later we should treat Int and JP as separate versions
2023-12-13 06:01:31 +00:00
EmmyHeart 32362dbe1e Added const support for both International and Sun Plus 2023-12-13 05:58:22 +00:00
EmmyHeart 59db7ad44a Fixed a null condition when importing from Aqua 2023-12-13 05:56:40 +00:00
Hay1tsme 5491266a85 sao: fix https 2023-12-10 18:35:59 -05:00
Hay1tsme 283cf41bce chuni: bandaid fix for if a user or team is named 'true' or 'false', fixes #65 2023-12-10 17:41:05 -05:00
Hay1tsme 26cdc6c10f maidx: ignore guest upserts, fixes #74 2023-12-10 17:36:29 -05:00
Hay1tsme c1412ca9a8 sao: fix title server port 2023-12-10 17:27:50 -05:00
Hay1tsme 21492fbfb3 wacca: hotfix handle_housing_start_request 2023-12-07 18:53:51 -05:00
Hay1tsme 82c5ae3ad7 wacca: fix housing/start on lilyr and above 2023-12-07 16:07:17 -05:00
Dniel97 7d0cf6a0c4
idac: show both attract videos 2023-12-06 19:09:15 +01:00
Dniel97 95dacccc0e
idac: small timeRelease hotfix 2023-12-06 16:32:22 +01:00
Dniel97 dace9ae980
idac: enabled Touhou attract video and added documentation 2023-12-06 16:06:57 +01:00
Dniel97 6ef8fc4d57
Merge branch 'develop' into idac 2023-12-06 15:57:44 +01:00
Hay1tsme 262155f83f ongeki: fix handle_upsert_client_setting_api_request 2023-12-04 11:40:20 -05:00
Hay1tsme 936316f129 ongeki: fix put_client_setting_data 2023-12-04 11:34:58 -05:00
Hay1tsme 104a483f4c maimai: fixes for finale 2023-12-02 22:30:55 -05:00
Hay1tsme 29a1dac983 Merge pull request 'Fixed typo in game_specific_info.md' (#73) from AkaiiKitsune/artemis:akaiikitsune-typo into develop
Reviewed-on: Hay1tsme/artemis#73
2023-12-03 02:13:56 +00:00
Hay1tsme 8c0ebbd21b mai: fix pre-dx 2023-12-02 21:01:55 -05:00
Hay1tsme d024b2eeb8 billing: fixes 2023-12-02 21:01:33 -05:00
Hay1tsme ed2d306dee adb: add configurable auth id lifetime 2023-11-30 18:22:01 -05:00
Hay1tsme ffc0f2fa5e add keychip ID to auth key 2023-11-30 18:17:40 -05:00
Hay1tsme a1569bca9d fix typing for create_sega_auth_key 2023-11-30 18:14:20 -05:00
Hay1tsme cc86d4509e adb: fix for when user_id is None 2023-11-30 18:10:22 -05:00
Hay1tsme 216fa49f8b aimedb: implement auth keys 2023-11-30 02:07:24 -05:00
Hay1tsme 662fd05d24 ongeki: bump schema ver, fix error if events lookup fails 2023-11-29 18:56:57 -05:00
Hay1tsme 46f61325cb aimedb: soft impl of auth ids 2023-11-29 18:01:19 -05:00
Hay1tsme e7fb9ce07d mai2: fix get_allnet_info 2023-11-29 11:39:19 -05:00
Farewell_ fbc0e489bc Fixed typo in game_specific_info.md
I suck at git; The Maimai version table has been fixed as suggested.
2023-11-29 11:34:15 +00:00
Hay1tsme 6774716e06 add sega auth key encode/decode in Utils, requires pyjwt 2023-11-27 16:39:50 -05:00
Midorica 2efdf79b87 Merge pull request 'fixup technical challenge event' (#71) from phantomlan/artemis:develop into develop
Reviewed-on: Hay1tsme/artemis#71
2023-11-25 23:24:00 +00:00
phantomlan cf0c34cafb fixup technical challenge event 2023-11-26 00:12:23 +01:00
Midorica 5fd966eaad cxb: adding non-omni music list support 2023-11-25 16:03:26 -05:00
Midorica cdd67ffbc3 cxb: fix data handlers for s1 & add coupons for s2 2023-11-25 15:41:38 -05:00
Midorica 7fb98118b2 cxb: fixing index and rss2 /data handler 2023-11-25 13:23:39 -05:00
Hay1tsme fe25359e8e chuni: add userRecentPlayerList check in upsertuserall 2023-11-25 01:00:49 -05:00
Hay1tsme 468c99c3c2 add gitattributes to hopefully stop git from clowing our CSV, TXT and JSON data files 2023-11-25 00:43:48 -05:00
Hay1tsme 6504f120ad chuni: bandaid fixes for air 2023-11-25 00:31:15 -05:00
Hay1tsme d769285d89 Merge branch 'develop' of https://gitea.tendokyu.moe/Hay1tsme/artemis into develop 2023-11-24 10:36:47 -05:00
Hay1tsme ad61bb3d9b mai2: fis issue with saving and loading charges 2023-11-24 10:36:46 -05:00
Dniel97 e124d5e52e
idac: added scrolling up after page change 2023-11-22 01:25:12 +01:00
Dniel97 989cbdf748
idac: added TA eval ranks to the frontend 2023-11-21 23:54:02 +01:00
Dniel97 6ea8cca1a2
idac: added "simple" ranking to frontend 2023-11-21 22:51:10 +01:00
Hay1tsme e941c6a606 add host field to allnet response for some games to fix an issue with nginx 2023-11-20 11:54:13 -05:00
UncleJim ecd63c02e3 Update titles/idac/schema/item.py 2023-11-20 04:54:05 +00:00
UncleJim 581c1506a1 Update titles/idac/season2.py 2023-11-20 04:50:23 +00:00
UncleJim 4d5823a4f7 Update titles/idac/season2.py 2023-11-20 04:42:28 +00:00
UncleJim df9ba9beda Update titles/idac/season2.py 2023-11-20 04:40:32 +00:00
Hay1tsme 8bd361d3e3 wacca: fix some logging 2023-11-19 12:15:57 -05:00
Hay1tsme 0eee2e92a8 wacca: fix bad items being given 2023-11-19 11:43:46 -05:00
Hay1tsme b4a0d331d4 wacca: fix housing/start for unregistered serials 2023-11-19 11:28:42 -05:00
UncleJim 77b22cd77d fix vs_info handles of getdata 2023-11-19 11:57:32 +00:00
UncleJim 25ab37561d fix the logic 2023-11-19 11:54:04 +00:00
Hay1tsme d467070ba7 wacca: fix handle_user_status_create_request for lily 2023-11-19 01:18:09 -05:00
Hay1tsme e7f35db3a4 fix comparability with python 8.7 - 3.10 2023-11-19 01:05:34 -05:00
Hay1tsme 0422a2bfd4 wacca: fix lily using wrong handler for hosuing/start 2023-11-19 01:05:16 -05:00
Hay1tsme 05be10dabc wacca: add request validation, fix HousingStartRequestV2 2023-11-17 11:37:11 -05:00
Hay1tsme 9a017f75e9 wacca: add traceback printing 2023-11-17 10:57:12 -05:00
Hay1tsme 69cd79003c wacca: fix exception handling 2023-11-17 10:45:18 -05:00
UncleJim 9605c13e8e implemented simple queue online matchmaking 2023-11-17 03:36:14 +00:00
UncleJim 9c40bad89c Update titles/idac/schema/item.py 2023-11-17 03:00:41 +00:00
UncleJim d34bc424c2 clean up 2023-11-17 02:56:40 +00:00
UncleJim 913987da1d refactored vs_info 2023-11-17 02:40:09 +00:00
UncleJim 7b58cae214 hotfix 2023-11-17 02:27:16 +00:00
UncleJim cdb040a65e Added round tables, refactored vs_info methonds 2023-11-17 02:03:53 +00:00
Midorica d3b01e03c5 Merge pull request 'Hotfix for mission events' (#68) from phantomlan/artemis:develop into develop
Reviewed-on: Hay1tsme/artemis#68
2023-11-15 14:30:33 +00:00
phantomlan a7e0099294 Merge branch 'develop' of https://gitea.tendokyu.moe/phantomlan/artemis into develop 2023-11-15 15:14:23 +01:00
phantomlan dcebc5c21a update item.py put_mission_point 2023-11-15 15:11:39 +01:00
phantomlan 04c708be8c update item.py put_mission_point 2023-11-15 15:09:57 +01:00
Hay1tsme 67c9ac5ef9 sao: noop get_m_res_earn_campaign_shops 2023-11-15 00:46:51 -05:00
Hay1tsme 328f64ad8a sao: hotfix 2023-11-14 22:11:24 -05:00
Hay1tsme e0aeb2ac21 sao: fix temp responses 2023-11-14 22:08:09 -05:00
Hay1tsme 3d8ffaa098 sao: fix csc fields not being integers 2023-11-14 21:24:33 -05:00
Hay1tsme 7eb095cdb4 soa: fix get_m_gasha_medal_shops 2023-11-14 21:18:22 -05:00
Hay1tsme 9c49a4f54a sao: fix get_m_res_earn_campaign_shops 2023-11-14 21:07:58 -05:00
Hay1tsme dffa11f420 sao: fix get_m_gasha_medal_shops 2023-11-14 20:51:51 -05:00
Hay1tsme 1ba14da90c sao: gasha medal shop handler classes 2023-11-14 13:20:05 -05:00
Hay1tsme 0003ca4412 sao: fix bad handlers 2023-11-13 17:39:45 -05:00
Hay1tsme 0ca041c042 sao: fix date formatting in YuiMedalShopData 2023-11-13 17:36:22 -05:00
Hay1tsme 25528203fe sao: fix encoder helpers 2023-11-13 17:31:23 -05:00
Hay1tsme a06d1aaf8c sao: fix helpers 2023-11-13 17:24:22 -05:00
Hay1tsme e035806b41 sao: fix yui shops 2023-11-13 17:17:27 -05:00
Hay1tsme a9f72cec69 sao: add shop data tables 2023-11-13 14:45:20 -05:00
Hay1tsme 22cf3f83a6 remove randbytes 2023-11-13 13:47:17 -05:00
Hay1tsme ee45c55f66 sao: stub get_shop_resource_sales_data_list 2023-11-13 12:53:40 -05:00
Hay1tsme 2c275d1130 idac: add logging to setup 2023-11-13 12:14:34 -05:00
Midorica b19288fb7e idac: removing public urls 2023-11-13 12:06:14 -05:00
Midorica a83edee657 Merge pull request 'Initial D THE ARCADE support added' (#41) from Dniel97/artemis:idac into develop
Reviewed-on: Hay1tsme/artemis#41
2023-11-13 16:35:53 +00:00
Dniel97 d1a7b898a7
idac: hotfix for title port and changelog 2023-11-13 17:32:39 +01:00
Dniel97 e561f8f15d
idac: updated to latest develop branch, minified timeRelease json 2023-11-13 17:27:14 +01:00
Midorica c9a20b2433 ongeki: fixing render_POST 2023-11-13 10:47:39 -05:00
Midorica 1034a505f0 fixing get_allnet_info for cxb, sao and ongeki 2023-11-13 10:43:38 -05:00
Dniel97 565dc38e9a
Merge branch 'develop' into idac 2023-11-13 16:18:04 +01:00
Midorica 1a4dc88c8a cxb: cleanup of config 2023-11-13 10:12:52 -05:00
Midorica 005adfae38 Merge pull request 'Add Support for Missing API Calls in ONGEKI' (#61) from phantomlan/artemis:develop into develop
Reviewed-on: Hay1tsme/artemis#61
2023-11-13 03:15:47 +00:00
phantomlan 80ae372ccf fix rollback 2023-11-13 04:05:13 +01:00
phantomlan e461ffe587 add more info 2023-11-13 03:38:30 +01:00
phantomlan b56a5f020b add rollback sql
move GP support to table in DB
small fixes
2023-11-13 03:26:09 +01:00
phantomlan d663b1ef7c fix foreign key issue 2023-11-13 03:03:04 +01:00
phantomlan 31d39ffb37 merge conflict fix 2023-11-13 02:01:56 +00:00
phantomlan 530016ef84 i ate one ) 2023-11-13 02:01:56 +00:00
phantomlan f81c53558e - Add ClientTestmode upsert
- Add ClientSetting upsert
- Add endDate for Events
- Add Upgrade Schema
- Small bugfixes for events
2023-11-13 02:01:56 +00:00
phantomlan d2e2c14074 update Event Ranking, Tech Challenge, and Music Ranking 2023-11-13 02:01:56 +00:00
phantomlan 1897e8002d add RewardList support
add PresentList support
add reading rewards to read.py
add Ranking Music List support
2023-11-13 02:01:56 +00:00
phantomlan 4da886a083 - Add Ranking Event Support
- Add Technical Challenge Event Support
- Fix Event Enumeration for EVT_TYPES as Enum starts with 1, to be in spec with what game expects, also add missing Max EVT_TYPE
- Add documentation on how to properly configure and run Events for ONGEKI
2023-11-13 02:01:56 +00:00
Hay1tsme 4bedf71d3d sao: fix trial_tower_play_end 2023-11-12 20:02:08 -05:00
Hay1tsme 84b44d2a02 sao: add logging to synthesize_enhancement_equipment 2023-11-12 19:55:10 -05:00
Hay1tsme b516da2e6e sao: add logging to synthesize_enhancement_hero_log 2023-11-12 19:53:36 -05:00
Hay1tsme a767646361 sao: make synthesize_enhancement_hero_log similar to synthesize_enhancement_equipment 2023-11-12 19:48:03 -05:00
Hay1tsme 3596f0f34c sao: fix synthesize_enhancement_equipment 2023-11-12 19:45:39 -05:00
Hay1tsme ab3b6a9814 sao: fix SaoSynthesizeEnhancementHeroLogRequest 2023-11-12 19:24:08 -05:00
Hay1tsme c384a8d1f5 sao: properly initialize material_common_reward_user_data_count 2023-11-12 19:22:46 -05:00
Hay1tsme 9bc76279f1 sao: fix episode_play_end 2023-11-12 18:46:38 -05:00
Hay1tsme 9026c25fcc sao: fix both unanalyzed log fixed requests 2023-11-12 17:35:06 -05:00
Hay1tsme ff48438b85 sao: fix change_party 2023-11-12 17:26:21 -05:00
Midorica e1a70d1a06 sao: fix dict lengths of multiple response handlers 2023-11-12 16:13:04 -05:00
Midorica 1ff0e4e2c6 sao: fix episode_play_end again 2023-11-11 11:10:57 -05:00
Hay1tsme c23ab1438e sao: remove unused imports 2023-11-11 01:13:09 -05:00
Hay1tsme 773729dc0c sao: fix episode_play_end 2023-11-11 01:11:41 -05:00
Midorica 0e739b2077 adding SSL support for SAO 2023-11-10 23:20:30 -05:00
Midorica c4f2232457 fixing threading profile loading on CXB 2023-11-10 20:32:24 -05:00
Hay1tsme 9fdd96b717 sao: fix synthesize_enhancement_equipment 2023-11-10 18:01:16 -05:00
Hay1tsme c370542761 sao: fix req header 2023-11-10 15:52:55 -05:00
Hay1tsme a299ba98ac sao: fix erronius length errors 2023-11-09 23:37:40 -05:00
Hay1tsme 44755d4d0f sao: begin implementing request helpers 2023-11-09 23:29:06 -05:00
Hay1tsme 3d62b9d372 sao: add SaoResponseHeader 2023-11-09 22:36:33 -05:00
Hay1tsme 18bf250fd7 sao: fix length calculation 2023-11-09 21:23:19 -05:00
Hay1tsme 0fa8fe06f0 sao: fix crypt maybe 2023-11-09 21:17:18 -05:00
Hay1tsme 4cd1181ef7 sao: fix generic handler 2023-11-09 21:09:55 -05:00
Hay1tsme 8d190ce7f0 sao: fix padding 2023-11-09 21:05:30 -05:00
Hay1tsme 81d588cbc7 sao: crypt fixes 2023-11-09 21:00:51 -05:00
Hay1tsme e6801c1c46 sao: add logging 2023-11-09 20:56:33 -05:00
Hay1tsme 840db275fa sao: add encrtped response 2023-11-09 20:56:03 -05:00
Midorica 8769c99f4a fixing ongeki handler 2023-11-09 19:34:04 -05:00
Hay1tsme 3c06f46644 sao: add decoder helpers, hash checks 2023-11-09 18:11:58 -05:00
Hay1tsme 95234a421c sao: fix header 2023-11-09 13:47:50 -05:00
Hay1tsme c425ca1ea8 sao: hotfix 2023-11-09 13:41:56 -05:00
Hay1tsme b0ebcaf0cf sao: parse header. add crypt 2023-11-09 13:16:32 -05:00
Hay1tsme 8560c05928 sao: remove hostname/port config, fix allnet uri 2023-11-09 11:55:49 -05:00
Hay1tsme 580a9faae8 sao: fix allnet 2023-11-09 11:51:50 -05:00
Hay1tsme 9537331d1c sao: fix endpoint 2023-11-09 11:32:04 -05:00
Midorica 4047a38370 adding encryption version check for Ongeki 2023-11-09 00:10:19 -05:00
Midorica eaf64dbd50 Merge branch 'develop' of https://gitea.tendokyu.moe/Hay1tsme/artemis into develop 2023-11-09 00:06:24 -05:00
Midorica e769404e16 fixing TLS support for ongeki 2023-11-09 00:06:15 -05:00
Hay1tsme eb10bc2560 idz: fix news url 2023-11-08 23:54:35 -05:00
Midorica 4b22bd734e fixing handle_data for Crossbeats 2023-11-08 23:36:26 -05:00
Hay1tsme 94c326a27d ongeki: add option to use https for red and beyond 2023-11-08 21:42:53 -05:00
Hay1tsme cb8eaae2c0 Per-version URI/Host (#66)
Allows setting allnet uri/host response based on things like version, config files, and other factors to accommodate a wider range of potential setups under the same roof. This DOES require all titles to adopt a new structure but it's documented and should hopefully be somewhat intuitive.

Co-authored-by: Hay1tsme <kevin@hay1ts.me>
Reviewed-on: Hay1tsme/artemis#66
Co-authored-by: Kevin Trocolli <pitok236@gmail.com>
Co-committed-by: Kevin Trocolli <pitok236@gmail.com>
2023-11-09 02:17:48 +00:00
Midorica c2a330f42c Merge pull request 'fix: dockerfile fixes' (#60) from Rylie/artemis:fix/docker into develop
Reviewed-on: Hay1tsme/artemis#60
2023-11-08 20:53:00 +00:00
Midorica fbd1d7cb1c fixing CXB render_POST 2023-11-06 23:35:02 -05:00
Midorica fbd12fd6e2 fixing get_energy for CXB 2023-11-06 21:54:06 -05:00
Midorica 40e6c018e9 fixing again the render_POST for CXB 2023-11-06 21:04:13 -05:00
Dniel97 4c3852d6d4
IDAC: Added round event "Season 2 2nd" 2023-11-06 19:54:13 +01:00
Dniel97 acac2c4670
IDAC: time trial should display car time now 2023-11-06 18:11:30 +01:00
UncleJim 1224baeade Experimental online match API support and car use countting fix (#5)
Reviewed-on: Dniel97/artemis#5
Co-authored-by: UncleJim <unclejim@noreply.gitea.tendokyu.moe>
Co-committed-by: UncleJim <unclejim@noreply.gitea.tendokyu.moe>
2023-11-06 15:32:40 +00:00
Rylie 040742a9a4
fix(docker): rename `log` to `logs` for consistency 2023-11-05 11:10:01 +07:00
Rylie b4a0736f7b
fix(docker): add pkg-config for docker build process 2023-11-05 11:09:52 +07:00
Hay1tsme a7b0b1be49 allnet: fix dli report logging 2023-11-04 12:41:47 -04:00
Hay1tsme dde397a96c allnet: fix event type for handle_dlorder_report 2023-11-01 20:09:14 -04:00
Hay1tsme f99bf7d1ed allnet: add DLI_STATUS 2023-11-01 20:08:20 -04:00
Hay1tsme bd3e1918aa allnet: further dl report logging 2023-11-01 01:17:23 -04:00
Hay1tsme ffc0a07f6d allnet: fix dl report message typo 2023-11-01 01:02:09 -04:00
Hay1tsme ab640760a9 allnet: add event logging to download report 2023-11-01 00:53:04 -04:00
Midorica a8f06ee266 Merge pull request 'Aime Locks/Bans and Chunithm Improvements' (#47) from EmmyHeart/artemis:develop into develop
Reviewed-on: Hay1tsme/artemis#47
2023-10-27 17:25:35 +00:00
UncleJim 3f192bd84a Proper store name and region handling (#4)
Now the code will read store name from `aime.arcade` table instead of putting `self.core_cfg.server.name` into it braindead-ly

Reviewed-on: Dniel97/artemis#4
Co-authored-by: UncleJim <unclejim@noreply.gitea.tendokyu.moe>
Co-committed-by: UncleJim <unclejim@noreply.gitea.tendokyu.moe>
2023-10-27 11:05:55 +00:00
Hay1tsme 4b83d3f316 Merge pull request 'wacca: name unknown fields, rename incorrect fields.' (#56) from Yellowberry/artemis:wacca_notice_fix into develop
Reviewed-on: Hay1tsme/artemis#56
2023-10-27 02:06:38 +00:00
Midorica 1b5c335f4e Merge pull request 'Chunithm: Fix getUserMusic duplicate songs and fix missing song diffs' (#54) from DSRLIN/artemis-bugfix:develop into develop
Reviewed-on: Hay1tsme/artemis#54
2023-10-27 01:51:43 +00:00
Zsolt Zitting 680223dba2 wacca: name unknown fields, rename incorrect fields. 2023-10-26 14:25:04 -07:00
UncleJim 4fc4d98a74 Always return matching state to keep players out of looping back to matching screen (#6)
Reviewed-on: Dniel97/artemis#6
Co-authored-by: UncleJim <unclejim@noreply.gitea.tendokyu.moe>
Co-committed-by: UncleJim <unclejim@noreply.gitea.tendokyu.moe>
2023-10-26 10:21:01 +00:00
DSRLIN 3259552c29 update bug 2023-10-26 05:38:33 +00:00
DSRLIN b5ce13d1f1 fix chuni songlist duplicate songs and missing diff 2023-10-26 05:37:11 +00:00
Midorica be86448b23 Merge pull request 'Updates and Fixes for Dockerfile and docker-compose, and docker installation guide' (#51) from phantomlan/artemis:develop into develop
Reviewed-on: Hay1tsme/artemis#51
2023-10-22 14:22:41 +00:00
phantomlan 01182087e0 Merge branch 'docker-and-docs' into develop 2023-10-21 20:38:14 +02:00
phantomlan e9637c94de update dx.py 2023-10-21 20:35:46 +02:00
phantomlan 37a6502dc9 Merge branch 'docker-and-docs' into develop 2023-10-21 20:04:16 +02:00
phantomlan d1259509ad update pull request 2023-10-21 19:57:45 +02:00
phantomlan 7f1ff60b9d Update docs/INSTALL_DOCKER.md 2023-10-21 15:54:17 +00:00
phantomlan 12d0a3f927 - prettier docker guide
- update docker-compose to reflect 1 level higher config folder
2023-10-21 17:47:23 +02:00
phantomlan 5420076c8e - update docker-compose database charset and collation to utf8mb4
- update TODO list in docker guide
- update readme.md
2023-10-21 17:33:58 +02:00
phantomlan a3d2955fce - update docker-compose.yml
- update mysqldb to mariadb-alpine
- update mysql healthcheck to also include Password
- update memcached to alpine counterpart
- update phpmyadmin port to not collide with ARTEMiS main app
- add commented out options for DB Persistency

- update Dockerfile
- add read.py to Dockerfile

- add docs/INSTALL_DOCKER.md guide
2023-10-21 17:22:58 +02:00
EmmyHeart 990d60cf27 Forgot to remove rank scale option from the example config, as it does literally nothing now lol 2023-10-20 04:25:08 +00:00
EmmyHeart 21cb37001b Stubbed Team Course functions as they do not currently do anything 2023-10-20 03:31:36 +00:00
EmmyHeart 32903d979e Added code for song rankings using romVersion, and ensuring romVersion is set on playlog upsert 2023-10-20 03:29:05 +00:00
EmmyHeart 3f8c62044c Optimized rival music list, added ranking API, and began work on Team Courses (need help) 2023-10-20 03:26:51 +00:00
EmmyHeart 719ae9cfb1 Added checks for if Aime card is locked or banned, and pass that status to the game 2023-10-20 03:25:12 +00:00
EmmyHeart 8fcd227b33 Added functions for checking if Aime card is locked or banned 2023-10-20 03:24:25 +00:00
Dniel97 b5e3bf6f80
IDAC: Hotfix for UTF-8 encoding 2023-10-19 19:04:06 +02:00
Dniel97 81f7106264
IDAC: avatar gacha updated (HUGE PAIN), config fixes 2023-10-19 18:52:10 +02:00
Midorica 54c77c047e fixing duplicated is_cab value from machine table 2023-10-18 18:44:03 -04:00
Midorica 93b8b86b55 Adding base handler & config for HTTPS title support 2023-10-17 13:12:08 -04:00
Midorica eaa2652647 Merge pull request 'Chunithm Fixes and Maintenance for all!' (#46) from EmmyHeart/artemis:develop into develop
Reviewed-on: Hay1tsme/artemis#46
2023-10-17 17:00:08 +00:00
Midorica 46f83b9925 Merge pull request 'maimai DX FESTiVAL PLUS support' (#44) from Dniel97/artemis:mai2_festival_plus into develop
Reviewed-on: Hay1tsme/artemis#44
2023-10-16 20:00:08 +00:00
Dniel97 862907b82a
mai2: Fixed cards not showing up 2023-10-16 21:36:26 +02:00
EmmyHeart 8c114532e8 Updated config documentation with IP enforcement and reboot times 2023-10-16 13:30:10 +00:00
EmmyHeart 300cd10abf Updated example config with reboot time
You can leave this option out to disable reboot times, and the games will use the prior mechanism of maintenance always being 4~ hours before current time
2023-10-16 13:23:56 +00:00
EmmyHeart 4f0a5f60ab Added config for reboot time support 2023-10-16 13:22:18 +00:00
EmmyHeart 9cff321857 Added reboot time support 2023-10-16 13:21:07 +00:00
EmmyHeart 8d289ca066 Added reboot time support 2023-10-16 13:20:37 +00:00
EmmyHeart e18b87ee5c Added reboot time support 2023-10-16 13:20:00 +00:00
EmmyHeart 540d7fc2c2 Added reboot time support 2023-10-16 13:18:23 +00:00
EmmyHeart 3ef40fe85e Removed Team Rank Scaling and fixed RIval Music Data. Also cleaned up extraneous functions 2023-10-16 13:15:39 +00:00
EmmyHeart 35a7525f74 Removed extraneous function 2023-10-16 13:14:11 +00:00
EmmyHeart d49997f832 Team Rank Scaling is dead, long live Team Rank Scaling 2023-10-16 13:09:53 +00:00
EmmyHeart c78a62de14 Fixed Rival Music retrieval 2023-10-16 13:09:11 +00:00
EmmyHeart 480551f942 Fixed logic error leading to strict IP checking always being enabled 2023-10-16 13:07:05 +00:00
Dniel97 d6e4db48f4
Added maimai DX FESTiVAL PLUS support
- Added Card Maker support for FESTiVAL PLUS
- Bumped SDEZ database to version 8
- Updated docs for FESTiVAL PLUS
2023-10-15 19:04:15 +02:00
Dniel97 346b74e51b
Merge branch 'develop' into fork_develop 2023-10-15 18:06:07 +02:00
Midorica aa9d48ccc9 fixed the diva profile again 2023-10-10 18:37:18 -04:00
Midorica dc02d60690 Merge branch 'develop' of https://gitea.tendokyu.moe/Hay1tsme/artemis into develop 2023-10-10 18:25:01 -04:00
Midorica 6d592dcbc7 fixing skin issue with diva profile 2023-10-10 18:24:54 -04:00
Dniel97 3ee1801ee6
IDAC: Hotfix for play stamp and time trials 2023-10-10 17:58:20 +02:00
Dniel97 31188ccce6
IDAC: Hotfix to block amdaemon_emu users without correct access code 2023-10-10 16:06:58 +02:00
Midorica ab10f27cb4 Merge pull request 'Fixed typo with game_cfg/game_config' (#42) from EmmyHeart/artemis:develop into develop
Reviewed-on: Hay1tsme/artemis#42
2023-10-09 12:38:46 +00:00
EmmyHeart 0a12e93593 news_msg config option doesn't actually return anything. Fixed! 2023-10-09 06:47:55 +00:00
EmmyHeart bad106ceba Fixed typo with game_cfg/game_config
This resulted in an exception on Plost and earlier, leading to games not actually working.
2023-10-09 06:35:14 +00:00
Midorica 7fc5544c15 adding a SAO note to documentation 2023-10-08 15:52:41 -04:00
Dniel97 d55ada2538
IDAC: Improvements
- Fixed play stamp event name not shown
- Fixed broken cars such as: LEVIN SR (AE85), SKYLINE 25GT TURBO (ER34), S2000 (AP1), GR SUPRA (DB42), SWIFT Sport (ZC33S), LANCER RS EVOLUTION Ⅴ (CP9A), LANCER GSR EVOLUTION Ⅵ TMEDITION (CP9A)
- Fixed bunta/special mode not properly updating
- Fixed frontend (for real this time)
2023-10-08 19:56:04 +02:00
Midorica bfaadff9f6 pushing news support customization for chunithm 2023-10-07 12:31:08 -04:00
Midorica 1996f3f356 fix for chunithm song loading after 600 scores 2023-10-07 12:11:24 -04:00
Midorica 56ddd3b1cc fix yet again to the md file 2023-10-07 12:07:14 -04:00
Midorica 41fcadfd55 quick fix to markdown file 2023-10-07 12:04:33 -04:00
Midorica 06d95c8c5f Adding details under the game specific info doc 2023-10-07 11:59:01 -04:00
Hay1tsme b5ccd67940 db: add memcache toggle 2023-10-05 22:16:50 -04:00
Midorica 1f65cfd2eb Merge pull request 'Added Team and Rival support to Chunithm' (#24) from EmmyHeart/artemis:develop into develop
Reviewed-on: Hay1tsme/artemis#24
2023-10-06 01:48:50 +00:00
EmmyHeart 9681f86e33 Chunithm SQL documentation
Figured I would outline all of the neat SQL tricks you can do with this build, as well as properly document the features.
2023-10-05 07:56:46 +00:00
Dniel97 8ccb7f08f2
IDAC: Fixed frontend `user_id` not found 2023-10-03 17:00:59 +02:00
Dniel97 0e664e03f9
IDAC: Updated docs, added TA Data
Known issue: after selecting Season 1, the TA Data for Season 2 will not be shown
2023-10-03 16:50:52 +02:00
Hay1tsme d641705273 pokken: fix last_play_event_id 2023-10-02 15:12:29 -04:00
Hay1tsme 3d13eb1698 pokken: add coalesce 2023-10-01 23:49:47 -04:00
Hay1tsme 33e0288e5e pokken: pokemon data save/load 2023-10-01 22:38:56 -04:00
Dniel97 ee5f13a3bb
IDAC: Added changelog, fixed doc 2023-10-01 22:29:50 +02:00
Dniel97 4ba01cdf20
IDAC: Fixed time trial events config, added new events 2023-10-01 22:17:06 +02:00
Dniel97 7033234b23
IDAC: Added better doc, fixed timetrial event 2023-10-01 21:41:08 +02:00
Dniel97 e0265485ff
Initial D THE ARCADE S2 support added 2023-10-01 03:54:23 +02:00
Dniel97 38c1c31cf5
Merge branch 'develop' into fork_develop 2023-09-25 22:48:53 +02:00
Midorica 91791813dc Merge pull request 'ONGEKI Rival Functionality' (#36) from 2TT/artemis:develop into develop
Reviewed-on: Hay1tsme/artemis#36
2023-09-23 16:08:15 +00:00
Hay1tsme 1497bf4a95 frontend: fixes to searching 2023-09-19 02:42:13 -04:00
Hay1tsme aa17f99252 frontend: add placeholders, arcade search by IP 2023-09-19 02:31:29 -04:00
Hay1tsme 50de0916d4 allnet: minor tweeks to mirrior realnet better 2023-09-19 02:15:22 -04:00
Midorica 2affd0aae9 Merge pull request 'Fix wrong id being used to check for Chunithm WORLD'S END charts' (#33) from ASleepyCat/artemis:fix-worlds-end-id into develop
Reviewed-on: Hay1tsme/artemis#33
2023-09-12 04:36:59 +00:00
ASleepyCat 4a916cb4d1 Fix reading WORLD'S END charts for SDBT 2023-09-10 12:29:19 +10:00
ASleepyCat 6c98f5f0a7 Fix wrong id being used to check for WORLD'S END charts 2023-09-10 12:29:19 +10:00
Midorica 090b3148d8 adding encryption support for ongeki 2023-09-09 12:03:42 -04:00
Wanich Keatkajonjumroen ed46ea33e3 Added version select in session, removed get_profile_data_ignore_version, renamed scripts file 2023-09-09 06:55:18 +09:00
Dniel97 08927db100
Merge branch 'develop' into fork_develop 2023-09-07 15:09:36 +02:00
Hay1tsme 238e39f415 adb: properly handle an incorrect adb status value 2023-09-06 22:47:37 -04:00
Hay1tsme 5499d38bb4 mucha: streamline mucha_postprocess (thanks Bottersnike!) 2023-09-05 17:32:51 -04:00
2TT d584b93ca5 Merge branch 'develop' into develop 2023-09-02 06:39:37 +00:00
Wanich Keatkajonjumroen d5d2803cc0 forgot a print command, see previous commit 2023-09-02 07:50:09 +09:00
Wanich Keatkajonjumroen 16d801aff5 Rival Delete functionality 2023-09-02 07:40:57 +09:00
Wanich Keatkajonjumroen 147d7adaaf Frontend for adding rivals, versionless backend function to support 2023-09-02 06:21:02 +09:00
Wanich Keatkajonjumroen 1f545aed41 removed print statements 2023-09-02 05:04:59 +09:00
Hay1tsme 3a6cfedcca maimai: fix net deliver paths 2023-08-31 01:24:41 -04:00
Hay1tsme 7a6272dcc5 Merge branch 'develop' of https://gitea.tendokyu.moe/Hay1tsme/artemis into develop 2023-08-30 11:19:14 -04:00
Hay1tsme 136e47d1e6 reader: series -> game 2023-08-30 11:19:13 -04:00
Wanich Keatkajonjumroen eaf9e0cf09 removed temp file 2023-08-30 13:53:55 +09:00
Wanich Keatkajonjumroen 0d7409906a fixed backend ongeki rival functionality 2023-08-30 13:53:45 +09:00
Wanich Keatkajonjumroen 5cccc9224a geki version display 2023-08-30 13:53:41 +09:00
Wanich Keatkajonjumroen 2a52e391d6 Ongeki template start 2023-08-30 13:53:31 +09:00
Hay1tsme dac655b4ae adb: add catch for uninitialized amlib requests 2023-08-27 19:18:22 -04:00
Hay1tsme 37e2da2051 maimai: fix usbdl endpoints 2023-08-27 11:53:29 -04:00
Dniel97 9d74d60c14
Merge remote-tracking branch 'origin/develop' into fork_develop 2023-08-21 10:33:25 +02:00
Hay1tsme d4ea3bc12a billing: float5 hotfix 2023-08-21 01:53:27 -04:00
Hay1tsme d8b0e2ea2a billing: add classes and validation, fix response 2023-08-21 01:50:59 -04:00
Hay1tsme 984949d902 allnet: partial DFI implementation 2023-08-21 00:10:25 -04:00
Hay1tsme 2e8d99e5fa allnet: add ip check config option 2023-08-20 23:25:50 -04:00
Hay1tsme fd6cadf2da adb: hotfix 2023-08-20 19:56:16 -04:00
Hay1tsme 71489c1272 adb: fix log_ex 2023-08-20 19:55:26 -04:00
Dniel97 3773c57de1
Merge remote-tracking branch 'origin/develop' into fork_develop 2023-08-20 14:22:58 +02:00
Hay1tsme 8ea82ffe1a adb: add from_req 2023-08-19 01:35:37 -04:00
Hay1tsme 904ea10920 cm: remove print, fix default config 2023-08-19 01:35:15 -04:00
Dniel97 8a8c0e023e
Merge branch 'develop' into fork_develop 2023-08-16 11:02:22 +02:00
Hay1tsme cf7cc0997a mucha: add other request/response structures 2023-08-15 23:19:48 -04:00
Hay1tsme 92567504f4 adb: fix for felica_lookup_ex, for #32 2023-08-15 10:43:49 -04:00
Hay1tsme fd50a7ee68 aimedb_redux (#30)
Update AimeDB from new [documentation](https://minori.tendokyu.moe/docs/allnet/aimedb/) of the protocol.
Reviewed-on: Hay1tsme/artemis#30
2023-08-14 03:32:03 +00:00
Hay1tsme 9e3a51a57a allnet: add IP checking 2023-08-08 12:35:38 -04:00
Hay1tsme 4744e8cf5f add ip checking config options 2023-08-08 10:24:28 -04:00
Hay1tsme 88a1462304 logger: change from warn to warning 2023-08-08 10:17:56 -04:00
Hay1tsme 2e277e7791 add arcade ip column 2023-08-08 10:17:38 -04:00
Hay1tsme 757fdc5c57 mai2: add check for Mai-Encoding headers 2023-08-01 02:34:40 -04:00
Hay1tsme 23bcb5cc13 Merge branch 'develop' of https://gitea.tendokyu.moe/Hay1tsme/artemis into develop 2023-07-25 09:18:47 -04:00
Hay1tsme 9f0c181593 add handler for /mucha_front in addition to /mucha 2023-07-25 09:18:45 -04:00
Midorica 5a4baba102 Diva: Adding threading to score loading 2023-07-24 13:19:54 -04:00
Hay1tsme fc947d36a5 mai2: fix get user music for dx and up 2023-07-24 01:32:56 -04:00
Hay1tsme 156b4e4ede mai2: fix get user music for dx and up 2023-07-24 00:49:08 -04:00
Hay1tsme 6c89a97fe3 frontend: add management pages 2023-07-23 22:21:49 -04:00
Hay1tsme b943807904 core: add columns to machine table, bump to v5 2023-07-23 22:21:41 -04:00
Hay1tsme f417be671b pokken: fix typo 2023-07-23 12:47:37 -04:00
Hay1tsme 20335aaebe add download report api 2023-07-23 12:47:10 -04:00
Dniel97 097181008b
Merge remote-tracking branch 'origin/develop' into fork_develop 2023-07-16 23:41:38 +02:00
Hay1tsme 5f586379ca Merge branch 'develop' 2023-07-16 17:25:18 -04:00
Hay1tsme 63d81a2704 changelog: fix typos 2023-07-16 17:24:07 -04:00
Hay1tsme 718229b267 Update changelog 2023-07-16 17:21:45 -04:00
Hay1tsme 7c78975431 mai2: update example config 2023-07-16 17:00:52 -04:00
Hay1tsme 14a315a673 replace except with except Exception 2023-07-16 16:58:34 -04:00
Hay1tsme 343fe4357c mai2: add image validation via Pillow 2023-07-16 16:58:18 -04:00
Hay1tsme d0e43140ba mai2: fix ghost saving, add memorial photo upload 2023-07-16 16:06:34 -04:00
Dniel97 859bf4bf5d
Merge remote-tracking branch 'origin/develop' into fork_develop 2023-07-16 19:07:56 +02:00
Midorica c6e7100f51 Merge pull request 'O.N.G.E.K.I.: Card Maker fixes, improvements and bug fixes' (#25) from Dniel97/artemis:cardmaker_ongeki_fix into develop
Reviewed-on: Hay1tsme/artemis#25
2023-07-16 17:06:44 +00:00
Midorica b0ca37815b Merge pull request 'maimai DX: Fixes' (#27) from Dniel97/artemis:mai2_fixes into develop
Reviewed-on: Hay1tsme/artemis#27
2023-07-16 17:00:32 +00:00
Dniel97 f39317301b
mai2: fixed update script, added mai2 heredity, fixed cards import 2023-07-15 22:51:54 +02:00
Dniel97 389784ce82
Merge remote-tracking branch 'origin/develop' into fork_develop 2023-07-15 22:47:05 +02:00
Hay1tsme 2f13596885 fix db ignoring port in config, createing database no longer runs over version 2023-07-15 00:15:14 -04:00
Dniel97 6a41dac46c
ongeki: card maker config added, small fixes, improved credits
- Changed the credits config to the default 370 instead of 360
- Added `start_date` to the events to show new events
- Fixed Card Maker Gachas to only allow "Select Gacha" once
- Fixed the `get_profile_rating_log` database query
2023-07-12 11:25:46 +02:00
Hay1tsme 85b73e634d mucha: add DownloadState 2023-07-12 00:41:53 -04:00
EmmyHeart 3c7ceabf4e And again 2023-07-11 10:04:25 +00:00
EmmyHeart 1bc8648e35 I knew I forgot something (fixed config) 2023-07-11 09:56:09 +00:00
EmmyHeart eecd3a829d Added value for team rank scaling, and documented it a bit 2023-07-11 09:40:49 +00:00
EmmyHeart b42e8ab76c Added function for pulling a song via the DB unique ID instead of the native song ID 2023-07-11 09:16:11 +00:00
EmmyHeart 043ff17008 Add team support, rivals, and test function for getting playcounts 2023-07-11 09:14:53 +00:00
EmmyHeart c01d3f49f5 Added call for getting rival's music lists 2023-07-11 09:13:19 +00:00
EmmyHeart 75842b5a88 Add team support, implement team upsert, add rivals 2023-07-11 09:12:34 +00:00
Midorica 09c4f8cda4 Async request to CXB profile loading 2023-07-08 18:44:02 -04:00
Dniel97 36d338e618
Merge remote-tracking branch 'origin/develop' into fork_develop 2023-07-08 17:38:41 +02:00
Hay1tsme 03cf535ff6 make threading optional 2023-07-08 00:34:55 -04:00
Hay1tsme 6c155a5e48 database: add static variables to prevent having multiple sessions 2023-07-08 00:01:52 -04:00
Midorica 737312ca3d Threading support to main twisted reactor 2023-07-07 21:50:24 -04:00
Hay1tsme 1edec7dba2 sao: add response debug logging 2023-07-05 12:35:00 -04:00
Hay1tsme d60f827000 fix typing across multiple games, fixes #23 2023-07-05 10:47:43 -04:00
Midorica da422e602b fixing trial_tower_play_end_unanalyzed_log_fixed for SAO 2023-07-02 15:50:42 -04:00
Midorica d276ac8598 Merge branch 'develop' of https://gitea.tendokyu.moe/Hay1tsme/artemis into develop 2023-07-02 14:25:36 -04:00
Midorica 84e880e94f fixing unanalyzed reward request for SAO 2023-07-02 14:25:24 -04:00
Hay1tsme 432177957a pokken: save most profile data 2023-07-02 02:42:49 -04:00
Hay1tsme a89247cdd6 wacca: add note about VIP rewards 2023-07-02 02:33:45 -04:00
Hay1tsme f279adb894 mai2: add consecutive day login count, update db to v7, fix reader, courses, and docs 2023-07-01 21:51:18 -04:00
Hay1tsme a680699939 Merge branch 'finale' into develop 2023-07-01 02:59:12 -04:00
Hay1tsme d204954447 mai2: add missing finale endpoints 2023-07-01 02:40:07 -04:00
Hay1tsme 042440c76e mai2: fix handle_get_user_preview_api_request 2023-07-01 02:27:26 -04:00
Hay1tsme c4c0566cd5 mai2: fix userWebOption 2023-07-01 02:19:19 -04:00
Hay1tsme 3e9cec3a20 mai2: put_recent_rating final fix 2023-07-01 02:11:37 -04:00
Hay1tsme 8f9584c3d2 mai2: hotfix put_recent_rating 2023-07-01 02:07:19 -04:00
Hay1tsme b29cb0fbaa mai2: fix put_recent_rating 2023-07-01 02:06:00 -04:00
Hay1tsme d9a92f5865 mai2: 4th round of fixes 2023-07-01 02:04:30 -04:00
Hay1tsme 9859ab4fdb mai2: fix playlog saving 2023-07-01 01:59:19 -04:00
Hay1tsme d89eb61e62 mai2: fixes round 3 2023-07-01 01:56:52 -04:00
Hay1tsme dc8c27046e mai2: more finale fixes 2023-07-01 01:42:38 -04:00
Hay1tsme 3e461f4d71 mai2: finale fixes 2023-07-01 01:41:34 -04:00
Hay1tsme 2c6902a546 mai2: fix typos 2023-07-01 01:12:15 -04:00
Hay1tsme 318b73dd57 finale: finish porting request data from aqua 2023-07-01 01:08:54 -04:00
Hay1tsme 9d33091bb8 allnet: use parse_qsl 2023-06-30 01:34:46 -04:00
Hay1tsme 8b43d554fc allnet: make use of urllib.parse where applicable 2023-06-30 01:19:17 -04:00
Hay1tsme 610ef70bad allnet: add Alive get and post handlers 2023-06-30 00:32:52 -04:00
Hay1tsme 60b3bc7750 Merge branch 'develop' of https://gitea.tendokyu.moe/Hay1tsme/artemis into develop 2023-06-30 00:26:10 -04:00
Hay1tsme 4ea83f6025 allnet: add handler for LoaderStateRecorder 2023-06-30 00:26:07 -04:00
Midorica 20389011e9 Adding proper hero unlock after stage clear on SAO 2023-06-28 12:54:16 -04:00
Midorica e446816b9a fixing issue where SaoItemData was not working 2023-06-28 08:24:53 -04:00
Midorica 9dd2b4d524 Adding dummy hero QR code scanning for SAO 2023-06-28 00:18:02 -04:00
Midorica b60cf6258d Dummy defrag match handler for SAO 2023-06-27 21:32:46 -04:00
Hay1tsme 127e6f8aa8 mai2: add finale databases 2023-06-27 00:32:35 -04:00
Midorica 5155353360 fixing chapter progression after chapter 2 on SAO 2023-06-26 19:30:03 -04:00
Hay1tsme e3d38dacde mai2: fix movies 2023-06-25 19:10:34 -04:00
Hay1tsme 0c6d9a36ce mai2: add movie server endpoints 2023-06-25 18:43:00 -04:00
Hay1tsme b1968fe320 Merge branch 'develop' into finale 2023-06-25 18:35:12 -04:00
Midorica 03f91d18c9 fixing hero party saving for SAO 2023-06-25 14:40:34 -04:00
Midorica 17508f09b2 fixed episode VP saving & hero level in DB for SAO 2023-06-25 13:47:31 -04:00
Midorica aae4afe7b8 Adding debug logging to SAO 2023-06-25 11:59:17 -04:00
Hay1tsme 514f786e2d pokken: Switch to using external STUN server 2023-06-25 01:09:23 -04:00
Midorica ec9ad1ebb0 fixing stage progression for SAO 2023-06-25 00:08:50 -04:00
Midorica 08ebb5c907 another quick fix for SAO tower stage 2023-06-24 20:42:00 -04:00
Midorica 571b92d0cd forgot one line, see previous commit 2023-06-24 20:33:30 -04:00
Midorica 01b5282899 small fix about next tower stage progression for SAO 2023-06-24 20:31:14 -04:00
Midorica 391edd3354 Tower progression now working for SAO 2023-06-24 20:09:37 -04:00
Midorica d5bff0e891 Stage progression done for SAO 2023-06-24 18:48:48 -04:00
Hay1tsme 402e753469 wacca: fix tabbing error in util_put_items 2023-06-24 15:09:38 -04:00
Hay1tsme 154ccbdae5 Merge branch 'develop' of https://gitea.tendokyu.moe/Hay1tsme/artemis into develop 2023-06-24 13:14:42 -04:00
Hay1tsme 858b101a36 dbutils: add command to show versions 2023-06-24 13:14:40 -04:00
Midorica 1d10e798a5 Fixed few issues for SAO & removed static hex ranges 2023-06-24 12:29:28 -04:00
Hay1tsme 3c385f505b pokken: add requirement for autobahn, add stun, turn and admission servers 2023-06-23 00:30:25 -04:00
Hay1tsme b12938bcd8 pokken: add partial profile save logic 2023-06-14 03:00:52 -04:00
Hay1tsme 1b2f5e3709 mai2: fix logic in handle_get_user_music_api_request 2023-06-13 22:50:57 -04:00
Hay1tsme 65686fb615 mai2: add loggin to handle_get_user_music_api_request 2023-06-13 22:35:09 -04:00
Hay1tsme f56332141e mai2: fix old server (finale isn't ready yet) 2023-06-13 22:16:30 -04:00
Hay1tsme 5a35b1c823 mai2: GetUserMusicApi hotfix 2023-06-13 22:10:35 -04:00
Hay1tsme 5ca16f2067 mai2: fix GetUserMusicApi pagination 2023-06-13 22:07:48 -04:00
Midorica a0b25e2b7b Adding rare drops saving to SAO 2023-06-03 11:42:50 -04:00
Midorica 84fc002cdb Adding trial tower support for SAO 2023-06-02 13:53:49 -04:00
Midorica 3bd03c592e Item progression and synthesize of hero and equipment done 2023-06-01 13:19:48 -04:00
Midorica cf6cfdbd3b adding partial synthetize system for SAO 2023-05-31 21:58:30 -04:00
Hay1tsme db77e61b79 allnet: add event logging 2023-05-30 21:52:21 -04:00
Hay1tsme ac9e71ee2f hotfix allnet logging 2023-05-30 21:46:26 -04:00
Hay1tsme 20865dc495 allnet: add logging 2023-05-30 21:45:37 -04:00
Hay1tsme 37d24b3b4d mucha: now respects log level set in core.yaml 2023-05-30 21:32:27 -04:00
Hay1tsme 2418abacce title: convert version to int to match POST endpoint 2023-05-30 21:31:09 -04:00
Hay1tsme 5c3f812caf cxb: fix missing parameters on render_POST 2023-05-30 21:30:34 -04:00
Midorica 4854bcfcad Merge pull request 'Added individual Card Maker versions and maimai DX card/passes working' (#21) from Dniel97/artemis:cardmaker_maimai into develop
Reviewed-on: Hay1tsme/artemis#21
2023-05-30 23:59:44 +00:00
Midorica bf6d126f8a Equipments saving for SAO now completed 2023-05-30 18:03:52 -04:00
Midorica e466ddce55 Adding SAO rewards saving for heroes 2023-05-30 14:29:50 -04:00
Dniel97 ad820ed091
cm: Added individual Card Maker version and maimai DX passes working 2023-05-30 12:14:18 +02:00
Dniel97 960a0e3fd9
Merge branch 'develop' into fork_develop 2023-05-30 12:08:36 +02:00
Midorica a2fe11d654 Fixing level calculation saving & loading on SAO 2023-05-29 20:57:02 -04:00
Midorica 2b4ac06389 adding more profile & hero saving stuff to SAO 2023-05-29 19:21:26 -04:00
Midorica d8af7be4a4 Adding SAO item table and adding party saving 2023-05-29 16:51:41 -04:00
Midorica 84cb786bde Adding some structs for SAO for later use 2023-05-29 11:10:32 -04:00
Hay1tsme 05dee87a9a allnet: update default values, add debug log for unknown but allowed auths 2023-05-26 21:41:16 -04:00
Midorica 049dc40a8b small typo of the documentation 2023-05-26 14:04:49 -04:00
Midorica cab1d6814a added game specifics for SAO 2023-05-26 13:57:16 -04:00
Midorica 72594fef31 adding partial Sword Art Online Arcade support 2023-05-26 13:45:20 -04:00
Hay1tsme 7ed294e9f7 delivery: remove period from version 2023-05-24 01:08:53 -04:00
Hay1tsme b9fd4f294d wacca: fix type mismatch in user/music/unlock 2023-05-22 12:33:43 -04:00
Hay1tsme 5ddfb88182 wacca: fix user/music/unlock error when using tickets. 2023-05-22 12:24:16 -04:00
Hay1tsme 4da8622977 frontend: user page fixes, add card display 2023-05-20 15:32:02 -04:00
Hay1tsme 97892d6a7d idz: try-catch for userdb request decryption 2023-05-18 21:20:28 -04:00
Hay1tsme 02078080a8 index: additional logging for malformed return data 2023-05-12 22:12:03 -04:00
Hay1tsme 61e3a2c930 index: remove hanging debug log call 2023-05-12 22:06:19 -04:00
Hay1tsme 8ae0aba89c mai2: update default config 2023-05-12 22:05:05 -04:00
Hay1tsme 49166c1a7b mai2: fix handle_get_game_setting_api_request 2023-05-11 09:52:18 -04:00
Midorica 013e83420b Merge pull request 'CHUNITHM SUN support (with basic matchmaking)' (#20) from Dniel97/artemis:chuni_sun into develop
Reviewed-on: Hay1tsme/artemis#20
2023-05-11 13:44:30 +00:00
Dniel97 0dce7e7849
docs: fixed opt typo 2023-05-11 15:33:29 +02:00
Raymonf f959236af0
SUN encryption support 2023-05-10 17:20:13 -04:00
Dniel97 b85a65204f
chuni: added SUN support, matchmaking, fixed bugs, added docs
- Added CHUNITHM SUN support
- Added first matchmaking support with CPU spawning and messages
- Fixed wrong `next_idx` calculations
- Added `startDate` to events to spawn the correct items
- Fixed login bonus per version
- Added information to docs
2023-05-10 21:32:35 +02:00
Dniel97 0ab539173a
Merge branch 'develop' into fork_develop 2023-05-10 12:49:01 +02:00
Hay1tsme 42ed222095 mai2: add gamesetting urls 2023-05-10 02:31:30 -04:00
Hay1tsme d172e5582b fixup allnet response for res class 2 2023-05-09 03:53:31 -04:00
Hay1tsme 9766e3ab78 mai2: hardcode reboot time 2023-05-07 02:16:50 -04:00
Hay1tsme b34b441ba8 mai2: reimplement pre-dx versions 2023-05-06 19:04:10 -04:00
Hay1tsme 8149f09a40 mai2: stub music reader 2023-05-05 00:37:05 -04:00
Hay1tsme cad523dfce mai2: add patch reader 2023-05-05 00:36:07 -04:00
Hay1tsme 8b9771b5af mai2: implement event reader for pre-dx games 2023-05-05 00:24:47 -04:00
Hay1tsme 989c080657 mai2: further documentation clarification 2023-05-04 20:25:14 -04:00
Hay1tsme dcff8adbab mai2: update documentation 2023-05-04 20:22:41 -04:00
Hay1tsme e3b1addce6 mai2: fix up version comments 2023-05-04 20:12:31 -04:00
Hay1tsme b6f43d887a Merge branch 'develop' into finale 2023-05-04 20:12:02 -04:00
Hay1tsme efd8f86e48 re-add docker files for #19 2023-05-04 09:46:16 -04:00
Hay1tsme d0242b456d mai2: fix for dx 1.00 2023-05-03 22:29:08 -04:00
Hay1tsme 7bb8c2c80c billing: handle malformed requests 2023-05-03 03:26:39 -04:00
Hay1tsme 6d1855a6bc billing: handle malformed requests 2023-05-03 03:25:55 -04:00
Hay1tsme 8d94d25893 mai2: add version seperators 2023-05-03 03:25:29 -04:00
Hay1tsme ae6dcb68df Merge branch 'develop' into finale 2023-05-02 23:56:10 -04:00
Hay1tsme 3b6fc6618c fix naomitest 2023-05-02 23:55:57 -04:00
Hay1tsme deeac1d8db add finale handler, pre-dx game codes 2023-04-30 22:19:31 -04:00
Midorica 6ad5194bb8 Merge pull request 'Project Diva Arcade: Added Clear Status calculation + small improvements' (#18) from Dniel97/artemis:diva_clear_set into develop
Reviewed-on: Hay1tsme/artemis#18
2023-04-30 23:18:19 +00:00
Dniel97 a0793aa13a
diva: added clear set calculation + small improvements 2023-04-30 23:31:13 +02:00
Dniel97 7364181de1
Merge branch 'develop' into fork_develop 2023-04-30 20:39:35 +02:00
Hay1tsme 9d8762d3da Update 'readme.md' 2023-04-29 21:18:28 -04:00
Hay1tsme fe4dfe369b Update 'readme.md' 2023-04-30 01:18:00 +00:00
Hay1tsme 02040300b8 Merge branch 'develop' 2023-04-23 21:05:25 -04:00
Hay1tsme 238d437519 reformat with black in preperation for merge to master 2023-04-23 21:04:52 -04:00
Hay1tsme 9d23d59e43 add changelog 2023-04-23 21:04:15 -04:00
Hay1tsme f4ee4238d9 update readme 2023-04-23 20:51:23 -04:00
Hay1tsme b498e82bf8 dli: remove dot from version 2023-04-23 19:08:45 -04:00
Hay1tsme 0668488ccf update core example config 2023-04-23 19:06:44 -04:00
Hay1tsme 47f4aaddf8 allnet: add download order infrastructure 2023-04-23 19:00:30 -04:00
Hay1tsme 26c4bcb466 idz: Add requests, fix load_config_b 2023-04-23 13:26:44 -04:00
Hay1tsme d8c3ed5c01 Add support for initial d zero 2023-04-23 04:38:28 -04:00
Hay1tsme 58a088b9a4 wacca: add debug log for ticket use 2023-04-21 10:51:44 -04:00
Hay1tsme 190c41e03e wacca: added play mode counter for time free 2023-04-21 10:51:30 -04:00
Hay1tsme a30967e8d7 wacca: fix time free not saving, add counter to profile table 2023-04-20 09:46:18 -04:00
Hay1tsme 00b127361b wacca: enable time play 2023-04-20 02:27:09 -04:00
Hay1tsme 241f29e29c wacca: add a comment 2023-04-20 02:02:42 -04:00
Hay1tsme 4d6afd757f wacca: add helpers for gacha, event and friend info, fix settings not being applied correctly 2023-04-20 00:54:16 -04:00
Hay1tsme 68b0894e47 wacca: fix first play of the day calculation 2023-04-19 16:12:35 -04:00
Midorica 017ef1e224 Merge pull request 'maimai DX FESTiVAL support' (#17) from Dniel97/artemis:maimai_dx_festival into develop
Reviewed-on: Hay1tsme/artemis#17
2023-04-19 18:13:31 +00:00
Dniel97 958471b8eb
mai2: update script hotfix 2023-04-19 17:41:36 +02:00
Hay1tsme 15433b681c mai2: fix logging in put_card 2023-04-19 11:26:33 -04:00
Hay1tsme b0042bc776 docs: update in accordance with new dbutils 2023-04-19 10:50:40 -04:00
Hay1tsme 469ead7a84 wacca: add previously disabled gates, for #15 2023-04-18 02:52:41 -04:00
Hay1tsme 0dc96f33e1 database: don't set schema version if autoupdate fails 2023-04-15 03:13:14 -04:00
Hay1tsme 4102ba21fc database: remove print 2023-04-15 03:07:15 -04:00
Hay1tsme 83d2151b6b dbutils: fix config loading incorrectly 2023-04-15 03:06:11 -04:00
Hay1tsme 9895068125 database: fix autoupdate 2023-04-15 01:31:52 -04:00
Hay1tsme 4419310086 fix schema versions for diva and ongeki 2023-04-15 01:31:40 -04:00
Hay1tsme a416fb09e1 dbutils: version can now be left black to auto-upgrade to latest 2023-04-15 00:13:04 -04:00
Hay1tsme baa885f674 Utils: exclude malformed game folders 2023-04-15 00:12:45 -04:00
Hay1tsme 0d5567c990 wacca: fix v4 upgrade scripts 2023-04-15 00:12:28 -04:00
Hay1tsme b1f9be0121 wacca: fix crash on 4th page of Reverse Gate, partially fixes #5 2023-04-14 23:47:31 -04:00
Hay1tsme dc3e3e1fb3 pokken: add constants, add stats to profile table 2023-04-14 02:51:28 -04:00
Dniel97 97e3f1af01
mai2: cardmaker festival support 2023-04-13 22:22:28 +02:00
Hay1tsme 71c43a4a57 pokken: add_profile_points stub 2023-04-12 02:39:56 -04:00
Hay1tsme bd356af272 pokken: restructure database 2023-04-12 02:34:29 -04:00
Dniel97 28c06335b6
mai2: added upsert returns, fixed event reader, thanks @One3
Thanks to @One3 for helping with the events
2023-04-11 17:57:21 +02:00
Hay1tsme 68e25b9c5e pokken: add frontend stub 2023-04-11 11:40:05 -04:00
Dniel97 f63dd07937
maimai: Initial Festival support 2023-04-10 19:11:58 +02:00
Dniel97 7fdb3e8222
Merge branch 'develop' into fork_develop 2023-04-10 18:35:43 +02:00
Hay1tsme bf6c7d39f5 pokken: small cleanup on LoadUser 2023-04-10 04:47:19 -04:00
Hay1tsme 5ec280ab8c pokken: fill LoadUser, add auto_register flag 2023-04-10 04:42:40 -04:00
Hay1tsme de5f61b0de pokken: add database tables 2023-04-10 03:35:14 -04:00
Hay1tsme 0f642629a2 add debug logger for the uri 2023-04-10 01:53:36 -04:00
Midorica 979bd7d718 Merge pull request 'Chunithm Improvements' (#16) from Dniel97/artemis:chunithm_improvements into develop
Reviewed-on: Hay1tsme/artemis#16
2023-03-30 21:03:37 +00:00
Dniel97 a60d52b742
chuni: fixed missing login boni IndexError 2023-03-30 22:58:45 +02:00
Dniel97 571a691e0e
chuni: added `use_login_bonus` check to UserLoginBonusApi 2023-03-28 18:54:27 +02:00
Dniel97 1aa92458f4
chuni: added login bonus (+importer), fixed config strings 2023-03-28 18:28:57 +02:00
Dniel97 541fe76a7c
cardmaker: fixed chuni endless loading 2023-03-28 01:09:16 +02:00
Hay1tsme 6489e3ca21 pokken: add skeleton LoadUser response 2023-03-26 04:33:53 -04:00
Dniel97 2a290f2a3d
chuni: added teams and ticket saving, fixed last played song 2023-03-24 18:10:10 +01:00
Dniel97 b21ddb92ce
Merge branch 'develop' into fork_develop 2023-03-24 18:00:22 +01:00
Hay1tsme ac8a660e13 allnet: allow unknown games to auth in develop mode 2023-03-19 23:52:33 -04:00
Hay1tsme 780a96ec15 Merge branch 'develop' 2023-03-19 23:33:01 -04:00
Hay1tsme 12fd663eb7 wacca: fix songs locking after playing them after unlcoked them with an ex unlock ticket, fixes #12 2023-03-19 23:29:57 -04:00
Hay1tsme b8b93a8d51 Merge branch 'develop' 2023-03-18 23:56:23 -04:00
Hay1tsme dfd3877889 wacca: rename locked -> lock_state in MusicUpdateDetailV1 2023-03-18 12:55:04 -04:00
Hay1tsme 62b62db5b5 wacca: fix favorites, purchasing and unlocking songs, incorrectly displayed grades 2023-03-18 12:26:57 -04:00
Hay1tsme 188be2dfc1 database: add autoupgrade command 2023-03-18 02:12:58 -04:00
Hay1tsme 6ff8c4d931 fix mai2 and diva db scripts 2023-03-18 02:12:33 -04:00
Hay1tsme 401623f20b docs: fix ongeki anchor in game_specific_info.md 2023-03-17 20:03:07 -04:00
Hay1tsme 5a388e2a24 docs: fix casing in game_specific_info.md 2023-03-17 19:53:00 -04:00
Hay1tsme 6965132e5b chuni: fix hard error caused by not having the db set up 2023-03-17 02:16:49 -04:00
Hay1tsme 7ca4e6adb9 fix IP address logging 2023-03-17 02:11:49 -04:00
Hay1tsme 4bd1dea6bf allnet: add info log to downloadorder 2023-03-17 02:06:15 -04:00
Hay1tsme 8c5c7f31b6 allnet: fix setting=1 2023-03-16 22:31:41 -04:00
Hay1tsme a7db5eed77 chuni: fix encryption 2023-03-16 21:56:36 -04:00
Hay1tsme a6e9e80bc7 chuni: fix encryption 2023-03-16 21:42:43 -04:00
Hay1tsme 71eec6e34d fix error if log folder not created or not writeable 2023-03-16 21:36:42 -04:00
Hay1tsme 8b718c601f chuni: add method hashing support 2023-03-16 21:27:03 -04:00
Dniel97 2af7751504 Added support for maimai and Chunithm in Card Maker 1.34/1.35 (#14)
Co-authored-by: Dniel97 <Dniel97@noreply.gitea.tendokyu.moe>
Reviewed-on: Hay1tsme/artemis#14
Co-authored-by: Dniel97 <dniel97@noreply.gitea.tendokyu.moe>
Co-committed-by: Dniel97 <dniel97@noreply.gitea.tendokyu.moe>
2023-03-15 20:03:22 +00:00
Hay1tsme a791142f95 database: add check for current_schema_version 2023-03-12 22:37:44 -04:00
Hay1tsme 346e898983 pokken: remove hanging debug log 2023-03-12 16:34:20 -04:00
Hay1tsme fddf2e448a pokken: rearrange logging, fix types 2023-03-12 16:30:57 -04:00
Hay1tsme 65e9ecd58c wacca: fix crash when enabling frontend with no wacca.yaml file 2023-03-12 14:03:00 -04:00
Hay1tsme 6fa0175baa print cleanup, remove unused mucha options 2023-03-12 01:59:12 -05:00
Hay1tsme a97509bb43 add X-Forwarded-For to nginx config 2023-03-12 01:47:59 -05:00
Hay1tsme 18a95f5213 add get_ip_addr util function for servers behind proxies 2023-03-12 01:00:51 -05:00
Hay1tsme ea14f105d5 database: skip games that lack a database member 2023-03-12 00:26:48 -05:00
Hay1tsme e4b7809e34 pokken: add matching server skeleton 2023-03-11 23:42:12 -05:00
Hay1tsme eb51fc315c pokken: update example config 2023-03-11 20:22:56 -05:00
Hay1tsme a9f49e8d5d pokken: remove setup(), ssl config options, change matching uri 2023-03-11 20:17:05 -05:00
Hay1tsme edb9ec1971 pokken: add responnse debug logging 2023-03-11 20:02:58 -05:00
Dniel97 3a234244d4
Merge branch 'develop' into fork_develop 2023-03-12 01:05:31 +01:00
Hay1tsme 2dd84bbe3e pokken: fix mucha and allnet info, fix allnet 2.00 format requests 2023-03-10 20:31:29 -05:00
Hay1tsme f283dd10a9 index.py: Fix log directory creation 2023-03-09 19:03:30 -05:00
Midorica 57ecff641a fixing Card Maker get_game_connect_api response 2023-03-09 17:09:37 -05:00
Midorica a088dd82de fixing allnet power on response 2023-03-09 16:59:50 -05:00
Midorica 9295299dca Merge pull request 'chuni: Add 'handle_remove_token_api_request' for event mode' (#13) from Raymonf/artemis:fix/chuni-event-mode into develop
Reviewed-on: Hay1tsme/artemis#13
2023-03-09 20:34:07 +00:00
Raymonf b076a9a9df
chuni: Add 'handle_remove_token_api_request' for event mode
Not sure if `handle_delete_token_api_request` is used in other versions, so it's duplicated to be safe.
2023-03-09 15:16:59 -05:00
Hay1tsme 2033bc897f pokken: fix a crash that happens when the game is disabled 2023-03-09 13:46:28 -05:00
Hay1tsme e9ffd95435 implement dict.get() 2023-03-09 12:17:10 -05:00
Hay1tsme dafc030050 wacca: add .partition() 2023-03-09 12:02:02 -05:00
Hay1tsme a76bb94eb1 let black do it's magic 2023-03-09 11:38:58 -05:00
Hay1tsme fa7206848c general code cleanup for multiple games 2023-03-09 11:29:36 -05:00
Hay1tsme 6761915a3f add .lower() to ping requsts 2023-03-09 10:56:30 -05:00
Hay1tsme c8d4bc6109 add special-case ping handlers to mai2, ongeki and chuni 2023-03-09 10:52:49 -05:00
Hay1tsme 6dcd7b67ef cm: hotfix for handle_get_game_connect_api_request 2023-03-09 10:37:29 -05:00
Hay1tsme 2f1728b64d cardmaker: simplify handle_get_game_connect_api_request, add develop mode check 2023-03-09 10:35:58 -05:00
Midorica fb6a026b84 Adding the music list for crossbeats again 2023-03-09 09:10:36 -05:00
Midorica 4c64305f15 Merge pull request 'Card Maker 1.34/1.36 ONGEKI support' (#10) from Dniel97/artemis:cardmaker_ongeki into develop
Reviewed-on: Hay1tsme/artemis#10
2023-03-08 22:49:14 +00:00
Dniel97 44c75d0156
Merge branch 'cardmaker_ongeki' into fork_develop 2023-03-06 17:08:51 +01:00
Dniel97 78b2a81c79
Merge remote-tracking branch 'origin/develop' into fork_develop 2023-03-06 17:08:50 +01:00
Dniel97 6609732546
cm: added get_allnet_info 2023-03-06 16:20:44 +01:00
Dniel97 74f3ab7c3f
cm: added support for 1.36, fixed importer
- Added support for Card Maker 1.36.xx
- Added cards importer to ONGEKI importer
- Added 4 new 1.36 gachas (requires importing them from opt files)
- Fixed version for Card Maker opt importer
2023-03-05 23:54:13 +01:00
Hay1tsme 36054ebb66 mucha: add missing boardath values 2023-03-05 11:42:03 -05:00
Hay1tsme 82b159e5b1 MuchaUpdateResponse: add missing values 2023-03-05 09:45:34 -05:00
Hay1tsme 59b2401a67 mucha: add updatecheck response stub 2023-03-05 09:43:05 -05:00
Hay1tsme f25152a6bf add RIP contributing guide 2023-03-04 23:00:21 -05:00
Hay1tsme ff6ef16b89 mucha: small cleanup with a oneliner 2023-03-04 22:52:17 -05:00
Hay1tsme a7a830c6b7 mucha: remove default values from response classes 2023-03-04 22:48:12 -05:00
Hay1tsme b12f61198f mucha: fixups 2023-03-04 22:46:26 -05:00
Hay1tsme bfe5294d51 add get_allnet_info and config loading safety to all games 2023-03-04 21:58:51 -05:00
Hay1tsme b2b28850dd ongeki: add get_allnet_info, 2023-03-04 21:39:38 -05:00
Hay1tsme e0fdd937e6 pokken: add safety for loading config that doesn't exist 2023-03-04 21:36:15 -05:00
Hay1tsme b8fd0baee5 wacca: move to get_allnet_info, add safety for loading config 2023-03-04 21:34:35 -05:00
Hay1tsme a340bcf1dd change how allnet uri/host is generated 2023-03-04 21:27:52 -05:00
Dniel97 fe8f40c627
Fixed upgrade script endDate 2023-03-04 11:09:03 +01:00
Hay1tsme f5d4f519d3 database: add create-owner, migrate-card, and cleanup commands 2023-03-04 00:04:47 -05:00
Hay1tsme 3181e1f4f8 frontend: add registration instructions 2023-03-03 21:38:26 -05:00
Hay1tsme 279f48dc0c frontend: fix login, remove frontend_session in favor of twisted sessions 2023-03-03 21:31:23 -05:00
Hay1tsme dc5e5c1440 database: fix event logging table 2023-03-03 19:56:12 -05:00
Hay1tsme 4f3d3d8395 database: fix error when trying to upgrade the schema for a game that wasn't created yet 2023-03-03 19:23:14 -05:00
Dniel97 3acc2dc197
Initial Card Maker ONGEKI support 2023-03-04 00:22:08 +01:00
Dniel97 8fe0acae93
Merge remote-tracking branch 'origin/develop' into fork_develop 2023-03-03 23:31:46 +01:00
Hay1tsme a0e24c6742 Merge branch 'develop' 2023-03-03 17:07:58 -05:00
Hay1tsme 02e1838d95 database: add format_serial, validate_keychip_format, set_machine_boardid, set_machine_serial 2023-03-03 17:05:16 -05:00
Hay1tsme 102bf3b5a4 database: remove functions that no longer exist 2023-03-03 17:04:26 -05:00
Hay1tsme 2a6842db24 remove db old-to-new migration 2023-03-03 17:03:19 -05:00
Hay1tsme c26f6b7b1d wacca: fix typing 2023-03-03 16:31:42 -05:00
Hay1tsme 9ad724d64b wacca: fix edge case in handle_housing_start_request 2023-03-03 16:28:42 -05:00
Hay1tsme f24d554a44 wacca: pull region_id from allnet if available 2023-03-03 16:26:07 -05:00
Hay1tsme 34e2c50fb5 allnet: see previous 2023-03-03 15:52:58 -05:00
Hay1tsme b35e7d6983 allnet: hotfix for country 2023-03-03 15:49:33 -05:00
Hay1tsme f6cfb9e36d allnet: fix "none" in response 2023-03-03 15:45:21 -05:00
Hay1tsme 101b966e3a add allnet request debug logging 2023-03-03 15:39:14 -05:00
Hay1tsme fae6b77403 core: TESTING fix for get_machine 2023-03-03 15:03:57 -05:00
Midorica 4c64554383 pushing small typo for title port on both guides 2023-03-03 13:27:22 -05:00
Hay1tsme 90024ddbd9 Merge branch 'develop' 2023-03-03 13:19:55 -05:00
Hay1tsme 45fedd8425 wacca: tidy up UserStatusUpdateRequestV2 2023-03-03 13:12:06 -05:00
Hay1tsme 2da12e515e wacca: see previous 2023-03-03 13:07:36 -05:00
Hay1tsme cd78ecd7ea wacca: fix typo in UserInfoUpdateRequest 2023-03-03 13:07:10 -05:00
Hay1tsme 7953519e68 wacca: fix UserInfoUpdateRequest, per #5 2023-03-03 13:03:48 -05:00
Hay1tsme 524f99879f Merge branch 'develop' of https://gitea.tendokyu.moe/Hay1tsme/artemis into develop 2023-03-03 12:40:05 -05:00
Hay1tsme 4626ec36cd wacca: fix options not saving 2023-03-03 12:40:03 -05:00
Midorica 3791b2b238 Adding Ongeki Bright Memory support 2023-03-03 00:00:22 -05:00
Midorica 937dba20ca Fixing billing that was failing for Chunithm 2023-03-02 22:13:18 -05:00
Hay1tsme c5fc879af6 core: add taiwan to AllnetCountryCode 2023-03-02 13:02:43 -05:00
Hay1tsme e205777693 docs: fix typo in prod.md 2023-03-02 12:05:38 -05:00
Hay1tsme 4d9ae19cb2 update readme 2023-03-02 11:59:52 -05:00
Hay1tsme 99881ea220 docs: Add note about SSL certs to prod.md 2023-03-02 11:54:50 -05:00
Hay1tsme d5a7247a7f add production config 2023-03-02 11:47:40 -05:00
Hay1tsme 846a556c5b fix nginx example 2023-03-02 10:28:49 -05:00
Hay1tsme 7071ab0bd9 chuni: add IP logging, clean up logs 2023-03-02 00:24:04 -05:00
Hay1tsme 5965362a0f title: add 405 and 404 error responses 2023-03-02 00:14:13 -05:00
Hay1tsme 6b0838062e wacca: add lily to list of items given on profile create, fixes #4 2023-03-01 23:56:07 -05:00
Hay1tsme be00ad8e96 wacca: add lily to list of items given on profile create, fixes #4 2023-03-01 23:55:29 -05:00
Hay1tsme e2129b45b7 Merge branch 'develop' 2023-03-01 23:41:42 -05:00
Hay1tsme 1567ec23ab wacca: fix stageup list not populating correctly, fix #3 2023-03-01 23:24:36 -05:00
Hay1tsme a0739436cc Wacca: Fix stageup order, fixes #3 2023-03-01 23:03:29 -05:00
Hay1tsme e961c1dfb3 wacca: add region logic 2023-03-01 22:27:33 -05:00
Hay1tsme e46c8e7dbd Merge pull request 'wacca: allow setting prefecture in config by name' (#9) from Raymonf/artemis:feat/wacca-prefecture into develop
Reviewed-on: Hay1tsme/artemis#9
2023-03-02 03:10:41 +00:00
Raymonf 379388c749 wacca: allow setting prefecture in config by name 2023-03-02 03:04:03 +00:00
Hay1tsme 078059f54e core: remove unused class from const 2023-03-01 21:51:52 -05:00
Hay1tsme 382e36e60f core: Add county codes and Japanese region IDs 2023-03-01 21:49:00 -05:00
Hay1tsme 88f6eba30b wacca: add region IDs and version helper classes 2023-03-01 21:48:43 -05:00
Midorica 447743da4c Merge pull request 'chuni: add missing columns for course mode' (#8) from Raymonf/artemis:fix/chuni-course-mode into develop
Reviewed-on: Hay1tsme/artemis#8
2023-03-02 01:05:55 +00:00
Raymonf b0bf151c9f
chuni: SDHD -> SDBT for upgrade scripts 2023-03-01 20:04:34 -05:00
Raymonf 97aeba20e5
chuni: add missing columns for course mode 2023-03-01 16:08:36 -05:00
Dniel97 a65055fc8c
Merge remote-tracking branch 'origin/develop' into fork_develop 2023-03-01 21:53:25 +01:00
Midorica e98a7c8ae0 Merge pull request 'Chunithm New!! (Plus)' (#7) from Dniel97/artemis:chuni_importer_fix into develop
Reviewed-on: Hay1tsme/artemis#7
2023-03-01 20:19:40 +00:00
Dniel97 842e3a313e
chuni: use title hostname instead of server hostname 2023-03-01 21:18:29 +01:00
Dniel97 b81767af8a
Chunithm New!!+ Importer and settings fixed 2023-03-01 21:09:06 +01:00
Dniel97 435a098fe0
Merge branch 'develop' into fork_develop 2023-02-28 23:45:34 +01:00
Hay1tsme 0e3265a162 wacca: fix vip_start 2023-02-28 17:32:23 -05:00
Hay1tsme 0284885926 add pokken, cxb and frontend to nginx config 2023-02-28 00:41:43 -05:00
Hay1tsme 066f92d94b pokken index fixes 2023-02-28 00:41:32 -05:00
Hay1tsme abe1fa7853 Pokken: added check for ssl cert/key 2023-02-27 16:51:17 -05:00
Hay1tsme 0da3053454 add dummy database attribute for pokken 2023-02-27 11:57:49 -05:00
Dniel97 d3862b7483
Merge branch 'develop' into fork_develop 2023-02-27 17:57:21 +01:00
Hay1tsme 1f2d12f318 maidx: upgrade schema for uni+ 2023-02-27 11:55:51 -05:00
Dniel97 b30e9570e7
Merge branch 'develop' into fork_develop 2023-02-27 17:47:28 +01:00
Hay1tsme 806dd717e6 maidx: fix score, playlog and courses not saving properly 2023-02-27 11:39:42 -05:00
Hay1tsme b31e739ecd remove unnecessassary logging dup check 2023-02-24 14:14:18 -05:00
Hay1tsme cb227f9cf4 remove unnecessassary print statements 2023-02-24 14:13:31 -05:00
Hay1tsme bd1665a849 add logging levels for index.py and read.py 2023-02-24 14:10:41 -05:00
Hay1tsme c213926893 added core logger, allnet resiliancy 2023-02-24 14:07:54 -05:00
Hay1tsme 2bd980165e update windows guide 2023-02-24 13:38:31 -05:00
Hay1tsme 670747cf48 add platform_system to requirements.txt 2023-02-24 13:35:37 -05:00
Hay1tsme e7d73dd257 add platform_system to requirements.txt 2023-02-24 13:34:32 -05:00
Hay1tsme 6b265ea866 pokken: add ssl_enable 2023-02-24 13:27:37 -05:00
Midorica b105418431 Install guide for Ubuntu 20.04 2023-02-24 10:36:13 -05:00
Midorica e8e6414b66 Install guide for Windows 2023-02-24 10:24:35 -05:00
Hay1tsme 7df998a51a add naomitest endpoint 2023-02-23 23:11:43 -05:00
Hay1tsme c3aac4c38e Move wacca from megaime develop branch, should at least partially fix #3 #4 and #5 2023-02-22 22:22:03 -05:00
Midorica 026fcc5182 Merge pull request 'diva: added all previous commits, added username and password change' (#1) from Dniel97/artemis:diva_card_procedure into develop
Reviewed-on: Hay1tsme/artemis#1
2023-02-22 23:27:11 +00:00
Midorica b300bb302b title fix for the version int - Thanks to Dniel97 2023-02-22 12:20:50 -05:00
God601 fff7eb4666 fixing logs for the read.py - Thanks to Dniel97 2023-02-22 11:47:32 -05:00
Hay1tsme 83f09e180e add protobuf to requirements, fixes #2 2023-02-22 10:01:42 -05:00
Hay1tsme 9c62ea24be add protobuf to requirements, fixes #2 2023-02-22 09:59:31 -05:00
Hay1tsme 3f40e083ce add mucha config to coreconfig 2023-02-21 16:46:43 -05:00
Hay1tsme a83b717821 carry over database functions from megaime 2023-02-21 14:05:55 -05:00
Hay1tsme b343228072 refactor template directory to be artemis root dir 2023-02-20 21:55:12 -05:00
Dniel97 0b76c61059
Merge branch 'diva_card_procedure' into fork_develop 2023-02-20 20:44:39 +01:00
Dniel97 18a1923f6a
Merge remote-tracking branch 'origin/develop' into fork_develop 2023-02-20 20:44:35 +01:00
Dniel97 a7821fade8
diva: improved update_profile() function 2023-02-19 22:56:09 +01:00
Hay1tsme db6b950c29 add partial frontend 2023-02-19 15:40:25 -05:00
Hay1tsme 97d16365df carry over database functions from megaime 2023-02-19 14:52:20 -05:00
Dniel97 285bb966a5
Merge branch 'diva_card_procedure' into fork_develop 2023-02-19 18:24:27 +01:00
Dniel97 d1535d7be1
Merge branch 'develop' into fork_develop 2023-02-19 16:40:15 +01:00
Dniel97 8bdc2071da
diva: changed player_name length to 10 2023-02-19 16:08:53 +01:00
Hay1tsme df4efa1fda replace print statements with error logging 2023-02-19 00:10:42 -05:00
Hay1tsme a57d2cf71c fix typo in aimedb 2023-02-19 00:07:14 -05:00
Hay1tsme d434bf084d fix sending incorrect params to dict_to_http_form_string 2023-02-19 00:06:21 -05:00
Hay1tsme f5d9bd8003 fix copypasta code 2023-02-19 00:01:39 -05:00
Hay1tsme ed479866cc fix download order 2023-02-18 23:57:45 -05:00
Hay1tsme a3c689cd09 fix using the request instead of the response 2023-02-18 23:42:27 -05:00
Hay1tsme a843e3d3ac added token replacement 2023-02-18 23:40:19 -05:00
Hay1tsme f42c2d7785 fix allnet requests not processing 2023-02-18 23:31:52 -05:00
Hay1tsme b09d2326c2 fix kvp_to_dict not returning 2023-02-18 23:27:25 -05:00
Hay1tsme 68b9c64f71 fix kvp_to_dict 2023-02-18 23:24:38 -05:00
Hay1tsme 655d9dc530 simplified main dispatcher 2023-02-18 23:12:40 -05:00
Hay1tsme 9a43303880 fix incorrect class being used 2023-02-18 23:02:50 -05:00
Hay1tsme edddb2e9d4 fix allnet request using the wrong function 2023-02-18 22:58:40 -05:00
Dniel97 c99bfda015
diva: added all previous commits, added username and password change
- Changed `update_profile()` function to allow a single Dict instead of multiple values
- Added `passwd*` Columns to profile table and added corresponding update/rollback sql scripts
- Added `handle_card_procedure_request()`, `handle_change_name_request ()` and `handle_change_passwd_request()` functions to DivaBase
2023-02-18 21:01:31 +01:00
469 changed files with 89185 additions and 7313 deletions

3
.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
*.csv binary
*.txt binary
*.json binary

1
.gitignore vendored
View File

@ -158,5 +158,6 @@ cert/*
!cert/server.pem
config/*
deliver/*
*.gz
dbdump-*.json

21
Dockerfile Normal file
View File

@ -0,0 +1,21 @@
FROM python:3.9.15-slim-bullseye
RUN apt update && apt install default-libmysqlclient-dev build-essential libtk nodejs npm pkg-config -y
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt
RUN npm i -g nodemon
COPY entrypoint.sh entrypoint.sh
RUN chmod +x entrypoint.sh
COPY index.py index.py
COPY dbutils.py dbutils.py
COPY read.py read.py
ADD core core
ADD titles titles
ADD logs logs
ADD cert cert
ENTRYPOINT [ "/app/entrypoint.sh" ]

13
LICENSE.txt Normal file
View File

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

226
changelog.md Normal file
View File

@ -0,0 +1,226 @@
# Changelog
Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to.
## 20240408
### System
+ Modified the game specific documentation
## 20240407
### Maimai
+ Support maimai DX International [#118](https://gitea.tendokyu.moe/Hay1tsme/artemis/pulls/118) (Thanks beerpsi!)
+ Fixed the maimai DX reboot time from config [#120](https://gitea.tendokyu.moe/Hay1tsme/artemis/pulls/120) (Thanks topty!)
## 20240318
### CXB
+ Fixing handle_data_shop_list_detail_request for Sunrise S1
## 20240302
### SAO
+ Fixing new profile creation with right heroes and start VP
+ Fix to the Unanalyzed Log responses returning the wrong rewards
+ Documentation revised
## 20240226
### CXB
+ Fixing paths for rev.py
+ Changed encoding for handle_data_item_list_icon_request
## 20240202
### SAO
+ Added reader assets and edited the game specific documentation
## 20240118
### System
+ Added game version names to the readme
## 20240109
### System
+ Removed `ADD config config` from dockerfile [#83](https://gitea.tendokyu.moe/Hay1tsme/artemis/pulls/83) (Thanks zaphkito!)
### Aimedb
+ Fixed an error that resulted from trying to scan a banned or locked card
## 20240108
### System
+ Change how the underlying system handles URLs
+ This can now allow for things like version-specific, or even keychip-specific URLs
+ Specific changes to games are noted below
+ Fix docker files [#60](https://gitea.tendokyu.moe/Hay1tsme/artemis/pulls/60) (Thanks Rylie!)
+ Fix support for python 3.8 - 3.10
### Aimedb
+ Add support for SegaAuth key in games that support it (for now only Chunithm)
+ This is a JWT that is sent to games, by Aimedb, that the games send to their game server, to verify that the access code the game is sending to the server was obtained via aimedb.
+ Requires a base64-encoded secret to be set in the `core.yaml`
### Chunithm
+ Fix Air support
+ Add saving for userRecentPlayerList
+ Add support for SegaAuthKey
+ Fix a bug arising if a user set their name to be 'true' or 'false'
+ Add support for Sun+ [#78](https://gitea.tendokyu.moe/Hay1tsme/artemis/pulls/78) (Thanks EmmyHeart!)
+ Add `matching` section to `chuni.yaml`
+ ~~Change `udpHolePunchUri` and `reflectorUri` to be STUN and TURN servers~~ Reverted
+ Imrpove `GetGameSetting` request handling for different versions
+ Fix issue where songs would not always return all scores [#92](https://gitea.tendokyu.moe/Hay1tsme/artemis/pulls/92) (Thanks Kumubou!)
### maimai DX
+ Fix user charges failing to save
### maimai
+ Made it functional
### CXB
+ Improvements to request dispatching
+ Add support for non-omnimix music lists
### IDZ
+ Fix news urls in accordance with the system change to URLs
### Initial D THE ARCADE
+ Added support for Initial D THE ARCADE S2
+ Story mode progress added
+ Bunta Challenge/Touhou Project modes added
+ Time Trials added
+ Leaderboards added, but doesn't refresh sometimes
+ Theory of Street mode added (with CPUs)
+ Play Stamp/Timetrial events added
+ Frontend to download profile added
+ Importer to import profiles added
### ONGEKI
+ Now supports HTTPS on a per-version basis
+ Merg PR [#61](https://gitea.tendokyu.moe/Hay1tsme/artemis/pulls/61) (Thanks phantomlan!)
+ Add Ranking Event Support
+ Add reward list support
+ Add version segregation to Event Ranking, Tech Challenge, and Music Ranking
+ Now stores ClientTestmode and ClientSetting data
+ Fix mission points not adding correctly [#68](https://gitea.tendokyu.moe/Hay1tsme/artemis/pulls/68) (Thanks phantomlan!)
+ Fix tech challenge [#70](https://gitea.tendokyu.moe/Hay1tsme/artemis/pulls/70) (Thanks phantomlan!)
### SAO
+ Change endpoint in accordance with the system change to URLs
+ Update request header class to be more accurate
+ Encrypted requests are now supported
+ Change to using handler classes instead of raw structs for simplicity
### Wacca
+ Fix a server error causing a seperate error that casued issues
+ Add better error printing
+ Add better request validation
+ Fix HousingStartV2
+ Fix Lily's housing/get handler
## 20231107
### CXB
+ Hotfix `render_POST` sometimes failing to read the request body on large requests
## 20231106
### CXB
+ Hotfix `render_POST` function signature signature
+ Hotfix `handle_action_addenergy_request` hard failing if `get_energy` returns None
## 20231015
### maimai DX
+ Added support for FESTiVAL PLUS
### Card Maker
+ Added support for maimai DX FESTiVAL PLUS
## 20230716
### General
+ Docker files added (#19)
+ Added support for threading
+ This comes with the caviat that enabling it will not allow you to use Ctrl + C to stop the server.
### Webui
+ Small improvements
+ Add card display
### Allnet
+ Billing format validation
+ Fix naomitest.html endpoint
+ Add event logging for auths and billing
+ LoaderStateRecorder endpoint handler added
### Mucha
+ Fixed log level always being "Info"
+ Add stub handler for DownloadState
### Sword Art Online
+ Support added
### Crossbeats
+ Added threading to profile loading
+ This should cause a noticeable speed-up
### Card Maker
+ DX Passes fixed
+ Various improvements
### Diva
+ Added clear status calculation
+ Various minor fixes and improvements
### Maimai
+ Added support for memorial photo uploads
+ Added support for the following versions
+ Festival
+ FiNALE
+ Various bug fixes and improvements
### Wacca
+ Fixed an error that sometimes occoured when trying to unlock songs (#22)
### Pokken
+ Profile saving added (loading TBA)
+ Use external STUN server for matching by default
+ Matching still not working
## 2023042300
### Wacca
+ Time free now works properly
+ Fix reverse gate mission causing a fatal error
+ Other misc. fixes
+ Latest DB: 5
### Pokken
+ Added preliminary support
+ Nothing saves currently, but the game will boot and function properly.
### Initial D Zero
+ Added preliminary support
+ Nothing saves currently, but the game will boot and function for the most part.
### Mai2
+ Added support for Festival
+ Lasted DB Version: 4
### Ongeki
+ Misc fixes
+ Lasted DB Version: 4
### Diva
+ Misc fixes
+ Lasted DB Version: 4
### Chuni
+ Fix network encryption
+ Add `handle_remove_token_api_request` for event mode
### Allnet
+ Added download order support
+ It is up to the sysop to provide the INI file, and host the files.
+ ONLY for use with cabs. It's not checked currently, which it's why it's default disabled
+ YMMV, use at your own risk
+ When running develop mode, games that are not recognised will still be able to authenticate.
### Database
+ Add autoupgrade command
+ Invoke to automatically upgrade all schemas to their latest versions
+ `version` arg no longer required, leave it blank to update the game schema to latest if it isn't already
### Misc
+ Update example nginx config file

8
contributing.md Normal file
View File

@ -0,0 +1,8 @@
# Contributing to ARTEMiS
If you would like to contribute to artemis, either by adding features, games, or fixing bugs, you can do so by forking the repo and submitting a pull request [here](https://gitea.tendokyu.moe/Hay1tsme/artemis/pulls). Please make sure, if you're submitting a PR for a game or game version, that you're following the n-0/y-1 guidelines, or it will be rejected.
## Adding games
Guide WIP
## Adding game versions
Guide WIP

View File

@ -1,6 +1,7 @@
from core.config import CoreConfig
from core.allnet import AllnetServlet
from core.aimedb import AimedbFactory
from core.allnet import AllnetServlet, BillingServlet
from core.aimedb import AimedbServlette
from core.title import TitleServlet
from core.utils import Utils
from core.mucha import MuchaServlet
from core.frontend import FrontendServlet

View File

@ -0,0 +1,6 @@
from .base import ADBBaseRequest, ADBBaseResponse, ADBHeader, ADBHeaderException, PortalRegStatus, LogStatus, ADBStatus
from .base import CompanyCodes, ReaderFwVer, CMD_CODE_GOODBYE, HEADER_SIZE
from .lookup import ADBLookupRequest, ADBLookupResponse, ADBLookupExResponse
from .campaign import ADBCampaignClearRequest, ADBCampaignClearResponse, ADBCampaignResponse, ADBOldCampaignRequest, ADBOldCampaignResponse
from .felica import ADBFelicaLookupRequest, ADBFelicaLookupResponse, ADBFelicaLookup2Request, ADBFelicaLookup2Response
from .log import ADBLogExRequest, ADBLogRequest, ADBStatusLogRequest, ADBLogExResponse

170
core/adb_handlers/base.py Normal file
View File

@ -0,0 +1,170 @@
import struct
from construct import Struct, Int16ul, Int32ul, PaddedString
from enum import Enum
import re
from typing import Union, Final
class LogStatus(Enum):
NONE = 0
START = 1
CONTINUE = 2
END = 3
OTHER = 4
class PortalRegStatus(Enum):
NO_REG = 0
PORTAL = 1
SEGA_ID = 2
class ADBStatus(Enum):
UNKNOWN = 0
GOOD = 1
BAD_AMIE_ID = 2
ALREADY_REG = 3
BAN_SYS_USER = 4
BAN_SYS = 5
BAN_USER = 6
BAN_GEN = 7
LOCK_SYS_USER = 8
LOCK_SYS = 9
LOCK_USER = 10
class CompanyCodes(Enum):
NONE = 0
SEGA = 1
BAMCO = 2
KONAMI = 3
TAITO = 4
class ReaderFwVer(Enum): # Newer readers use a singly byte value
NONE = 0
TN32_10 = 1
TN32_12 = 2
OTHER = 9
def __str__(self) -> str:
if self == self.TN32_10:
return "TN32MSEC003S F/W Ver1.0"
elif self == self.TN32_12:
return "TN32MSEC003S F/W Ver1.2"
elif self == self.NONE:
return "Not Specified"
elif self == self.OTHER:
return "Unknown/Other"
else:
raise ValueError(f"Bad ReaderFwVer value {self.value}")
@classmethod
def from_byte(self, byte: bytes) -> Union["ReaderFwVer", int]:
try:
i = int.from_bytes(byte, 'little')
try:
return ReaderFwVer(i)
except ValueError:
return i
except TypeError:
return 0
class ADBHeaderException(Exception):
pass
HEADER_SIZE: Final[int] = 0x20
CMD_CODE_GOODBYE: Final[int] = 0x66
# everything is LE
class ADBHeader:
def __init__(self, magic: int, protocol_ver: int, cmd: int, length: int, status: int, game_id: Union[str, bytes], store_id: int, keychip_id: Union[str, bytes]) -> None:
self.magic = magic # u16
self.protocol_ver = protocol_ver # u16
self.cmd = cmd # u16
self.length = length # u16
try:
self.status = ADBStatus(status) # u16
except ValueError as e:
raise ADBHeaderException(f"Status is incorrect! {e}")
self.game_id = game_id # 4 char + \x00
self.store_id = store_id # u32
self.keychip_id = keychip_id# 11 char + \x00
if type(self.game_id) == bytes:
self.game_id = self.game_id.decode()
if type(self.keychip_id) == bytes:
self.keychip_id = self.keychip_id.decode()
self.game_id = self.game_id.replace("\0", "")
self.keychip_id = self.keychip_id.replace("\0", "")
if self.cmd != CMD_CODE_GOODBYE: # Games for some reason send no data with goodbye
self.validate()
@classmethod
def from_data(cls, data: bytes) -> "ADBHeader":
magic, protocol_ver, cmd, length, status, game_id, store_id, keychip_id = struct.unpack_from("<5H6sI12s", data)
head = cls(magic, protocol_ver, cmd, length, status, game_id, store_id, keychip_id)
if head.length > len(data):
raise ADBHeaderException(f"Length is incorrect! Expect {head.length}, got {len(data)}")
return head
def validate(self) -> bool:
if self.magic != 0xa13e:
raise ADBHeaderException(f"Magic {self.magic} != 0xa13e")
if self.protocol_ver < 0x1000:
raise ADBHeaderException(f"Protocol version {hex(self.protocol_ver)} is invalid!")
if re.fullmatch(r"^S[0-9A-Z]{3}[P]?$", self.game_id) is None:
raise ADBHeaderException(f"Game ID {self.game_id} is invalid!")
if self.store_id == 0:
raise ADBHeaderException(f"Store ID cannot be 0!")
if re.fullmatch(r"^A[0-9]{2}[E|X][0-9]{2}[A-HJ-NP-Z][0-9]{4}$", self.keychip_id) is None:
raise ADBHeaderException(f"Keychip ID {self.keychip_id} is invalid!")
return True
def make(self) -> bytes:
resp_struct = Struct(
"magic" / Int16ul,
"unknown" / Int16ul,
"response_code" / Int16ul,
"length" / Int16ul,
"status" / Int16ul,
"game_id" / PaddedString(6, 'utf_8'),
"store_id" / Int32ul,
"keychip_id" / PaddedString(12, 'utf_8'),
)
return resp_struct.build(dict(
magic=self.magic,
unknown=self.protocol_ver,
response_code=self.cmd,
length=self.length,
status=self.status.value,
game_id = self.game_id,
store_id = self.store_id,
keychip_id = self.keychip_id,
))
class ADBBaseRequest:
def __init__(self, data: bytes) -> None:
self.head = ADBHeader.from_data(data)
class ADBBaseResponse:
def __init__(self, code: int = 0, length: int = 0x20, status: int = 1, game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", protocol_ver: int = 0x3087) -> None:
self.head = ADBHeader(0xa13e, protocol_ver, code, length, status, game_id, store_id, keychip_id)
@classmethod
def from_req(cls, req: ADBHeader, cmd: int, length: int = 0x20, status: int = 1) -> "ADBBaseResponse":
return cls(cmd, length, status, req.game_id, req.store_id, req.keychip_id, req.protocol_ver)
def append_padding(self, data: bytes):
"""Appends 0s to the end of the data until it's at the correct size"""
padding_size = self.head.length - len(data)
data += bytes(padding_size)
return data
def make(self) -> bytes:
return self.head.make()

View File

@ -0,0 +1,132 @@
from construct import Struct, Int16ul, Padding, Bytes, Int32ul, Int32sl
from .base import *
class Campaign:
def __init__(self) -> None:
self.id = 0
self.name = ""
self.announce_date = 0
self.start_date = 0
self.end_date = 0
self.distrib_start_date = 0
self.distrib_end_date = 0
def make(self) -> bytes:
name_padding = bytes(128 - len(self.name))
return Struct(
"id" / Int32ul,
"name" / Bytes(128),
"announce_date" / Int32ul,
"start_date" / Int32ul,
"end_date" / Int32ul,
"distrib_start_date" / Int32ul,
"distrib_end_date" / Int32ul,
Padding(8),
).build(dict(
id = self.id,
name = self.name.encode() + name_padding,
announce_date = self.announce_date,
start_date = self.start_date,
end_date = self.end_date,
distrib_start_date = self.distrib_start_date,
distrib_end_date = self.distrib_end_date,
))
class CampaignClear:
def __init__(self) -> None:
self.id = 0
self.entry_flag = 0
self.clear_flag = 0
def make(self) -> bytes:
return Struct(
"id" / Int32ul,
"entry_flag" / Int32ul,
"clear_flag" / Int32ul,
Padding(4),
).build(dict(
id = self.id,
entry_flag = self.entry_flag,
clear_flag = self.clear_flag,
))
class ADBCampaignResponse(ADBBaseResponse):
def __init__(self, game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", code: int = 0x0C, length: int = 0x200, status: int = 1) -> None:
super().__init__(code, length, status, game_id, store_id, keychip_id)
self.campaigns = [Campaign(), Campaign(), Campaign()]
@classmethod
def from_req(cls, req: ADBHeader) -> "ADBCampaignResponse":
c = cls(req.game_id, req.store_id, req.keychip_id)
c.head.protocol_ver = req.protocol_ver
return c
def make(self) -> bytes:
body = b""
for c in self.campaigns:
body += c.make()
self.head.length = HEADER_SIZE + len(body)
return self.head.make() + body
class ADBOldCampaignRequest(ADBBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
self.campaign_id = struct.unpack_from("<I", data, 0x20)
class ADBOldCampaignResponse(ADBBaseResponse):
def __init__(self, game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", code: int = 0x0C, length: int = 0x30, status: int = 1) -> None:
super().__init__(code, length, status, game_id, store_id, keychip_id)
self.info0 = 0
self.info1 = 0
self.info2 = 0
self.info3 = 0
@classmethod
def from_req(cls, req: ADBHeader) -> "ADBCampaignResponse":
c = cls(req.game_id, req.store_id, req.keychip_id)
c.head.protocol_ver = req.protocol_ver
return c
def make(self) -> bytes:
resp_struct = Struct(
"info0" / Int32sl,
"info1" / Int32sl,
"info2" / Int32sl,
"info3" / Int32sl,
).build(
info0 = self.info0,
info1 = self.info1,
info2 = self.info2,
info3 = self.info3,
)
self.head.length = HEADER_SIZE + len(resp_struct)
return self.head.make() + resp_struct
class ADBCampaignClearRequest(ADBBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
self.aime_id = struct.unpack_from("<i", data, 0x20)
class ADBCampaignClearResponse(ADBBaseResponse):
def __init__(self, game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", code: int = 0x0E, length: int = 0x50, status: int = 1) -> None:
super().__init__(code, length, status, game_id, store_id, keychip_id)
self.campaign_clear_status = [CampaignClear(), CampaignClear(), CampaignClear()]
@classmethod
def from_req(cls, req: ADBHeader) -> "ADBCampaignResponse":
c = cls(req.game_id, req.store_id, req.keychip_id)
c.head.protocol_ver = req.protocol_ver
return c
def make(self) -> bytes:
body = b""
for c in self.campaign_clear_status:
body += c.make()
self.head.length = HEADER_SIZE + len(body)
return self.head.make() + body

View File

@ -0,0 +1,85 @@
from construct import Struct, Int32sl, Padding, Int8ub, Int16sl
from typing import Union
from .base import *
class ADBFelicaLookupRequest(ADBBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
idm, pmm = struct.unpack_from(">QQ", data, 0x20)
self.idm = hex(idm)[2:].upper()
self.pmm = hex(pmm)[2:].upper()
class ADBFelicaLookupResponse(ADBBaseResponse):
def __init__(self, access_code: str = None, game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", code: int = 0x03, length: int = 0x30, status: int = 1) -> None:
super().__init__(code, length, status, game_id, store_id, keychip_id)
self.access_code = access_code if access_code is not None else "00000000000000000000"
@classmethod
def from_req(cls, req: ADBHeader, access_code: str = None) -> "ADBFelicaLookupResponse":
c = cls(access_code, req.game_id, req.store_id, req.keychip_id)
c.head.protocol_ver = req.protocol_ver
return c
def make(self) -> bytes:
resp_struct = Struct(
"felica_idx" / Int32ul,
"access_code" / Int8ub[10],
Padding(2)
).build(dict(
felica_idx = 0,
access_code = bytes.fromhex(self.access_code)
))
self.head.length = HEADER_SIZE + len(resp_struct)
return self.head.make() + resp_struct
class ADBFelicaLookup2Request(ADBBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
self.random = struct.unpack_from("<16s", data, 0x20)[0]
idm, pmm = struct.unpack_from(">QQ", data, 0x30)
self.card_key_ver, self.write_ct, self.maca, company, fw_ver, self.dfc = struct.unpack_from("<16s16sQccH", data, 0x40)
self.idm = hex(idm)[2:].upper()
self.pmm = hex(pmm)[2:].upper()
self.company = CompanyCodes(int.from_bytes(company, 'little'))
self.fw_ver = ReaderFwVer.from_byte(fw_ver)
class ADBFelicaLookup2Response(ADBBaseResponse):
def __init__(self, user_id: Union[int, None] = None, access_code: Union[str, None] = None, game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", code: int = 0x12, length: int = 0x130, status: int = 1) -> None:
super().__init__(code, length, status, game_id, store_id, keychip_id)
self.user_id = user_id if user_id is not None else -1
self.access_code = access_code if access_code is not None else "00000000000000000000"
self.company = CompanyCodes.SEGA
self.portal_status = PortalRegStatus.NO_REG
self.auth_key = [0] * 256
@classmethod
def from_req(cls, req: ADBHeader, user_id: Union[int, None] = None, access_code: Union[str, None] = None) -> "ADBFelicaLookup2Response":
c = cls(user_id, access_code, req.game_id, req.store_id, req.keychip_id)
c.head.protocol_ver = req.protocol_ver
return c
def make(self) -> bytes:
resp_struct = Struct(
"user_id" / Int32sl,
"relation1" / Int32sl,
"relation2" / Int32sl,
"access_code" / Int8ub[10],
"portal_status" / Int8ub,
"company_code" / Int8ub,
Padding(8),
"auth_key" / Int8ub[256],
).build(dict(
user_id = self.user_id,
relation1 = -1, # Unsupported
relation2 = -1, # Unsupported
access_code = bytes.fromhex(self.access_code),
portal_status = self.portal_status.value,
company_code = self.company.value,
auth_key = self.auth_key
))
self.head.length = HEADER_SIZE + len(resp_struct)
return self.head.make() + resp_struct

56
core/adb_handlers/log.py Normal file
View File

@ -0,0 +1,56 @@
from construct import Struct, Padding, Int8sl
from typing import Final, List
from .base import *
NUM_LOGS: Final[int] = 20
NUM_LEN_LOG_EX: Final[int] = 48
class AmLogEx:
def __init__(self, data: bytes) -> None:
self.aime_id, status, self.user_id, self.credit_ct, self.bet_ct, self.won_ct, self.local_time, \
self.tseq, self.place_id = struct.unpack("<IIQiii4xQiI", data)
self.status = LogStatus(status)
class ADBStatusLogRequest(ADBBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
self.aime_id, status = struct.unpack_from("<II", data, 0x20)
self.status = LogStatus(status)
class ADBLogRequest(ADBBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
self.aime_id, status, self.user_id, self.credit_ct, self.bet_ct, self.won_ct = struct.unpack_from("<IIQiii", data, 0x20)
self.status = LogStatus(status)
class ADBLogExRequest(ADBBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
self.logs: List[AmLogEx] = []
for x in range(NUM_LOGS):
self.logs.append(AmLogEx(data[0x20 + (NUM_LEN_LOG_EX * x): 0x50 + (NUM_LEN_LOG_EX * x)]))
self.num_logs = struct.unpack_from("<I", data, 0x03E0)[0]
class ADBLogExResponse(ADBBaseResponse):
def __init__(self, game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", protocol_ver: int = 12423, code: int = 20, length: int = 64, status: int = 1) -> None:
super().__init__(code, length, status, game_id, store_id, keychip_id, protocol_ver)
@classmethod
def from_req(cls, req: ADBHeader) -> "ADBLogExResponse":
c = cls(req.game_id, req.store_id, req.keychip_id, req.protocol_ver)
return c
def make(self) -> bytes:
resp_struct = Struct(
"log_result" / Int8sl[NUM_LOGS],
Padding(12)
)
body = resp_struct.build(dict(
log_result = [1] * NUM_LOGS
))
self.head.length = HEADER_SIZE + len(body)
return self.head.make() + body

View File

@ -0,0 +1,82 @@
from construct import Struct, Int32sl, Padding, Int8sl
from typing import Union
from .base import *
class ADBLookupException(Exception):
pass
class ADBLookupRequest(ADBBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
self.access_code = data[0x20:0x2A].hex()
company_code, fw_version, self.serial_number = struct.unpack_from("<bbI", data, 0x2A)
try:
self.company_code = CompanyCodes(company_code)
except ValueError as e:
raise ADBLookupException(f"Invalid company code - {e}")
self.fw_version = ReaderFwVer.from_byte(fw_version)
class ADBLookupResponse(ADBBaseResponse):
def __init__(self, user_id: Union[int, None], game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", code: int = 0x06, length: int = 0x30, status: int = 1) -> None:
super().__init__(code, length, status, game_id, store_id, keychip_id)
self.user_id = user_id if user_id is not None else -1
self.portal_reg = PortalRegStatus.NO_REG
@classmethod
def from_req(cls, req: ADBHeader, user_id: Union[int, None]) -> "ADBLookupResponse":
c = cls(user_id, req.game_id, req.store_id, req.keychip_id)
c.head.protocol_ver = req.protocol_ver
return c
def make(self):
resp_struct = Struct(
"user_id" / Int32sl,
"portal_reg" / Int8sl,
Padding(11)
)
body = resp_struct.build(dict(
user_id = self.user_id,
portal_reg = self.portal_reg.value
))
self.head.length = HEADER_SIZE + len(body)
return self.head.make() + body
class ADBLookupExResponse(ADBBaseResponse):
def __init__(self, user_id: Union[int, None], game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888",
code: int = 0x10, length: int = 0x130, status: int = 1) -> None:
super().__init__(code, length, status, game_id, store_id, keychip_id)
self.user_id = user_id if user_id is not None else -1
self.portal_reg = PortalRegStatus.NO_REG
self.auth_key = [0] * 256
@classmethod
def from_req(cls, req: ADBHeader, user_id: Union[int, None]) -> "ADBLookupExResponse":
c = cls(user_id, req.game_id, req.store_id, req.keychip_id)
c.head.protocol_ver = req.protocol_ver
return c
def make(self):
resp_struct = Struct(
"user_id" / Int32sl,
"portal_reg" / Int8sl,
Padding(3),
"auth_key" / Int8sl[256],
"relation1" / Int32sl,
"relation2" / Int32sl,
)
body = resp_struct.build(dict(
user_id = self.user_id,
portal_reg = self.portal_reg.value,
auth_key = self.auth_key,
relation1 = -1,
relation2 = -1
))
self.head.length = HEADER_SIZE + len(body)
return self.head.make() + body

View File

@ -1,234 +1,369 @@
from twisted.internet.protocol import Factory, Protocol
import logging, coloredlogs
from Crypto.Cipher import AES
import struct
from typing import Dict, Any
from typing import Dict, Tuple, Callable, Union, Optional
import asyncio
from logging.handlers import TimedRotatingFileHandler
from core.config import CoreConfig
from core.utils import create_sega_auth_key
from core.data import Data
from .adb_handlers import *
class AimedbProtocol(Protocol):
AIMEDB_RESPONSE_CODES = {
"felica_lookup": 0x03,
"lookup": 0x06,
"log": 0x0a,
"campaign": 0x0c,
"touch": 0x0e,
"lookup2": 0x10,
"felica_lookup2": 0x12,
"log2": 0x14,
"hello": 0x65
}
request_list: Dict[int, Any] = {}
def __init__(self, core_cfg: CoreConfig) -> None:
self.logger = logging.getLogger("aimedb")
self.config = core_cfg
class AimedbServlette():
request_list: Dict[int, Tuple[Callable[[bytes, int], Union[ADBBaseResponse, bytes]], int, str]] = {}
def __init__(self, core_cfg: CoreConfig) -> None:
self.config = core_cfg
self.data = Data(core_cfg)
if core_cfg.aimedb.key == "":
self.logger = logging.getLogger("aimedb")
if not hasattr(self.logger, "initted"):
log_fmt_str = "[%(asctime)s] Aimedb | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.config.server.log_dir, "aimedb"),
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
self.logger.setLevel(self.config.aimedb.loglevel)
coloredlogs.install(
level=core_cfg.aimedb.loglevel, logger=self.logger, fmt=log_fmt_str
)
self.logger.initted = True
if not core_cfg.aimedb.key:
self.logger.error("!!!KEY NOT SET!!!")
exit(1)
self.request_list[0x01] = self.handle_felica_lookup
self.request_list[0x04] = self.handle_lookup
self.request_list[0x05] = self.handle_register
self.request_list[0x09] = self.handle_log
self.request_list[0x0b] = self.handle_campaign
self.request_list[0x0d] = self.handle_touch
self.request_list[0x0f] = self.handle_lookup2
self.request_list[0x11] = self.handle_felica_lookup2
self.request_list[0x13] = self.handle_log2
self.request_list[0x64] = self.handle_hello
def append_padding(self, data: bytes):
"""Appends 0s to the end of the data until it's at the correct size"""
length = struct.unpack_from("<H", data, 6)
padding_size = length[0] - len(data)
data += bytes(padding_size)
return data
self.register_handler(0x01, 0x03, self.handle_felica_lookup, 'felica_lookup')
self.register_handler(0x02, 0x03, self.handle_felica_register, 'felica_register')
def connectionMade(self) -> None:
self.logger.debug(f"{self.transport.getPeer().host} Connected")
self.register_handler(0x04, 0x06, self.handle_lookup, 'lookup')
self.register_handler(0x05, 0x06, self.handle_register, 'register')
def connectionLost(self, reason) -> None:
self.logger.debug(f"{self.transport.getPeer().host} Disconnected - {reason.value}")
self.register_handler(0x07, 0x08, self.handle_status_log, 'status_log')
self.register_handler(0x09, 0x0A, self.handle_log, 'aime_log')
self.register_handler(0x0B, 0x0C, self.handle_campaign, 'campaign')
self.register_handler(0x0D, 0x0E, self.handle_campaign_clear, 'campaign_clear')
self.register_handler(0x0F, 0x10, self.handle_lookup_ex, 'lookup_ex')
self.register_handler(0x11, 0x12, self.handle_felica_lookup_ex, 'felica_lookup_ex')
self.register_handler(0x13, 0x14, self.handle_log_ex, 'aime_log_ex')
self.register_handler(0x64, 0x65, self.handle_hello, 'hello')
def register_handler(self, cmd: int, resp:int, handler: Callable[[bytes, int], Union[ADBBaseResponse, bytes]], name: str) -> None:
self.request_list[cmd] = (handler, resp, name)
def dataReceived(self, data: bytes) -> None:
def start(self) -> None:
self.logger.info(f"Start on port {self.config.aimedb.port}")
addr = self.config.aimedb.listen_address if self.config.aimedb.listen_address else self.config.server.listen_address
asyncio.create_task(asyncio.start_server(self.dataReceived, addr, self.config.aimedb.port))
async def dataReceived(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
self.logger.debug(f"Connection made from {writer.get_extra_info('peername')[0]}")
while True:
try:
data: bytes = await reader.read(4096)
if len(data) == 0:
self.logger.debug("Connection closed")
return
await self.process_data(data, reader, writer)
await writer.drain()
except ConnectionResetError as e:
self.logger.debug("Connection reset, disconnecting")
return
async def process_data(self, data: bytes, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> Optional[bytes]:
addr = writer.get_extra_info('peername')[0]
cipher = AES.new(self.config.aimedb.key.encode(), AES.MODE_ECB)
try:
decrypted = cipher.decrypt(data)
except:
self.logger.error(f"Failed to decrypt {data.hex()}")
return None
self.logger.debug(f"{self.transport.getPeer().host} wrote {decrypted.hex()}")
if not decrypted[1] == 0xa1 and not decrypted[0] == 0x3e:
self.logger.error(f"Bad magic")
return None
req_code = decrypted[4]
if req_code == 0x66:
self.logger.info(f"goodbye from {self.transport.getPeer().host}")
self.transport.loseConnection()
except Exception as e:
self.logger.error(f"Failed to decrypt {data.hex()} because {e}")
return
self.logger.debug(f"{addr} wrote {decrypted.hex()}")
try:
resp = self.request_list[req_code](decrypted)
encrypted = cipher.encrypt(resp)
self.logger.debug(f"Response {resp.hex()}")
self.transport.write(encrypted)
except KeyError:
self.logger.error(f"Unknown command code {hex(req_code)}")
return None
except ValueError as e:
self.logger.error(f"Failed to encrypt {resp.hex()} because {e}")
return None
def handle_campaign(self, data: bytes) -> bytes:
self.logger.info(f"campaign from {self.transport.getPeer().host}")
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["campaign"], 0x0200, 0x0001)
return self.append_padding(ret)
def handle_hello(self, data: bytes) -> bytes:
self.logger.info(f"hello from {self.transport.getPeer().host}")
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["hello"], 0x0020, 0x0001)
return self.append_padding(ret)
def handle_lookup(self, data: bytes) -> bytes:
luid = data[0x20: 0x2a].hex()
user_id = self.data.card.get_user_id_from_card(access_code=luid)
if user_id is None: user_id = -1
self.logger.info(f"lookup from {self.transport.getPeer().host}: luid {luid} -> user_id {user_id}")
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["lookup"], 0x0130, 0x0001)
ret += bytes(0x20 - len(ret))
if user_id is None: ret += struct.pack("<iH", -1, 0)
else: ret += struct.pack("<l", user_id)
return self.append_padding(ret)
def handle_lookup2(self, data: bytes) -> bytes:
self.logger.info(f"lookup2")
ret = bytearray(self.handle_lookup(data))
ret[4] = self.AIMEDB_RESPONSE_CODES["lookup2"]
return bytes(ret)
def handle_felica_lookup(self, data: bytes) -> bytes:
idm = data[0x20: 0x28].hex()
pmm = data[0x28: 0x30].hex()
access_code = self.data.card.to_access_code(idm)
self.logger.info(f"felica_lookup from {self.transport.getPeer().host}: idm {idm} pmm {pmm} -> access_code {access_code}")
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["felica_lookup"], 0x0030, 0x0001)
ret += bytes(26)
ret += bytes.fromhex(access_code)
return self.append_padding(ret)
def handle_felica_lookup2(self, data: bytes) -> bytes:
idm = data[0x30: 0x38].hex()
pmm = data[0x38: 0x40].hex()
access_code = self.data.card.to_access_code(idm)
user_id = self.data.card.get_user_id_from_card(access_code=access_code)
if user_id is None: user_id = -1
self.logger.info(f"felica_lookup2 from {self.transport.getPeer().host}: idm {idm} ipm {pmm} -> access_code {access_code} user_id {user_id}")
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["felica_lookup2"], 0x0140, 0x0001)
ret += bytes(22)
ret += struct.pack("<lq", user_id, -1) # first -1 is ext_id, 3rd is access code
ret += bytes.fromhex(access_code)
ret += struct.pack("<l", 1)
head = ADBHeader.from_data(decrypted)
return self.append_padding(ret)
def handle_touch(self, data: bytes) -> bytes:
self.logger.info(f"touch from {self.transport.getPeer().host}")
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["touch"], 0x0050, 0x0001)
ret += bytes(5)
ret += struct.pack("<3H", 0x6f, 0, 1)
except ADBHeaderException as e:
self.logger.error(f"Error parsing ADB header: {e}")
try:
encrypted = cipher.encrypt(ADBBaseResponse().make())
writer.write(encrypted)
await writer.drain()
return
return self.append_padding(ret)
except Exception as e:
self.logger.error(f"Failed to encrypt default response because {e}")
return
def handle_register(self, data: bytes) -> bytes:
luid = data[0x20: 0x2a].hex()
if self.config.server.allow_registration:
user_id = self.data.user.create_user()
if head.keychip_id == "ABCD1234567" or head.store_id == 0xfff0:
self.logger.warning(f"Request from uninitialized AMLib: {vars(head)}")
if user_id is None:
user_id = -1
self.logger.error("Failed to register user!")
if head.cmd == 0x66:
self.logger.info("Goodbye")
writer.close()
return
else:
card_id = self.data.card.create_card(user_id, luid)
handler, resp_code, name = self.request_list.get(head.cmd, (self.handle_default, None, 'default'))
if card_id is None:
user_id = -1
self.logger.error("Failed to register card!")
if resp_code is None:
self.logger.warning(f"No handler for cmd {hex(head.cmd)}")
elif resp_code > 0:
self.logger.info(f"{name} from {head.keychip_id} ({head.game_id}) @ {addr}")
resp = await handler(decrypted, resp_code)
self.logger.info(f"register from {self.transport.getPeer().host}: luid {luid} -> user_id {user_id}")
if type(resp) == ADBBaseResponse or issubclass(type(resp), ADBBaseResponse):
resp_bytes = resp.make()
elif type(resp) == bytes:
resp_bytes = resp
elif resp is None: # Nothing to send, probably a goodbye
self.logger.warn(f"None return by handler for {name}")
return
else:
self.logger.info(f"register from {self.transport.getPeer().host} blocked!: luid {luid}")
self.logger.error(f"Unsupported type returned by ADB handler for {name}: {type(resp)}")
raise TypeError(f"Unsupported type returned by ADB handler for {name}: {type(resp)}")
try:
encrypted = cipher.encrypt(resp_bytes)
self.logger.debug(f"Response {resp_bytes.hex()}")
writer.write(encrypted)
except Exception as e:
self.logger.error(f"Failed to encrypt {resp_bytes.hex()} because {e}")
async def handle_default(self, data: bytes, resp_code: int, length: int = 0x20) -> ADBBaseResponse:
req = ADBHeader.from_data(data)
return ADBBaseResponse(resp_code, length, 1, req.game_id, req.store_id, req.keychip_id, req.protocol_ver)
async def handle_hello(self, data: bytes, resp_code: int) -> ADBBaseResponse:
return await self.handle_default(data, resp_code)
async def handle_campaign(self, data: bytes, resp_code: int) -> ADBBaseResponse:
h = ADBHeader.from_data(data)
if h.protocol_ver >= 0x3030:
req = h
resp = ADBCampaignResponse.from_req(req)
else:
req = ADBOldCampaignRequest(data)
self.logger.info(f"Legacy campaign request for campaign {req.campaign_id} (protocol version {hex(h.protocol_ver)})")
resp = ADBOldCampaignResponse.from_req(req.head)
# We don't currently support campaigns
return resp
async def handle_lookup(self, data: bytes, resp_code: int) -> ADBBaseResponse:
req = ADBLookupRequest(data)
user_id = await self.data.card.get_user_id_from_card(req.access_code)
is_banned = await self.data.card.get_card_banned(req.access_code)
is_locked = await self.data.card.get_card_locked(req.access_code)
ret = ADBLookupResponse.from_req(req.head, user_id)
if is_banned and is_locked:
ret.head.status = ADBStatus.BAN_SYS_USER
elif is_banned:
ret.head.status = ADBStatus.BAN_SYS
elif is_locked:
ret.head.status = ADBStatus.LOCK_USER
self.logger.info(
f"access_code {req.access_code} -> user_id {ret.user_id}"
)
if user_id and user_id > 0:
await self.data.card.update_card_last_login(req.access_code)
return ret
async def handle_lookup_ex(self, data: bytes, resp_code: int) -> ADBBaseResponse:
req = ADBLookupRequest(data)
user_id = await self.data.card.get_user_id_from_card(req.access_code)
is_banned = await self.data.card.get_card_banned(req.access_code)
is_locked = await self.data.card.get_card_locked(req.access_code)
ret = ADBLookupExResponse.from_req(req.head, user_id)
if is_banned and is_locked:
ret.head.status = ADBStatus.BAN_SYS_USER
elif is_banned:
ret.head.status = ADBStatus.BAN_SYS
elif is_locked:
ret.head.status = ADBStatus.LOCK_USER
self.logger.info(
f"access_code {req.access_code} -> user_id {ret.user_id}"
)
if user_id and user_id > 0 and self.config.aimedb.id_secret:
auth_key = create_sega_auth_key(user_id, req.head.game_id, req.head.store_id, req.head.keychip_id, self.config.aimedb.id_secret, self.config.aimedb.id_lifetime_seconds)
if auth_key is not None:
auth_key_extra_len = 256 - len(auth_key)
auth_key_full = auth_key.encode() + (b"\0" * auth_key_extra_len)
self.logger.debug(f"Generated auth token {auth_key}")
ret.auth_key = auth_key_full
if user_id and user_id > 0:
await self.data.card.update_card_last_login(req.access_code)
return ret
async def handle_felica_lookup(self, data: bytes, resp_code: int) -> bytes:
"""
On official, I think a card has to be registered for this to actually work, but
I'm making the executive decision to not implement that and just kick back our
faux generated access code. The real felica IDm -> access code conversion is done
on the ADB server, which we do not and will not ever have access to. Because we can
assure that all IDms will be unique, this basic 0-padded hex -> int conversion will
be fine.
"""
req = ADBFelicaLookupRequest(data)
ac = self.data.card.to_access_code(req.idm)
self.logger.info(
f"idm {req.idm} ipm {req.pmm} -> access_code {ac}"
)
return ADBFelicaLookupResponse.from_req(req.head, ac)
async def handle_felica_register(self, data: bytes, resp_code: int) -> bytes:
"""
I've never seen this used.
"""
req = ADBFelicaLookupRequest(data)
ac = self.data.card.to_access_code(req.idm)
if self.config.server.allow_user_registration:
user_id = await self.data.user.create_user()
if user_id is None:
self.logger.error("Failed to register user!")
user_id = -1
else:
card_id = await self.data.card.create_card(user_id, ac)
if card_id is None:
self.logger.error("Failed to register card!")
user_id = -1
self.logger.info(
f"Register access code {ac} (IDm: {req.idm} PMm: {req.pmm}) -> user_id {user_id}"
)
else:
self.logger.info(
f"Registration blocked!: access code {ac} (IDm: {req.idm} PMm: {req.pmm})"
)
if user_id > 0:
await self.data.card.update_card_last_login(ac)
return ADBFelicaLookupResponse.from_req(req.head, ac)
async def handle_felica_lookup_ex(self, data: bytes, resp_code: int) -> bytes:
req = ADBFelicaLookup2Request(data)
access_code = self.data.card.to_access_code(req.idm)
user_id = await self.data.card.get_user_id_from_card(access_code=access_code)
if user_id is None:
user_id = -1
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["lookup"], 0x0030, 0x0001 if user_id > -1 else 0)
ret += bytes(0x20 - len(ret))
ret += struct.pack("<l", user_id)
self.logger.info(
f"idm {req.idm} ipm {req.pmm} -> access_code {access_code} user_id {user_id}"
)
return self.append_padding(ret)
resp = ADBFelicaLookup2Response.from_req(req.head, user_id, access_code)
def handle_log(self, data: bytes) -> bytes:
# TODO: Save aimedb logs
self.logger.info(f"log from {self.transport.getPeer().host}")
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["log"], 0x0020, 0x0001)
return self.append_padding(ret)
def handle_log2(self, data: bytes) -> bytes:
self.logger.info(f"log2 from {self.transport.getPeer().host}")
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["log2"], 0x0040, 0x0001)
ret += bytes(22)
ret += struct.pack("H", 1)
return self.append_padding(ret)
class AimedbFactory(Factory):
protocol = AimedbProtocol
def __init__(self, cfg: CoreConfig) -> None:
self.config = cfg
log_fmt_str = "[%(asctime)s] Aimedb | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
self.logger = logging.getLogger("aimedb")
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "aimedb"), when="d", backupCount=10)
fileHandler.setFormatter(log_fmt)
if user_id and user_id > 0 and self.config.aimedb.id_secret:
auth_key = create_sega_auth_key(user_id, req.head.game_id, req.head.store_id, req.head.keychip_id, self.config.aimedb.id_secret, self.config.aimedb.id_lifetime_seconds)
if auth_key is not None:
auth_key_extra_len = 256 - len(auth_key)
auth_key_full = auth_key.encode() + (b"\0" * auth_key_extra_len)
self.logger.debug(f"Generated auth token {auth_key}")
resp.auth_key = auth_key_full
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
if user_id and user_id > 0:
await self.data.card.update_card_last_login(access_code)
return resp
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
self.logger.setLevel(self.config.aimedb.loglevel)
coloredlogs.install(level=cfg.aimedb.loglevel, logger=self.logger, fmt=log_fmt_str)
if self.config.aimedb.key == "":
self.logger.error("Please set 'key' field in your config file.")
exit(1)
async def handle_campaign_clear(self, data: bytes, resp_code: int) -> ADBBaseResponse:
req = ADBCampaignClearRequest(data)
resp = ADBCampaignClearResponse.from_req(req.head)
# We don't support campaign stuff
return resp
async def handle_register(self, data: bytes, resp_code: int) -> bytes:
req = ADBLookupRequest(data)
user_id = -1
if self.config.server.allow_user_registration:
user_id = await self.data.user.create_user()
if user_id is None:
self.logger.error("Failed to register user!")
user_id = -1
else:
card_id = await self.data.card.create_card(user_id, req.access_code)
if card_id is None:
self.logger.error("Failed to register card!")
user_id = -1
self.logger.info(
f"Register access code {req.access_code} -> user_id {user_id}"
)
else:
self.logger.info(
f"Registration blocked!: access code {req.access_code}"
)
resp = ADBLookupResponse.from_req(req.head, user_id)
if resp.user_id <= 0:
resp.head.status = ADBStatus.BAN_SYS # Closest we can get to a "You cannot register"
else:
await self.data.card.update_card_last_login(req.access_code)
return resp
# TODO: Save these in some capacity, as deemed relevant
async def handle_status_log(self, data: bytes, resp_code: int) -> bytes:
req = ADBStatusLogRequest(data)
self.logger.info(f"User {req.aime_id} logged {req.status.name} event")
return ADBBaseResponse(resp_code, 0x20, 1, req.head.game_id, req.head.store_id, req.head.keychip_id, req.head.protocol_ver)
async def handle_log(self, data: bytes, resp_code: int) -> bytes:
req = ADBLogRequest(data)
self.logger.info(f"User {req.aime_id} logged {req.status.name} event, credit_ct: {req.credit_ct} bet_ct: {req.bet_ct} won_ct: {req.won_ct}")
return ADBBaseResponse(resp_code, 0x20, 1, req.head.game_id, req.head.store_id, req.head.keychip_id, req.head.protocol_ver)
async def handle_log_ex(self, data: bytes, resp_code: int) -> bytes:
req = ADBLogExRequest(data)
strs = []
self.logger.info(f"Recieved {req.num_logs} or {len(req.logs)} logs")
for x in range(req.num_logs):
self.logger.debug(f"User {req.logs[x].aime_id} logged {req.logs[x].status.name} event, credit_ct: {req.logs[x].credit_ct} bet_ct: {req.logs[x].bet_ct} won_ct: {req.logs[x].won_ct}")
return ADBLogExResponse.from_req(req.head)
self.logger.info(f"Ready on port {self.config.aimedb.port}")
def buildProtocol(self, addr):
return AimedbProtocol(self.config)

File diff suppressed because it is too large Load Diff

92
core/app.py Normal file
View File

@ -0,0 +1,92 @@
import yaml
import logging
import coloredlogs
from logging.handlers import TimedRotatingFileHandler
from starlette.routing import Route
from starlette.requests import Request
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from os import environ, path, mkdir, W_OK, access
from typing import List
from core import CoreConfig, TitleServlet, MuchaServlet, AllnetServlet, BillingServlet, AimedbServlette
from core.frontend import FrontendServlet
async def dummy_rt(request: Request):
return PlainTextResponse("Service OK")
cfg_dir = environ.get("ARTEMIS_CFG_DIR", "config")
cfg: CoreConfig = CoreConfig()
if path.exists(f"{cfg_dir}/core.yaml"):
cfg.update(yaml.safe_load(open(f"{cfg_dir}/core.yaml")))
if not path.exists(cfg.server.log_dir):
mkdir(cfg.server.log_dir)
if not access(cfg.server.log_dir, W_OK):
print(
f"Log directory {cfg.server.log_dir} NOT writable, please check permissions"
)
exit(1)
logger = logging.getLogger("core")
log_fmt_str = "[%(asctime)s] Core | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(cfg.server.log_dir, "core"), when="d", backupCount=10
)
fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
logger.addHandler(fileHandler)
logger.addHandler(consoleHandler)
log_lv = logging.DEBUG if cfg.server.is_develop else logging.INFO
logger.setLevel(log_lv)
coloredlogs.install(level=log_lv, logger=logger, fmt=log_fmt_str)
logger.info(f"Artemis starting in {'develop' if cfg.server.is_develop else 'production'} mode")
title = TitleServlet(cfg, cfg_dir) # This has to be loaded first to load plugins
mucha = MuchaServlet(cfg, cfg_dir)
route_lst: List[Route] = [
# Mucha
Route("/mucha_front/boardauth.do", mucha.handle_boardauth, methods=["POST"]),
Route("/mucha_front/updatacheck.do", mucha.handle_updatecheck, methods=["POST"]),
Route("/mucha_front/downloadstate.do", mucha.handle_dlstate, methods=["POST"]),
# General
Route("/", dummy_rt),
Route("/robots.txt", FrontendServlet.robots)
]
if not cfg.billing.standalone:
billing = BillingServlet(cfg, cfg_dir)
route_lst += [
Route("/request", billing.handle_billing_request, methods=["POST"]),
Route("/request/", billing.handle_billing_request, methods=["POST"]),
]
if not cfg.allnet.standalone:
allnet = AllnetServlet(cfg, cfg_dir)
route_lst += [
Route("/sys/servlet/PowerOn", allnet.handle_poweron, methods=["GET", "POST"]),
Route("/sys/servlet/DownloadOrder", allnet.handle_dlorder, methods=["GET", "POST"]),
Route("/sys/servlet/LoaderStateRecorder", allnet.handle_loaderstaterecorder, methods=["GET", "POST"]),
Route("/sys/servlet/Alive", allnet.handle_alive, methods=["GET", "POST"]),
Route("/naomitest.html", allnet.handle_naomitest),
]
if cfg.allnet.allow_online_updates:
route_lst += [
Route("/report-api/Report", allnet.handle_dlorder_report, methods=["POST"]),
Route("/dl/ini/{file:str}", allnet.handle_dlorder_ini),
]
for code, game in title.title_registry.items():
route_lst += game.get_routes()
app = Starlette(cfg.server.is_develop, route_lst)

View File

@ -7,27 +7,110 @@ class ServerConfig:
@property
def listen_address(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'listen_address', default='127.0.0.1')
"""
Address Artemis will bind to and listen on
"""
return CoreConfig.get_config_field(
self.__config, "core", "server", "listen_address", default="127.0.0.1"
)
@property
def hostname(self) -> str:
"""
Hostname sent to games
"""
return CoreConfig.get_config_field(
self.__config, "core", "server", "hostname", default="localhost"
)
@property
def port(self) -> int:
"""
Port the game will listen on
"""
return CoreConfig.get_config_field(
self.__config, "core", "server", "port", default=80
)
@property
def ssl_key(self) -> str:
return CoreConfig.get_config_field(
self.__config, "core", "server", "ssl_key", default="cert/title.key"
)
@property
def ssl_cert(self) -> str:
return CoreConfig.get_config_field(
self.__config, "core", "title", "ssl_cert", default="cert/title.pem"
)
@property
def allow_user_registration(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'allow_user_registration', default=True)
return CoreConfig.get_config_field(
self.__config, "core", "server", "allow_user_registration", default=True
)
@property
def allow_unregistered_serials(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'allow_unregistered_serials', default=True)
return CoreConfig.get_config_field(
self.__config, "core", "server", "allow_unregistered_serials", default=True
)
@property
def name(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'name', default="ARTEMiS")
return CoreConfig.get_config_field(
self.__config, "core", "server", "name", default="ARTEMiS"
)
@property
def is_develop(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'is_develop', default=True)
return CoreConfig.get_config_field(
self.__config, "core", "server", "is_develop", default=True
)
@property
def is_using_proxy(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "core", "server", "is_using_proxy", default=False
)
@property
def proxy_port(self) -> int:
"""
What port the proxy is listening on. This will be sent instead of 'port' if
is_using_proxy is True and this value is non-zero
"""
return CoreConfig.get_config_field(
self.__config, "core", "server", "proxy_port", default=0
)
@property
def proxy_port_ssl(self) -> int:
"""
What port the proxy is listening for secure connections on. This will be sent
instead of 'port' if is_using_proxy is True and this value is non-zero
"""
return CoreConfig.get_config_field(
self.__config, "core", "server", "proxy_port_ssl", default=0
)
@property
def log_dir(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'log_dir', default='logs')
return CoreConfig.get_config_field(
self.__config, "core", "server", "log_dir", default="logs"
)
@property
def check_arcade_ip(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "core", "server", "check_arcade_ip", default=False
)
@property
def strict_ip_checking(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "core", "server", "strict_ip_checking", default=False
)
class TitleConfig:
def __init__(self, parent_config: "CoreConfig") -> None:
@ -35,15 +118,23 @@ class TitleConfig:
@property
def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'title', 'loglevel', default="info"))
return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "core", "title", "loglevel", default="info"
)
)
@property
def hostname(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'title', 'hostname', default="localhost")
def reboot_start_time(self) -> str:
return CoreConfig.get_config_field(
self.__config, "core", "title", "reboot_start_time", default=""
)
@property
def port(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'title', 'port', default=8080)
def reboot_end_time(self) -> str:
return CoreConfig.get_config_field(
self.__config, "core", "title", "reboot_end_time", default=""
)
class DatabaseConfig:
def __init__(self, parent_config: "CoreConfig") -> None:
@ -51,139 +142,233 @@ class DatabaseConfig:
@property
def host(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'host', default="localhost")
return CoreConfig.get_config_field(
self.__config, "core", "database", "host", default="localhost"
)
@property
def username(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'username', default='aime')
return CoreConfig.get_config_field(
self.__config, "core", "database", "username", default="aime"
)
@property
def password(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'password', default='aime')
return CoreConfig.get_config_field(
self.__config, "core", "database", "password", default="aime"
)
@property
def name(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'name', default='aime')
return CoreConfig.get_config_field(
self.__config, "core", "database", "name", default="aime"
)
@property
def port(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'port', default=3306)
return CoreConfig.get_config_field(
self.__config, "core", "database", "port", default=3306
)
@property
def protocol(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'type', default="mysql")
@property
def sha2_password(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'sha2_password', default=False)
@property
def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'database', 'loglevel', default="info"))
return CoreConfig.get_config_field(
self.__config, "core", "database", "protocol", default="mysql"
)
@property
def user_table_autoincrement_start(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'user_table_autoincrement_start', default=10000)
def sha2_password(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "core", "database", "sha2_password", default=False
)
@property
def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "core", "database", "loglevel", default="info"
)
)
@property
def enable_memcached(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "core", "database", "enable_memcached", default=True
)
@property
def memcached_host(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'memcached_host', default="localhost")
return CoreConfig.get_config_field(
self.__config, "core", "database", "memcached_host", default="localhost"
)
class FrontendConfig:
def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config
@property
def enable(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'frontend', 'enable', default=False)
def enable(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "core", "frontend", "enable", default=False
)
@property
def port(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'frontend', 'port', default=8090)
return CoreConfig.get_config_field(
self.__config, "core", "frontend", "port", default=8080
)
@property
def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'frontend', 'loglevel', default="info"))
return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "core", "frontend", "loglevel", default="info"
)
)
@property
def secret(self) -> str:
return CoreConfig.get_config_field(
self.__config, "core", "frontend", "secret", default=""
)
class AllnetConfig:
def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config
@property
def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'allnet', 'loglevel', default="info"))
@property
def port(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'allnet', 'port', default=80)
def standalone(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "core", "allnet", "standalone", default=False
)
@property
def port(self) -> int:
return CoreConfig.get_config_field(
self.__config, "core", "allnet", "port", default=80
)
@property
def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "core", "allnet", "loglevel", default="info"
)
)
@property
def allow_online_updates(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'allnet', 'allow_online_updates', default=False)
return CoreConfig.get_config_field(
self.__config, "core", "allnet", "allow_online_updates", default=False
)
@property
def update_cfg_folder(self) -> str:
return CoreConfig.get_config_field(
self.__config, "core", "allnet", "update_cfg_folder", default=""
)
class BillingConfig:
def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config
@property
def standalone(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "core", "billing", "standalone", default=True
)
@property
def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "core", "billing", "loglevel", default="info"
)
)
@property
def port(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'billing', 'port', default=8443)
return CoreConfig.get_config_field(
self.__config, "core", "billing", "port", default=8443
)
@property
def ssl_key(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'billing', 'ssl_key', default="cert/server.key")
return CoreConfig.get_config_field(
self.__config, "core", "billing", "ssl_key", default="cert/server.key"
)
@property
def ssl_cert(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'billing', 'ssl_cert', default="cert/server.pem")
return CoreConfig.get_config_field(
self.__config, "core", "billing", "ssl_cert", default="cert/server.pem"
)
@property
def signing_key(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'billing', 'signing_key', default="cert/billing.key")
return CoreConfig.get_config_field(
self.__config, "core", "billing", "signing_key", default="cert/billing.key"
)
class AimedbConfig:
def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config
@property
def enable(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "core", "aimedb", "enable", default=True
)
@property
def listen_address(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "core", "aimedb", "listen_address", default=""
)
@property
def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'aimedb', 'loglevel', default="info"))
return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "core", "aimedb", "loglevel", default="info"
)
)
@property
def port(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'aimedb', 'port', default=22345)
return CoreConfig.get_config_field(
self.__config, "core", "aimedb", "port", default=22345
)
@property
def key(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'aimedb', 'key', default="")
return CoreConfig.get_config_field(
self.__config, "core", "aimedb", "key", default=""
)
@property
def id_secret(self) -> str:
return CoreConfig.get_config_field(
self.__config, "core", "aimedb", "id_secret", default=""
)
@property
def id_lifetime_seconds(self) -> int:
return CoreConfig.get_config_field(
self.__config, "core", "aimedb", "id_lifetime_seconds", default=86400
)
class MuchaConfig:
def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config
@property
def enable(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'enable', default=False)
@property
def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'loglevel', default="info"))
@property
def hostname(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'hostname', default="localhost")
@property
def port(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'port', default=8444)
@property
def ssl_cert(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'ssl_cert', default="cert/server.pem")
@property
def signing_key(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'signing_key', default="cert/billing.key")
return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "core", "mucha", "loglevel", default="info"
)
)
class CoreConfig(dict):
def __init__(self) -> None:
@ -194,25 +379,41 @@ class CoreConfig(dict):
self.allnet = AllnetConfig(self)
self.billing = BillingConfig(self)
self.aimedb = AimedbConfig(self)
self.mucha = MuchaConfig(self)
@classmethod
def str_to_loglevel(cls, level_str: str):
if level_str.lower() == "error":
return logging.ERROR
elif level_str.lower().startswith("warn"): # Fits warn or warning
elif level_str.lower().startswith("warn"): # Fits warn or warning
return logging.WARN
elif level_str.lower() == "debug":
return logging.DEBUG
else:
return logging.INFO
return logging.INFO
@classmethod
def loglevel_to_str(cls, level: int) -> str:
if level == logging.ERROR:
return "error"
elif level == logging.WARN:
return "warn"
elif level == logging.INFO:
return "info"
elif level == logging.DEBUG:
return "debug"
else:
return "notset"
@classmethod
def get_config_field(cls, __config: dict, module, *path: str, default: Any = "") -> Any:
envKey = f'CFG_{module}_'
def get_config_field(
cls, __config: dict, module, *path: str, default: Any = ""
) -> Any:
envKey = f"CFG_{module}_"
for arg in path:
envKey += arg + '_'
if envKey.endswith('_'):
envKey += arg + "_"
if envKey.endswith("_"):
envKey = envKey[:-1]
if envKey in os.environ:

View File

@ -1,6 +1,7 @@
from enum import Enum
class MainboardPlatformCodes():
class MainboardPlatformCodes:
RINGEDGE = "AALE"
RINGWIDE = "AAML"
NU = "AAVE"
@ -8,7 +9,8 @@ class MainboardPlatformCodes():
ALLS_UX = "ACAE"
ALLS_HX = "ACAX"
class MainboardRevisions():
class MainboardRevisions:
RINGEDGE = 1
RINGEDGE2 = 2
@ -26,11 +28,70 @@ class MainboardRevisions():
ALLS_UX2 = 2
ALLS_HX2 = 12
class KeychipPlatformsCodes():
class KeychipPlatformsCodes:
RING = "A72E"
NU = ("A60E", "A60E", "A60E")
NUSX = ("A61X", "A69X")
ALLS = "A63E"
class RegionIDs(Enum):
pass
class AllnetCountryCode(Enum):
JAPAN = "JPN"
UNITED_STATES = "USA"
HONG_KONG = "HKG"
SINGAPORE = "SGP"
SOUTH_KOREA = "KOR"
TAIWAN = "TWN"
CHINA = "CHN"
class AllnetJapanRegionId(Enum):
NONE = 0
AICHI = 1
AOMORI = 2
AKITA = 3
ISHIKAWA = 4
IBARAKI = 5
IWATE = 6
EHIME = 7
OITA = 8
OSAKA = 9
OKAYAMA = 10
OKINAWA = 11
KAGAWA = 12
KAGOSHIMA = 13
KANAGAWA = 14
GIFU = 15
KYOTO = 16
KUMAMOTO = 17
GUNMA = 18
KOCHI = 19
SAITAMA = 20
SAGA = 21
SHIGA = 22
SHIZUOKA = 23
SHIMANE = 24
CHIBA = 25
TOKYO = 26
TOKUSHIMA = 27
TOCHIGI = 28
TOTTORI = 29
TOYAMA = 30
NAGASAKI = 31
NAGANO = 32
NARA = 33
NIIGATA = 34
HYOGO = 35
HIROSHIMA = 36
FUKUI = 37
FUKUOKA = 38
FUKUSHIMA = 39
HOKKAIDO = 40
MIE = 41
MIYAGI = 42
MIYAZAKI = 43
YAMAGATA = 44
YAMAGUCHI = 45
YAMANASHI = 46
WAKAYAMA = 47

View File

@ -1,2 +1,2 @@
from core.data.database import Data
from core.data.cache import cached
from core.data.cache import cached

1
core/data/alembic/README Normal file
View File

@ -0,0 +1 @@
Generic single-database configuration.

View File

@ -0,0 +1,64 @@
# A generic, single database configuration.
[alembic]
script_location=.
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# max length of characters to apply to the
# "slug" field
#truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; this defaults
# to migrations//versions. When using multiple version
# directories, initial revisions must be specified with --version-path
# version_locations = %(here)s/bar %(here)s/bat migrations//versions
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

81
core/data/alembic/env.py Normal file
View File

@ -0,0 +1,81 @@
from __future__ import with_statement
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig
from core.data.schema.base import metadata
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
raise Exception('Not implemented or configured!')
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
ini_section = config.get_section(config.config_ini_section)
overrides = context.get_x_argument(as_dictionary=True)
for override in overrides:
ini_section[override] = overrides[override]
connectable = engine_from_config(
ini_section,
prefix='sqlalchemy.',
poolclass=pool.NullPool)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
compare_type=True,
compare_server_default=True,
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@ -0,0 +1,56 @@
"""GekiChu rating tables
Revision ID: 6a7e8277763b
Revises: d8950c7ce2fc
Create Date: 2024-03-13 12:18:53.210018
"""
from alembic import op
from sqlalchemy import Column, Integer, String
# revision identifiers, used by Alembic.
revision = '6a7e8277763b'
down_revision = 'd8950c7ce2fc'
branch_labels = None
depends_on = None
GEKICHU_RATING_TABLE_NAMES = [
"chuni_profile_rating",
"ongeki_profile_rating",
]
def upgrade():
for table_name in GEKICHU_RATING_TABLE_NAMES:
op.create_table(
table_name,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", Integer, nullable=False),
Column("version", Integer, nullable=False),
Column("type", String(255), nullable=False),
Column("index", Integer, nullable=False),
Column("musicId", Integer),
Column("difficultId", Integer),
Column("romVersionCode", Integer),
Column("score", Integer),
mysql_charset="utf8mb4",
)
op.create_foreign_key(
None,
table_name,
"aime_user",
["user"],
["id"],
ondelete="cascade",
onupdate="cascade",
)
op.create_unique_constraint(
f"{table_name}_uk",
table_name,
["user", "version", "type", "index"],
)
def downgrade():
for table_name in GEKICHU_RATING_TABLE_NAMES:
op.drop_table(table_name)

View File

@ -0,0 +1,68 @@
"""mai2_buddies_support
Revision ID: 81e44dd6047a
Revises: d8950c7ce2fc
Create Date: 2024-03-12 19:10:37.063907
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = "81e44dd6047a"
down_revision = "6a7e8277763b"
branch_labels = None
depends_on = None
def upgrade():
op.create_table(
"mai2_playlog_2p",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("user", sa.Integer(), nullable=False),
sa.Column("userId1", sa.Integer(), nullable=True),
sa.Column("userId2", sa.Integer(), nullable=True),
sa.Column("userName1", sa.String(length=25), nullable=True),
sa.Column("userName2", sa.String(length=25), nullable=True),
sa.Column("regionId", sa.Integer(), nullable=True),
sa.Column("placeId", sa.Integer(), nullable=True),
sa.Column("user2pPlaylogDetailList", sa.JSON(), nullable=True),
sa.ForeignKeyConstraint(
["user"], ["aime_user.id"], onupdate="cascade", ondelete="cascade"
),
sa.PrimaryKeyConstraint("id"),
mysql_charset="utf8mb4",
)
op.add_column(
"mai2_playlog",
sa.Column(
"extBool1", sa.Boolean(), nullable=True, server_default=sa.text("NULL")
),
)
op.add_column(
"mai2_profile_detail",
sa.Column(
"renameCredit", sa.Integer(), nullable=True, server_default=sa.text("NULL")
),
)
op.add_column(
"mai2_profile_detail",
sa.Column(
"currentPlayCount",
sa.Integer(),
nullable=True,
server_default=sa.text("NULL"),
),
)
def downgrade():
op.drop_table("mai2_playlog_2p")
op.drop_column("mai2_playlog", "extBool1")
op.drop_column("mai2_profile_detail", "renameCredit")
op.drop_column("mai2_profile_detail", "currentPlayCount")

View File

@ -0,0 +1,24 @@
"""Initial Migration
Revision ID: 835b862f9bf0
Revises:
Create Date: 2024-01-09 13:06:10.787432
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '835b862f9bf0'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
pass
def downgrade():
pass

View File

@ -0,0 +1,29 @@
"""Remove old db mgmt system
Revision ID: d8950c7ce2fc
Revises: 835b862f9bf0
Create Date: 2024-01-09 13:43:51.381175
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'd8950c7ce2fc'
down_revision = '835b862f9bf0'
branch_labels = None
depends_on = None
def upgrade():
op.drop_table("schema_versions")
def downgrade():
op.create_table(
"schema_versions",
sa.Column("game", sa.String(4), primary_key=True, nullable=False),
sa.Column("version", sa.Integer, nullable=False, server_default="1"),
mysql_charset="utf8mb4",
)

View File

@ -0,0 +1,83 @@
"""IDAC Battle Gift and Tips added
Revision ID: e4e8d89c9b02
Revises: 81e44dd6047a
Create Date: 2024-04-01 17:49:50.009718
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = "e4e8d89c9b02"
down_revision = "81e44dd6047a"
branch_labels = None
depends_on = None
def upgrade():
op.create_table(
"idac_user_battle_gift",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("user", sa.Integer(), nullable=False),
sa.Column("battle_gift_event_id", sa.Integer(), nullable=True),
sa.Column("gift_id", sa.Integer(), nullable=True),
sa.Column("gift_status", sa.Integer(), nullable=True),
sa.Column(
"received_date",
sa.TIMESTAMP(),
server_default=sa.text("now()"),
nullable=True,
),
sa.ForeignKeyConstraint(
["user"], ["aime_user.id"], onupdate="cascade", ondelete="cascade"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"user", "battle_gift_event_id", "gift_id", name="idac_user_battle_gift_uk"
),
mysql_charset="utf8mb4",
)
op.create_table(
"idac_profile_tips",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("user", sa.Integer(), nullable=False),
sa.Column("version", sa.Integer(), nullable=False),
sa.Column(
"tips_list",
sa.String(length=16),
server_default="QAAAAAAAAAAAAAAA",
nullable=True,
),
sa.Column(
"timetrial_play_count", sa.Integer(), server_default="0", nullable=True
),
sa.Column("story_play_count", sa.Integer(), server_default="0", nullable=True),
sa.Column(
"store_battle_play_count", sa.Integer(), server_default="0", nullable=True
),
sa.Column(
"online_battle_play_count", sa.Integer(), server_default="0", nullable=True
),
sa.Column(
"special_play_count", sa.Integer(), server_default="0", nullable=True
),
sa.Column(
"challenge_play_count", sa.Integer(), server_default="0", nullable=True
),
sa.Column("theory_play_count", sa.Integer(), server_default="0", nullable=True),
sa.ForeignKeyConstraint(
["user"], ["aime_user.id"], onupdate="cascade", ondelete="cascade"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("user", "version", name="idac_profile_tips_uk"),
mysql_charset="utf8mb4",
)
def downgrade():
op.drop_table("idac_user_battle_gift")
op.drop_table("idac_profile_tips")

View File

@ -1,4 +1,3 @@
from typing import Any, Callable
from functools import wraps
import hashlib
@ -6,27 +5,28 @@ import pickle
import logging
from core.config import CoreConfig
cfg:CoreConfig = None # type: ignore
cfg: CoreConfig = None # type: ignore
# Make memcache optional
try:
import pylibmc # type: ignore
has_mc = True
except ModuleNotFoundError:
has_mc = False
def cached(lifetime: int=10, extra_key: Any=None) -> Callable:
def cached(lifetime: int = 10, extra_key: Any = None) -> Callable:
def _cached(func: Callable) -> Callable:
if has_mc:
if has_mc and (cfg and cfg.database.enable_memcached):
hostname = "127.0.0.1"
if cfg:
hostname = cfg.database.memcached_host
memcache = pylibmc.Client([hostname], binary=True)
memcache.behaviors = {"tcp_nodelay": True, "ketama": True}
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
if lifetime is not None:
# Hash function args
items = kwargs.items()
hashable_args = (args[1:], sorted(list(items)))
@ -41,7 +41,7 @@ def cached(lifetime: int=10, extra_key: Any=None) -> Callable:
except pylibmc.Error as e:
logging.getLogger("database").error(f"Memcache failed: {e}")
result = None
if result is not None:
logging.getLogger("database").debug(f"Cache hit: {result}")
return result
@ -55,7 +55,9 @@ def cached(lifetime: int=10, extra_key: Any=None) -> Callable:
memcache.set(cache_key, result, lifetime)
return result
else:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
return func(*args, **kwargs)

View File

@ -1,45 +1,70 @@
import logging, coloredlogs
from typing import Any, Dict, List
from typing import Optional
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import create_engine
from logging.handlers import TimedRotatingFileHandler
import os
import secrets, string
import bcrypt
from hashlib import sha256
import alembic.config
import glob
from core.config import CoreConfig
from core.data.schema import *
from core.utils import Utils
class Data:
engine = None
session = None
user = None
arcade = None
card = None
base = None
def __init__(self, cfg: CoreConfig) -> None:
self.config = cfg
if self.config.database.sha2_password:
passwd = sha256(self.config.database.password.encode()).digest()
self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{passwd.hex()}@{self.config.database.host}/{self.config.database.name}?charset=utf8mb4"
self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{passwd.hex()}@{self.config.database.host}:{self.config.database.port}/{self.config.database.name}?charset=utf8mb4"
else:
self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{self.config.database.password}@{self.config.database.host}/{self.config.database.name}?charset=utf8mb4"
self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{self.config.database.password}@{self.config.database.host}:{self.config.database.port}/{self.config.database.name}?charset=utf8mb4"
if Data.engine is None:
Data.engine = create_engine(self.__url, pool_recycle=3600)
self.__engine = Data.engine
if Data.session is None:
s = sessionmaker(bind=Data.engine, autoflush=True, autocommit=True)
Data.session = scoped_session(s)
if Data.user is None:
Data.user = UserData(self.config, self.session)
self.__engine = create_engine(self.__url, pool_recycle=3600)
session = sessionmaker(bind=self.__engine, autoflush=True, autocommit=True)
self.session = scoped_session(session)
if Data.arcade is None:
Data.arcade = ArcadeData(self.config, self.session)
if Data.card is None:
Data.card = CardData(self.config, self.session)
if Data.base is None:
Data.base = BaseData(self.config, self.session)
self.user = UserData(self.config, self.session)
self.arcade = ArcadeData(self.config, self.session)
self.card = CardData(self.config, self.session)
self.base = BaseData(self.config, self.session)
self.schema_ver_latest = 1
log_fmt_str = "[%(asctime)s] %(levelname)s | Database | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
self.logger = logging.getLogger("database")
# Prevent the logger from adding handlers multiple times
if not getattr(self.logger, 'handler_set', None):
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "db"), encoding="utf-8",
when="d", backupCount=10)
if not getattr(self.logger, "handler_set", None):
log_fmt_str = "[%(asctime)s] %(levelname)s | Database | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.config.server.log_dir, "db"),
encoding="utf-8",
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
@ -47,7 +72,182 @@ class Data:
self.logger.addHandler(consoleHandler)
self.logger.setLevel(self.config.database.loglevel)
coloredlogs.install(cfg.database.loglevel, logger=self.logger, fmt=log_fmt_str)
self.logger.handler_set = True # type: ignore
coloredlogs.install(
cfg.database.loglevel, logger=self.logger, fmt=log_fmt_str
)
self.logger.handler_set = True # type: ignore
def __alembic_cmd(self, command: str, *args: str) -> None:
old_dir = os.path.abspath(os.path.curdir)
base_dir = os.path.join(os.path.abspath(os.path.curdir), 'core', 'data', 'alembic')
alembicArgs = [
"-c",
os.path.join(base_dir, "alembic.ini"),
"-x",
f"script_location={base_dir}",
"-x",
f"sqlalchemy.url={self.__url}",
command,
]
alembicArgs.extend(args)
os.chdir(base_dir)
alembic.config.main(argv=alembicArgs)
os.chdir(old_dir)
def create_database(self):
self.logger.info("Creating databases...")
metadata.create_all(
self.engine,
checkfirst=True,
)
for _, mod in Utils.get_all_titles().items():
if hasattr(mod, "database"):
mod.database(self.config)
metadata.create_all(
self.engine,
checkfirst=True,
)
# Stamp the end revision as if alembic had created it, so it can take off after this.
self.__alembic_cmd(
"stamp",
"head",
)
def schema_upgrade(self, ver: str = None):
self.__alembic_cmd(
"upgrade",
"head" if not ver else ver,
)
def schema_downgrade(self, ver: str):
self.__alembic_cmd(
"downgrade",
ver,
)
async def create_owner(self, email: Optional[str] = None, code: Optional[str] = "00000000000000000000") -> None:
pw = "".join(
secrets.choice(string.ascii_letters + string.digits) for i in range(20)
)
hash = bcrypt.hashpw(pw.encode(), bcrypt.gensalt())
user_id = await self.user.create_user(username="sysowner", email=email, password=hash.decode(), permission=255)
if user_id is None:
self.logger.error(f"Failed to create owner with email {email}")
return
card_id = await self.card.create_card(user_id, code)
if card_id is None:
self.logger.error(f"Failed to create card for owner with id {user_id}")
return
self.logger.warning(
f"Successfully created owner with email {email}, access code {code}, and password {pw} Make sure to change this password and assign a real card ASAP!"
)
async def migrate(self) -> None:
exist = await self.base.execute("SELECT * FROM alembic_version")
if exist is not None:
self.logger.warn("No need to migrate as you have already migrated to alembic. If you are trying to upgrade the schema, use `upgrade` instead!")
return
self.logger.info("Upgrading to latest with legacy system")
if not await self.legacy_upgrade():
self.logger.warn("No need to migrate as you have already deleted the old schema_versions system. If you are trying to upgrade the schema, use `upgrade` instead!")
return
self.logger.info("Done")
self.logger.info("Stamp with initial revision")
self.__alembic_cmd(
"stamp",
"835b862f9bf0",
)
self.logger.info("Upgrade")
self.__alembic_cmd(
"upgrade",
"head",
)
async def legacy_upgrade(self) -> bool:
vers = await self.base.execute("SELECT * FROM schema_versions")
if vers is None:
self.logger.warn("Cannot legacy upgrade, schema_versions table unavailable!")
return False
db_vers = {}
vers_list = vers.fetchall()
for x in vers_list:
db_vers[x['game']] = x['version']
core_now_ver = int(db_vers['CORE']) + 1
while os.path.exists(f"core/data/schema/versions/CORE_{core_now_ver}_upgrade.sql"):
with open(f"core/data/schema/versions/CORE_{core_now_ver}_upgrade.sql", "r") as f:
result = await self.base.execute(f.read())
if result is None:
self.logger.error(f"Invalid upgrade script CORE_{core_now_ver}_upgrade.sql")
break
result = await self.base.execute(f"UPDATE schema_versions SET version = {core_now_ver} WHERE game = 'CORE'")
if result is None:
self.logger.error(f"Failed to update schema version for CORE to {core_now_ver}")
break
self.logger.info(f"Upgrade CORE to version {core_now_ver}")
core_now_ver += 1
for _, mod in Utils.get_all_titles().items():
game_codes = getattr(mod, "game_codes", [])
for game in game_codes:
if game not in db_vers:
self.logger.warn(f"{game} does not have an antry in schema_versions, skipping")
continue
now_ver = int(db_vers[game]) + 1
while os.path.exists(f"core/data/schema/versions/{game}_{now_ver}_upgrade.sql"):
with open(f"core/data/schema/versions/{game}_{now_ver}_upgrade.sql", "r") as f:
result = await self.base.execute(f.read())
if result is None:
self.logger.error(f"Invalid upgrade script {game}_{now_ver}_upgrade.sql")
break
result = await self.base.execute(f"UPDATE schema_versions SET version = {now_ver} WHERE game = '{game}'")
if result is None:
self.logger.error(f"Failed to update schema version for {game} to {now_ver}")
break
self.logger.info(f"Upgrade {game} to version {now_ver}")
now_ver += 1
return True
async def create_revision(self, message: str) -> None:
if not message:
self.logger.info("Message is required for create-revision")
return
self.__alembic_cmd(
"revision",
"-m",
message,
)
async def create_revision_auto(self, message: str) -> None:
if not message:
self.logger.info("Message is required for create-revision")
return
for _, mod in Utils.get_all_titles().items():
if hasattr(mod, "database"):
mod.database(self.config)
self.__alembic_cmd(
"revision",
"--autogenerate",
"-m",
message,
)

View File

@ -3,4 +3,4 @@ from core.data.schema.card import CardData
from core.data.schema.base import BaseData, metadata
from core.data.schema.arcade import ArcadeData
__all__ = ["UserData", "CardData", "BaseData", "metadata", "ArcadeData"]
__all__ = ["UserData", "CardData", "BaseData", "metadata", "ArcadeData"]

View File

@ -1,113 +1,232 @@
from typing import Optional, Dict
from sqlalchemy import Table, Column
from typing import Optional, Dict, List
from sqlalchemy import Table, Column, and_, or_
from sqlalchemy.sql.schema import ForeignKey, PrimaryKeyConstraint
from sqlalchemy.types import Integer, String, Boolean
from sqlalchemy.types import Integer, String, Boolean, JSON
from sqlalchemy.sql import func, select
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.engine import Row
import re
from core.data.schema.base import BaseData, metadata
from core.const import *
arcade = Table(
"arcade",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("name", String(255)),
Column("nickname", String(255)),
Column("nickname", String(255)),
Column("country", String(3)),
Column("country_id", Integer),
Column("state", String(255)),
Column("city", String(255)),
Column("region_id", Integer),
Column("timezone", String(255)),
mysql_charset='utf8mb4'
Column("ip", String(39)),
mysql_charset="utf8mb4",
)
machine = Table(
"machine",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("arcade", ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade"), nullable=False),
Column(
"arcade",
ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("serial", String(15), nullable=False),
Column("board", String(15)),
Column("game", String(4)),
Column("country", String(3)), # overwrites if not null
Column("country", String(3)), # overwrites if not null
Column("timezone", String(255)),
Column("ota_enable", Boolean),
Column("memo", String(255)),
Column("is_cab", Boolean),
mysql_charset='utf8mb4'
Column("data", JSON),
mysql_charset="utf8mb4",
)
arcade_owner = Table(
'arcade_owner',
"arcade_owner",
metadata,
Column('user', Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
Column('arcade', Integer, ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade"), nullable=False),
Column('permissions', Integer, nullable=False),
PrimaryKeyConstraint('user', 'arcade', name='arcade_owner_pk'),
mysql_charset='utf8mb4'
Column(
"user",
Integer,
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column(
"arcade",
Integer,
ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("permissions", Integer, nullable=False),
PrimaryKeyConstraint("user", "arcade", name="arcade_owner_pk"),
mysql_charset="utf8mb4",
)
class ArcadeData(BaseData):
def get_machine(self, serial: str = None, id: int = None) -> Optional[Dict]:
async def get_machine(self, serial: str = None, id: int = None) -> Optional[Row]:
if serial is not None:
sql = machine.select(machine.c.serial == serial)
serial = serial.replace("-", "")
if len(serial) == 11:
sql = machine.select(machine.c.serial.like(f"{serial}%"))
elif len(serial) == 15:
sql = machine.select(machine.c.serial == serial)
else:
self.logger.error(f"{__name__ }: Malformed serial {serial}")
return None
elif id is not None:
sql = machine.select(machine.c.id == id)
else:
else:
self.logger.error(f"{__name__ }: Need either serial or ID to look up!")
return None
result = self.execute(sql)
if result is None: return None
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
def put_machine(self, arcade_id: int, serial: str = None, board: str = None, game: str = None, is_cab: bool = False) -> Optional[int]:
if arcade_id:
async def create_machine(
self,
arcade_id: int,
serial: str = "",
board: str = None,
game: str = None,
is_cab: bool = False,
) -> Optional[int]:
if not arcade_id:
self.logger.error(f"{__name__ }: Need arcade id!")
return None
if serial is None:
pass
sql = machine.insert().values(arcade = arcade_id, keychip = serial, board = board, game = game, is_cab = is_cab)
result = self.execute(sql)
if result is None: return None
return result.lastrowid
def get_arcade(self, id: int) -> Optional[Dict]:
sql = arcade.select(arcade.c.id == id)
result = self.execute(sql)
if result is None: return None
return result.fetchone()
def put_arcade(self, name: str, nickname: str = None, country: str = "JPN", country_id: int = 1,
state: str = "", city: str = "", regional_id: int = 1) -> Optional[int]:
if nickname is None: nickname = name
sql = arcade.insert().values(name = name, nickname = nickname, country = country, country_id = country_id,
state = state, city = city, regional_id = regional_id)
result = self.execute(sql)
if result is None: return None
return result.lastrowid
def get_arcade_owners(self, arcade_id: int) -> Optional[Dict]:
sql = select(arcade_owner).where(arcade_owner.c.arcade==arcade_id)
result = self.execute(sql)
if result is None: return None
return result.fetchall()
def add_arcade_owner(self, arcade_id: int, user_id: int) -> None:
sql = insert(arcade_owner).values(
arcade=arcade_id,
user=user_id
sql = machine.insert().values(
arcade=arcade_id, serial=serial, board=board, game=game, is_cab=is_cab
)
result = self.execute(sql)
if result is None: return None
result = await self.execute(sql)
if result is None:
return None
return result.lastrowid
def generate_keychip_serial(self, platform_id: int) -> str:
pass
async def set_machine_serial(self, machine_id: int, serial: str) -> None:
result = await self.execute(
machine.update(machine.c.id == machine_id).values(keychip=serial)
)
if result is None:
self.logger.error(
f"Failed to update serial for machine {machine_id} -> {serial}"
)
return result.lastrowid
async def set_machine_boardid(self, machine_id: int, boardid: str) -> None:
result = await self.execute(
machine.update(machine.c.id == machine_id).values(board=boardid)
)
if result is None:
self.logger.error(
f"Failed to update board id for machine {machine_id} -> {boardid}"
)
async def get_arcade(self, id: int) -> Optional[Row]:
sql = arcade.select(arcade.c.id == id)
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
async def get_arcade_machines(self, id: int) -> Optional[List[Row]]:
sql = machine.select(machine.c.arcade == id)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def create_arcade(
self,
name: str = None,
nickname: str = None,
country: str = "JPN",
country_id: int = 1,
state: str = "",
city: str = "",
region_id: int = 1,
) -> Optional[int]:
if nickname is None:
nickname = name
sql = arcade.insert().values(
name=name,
nickname=nickname,
country=country,
country_id=country_id,
state=state,
city=city,
region_id=region_id,
)
result = await self.execute(sql)
if result is None:
return None
return result.lastrowid
async def get_arcades_managed_by_user(self, user_id: int) -> Optional[List[Row]]:
sql = select(arcade).join(arcade_owner, arcade_owner.c.arcade == arcade.c.id).where(arcade_owner.c.user == user_id)
result = await self.execute(sql)
if result is None:
return False
return result.fetchall()
async def get_manager_permissions(self, user_id: int, arcade_id: int) -> Optional[int]:
sql = select(arcade_owner.c.permissions).where(and_(arcade_owner.c.user == user_id, arcade_owner.c.arcade == arcade_id))
result = await self.execute(sql)
if result is None:
return False
return result.fetchone()
async def get_arcade_owners(self, arcade_id: int) -> Optional[Row]:
sql = select(arcade_owner).where(arcade_owner.c.arcade == arcade_id)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def add_arcade_owner(self, arcade_id: int, user_id: int) -> None:
sql = insert(arcade_owner).values(arcade=arcade_id, user=user_id)
result = await self.execute(sql)
if result is None:
return None
return result.lastrowid
def format_serial( # TODO: Actual serial stuff
self, platform_code: str, platform_rev: int, serial_num: int, append: int = 8888
) -> str:
return f"{platform_code}{platform_rev:02d}A{serial_num:04d}{append:04d}" # 0x41 = A, 0x52 = R
def validate_keychip_format(self, serial: str) -> bool:
if re.fullmatch(r"^A[0-9]{2}[E|X][-]?[0-9]{2}[A-HJ-NP-Z][0-9]{4}([0-9]{4})?$", serial) is None:
return False
return True
async def get_arcade_by_name(self, name: str) -> Optional[List[Row]]:
sql = arcade.select(or_(arcade.c.name.like(f"%{name}%"), arcade.c.nickname.like(f"%{name}%")))
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def get_arcades_by_ip(self, ip: str) -> Optional[List[Row]]:
sql = arcade.select().where(arcade.c.ip == ip)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()

View File

@ -2,6 +2,7 @@ import json
import logging
from random import randrange
from typing import Any, Optional, Dict, List
from sqlalchemy.engine import Row
from sqlalchemy.engine.cursor import CursorResult
from sqlalchemy.engine.base import Connection
from sqlalchemy.sql import text, func, select
@ -14,14 +15,6 @@ from core.config import CoreConfig
metadata = MetaData()
schema_ver = Table(
"schema_versions",
metadata,
Column("game", String(4), primary_key=True, nullable=False),
Column("version", Integer, nullable=False, server_default="1"),
mysql_charset='utf8mb4'
)
event_log = Table(
"event_log",
metadata,
@ -29,96 +22,93 @@ event_log = Table(
Column("system", String(255), nullable=False),
Column("type", String(255), nullable=False),
Column("severity", Integer, nullable=False),
Column("message", String(1000), nullable=False),
Column("details", JSON, nullable=False),
Column("when_logged", TIMESTAMP, nullable=False, server_default=func.now()),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
class BaseData():
class BaseData:
def __init__(self, cfg: CoreConfig, conn: Connection) -> None:
self.config = cfg
self.conn = conn
self.logger = logging.getLogger("database")
def execute(self, sql: str, opts: Dict[str, Any]={}) -> Optional[CursorResult]:
async def execute(self, sql: str, opts: Dict[str, Any] = {}) -> Optional[CursorResult]:
res = None
try:
self.logger.info(f"SQL Execute: {''.join(str(sql).splitlines())} || {opts}")
self.logger.debug(f"SQL Execute: {''.join(str(sql).splitlines())}")
res = self.conn.execute(text(sql), opts)
except SQLAlchemyError as e:
self.logger.error(f"SQLAlchemy error {e}")
return None
except UnicodeEncodeError as e:
self.logger.error(f"UnicodeEncodeError error {e}")
return None
except:
except Exception:
try:
res = self.conn.execute(sql, opts)
except SQLAlchemyError as e:
self.logger.error(f"SQLAlchemy error {e}")
return None
except UnicodeEncodeError as e:
self.logger.error(f"UnicodeEncodeError error {e}")
return None
except:
except Exception:
self.logger.error(f"Unknown error")
raise
return res
def generate_id(self) -> int:
"""
Generate a random 5-7 digit id
"""
return randrange(10000, 9999999)
def get_schema_ver(self, game: str) -> Optional[int]:
sql = select(schema_ver).where(schema_ver.c.game == game)
result = self.execute(sql)
if result is None:
return None
return result.fetchone()["version"]
def set_schema_ver(self, ver: int, game: str = "CORE") -> Optional[int]:
sql = insert(schema_ver).values(game = game, version = ver)
conflict = sql.on_duplicate_key_update(version = ver)
result = self.execute(conflict)
if result is None:
self.logger.error(f"Failed to update schema version for game {game} (v{ver})")
return None
return result.lastrowid
def log_event(self, system: str, type: str, severity: int, details: Dict) -> Optional[int]:
sql = event_log.insert().values(system = system, type = type, severity = severity, details = json.dumps(details))
result = self.execute(sql)
async def log_event(
self, system: str, type: str, severity: int, message: str, details: Dict = {}
) -> Optional[int]:
sql = event_log.insert().values(
system=system,
type=type,
severity=severity,
message=message,
details=json.dumps(details),
)
result = await self.execute(sql)
if result is None:
self.logger.error(f"{__name__}: Failed to insert event into event log! system = {system}, type = {type}, severity = {severity}, details = {details}")
self.logger.error(
f"{__name__}: Failed to insert event into event log! system = {system}, type = {type}, severity = {severity}, message = {message}"
)
return None
return result.lastrowid
def get_event_log(self, entries: int = 100) -> Optional[List[Dict]]:
async def get_event_log(self, entries: int = 100) -> Optional[List[Dict]]:
sql = event_log.select().limit(entries).all()
result = self.execute(sql)
result = await self.execute(sql)
if result is None: return None
if result is None:
return None
return result.fetchall()
def fix_bools(self, data: Dict) -> Dict:
for k,v in data.items():
for k, v in data.items():
if k == "userName" or k == "teamName":
continue
if type(v) == str and v.lower() == "true":
data[k] = True
elif type(v) == str and v.lower() == "false":
data[k] = False
return data

View File

@ -3,57 +3,125 @@ from sqlalchemy import Table, Column, UniqueConstraint
from sqlalchemy.types import Integer, String, Boolean, TIMESTAMP
from sqlalchemy.sql.schema import ForeignKey
from sqlalchemy.sql import func
from sqlalchemy.engine import Row
from core.data.schema.base import BaseData, metadata
aime_card = Table(
'aime_card',
"aime_card",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("access_code", String(20)),
Column("created_date", TIMESTAMP, server_default=func.now()),
Column("last_login_date", TIMESTAMP, onupdate=func.now()),
Column("is_locked", Boolean, server_default="0"),
Column("is_banned", Boolean, server_default="0"),
UniqueConstraint("user", "access_code", name="aime_card_uk"),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
class CardData(BaseData):
def get_user_id_from_card(self, access_code: str) -> Optional[int]:
async def get_card_by_access_code(self, access_code: str) -> Optional[Row]:
sql = aime_card.select(aime_card.c.access_code == access_code)
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
async def get_card_by_id(self, card_id: int) -> Optional[Row]:
sql = aime_card.select(aime_card.c.id == card_id)
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
async def update_access_code(self, old_ac: str, new_ac: str) -> None:
sql = aime_card.update(aime_card.c.access_code == old_ac).values(
access_code=new_ac
)
result = await self.execute(sql)
if result is None:
self.logger.error(
f"Failed to change card access code from {old_ac} to {new_ac}"
)
async def get_user_id_from_card(self, access_code: str) -> Optional[int]:
"""
Given a 20 digit access code as a string, get the user id associated with that card
"""
sql = aime_card.select(aime_card.c.access_code == access_code)
result = self.execute(sql)
if result is None: return None
card = result.fetchone()
if card is None: return None
card = await self.get_card_by_access_code(access_code)
if card is None:
return None
return int(card["user"])
def get_user_cards(self, aime_id: int) -> Optional[List[Dict]]:
async def get_card_banned(self, access_code: str) -> Optional[bool]:
"""
Given a 20 digit access code as a string, check if the card is banned
"""
card = await self.get_card_by_access_code(access_code)
if card is None:
return None
if card["is_banned"]:
return True
return False
async def get_card_locked(self, access_code: str) -> Optional[bool]:
"""
Given a 20 digit access code as a string, check if the card is locked
"""
card = await self.get_card_by_access_code(access_code)
if card is None:
return None
if card["is_locked"]:
return True
return False
async def delete_card(self, card_id: int) -> None:
sql = aime_card.delete(aime_card.c.id == card_id)
result = await self.execute(sql)
if result is None:
self.logger.error(f"Failed to delete card with id {card_id}")
async def get_user_cards(self, aime_id: int) -> Optional[List[Row]]:
"""
Returns all cards owned by a user
"""
sql = aime_card.select(aime_card.c.user == aime_id)
result = self.execute(sql)
if result is None: return None
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
def create_card(self, user_id: int, access_code: str) -> Optional[int]:
async def create_card(self, user_id: int, access_code: str) -> Optional[int]:
"""
Given a aime_user id and a 20 digit access code as a string, create a card and return the ID if successful
"""
sql = aime_card.insert().values(user=user_id, access_code=access_code)
result = self.execute(sql)
if result is None: return None
result = await self.execute(sql)
if result is None:
return None
return result.lastrowid
async def update_card_last_login(self, access_code: str) -> None:
sql = aime_card.update(aime_card.c.access_code == access_code).values(
last_login_date=func.now()
)
result = await self.execute(sql)
if result is None:
self.logger.warn(f"Failed to update last login time for {access_code}")
def to_access_code(self, luid: str) -> str:
"""
Given a felica cards internal 16 hex character luid, convert it to a 0-padded 20 digit access code as a string
@ -64,4 +132,4 @@ class CardData(BaseData):
"""
Given a 20 digit access code as a string, return the 16 hex character luid
"""
return f'{int(access_code):0{16}x}'
return f"{int(access_code):0{16}x}"

View File

@ -1,9 +1,11 @@
from enum import Enum
from typing import Dict, Optional
from typing import Optional, List
from sqlalchemy import Table, Column
from sqlalchemy.types import Integer, String, TIMESTAMP
from sqlalchemy.sql.schema import ForeignKey
from sqlalchemy.sql import func
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.sql import func, select
from sqlalchemy.engine import Row
import bcrypt
from core.data.schema.base import BaseData, metadata
@ -14,44 +16,107 @@ aime_user = Table(
Column("username", String(25), unique=True),
Column("email", String(255), unique=True),
Column("password", String(255)),
Column("permissions", Integer),
Column("permissions", Integer),
Column("created_date", TIMESTAMP, server_default=func.now()),
Column("last_login_date", TIMESTAMP, onupdate=func.now()),
Column("suspend_expire_time", TIMESTAMP),
mysql_charset='utf8mb4'
mysql_charset="utf8mb4",
)
frontend_session = Table(
"frontend_session",
metadata,
Column("id", Integer, primary_key=True, unique=True),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
Column('session_cookie', String(32), nullable=False, unique=True),
Column("expires", TIMESTAMP, nullable=False),
mysql_charset='utf8mb4'
)
class PermissionBits(Enum):
PermUser = 1
PermMod = 2
PermSysAdmin = 4
class UserData(BaseData):
def create_user(self, username: str = None, email: str = None, password: str = None) -> Optional[int]:
if email is None:
permission = None
async def create_user(
self,
id: int = None,
username: str = None,
email: str = None,
password: str = None,
permission: int = 1,
) -> Optional[int]:
if id is None:
sql = insert(aime_user).values(
username=username,
email=email,
password=password,
permissions=permission,
)
else:
permission = 0
sql = insert(aime_user).values(
id=id,
username=username,
email=email,
password=password,
permissions=permission,
)
sql = aime_user.insert().values(username=username, email=email, password=password, permissions=permission)
result = self.execute(sql)
if result is None: return None
conflict = sql.on_duplicate_key_update(
username=username, email=email, password=password, permissions=permission
)
result = await self.execute(conflict)
if result is None:
return None
return result.lastrowid
def reset_autoincrement(self, ai_value: int) -> None:
# Didn't feel like learning how to do this the right way
# if somebody wants a free PR go nuts I guess
sql = f"ALTER TABLE aime_user AUTO_INCREMENT={ai_value}"
self.execute(sql)
async def get_user(self, user_id: int) -> Optional[Row]:
sql = select(aime_user).where(aime_user.c.id == user_id)
result = await self.execute(sql)
if result is None:
return False
return result.fetchone()
async def check_password(self, user_id: int, passwd: bytes = None) -> bool:
usr = await self.get_user(user_id)
if usr is None:
return False
if usr["password"] is None:
return False
if passwd is None or not passwd:
return False
return bcrypt.checkpw(passwd, usr["password"].encode())
async def delete_user(self, user_id: int) -> None:
sql = aime_user.delete(aime_user.c.id == user_id)
result = await self.execute(sql)
if result is None:
self.logger.error(f"Failed to delete user with id {user_id}")
async def get_unregistered_users(self) -> List[Row]:
"""
Returns a list of users who have not registered with the webui. They may or may not have cards.
"""
sql = select(aime_user).where(aime_user.c.password == None)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def find_user_by_email(self, email: str) -> Row:
sql = select(aime_user).where(aime_user.c.email == email)
result = await self.execute(sql)
if result is None:
return False
return result.fetchone()
async def find_user_by_username(self, username: str) -> List[Row]:
sql = aime_user.select(aime_user.c.username.like(f"%{username}%"))
result = await self.execute(sql)
if result is None:
return False
return result.fetchall()
async def change_password(self, user_id: int, new_passwd: str) -> bool:
sql = aime_user.update(aime_user.c.id == user_id).values(password = new_passwd)
result = await self.execute(sql)
return result is not None
async def change_username(self, user_id: int, new_name: str) -> bool:
sql = aime_user.update(aime_user.c.id == user_id).values(username = new_name)
result = await self.execute(sql)
return result is not None

View File

@ -0,0 +1,2 @@
ALTER TABLE `frontend_session`
DROP COLUMN `ip`;

View File

@ -0,0 +1 @@
ALTER TABLE `event_log` DROP COLUMN `message`;

View File

@ -0,0 +1,2 @@
ALTER TABLE `frontend_session`
ADD `ip` CHAR(15);

View File

@ -0,0 +1,12 @@
CREATE TABLE `frontend_session` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user` int(11) NOT NULL,
`ip` varchar(15) DEFAULT NULL,
`session_cookie` varchar(32) NOT NULL,
`expires` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`),
UNIQUE KEY `session_cookie` (`session_cookie`),
KEY `user` (`user`),
CONSTRAINT `frontend_session_ibfk_1` FOREIGN KEY (`user`) REFERENCES `aime_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4;

View File

@ -0,0 +1 @@
ALTER TABLE `event_log` ADD COLUMN `message` VARCHAR(1000) NOT NULL AFTER `severity`;

View File

@ -0,0 +1,3 @@
ALTER TABLE machine DROP COLUMN memo;
ALTER TABLE machine DROP COLUMN is_blacklisted;
ALTER TABLE machine DROP COLUMN `data`;

View File

@ -0,0 +1 @@
DROP TABLE `frontend_session`;

View File

@ -0,0 +1 @@
ALTER TABLE arcade DROP COLUMN 'ip';

View File

@ -0,0 +1,3 @@
ALTER TABLE machine ADD memo varchar(255) NULL;
ALTER TABLE machine ADD is_blacklisted tinyint(1) NULL;
ALTER TABLE machine ADD `data` longtext NULL;

View File

@ -0,0 +1 @@
ALTER TABLE arcade ADD ip varchar(39) NULL;

View File

@ -0,0 +1,9 @@
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE diva_score DROP FOREIGN KEY diva_score_ibfk_1;
ALTER TABLE diva_score DROP CONSTRAINT diva_score_uk;
ALTER TABLE diva_score ADD CONSTRAINT diva_score_uk UNIQUE (user, pv_id, difficulty);
ALTER TABLE diva_score ADD CONSTRAINT diva_score_ibfk_1 FOREIGN KEY (user) REFERENCES aime_user(id) ON DELETE CASCADE;
ALTER TABLE diva_score DROP COLUMN edition;
ALTER TABLE diva_playlog DROP COLUMN edition;
SET FOREIGN_KEY_CHECKS=1;

View File

@ -0,0 +1,17 @@
ALTER TABLE diva_profile_shop DROP COLUMN c_itm_eqp_ary;
ALTER TABLE diva_profile_shop DROP COLUMN ms_itm_flg_ary;
ALTER TABLE diva_profile DROP COLUMN use_pv_mdl_eqp;
ALTER TABLE diva_profile DROP COLUMN use_mdl_pri;
ALTER TABLE diva_profile DROP COLUMN use_pv_skn_eqp;
ALTER TABLE diva_profile DROP COLUMN use_pv_btn_se_eqp;
ALTER TABLE diva_profile DROP COLUMN use_pv_sld_se_eqp;
ALTER TABLE diva_profile DROP COLUMN use_pv_chn_sld_se_eqp;
ALTER TABLE diva_profile DROP COLUMN use_pv_sldr_tch_se_eqp;
ALTER TABLE diva_profile ADD COLUMN use_pv_mdl_eqp VARCHAR(8) NOT NULL DEFAULT "true" AFTER sort_kind;
ALTER TABLE diva_profile ADD COLUMN use_pv_btn_se_eqp VARCHAR(8) NOT NULL DEFAULT "true" AFTER use_pv_mdl_eqp;
ALTER TABLE diva_profile ADD COLUMN use_pv_sld_se_eqp VARCHAR(8) NOT NULL DEFAULT "false" AFTER use_pv_btn_se_eqp;
ALTER TABLE diva_profile ADD COLUMN use_pv_chn_sld_se_eqp VARCHAR(8) NOT NULL DEFAULT "false" AFTER use_pv_sld_se_eqp;
ALTER TABLE diva_profile ADD COLUMN use_pv_sldr_tch_se_eqp VARCHAR(8) NOT NULL DEFAULT "false" AFTER use_pv_chn_sld_se_eqp;
DROP TABLE IF EXISTS `diva_profile_pv_customize`;

View File

@ -0,0 +1,9 @@
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE diva_score ADD COLUMN edition int(11) DEFAULT 0 AFTER difficulty;
ALTER TABLE diva_playlog ADD COLUMN edition int(11) DEFAULT 0 AFTER difficulty;
ALTER TABLE diva_score DROP FOREIGN KEY diva_score_ibfk_1;
ALTER TABLE diva_score DROP CONSTRAINT diva_score_uk;
ALTER TABLE diva_score ADD CONSTRAINT diva_score_uk UNIQUE (user, pv_id, difficulty, edition);
ALTER TABLE diva_score ADD CONSTRAINT diva_score_ibfk_1 FOREIGN KEY (user) REFERENCES aime_user(id) ON DELETE CASCADE;
SET FOREIGN_KEY_CHECKS=1;

View File

@ -0,0 +1,3 @@
ALTER TABLE diva_profile DROP COLUMN passwd_stat;
ALTER TABLE diva_profile DROP COLUMN passwd;
ALTER TABLE diva_profile MODIFY player_name VARCHAR(8);

View File

@ -0,0 +1,33 @@
ALTER TABLE diva_profile_shop ADD COLUMN c_itm_eqp_ary varchar(59) DEFAULT "-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999";
ALTER TABLE diva_profile_shop ADD COLUMN ms_itm_flg_ary varchar(59) DEFAULT "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1";
ALTER TABLE diva_profile DROP COLUMN use_pv_mdl_eqp;
ALTER TABLE diva_profile DROP COLUMN use_pv_btn_se_eqp;
ALTER TABLE diva_profile DROP COLUMN use_pv_sld_se_eqp;
ALTER TABLE diva_profile DROP COLUMN use_pv_chn_sld_se_eqp;
ALTER TABLE diva_profile DROP COLUMN use_pv_sldr_tch_se_eqp;
ALTER TABLE diva_profile ADD COLUMN use_pv_mdl_eqp BOOLEAN NOT NULL DEFAULT true AFTER sort_kind;
ALTER TABLE diva_profile ADD COLUMN use_mdl_pri BOOLEAN NOT NULL DEFAULT false AFTER use_pv_mdl_eqp;
ALTER TABLE diva_profile ADD COLUMN use_pv_skn_eqp BOOLEAN NOT NULL DEFAULT false AFTER use_mdl_pri;
ALTER TABLE diva_profile ADD COLUMN use_pv_btn_se_eqp BOOLEAN NOT NULL DEFAULT true AFTER use_pv_skn_eqp;
ALTER TABLE diva_profile ADD COLUMN use_pv_sld_se_eqp BOOLEAN NOT NULL DEFAULT false AFTER use_pv_btn_se_eqp;
ALTER TABLE diva_profile ADD COLUMN use_pv_chn_sld_se_eqp BOOLEAN NOT NULL DEFAULT false AFTER use_pv_sld_se_eqp;
ALTER TABLE diva_profile ADD COLUMN use_pv_sldr_tch_se_eqp BOOLEAN NOT NULL DEFAULT false AFTER use_pv_chn_sld_se_eqp;
CREATE TABLE diva_profile_pv_customize (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
user INT NOT NULL,
version INT NOT NULL,
pv_id INT NOT NULL,
mdl_eqp_ary VARCHAR(14) DEFAULT '-999,-999,-999',
c_itm_eqp_ary VARCHAR(59) DEFAULT '-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999',
ms_itm_flg_ary VARCHAR(59) DEFAULT '-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1',
skin INT DEFAULT '-1',
btn_se INT DEFAULT '-1',
sld_se INT DEFAULT '-1',
chsld_se INT DEFAULT '-1',
sldtch_se INT DEFAULT '-1',
UNIQUE KEY diva_profile_pv_customize_uk (user, version, pv_id),
CONSTRAINT diva_profile_pv_customize_ibfk_1 FOREIGN KEY (user) REFERENCES aime_user (id) ON DELETE CASCADE ON UPDATE CASCADE
);

View File

@ -0,0 +1,9 @@
ALTER TABLE diva_profile
DROP cnp_cid,
DROP cnp_val,
DROP cnp_rr,
DROP cnp_sp,
DROP btn_se_eqp,
DROP sld_se_eqp,
DROP chn_sld_se_eqp,
DROP sldr_tch_se_eqp;

View File

@ -0,0 +1,3 @@
ALTER TABLE diva_profile ADD COLUMN passwd_stat INTEGER NOT NULL DEFAULT 0;
ALTER TABLE diva_profile ADD COLUMN passwd VARCHAR(12) NOT NULL DEFAULT "**********";
ALTER TABLE diva_profile MODIFY player_name VARCHAR(10);

View File

@ -0,0 +1,2 @@
ALTER TABLE diva_profile
DROP skn_eqp;

View File

@ -0,0 +1,9 @@
ALTER TABLE diva_profile
ADD cnp_cid INT NOT NULL DEFAULT -1,
ADD cnp_val INT NOT NULL DEFAULT -1,
ADD cnp_rr INT NOT NULL DEFAULT -1,
ADD cnp_sp VARCHAR(255) NOT NULL DEFAULT "",
ADD btn_se_eqp INT NOT NULL DEFAULT -1,
ADD sld_se_eqp INT NOT NULL DEFAULT -1,
ADD chn_sld_se_eqp INT NOT NULL DEFAULT -1,
ADD sldr_tch_se_eqp INT NOT NULL DEFAULT -1;

View File

@ -0,0 +1,2 @@
ALTER TABLE diva_profile
ADD skn_eqp INT NOT NULL DEFAULT 0;

View File

@ -0,0 +1 @@
ALTER TABLE chuni_static_music CHANGE COLUMN worldsEndTag worldsEndTag VARCHAR(20) NULL DEFAULT NULL ;

View File

@ -0,0 +1 @@
ALTER TABLE chuni_score_course DROP COLUMN theoryCount, DROP COLUMN orderId, DROP COLUMN playerRating;

View File

@ -0,0 +1 @@
ALTER TABLE chuni_static_music CHANGE COLUMN worldsEndTag worldsEndTag VARCHAR(7) NULL DEFAULT NULL ;

View File

@ -0,0 +1,30 @@
SET FOREIGN_KEY_CHECKS = 0;
ALTER TABLE chuni_score_playlog
DROP COLUMN regionId,
DROP COLUMN machineType;
ALTER TABLE chuni_static_events
DROP COLUMN startDate;
ALTER TABLE chuni_profile_data
DROP COLUMN rankUpChallengeResults;
ALTER TABLE chuni_static_login_bonus
DROP FOREIGN KEY chuni_static_login_bonus_ibfk_1;
ALTER TABLE chuni_static_login_bonus_preset
DROP PRIMARY KEY;
ALTER TABLE chuni_static_login_bonus_preset
CHANGE COLUMN presetId id INT NOT NULL;
ALTER TABLE chuni_static_login_bonus_preset
ADD PRIMARY KEY(id);
ALTER TABLE chuni_static_login_bonus_preset
ADD CONSTRAINT chuni_static_login_bonus_preset_uk UNIQUE(id, version);
ALTER TABLE chuni_static_login_bonus
ADD CONSTRAINT chuni_static_login_bonus_ibfk_1 FOREIGN KEY(presetId)
REFERENCES chuni_static_login_bonus_preset(id) ON UPDATE CASCADE ON DELETE CASCADE;
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -0,0 +1 @@
ALTER TABLE chuni_score_course ADD theoryCount int(11), ADD orderId int(11), ADD playerRating int(11);

View File

@ -0,0 +1,12 @@
SET FOREIGN_KEY_CHECKS = 0;
ALTER TABLE chuni_score_playlog
CHANGE COLUMN isClear isClear TINYINT(1) NULL DEFAULT NULL;
ALTER TABLE chuni_score_best
CHANGE COLUMN isSuccess isSuccess TINYINT(1) NULL DEFAULT NULL ;
ALTER TABLE chuni_score_playlog
DROP COLUMN ticketId;
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -0,0 +1,29 @@
SET FOREIGN_KEY_CHECKS = 0;
ALTER TABLE chuni_score_playlog
ADD COLUMN regionId INT,
ADD COLUMN machineType INT;
ALTER TABLE chuni_static_events
ADD COLUMN startDate TIMESTAMP NOT NULL DEFAULT current_timestamp();
ALTER TABLE chuni_profile_data
ADD COLUMN rankUpChallengeResults JSON;
ALTER TABLE chuni_static_login_bonus
DROP FOREIGN KEY chuni_static_login_bonus_ibfk_1;
ALTER TABLE chuni_static_login_bonus_preset
CHANGE COLUMN id presetId INT NOT NULL;
ALTER TABLE chuni_static_login_bonus_preset
DROP PRIMARY KEY;
ALTER TABLE chuni_static_login_bonus_preset
DROP INDEX chuni_static_login_bonus_preset_uk;
ALTER TABLE chuni_static_login_bonus_preset
ADD CONSTRAINT chuni_static_login_bonus_preset_pk PRIMARY KEY (presetId, version);
ALTER TABLE chuni_static_login_bonus
ADD CONSTRAINT chuni_static_login_bonus_ibfk_1 FOREIGN KEY (presetId, version)
REFERENCES chuni_static_login_bonus_preset(presetId, version) ON UPDATE CASCADE ON DELETE CASCADE;
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -0,0 +1,12 @@
SET FOREIGN_KEY_CHECKS = 0;
ALTER TABLE chuni_score_playlog
CHANGE COLUMN isClear isClear TINYINT(6) NULL DEFAULT NULL;
ALTER TABLE chuni_score_best
CHANGE COLUMN isSuccess isSuccess INT(11) NULL DEFAULT NULL ;
ALTER TABLE chuni_score_playlog
ADD COLUMN ticketId INT(11) NULL AFTER machineType;
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -0,0 +1,7 @@
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE ongeki_profile_data DROP COLUMN isDialogWatchedSuggestMemory;
ALTER TABLE ongeki_score_best DROP COLUMN platinumScoreMax;
ALTER TABLE ongeki_score_playlog DROP COLUMN platinumScore;
ALTER TABLE ongeki_score_playlog DROP COLUMN platinumScoreMax;
DROP TABLE IF EXISTS `ongeki_user_memorychapter`;
SET FOREIGN_KEY_CHECKS=1;

View File

@ -0,0 +1 @@
ALTER TABLE ongeki_profile_data DROP COLUMN lastEmoneyCredit;

View File

@ -0,0 +1,27 @@
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE ongeki_profile_data ADD COLUMN isDialogWatchedSuggestMemory BOOLEAN;
ALTER TABLE ongeki_score_best ADD COLUMN platinumScoreMax INTEGER;
ALTER TABLE ongeki_score_playlog ADD COLUMN platinumScore INTEGER;
ALTER TABLE ongeki_score_playlog ADD COLUMN platinumScoreMax INTEGER;
CREATE TABLE ongeki_user_memorychapter (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
user INT NOT NULL,
chapterId INT NOT NULL,
gaugeId INT NOT NULL,
gaugeNum INT NOT NULL,
jewelCount INT NOT NULL,
isStoryWatched BOOLEAN NOT NULL,
isBossWatched BOOLEAN NOT NULL,
isDialogWatched BOOLEAN NOT NULL,
isEndingWatched BOOLEAN NOT NULL,
isClear BOOLEAN NOT NULL,
lastPlayMusicId INT NOT NULL,
lastPlayMusicLevel INT NOT NULL,
lastPlayMusicCategory INT NOT NULL,
UNIQUE KEY ongeki_user_memorychapter_uk (user, chapterId),
CONSTRAINT ongeki_user_memorychapter_ibfk_1 FOREIGN KEY (user) REFERENCES aime_user (id) ON DELETE CASCADE ON UPDATE CASCADE
);
SET FOREIGN_KEY_CHECKS=1;

View File

@ -0,0 +1,2 @@
ALTER TABLE ongeki_static_events
DROP COLUMN startDate;

View File

@ -0,0 +1 @@
ALTER TABLE ongeki_profile_data ADD COLUMN lastEmoneyCredit INTEGER DEFAULT 0;

View File

@ -0,0 +1,22 @@
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE ongeki_user_event_point DROP COLUMN version;
ALTER TABLE ongeki_user_event_point DROP COLUMN `rank`;
ALTER TABLE ongeki_user_event_point DROP COLUMN `type`;
ALTER TABLE ongeki_user_event_point DROP COLUMN date;
ALTER TABLE ongeki_user_tech_event DROP COLUMN version;
ALTER TABLE ongeki_user_mission_point DROP COLUMN version;
ALTER TABLE ongeki_static_events DROP COLUMN endDate;
DROP TABLE ongeki_tech_event_ranking;
DROP TABLE ongeki_static_music_ranking_list;
DROP TABLE ongeki_static_rewards;
DROP TABLE ongeki_static_present_list;
DROP TABLE ongeki_static_tech_music;
DROP TABLE ongeki_static_client_testmode;
DROP TABLE ongeki_static_game_point;
SET FOREIGN_KEY_CHECKS=1;

View File

@ -0,0 +1,2 @@
ALTER TABLE ongeki_static_events
ADD COLUMN startDate TIMESTAMP NOT NULL DEFAULT current_timestamp();

View File

@ -0,0 +1,98 @@
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE ongeki_user_event_point ADD COLUMN version INTEGER NOT NULL;
ALTER TABLE ongeki_user_event_point ADD COLUMN `rank` INTEGER;
ALTER TABLE ongeki_user_event_point ADD COLUMN `type` INTEGER NOT NULL;
ALTER TABLE ongeki_user_event_point ADD COLUMN date VARCHAR(25);
ALTER TABLE ongeki_user_tech_event ADD COLUMN version INTEGER NOT NULL;
ALTER TABLE ongeki_user_mission_point ADD COLUMN version INTEGER NOT NULL;
ALTER TABLE ongeki_static_events ADD COLUMN endDate TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
CREATE TABLE ongeki_tech_event_ranking (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
user INT NOT NULL,
version INT NOT NULL,
date VARCHAR(25),
eventId INT NOT NULL,
`rank` INT,
totalPlatinumScore INT NOT NULL,
totalTechScore INT NOT NULL,
UNIQUE KEY ongeki_tech_event_ranking_uk (user, eventId),
CONSTRAINT ongeki_tech_event_ranking_ibfk1 FOREIGN KEY (user) REFERENCES aime_user(id) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE ongeki_static_music_ranking_list (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
version INT NOT NULL,
musicId INT NOT NULL,
point INT NOT NULL,
userName VARCHAR(255),
UNIQUE KEY ongeki_static_music_ranking_list_uk (version, musicId)
);
CREATE TABLE ongeki_static_rewards (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
version INT NOT NULL,
rewardId INT NOT NULL,
rewardName VARCHAR(255) NOT NULL,
itemKind INT NOT NULL,
itemId INT NOT NULL,
UNIQUE KEY ongeki_tech_event_ranking_uk (version, rewardId)
);
CREATE TABLE ongeki_static_present_list (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
version INT NOT NULL,
presentId INT NOT NULL,
presentName VARCHAR(255) NOT NULL,
rewardId INT NOT NULL,
stock INT NOT NULL,
message VARCHAR(255),
startDate VARCHAR(25) NOT NULL,
endDate VARCHAR(25) NOT NULL,
UNIQUE KEY ongeki_static_present_list_uk (version, presentId, rewardId)
);
CREATE TABLE ongeki_static_tech_music (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
version INT NOT NULL,
eventId INT NOT NULL,
musicId INT NOT NULL,
level INT NOT NULL,
UNIQUE KEY ongeki_static_tech_music_uk (version, musicId, eventId)
);
CREATE TABLE ongeki_static_client_testmode (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
regionId INT NOT NULL,
placeId INT NOT NULL,
clientId VARCHAR(11) NOT NULL,
updateDate TIMESTAMP NOT NULL,
isDelivery BOOLEAN NOT NULL,
groupId INT NOT NULL,
groupRole INT NOT NULL,
continueMode INT NOT NULL,
selectMusicTime INT NOT NULL,
advertiseVolume INT NOT NULL,
eventMode INT NOT NULL,
eventMusicNum INT NOT NULL,
patternGp INT NOT NULL,
limitGp INT NOT NULL,
maxLeverMovable INT NOT NULL,
minLeverMovable INT NOT NULL,
UNIQUE KEY ongeki_static_client_testmode_uk (clientId)
);
CREATE TABLE ongeki_static_game_point (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
`type` INT NOT NULL,
cost INT NOT NULL,
startDate VARCHAR(25) NOT NULL DEFAULT "2000-01-01 05:00:00.0",
endDate VARCHAR(25) NOT NULL DEFAULT "2099-01-01 05:00:00.0",
UNIQUE KEY ongeki_static_game_point_uk (`type`)
);
SET FOREIGN_KEY_CHECKS=1;

View File

@ -0,0 +1,3 @@
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE mai2_playlog DROP COLUMN trialPlayAchievement;
SET FOREIGN_KEY_CHECKS=1;

View File

@ -0,0 +1,21 @@
ALTER TABLE mai2_item_card
CHANGE COLUMN cardId card_id INT NOT NULL AFTER user,
CHANGE COLUMN cardTypeId card_kind INT NOT NULL,
CHANGE COLUMN charaId chara_id INT NOT NULL,
CHANGE COLUMN mapId map_id INT NOT NULL,
CHANGE COLUMN startDate start_date TIMESTAMP NULL DEFAULT '2018-01-01 00:00:00',
CHANGE COLUMN endDate end_date TIMESTAMP NULL DEFAULT '2038-01-01 00:00:00';
ALTER TABLE mai2_item_item
CHANGE COLUMN itemId item_id INT NOT NULL AFTER user,
CHANGE COLUMN itemKind item_kind INT NOT NULL,
CHANGE COLUMN isValid is_valid TINYINT(1) NOT NULL DEFAULT '1';
ALTER TABLE mai2_item_character
CHANGE COLUMN characterId character_id INT NOT NULL,
CHANGE COLUMN useCount use_count INT NOT NULL DEFAULT '0';
ALTER TABLE mai2_item_charge
CHANGE COLUMN chargeId charge_id INT NOT NULL,
CHANGE COLUMN purchaseDate purchase_date TIMESTAMP NOT NULL,
CHANGE COLUMN validDate valid_date TIMESTAMP NOT NULL;

View File

@ -0,0 +1,3 @@
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE mai2_playlog ADD trialPlayAchievement INT NULL;
SET FOREIGN_KEY_CHECKS=1;

View File

@ -0,0 +1,31 @@
ALTER TABLE mai2_profile_option
DROP COLUMN tapSe;
ALTER TABLE mai2_score_best
DROP COLUMN extNum1;
ALTER TABLE mai2_profile_extend
DROP COLUMN playStatusSetting;
ALTER TABLE mai2_playlog
DROP COLUMN extNum4;
ALTER TABLE mai2_static_event
DROP COLUMN startDate;
ALTER TABLE mai2_item_map
CHANGE COLUMN mapId map_id INT NOT NULL,
CHANGE COLUMN isLock is_lock BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN isClear is_clear BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN isComplete is_complete BOOLEAN NOT NULL DEFAULT 0;
ALTER TABLE mai2_item_friend_season_ranking
CHANGE COLUMN seasonId season_id INT NOT NULL,
CHANGE COLUMN rewardGet reward_get BOOLEAN NOT NULL,
CHANGE COLUMN userName user_name VARCHAR(8) NOT NULL,
CHANGE COLUMN recordDate record_date VARCHAR(255) NOT NULL;
ALTER TABLE mai2_item_login_bonus
CHANGE COLUMN bonusId bonus_id INT NOT NULL,
CHANGE COLUMN isCurrent is_current BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN isComplete is_complete BOOLEAN NOT NULL DEFAULT 0;

View File

@ -0,0 +1,21 @@
ALTER TABLE mai2_item_card
CHANGE COLUMN card_id cardId INT NOT NULL AFTER user,
CHANGE COLUMN card_kind cardTypeId INT NOT NULL,
CHANGE COLUMN chara_id charaId INT NOT NULL,
CHANGE COLUMN map_id mapId INT NOT NULL,
CHANGE COLUMN start_date startDate TIMESTAMP NULL DEFAULT '2018-01-01 00:00:00',
CHANGE COLUMN end_date endDate TIMESTAMP NULL DEFAULT '2038-01-01 00:00:00';
ALTER TABLE mai2_item_item
CHANGE COLUMN item_id itemId INT NOT NULL AFTER user,
CHANGE COLUMN item_kind itemKind INT NOT NULL,
CHANGE COLUMN is_valid isValid TINYINT(1) NOT NULL DEFAULT '1';
ALTER TABLE mai2_item_character
CHANGE COLUMN character_id characterId INT NOT NULL,
CHANGE COLUMN use_count useCount INT NOT NULL DEFAULT '0';
ALTER TABLE mai2_item_charge
CHANGE COLUMN charge_id chargeId INT NOT NULL,
CHANGE COLUMN purchase_date purchaseDate TIMESTAMP NOT NULL,
CHANGE COLUMN valid_date validDate TIMESTAMP NOT NULL;

View File

@ -0,0 +1,3 @@
ALTER TABLE mai2_item_card
CHANGE COLUMN startDate startDate TIMESTAMP DEFAULT "2018-01-01 00:00:00.0",
CHANGE COLUMN endDate endDate TIMESTAMP DEFAULT "2038-01-01 00:00:00.0";

View File

@ -0,0 +1,31 @@
ALTER TABLE mai2_profile_option
ADD COLUMN tapSe INT NOT NULL DEFAULT 0 AFTER tapDesign;
ALTER TABLE mai2_score_best
ADD COLUMN extNum1 INT NOT NULL DEFAULT 0;
ALTER TABLE mai2_profile_extend
ADD COLUMN playStatusSetting INT NOT NULL DEFAULT 0;
ALTER TABLE mai2_playlog
ADD COLUMN extNum4 INT NOT NULL DEFAULT 0;
ALTER TABLE mai2_static_event
ADD COLUMN startDate TIMESTAMP NOT NULL DEFAULT current_timestamp();
ALTER TABLE mai2_item_map
CHANGE COLUMN map_id mapId INT NOT NULL,
CHANGE COLUMN is_lock isLock BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN is_clear isClear BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN is_complete isComplete BOOLEAN NOT NULL DEFAULT 0;
ALTER TABLE mai2_item_friend_season_ranking
CHANGE COLUMN season_id seasonId INT NOT NULL,
CHANGE COLUMN reward_get rewardGet BOOLEAN NOT NULL,
CHANGE COLUMN user_name userName VARCHAR(8) NOT NULL,
CHANGE COLUMN record_date recordDate TIMESTAMP NOT NULL;
ALTER TABLE mai2_item_login_bonus
CHANGE COLUMN bonus_id bonusId INT NOT NULL,
CHANGE COLUMN is_current isCurrent BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN is_complete isComplete BOOLEAN NOT NULL DEFAULT 0;

View File

@ -0,0 +1,78 @@
DELETE FROM mai2_static_event WHERE version < 13;
UPDATE mai2_static_event SET version = version - 13 WHERE version >= 13;
DELETE FROM mai2_static_music WHERE version < 13;
UPDATE mai2_static_music SET version = version - 13 WHERE version >= 13;
DELETE FROM mai2_static_ticket WHERE version < 13;
UPDATE mai2_static_ticket SET version = version - 13 WHERE version >= 13;
DELETE FROM mai2_static_cards WHERE version < 13;
UPDATE mai2_static_cards SET version = version - 13 WHERE version >= 13;
DELETE FROM mai2_profile_detail WHERE version < 13;
UPDATE mai2_profile_detail SET version = version - 13 WHERE version >= 13;
DELETE FROM mai2_profile_extend WHERE version < 13;
UPDATE mai2_profile_extend SET version = version - 13 WHERE version >= 13;
DELETE FROM mai2_profile_option WHERE version < 13;
UPDATE mai2_profile_option SET version = version - 13 WHERE version >= 13;
DELETE FROM mai2_profile_ghost WHERE version < 13;
UPDATE mai2_profile_ghost SET version = version - 13 WHERE version >= 13;
DELETE FROM mai2_profile_rating WHERE version < 13;
UPDATE mai2_profile_rating SET version = version - 13 WHERE version >= 13;
DROP TABLE maimai_score_best;
DROP TABLE maimai_playlog;
DROP TABLE maimai_profile_detail;
DROP TABLE maimai_profile_option;
DROP TABLE maimai_profile_web_option;
DROP TABLE maimai_profile_grade_status;
ALTER TABLE mai2_item_character DROP COLUMN point;
ALTER TABLE mai2_item_card MODIFY COLUMN cardId int(11) NOT NULL;
ALTER TABLE mai2_item_card MODIFY COLUMN cardTypeId int(11) NOT NULL;
ALTER TABLE mai2_item_card MODIFY COLUMN charaId int(11) NOT NULL;
ALTER TABLE mai2_item_card MODIFY COLUMN mapId int(11) NOT NULL;
ALTER TABLE mai2_item_character MODIFY COLUMN characterId int(11) NOT NULL;
ALTER TABLE mai2_item_character MODIFY COLUMN level int(11) NOT NULL;
ALTER TABLE mai2_item_character MODIFY COLUMN awakening int(11) NOT NULL;
ALTER TABLE mai2_item_character MODIFY COLUMN useCount int(11) NOT NULL;
ALTER TABLE mai2_item_charge MODIFY COLUMN chargeId int(11) NOT NULL;
ALTER TABLE mai2_item_charge MODIFY COLUMN stock int(11) NOT NULL;
ALTER TABLE mai2_item_favorite MODIFY COLUMN itemKind int(11) NOT NULL;
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN seasonId int(11) NOT NULL;
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN point int(11) NOT NULL;
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN `rank` int(11) NOT NULL;
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN rewardGet tinyint(1) NOT NULL;
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN userName varchar(8) NOT NULL;
ALTER TABLE mai2_item_item MODIFY COLUMN itemId int(11) NOT NULL;
ALTER TABLE mai2_item_item MODIFY COLUMN itemKind int(11) NOT NULL;
ALTER TABLE mai2_item_item MODIFY COLUMN stock int(11) NOT NULL;
ALTER TABLE mai2_item_item MODIFY COLUMN isValid tinyint(1) NOT NULL;
ALTER TABLE mai2_item_login_bonus MODIFY COLUMN bonusId int(11) NOT NULL;
ALTER TABLE mai2_item_login_bonus MODIFY COLUMN point int(11) NOT NULL;
ALTER TABLE mai2_item_login_bonus MODIFY COLUMN isCurrent tinyint(1) NOT NULL;
ALTER TABLE mai2_item_login_bonus MODIFY COLUMN isComplete tinyint(1) NOT NULL;
ALTER TABLE mai2_item_map MODIFY COLUMN mapId int(11) NOT NULL;
ALTER TABLE mai2_item_map MODIFY COLUMN distance int(11) NOT NULL;
ALTER TABLE mai2_item_map MODIFY COLUMN isLock tinyint(1) NOT NULL;
ALTER TABLE mai2_item_map MODIFY COLUMN isClear tinyint(1) NOT NULL;
ALTER TABLE mai2_item_map MODIFY COLUMN isComplete tinyint(1) NOT NULL;
ALTER TABLE mai2_item_print_detail MODIFY COLUMN printDate timestamp DEFAULT current_timestamp() NOT NULL;
ALTER TABLE mai2_item_print_detail MODIFY COLUMN serialId varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;
ALTER TABLE mai2_item_print_detail MODIFY COLUMN placeId int(11) NOT NULL;
ALTER TABLE mai2_item_print_detail MODIFY COLUMN clientId varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;
ALTER TABLE mai2_item_print_detail MODIFY COLUMN printerSerialId varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;

View File

@ -0,0 +1,3 @@
ALTER TABLE mai2_item_card
CHANGE COLUMN startDate startDate TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHANGE COLUMN endDate endDate TIMESTAMP NOT NULL;

View File

@ -0,0 +1 @@
DROP TABLE aime.mai2_profile_consec_logins;

View File

@ -0,0 +1,62 @@
UPDATE mai2_static_event SET version = version + 13 WHERE version < 1000;
UPDATE mai2_static_music SET version = version + 13 WHERE version < 1000;
UPDATE mai2_static_ticket SET version = version + 13 WHERE version < 1000;
UPDATE mai2_static_cards SET version = version + 13 WHERE version < 1000;
UPDATE mai2_profile_detail SET version = version + 13 WHERE version < 1000;
UPDATE mai2_profile_extend SET version = version + 13 WHERE version < 1000;
UPDATE mai2_profile_option SET version = version + 13 WHERE version < 1000;
UPDATE mai2_profile_ghost SET version = version + 13 WHERE version < 1000;
UPDATE mai2_profile_rating SET version = version + 13 WHERE version < 1000;
ALTER TABLE mai2_item_character ADD point int(11) NULL;
ALTER TABLE mai2_item_card MODIFY COLUMN cardId int(11) NULL;
ALTER TABLE mai2_item_card MODIFY COLUMN cardTypeId int(11) NULL;
ALTER TABLE mai2_item_card MODIFY COLUMN charaId int(11) NULL;
ALTER TABLE mai2_item_card MODIFY COLUMN mapId int(11) NULL;
ALTER TABLE mai2_item_character MODIFY COLUMN characterId int(11) NULL;
ALTER TABLE mai2_item_character MODIFY COLUMN level int(11) NULL;
ALTER TABLE mai2_item_character MODIFY COLUMN awakening int(11) NULL;
ALTER TABLE mai2_item_character MODIFY COLUMN useCount int(11) NULL;
ALTER TABLE mai2_item_charge MODIFY COLUMN chargeId int(11) NULL;
ALTER TABLE mai2_item_charge MODIFY COLUMN stock int(11) NULL;
ALTER TABLE mai2_item_favorite MODIFY COLUMN itemKind int(11) NULL;
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN seasonId int(11) NULL;
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN point int(11) NULL;
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN `rank` int(11) NULL;
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN rewardGet tinyint(1) NULL;
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN userName varchar(8) NULL;
ALTER TABLE mai2_item_item MODIFY COLUMN itemId int(11) NULL;
ALTER TABLE mai2_item_item MODIFY COLUMN itemKind int(11) NULL;
ALTER TABLE mai2_item_item MODIFY COLUMN stock int(11) NULL;
ALTER TABLE mai2_item_item MODIFY COLUMN isValid tinyint(1) NULL;
ALTER TABLE mai2_item_login_bonus MODIFY COLUMN bonusId int(11) NULL;
ALTER TABLE mai2_item_login_bonus MODIFY COLUMN point int(11) NULL;
ALTER TABLE mai2_item_login_bonus MODIFY COLUMN isCurrent tinyint(1) NULL;
ALTER TABLE mai2_item_login_bonus MODIFY COLUMN isComplete tinyint(1) NULL;
ALTER TABLE mai2_item_map MODIFY COLUMN mapId int(11) NULL;
ALTER TABLE mai2_item_map MODIFY COLUMN distance int(11) NULL;
ALTER TABLE mai2_item_map MODIFY COLUMN isLock tinyint(1) NULL;
ALTER TABLE mai2_item_map MODIFY COLUMN isClear tinyint(1) NULL;
ALTER TABLE mai2_item_map MODIFY COLUMN isComplete tinyint(1) NULL;
ALTER TABLE mai2_item_print_detail MODIFY COLUMN printDate timestamp DEFAULT current_timestamp() NULL;
ALTER TABLE mai2_item_print_detail MODIFY COLUMN serialId varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL;
ALTER TABLE mai2_item_print_detail MODIFY COLUMN placeId int(11) NULL;
ALTER TABLE mai2_item_print_detail MODIFY COLUMN clientId varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL;
ALTER TABLE mai2_item_print_detail MODIFY COLUMN printerSerialId varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL;

View File

@ -0,0 +1,10 @@
ALTER TABLE mai2_profile_detail
DROP COLUMN mapStock;
ALTER TABLE mai2_profile_extend
DROP COLUMN selectResultScoreViewType;
ALTER TABLE mai2_profile_option
DROP COLUMN outFrameType,
DROP COLUMN touchVolume,
DROP COLUMN breakSlideVolume;

View File

@ -0,0 +1,9 @@
CREATE TABLE `mai2_profile_consec_logins` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user` int(11) NOT NULL,
`version` int(11) NOT NULL,
`logins` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `mai2_profile_consec_logins_uk` (`user`,`version`),
CONSTRAINT `mai2_profile_consec_logins_ibfk_1` FOREIGN KEY (`user`) REFERENCES `aime_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

View File

@ -0,0 +1,10 @@
ALTER TABLE mai2_profile_detail
ADD mapStock INT NULL AFTER playCount;
ALTER TABLE mai2_profile_extend
ADD selectResultScoreViewType INT NULL AFTER selectResultDetails;
ALTER TABLE mai2_profile_option
ADD outFrameType INT NULL AFTER dispCenter,
ADD touchVolume INT NULL AFTER slideVolume,
ADD breakSlideVolume INT NULL AFTER slideVolume;

View File

@ -0,0 +1,2 @@
SET FOREIGN_KEY_CHECKS=0;
SET FOREIGN_KEY_CHECKS=1;

View File

@ -0,0 +1 @@
ALTER TABLE wacca_profile DROP COLUMN playcount_time_free;

View File

@ -0,0 +1 @@
DELETE FROM wacca_item WHERE type=17 AND item_id=312002;

View File

@ -0,0 +1 @@
ALTER TABLE wacca_profile ADD playcount_time_free int(11) DEFAULT 0 NULL AFTER playcount_stageup;

View File

@ -0,0 +1,54 @@
SET FOREIGN_KEY_CHECKS=0;
-- WARNING: This script is NOT idempotent! MAKE A BACKUP BEFORE RUNNING THIS SCRIPT!
-- Drop UK idac_user_vs_info_uk
ALTER TABLE idac_user_vs_info
DROP FOREIGN KEY idac_user_vs_info_ibfk_1,
DROP INDEX idac_user_vs_info_uk;
-- Drop the new columns added to the original table
ALTER TABLE idac_user_vs_info
DROP COLUMN battle_mode,
DROP COLUMN invalid,
DROP COLUMN str,
DROP COLUMN str_now,
DROP COLUMN lose_now;
-- Add back the old columns to the original table
ALTER TABLE idac_user_vs_info
ADD COLUMN group_key VARCHAR(25),
ADD COLUMN win_flg INT,
ADD COLUMN style_car_id INT,
ADD COLUMN course_id INT,
ADD COLUMN course_day INT,
ADD COLUMN players_num INT,
ADD COLUMN winning INT,
ADD COLUMN advantage_1 INT,
ADD COLUMN advantage_2 INT,
ADD COLUMN advantage_3 INT,
ADD COLUMN advantage_4 INT,
ADD COLUMN select_course_id INT,
ADD COLUMN select_course_day INT,
ADD COLUMN select_course_random INT,
ADD COLUMN matching_success_sec INT,
ADD COLUMN boost_flag INT;
-- Delete the data from the original table where group_key is NULL
DELETE FROM idac_user_vs_info
WHERE group_key IS NULL;
-- Insert data back to the original table from idac_user_vs_course_info
INSERT INTO idac_user_vs_info (user, group_key, win_flg, style_car_id, course_id, course_day, players_num, winning, advantage_1, advantage_2, advantage_3, advantage_4, select_course_id, select_course_day, select_course_random, matching_success_sec, boost_flag, vs_history, break_count, break_penalty_flag)
SELECT user, CONCAT(FLOOR(RAND()*(99999999999999-10000000000000+1)+10000000000000), 'A69E01A8888'), 0, 0, course_id, 0, 0, vs_cnt, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
FROM idac_user_vs_course_info;
-- Add back the constraints and indexes to the original table
ALTER TABLE idac_user_vs_info
ADD CONSTRAINT idac_user_vs_info_ibfk_1 FOREIGN KEY (user) REFERENCES aime_user(id) ON DELETE CASCADE ON UPDATE CASCADE,
ADD UNIQUE KEY idac_user_vs_info_uk (user, group_key);
-- Drop the new table idac_user_vs_course_info
DROP TABLE IF EXISTS idac_user_vs_course_info;
SET FOREIGN_KEY_CHECKS=1;

View File

@ -0,0 +1,71 @@
SET FOREIGN_KEY_CHECKS=0;
-- WARNING: This script is NOT idempotent! MAKE A BACKUP BEFORE RUNNING THIS SCRIPT!
-- Create the new table idac_user_vs_course_info
CREATE TABLE idac_user_vs_course_info (
id INT PRIMARY KEY AUTO_INCREMENT,
user INT,
battle_mode INT,
course_id INT,
vs_cnt INT,
vs_win INT,
CONSTRAINT idac_user_vs_course_info_fk FOREIGN KEY (user) REFERENCES aime_user(id) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE KEY idac_user_vs_course_info_uk (user, battle_mode, course_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Insert data from the original table to the new tables
INSERT INTO idac_user_vs_course_info (user, battle_mode, course_id, vs_cnt, vs_win)
SELECT user, 1 as battle_mode, course_id, COUNT(winning) as vs_cnt, SUM(win_flg) as vs_win
FROM idac_user_vs_info
GROUP BY user, course_id;
-- Drop UK idac_user_vs_info_uk
ALTER TABLE idac_user_vs_info
DROP FOREIGN KEY idac_user_vs_info_ibfk_1,
DROP INDEX idac_user_vs_info_uk;
-- Drop/Add the old columns from the original table
ALTER TABLE idac_user_vs_info
DROP COLUMN group_key,
DROP COLUMN win_flg,
DROP COLUMN style_car_id,
DROP COLUMN course_id,
DROP COLUMN course_day,
DROP COLUMN players_num,
DROP COLUMN winning,
DROP COLUMN advantage_1,
DROP COLUMN advantage_2,
DROP COLUMN advantage_3,
DROP COLUMN advantage_4,
DROP COLUMN select_course_id,
DROP COLUMN select_course_day,
DROP COLUMN select_course_random,
DROP COLUMN matching_success_sec,
DROP COLUMN boost_flag,
ADD COLUMN battle_mode TINYINT UNSIGNED DEFAULT 1 NOT NULL AFTER user,
ADD COLUMN invalid INT DEFAULT 0,
ADD COLUMN str INT DEFAULT 0,
ADD COLUMN str_now INT DEFAULT 0,
ADD COLUMN lose_now INT DEFAULT 0;
-- Create a temporary table to store the records you want to keep
CREATE TEMPORARY TABLE temp_table AS
SELECT MIN(id) AS min_id
FROM idac_user_vs_info
GROUP BY battle_mode, user;
-- Delete records from the original table based on the temporary table
DELETE FROM idac_user_vs_info
WHERE id NOT IN (SELECT min_id FROM temp_table);
-- Drop the temporary table
DROP TEMPORARY TABLE IF EXISTS temp_table;
-- Add UK idac_user_vs_info_uk
ALTER TABLE idac_user_vs_info
ADD CONSTRAINT idac_user_vs_info_ibfk_1 FOREIGN KEY (user) REFERENCES aime_user(id) ON DELETE CASCADE ON UPDATE CASCADE,
ADD UNIQUE KEY idac_user_vs_info_uk (user, battle_mode);
SET FOREIGN_KEY_CHECKS=1;

865
core/frontend.py Normal file
View File

@ -0,0 +1,865 @@
import logging, coloredlogs
from typing import Any, Dict, List, Union, Optional
from starlette.requests import Request
from starlette.routing import Route, Mount
from starlette.responses import Response, PlainTextResponse, RedirectResponse
from starlette.applications import Starlette
from logging.handlers import TimedRotatingFileHandler
import jinja2
import bcrypt
import re
import jwt
import yaml
import secrets
import string
import random
from base64 import b64decode
from enum import Enum
from datetime import datetime, timezone
from os import path, environ, mkdir, W_OK, access
from core import CoreConfig, Utils
from core.data import Data
class PermissionOffset(Enum):
USER = 0 # Regular user
USERMOD = 1 # Can moderate other users
ACMOD = 2 # Can add arcades and cabs
SYSADMIN = 3 # Can change settings
# 4 - 6 reserved for future use
OWNER = 7 # Can do anything
class ShopPermissionOffset(Enum):
VIEW = 0 # View info and cabs
BOOKKEEP = 1 # View bookeeping info
EDITOR = 2 # Can edit name, settings
REGISTRAR = 3 # Can add cabs
# 4 - 6 reserved for future use
OWNER = 7 # Can do anything
class ShopOwner():
def __init__(self, usr_id: int = 0, usr_name: str = "", perms: int = 0) -> None:
self.user_id = usr_id
self.username = usr_name
self.permissions = perms
class UserSession():
def __init__(self, usr_id: int = 0, ip: str = "", perms: int = 0, ongeki_ver: int = 7):
self.user_id = usr_id
self.current_ip = ip
self.permissions = perms
self.ongeki_version = ongeki_ver
class FrontendServlet():
def __init__(self, cfg: CoreConfig, config_dir: str) -> None:
self.config = cfg
log_fmt_str = "[%(asctime)s] Frontend | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
self.environment = jinja2.Environment(loader=jinja2.FileSystemLoader("."))
self.game_list: Dict[str, Dict[str, Any]] = {}
self.sn_cvt: Dict[str, str] = {}
self.logger = logging.getLogger("frontend")
if not hasattr(self.logger, "inited"):
fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.config.server.log_dir, "frontend"),
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
self.logger.setLevel(cfg.frontend.loglevel)
coloredlogs.install(
level=cfg.frontend.loglevel, logger=self.logger, fmt=log_fmt_str
)
self.logger.inited = True
games = Utils.get_all_titles()
for game_dir, game_mod in games.items():
if hasattr(game_mod, "frontend") and hasattr(game_mod, "index") and hasattr(game_mod, "game_codes"):
try:
if game_mod.index.is_game_enabled(game_mod.game_codes[0], self.config, config_dir):
game_fe = game_mod.frontend(cfg, self.environment, config_dir)
self.game_list[game_fe.nav_name] = {"url": f"/{game_dir}", "class": game_fe }
if hasattr(game_fe, "SN_PREFIX") and hasattr(game_fe, "NETID_PREFIX"):
if len(game_fe.SN_PREFIX) == len(game_fe.NETID_PREFIX):
for x in range(len(game_fe.SN_PREFIX)):
self.sn_cvt[game_fe.SN_PREFIX[x]] = game_fe.NETID_PREFIX[x]
except Exception as e:
self.logger.error(
f"Failed to import frontend from {game_dir} because {e}"
)
self.environment.globals["game_list"] = self.game_list
self.environment.globals["sn_cvt"] = self.sn_cvt
self.base = FE_Base(cfg, self.environment)
self.gate = FE_Gate(cfg, self.environment)
self.user = FE_User(cfg, self.environment)
self.system = FE_System(cfg, self.environment)
self.arcade = FE_Arcade(cfg, self.environment)
self.machine = FE_Machine(cfg, self.environment)
def get_routes(self) -> List[Route]:
g_routes = []
for nav_name, g_data in self.environment.globals["game_list"].items():
g_routes.append(Mount(g_data['url'], routes=g_data['class'].get_routes()))
return [
Route("/", self.base.render_GET, methods=['GET']),
Mount("/user", routes=[
Route("/", self.user.render_GET, methods=['GET']),
Route("/{user_id:int}", self.user.render_GET, methods=['GET']),
Route("/update.pw", self.user.render_POST, methods=['POST']),
Route("/update.name", self.user.update_username, methods=['POST']),
Route("/edit.card", self.user.edit_card, methods=['POST']),
Route("/add.card", self.user.add_card, methods=['POST']),
Route("/logout", self.user.render_logout, methods=['GET']),
]),
Mount("/gate", routes=[
Route("/", self.gate.render_GET, methods=['GET', 'POST']),
Route("/gate.login", self.gate.render_login, methods=['POST']),
Route("/gate.create", self.gate.render_create, methods=['POST']),
Route("/create", self.gate.render_create_get, methods=['GET']),
]),
Mount("/sys", routes=[
Route("/", self.system.render_GET, methods=['GET']),
Route("/lookup.user", self.system.lookup_user, methods=['GET']),
Route("/lookup.shop", self.system.lookup_shop, methods=['GET']),
Route("/add.user", self.system.add_user, methods=['POST']),
Route("/add.card", self.system.add_card, methods=['POST']),
Route("/add.shop", self.system.add_shop, methods=['POST']),
Route("/add.cab", self.system.add_cab, methods=['POST']),
]),
Mount("/shop", routes=[
Route("/", self.arcade.render_GET, methods=['GET']),
Route("/{shop_id:int}", self.arcade.render_GET, methods=['GET']),
]),
Mount("/cab", routes=[
Route("/", self.machine.render_GET, methods=['GET']),
Route("/{machine_id:int}", self.machine.render_GET, methods=['GET']),
]),
Mount("/game", routes=g_routes),
Route("/robots.txt", self.robots)
]
def startup(self) -> None:
self.config.update({
"frontend": {
"standalone": True,
"loglevel": CoreConfig.loglevel_to_str(self.config.frontend.loglevel),
"secret": self.config.frontend.secret
}
})
self.logger.info(f"Serving {len(self.game_list)} games")
@classmethod
async def robots(cls, request: Request) -> PlainTextResponse:
return PlainTextResponse("User-agent: *\nDisallow: /\n\nUser-agent: AdsBot-Google\nDisallow: /")
class FE_Base():
"""
A Generic skeleton class that all frontend handlers should inherit from
Initializes the environment, data, logger, config, and sets isLeaf to true
It is expected that game implementations of this class overwrite many of these
"""
def __init__(self, cfg: CoreConfig, environment: jinja2.Environment) -> None:
self.core_config = cfg
self.data = Data(cfg)
self.logger = logging.getLogger("frontend")
self.environment = environment
self.nav_name = "index"
async def render_GET(self, request: Request):
self.logger.debug(f"{Utils.get_ip_addr(request)} -> {request.url}")
template = self.environment.get_template("core/templates/index.jinja")
sesh = self.validate_session(request)
resp = Response(template.render(
server_name=self.core_config.server.name,
title=self.core_config.server.name,
game_list=self.environment.globals["game_list"],
sesh=vars(sesh) if sesh is not None else vars(UserSession()),
), media_type="text/html; charset=utf-8")
if sesh is None:
resp.delete_cookie("DIANA_SESH")
return resp
def get_routes(self) -> List[Route]:
return []
@classmethod
def test_perm(cls, permission: int, offset: Union[PermissionOffset, ShopPermissionOffset]) -> bool:
logging.getLogger('frontend').debug(f"{permission} vs {1 << offset.value}")
return permission & 1 << offset.value == 1 << offset.value
@classmethod
def test_perm_minimum(cls, permission: int, offset: Union[PermissionOffset, ShopPermissionOffset]) -> bool:
return permission >= 1 << offset.value
def decode_session(self, token: str) -> UserSession:
sesh = UserSession()
if not token: return sesh
try:
tk = jwt.decode(token, b64decode(self.core_config.frontend.secret), options={"verify_signature": True}, algorithms=["HS256"])
sesh.user_id = tk['user_id']
sesh.current_ip = tk['current_ip']
sesh.permissions = tk['permissions']
if sesh.user_id <= 0:
self.logger.error("User session failed to validate due to an invalid ID!")
return UserSession()
return sesh
except jwt.ExpiredSignatureError:
self.logger.error("User session failed to validate due to an expired signature!")
return sesh
except jwt.InvalidSignatureError:
self.logger.error("User session failed to validate due to an invalid signature!")
return sesh
except jwt.DecodeError as e:
self.logger.error(f"User session failed to decode! {e}")
return sesh
except jwt.InvalidTokenError as e:
self.logger.error(f"User session is invalid! {e}")
return sesh
except KeyError as e:
self.logger.error(f"{e} missing from User session!")
return UserSession()
except Exception as e:
self.logger.error(f"Unknown exception occoured when decoding User session! {e}")
return UserSession()
def validate_session(self, request: Request) -> Optional[UserSession]:
sesh = request.cookies.get('DIANA_SESH', "")
if not sesh:
return None
usr_sesh = self.decode_session(sesh)
req_ip = Utils.get_ip_addr(request)
if usr_sesh.current_ip != req_ip:
self.logger.error(f"User session failed to validate due to mismatched IPs! {usr_sesh.current_ip} -> {req_ip}")
return None
if usr_sesh.permissions <= 0 or usr_sesh.permissions > 255:
self.logger.error(f"User session failed to validate due to an invalid permission value! {usr_sesh.permissions}")
return None
return usr_sesh
def encode_session(self, sesh: UserSession, exp_seconds: int = 86400) -> str:
try:
return jwt.encode({ "user_id": sesh.user_id, "current_ip": sesh.current_ip, "permissions": sesh.permissions, "ongeki_version": sesh.ongeki_version, "exp": int(datetime.now(tz=timezone.utc).timestamp()) + exp_seconds }, b64decode(self.core_config.frontend.secret), algorithm="HS256")
except jwt.InvalidKeyError:
self.logger.error("Failed to encode User session because the secret is invalid!")
return ""
except Exception as e:
self.logger.error(f"Unknown exception occoured when encoding User session! {e}")
return ""
class FE_Gate(FE_Base):
async def render_GET(self, request: Request):
self.logger.debug(f"{Utils.get_ip_addr(request)} -> {request.url.path}")
usr_sesh = self.validate_session(request)
if usr_sesh and usr_sesh.user_id > 0:
return RedirectResponse("/user/", 303)
if "e" in request.query_params:
try:
err = int(request.query_params.get("e", ["0"])[0])
except Exception:
err = 0
else:
err = 0
template = self.environment.get_template("core/templates/gate/gate.jinja")
resp = Response(template.render(
title=f"{self.core_config.server.name} | Login Gate",
error=err,
sesh=vars(UserSession()),
), media_type="text/html; charset=utf-8")
resp.delete_cookie("DIANA_SESH")
return resp
async def render_login(self, request: Request):
ip = Utils.get_ip_addr(request)
frm = await request.form()
access_code: str = frm.get("access_code", None)
if not access_code:
return RedirectResponse("/gate/?e=1", 303)
passwd: bytes = frm.get("passwd", "").encode()
if passwd == b"":
passwd = None
uid = await self.data.card.get_user_id_from_card(access_code)
if uid is None:
self.logger.debug(f"Failed to find user for card {access_code}")
return RedirectResponse("/gate/?e=1", 303)
user = await self.data.user.get_user(uid)
if user is None:
self.logger.error(f"Failed to load user {uid}")
return RedirectResponse("/gate/?e=1", 303)
if passwd is None:
sesh = await self.data.user.check_password(uid)
if sesh is not None:
return RedirectResponse(f"/gate/create?ac={access_code}", 303)
return RedirectResponse("/gate/?e=1", 303)
if not await self.data.user.check_password(uid, passwd):
self.logger.debug(f"Failed password for access code {access_code}")
return RedirectResponse("/gate/?e=1", 303)
self.logger.info(f"Successful login of user {uid} at {ip}")
sesh = UserSession()
sesh.user_id = uid
sesh.current_ip = ip
sesh.permissions = user['permissions']
usr_sesh = self.encode_session(sesh)
self.logger.debug(f"Created session with JWT {usr_sesh}")
resp = RedirectResponse("/user/", 303)
resp.set_cookie("DIANA_SESH", usr_sesh)
return resp
async def render_create(self, request: Request):
ip = Utils.get_ip_addr(request)
frm = await request.form()
access_code: str = frm.get("access_code", "")
username: str = frm.get("username", "")
email: str = frm.get("email", "")
passwd: bytes = frm.get("passwd", "").encode()
if not access_code or not username or not email or not passwd:
return RedirectResponse("/gate/?e=1", 303)
uid = await self.data.card.get_user_id_from_card(access_code)
if uid is None:
return RedirectResponse("/gate/?e=1", 303)
salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(passwd, salt)
result = await self.data.user.create_user(
uid, username, email.lower(), hashed.decode(), 1
)
if result is None:
return RedirectResponse("/gate/?e=3", 303)
if not await self.data.user.check_password(uid, passwd):
return RedirectResponse("/gate/", 303)
sesh = UserSession()
sesh.user_id = uid
sesh.current_ip = ip
sesh.permissions = 1
usr_sesh = self.encode_session(sesh)
self.logger.debug(f"Created session with JWT {usr_sesh}")
resp = RedirectResponse("/user/", 303)
resp.set_cookie("DIANA_SESH", usr_sesh)
return resp
async def render_create_get(self, request: Request):
ac = request.query_params.get("ac", "")
if len(ac) != 20:
return RedirectResponse("/gate/?e=2", 303)
card = await self.data.card.get_card_by_access_code(ac)
if card is None:
return RedirectResponse("/gate/?e=1", 303)
user = await self.data.user.get_user(card['user'])
if user is None:
self.logger.warning(f"Card {ac} exists with no/invalid associated user ID {card['user']}")
return RedirectResponse("/gate/?e=0", 303)
if user['password'] is not None:
return RedirectResponse("/gate/?e=1", 303)
template = self.environment.get_template("core/templates/gate/create.jinja")
return Response(template.render(
title=f"{self.core_config.server.name} | Create User",
code=ac,
sesh={"user_id": 0, "permissions": 0},
), media_type="text/html; charset=utf-8")
class FE_User(FE_Base):
async def render_GET(self, request: Request):
uri = request.url.path
user_id = request.path_params.get('user_id', None)
self.logger.debug(f"{Utils.get_ip_addr(request)} -> {uri}")
template = self.environment.get_template("core/templates/user/index.jinja")
usr_sesh = self.validate_session(request)
if not usr_sesh:
return RedirectResponse("/gate/", 303)
if user_id:
if not self.test_perm(usr_sesh.permissions, PermissionOffset.USERMOD) and user_id != usr_sesh.user_id:
self.logger.warn(f"User {usr_sesh.user_id} does not have permission to view user {user_id}")
return RedirectResponse("/user/", 303)
else:
user_id = usr_sesh.user_id
user = await self.data.user.get_user(user_id)
if user is None:
self.logger.debug(f"User {user_id} not found")
return RedirectResponse("/user/", 303)
cards = await self.data.card.get_user_cards(user_id)
card_data = []
arcade_data = []
for c in cards:
if c['is_locked']:
status = 'Locked'
elif c['is_banned']:
status = 'Banned'
else:
status = 'Active'
#idm = c['idm']
ac = c['access_code']
if ac.startswith("5"): #or idm is not None:
c_type = "AmusementIC"
elif ac.startswith("3"):
c_type = "Banapass"
elif ac.startswith("010"):
c_type = "Aime" # TODO: Aime verification
elif ac.startswith("0008"):
c_type = "Generated AIC"
else:
c_type = "Unknown"
card_data.append({
'access_code': ac,
'status': status,
'chip_id': "", #None if c['chip_id'] is None else f"{c['chip_id']:X}",
'idm': "",
'type': c_type,
"memo": ""
})
if "e" in request.query_params:
try:
err = int(request.query_params.get("e", 0))
except Exception:
err = 0
else:
err = 0
if "s" in request.query_params:
try:
succ = int(request.query_params.get("s", 0))
except Exception:
succ = 0
else:
succ = 0
return Response(template.render(
title=f"{self.core_config.server.name} | Account",
sesh=vars(usr_sesh),
cards=card_data,
error=err,
success=succ,
username=user['username'],
arcades=arcade_data
), media_type="text/html; charset=utf-8")
async def render_logout(self, request: Request):
resp = RedirectResponse("/gate/", 303)
resp.delete_cookie("DIANA_SESH")
return resp
async def edit_card(self, request: Request) -> RedirectResponse:
return RedirectResponse("/user/", 303)
async def add_card(self, request: Request) -> RedirectResponse:
return RedirectResponse("/user/", 303)
async def render_POST(self, request: Request):
frm = await request.form()
usr_sesh = self.validate_session(request)
if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.USERMOD):
return RedirectResponse("/gate/", 303)
old_pw: str = frm.get('current_pw', None)
pw1: str = frm.get('password1', None)
pw2: str = frm.get('password2', None)
if old_pw is None or pw1 is None or pw2 is None:
return RedirectResponse("/user/?e=4", 303)
if pw1 != pw2:
return RedirectResponse("/user/?e=6", 303)
if not await self.data.user.check_password(usr_sesh.user_id, old_pw.encode()):
return RedirectResponse("/user/?e=5", 303)
if len(pw1) < 10 or not any(ele.isupper() for ele in pw1) or not any(ele.islower() for ele in pw1) \
or not any(ele.isdigit() for ele in pw1) or not any(not ele.isalnum() for ele in pw1):
return RedirectResponse("/user/?e=7", 303)
salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(pw1.encode(), salt)
if not await self.data.user.change_password(usr_sesh.user_id, hashed.decode()):
return RedirectResponse("/gate/?e=1", 303)
return RedirectResponse("/user/?s=1", 303)
async def update_username(self, request: Request):
frm = await request.form()
new_name: bytes = frm.get('new_name', "")
usr_sesh = self.validate_session(request)
if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.USERMOD):
return RedirectResponse("/gate/", 303)
if new_name is None or not new_name:
return RedirectResponse("/user/?e=4", 303)
if len(new_name) > 10:
return RedirectResponse("/user/?e=8", 303)
if not await self.data.user.change_username(usr_sesh.user_id, new_name):
return RedirectResponse("/user/?e=8", 303)
return RedirectResponse("/user/?s=2", 303)
class FE_System(FE_Base):
async def render_GET(self, request: Request):
template = self.environment.get_template("core/templates/sys/index.jinja")
self.logger.debug(f"{Utils.get_ip_addr(request)} -> {request.url.path}")
usr_sesh = self.validate_session(request)
if not usr_sesh or not self.test_perm_minimum(usr_sesh.permissions, PermissionOffset.USERMOD):
return RedirectResponse("/gate/", 303)
if request.query_params.get("e", None):
err = int(request.query_params.get("e"))
else:
err = 0
return Response(template.render(
title=f"{self.core_config.server.name} | System",
sesh=vars(usr_sesh),
usrlist=[],
error = err
), media_type="text/html; charset=utf-8")
async def lookup_user(self, request: Request):
template = self.environment.get_template("core/templates/sys/index.jinja")
usrlist: List[Dict] = []
usr_sesh = self.validate_session(request)
if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.USERMOD):
return RedirectResponse("/gate/", 303)
uid_search = request.query_params.get("usrId", None)
email_search = request.query_params.get("usrEmail", None)
uname_search = request.query_params.get("usrName", None)
if uid_search:
u = await self.data.user.get_user(uid_search)
if u is not None:
usrlist.append(u._asdict())
elif email_search:
u = await self.data.user.find_user_by_email(email_search)
if u is not None:
usrlist.append(u._asdict())
elif uname_search:
ul = await self.data.user.find_user_by_username(uname_search)
for u in ul:
usrlist.append(u._asdict())
return Response(template.render(
title=f"{self.core_config.server.name} | System",
sesh=vars(usr_sesh),
usrlist=usrlist,
shoplist=[],
), media_type="text/html; charset=utf-8")
async def lookup_shop(self, request: Request):
shoplist = []
template = self.environment.get_template("core/templates/sys/index.jinja")
usr_sesh = self.validate_session(request)
if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.ACMOD):
return RedirectResponse("/gate/", 303)
shopid_search = request.query_params.get("shopId", None)
sn_search = request.query_params.get("serialNum", None)
if shopid_search:
if shopid_search.isdigit():
shopid_search = int(shopid_search)
try:
sinfo = await self.data.arcade.get_arcade(shopid_search)
except Exception as e:
self.logger.error(f"Failed to fetch shop info for shop {shopid_search} in lookup_shop - {e}")
sinfo = None
if sinfo:
shoplist.append({
"name": sinfo['name'],
"id": sinfo['id']
})
else:
return Response(template.render(
title=f"{self.core_config.server.name} | System",
sesh=vars(usr_sesh),
usrlist=[],
shoplist=shoplist,
error=4
), media_type="text/html; charset=utf-8")
if sn_search:
sn_search = sn_search.upper().replace("-", "").strip()
if sn_search.isdigit() and len(sn_search) == 12:
prefix = sn_search[:4]
suffix = sn_search[5:]
netid_prefix = self.environment.globals["sn_cvt"].get(prefix, "")
sn_search = netid_prefix + suffix
if re.match(r"^AB[DGL]N\d{7}$", sn_search) or re.match(r"^A\d{2}[EX]\d{2}[A-Z]\d{4,8}$", sn_search):
cabinfo = await self.data.arcade.get_machine(sn_search)
if cabinfo is None: sinfo = None
else:
sinfo = await self.data.arcade.get_arcade(cabinfo['arcade'])
if sinfo:
shoplist.append({
"name": sinfo['name'],
"id": sinfo['id']
})
else:
return Response(template.render(
title=f"{self.core_config.server.name} | System",
sesh=vars(usr_sesh),
usrlist=[],
shoplist=shoplist,
error=10
), media_type="text/html; charset=utf-8")
return Response(template.render(
title=f"{self.core_config.server.name} | System",
sesh=vars(usr_sesh),
usrlist=[],
shoplist=shoplist,
), media_type="text/html; charset=utf-8")
async def add_user(self, request: Request):
template = self.environment.get_template("core/templates/sys/index.jinja")
usr_sesh = self.validate_session(request)
if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.ACMOD):
return RedirectResponse("/gate/", 303)
frm = await request.form()
username = frm.get("userName", None)
email = frm.get("userEmail", None)
perm = frm.get("usrPerm", "1")
passwd = "".join(
secrets.choice(string.ascii_letters + string.digits) for i in range(20)
)
hash = bcrypt.hashpw(passwd.encode(), bcrypt.gensalt())
if not email:
return RedirectResponse("/sys/?e=4", 303)
uid = await self.data.user.create_user(username=username if username else None, email=email, password=hash.decode(), permission=int(perm))
return Response(template.render(
title=f"{self.core_config.server.name} | System",
sesh=vars(usr_sesh),
usradd={"id": uid, "username": username, "password": passwd},
), media_type="text/html; charset=utf-8")
async def add_card(self, request: Request):
template = self.environment.get_template("core/templates/sys/index.jinja")
usr_sesh = self.validate_session(request)
if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.ACMOD):
return RedirectResponse("/gate/", 303)
frm = await request.form()
userid = frm.get("cardUsr", None)
access_code = frm.get("cardAc", None)
idm = frm.get("cardIdm", None)
if userid is None or access_code is None or not userid.isdigit() or not len(access_code) == 20 or not access_code.isdigit:
return RedirectResponse("/sys/?e=4", 303)
cardid = await self.data.card.create_card(int(userid), access_code)
if not cardid:
return RedirectResponse("/sys/?e=99", 303)
if idm is not None:
# TODO: save IDM
pass
return Response(template.render(
title=f"{self.core_config.server.name} | System",
sesh=vars(usr_sesh),
cardadd={"id": cardid, "user": userid, "access_code": access_code},
), media_type="text/html; charset=utf-8")
async def add_shop(self, request: Request):
template = self.environment.get_template("core/templates/sys/index.jinja")
usr_sesh = self.validate_session(request)
if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.ACMOD):
return RedirectResponse("/gate/", 303)
frm = await request.form()
name = frm.get("shopName", None)
country = frm.get("shopCountry", "JPN")
ip = frm.get("shopIp", None)
acid = await self.data.arcade.create_arcade(name if name else None, name if name else None, country)
if not acid:
return RedirectResponse("/sys/?e=99", 303)
if ip:
# TODO: set IP
pass
return Response(template.render(
title=f"{self.core_config.server.name} | System",
sesh=vars(usr_sesh),
shopadd={"id": acid},
), media_type="text/html; charset=utf-8")
async def add_cab(self, request: Request):
template = self.environment.get_template("core/templates/sys/index.jinja")
usr_sesh = self.validate_session(request)
if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.ACMOD):
return RedirectResponse("/gate/", 303)
frm = await request.form()
shopid = frm.get("cabShop", None)
serial = frm.get("cabSerial", None)
game_code = frm.get("cabGame", None)
if not shopid or not shopid.isdigit():
return RedirectResponse("/sys/?e=4", 303)
if not serial:
serial = self.data.arcade.format_serial("A69E", 1, random.randint(1, 9999))
cab_id = await self.data.arcade.create_machine(int(shopid), serial, None, game_code if game_code else None)
return Response(template.render(
title=f"{self.core_config.server.name} | System",
sesh=vars(usr_sesh),
cabadd={"id": cab_id, "serial": serial},
), media_type="text/html; charset=utf-8")
class FE_Arcade(FE_Base):
async def render_GET(self, request: Request):
template = self.environment.get_template("core/templates/arcade/index.jinja")
shop_id = request.path_params.get('shop_id', None)
usr_sesh = self.validate_session(request)
if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.ACMOD):
self.logger.warn(f"User {usr_sesh.user_id} does not have permission to view shops!")
return RedirectResponse("/gate/", 303)
if not shop_id:
return Response(template.render(
title=f"{self.core_config.server.name} | Arcade",
sesh=vars(usr_sesh),
), media_type="text/html; charset=utf-8")
sinfo = await self.data.arcade.get_arcade(shop_id)
if not sinfo:
return Response(template.render(
title=f"{self.core_config.server.name} | Arcade",
sesh=vars(usr_sesh),
), media_type="text/html; charset=utf-8")
cabs = await self.data.arcade.get_arcade_machines(shop_id)
cablst = []
if cabs:
for x in cabs:
cablst.append({
"id": x['id'],
"serial": x['serial'],
"game": x['game'],
})
return Response(template.render(
title=f"{self.core_config.server.name} | Arcade",
sesh=vars(usr_sesh),
arcade={
"name": sinfo['name'],
"id": sinfo['id'],
"cabs": cablst
}
), media_type="text/html; charset=utf-8")
class FE_Machine(FE_Base):
async def render_GET(self, request: Request):
template = self.environment.get_template("core/templates/machine/index.jinja")
cab_id = request.path_params.get('cab_id', None)
usr_sesh = self.validate_session(request)
if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.ACMOD):
self.logger.warn(f"User {usr_sesh.user_id} does not have permission to view shops!")
return RedirectResponse("/gate/", 303)
if not cab_id:
return Response(template.render(
title=f"{self.core_config.server.name} | Machine",
sesh=vars(usr_sesh),
), media_type="text/html; charset=utf-8")
return Response(template.render(
title=f"{self.core_config.server.name} | Machine",
sesh=vars(usr_sesh),
arcade={}
), media_type="text/html; charset=utf-8")
cfg_dir = environ.get("DIANA_CFG_DIR", "config")
cfg: CoreConfig = CoreConfig()
if path.exists(f"{cfg_dir}/core.yaml"):
cfg.update(yaml.safe_load(open(f"{cfg_dir}/core.yaml")))
if not path.exists(cfg.server.log_dir):
mkdir(cfg.server.log_dir)
if not access(cfg.server.log_dir, W_OK):
print(
f"Log directory {cfg.server.log_dir} NOT writable, please check permissions"
)
exit(1)
fe = FrontendServlet(cfg, cfg_dir)
app = Starlette(cfg.server.is_develop, fe.get_routes(), on_startup=[fe.startup])

View File

@ -1,115 +1,244 @@
from typing import Dict, Any, Optional
import logging, coloredlogs
from logging.handlers import TimedRotatingFileHandler
from twisted.web import resource
from twisted.web.http import Request
from starlette.requests import Request
from starlette.responses import PlainTextResponse
from datetime import datetime
from Crypto.Cipher import Blowfish
import pytz
from core.config import CoreConfig
from .config import CoreConfig
from .utils import Utils
from .title import TitleServlet
from .data import Data
from .const import *
class MuchaServlet:
def __init__(self, cfg: CoreConfig) -> None:
mucha_registry: Dict[str, Dict[str, str]] = {}
def __init__(self, cfg: CoreConfig, cfg_dir: str) -> None:
self.config = cfg
self.config_dir = cfg_dir
self.logger = logging.getLogger('mucha')
self.logger = logging.getLogger("mucha")
log_fmt_str = "[%(asctime)s] Mucha | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "mucha"), when="d", backupCount=10)
fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.config.server.log_dir, "mucha"),
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
self.logger.setLevel(logging.INFO)
coloredlogs.install(level=logging.INFO, logger=self.logger, fmt=log_fmt_str)
def handle_boardauth(self, request: Request) -> bytes:
req_dict = self.mucha_preprocess(request.content.getvalue())
self.logger.setLevel(cfg.mucha.loglevel)
coloredlogs.install(level=cfg.mucha.loglevel, logger=self.logger, fmt=log_fmt_str)
self.data = Data(cfg)
for _, mod in TitleServlet.title_registry.items():
enabled, game_cds, netids = mod.get_mucha_info(self.config, self.config_dir)
if enabled:
for x in range(len(game_cds)):
self.mucha_registry[game_cds[x]] = { "netid_prefix": netids[x] }
self.logger.info(f"Serving {len(self.mucha_registry)} games")
async def handle_boardauth(self, request: Request) -> bytes:
bod = await request.body()
req_dict = self.mucha_preprocess(bod)
client_ip = Utils.get_ip_addr(request)
if req_dict is None:
self.logger.error(f"Error processing mucha request {request.content.getvalue()}")
return b""
self.logger.error(
f"Error processing mucha request {bod}"
)
return PlainTextResponse("RESULTS=000")
req = MuchaAuthRequest(req_dict)
self.logger.info(f"Mucha request {vars(req)}")
resp = MuchaAuthResponse(mucha_url=f"{self.config.mucha.hostname}:{self.config.mucha.port}")
self.logger.info(f"Mucha response {vars(resp)}")
self.logger.debug(f"Mucha request {vars(req)}")
if not req.gameCd or not req.gameVer or not req.sendDate or not req.countryCd or not req.serialNum:
self.logger.warn(f"Missing required fields - {vars(req)}")
return PlainTextResponse("RESULTS=000")
minfo = self.mucha_registry.get(req.gameCd, {})
if not minfo:
self.logger.warning(f"Unknown gameCd {req.gameCd} from {client_ip}")
return PlainTextResponse("RESULTS=000")
b_key = b""
for x in range(8):
b_key += req.sendDate[(x - 1) & 7].encode()
b_iv = b_key # what the fuck namco
cipher = Blowfish.new(b_key, Blowfish.MODE_CBC, b_iv)
try:
sn_decrypt = cipher.decrypt(bytes.fromhex(req.serialNum))[:12].decode()
except Exception as e:
self.logger.error(f"Decrypt SN {req.serialNum} failed! - {e}")
return PlainTextResponse("RESULTS=000")
self.logger.info(f"Boardauth request from {sn_decrypt} ({client_ip}) for {req.gameVer}")
resp = MuchaAuthResponse(
f"{self.config.server.hostname}{':' + str(self.config.server.port) if not self.config.server.is_using_proxy else ''}"
)
netid = minfo.get('netid_prefix', "ABxN") + sn_decrypt[5:]
cab = await self.data.arcade.get_machine(netid)
if cab:
arcade = await self.data.arcade.get_arcade(cab['id'])
if not arcade:
self.logger.error(f"Failed to get arcade with id {cab['id']}")
return PlainTextResponse("RESULTS=000")
resp.AREA_0 = arcade["region_id"] or AllnetJapanRegionId.AICHI.name
resp.AREA_0_EN = arcade["region_id"] or AllnetJapanRegionId.AICHI.name
resp.AREA_FULL_0 = arcade["region_id"] or AllnetJapanRegionId.AICHI.name
resp.AREA_FULL_0_EN = arcade["region_id"] or AllnetJapanRegionId.AICHI.name
resp.AREA_1 = arcade["country"] or cab['country'] or AllnetCountryCode.JAPAN.value
resp.AREA_1_EN = arcade["country"] or cab['country'] or AllnetCountryCode.JAPAN.value
resp.AREA_FULL_1 = arcade["country"] or cab['country'] or AllnetCountryCode.JAPAN.value
resp.AREA_FULL_1_EN = arcade["country"] or cab['country'] or AllnetCountryCode.JAPAN.value
resp.AREA_2 = arcade["city"] if arcade["city"] else ""
resp.AREA_2_EN = arcade["city"] if arcade["city"] else ""
resp.AREA_FULL_2 = arcade["city"] if arcade["city"] else ""
resp.AREA_FULL_2_EN = arcade["city"] if arcade["city"] else ""
resp.AREA_3 = ""
resp.AREA_3_EN = ""
resp.AREA_FULL_3 = ""
resp.AREA_FULL_3_EN = ""
resp.PREFECTURE_ID = arcade['region_id']
resp.COUNTRY_CD = arcade['country'] or cab['country'] or AllnetCountryCode.JAPAN.value
resp.PLACE_ID = req.placeId if req.placeId else f"{arcade['country'] or cab['country'] or AllnetCountryCode.JAPAN.value}{arcade['id']:04X}"
resp.SHOP_NAME = arcade['name']
resp.SHOP_NAME_EN = arcade['name']
resp.SHOP_NICKNAME = arcade['nickname']
resp.SHOP_NICKNAME_EN = arcade['nickname']
elif self.config.server.allow_unregistered_serials:
self.logger.info(f"Allow unknown serial {netid} ({sn_decrypt}) to auth")
else:
self.logger.warn(f'Auth failed for NetID {netid}')
return PlainTextResponse("RESULTS=000")
self.logger.debug(f"Mucha response {vars(resp)}")
return PlainTextResponse(self.mucha_postprocess(vars(resp)))
async def handle_updatecheck(self, request: Request) -> bytes:
bod = await request.body()
req_dict = self.mucha_preprocess(bod)
client_ip = Utils.get_ip_addr(request)
return self.mucha_postprocess(vars(resp))
def handle_updatecheck(self, request: Request) -> bytes:
req_dict = self.mucha_preprocess(request.content.getvalue())
if req_dict is None:
self.logger.error(f"Error processing mucha request {request.content.getvalue()}")
return b""
self.logger.error(
f"Error processing mucha request {bod}"
)
return PlainTextResponse("RESULTS=000")
req = MuchaUpdateRequest(req_dict)
self.logger.info(f"Mucha request {vars(req)}")
resp = MuchaUpdateResponse(mucha_url=f"{self.config.mucha.hostname}:{self.config.mucha.port}")
self.logger.info(f"Mucha response {vars(resp)}")
self.logger.info(f"Updatecheck request from {req.serialNum} ({client_ip}) for {req.gameVer}")
self.logger.debug(f"Mucha request {vars(req)}")
return self.mucha_postprocess(vars(resp))
if req.gameCd not in self.mucha_registry:
self.logger.warning(f"Unknown gameCd {req.gameCd}")
return PlainTextResponse("RESULTS=000")
resp = MuchaUpdateResponse(req.gameVer, f"{self.config.server.hostname}{':' + str(self.config.server.port) if not self.config.server.is_using_proxy else ''}")
self.logger.debug(f"Mucha response {vars(resp)}")
return PlainTextResponse(self.mucha_postprocess(vars(resp)))
async def handle_dlstate(self, request: Request) -> bytes:
bod = await request.body()
req_dict = self.mucha_preprocess(bod)
client_ip = Utils.get_ip_addr(request)
if req_dict is None:
self.logger.error(
f"Error processing mucha request {bod}"
)
return PlainTextResponse("RESULTS=000")
req = MuchaDownloadStateRequest(req_dict)
self.logger.info(f"DownloadState request from {req.serialNum} ({client_ip}) for {req.gameCd} -> {req.updateVer}")
self.logger.debug(f"request {vars(req)}")
return PlainTextResponse("RESULTS=001")
def mucha_preprocess(self, data: bytes) -> Optional[Dict]:
try:
ret: Dict[str, Any] = {}
for x in data.decode().split('&'):
kvp = x.split('=')
for x in data.decode().split("&"):
kvp = x.split("=")
if len(kvp) == 2:
ret[kvp[0]] = kvp[1]
return ret
except:
except Exception:
self.logger.error(f"Error processing mucha request {data}")
return None
def mucha_postprocess(self, data: dict) -> Optional[bytes]:
try:
urlencode = ""
for k,v in data.items():
urlencode += f"{k}={v}&"
urlencode = "&".join(f"{k}={v}" for k, v in data.items())
return urlencode.encode()
except:
except Exception:
self.logger.error("Error processing mucha response")
return None
class MuchaAuthRequest():
def __init__(self, request: Dict) -> None:
self.gameVer = "" if "gameVer" not in request else request["gameVer"]
self.sendDate = "" if "sendDate" not in request else request["sendDate"]
self.serialNum = "" if "serialNum" not in request else request["serialNum"]
self.gameCd = "" if "gameCd" not in request else request["gameCd"]
self.boardType = "" if "boardType" not in request else request["boardType"]
self.boardId = "" if "boardId" not in request else request["boardId"]
self.placeId = "" if "placeId" not in request else request["placeId"]
self.storeRouterIp = "" if "storeRouterIp" not in request else request["storeRouterIp"]
self.countryCd = "" if "countryCd" not in request else request["countryCd"]
self.useToken = "" if "useToken" not in request else request["useToken"]
self.allToken = "" if "allToken" not in request else request["allToken"]
class MuchaAuthResponse():
def __init__(self, mucha_url: str = "localhost") -> None:
self.RESULTS = "001"
class MuchaAuthRequest:
def __init__(self, request: Dict) -> None:
# gameCd + boardType + countryCd + version
self.gameVer = request.get("gameVer", "")
self.sendDate = request.get("sendDate", "") # %Y%m%d
self.serialNum = request.get("serialNum", "")
self.gameCd = request.get("gameCd", "")
self.boardType = request.get("boardType", "")
self.boardId = request.get("boardId", "")
self.mac = request.get("mac", "")
self.placeId = request.get("placeId", "")
self.storeRouterIp = request.get("storeRouterIp", "")
self.countryCd = request.get("countryCd", "")
self.useToken = request.get("useToken", "")
self.allToken = request.get("allToken", "")
class MuchaAuthResponse:
def __init__(self, mucha_url: str) -> None:
self.RESULTS = "001"
self.AUTH_INTERVAL = "86400"
self.SERVER_TIME = datetime.strftime(datetime.now(), "%Y%m%d%H%M")
self.UTC_SERVER_TIME = datetime.strftime(datetime.now(pytz.UTC), "%Y%m%d%H%M")
self.SERVER_TIME_UTC = datetime.strftime(datetime.now(pytz.UTC), "%Y%m%d%H%M")
self.CHARGE_URL = f"https://{mucha_url}/charge/"
self.CHARGE_URL = f"https://{mucha_url}/charge/"
self.FILE_URL = f"https://{mucha_url}/file/"
self.URL_1 = f"https://{mucha_url}/url1/"
self.URL_2 = f"https://{mucha_url}/url2/"
self.URL_3 = f"https://{mucha_url}/url3/"
self.PLACE_ID = "JPN123"
self.COUNTRY_CD = "JPN"
self.PLACE_ID = "JPN123"
self.COUNTRY_CD = "JPN"
self.SHOP_NAME = "TestShop!"
self.SHOP_NICKNAME = "TestShop"
self.AREA_0 = "008"
@ -120,7 +249,7 @@ class MuchaAuthResponse():
self.AREA_FULL_1 = ""
self.AREA_FULL_2 = ""
self.AREA_FULL_3 = ""
self.SHOP_NAME_EN = "TestShop!"
self.SHOP_NICKNAME_EN = "TestShop"
self.AREA_0_EN = "008"
@ -132,32 +261,141 @@ class MuchaAuthResponse():
self.AREA_FULL_2_EN = ""
self.AREA_FULL_3_EN = ""
self.PREFECTURE_ID = "1"
self.PREFECTURE_ID = "1"
self.EXPIRATION_DATE = "null"
self.USE_TOKEN = "0"
self.CONSUME_TOKEN = "0"
self.DONGLE_FLG = "1"
self.FORCE_BOOT = "0"
class MuchaUpdateRequest():
def __init__(self, request: Dict) -> None:
self.gameVer = "" if "gameVer" not in request else request["gameVer"]
self.gameCd = "" if "gameCd" not in request else request["gameCd"]
self.serialNum = "" if "serialNum" not in request else request["serialNum"]
self.countryCd = "" if "countryCd" not in request else request["countryCd"]
self.placeId = "" if "placeId" not in request else request["placeId"]
self.storeRouterIp = "" if "storeRouterIp" not in request else request["storeRouterIp"]
class MuchaUpdateResponse():
def __init__(self, game_ver: str = "PKFN0JPN01.01", mucha_url: str = "localhost") -> None:
self.RESULTS = "001"
class MuchaUpdateRequest:
def __init__(self, request: Dict) -> None:
self.gameVer = request.get("gameVer", "")
self.gameCd = request.get("gameCd", "")
self.serialNum = request.get("serialNum", "")
self.countryCd = request.get("countryCd", "")
self.placeId = request.get("placeId", "")
self.storeRouterIp = request.get("storeRouterIp", "")
class MuchaUpdateResponse:
def __init__(self, game_ver: str, mucha_url: str) -> None:
self.RESULTS = "001"
self.EXE_VER = game_ver
self.UPDATE_VER_1 = game_ver
self.UPDATE_URL_1 = f"https://{mucha_url}/updUrl1/"
self.UPDATE_SIZE_1 = "0"
self.UPDATE_CRC_1 = "0000000000000000"
self.CHECK_URL_1 = f"https://{mucha_url}/checkUrl/"
self.EXE_VER_1 = game_ver
self.UPDATE_URL_1 = f"http://{mucha_url}/updUrl1/"
self.UPDATE_SIZE_1 = "20"
self.CHECK_CRC_1 = "0000000000000000"
self.CHECK_URL_1 = f"http://{mucha_url}/checkUrl/"
self.CHECK_SIZE_1 = "20"
self.INFO_SIZE_1 = "0"
self.COM_SIZE_1 = "0"
self.COM_TIME_1 = "0"
self.LAN_INFO_SIZE_1 = "0"
self.USER_ID = ""
self.PASSWORD = ""
"""
RESULTS
EXE_VER
UPDATE_VER_%d
UPDATE_URL_%d
UPDATE_SIZE_%d
CHECK_CRC_%d
CHECK_URL_%d
CHECK_SIZE_%d
INFO_SIZE_1
COM_SIZE_1
COM_TIME_1
LAN_INFO_SIZE_1
USER_ID
PASSWORD
"""
class MuchaUpdateResponseStub:
def __init__(self, game_ver: str) -> None:
self.RESULTS = "001"
self.UPDATE_VER_1 = game_ver
class MuchaDownloadStateRequest:
def __init__(self, request: Dict) -> None:
self.gameCd = request.get("gameCd", "")
self.updateVer = request.get("updateVer", "")
self.serialNum = request.get("serialNum", "")
self.fileSize = request.get("fileSize", "")
self.compFileSize = request.get("compFileSize", "")
self.boardId = request.get("boardId", "")
self.placeId = request.get("placeId", "")
self.storeRouterIp = request.get("storeRouterIp", "")
class MuchaDownloadErrorRequest:
def __init__(self, request: Dict) -> None:
self.gameCd = request.get("gameCd", "")
self.updateVer = request.get("updateVer", "")
self.serialNum = request.get("serialNum", "")
self.downloadUrl = request.get("downloadUrl", "")
self.errCd = request.get("errCd", "")
self.errMessage = request.get("errMessage", "")
self.boardId = request.get("boardId", "")
self.placeId = request.get("placeId", "")
self.storeRouterIp = request.get("storeRouterIp", "")
class MuchaRegiAuthRequest:
def __init__(self, request: Dict) -> None:
self.gameCd = request.get("gameCd", "")
self.serialNum = request.get("serialNum", "") # Encrypted
self.countryCd = request.get("countryCd", "")
self.registrationCd = request.get("registrationCd", "")
self.sendDate = request.get("sendDate", "")
self.useToken = request.get("useToken", "")
self.allToken = request.get("allToken", "")
self.placeId = request.get("placeId", "")
self.storeRouterIp = request.get("storeRouterIp", "")
class MuchaRegiAuthResponse:
def __init__(self) -> None:
self.RESULTS = "001" # 001 = success, 099, 098, 097 = fail, others = fail
self.ALL_TOKEN = "0" # Encrypted
self.ADD_TOKEN = "0" # Encrypted
class MuchaTokenStateRequest:
def __init__(self, request: Dict) -> None:
self.gameCd = request.get("gameCd", "")
self.serialNum = request.get("serialNum", "")
self.countryCd = request.get("countryCd", "")
self.useToken = request.get("useToken", "")
self.allToken = request.get("allToken", "")
self.placeId = request.get("placeId", "")
self.storeRouterIp = request.get("storeRouterIp", "")
class MuchaTokenStateResponse:
def __init__(self) -> None:
self.RESULTS = "001"
class MuchaTokenMarginStateRequest:
def __init__(self, request: Dict) -> None:
self.gameCd = request.get("gameCd", "")
self.serialNum = request.get("serialNum", "")
self.countryCd = request.get("countryCd", "")
self.placeId = request.get("placeId", "")
self.limitLowerToken = request.get("limitLowerToken", 0)
self.limitUpperToken = request.get("limitUpperToken", 0)
self.settlementMonth = request.get("settlementMonth", 0)
class MuchaTokenMarginStateResponse:
def __init__(self) -> None:
self.RESULTS = "001"
self.LIMIT_LOWER_TOKEN = 0
self.LIMIT_UPPER_TOKEN = 0
self.LAST_SETTLEMENT_MONTH = 0
self.LAST_LIMIT_LOWER_TOKEN = 0
self.LAST_LIMIT_UPPER_TOKEN = 0
self.SETTLEMENT_MONTH = 0

View File

@ -0,0 +1,19 @@
{% extends "core/templates/index.jinja" %}
{% block content %}
{% if arcade is defined %}
<h1>{{ arcade.name }}</h1>
<h2>PCBs assigned to this arcade <button class="btn btn-success" id="btn_add_cab" onclick="toggle_add_cab_form()">Add</button></h2>
{% if success is defined and success == 3 %}
<div style="background-color: #00AA00; padding: 20px; margin-bottom: 10px; width: 15%;">
Cab added successfully
</div>
{% endif %}
<ul style="font-size: 20px;">
{% for c in arcade.cabs %}
<li><a href="/cab/{{ c.id }}">{{ c.serial }}</a> ({{ c.game if c.game else "Any" }})&nbsp;<button class="btn btn-secondary" onclick="prep_edit_form()">Edit</button>&nbsp;<button class="btn-danger btn">Delete</button></li>
{% endfor %}
</ul>
{% else %}
<h3>Arcade Not Found</h3>
{% endif %}
{% endblock content %}

View File

@ -0,0 +1,24 @@
{% extends "core/templates/index.jinja" %}
{% block content %}
<h1>Create User</h1>
<form id="create" style="max-width: 240px; min-width: 10%;" action="/gate/gate.create" method="post">
<div class="form-group row">
<label for="access_code">Card Access Code</label><br>
<input class="form-control" name="access_code" id="access_code" type="text" placeholder="00000000000000000000" value={{ code }} maxlength="20" readonly>
</div>
<div class="form-group row">
<label for="username">Username</label><br>
<input id="username" class="form-control" name="username" type="text" placeholder="username">
</div>
<div class="form-group row">
<label for="email">Email</label><br>
<input id="email" class="form-control" name="email" type="email" placeholder="example@example.com">
</div>
<div class="form-group row">
<label for="passwd">Password</label><br>
<input id="passwd" class="form-control" name="passwd" type="password" placeholder="password">
</div>
<p></p>
<input id="submit" class="btn btn-primary" style="display: block; margin: 0 auto;" type="submit" value="Create">
</form>
{% endblock content %}

View File

@ -0,0 +1,32 @@
{% extends "core/templates/index.jinja" %}
{% block content %}
<h1>Gate</h1>
{% include "core/templates/widgets/err_banner.jinja" %}
<style>
/* Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type=number] {
-moz-appearance: textfield;
}
</style>
<form id="login" style="max-width: 240px; min-width: 10%;" action="/gate/gate.login" method="post">
<div class="form-group row">
<label for="access_code">Card Access Code</label><br>
<input form="login" class="form-control" name="access_code" id="access_code" type="number" placeholder="00000000000000000000" maxlength="20" required>
</div>
<div class="form-group row">
<label for="passwd">Password</label><br>
<input id="passwd" class="form-control" name="passwd" type="password" placeholder="password">
</div>
<p></p>
<input id="submit" class="btn btn-primary" style="display: block; margin: 0 auto;" form="login" type="submit" value="Login">
</form>
<h6>*To register for the webui, type in the access code of your card, as shown in a game, and leave the password field blank.</h6>
<h6>*If you have not registered a card with this server, you cannot create a webui account.</h6>
{% endblock content %}

View File

@ -0,0 +1,92 @@
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
<style>
html {
background-color: #181a1b !important;
margin: 10px;
}
html {
color-scheme: dark !important;
}
html, body, input, textarea, select, button, dialog {
background-color: #181a1b;
}
html, body, input, textarea, select, button {
border-color: #736b5e;
color: #e8e6e3;
}
a {
color: #3391ff;
}
table {
border-color: #545b5e;
}
::placeholder {
color: #b2aba1;
}
input:-webkit-autofill,
textarea:-webkit-autofill,
select:-webkit-autofill {
background-color: #404400 !important;
color: #e8e6e3 !important;
}
::-webkit-scrollbar {
background-color: #202324;
color: #aba499;
}
::-webkit-scrollbar-thumb {
background-color: #454a4d;
}
::-webkit-scrollbar-thumb:hover {
background-color: #575e62;
}
::-webkit-scrollbar-thumb:active {
background-color: #484e51;
}
::-webkit-scrollbar-corner {
background-color: #181a1b;
}
* {
scrollbar-color: #454a4d #202324;
}
::selection {
background-color: #004daa !important;
color: #e8e6e3 !important;
}
::-moz-selection {
background-color: #004daa !important;
color: #e8e6e3 !important;
}
input[type="text"], input[type="text"]:focus, input[type="password"], input[type="password"]:focus, input[type="email"], input[type="email"]:focus {
background-color: #202324 !important;
color: #e8e6e3;
}
form {
outline: 1px solid grey;
padding: 20px;
padding-top: 10px;
padding-bottom: 10px;
}
.err-banner {
background-color: #AA0000;
padding: 20px;
margin-bottom: 10px;
width: 15%;
}
.modal-content {
background-color: #181a1b;
}
</style>
</head>
<body>
{% include "core/templates/widgets/topbar.jinja" %}
{% block content %}
<h1>{{ server_name }}</h1>
{% endblock content %}
</body>
</html>

View File

@ -0,0 +1,4 @@
{% extends "core/templates/index.jinja" %}
{% block content %}
<h1>Machine Management</h1>
{% endblock content %}

View File

@ -0,0 +1,189 @@
{% extends "core/templates/index.jinja" %}
{% block content %}
<h1>System Management</h1>
{% if error is defined %}
{% include "core/templates/widgets/err_banner.jinja" %}
{% endif %}
<h2>Search</h2>
<div class="row" id="rowForm">
{% if "{:08b}".format(sesh.permissions)[6] == "1" %}
<div class="col-sm-6" style="max-width: 25%;">
<form id="usrLookup" name="usrLookup" action="/sys/lookup.user" class="form-inline">
<h3>User Search</h3>
<div class="form-group">
<label for="usrId">User ID</label>
<input type="number" class="form-control" id="usrId" name="usrId">
</div>
OR
<div class="form-group">
<label for="usrName">Username</label>
<input type="text" class="form-control" id="usrName" name="usrName">
</div>
OR
<div class="form-group">
<label for="usrEmail">Email address</label>
<input type="email" class="form-control" id="usrEmail" name="usrEmail">
</div>
OR
<div class="form-group">
<label for="usrAc">Access Code</label>
<input type="text" class="form-control" id="usrAc" name="usrAc" maxlength="20" placeholder="00000000000000000000">
</div>
<br />
<button type="submit" class="btn btn-primary">Search</button>
</form>
</div>
{% endif %}
{% if "{:08b}".format(sesh.permissions)[5] == "1" %}
<div class="col-sm-6" style="max-width: 25%;">
<form id="shopLookup" name="shopLookup" action="/sys/lookup.shop" class="form-inline">
<h3>Shop search</h3>
<div class="form-group">
<label for="shopId">Shop ID</label>
<input type="number" class="form-control" id="shopId" name="shopId">
</div>
OR
<div class="form-group">
<label for="serialNum">Serial Number</label>
<input type="text" class="form-control" id="serialNum" name="serialNum" maxlength="15">
</div>
<br />
<button type="submit" class="btn btn-primary">Search</button>
</form>
</div>
{% endif %}
</div>
<div class="row" id="rowResult" style="margin: 10px;">
{% if "{:08b}".format(sesh.permissions)[6] == "1" %}
<div id="userSearchResult" class="col-sm-6" style="max-width: 25%;">
{% for usr in usrlist %}
<a href=/user/{{ usr.id }}><pre>{{ usr.username if usr.username is not none else "<i>No Name Set</i>"}}</pre></a>
{% endfor %}
</div>
{% endif %}
{% if "{:08b}".format(sesh.permissions)[5] == "1" %}
<div id="shopSearchResult" class="col-sm-6" style="max-width: 25%;">
{% for shop in shoplist %}
<a href="/shop/{{ shop.id }}"><pre>{{ shop.name if shop.name else "<i>No Name Set</i>"}}</pre></a>
{% endfor %}
</div>
{% endif %}
</div>
<h2>Add</h2>
<div class="row" id="rowAdd">
{% if "{:08b}".format(sesh.permissions)[6] == "1" %}
<div class="col-sm-6" style="max-width: 25%;">
<form id="usrAdd" name="usrAdd" action="/sys/add.user" class="form-inline" method="POST">
<h3>Add User</h3>
<div class="form-group">
<label for="usrName">Username</label>
<input type="text" class="form-control" id="usrName" name="usrName">
</div>
<br>
<div class="form-group">
<label for="usrEmail">Email address</label>
<input type="email" class="form-control" id="usrEmail" name="usrEmail" required>
</div>
<br>
<div class="form-group">
<label for="usrPerm">Permission Level</label>
<input type="number" class="form-control" id="usrPerm" name="usrPerm" value="1">
</div>
<br />
<button type="submit" class="btn btn-primary">Add</button>
</form>
</div>
<div class="col-sm-6" style="max-width: 25%;">
<form id="cardAdd" name="cardAdd" action="/sys/add.card" class="form-inline" method="POST">
<h3>Add Card</h3>
<div class="form-group">
<label for="cardUsr">User ID</label>
<input type="number" class="form-control" id="cardUsr" name="cardUsr" required>
</div>
<br>
<div class="form-group">
<label for="cardAc">Access Code</label>
<input type="text" class="form-control" id="cardAc" name="cardAc" maxlength="20" placeholder="00000000000000000000" required>
</div>
<br>
<div class="form-group">
<label for="cardIdm">IDm/Chip ID</label>
<input type="text" class="form-control" id="cardIdm" name="cardIdm" disabled>
</div>
<br />
<button type="submit" class="btn btn-primary">Add</button>
</form>
</div>
{% endif %}
{% if "{:08b}".format(sesh.permissions)[5] == "1" %}
<div class="col-sm-6" style="max-width: 25%;">
<form id="shopAdd" name="shopAdd" action="/sys/add.shop" class="form-inline" method="POST">
<h3>Add Shop</h3>
<div class="form-group">
<label for="shopName">Name</label>
<input type="text" class="form-control" id="shopName" name="shopName">
</div>
<br>
<div class="form-group">
<label for="shopCountry">Country Code</label>
<input type="text" class="form-control" id="shopCountry" name="shopCountry" maxlength="3" placeholder="JPN">
</div>
<br />
<div class="form-group">
<label for="shopIp">VPN IP</label>
<input type="text" class="form-control" id="shopIp" name="shopIp">
</div>
<br />
<button type="submit" class="btn btn-primary">Add</button>
</form>
</div>
<div class="col-sm-6" style="max-width: 25%;">
<form id="cabAdd" name="cabAdd" action="/sys/add.cab" class="form-inline" method="POST">
<h3>Add Machine</h3>
<div class="form-group">
<label for="cabShop">Shop ID</label>
<input type="number" class="form-control" id="cabShop" name="cabShop" required>
</div>
<br>
<div class="form-group">
<label for="cabSerial">Serial</label>
<input type="text" class="form-control" id="cabSerial" name="cabSerial">
</div>
<br />
<div class="form-group">
<label for="cabGame">Game Code</label>
<input type="text" class="form-control" id="cabGame" name="cabGame" maxlength="4" placeholder="SXXX">
</div>
<br />
<button type="submit" class="btn btn-primary">Add</button>
</form>
</div>
{% endif %}
</div>
<div class="row" id="rowAddResult" style="margin: 10px;">
{% if "{:08b}".format(sesh.permissions)[6] == "1" %}
<div id="userAddResult" class="col-sm-6" style="max-width: 25%;">
{% if usradd is defined %}
<pre>Added user {{ usradd.username if usradd.username is not none else "with no name"}} with id {{usradd.id}} and password {{ usradd.password }}</pre>
{% endif %}
</div>
<div id="cardAddResult" class="col-sm-6" style="max-width: 25%;">
{% if cardadd is defined %}
<pre>Added {{ cardadd.access_code }} with id {{cardadd.id}} to user {{ cardadd.user }}</pre>
{% endif %}
</div>
{% endif %}
{% if "{:08b}".format(sesh.permissions)[5] == "1" %}
<div id="shopAddResult" class="col-sm-6" style="max-width: 25%;">
{% if shopadd is defined %}
<pre>Added Shop {{ shopadd.id }}</pre></a>
{% endif %}
</div>
<div id="cabAddResult" class="col-sm-6" style="max-width: 25%;">
{% if cabadd is defined %}
<pre>Added Machine {{ cabadd.id }} with serial {{ cabadd.serial }}</pre></a>
{% endif %}
</div>
{% endif %}
</div>
{% endblock content %}

View File

@ -0,0 +1,175 @@
{% extends "core/templates/index.jinja" %}
{% block content %}
<script type="text/javascript">
function toggle_new_name_form() {
let frm = document.getElementById("new_name_form");
let btn = document.getElementById("btn_toggle_form");
if (frm.style['display'] != "") {
frm.style['display'] = "";
frm.style['max-height'] = "";
btn.innerText = "Cancel";
} else {
frm.style['display'] = "none";
frm.style['max-height'] = "0px";
btn.innerText = "Edit";
}
}
function toggle_add_card_form() {
let btn = document.getElementById("btn_add_card");
let dv = document.getElementById("add_card_container")
if (dv.style['display'] != "") {
btn.innerText = "Cancel";
dv.style['display'] = "";
} else {
btn.innerText = "Add";
dv.style['display'] = "none";
}
}
function prep_edit_form(access_code, chip_id, idm, card_type, u_memo) {
ac = document.getElementById("card_edit_frm_access_code");
cid = document.getElementById("card_edit_frm_chip_id");
fidm = document.getElementById("card_edit_frm_idm");
memo = document.getElementById("card_edit_frm_memo");
if (chip_id == "None" || chip_id == undefined) {
chip_id = ""
}
if (idm == "None" || idm == undefined) {
idm = ""
}
if (u_memo == "None" || u_memo == undefined) {
u_memo = ""
}
ac.value = access_code;
cid.value = chip_id;
fidm.value = idm;
memo.value = u_memo;
if (card_type == "AmusementIC") {
cid.disabled = true;
fidm.disabled = false;
} else {
cid.disabled = false;
fidm.disabled = true;
}
}
</script>
<h1>Management for {{ username }}&nbsp;<button onclick="toggle_new_name_form()" class="btn btn-secondary" id="btn_toggle_form">Edit</button></h1>
{% if error is defined %}
{% include "core/templates/widgets/err_banner.jinja" %}
{% endif %}
{% if success is defined and success == 2 %}
<div style="background-color: #00AA00; padding: 20px; margin-bottom: 10px; width: 15%;">
Update successful
</div>
{% endif %}
<form style="max-width: 33%; display: none; max-height: 0px;" action="/user/update.name" method="post" id="new_name_form">
<div class="mb-3">
<label for="new_name" class="form-label">New Nickname</label>
<input type="text" class="form-control" id="new_name" name="new_name" aria-describedby="new_name_help">
<div id="new_name_help" class="form-text">Must be 10 characters or less</div>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<p></p>
<h2>Cards <button class="btn btn-success" id="btn_add_card" onclick="toggle_add_card_form()">Add</button></h2>
{% if success is defined and success == 3 %}
<div style="background-color: #00AA00; padding: 20px; margin-bottom: 10px; width: 15%;">
Card added successfully
</div>
{% endif %}
<div id="add_card_container" style="display: none; max-width: 33%;">
<form action="/user/add.card" method="post", id="frm_add_card">
<label class="form-label" for="card_add_frm_access_code">Access Code:</label>
<input class="form-control" name="add_access_code" id="card_add_frm_access_code" maxlength="20" type="text" required aria-describedby="ac_help">
<div id="ac_help" class="form-text">20 digit code on the back of the card.</div>
<button type="submit" class="btn btn-primary">Add</button>
</form>
<br>
</div>
<ul style="font-size: 20px;">
{% for c in cards %}
<li>{{ c.access_code }} ({{ c.type}}): {{ c.status }}&nbsp;<button onclick="prep_edit_form('{{ c.access_code }}', '{{ c.chip_id}}', '{{ c.idm }}', '{{ c.type }}', '{{ c.memo }}')" data-bs-toggle="modal" data-bs-target="#card_edit" class="btn btn-secondary" id="btn_edit_card_{{ c.access_code }}">Edit</button>&nbsp;{% if c.status == 'Active'%}<button class="btn-warning btn">Lock</button>{% elif c.status == 'Locked' %}<button class="btn-warning btn">Unlock</button>{% endif %}&nbsp;<button class="btn-danger btn">Delete</button></li>
{% endfor %}
</ul>
<h2>Reset Password</h2>
{% if success is defined and success == 1 %}
<div style="background-color: #00AA00; padding: 20px; margin-bottom: 10px; width: 15%;">
Update successful
</div>
{% endif %}
<form style="max-width: 33%;" action="/user/update.pw" method="post">
<div class="mb-3">
<label for="current_pw" class="form-label">Current Password</label>
<input type="password" class="form-control" id="current_pw" name="current_pw">
</div>
<div class="mb-3">
<label for="password1" class="form-label">New Password</label>
<input type="password" class="form-control" id="password1" name="password1" aria-describedby="password_help">
<div id="password_help" class="form-text">Password must be at least 10 characters long, contain an upper and lowercase character, number, and special character</div>
</div>
<div class="mb-3">
<label for="password2" class="form-label">Retype New Password</label>
<input type="password" class="form-control" id="password2" name="password2">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
{% if arcades is defined and arcades|length > 0 %}
<h2>Arcades</h2>
<ul>
{% for a in arcades %}
<li><h3>{{ a.name }}</h3>
{% if a.machines|length > 0 %}
<table>
<tr><th>Serial</th><th>Game</th><th>Last Seen</th></tr>
{% for m in a.machines %}
<tr><td>{{ m.serial }}</td><td>{{ m.game }}</td><td>{{ m.last_seen }}</td></tr>
{% endfor %}
</table>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
<div class="modal fade" id="card_edit" tabindex="-1" aria-labelledby="card_edit_label" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="card_edit_label">Edit Card</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form action="/user/edit.card" method="post" id="frm_edit_card">
<label class="form-label" for="card_edit_frm_access_code">Access Code:</label>
<input class="form-control" readonly name="add_access_code" id="card_edit_frm_access_code" maxlength="20" type="text" required aria-describedby="ac_help">
<div id="ac_help" class="form-text">20 digit code on the back of the card. If this is incorrect, contact a sysadmin.</div>
<label class="form-label" for="card_edit_frm_memo" id="card_edit_frm_memo_lbl">Memo:</label>
<input class="form-control" aria-describedby="memo_help" name="add_memo" id="card_edit_frm_memo" maxlength="16" type="text">
<div id="memo_help" class="form-text">Must be 16 characters or less.</div>
<label class="form-label" for="card_edit_frm_idm" id="card_edit_frm_idm_lbl">FeliCa IDm:</label>
<input class="form-control" aria-describedby="idm_help" name="add_felica_idm" id="card_edit_frm_idm" maxlength="16" type="text">
<div id="idm_help" class="form-text">8 bytes that uniquly idenfites a FeliCa card. Obtained by reading the card with an NFC reader.</div>
<label class="form-label" for="card_edit_frm_chip_id" id="card_edit_frm_chip_id_lbl">Mifare UID:</label>
<input class="form-control" aria-describedby="chip_id_help" name="add_mifare_chip_id" id="card_edit_frm_chip_id" maxlength="8" type="text">
<div id="chip_id_help" class="form-text">4 byte integer that uniquly identifies a Mifare card. Obtained by reading the card with an NFC reader.</div>
</form>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" form="frm_edit_card">Edit</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endblock content %}

Some files were not shown because too many files have changed in this diff Show More