176 Commits
Author SHA1 Message Date
Emi Midnight 87661cf871 check for both null AND undefined in stream writer (#44)
This fixes IDZ support when minime is run using a newer LTS version of
node js.
(Streams use null for the error argument now for write and end
callbacks)
2024-03-19 11:59:08 +01:00
Tau d6d98aa561 v016 2021-02-04 17:03:11 -05:00
Tau 2c78e6646a Fix trailing comma in SQL 2021-02-04 17:02:23 -05:00
Tau 0f8010ae8d v015 2021-02-04 16:47:21 -05:00
Tau cb5db5e55f idz: Drop idz_team_auto_uq constraint 2021-02-04 16:41:42 -05:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 a99503a86d idz: Fix team message encoding 2021-02-04 16:32:03 -05:00
Tau 37660a382a v014 2021-02-03 18:51:28 -05:00
Tau e43faeff10 idz: Fix SqlTeamRepository 2021-02-03 23:43:46 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 2189132753 idz: Fix logic in SqlStoryRepository 2021-02-03 23:43:45 +00:00
Tau 85aad3377e idz: Fix SqlStampsRepository 2021-02-03 23:43:45 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 2f5a63858f idz: SqlSettingsRepository: Persist driving_style 2021-02-03 23:43:45 +00:00
Tau dbffb15779 idz: Fix SqlProfileRepository 2021-02-03 23:43:45 +00:00
Tau 1f544ddb7e idz: Fix SqlMyCharaRepository 2021-02-03 23:43:45 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 7f47315130 idz: Save weekly missions progress 2021-02-03 23:43:45 +00:00
Tau afe9f27c25 idz: Fix SqlWeeklyMissionsRepository 2021-02-03 23:43:44 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 f8ca0bdcd3 idz: Fix remaining encoder mappings 2021-02-03 23:43:44 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 98caa9248b idz: Split/fix loadServerList encoder 2021-02-03 23:43:44 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 0236d44466 idz: Split/fix loadRewardTable encoder 2021-02-03 23:43:44 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 4c0e933ea4 idz: Split/fix loadGhost encoder 2021-02-03 23:43:44 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 8b284107b1 idz: Split/fix "generic" response encoding 2021-02-03 23:43:44 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 2c31bd0c47 idz: Fix _team encoders 2021-02-03 23:43:44 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 7b329bc37c idz: Fix unknownA_2 message length rounding 2021-02-03 23:43:43 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 0937c0fadf idz: Fix saveGarage processing 2021-02-03 23:43:43 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 ea92e4e981 idz: Fix loadProfile codecs 2021-02-03 23:43:43 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 1cfd577f56 idz: Fix loadGeneralReward codecs 2021-02-03 23:43:43 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 b7187fe0ed idz: Fix updateStoryClearNum codecs 2021-02-03 23:43:43 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 60c089a810 idz: Fix saveTimeAttack codecs 2021-02-03 23:43:43 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 377096a077 idz: Fix loadTopTen 2021-02-03 23:43:43 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 87de79ef12 idz: Fix loadTeamRanking mapping 2021-02-03 23:43:43 +00:00
Tau f1386f8e3d idz: Split/fix loadConfig messages 2021-02-03 23:43:42 +00:00
Tau 6d581c114d idz: Split/fix load2on2 messages 2021-02-03 23:43:42 +00:00
Tau 9570349565 idz: Plumb clientHello into request dispatcher 2021-02-03 23:43:42 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 206d12742a idz: Ignore ServerBox traffic 2021-02-03 23:43:42 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 f2434cef7e idz: Add idz2 encoders 2021-02-03 23:43:42 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 2474879c89 idz: Add idz2 decoders 2021-02-03 23:43:42 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 4c36e0abf0 idz: Add LoadStockerResponse.myChara field 2021-02-03 23:43:42 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 ccd5fd72c8 idz: Add field "c" to story cell tuple 2021-02-03 23:43:41 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 46499a5425 idz: Add UpdateExpedition command 2021-02-03 23:43:41 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 8562da15b6 idz: Add SaveStockerRequest.myChara 2021-02-03 23:43:41 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 8195e9f937 idz: Add stamps to SaveStockerRequest 2021-02-03 23:43:41 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 9f57ff1342 idz: Add idz_my_chara table 2021-02-03 23:43:41 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 a7b7a01685 idz: Add idz_weekly_missions table 2021-02-03 23:43:41 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 8ada783f7c idz: Add stamps tables 2021-02-03 23:43:41 +00:00
b166fe48b91eb09f279b79a4ecf57b2f8f0d3705 f79c4a4370 idz: Add idz_settings.driving_style column 2021-02-03 23:43:41 +00:00
Tau f3be39823a idz: Explicitly split team msg encoders 2021-02-03 23:43:40 +00:00
Tau 03d0e87b55 idz: Push story geometry down into codecs
Sparsify the data model returned from the repository layer. Different
versions of the game can have drastically different dimensions for the
story data grid, so handling those dimensions is best done in the
encoders themselves.
2021-02-03 23:43:40 +00:00
Tau efcc1caa76 idz: Add explicit CreateProfileResponse 2021-02-03 23:43:40 +00:00
Tau 2a546c0d9d idz: Generalize msg00AD
The purpose of this request is unknown, but its message code
changed in idz2.
2021-02-03 23:43:40 +00:00
Tau 63627eaf78 idz: Combine loadConfig requests 2021-02-03 23:43:40 +00:00
Tau ed7c461fe1 idz: Dispatch on declared protocol version 2021-02-03 23:43:39 +00:00
Tau 75ac6e03f2 idz: Support multiple major versions
Doesn't actually add support for any additional versions, but it
does lay the groundwork.
2021-02-03 23:43:39 +00:00
Tau 2f2dc82b5a idz: Add type defs for common net comms context 2021-02-03 23:43:38 +00:00
Tau 198256e6ff idz: Add implicit version field to requests 2021-02-03 23:43:38 +00:00
Tau eb276128c2 sql: Add "maintenance transaction" system
SQLite's ALTER TABLE functionality leaves a lot to be desired, so
in practice the way you alter SQLite schema is to create a new
table, migrate the data into it, then drop the old table. However,
this is potentially complicated by the presence of foreign key
constraints.

In order to resolve the foreign key constraint issue we add a
`maintenance()` method to the `DataSource` abstraction layer. This
operates similarly to the existing `transaction()` method, but the
SQLite implementation of this method defers enforcement of foreign
keys until the transaction is about to commit.
2021-02-03 23:43:38 +00:00
Tau 6090a60467 sql: Drive-by method documentation 2021-02-03 23:43:38 +00:00
Tau 2099622b0f sql: Throw conventionally... 2021-02-03 23:43:38 +00:00
Tau 9494f3026b checkdb: Use single transaction 2021-02-03 23:43:38 +00:00
Tau f7ceb6bcda idz: Prettier compliance 2021-02-03 23:43:38 +00:00
Tau 04dac7279e idz: Remove some dead code 2021-02-03 23:43:37 +00:00
Tau 8c164b6130 idz: Use string I/O helpers 2021-02-03 23:43:37 +00:00
Tau 27b229e1c2 idz: Remove iffy redundant NUL termination op 2021-02-03 23:43:37 +00:00
Tau c877d9d214 idz: Fix apparent buffer stride bug 2021-02-03 23:43:37 +00:00
Tau 636789b8bf idz: Delete dead imports 2021-02-03 23:43:37 +00:00
Tau 48aeb1c9d2 types: Declare ON CONFLICT ... DO NOTHING clause 2021-02-03 23:43:36 +00:00
Tau 5f09c00eb4 Add explicit vscode formatter conf 2021-02-03 23:43:36 +00:00
Tau bf28710000 v013 2020-12-15 18:05:28 -05:00
Tau 8c61bda865 Apply Prettier to CHANGELOG 2020-12-15 18:05:09 -05:00
Felix 1fa268e6ba chunithm: stub GetUserFavoriteMusicApi 2020-12-15 22:59:23 +00:00
e76cd2ec3a63f11710dc9fa0bc5b66176521af0a 4fe69077fc chuni: limit response size of getUserActivity 2020-11-08 23:54:28 -08:00
Tau 4f5eb97309 Fix npm audit noise 2020-09-01 01:10:32 +00:00
Tau 122c397b59 idz: Add utilities for binary I/O 2020-09-01 01:10:32 +00:00
Tau d3984dcd5d idz: Use Switchboard config for some ports 2020-09-01 01:10:31 +00:00
Tau 4abc04e9f6 idz: Port userdb/ onto common/ 2020-09-01 01:10:31 +00:00
Tau c01dbb50f7 idz: Factor out common TCP obfuscation 2020-09-01 01:10:31 +00:00
Tau fa5ed284e1 util: Add promise-based stream wrapper
You'd think something like this would exist on npm somewhere.
2020-09-01 01:10:31 +00:00
Tau 76583f17ce idz: Fix userdb msg 00AD block alignment 2020-09-01 01:10:31 +00:00
Tau f959993197 idz: Hoist userdb code 2020-09-01 01:10:31 +00:00
Tau 6dceb7c48a Fix CHANGELOG attribution 2020-08-19 18:44:29 -04:00
Tau 1869c37949 Add CHANGELOG up to v012 2020-08-17 21:26:40 -04:00
e76cd2ec3a63f11710dc9fa0bc5b66176521af0a 1d8fd582c9 chuni: sort user activity response 2020-08-17 21:11:00 -04:00
e76cd2ec3a63f11710dc9fa0bc5b66176521af0a 9b22318934 chuni: Fix upsert with an empty userRecentRating 2020-08-09 01:18:31 -07:00
e76cd2ec3a63f11710dc9fa0bc5b66176521af0a 71a7cd7eed chuni: Correctly implement recent rating requests 2020-08-02 12:22:01 -04:00
5ebef5658c5fcb6f5a62e75318bdc7fad90b21bc 6c22c4287e chuni: implement UserCharge APIs, add table for purchased tickets 2020-08-02 11:24:21 -04:00
5ebef5658c5fcb6f5a62e75318bdc7fad90b21bc a8539b475e chuni: enable ticket system 2020-08-02 11:24:21 -04:00
340f81fd089d20627f63fce796e939b691538bc5 abcccfb9a0 chuni: add new felica lookup mode 2020-08-01 12:55:25 -04:00
643161de4220f1bc925fbb05bde805fd8ec95c63 b2288b9b8f idz: Fix team-leader, add leader name and shop name 2020-08-01 12:50:09 -04:00
cd6c1fd2d946dff292b101ca3305fc7f31d2dce5 e61b103221 chuni: Changes to make compatible with old CHUNITHM 2020-08-01 12:41:55 -04:00
Tau 94ed27503a Add tests to CI build 2020-08-01 12:38:47 -04:00
Tau ad1b48c210 Fix? long-broken test 2020-08-01 12:38:47 -04:00
Tau 75b35e09e2 Add GitLab CI 2020-08-01 12:38:47 -04:00
cd6c1fd2d946dff292b101ca3305fc7f31d2dce5 834804b2ea chuni: fix lost scores for real 2020-04-19 23:03:31 +08:00
Matt Bilker ac9cf50d28 chuni: Fix maintenance timer 2020-03-22 23:16:33 +00:00
Matt Bilker cb4743f86f chuni: Add remaining Chunithm Amazon event IDs 2020-03-22 23:16:30 +00:00
Matt Bilker cef7e99e46 chuni: Put event IDs in separate static file 2020-03-22 23:15:01 +00:00
Matt Bilker 4b007b446d chuni: Enable all events 2020-03-22 23:15:00 +00:00
Matt Bilker 4c5122d163 chuni: Prevent recursive item calls if user has more than 600 items 2020-03-22 23:15:00 +00:00
Tau 01198d546a sql: Add NULL support to database wrappers 2020-03-22 22:38:54 +00:00
07bea96aed831abce88a9591d3ff86af19babd79 bc5f26d768 chuni: Add the api endpoint use in older chunithm 2020-01-30 18:26:36 +00:00
07bea96aed831abce88a9591d3ff86af19babd79 9a652e1ace chuni: lower max result per page to prevent game fail to read content. Fix #6 2020-01-30 16:52:31 +00:00
Tau 7b21a0bdae idz: Implement multi-page Garage fetch 2020-01-25 19:26:42 -05:00
Tau 5311618d1d Fix new DB initialization, AGAIN. 2020-01-05 10:45:40 -05:00
Tau 37ce14dda7 Switch to @decafcode/sqlite for database access 2020-01-05 10:00:12 -05:00
Tau 0d8d5845c9 schema: Fix M0011 for newly-created databases 2019-12-09 23:06:20 -05:00
Tau 143f534ba1 npm audit 2019-12-09 22:55:25 -05:00
Tau 4db3d94ee8 idz: Un-hardcode result limit in repo layer
Minor design nit but I should probably take my own code review
advice.
2019-11-30 14:13:49 -05:00
Tau e195cf77cd idz: Wire up TA topTen team results 2019-11-30 14:05:36 -05:00
Tau ac9622f1bd idz: Extend Time Attack topTen result set 2019-11-30 14:05:14 -05:00
Tau e8535b1a80 idz: Label additional team info in topTen response 2019-11-30 13:35:37 -05:00
07bea96aed831abce88a9591d3ff86af19babd79 d81b763d9b chuni: save and load user course result 2019-11-28 11:19:39 -05:00
Matt Bilker 1c5726be4c chunithm: Save and load user duel list 2019-11-28 07:04:33 +00:00
85be42b2ce67a8e34b32793360824b056c0c2444 6da5abcefc IDZ: Add support for player auras 2019-11-27 02:22:03 +00:00
07bea96aed831abce88a9591d3ff86af19babd79 c5e39f104f chuni: Add recent rating response to fix rating calculation 2019-11-27 01:49:05 +00:00
Tau 60d2f7cfd3 Fix working directory issues in startup batch file 2019-11-09 14:03:16 -05:00
Tau 0ac8b44ce0 idz/sql/teamMember.ts: Fix another ignored result set
That should cover all the leftover uses of forUpdate() ...
2019-11-09 14:00:24 -05:00
Tau 59fec74d08 Add Project DIVA workaround to startup script 2019-11-09 13:42:40 -05:00
Tau 52f142a2b9 Bump deps so npm audit will shut up 2019-11-08 19:46:19 -05:00
Tau 3a7132d276 Drop ts-node
Stopped using it a while ago, `tsc --watch` is more convenient.
2019-11-08 19:43:59 -05:00
Tau 4f89894fd1 chunithm: Raise POST payload size limit 2019-11-08 19:39:03 -05:00
Tau c50a0093fd idz/sql/teamReservation.ts: Fix ignored result set
Apparently the latest SQLite driver doesn't like it if you do that.
2019-11-06 17:29:07 -05:00
Tau 1b41b82745 Tiny cosmetic change 2019-11-06 17:29:07 -05:00
Tau d1d63e1ef9 chunithm: Initial implementation code drop
No cleaner development history than this exists. It's difficult to
methodically design something when you don't yet know what it is
you're designing.
2019-11-06 17:29:07 -05:00
Tau beb9144e9b sql/util.ts: Add a simple SQL record mapper
sql-bricks can write queries for us, and the mapper handles type
conversions to and from SQL-friendly strings.
2019-11-06 17:29:07 -05:00
Tau d58f4bed8f Add npm "snakeCase" dep 2019-11-06 17:29:07 -05:00
Tau 125e4d8ca7 sql/sqlite.ts: Fix NULL handling 2019-11-06 17:29:07 -05:00
Tau b096a79f95 Simplify aime_player schema 2019-11-06 17:29:07 -05:00
Tau 04b0ccfda3 Force UTC for all internal date processing 2019-11-06 17:29:07 -05:00
Tau 82bd9ad7bb Additional assorted layering cleanups 2019-11-06 17:29:07 -05:00
Tau 6a4ac795b2 Heal the layer break around the Id<> type 2019-11-06 17:29:07 -05:00
Tau 3a71f5054c Bump to Node v12
This is the LTS version now, might as well use it.
2019-11-06 17:29:07 -05:00
Tau 650e05310d Flip-flop on debug logging code style 2019-11-06 17:29:07 -05:00
Tau 3aee0f611c sql/sqlite.ts: Make FK constraints do something
They don't by default, because they didn't in older versions of
SQLite and some older applications might rely on that. You have to
send an explicit pragma prior to opening a transaction to make them
take effect.
2019-11-06 17:29:07 -05:00
Tau fa45169b96 Enhance vscode config files 2019-11-06 17:29:07 -05:00
Tau 07222c9ae9 Add stub README.md 2019-11-06 17:29:01 -05:00
Tau 38298a2840 Add Windows batch file to init and start the server 2019-11-06 17:28:31 -05:00
Tau 2440bedf4a Add Markdown to .editorconfig 2019-11-06 17:28:31 -05:00
Tau 9acc375d99 Tweak switchboard defaults 2019-11-06 17:28:31 -05:00
Tau 799ace06d7 Wire up schema automation 2019-11-06 17:28:31 -05:00
Tau 6d91659b24 Add automated schema management 2019-11-06 17:28:31 -05:00
Tau 151dd41e64 sql: Add db compaction method 2019-11-06 17:28:31 -05:00
Tau 13e234a68c sql: Add raw SQL interface method 2019-11-06 17:28:31 -05:00
Tau 839debca97 Bump declared TypeScript dep 2019-11-06 17:28:31 -05:00
Tau abda3d5279 Switch to npm from Yarn
Yarn is the better package manager IMO, but npm comes with node and
using it does make the end-user installation experience a little
simpler as a result.
2019-11-06 17:28:31 -05:00
Tau 44d8c0a824 Tweak package.json lifecycle scripts 2019-11-06 17:28:31 -05:00
Tau f348feff62 sql/sqlite.ts: Fix type check 2019-11-06 17:28:30 -05:00
Tau 6603b6aaa6 Rename database file 2019-11-06 17:28:30 -05:00
Tau a96924c24f Abbreviate startup msg logging 2019-11-06 17:28:30 -05:00
Tau aa53fd8146 allnet: Add some environment fallbacks 2019-11-06 17:28:30 -05:00
Tau bbb5a3b408 aimedb/pipeline.ts: Silence a deprecation warning 2019-11-06 17:28:30 -05:00
Tau 072d70b42a Migrate to SQLite3
We'll keep the door open for side-by-side support of Postgres in
the background, but due to SQLite's type system quirks we cannot
use the same DDL for both databases, so we would have to maintain
two sets of DDL (schema init and schema migration scripts) at once.

Interested future contributors can shoulder this maintenance burden
if they so choose.
2019-11-06 17:28:30 -05:00
Tau f9970fa81c Force explicit conversion of rows fetched from SQL DB
Everything is a scalar value now, and while this does involve more
boilerplate it will also catch type conversion issues like
"1" + 0 === "10" in the future.
2019-11-06 17:28:30 -05:00
Tau 30d42bd832 Store array colums as CSV text
This is in preparation for a transition to SQLite, which sadly does
not support array-valued columns (or indeed have much of a type
system in general).
2019-11-06 17:28:30 -05:00
Tau 3e6b4f4798 Add an SQL DB abstraction layer
Because what self-respecting Enterprise(R) project doesn't have
one of those?
2019-11-06 17:28:30 -05:00
Tau 2ccd9dc382 types/sql-bricks-postgres: Add initial typings
This commit declares and type-checks the following clauses:

* limit().offset() chains on SelectStatement
* onConflict().doUpdate() chains on InsertStatement

We need to flesh out the other possibilities for onConflict() in
particular before all these changes get submitted as an upstream
PR.
2019-11-06 17:28:30 -05:00
Tau 691841187b types/sql-bricks: Make Statement chains polymorphic
This will allow us to cleanly extend these interfaces with
additional clauses understood by Postgres, which we can then chain
without having to redeclare the entire interface.
2019-11-06 17:28:30 -05:00
Tau a71a73e324 types/sql-bricks: Export Statement interfaces
We'll need access to these statements so that we can extend them.
2019-11-06 17:28:30 -05:00
Tau 078cb84b35 types/sql-bricks: Export SqlBricksFn type
We'll need to export this in order to define the ops exported from
sql-bricks-postgres in terms of the base sql-bricks exports.
2019-11-06 17:28:30 -05:00
Tau 775584a645 types/sql-bricks: Fixup forked typings
Wrap everything in a module instead of a namespace. I'm still not
entirely clear on what the distinction is but the forked typings
don't seem to get applied without this change.
2019-11-06 17:28:30 -05:00
Tau 384212ce2e Locally fork sql-bricks types 2019-11-06 17:28:30 -05:00
Tau 1567b689b9 Add local typings directory to project 2019-11-06 17:28:30 -05:00
Tau 254478e20c Use workspace TypeScript ver in vscode 2019-11-06 17:28:30 -05:00
Tau 4dd3f39472 Fix sql-bricks import style 2019-11-06 17:28:30 -05:00
Tau 49802602bf Minor package.json cleanups 2019-11-06 17:28:30 -05:00
Tau 6ab2112ea9 Stop using SQL namespaces ("schemas")
This is in preparation for a transition to SQLite. SQLite does have
a similar concept called "attached databases", but it's kind of a
pain to deal with if you're casually browsing a database from the
`sqlite3` CLI so we'll just use some slightly uglier explicitly
namespaced table name identifiers instead.
2019-11-06 17:28:30 -05:00
Tau 6be23145d8 Crack Diva handlers 2019-11-06 17:28:30 -05:00
Tau cc0cd06c92 Crack Chunithm handlers 2019-11-06 17:28:30 -05:00
Tau 9697b4b06e Use debug package for logging 2019-11-06 17:28:30 -05:00
Tau db260d5f2f idz/decoder: Drop RequestCode typedef
This is just boilerplate for its own sake.
2019-11-06 17:28:30 -05:00
Tau 3f62f1683b idz/handler/createTeam.ts: Fix incomplete refactor
This looks like duplicated code that wasn't deleted after being
factored out into a helper proc.
2019-11-06 17:28:30 -05:00
Tau 7118a72b3b Use esModuleInterop 2019-11-06 17:28:30 -05:00
Tau a5256c97d3 Fix SQL column capitalization 2019-11-06 17:28:30 -05:00
Tau 84023e2eb8 chunithm.ts: Beat compression m/ware with a wrench
Force unconditional compression of responses harder.
2019-11-06 17:28:30 -05:00
Tau 39849483c8 Clean up obsolete state files
Hostname is configured from .env now along with everything else.
JSON state storage backend has been abandoned. We might add an
SQLite backend to go with the current Postgres backend at some
point though; SQLite even supports window functions these days.
2019-11-06 17:28:30 -05:00
Tau 490264b35a Check in a canonical PKI
For some reason initpki doesn't generate a set of certs that are
acceptable to the P-Ras core, even though I never modified it.
Maybe some defaults changed in a recent release of OpenSSL or
something.

Well, to make everybody's life easier let's all just use the same
private keys for everything. It's not like it matters.
2019-11-06 17:28:30 -05:00
Tau 4298810728 aimedb: Defensively close connection after a Goodbye
We should probably also add request timeouts and such.
2019-11-06 17:28:30 -05:00
Tau 3fea2dae56 allnet.ts: Restrict request filter to PowerOn
PowerOn uses HTTP slightly incorrectly, but other endpoints use
HTTP incorrectly in different ways (or maybe even correctly, who
knows). Scope the current request interceptor to just PowerOn.
2019-11-06 17:28:30 -05:00
Tau 4a2b3f51a4 allnet.ts: Rename from startup.ts 2019-11-06 17:28:30 -05:00
603 changed files with 20787 additions and 8076 deletions
+3
View File
@@ -9,6 +9,9 @@ insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 79
[*.md]
indent_size = 2
[*.sql]
indent_size = 4
+1 -3
View File
@@ -1,6 +1,4 @@
bin/
data/
node_modules/
pki/
state/
.env
hostname.txt
+16
View File
@@ -0,0 +1,16 @@
image: node:12-buster
cache:
paths:
- node_modules/
stages:
- build
do_build:
stage: build
script:
- npm install
- npm run build
- npm test
+7
View File
@@ -0,0 +1,7 @@
{
"recommendations": [
"EditorConfig.EditorConfig",
"esbenp.prettier-vscode",
"mikestead.dotenv"
]
}
+8
View File
@@ -0,0 +1,8 @@
{
"editor.formatOnSave": true,
"editor.rulers": [79],
"typescript.tsdk": "node_modules/typescript/lib",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
+70
View File
@@ -0,0 +1,70 @@
# CHANGELOG
## v016
- IDZ: Fix SQL syntax error (Tau)
## v015
- IDZ: Team fixes (BemaniWitch)
## v014
- IDZ: Add support for Initial D v2.1 (BemaniWitch)
## v013
- IDZ: Miscellaneous refactoring (Tau)
- Chunithm: Add stubs to support Amazon Plus (Felix)
## v012
- Chunithm: Fix large data loads (NeumPhis)
- Chunithm: Add some support for older versions (NeumPhis)
- Chunithm: Fix large data loads (Felix)
- Chunithm: Enable all events (Felix)
- Chunithm: Remove server maintenance period (Felix)
- Chunithm: Fix data loss (esterTion)
- Chunithm: Add some support for older versions (esterTion)
- IDZ: Team-related fixes (Emi Midnight)
- Add support for newer AIME card services (cxm)
- Chunithm: Enable ticket system (Rob)
- Chunithm: Implement Recent Rating feature (seika1)
- Chunithm: Fix User Activity feature (seika1)
## v011
- IDZ: Fix loading of >10 cars in garage (Tau)
## v010
- Chunithm: Bug fix: Fix new database creation and repair affected databases. Again. (Tau)
Switch to a different SQLite native code extension. C compiler is no longer required. (Tau)
## v009
- IDZ: Display team banners in attract loop rankings (Tau)
- Chunithm: Bug fix: Fix new database creation and repair affected databases (Tau)
## v008
- Implement auras in IDZ (BemaniWitch)
- Fix user ratings (NeumPhis)
- Load and save course results (NeumPhis)
- Load and save user duel list (Felix)
## v007
- Fix IDZ team creation (Tau)
- Fix startup script working directory (Tau)
## v006
- Fix Chunithm HTTP POST size limit (Tau)
## v005
- Initial public release (Tau)
- Chunithm v1.30.00 Amazon
- Initial D Zero v1.21.00
- Initial D Zero v1.31.00
+3
View File
@@ -0,0 +1,3 @@
# MiniMe
A simple self-hosted ALLNET/AiMe server written in TypeScript, running on Node.js, that stores its data in an SQLite database.
+3
View File
@@ -1,5 +1,8 @@
#!/bin/sh
# This shell script documents the process that was used to generate our fake
# P-Ras PKI. It should not need to be run again under normal circumstances.
set -e
D=`dirname $0`
+7796
View File
File diff suppressed because it is too large Load Diff
+21 -16
View File
@@ -1,49 +1,54 @@
{
"name": "knuckles",
"name": "minime",
"version": "0.1.0",
"description": "Test server",
"main": "src/index.js",
"private": true,
"dependencies": {
"@decafcode/sqlite": "^2.0.0",
"compression": "^1.7.3",
"date-fns": "^1.30.1",
"debug": "^4.1.1",
"dotenv": "^7.0.0",
"express": "^4.16.4",
"iconv-lite": "^0.4.24",
"morgan": "^1.9.1",
"multiparty": "^4.2.1",
"pg": "^7.10.0",
"raw-body": "^2.3.3",
"sql-bricks-postgres": "^0.5.0"
},
"scripts": {
"build": "tsc",
"test": "jest",
"start": "ts-node src/index.ts"
"snake-case": "^2.1.0",
"sql-bricks-postgres": "^0.5.0",
"supports-color": "^7.1.0"
},
"devDependencies": {
"@types/compression": "^0.0.36",
"@types/debug": "^4.1.5",
"@types/dotenv": "^6.1.1",
"@types/express": "^4.16.1",
"@types/jest": "^24.0.11",
"@types/multiparty": "^0.0.32",
"@types/node": "^11.11.3",
"@types/pg": "^7.4.14",
"@types/sql-bricks": "^2.0.1",
"jest": "^24.5.0",
"jest-haste-map": "^24.5.0",
"@types/node": "^12.11.7",
"jest": "^26.4.2",
"jest-haste-map": "^26.3.0",
"jest-resolve": "^24.5.0",
"prettier": "^1.16.4",
"ts-jest": "^24.0.0",
"ts-node": "^8.0.3",
"typescript": "^3.3.3333",
"ts-jest": "^26.3.0",
"typescript": "^3.8.3",
"utility-types": "^3.6.1"
},
"scripts": {
"build": "tsc",
"prepare": "tsc",
"test": "jest",
"watch": "tsc --watch"
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node"
},
"prettier": {
"trailingComma": "es5"
},
"engines": {
"node": ">=12"
}
}
+16
View File
@@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAML2GPUuzv2N4bYC
xtc5bZSzolHFWdCUbP+whjr3K98FOLnYeoi7mtUSUUYOW8wIqy6WM3c4c0Bp7FcQ
LnZ0zWMm1TfLGHZzZmk5n7Iv6HDPr3ehDgbWLnOpRrVqZDxpAGD2vQb4p2DW4I2x
GUqnqDa++C8dH/0lXqE6cqwGXNGtAgMBAAECgYEAizgPhG4Dk55QkpeTBDfXH3vT
Ko9B3qdO2ptkjxDX/C8PXe7POXq2SvcEoIE6Xg3Gp8LMR5NBAbth8J32f9JSov3P
SiGCGno4k2i2s3jRuVg76FGLDsZH/N1dt4h78VnW0VlInwaM6bQv3zp0u8rXVk/P
wpYh9AGmquBJS3VYUcECQQD0PDRe28SrhollygGZSO321rYbYhoTIstDXZWyQ/y/
PWKNwNHcYTHIVGmTrJx2AJUyr1tJhwjiOwlsI5Y1Q4/9AkEAzFpFPcs1r/xgSFxB
eYrcNseWYbVajtVxG9t57sayaEQbH2UMNA2vqSYK/nU6oJhj5eLRIsPHlA5ZbIiZ
rvc/cQJAKS0RQ0DX+ncXKQMSm+4wuGHgl+NFNB60mCnp+AEAVpmZyP5OI1J7myOo
HQ6H3lkgzkfEIzRR6ho773BcfaRjXQJAfS4nEE11G9ML4AezjBLGB0CIHF6NlMWn
PhtaPCy3iSt/OeIacaCYpJNLVMjXGx1+xIoG9rbbgRSxLs0W55lJ4QJBALOUVcNw
GKEJdxhIkA8iuUlEyGpKluAgHUNOOKvC3ogRoB0OyH+If/9o8wWDfxgexgM0zGBc
u178W9XDW+IijDA=
-----END PRIVATE KEY-----
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+28
View File
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDeHEJ2L+FXrcdW
G8LNZ/WknTzE9lsB4I1Gb7LcrXi/Yq+rEnF8iru2h4j1icmDSuwLBS//qENO2LfE
+iU1zmtlhjAfGv8SYSXg1ssAbB6Sv4vvz0U0pNCAvnJ/K8UN7kAXHB1rw5dRvO47
A+M1NO8S6pYTL5jJhK+BaNVmP6w9eOQ4c+0ZvKFOEX0E/uy1qcUuS386ZcPFRxRF
vErUo8WjyMAwV9HZoVa3h3tAywKfDrYjF8DtdNG93G3igzpv4Iro4AuESrPE3t4x
5uiO4UxwUObolkVsmOAzo7g1QiEi0wlHTQX1ZH3dXgaRAF5vv/V02ZAnmI5keXfP
DDIqURf3AgMBAAECggEAHQD3tNM/y+FHfHkXkRcYPqzBuL6q163pBN+lLagBcoyC
gAZih27eYFGGkvmxNNHdzPqab/oa//rQ1IoNvd78qz9AnW87C71f4uJpk96Kh4M3
1NLuKJe1GnrEHNMsYktQVzQ2q1HZOrU/LrmtO89zwLadblfyza3j9TQpWbbK2SPn
zVFlBZazX5s5APHgI9PymjPkwC9jsUbSrpe816W9KT8n8cG/CdS7relOXic5PWVL
zgWQdCFMhY/aHFbLWM7RIkQTdX0GopJWgleLQqryKHIVu6Vekyrp/eOpl22wzjku
DMWEK9ZWLHYejbCZJJIRKxSsxRlwAX2D/9kQ9vs2YQKBgQD/UYGD7ovAjTclJzyx
d6dj/C0fHwyFnF+Lknll7AhA1hBYzQ2uaqBSUc2oV19dsxfZfy/tD0gP2ZVSFl1f
iF3ON0xVbbb4r3Drck17c5iQ2+z5PHfOHSD3+AwF+stkKwLiJxIudJJppm1Cn6wc
WiysnqeEhsaklOe0PvdA8BIkDwKBgQDetA7g2m7FKfODHb9Tx7qDtiRjsbCYD1fF
UpF2yQ+twXJAO9AgkxYmmWMQ9/h+yxpsTjE4oDOO5K907x6gGzJFfFrDn0R0zIfw
0EobfwJ4l28rqUYvQzgYyc/UW2PiuLIr2otXQNmtOnYmPogLIMlgQSvEnseXCs//
0+TRKKbFmQKBgGVJYVcEeF6P5xOPKE3DCR3qOcBB4gbTTTgiiJR9eZy3D08bMdVC
qY1etHaXCtcyKED5avrheBYJnGovQyWWBJi9aUPuvYqUlvhgpQpXhmvZQ35wlZqo
BZ85wRNSNgPr6D3tgBH73uWYx3mJvI6W22gznIM/sGg0RmNEI3SYiKnTAoGAJ1mx
d1GhWP08peJPuEIGVteMPoFbLsMnQxp/0XldZ9pSkb7/24Gh3FgmBQ2LvvusQ47d
a6AC/DC0P/kwdCHaFCUQ9JfjxK+PJaHoNkuO6Df50MEsQZWjB95A9sjfMWRpNw56
qIQw8kbuMXvDFhRJANUDIs7bfXjPn+iU+dAxB6ECgYEAusdd0y30VXE9Mj6zJ2Ee
Cm1L9THijgcPH/Q7rD9Vk5yPSXuKO9Q2atgY0COOeQxwmwjf8UHYldDxLjor1nz0
CdwQihCexJSXuoQ9LA/XeTLDm/F/M1Eze+MgOE23dT9qEUOwHrjDsyuYwfev7ir9
a11MWJKcdkeKS/lGXeX3MbE=
-----END PRIVATE KEY-----
+20
View File
@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDLTCCAhWgAwIBAgIUQ67cF8bq3m9pcWq6zJeZRc8N60EwDQYJKoZIhvcNAQEL
BQAwJTEQMA4GA1UEAwwHRHVtbXlDQTERMA8GA1UECgwIRHVtbXlQS0kwIBcNMTgx
MDE1MTkzMDUxWhgPMjExODEwMTUxOTMwNTFaMCUxEDAOBgNVBAMMB0R1bW15Q0Ex
ETAPBgNVBAoMCER1bW15UEtJMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEA3hxCdi/hV63HVhvCzWf1pJ08xPZbAeCNRm+y3K14v2KvqxJxfIq7toeI9YnJ
g0rsCwUv/6hDTti3xPolNc5rZYYwHxr/EmEl4NbLAGwekr+L789FNKTQgL5yfyvF
De5AFxwda8OXUbzuOwPjNTTvEuqWEy+YyYSvgWjVZj+sPXjkOHPtGbyhThF9BP7s
tanFLkt/OmXDxUcURbxK1KPFo8jAMFfR2aFWt4d7QMsCnw62IxfA7XTRvdxt4oM6
b+CK6OALhEqzxN7eMebojuFMcFDm6JZFbJjgM6O4NUIhItMJR00F9WR93V4GkQBe
b7/1dNmQJ5iOZHl3zwwyKlEX9wIDAQABo1MwUTAdBgNVHQ4EFgQUxHdbXQ7dqFqw
jzzl02L+UefEwNgwHwYDVR0jBBgwFoAUxHdbXQ7dqFqwjzzl02L+UefEwNgwDwYD
VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEApcWGs+A06Aaqr9VAxhDp
i4vSV0K++912IJWRv6rRRy7Hz4b7Ov5UOZ7AHshnQNWQvd9Qp+ehhNoLjL5joWk9
BmwMPGAPhBAvidDh8QULUAIDJWDGF8CtDDaW8oHYjsBTBSVjKu1Ma/OVMc9vF0Ej
MUb3LtlKZkNUqmUCjlE/V2bYYjqVLNTogrXO4KxXyqfBFdUzTCB/qX7V+HNnszDq
IFHF+wyTklMSOwTUIrvW4R6zojXL0wjRdvtBqXSvPFq5HWJPdg0dfVyyVDZu7yPO
3gMJLqkDkHsaYLs2IKHUXqJCbHCJ/whpMWNYYozDFGrhetNSvo3sKht9wvQK08mc
yg==
-----END CERTIFICATE-----
+28
View File
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDg1xvj8JLt4nDq
rRQOGzCys0bD2VdGRnQ/TYC3ezg0whYbQRD7bUdkNQm1KeNOlj8yr8kjz30swIY6
6Ufw82qGH0QjsmbSOTLr13B9K1ogd11aMxErvVnxtNBOkuwtzKL+Zb1D99jvxX0u
e0dIsBcuojemyoz6iNbU7WSNtbIFeQFr+MlZujHIv0Hj76MYyZAqNq2F3Ou1SduV
kw+Acol8KzTfiII38ibZcP+INgK6YvnrJrxbDZ+NXwsXOeg0y/vZ7hc2h1k5wT3E
xWoyMD/bvnVrKTneQfwOI9s4s3pB6o1hGeUGRhPvpJ20+FGOE8LIRzBEixDKl82T
G63DVoMJAgMBAAECggEBANKVBf0+BA8jZ4iUpFT16G1mdZ/W/uPF9viXGThAAwt+
wH+0ODiUSCo1dqsj2U5wcC6D74pHukBg7RdeCFBHW3zU6dfZLm40vlmfRS8mnFoO
EfP6IlnqFcTJCdSdzPC0WfCUz2hKSPeA61bOhZwxuPSnYCIqUVIROczhrqz/AQYX
ZuuY7ut0h6X11xoddeeTOdfd+rktxPVpHeJDcz9F6Gk/0pg1ezs66TZ5sWt0/ZhP
ZWTB40KPec2lomRGwY36AMOT3uFucnbtJgxhTGdqsRv7Kl0xCTo7xW/85Za7rhXK
+xRdHrYr9w6xYTKHi9Ap4HEmUcx/fGAtddxr0fzdpIECgYEA8nRyYYHMdSr4WI/E
fD/cpIjKLxw9BCdoXhruVSKp1GmhyQTH7Y0zJQ0uvG7GlsEXJ2V9yqTofzWev8iQ
vVzX+14yTKaNUAwc7HCEXMN+xqnq61Bjldx6U54CfdInOhTFuoq8CXu80cHobYMs
8SjnYuhp5NqABGR+9YTYaHzjBhkCgYEA7Wa9orxkWruXUdROUZrdhlU44ilxvA66
r1vVVLQFNVle2hbie3b93ZQCZrZL8iFsGKOIhXSncQ6pl9GQqDLJvMUiffzUci6G
A4GgGlzXpkj+RmAkIkGYafmQqTZhMHNws35LMYoIabn0l8cWwG0XL1pO2YSjaoYg
5Kj0TjoNonECgYAcAk3Qa+FFy+ACwyEMxYfkzhSlWprF5xOMg4ny9d0ut8FD6rR6
Aezdo+c5R4bTlZzqJTRh+6kMQRKEz1PBPH+K/3fKGReMHsocmmcAHGmB49FKu++1
OVI8ZK2fAW8cq5eoFCzi35ORm9gRBq1jcrlAWN8a3A8b8swj6uPhNkQ3yQKBgCH+
HBk5MIVtZvVomO5GZoHdog+AL7DlywVg+OLwA+7npRVFQZi8KQ2ZK97ZK3a4ImpE
wD+bvH4Lw2zhrPzoiMpmz9GKakEPOFE4NlyP/rDossAQ9BuTmOdTvMr95lyxqumI
o+usABhjcAprj25uMGuvWqr6uwt9uSgEqTaqSVmBAoGAMs6/59L8EABC/CkBLp0t
y3sYzjyjZr6gnMCkIi95b2FWyIVjlK53dwjKYg67BUI8qLNbUW1NPhyotc/9GklP
qiNTkUeKcHOlZqOzP0zPquFk2lSfJuuGrMgElZPcEFLsAWRqnjRGp5iP05nk5OQh
foY7svzPedFk/t3eFcOyrPo=
-----END PRIVATE KEY-----
+17
View File
@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICtDCCAZwCAQAwDQYJKoZIhvcNAQELBQAwJTEQMA4GA1UEAwwHRHVtbXlDQTER
MA8GA1UECgwIRHVtbXlQS0kwIBcNMTgxMDE1MTkzMDUxWhgPMjExODEwMTUxOTMw
NTFaMBkxFzAVBgNVBAMMDmliLm5hb21pbmV0LmpwMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA4Ncb4/CS7eJw6q0UDhswsrNGw9lXRkZ0P02At3s4NMIW
G0EQ+21HZDUJtSnjTpY/Mq/JI899LMCGOulH8PNqhh9EI7Jm0jky69dwfStaIHdd
WjMRK71Z8bTQTpLsLcyi/mW9Q/fY78V9LntHSLAXLqI3psqM+ojW1O1kjbWyBXkB
a/jJWboxyL9B4++jGMmQKjathdzrtUnblZMPgHKJfCs034iCN/Im2XD/iDYCumL5
6ya8Ww2fjV8LFznoNMv72e4XNodZOcE9xMVqMjA/2751ayk53kH8DiPbOLN6QeqN
YRnlBkYT76SdtPhRjhPCyEcwRIsQypfNkxutw1aDCQIDAQABMA0GCSqGSIb3DQEB
CwUAA4IBAQANOqFqjnGf80vvwYEkUsfOWp8rNVat+8rdXl0BShGBXiDItzMOU79K
YkPObniQ5RLBMhrvqlCsSk0Np+ZgvV12J4Wtmf/znLa5ZKyeI4N1FCefU9cl4xpB
08Fv8YWbYV7SMNr54ZkURdho4FVR1pAnpuittpAEjMT4R4ubbOH8UEbMTbVgxXdn
086IAlfsYn0gnOlf76RkJFLe4UlWaZB75SaaXnNavBPN9iFnqXLckg6tsFUJnNMC
esq5aHQ9sXWs4oKpJi8SXxt/zNRmgTnQK2ONM38NeZpLmlWPkyNxzsRNlYWo+kWP
C98jVFe1+3K88ISk0DSN4XOQQnrIvn68
-----END CERTIFICATE-----
+5 -41
View File
@@ -1,45 +1,9 @@
create schema "aime";
create type "aime"."region" as enum (
'JPN',
'HKG',
'SGP',
'KOR',
'USA'
);
create table "aime"."shop" (
"id" bigint primary key not null,
create table "aime_player" (
"id" integer primary key not null,
"ext_id" integer not null,
"name" text not null,
"region" "aime"."region" not null,
constraint "shop_uq" unique ("ext_id")
);
create table "aime"."machine" (
"id" bigint primary key not null,
"shop_id" bigint not null
references "aime"."shop"("id"),
"pcb_id" text not null,
"keychip_id" text not null,
constraint "machine_pcb_id_uq" unique ("pcb_id"),
constraint "machine_keychip_id_uq" unique ("keychip_id")
);
create table "aime"."player" (
"id" bigint primary key not null,
"ext_id" integer not null,
"register_time" timestamp not null,
constraint "player_uq" unique ("ext_id")
);
create table "aime"."card" (
"id" bigint primary key not null,
"player_id" bigint not null
references "aime"."player"("id")
on delete cascade,
"nfc_id" text not null,
"luid" text not null,
"register_time" timestamp not null,
"access_time" timestamp not null,
constraint "card_uq" unique ("nfc_id")
constraint "aime_player_ext_id_uq" unique ("ext_id"),
constraint "aime_player_luid_uq" unique ("luid")
);
+372
View File
@@ -0,0 +1,372 @@
-- SQLite's ALTER TABLE statement is almost useless, and who knows how much of
-- this stuff is present in older versions. So while ideally we'd mark a lot
-- of these columns NOT NULL that might come back to bite us as we extend
-- support downward from Chunithm 4 to Chunithm 1.
create table "cm_user_data" (
"id" integer primary key not null,
-- Could in theory do a shared primary key with aime_player, since this
-- is going to be a 1:1 relation for the foreseeable future, but again
-- the SQLite strait-jacket forces us to design defensively.
"player_id" integer not null
references "aime_player"("id")
on delete cascade,
"access_code" text,
"user_name" text,
"is_web_join" text,
"web_limit_date" text,
"level" integer,
"reincarnation_num" integer,
"exp" integer,
"point" integer,
"total_point" integer,
"play_count" integer,
"multi_play_count" integer,
"multi_win_count" integer,
"request_res_count" integer,
"accept_res_count" integer,
"success_res_count" integer,
"player_rating" integer,
"highest_rating" integer,
"nameplate_id" integer,
"frame_id" integer,
"character_id" integer,
"trophy_id" integer,
"played_tutorial_bit" integer,
"first_tutorial_cancel_num" integer,
"master_tutorial_cancel_num" integer,
"total_repertoire_count" integer,
"total_map_num" integer,
"total_hi_score" integer,
"total_basic_high_score" integer,
"total_advanced_high_score" integer,
"total_expert_high_score" integer,
"total_master_high_score" integer,
"event_watched_date" text,
"friend_count" integer,
"is_maimai" text,
"first_game_id" text,
"first_rom_version" text,
"first_data_version" text,
"first_play_date" text,
"last_game_id" text,
"last_rom_version" text,
"last_data_version" text,
"last_play_date" text,
"last_place_id" integer,
"last_place_name" text,
"last_region_id" text,
"last_region_name" text,
"last_all_net_id" text,
"last_client_id" text
);
create table "cm_user_activity" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"kind" integer not null,
-- This is just called "id" on the wire but, well, we already have an "id"
-- column up there ^
"activity_id" integer not null,
"sort_number" integer not null,
"param1" integer not null,
"param2" integer not null,
"param3" integer not null,
"param4" integer not null,
constraint "cm_user_activity_uq" unique (
"profile_id",
"kind",
"activity_id"
)
);
create table "cm_user_charge" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"charge_id" integer not null,
"stock" integer not null,
"purchase_date" text not null,
"valid_date" text not null,
"param1" integer not null,
"param2" integer not null,
"param_date" text not null,
constraint "cm_user_charge_uq" unique ("profile_id", "charge_id")
);
create table "cm_user_course" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"course_id" integer not null,
"class_id" integer not null,
"play_count" integer not null,
"score_max" integer not null,
"is_full_combo" text not null,
"is_all_justice" text not null,
"is_success" text not null,
"score_rank" integer not null,
"event_id" integer not null,
"last_play_date" text not null,
"param1" integer not null,
"param2" integer not null,
"param3" integer not null,
"param4" integer not null,
"is_clear" text not null,
constraint "cm_user_course_uq" unique ("profile_id", "course_id")
);
create table "cm_user_data_ex" (
"id" integer primary key not null
references "cm_user_data"("id")
on delete cascade,
"compatible_cm_version" text not null,
"medal" integer not null,
"map_icon_id" integer not null,
"voice_id" integer not null,
"ext1" integer not null,
"ext2" integer not null,
"ext3" integer not null,
"ext4" integer not null,
"ext5" integer not null,
"ext6" integer not null,
"ext7" integer not null,
"ext8" integer not null,
"ext9" integer not null,
"ext10" integer not null,
"ext11" integer not null,
"ext12" integer not null,
"ext13" integer not null,
"ext14" integer not null,
"ext15" integer not null,
"ext16" integer not null,
"ext17" integer not null,
"ext18" integer not null,
"ext19" integer not null,
"ext20" integer not null,
"ext_str1" text not null,
"ext_str2" text not null,
"ext_str3" text not null,
"ext_str4" text not null,
"ext_str5" text not null,
"ext_long1" integer not null,
"ext_long2" integer not null,
"ext_long3" integer not null,
"ext_long4" integer not null,
"ext_long5" integer not null
);
create table "cm_user_duel_list" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"duel_id" integer not null,
"progress" integer not null,
"point" integer not null,
"is_clear" boolean not null,
"last_play_date" text not null,
"param1" integer not null,
"param2" integer not null,
"param3" integer not null,
"param4" integer not null,
constraint "cm_user_duel_list_uq" unique ("profile_id", "duel_id")
);
create table "cm_user_character" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"character_id" integer not null,
"play_count" integer not null,
"level" integer not null,
"skill_id" integer not null,
"friendship_exp" integer not null,
"is_valid" integer not null,
"is_new_mark" integer not null,
"param1" integer not null,
"param2" integer not null,
constraint "cm_user_character_uq" unique ("profile_id", "character_id")
);
create table "cm_user_game_option" (
"id" integer primary key not null
references "cm_user_data"("id")
on delete cascade,
"bg_info" integer not null,
"field_color" integer not null,
"guide_sound" integer not null,
"sound_effect" integer not null,
"guide_line" integer not null,
"speed" integer not null,
"option_set" integer not null,
"matching" integer not null,
"judge_pos" integer not null,
"rating" integer not null,
"judge_justice" integer not null,
"judge_attack" integer not null,
"headphone" integer not null,
"player_level" integer not null,
"success_tap" integer not null,
"success_ex_tap" integer not null,
"success_slide_hold" integer not null,
"success_air" integer not null,
"success_flick" integer not null,
"success_skill" integer not null,
"success_tap_timbre" integer not null,
"privacy" integer not null
);
create table "cm_user_game_option_ex" (
"id" integer primary key not null
references "cm_user_data"("id")
on delete cascade,
"ext1" integer not null,
"ext2" integer not null,
"ext3" integer not null,
"ext4" integer not null,
"ext5" integer not null,
"ext6" integer not null,
"ext7" integer not null,
"ext8" integer not null,
"ext9" integer not null,
"ext10" integer not null,
"ext11" integer not null,
"ext12" integer not null,
"ext13" integer not null,
"ext14" integer not null,
"ext15" integer not null,
"ext16" integer not null,
"ext17" integer not null,
"ext18" integer not null,
"ext19" integer not null,
"ext20" integer not null
);
create table "cm_user_item" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"item_kind" integer not null,
"item_id" integer not null,
"stock" integer not null,
"is_valid" text not null,
constraint "cm_user_item_uq" unique ("profile_id", "item_kind", "item_id")
);
create table "cm_user_map" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"map_id" integer not null,
"position" integer not null,
"is_clear" text not null,
"area_id" integer not null,
"route_number" integer not null,
"event_id" integer not null,
"rate" integer not null,
"status_count" integer not null,
"is_valid" text not null,
constraint "cm_user_map_uq" unique ("profile_id", "map_id")
);
create table "cm_user_music" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"music_id" integer not null,
"level" integer not null,
"play_count" integer not null,
"score_max" integer not null,
"res_request_count" integer not null,
"res_accept_count" integer not null,
"res_success_count" integer not null,
"miss_count" integer not null,
"max_combo_count" integer not null,
"is_full_combo" text not null,
"is_all_justice" text not null,
"is_success" text not null,
"full_chain" integer not null,
"max_chain" integer not null,
"score_rank" integer not null,
"is_lock" text not null,
constraint "cm_user_music_uq" unique ("profile_id", "music_id", "level")
);
create table "cm_user_playlog" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"order_id" integer not null,
"sort_number" integer not null,
"place_id" integer not null,
"play_date" text not null,
"user_play_date" text not null,
"music_id" integer not null,
"level" integer not null,
"custom_id" integer not null,
"played_user_id1" integer not null,
"played_user_id2" integer not null,
"played_user_id3" integer not null,
"played_user_name1" text not null,
"played_user_name2" text not null,
"played_user_name3" text not null,
"played_music_level1" integer not null,
"played_music_level2" integer not null,
"played_music_level3" integer not null,
"played_custom1" integer not null,
"played_custom2" integer not null,
"played_custom3" integer not null,
"track" integer not null,
"score" integer not null,
"rank" integer not null,
"max_combo" integer not null,
"max_chain" integer not null,
"rate_tap" integer not null,
"rate_hold" integer not null,
"rate_slide" integer not null,
"rate_air" integer not null,
"rate_flick" integer not null,
"judge_guilty" integer not null,
"judge_attack" integer not null,
"judge_justice" integer not null,
"judge_critical" integer not null,
"event_id" integer not null,
"player_rating" integer not null,
"is_new_record" text not null,
"is_full_combo" text not null,
"full_chain_kind" integer not null,
"is_all_justice" text not null,
"is_continue" text not null,
"is_free_to_play" text not null,
"character_id" integer not null,
"skill_id" integer not null,
"play_kind" integer not null,
"is_clear" text not null,
"skill_level" integer not null,
"skill_effect" integer not null,
"place_name" text not null,
"is_maimai" text not null
);
create table "cm_user_recent_rating" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"sort_order" integer not null,
"music_id" integer not null,
"difficult_id" integer not null,
"rom_version_code" integer not null,
"score" integer not null,
constraint "cm_user_recent_rating_uq" unique ("profile_id", "sort_order")
);
+180 -134
View File
@@ -2,129 +2,159 @@
-- A "course" contains an uphill and downhill "route"
-- (or inbound/outbound etc)
create schema "idz";
create type "idz"."gender" as enum ('male', 'female');
create table "idz"."profile" (
"id" bigint primary key not null,
"player_id" bigint not null
references "aime"."player"("id")
create table "idz_profile" (
"id" integer primary key not null,
"player_id" integer not null
references "aime_player"("id")
on delete cascade,
-- Major version of Initial D Zero, either 1 or 2.
-- The two major versions are incompatible with each other and do not
-- permit the player to carry progress over from one to the other.
"version" integer not null,
-- TODO shop_id
"name" text not null,
"lv" smallint not null,
"lv" integer not null,
"exp" integer not null,
"fame" integer not null,
"dpoint" integer not null,
"mileage" integer not null,
"register_time" timestamp not null,
"access_time" timestamp not null,
constraint "profile_player_uq" unique ("player_id")
constraint "idz_profile_player_uq" unique ("player_id", "version")
);
create table "idz"."chara" (
"id" bigint primary key not null
references "idz"."profile"("id")
create table "idz_chara" (
"id" integer primary key not null
references "idz_profile"("id")
on delete cascade,
"gender" "idz"."gender" not null,
"gender" text not null,
"field_02" integer not null,
"field_04" integer not null,
"field_06" integer not null,
"field_08" integer not null,
"field_0A" integer not null,
"field_0C" integer not null,
"field_0E" integer not null,
"field_0a" integer not null,
"field_0c" integer not null,
"field_0e" integer not null,
"title" integer not null,
"background" integer not null
);
create table "idz"."course_plays" (
"id" bigint primary key not null,
"profile_id" bigint not null
references "idz"."profile"("id")
create table "idz_course_plays" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"course_no" smallint not null,
"course_no" integer not null,
"count" integer not null,
constraint "course_plays_uq" unique ("profile_id", "course_no")
constraint "idz_course_plays_uq" unique ("profile_id", "course_no")
);
create table "idz"."car" (
"id" bigint primary key not null,
"profile_id" bigint not null
references "idz"."profile"("id")
create table "idz_car" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"selector" integer not null,
"field_00" integer not null,
"field_02" integer not null,
"field_04" integer[] not null,
"field_04" text not null,
"field_46" integer not null,
"field_48" integer not null,
"field_4A" integer not null,
"field_4C" integer not null,
"field_4a" integer not null,
"field_4c" integer not null,
"field_50_lo" integer not null,
"field_50_hi" integer not null,
"field_58" integer not null,
"field_5A" integer not null,
"field_5B" integer not null,
"field_5C" integer not null,
"field_5E" integer not null,
constraint "car_uq" unique ("profile_id", "selector")
"field_5a" integer not null,
"field_5b" integer not null,
"field_5c" integer not null,
"field_5e" integer not null,
constraint "idz_car_uq" unique ("profile_id", "selector")
);
create table "idz"."car_selection" (
"id" bigint primary key not null
references "idz"."profile"("id")
create table "idz_car_selection" (
"id" integer primary key not null
references "idz_profile"("id")
on delete cascade,
"car_id" bigint not null
references "idz"."car"("id")
"car_id" integer not null
references "idz_car"("id")
on delete cascade
);
create table "idz"."solo_mission_state" (
"id" bigint primary key not null,
"profile_id" bigint not null
references "idz"."profile"("id")
create table "idz_solo_mission_state" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"grid_no" smallint not null,
"cell_no" smallint not null,
"value" smallint not null,
constraint "solo_mission_state_uq" unique ("profile_id", "grid_no", "cell_no")
"grid_no" integer not null,
"cell_no" integer not null,
"value" integer not null,
constraint "idz_solo_mission_state_uq" unique (
"profile_id",
"grid_no",
"cell_no"
)
);
create table "idz"."settings" (
"id" bigint primary key not null
references "idz"."profile"("id")
create table "idz_settings" (
"id" integer primary key not null
references "idz_profile"("id")
on delete cascade,
"music" smallint not null,
"pack" bigint not null,
"paper_cup" smallint not null, -- Not a boolean, oddly enough
"gauges" integer not null
"music" integer not null,
"pack" integer not null,
"aura" integer not null,
"paper_cup" integer not null, -- Not a boolean, oddly enough
"gauges" integer not null,
"driving_style" integer not null
);
create table "idz"."story_state" (
"id" bigint primary key not null
references "idz"."profile"("id")
create table "idz_stamp_selections" (
"id" integer primary key not null
references "idz_profile"("id")
on delete cascade,
"x" smallint not null,
"y" smallint not null
"stamp_01" integer not null,
"stamp_02" integer not null,
"stamp_03" integer not null,
"stamp_04" integer not null
);
create table "idz"."story_cell_state" (
"id" bigint primary key not null,
"profile_id" bigint not null
references "idz"."profile"("id")
create table "idz_stamp_unlock" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"stamp_no",
constraint "idz_stamp_unlock_uq" unique ("profile_id", "stamp_no")
);
create table "idz_story_state" (
"id" integer primary key not null
references "idz_profile"("id")
on delete cascade,
"x" integer not null,
"y" integer not null
);
create table "idz_story_cell_state" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"row_no" integer not null,
"col_no" integer not null,
"a" smallint not null,
"b" smallint not null,
constraint story_cell_state_uq unique ("profile_id", "row_no", "col_no")
"a" integer not null,
"b" integer not null,
"c" integer not null,
constraint "idz_story_cell_state_uq" unique (
"profile_id",
"row_no",
"col_no"
)
);
create table "idz"."free_car" (
"id" bigint primary key not null
references "idz"."profile"("id")
create table "idz_free_car" (
"id" integer primary key not null
references "idz_profile"("id")
on delete cascade,
"valid_from" timestamp not null
-- Expiry cannot be controlled by the server
@@ -132,112 +162,128 @@ create table "idz"."free_car" (
-- Times are stored as floating-point seconds
create table "idz"."ta_result" (
"id" bigint primary key not null,
"profile_id" bigint not null
references "idz"."profile"("id")
create table "idz_ta_result" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"route_no" smallint not null,
"route_no" integer not null,
"total_time" float not null,
"section_times" float[] not null,
"flags" smallint not null,
"grade" smallint not null,
"section_times" text not null,
"flags" integer not null,
"grade" integer not null,
"car_selector" integer not null,
"timestamp" timestamp not null
);
create table "idz"."ta_best" (
"id" bigint primary key not null,
"profile_id" bigint not null
references "idz"."profile"("id")
create table "idz_ta_best" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"route_no" smallint not null,
"route_no" integer not null,
"total_time" float not null,
"section_times" float[] not null,
"flags" smallint not null,
"grade" smallint not null, -- TODO enum
"section_times" text not null,
"flags" integer not null,
"grade" integer not null, -- TODO enum
"car_selector" integer not null,
"timestamp" timestamp not null,
constraint "ta_best_uq" unique ("profile_id", "route_no")
constraint "idz_ta_best_uq" unique ("profile_id", "route_no")
);
-- TODO ta_section_best
create table "idz"."background_unlock" (
"id" bigint primary key not null,
"profile_id" bigint not null
references "idz"."profile"("id")
create table "idz_background_unlock" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"background_no" smallint not null,
constraint "background_unlock_uq" unique ("profile_id", "background_no")
"background_no" integer not null,
constraint "idz_background_unlock_uq" unique (
"profile_id",
"background_no"
)
);
create table "idz"."title_unlock" (
"id" bigint primary key not null,
"profile_id" bigint not null
references "idz"."profile"("id")
create table "idz_title_unlock" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"title_no" smallint not null,
constraint "title_unlock_uq" unique ("profile_id", "title_no")
"title_no" integer not null,
constraint "idz_title_unlock_uq" unique ("profile_id", "title_no")
);
create table "idz"."unlocks" (
"id" bigint primary key not null
references "idz"."profile"("id")
create table "idz_unlocks" (
"id" integer primary key not null
references "idz_profile"("id")
on delete cascade,
"cup" smallint not null,
"auras" integer not null,
"cup" integer not null,
"gauges" integer not null,
"music" integer not null,
"last_mileage_reward" integer not null
);
create table "idz"."team" (
"id" bigint primary key not null,
create table "idz_team" (
"id" integer primary key not null,
"version" integer not null,
"ext_id" integer not null,
"name" text not null,
"name_bg" smallint not null,
"name_fx" smallint not null,
"name_bg" integer not null,
"name_fx" integer not null,
"register_time" timestamp not null,
constraint "team_uq" unique ("ext_id")
constraint "idz_team_uq" unique ("version", "ext_id")
);
create table "idz"."team" (
"id" bigint primary key not null,
"ext_id" integer not null,
"name" text not null,
"name_bg" smallint not null,
"name_fx" smallint not null,
"register_time" timestamp not null,
constraint "team_uq" unique ("ext_id")
);
create table "idz"."team_auto" (
"id" bigint primary key not null
references "idz"."team"("id")
create table "idz_team_auto" (
"id" integer primary key not null
references "idz_team"("id")
on delete cascade,
"serial_no" smallint not null,
"name_idx" smallint not null,
constraint "team_auto_uq" unique ("serial_no", "name_idx")
"serial_no" integer not null,
"name_idx" integer not null
);
create table "idz"."team_member" (
"id" bigint primary key not null
references "idz"."profile"("id")
create table "idz_team_member" (
"id" integer primary key not null
references "idz_profile"("id")
on delete cascade,
"team_id" bigint not null
references "idz"."team"("id")
"team_id" integer not null
references "idz_team"("id")
on delete cascade,
"join_time" timestamp not null,
"leader" boolean not null
);
create table "idz"."team_reservation" (
"id" bigint primary key not null
references "aime"."player"("id")
create table "idz_team_reservation" (
"id" integer primary key not null
references "aime_player"("id")
on delete cascade,
"team_id" bigint not null
references "idz"."team"("id")
"team_id" integer not null
references "idz_team"("id")
on delete cascade,
"join_time" timestamp not null,
"leader" boolean not null
);
create table "idz_weekly_missions" (
"id" integer primary key not null
references "idz_profile"("id")
on delete cascade,
"weekly_reset" timestamp not null,
"mission_left" integer not null,
"progress_left" integer not null,
"params_left" integer not null,
"mission_right" integer not null,
"progress_right" integer not null,
"params_right" integer not null
);
create table "idz_my_chara" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"my_chara_no" integer not null,
constraint "idz_my_chara_uq" unique ("profile_id", "my_chara_no")
);
-1
View File
@@ -1,2 +1 @@
create table "meta" ("schemaver" integer not null);
insert into "meta" values (3);
-81
View File
@@ -1,81 +0,0 @@
-- Helper functions which might be useful in one-off queries.
-- These are not utilised by any services; those have their own ID generators.
create or replace function idnum(
str text)
returns bigint as $$
declare
num bigint;
x integer;
begin
num := 0;
for i in 1..11 loop
x := ascii(substr(str, i, 1));
num := num << 6;
if x >= 65 and x <= 90 then
-- A to Z
num = num | (x - 65);
elsif x >= 97 and x <= 122 then
-- a to z
num = num | (x - 71);
elsif x >= 48 and x <= 58 then
-- 0 to 9
num = num | (x + 4);
elsif x = 45 then
-- Dash
num = num | 62;
elsif x = 95 then
-- Underscore
num = num | 63;
else
raise 'Bad input';
end if;
end loop;
return num;
end;
$$ language plpgsql;
create or replace function idstr(
num bigint)
returns text as $$
declare
a text;
pos integer;
str text;
begin
a := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
str := '';
for i in 1..11 loop
pos := (num & 63);
str := substr(a, pos + 1, 1) || str;
num := num >> 6;
end loop;
return str;
end;
$$ language plpgsql;
create or replace function newid()
returns bigint as $$
declare
bytes bytea;
result bigint;
begin
bytes := gen_random_bytes(8);
result := 0;
for i in 0..7 loop
result := (result << 8) | get_byte(bytes, i);
end loop;
-- Truncate high bit to ensure result is positive
-- This value is just the decimal representation of 0x7FFFFFFF`FFFFFFFF.
return result & 9223372036854775807;
end;
$$ language plpgsql;
@@ -1,3 +0,0 @@
alter table "idz"."profile" drop column "ext_id";
update "meta" set "schemaver" = 1;
-42
View File
@@ -1,42 +0,0 @@
create table "idz"."team" (
"id" bigint primary key not null,
"ext_id" integer not null,
"name" text not null,
"name_bg" smallint not null,
"name_fx" smallint not null,
"register_time" timestamp not null,
constraint "team_uq" unique ("ext_id")
);
create table "idz"."team_auto" (
"id" bigint primary key not null
references "idz"."team"("id")
on delete cascade,
"serial_no" smallint not null,
"name_idx" smallint not null,
constraint "team_auto_uq" unique ("serial_no", "name_idx")
);
create table "idz"."team_member" (
"id" bigint primary key not null
references "idz"."profile"("id")
on delete cascade,
"team_id" bigint not null
references "idz"."team"("id")
on delete cascade,
"join_time" timestamp not null,
"leader" boolean not null
);
create table "idz"."team_reservation" (
"id" bigint primary key not null
references "aime"."player"("id")
on delete cascade,
"team_id" bigint not null
references "idz"."team"("id")
on delete cascade,
"join_time" timestamp not null,
"leader" boolean not null
);
update "meta" set "schemaver" = 2;
@@ -0,0 +1,27 @@
-- Can't add NOT NULL or UNIQUE constraints as we'd like to. Fortunately we
-- haven't released anything yet so there aren't really any DBs with live
-- data out there in the wild.
-- (Can't drop and recreate the table either btw because "aime_player" is
-- transitively referenced by _literally everything_).
alter table "aime_player" add column "luid" text;
alter table "aime_player" add column "access_time" text;
update "aime_player" as "p"
set "luid" = (
select "c"."nfc_id"
from "aime_card" as "c"
where "c"."player_id" = "p"."id"
),
"access_time" = (
select "c"."access_time"
from "aime_card" as "c"
where "c"."player_id" = "p"."id"
);
drop table "aime_card";
-- These were never used. Re-create them if they become necessary.
drop table "aime_machine";
drop table "aime_shop";
+304
View File
@@ -0,0 +1,304 @@
-- SQLite's ALTER TABLE statement is almost useless, and who knows how much of
-- this stuff is present in older versions. So while ideally we'd mark a lot
-- of these columns NOT NULL that might come back to bite us as we extend
-- support downward from Chunithm 4 to Chunithm 1.
create table "cm_user_data" (
"id" integer primary key not null,
-- Could in theory do a shared primary key with aime_player, since this
-- is going to be a 1:1 relation for the foreseeable future, but again
-- the SQLite strait-jacket forces us to design defensively.
"player_id" integer not null
references "aime_player"("id")
on delete cascade,
"access_code" text,
"user_name" text,
"is_web_join" text,
"web_limit_date" text,
"level" integer,
"reincarnation_num" integer,
"exp" integer,
"point" integer,
"total_point" integer,
"play_count" integer,
"multi_play_count" integer,
"multi_win_count" integer,
"request_res_count" integer,
"accept_res_count" integer,
"success_res_count" integer,
"player_rating" integer,
"highest_rating" integer,
"nameplate_id" integer,
"frame_id" integer,
"character_id" integer,
"trophy_id" integer,
"played_tutorial_bit" integer,
"first_tutorial_cancel_num" integer,
"master_tutorial_cancel_num" integer,
"total_repertoire_count" integer,
"total_map_num" integer,
"total_hi_score" integer,
"total_basic_high_score" integer,
"total_advanced_high_score" integer,
"total_expert_high_score" integer,
"total_master_high_score" integer,
"event_watched_date" text,
"friend_count" integer,
"is_maimai" text,
"first_game_id" text,
"first_rom_version" text,
"first_data_version" text,
"first_play_date" text,
"last_game_id" text,
"last_rom_version" text,
"last_data_version" text,
"last_play_date" text,
"last_place_id" integer,
"last_place_name" text,
"last_region_id" text,
"last_region_name" text,
"last_all_net_id" text,
"last_client_id" text
);
create table "cm_user_activity" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"kind" integer not null,
-- This is just called "id" on the wire but, well, we already have an "id"
-- column up there ^
"activity_id" integer not null,
"sort_number" integer not null,
"param1" integer not null,
"param2" integer not null,
"param3" integer not null,
"param4" integer not null,
constraint "cm_user_activity_uq" unique (
"profile_id",
"kind",
"activity_id"
)
);
create table "cm_user_data_ex" (
"id" integer primary key not null
references "cm_user_data"("id")
on delete cascade,
"compatible_cm_version" text not null,
"medal" integer not null,
"map_icon_id" integer not null,
"voice_id" integer not null,
"ext1" integer not null,
"ext2" integer not null,
"ext3" integer not null,
"ext4" integer not null,
"ext5" integer not null,
"ext6" integer not null,
"ext7" integer not null,
"ext8" integer not null,
"ext9" integer not null,
"ext10" integer not null,
"ext11" integer not null,
"ext12" integer not null,
"ext13" integer not null,
"ext14" integer not null,
"ext15" integer not null,
"ext16" integer not null,
"ext17" integer not null,
"ext18" integer not null,
"ext19" integer not null,
"ext20" integer not null,
"ext_str1" text not null,
"ext_str2" text not null,
"ext_str3" text not null,
"ext_str4" text not null,
"ext_str5" text not null,
"ext_long1" integer not null,
"ext_long2" integer not null,
"ext_long3" integer not null,
"ext_long4" integer not null,
"ext_long5" integer not null
);
create table "cm_user_character" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"character_id" integer not null,
"play_count" integer not null,
"level" integer not null,
"skill_id" integer not null,
"friendship_exp" integer not null,
"is_valid" integer not null,
"is_new_mark" integer not null,
"param1" integer not null,
"param2" integer not null,
constraint "cm_user_character_uq" unique ("profile_id", "character_id")
);
create table "cm_user_game_option" (
"id" integer primary key not null
references "cm_user_data"("id")
on delete cascade,
"bg_info" integer not null,
"field_color" integer not null,
"guide_sound" integer not null,
"sound_effect" integer not null,
"guide_line" integer not null,
"speed" integer not null,
"option_set" integer not null,
"matching" integer not null,
"judge_pos" integer not null,
"rating" integer not null,
"judge_justice" integer not null,
"judge_attack" integer not null,
"headphone" integer not null,
"player_level" integer not null,
"success_tap" integer not null,
"success_ex_tap" integer not null,
"success_slide_hold" integer not null,
"success_air" integer not null,
"success_flick" integer not null,
"success_skill" integer not null,
"success_tap_timbre" integer not null,
"privacy" integer not null
);
create table "cm_user_game_option_ex" (
"id" integer primary key not null
references "cm_user_data"("id")
on delete cascade,
"ext1" integer not null,
"ext2" integer not null,
"ext3" integer not null,
"ext4" integer not null,
"ext5" integer not null,
"ext6" integer not null,
"ext7" integer not null,
"ext8" integer not null,
"ext9" integer not null,
"ext10" integer not null,
"ext11" integer not null,
"ext12" integer not null,
"ext13" integer not null,
"ext14" integer not null,
"ext15" integer not null,
"ext16" integer not null,
"ext17" integer not null,
"ext18" integer not null,
"ext19" integer not null,
"ext20" integer not null
);
create table "cm_user_item" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"item_kind" integer not null,
"item_id" integer not null,
"stock" integer not null,
"is_valid" text not null,
constraint "cm_user_item_uq" unique ("profile_id", "item_kind", "item_id")
);
create table "cm_user_map" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"map_id" integer not null,
"position" integer not null,
"is_clear" text not null,
"area_id" integer not null,
"route_number" integer not null,
"event_id" integer not null,
"rate" integer not null,
"status_count" integer not null,
"is_valid" text not null,
constraint "cm_user_map_uq" unique ("profile_id", "map_id")
);
create table "cm_user_music" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"music_id" integer not null,
"level" integer not null,
"play_count" integer not null,
"score_max" integer not null,
"res_request_count" integer not null,
"res_accept_count" integer not null,
"res_success_count" integer not null,
"miss_count" integer not null,
"max_combo_count" integer not null,
"is_full_combo" text not null,
"is_all_justice" text not null,
"is_success" text not null,
"full_chain" integer not null,
"max_chain" integer not null,
"score_rank" integer not null,
"is_lock" text not null,
constraint "cm_user_music_uq" unique ("profile_id", "music_id", "level")
);
create table "cm_user_playlog" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"order_id" integer not null,
"sort_number" integer not null,
"place_id" integer not null,
"play_date" text not null,
"user_play_date" text not null,
"music_id" integer not null,
"level" integer not null,
"custom_id" integer not null,
"played_user_id1" integer not null,
"played_user_id2" integer not null,
"played_user_id3" integer not null,
"played_user_name1" text not null,
"played_user_name2" text not null,
"played_user_name3" text not null,
"played_music_level1" integer not null,
"played_music_level2" integer not null,
"played_music_level3" integer not null,
"played_custom1" integer not null,
"played_custom2" integer not null,
"played_custom3" integer not null,
"track" integer not null,
"score" integer not null,
"rank" integer not null,
"max_combo" integer not null,
"max_chain" integer not null,
"rate_tap" integer not null,
"rate_hold" integer not null,
"rate_slide" integer not null,
"rate_air" integer not null,
"rate_flick" integer not null,
"judge_guilty" integer not null,
"judge_attack" integer not null,
"judge_justice" integer not null,
"judge_critical" integer not null,
"event_id" integer not null,
"player_rating" integer not null,
"is_new_record" text not null,
"is_full_combo" text not null,
"full_chain_kind" integer not null,
"is_all_justice" text not null,
"is_continue" text not null,
"is_free_to_play" text not null,
"character_id" integer not null,
"skill_id" integer not null,
"play_kind" integer not null,
"is_clear" text not null,
"skill_level" integer not null,
"skill_effect" integer not null,
"place_name" text not null,
"is_maimai" text not null
);
+5
View File
@@ -0,0 +1,5 @@
-- This adds the required columns to save player aura settings and unlocks
-- Default values are set according to what the game expects
alter table "idz_settings" add column "aura" integer NOT NULL DEFAULT '0';
alter table "idz_unlocks" add column "auras" integer NOT NULL DEFAULT '1';
@@ -0,0 +1,16 @@
create table "cm_user_duel_list" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"duel_id" integer not null,
"progress" integer not null,
"point" integer not null,
"is_clear" boolean not null,
"last_play_date" text not null,
"param1" integer not null,
"param2" integer not null,
"param3" integer not null,
"param4" integer not null,
constraint "cm_user_duel_list_uq" unique ("profile_id", "duel_id")
);
+22
View File
@@ -0,0 +1,22 @@
create table "cm_user_course" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"course_id" integer not null,
"class_id" integer not null,
"play_count" integer not null,
"score_max" integer not null,
"is_full_combo" text not null,
"is_all_justice" text not null,
"is_success" text not null,
"score_rank" integer not null,
"event_id" integer not null,
"last_play_date" text not null,
"param1" integer not null,
"param2" integer not null,
"param3" integer not null,
"param4" integer not null,
"is_clear" text not null,
constraint "cm_user_course_uq" unique ("profile_id", "course_id")
);
@@ -0,0 +1,23 @@
-- This was not present in new DBs initialized to schema version 11.
create table if not exists "cm_user_course" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"course_id" integer not null,
"class_id" integer not null,
"play_count" integer not null,
"score_max" integer not null,
"is_full_combo" text not null,
"is_all_justice" text not null,
"is_success" text not null,
"score_rank" integer not null,
"event_id" integer not null,
"last_play_date" text not null,
"param1" integer not null,
"param2" integer not null,
"param3" integer not null,
"param4" integer not null,
"is_clear" text not null,
constraint "cm_user_course_uq" unique ("profile_id", "course_id")
);
@@ -0,0 +1,17 @@
-- This was also not present in the db init scripts...
create table if not exists "cm_user_duel_list" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"duel_id" integer not null,
"progress" integer not null,
"point" integer not null,
"is_clear" boolean not null,
"last_play_date" text not null,
"param1" integer not null,
"param2" integer not null,
"param3" integer not null,
"param4" integer not null,
constraint "cm_user_duel_list_uq" unique ("profile_id", "duel_id")
);
+14
View File
@@ -0,0 +1,14 @@
create table if not exists "cm_user_charge" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"charge_id" integer not null,
"stock" integer not null,
"purchase_date" text not null,
"valid_date" text not null,
"param1" integer not null,
"param2" integer not null,
"param_date" text not null,
constraint "cm_user_charge_uq" unique ("profile_id", "charge_id")
);
@@ -0,0 +1,31 @@
create table if not exists "cm_user_recent_rating" (
"id" integer primary key not null,
"profile_id" integer not null
references "cm_user_data"("id")
on delete cascade,
"sort_order" integer not null,
"music_id" integer not null,
"difficult_id" integer not null,
"rom_version_code" integer not null,
"score" integer not null,
constraint "cm_user_recent_rating_uq" unique ("profile_id", "sort_order")
);
-- Prepopulate this table by backfilling the most recent 30 scores per user.
-- This isn't exactly correct since not every recent score should go in here, but it's probably close enough.
INSERT INTO cm_user_recent_rating (profile_id, sort_order, music_id, difficult_id, rom_version_code, score)
SELECT * FROM (
SELECT
profile_id,
row_number()
OVER (PARTITION BY profile_id ORDER BY user_play_date DESC) AS sort_order,
music_id,
level AS difficult_id,
'1030000' AS rom_version_code,
score
FROM cm_user_playlog
WHERE
difficult_id < 4 -- skip world's end
ORDER BY profile_id ASC, user_play_date DESC
)
WHERE sort_order <= 30;
@@ -0,0 +1,77 @@
create table "new_idz_profile" (
"id" integer primary key not null,
"player_id" integer not null
references "aime_player"("id")
on delete cascade,
"version" integer not null,
"name" text not null,
"lv" integer not null,
"exp" integer not null,
"fame" integer not null,
"dpoint" integer not null,
"mileage" integer not null,
"register_time" timestamp not null,
"access_time" timestamp not null,
constraint "idz_profile_player_uq" unique ("player_id", "version")
);
create table "new_idz_team" (
"id" integer primary key not null,
"version" integer not null,
"ext_id" integer not null,
"name" text not null,
"name_bg" integer not null,
"name_fx" integer not null,
"register_time" timestamp not null,
constraint "idz_team_uq" unique ("version", "ext_id")
);
insert into "new_idz_profile" (
"id",
"player_id",
"version",
"name",
"lv",
"exp",
"fame",
"dpoint",
"mileage",
"register_time",
"access_time"
) select
x."id",
x."player_id",
1,
x."name",
x."lv",
x."exp",
x."fame",
x."dpoint",
x."mileage",
x."register_time",
x."access_time"
from "idz_profile" as "x";
insert into "new_idz_team" (
"id",
"version",
"ext_id",
"name",
"name_bg",
"name_fx",
"register_time"
) select
x."id",
1,
x."ext_id",
x."name",
x."name_bg",
x."name_fx",
x."register_time"
from "idz_team" as "x";
drop table "idz_profile";
drop table "idz_team";
alter table "new_idz_profile" rename to "idz_profile";
alter table "new_idz_team" rename to "idz_team";
+111
View File
@@ -0,0 +1,111 @@
create table "idz_my_chara" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"my_chara_no" integer not null,
constraint "idz_my_chara_uq" unique ("profile_id", "my_chara_no")
);
create table "new_idz_settings" (
"id" integer primary key not null
references "idz_profile"("id")
on delete cascade,
"music" integer not null,
"pack" integer not null,
"aura" integer not null,
"paper_cup" integer not null, -- Not a boolean, oddly enough
"gauges" integer not null,
"driving_style" integer not null
);
create table "idz_stamp_selections" (
"id" integer primary key not null
references "idz_profile"("id")
on delete cascade,
"stamp_01" integer not null,
"stamp_02" integer not null,
"stamp_03" integer not null,
"stamp_04" integer not null
);
create table "idz_stamp_unlock" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"stamp_no",
constraint "idz_stamp_unlock_uq" unique ("profile_id", "stamp_no")
);
create table "new_idz_story_cell_state" (
"id" integer primary key not null,
"profile_id" integer not null
references "idz_profile"("id")
on delete cascade,
"row_no" integer not null,
"col_no" integer not null,
"a" integer not null,
"b" integer not null,
"c" integer not null,
constraint "idz_story_cell_state_uq" unique (
"profile_id",
"row_no",
"col_no"
)
);
create table "idz_weekly_missions" (
"id" integer primary key not null
references "idz_profile"("id")
on delete cascade,
"weekly_reset" timestamp not null,
"mission_left" integer not null,
"progress_left" integer not null,
"params_left" integer not null,
"mission_right" integer not null,
"progress_right" integer not null,
"params_right" integer not null
);
insert into "new_idz_settings" (
"id",
"music",
"pack",
"aura",
"paper_cup",
"gauges",
"driving_style"
) select
x."id",
x."music",
x."pack",
x."aura",
x."paper_cup",
x."gauges",
0
from "idz_settings" as x;
insert into "new_idz_story_cell_state" (
"id",
"profile_id",
"row_no",
"col_no",
"a",
"b",
"c"
) select
x."id",
x."profile_id",
x."row_no",
x."col_no",
x."a",
x."b",
0
from "idz_story_cell_state" as x;
drop table "idz_settings";
drop table "idz_story_cell_state";
alter table "new_idz_settings" rename to "idz_settings";
alter table "new_idz_story_cell_state" rename to "idz_story_cell_state";
@@ -0,0 +1,13 @@
create table "new_idz_team_auto" (
"id" integer primary key not null
references "idz_team"("id")
on delete cascade,
"serial_no" integer not null,
"name_idx" integer not null
);
insert into "new_idz_team_auto"
select "id", "serial_no", "name_idx" from "idz_team_auto";
drop table "idz_team_auto";
alter table "new_idz_team_auto" rename to "idz_team_auto";
-94
View File
@@ -1,94 +0,0 @@
import { PoolClient } from "pg";
import * as sql from "sql-bricks";
import { CardRepository, Transaction } from "./repo";
import { connect, generateId, generateExtId } from "../db";
import { AimeId } from "../model";
class CardRepositoryImpl implements CardRepository {
constructor(private readonly _conn: PoolClient) {}
async lookup(luid: string, now: Date): Promise<AimeId | undefined> {
const fetchSql = sql
.select("c.id", "p.ext_id")
.from("aime.card c")
.join("aime.player p", { "c.player_id": "p.id" })
.where("c.nfc_id", luid)
.toParams();
const { rows } = await this._conn.query(fetchSql);
if (rows.length === 0) {
return undefined;
}
const id = rows[0].id;
const extId = rows[0].ext_id;
const touchSql = sql
.update("aime.card")
.set({ access_time: now })
.where("id", id)
.toParams();
await this._conn.query(touchSql);
return extId;
}
async register(luid: string, now: Date): Promise<AimeId> {
const playerId = generateId();
const cardId = generateId();
const aimeId = generateExtId() as AimeId;
const playerSql = sql
.insert("aime.player", {
id: playerId,
ext_id: aimeId,
register_time: now,
})
.toParams();
await this._conn.query(playerSql);
const cardSql = sql
.insert("aime.card", {
id: cardId,
player_id: playerId,
nfc_id: luid,
register_time: now,
access_time: now,
})
.toParams();
await this._conn.query(cardSql);
return aimeId;
}
}
class TransactionImpl implements Transaction {
constructor(private readonly _conn: PoolClient) {}
cards(): CardRepository {
return new CardRepositoryImpl(this._conn);
}
async commit(): Promise<void> {
await this._conn.query("commit");
await this._conn.release();
}
async rollback(): Promise<void> {
await this._conn.query("rollback");
await this._conn.release();
}
}
export async function beginDbSession(): Promise<Transaction> {
const conn = await connect();
await conn.query("begin");
return new TransactionImpl(conn);
}
+1 -1
View File
@@ -105,7 +105,7 @@ test("decode lookup", () => {
const obj = decode(req);
expect(obj.type).toBe("lookup");
expect(obj.type).toBe("lookup2");
expect(obj.gameId).toBe("SDBT");
expect(obj.keychipId).toBe("A69E01A9999");
expect(obj.luid).toBe("01036495850523030676");
+18 -1
View File
@@ -1,7 +1,10 @@
import logger from "debug";
import { Transform } from "stream";
import * as Request from "./request";
const debug = logger("app:aimedb:decoder");
function begin(msg: Buffer): Request.AimeRequestBase {
const gameId = msg.toString("ascii", 0x000a, 0x000e);
const keychipId = msg.toString("ascii", 0x0014, 0x001f);
@@ -28,6 +31,15 @@ function readFeliCaLookupRequest(msg: Buffer): Request.FeliCaLookupRequest {
};
}
function readFeliCaLookupRequest2(msg: Buffer): Request.FeliCaLookup2Request {
return {
...begin(msg),
type: "felica_lookup2",
idm: msg.slice(0x0030, 0x0038).toString("hex"),
pmm: msg.slice(0x0038, 0x0040).toString("hex"),
};
}
function readLogRequest(msg: Buffer): Request.LogRequest {
// idk what any of this stuff means yet
// field20 and field28 appear to be an aime id but that is all.
@@ -95,6 +107,7 @@ readers.set(0x0009, readLogRequest);
readers.set(0x000b, readCampaignRequest);
readers.set(0x000d, readRegisterRequest);
readers.set(0x000f, readLookupRequest2);
readers.set(0x0011, readFeliCaLookupRequest2);
readers.set(0x0064, readHelloRequest);
readers.set(0x0066, readGoodbyeRequest);
@@ -116,6 +129,10 @@ export class Decoder extends Transform {
);
}
return callback(null, reader(msg));
const obj = reader(msg);
debug("Decode %j", obj);
return callback(null, obj);
}
}
+20 -3
View File
@@ -1,7 +1,10 @@
import logger from "debug";
import { Transform } from "stream";
import { AimeResponse, RegisterLevel } from "./response";
const debug = logger("app:aimedb:encoder");
const registerLevels = new Map<RegisterLevel, number>();
registerLevels.set("none", 0);
@@ -11,7 +14,7 @@ registerLevels.set("segaid", 2);
function begin(length: number) {
const buf = Buffer.alloc(length);
buf.writeUInt16LE(0xa13e, 0x0000); // Magic?
buf.writeUInt16LE(0xa13e, 0x0000); // Magic: aime
buf.writeUInt16LE(0x3087, 0x0002); // ???
buf.writeUInt16LE(length, 0x0006);
@@ -27,7 +30,7 @@ export class Encoder extends Transform {
}
_transform(msg: AimeResponse, encoding, callback) {
console.log("Aimedb: Encode", msg);
debug("Encode %j", msg);
let buf: Buffer;
@@ -40,6 +43,18 @@ export class Encoder extends Transform {
break;
case "felica_lookup2":
buf = begin(0x0140);
buf.writeUInt16LE(0x0012, 0x0004); // cmd code
buf.writeUInt16LE(msg.status, 0x0008);
buf.writeInt32LE(msg.aimeId || -1, 0x0020);
buf.writeUInt32LE(0xffffffff, 0x0024); // FF
buf.writeUInt32LE(0xffffffff, 0x0028); // FF
buf.write(msg.accessCode, 0x002c, "hex");
buf.writeUInt16LE(0x0001, 0x0037); // 00 01
break;
case "hello":
buf = begin(0x0020);
buf.writeUInt16LE(0x0065, 0x0004); // cmd code
@@ -105,7 +120,9 @@ export class Encoder extends Transform {
return callback(new Error("Unimplemented response type"));
}
console.log("Aimedb: Send", buf.toString("hex"));
if (debug.enabled) {
debug("Send %s", buf.toString("hex"));
}
return callback(null, buf);
}
+6 -1
View File
@@ -1,5 +1,8 @@
import logger from "debug";
import { Transform } from "stream";
const debug = logger("app:aimedb:frame");
export class Deframer extends Transform {
private state: Buffer;
@@ -33,7 +36,9 @@ export class Deframer extends Transform {
const frame = this.state.slice(0, len);
console.log("Aimedb: Recv", frame.toString("hex"));
if (debug.enabled) {
debug("Recv %s", frame.toString("hex"));
}
this.state = this.state.slice(len);
+37 -8
View File
@@ -1,13 +1,17 @@
import logger from "debug";
import { Repositories } from "./repo";
import * as Req from "./request";
import * as Res from "./response";
const debug = logger("app:aimedb:ops");
function hello(
rep: Repositories,
req: Req.HelloRequest,
now: Date
): Res.HelloResponse {
console.log("Aimedb: Hello");
debug("Hello");
return { type: req.type, status: 1 };
}
@@ -17,7 +21,7 @@ function campaign(
req: Req.CampaignRequest,
now: Date
): Res.CampaignResponse {
console.log("Aimedb: Campaign stuff");
debug("Campaign stuff");
return { type: req.type, status: 1 };
}
@@ -27,7 +31,7 @@ function feliCaLookup(
req: Req.FeliCaLookupRequest,
now: Date
): Res.FeliCaLookupResponse {
console.log("Aimedb: FeliCa access code lookup");
debug("FeliCa access code lookup");
// Well, this access code transformation is the million dollar question eh
// Return a decimal representation for now.
@@ -42,12 +46,34 @@ function feliCaLookup(
return { type: req.type, status: 1, accessCode };
}
async function feliCaLookup2(
rep: Repositories,
req: Req.FeliCaLookup2Request,
now: Date
): Promise<Res.FeliCaLookup2Response> {
debug("FeliCa access code lookup");
const num = BigInt("0x" + req.idm);
let accessCode = num.toString();
while (accessCode.length < 20) {
accessCode = "0" + accessCode;
}
return {
type: req.type,
status: 1,
accessCode,
aimeId: await rep.cards().lookup(accessCode, now),
};
}
async function lookup(
rep: Repositories,
req: Req.LookupRequest,
now: Date
): Promise<Res.LookupResponse> {
console.log("Aimedb: Mifare lookup v1", req.luid);
debug("Mifare lookup: luid=%s", req.luid);
return {
type: req.type,
@@ -62,7 +88,7 @@ async function lookup2(
req: Req.LookupRequest2,
now: Date
): Promise<Res.LookupResponse2> {
console.log("Aimedb: Mifare lookup v2", req.luid);
debug("FeliCa lookup: luid=%s", req.luid);
return {
type: req.type,
@@ -77,7 +103,7 @@ async function register(
req: Req.RegisterRequest,
now: Date
): Promise<Res.RegisterResponse> {
console.log("Aimedb: Mifare register", req.luid);
debug("User register: luid=%s", req.luid);
return {
type: req.type,
@@ -91,7 +117,7 @@ function log(
req: Req.LogRequest,
now: Date
): Res.LogResponse {
console.log("Aimedb: Log message");
debug("Log message");
return { type: req.type, status: 1 };
}
@@ -111,6 +137,9 @@ export async function dispatch(
case "felica_lookup":
return feliCaLookup(rep, req, now);
case "felica_lookup2":
return feliCaLookup2(rep, req, now);
case "lookup":
return lookup(rep, req, now);
@@ -124,7 +153,7 @@ export async function dispatch(
return log(rep, req, now);
case "goodbye":
console.log("Aimedb: Goodbye");
debug("Goodbye");
return undefined;
+27 -18
View File
@@ -1,35 +1,44 @@
import logger from "debug";
import { Socket } from "net";
import { dispatch } from "./handler";
import { AimeRequest } from "./request";
import { setup } from "./pipeline";
import { beginDbSession } from "./db";
import { DataSource } from "../sql/api";
import { SqlRepositories } from "./sql";
export default async function aimedb(socket: Socket) {
console.log("Aimedb: Connection opened");
const debug = logger("app:aimedb:session");
export default function aimedb(db: DataSource) {
return async function(socket: Socket) {
debug("Connection opened");
const { input, output } = setup(socket);
const txn = await beginDbSession();
try {
for await (const obj of input) {
console.log("Aimedb: Decode", obj);
try {
const now = new Date();
const req = obj as AimeRequest;
const res = await dispatch(txn, req, now);
const req = obj;
const res = await db.transaction(txn =>
dispatch(new SqlRepositories(txn), req, now)
);
if (res === undefined) {
debug("Closing connection");
break;
}
if (res !== undefined) {
output.write(res);
}
}
await txn.commit();
} catch (e) {
console.log("Aimedb: Connection error:\n", e);
await txn.rollback();
if (debug.enabled) {
debug("Connection error: %s", e.stack);
}
console.log("Aimedb: Connection closed\n");
break;
}
}
debug("Connection closed");
socket.end();
};
}
+3 -3
View File
@@ -1,4 +1,4 @@
import * as crypto from "crypto";
import { createCipheriv, createDecipheriv } from "crypto";
import { Socket } from "net";
import * as stream from "stream";
import { promisify } from "util";
@@ -26,7 +26,7 @@ export function setup(socket: Socket): Session {
pipeline(
socket,
crypto.createDecipheriv("aes-128-ecb", K, null).setAutoPadding(false),
createDecipheriv("aes-128-ecb", K, null).setAutoPadding(false),
new Deframer({}),
input
).catch(doNothing);
@@ -35,7 +35,7 @@ export function setup(socket: Socket): Session {
pipeline(
output,
crypto.createCipheriv("aes-128-ecb", K, null).setAutoPadding(false),
createCipheriv("aes-128-ecb", K, null).setAutoPadding(false),
socket
).catch(doNothing);
-6
View File
@@ -9,9 +9,3 @@ export interface CardRepository {
export interface Repositories {
cards(): CardRepository;
}
export interface Transaction extends Repositories {
commit(): Promise<void>;
rollback(): Promise<void>;
}
+7
View File
@@ -9,6 +9,12 @@ export interface FeliCaLookupRequest extends AimeRequestBase {
pmm: string;
}
export interface FeliCaLookup2Request extends AimeRequestBase {
type: "felica_lookup2";
idm: string;
pmm: string;
}
export interface RegisterRequest extends AimeRequestBase {
type: "register";
luid: string;
@@ -50,6 +56,7 @@ export interface GoodbyeRequest {
export type AimeRequest =
| FeliCaLookupRequest
| FeliCaLookup2Request
| CampaignRequest
| GoodbyeRequest
| HelloRequest
+7
View File
@@ -11,6 +11,12 @@ export interface FeliCaLookupResponse extends AimeResponseBase {
accessCode: string;
}
export interface FeliCaLookup2Response extends AimeResponseBase {
type: "felica_lookup2";
accessCode: string;
aimeId?: AimeId;
}
export interface CampaignResponse extends AimeResponseBase {
type: "campaign";
}
@@ -44,6 +50,7 @@ export interface RegisterResponse extends AimeResponseBase {
export type AimeResponse =
| FeliCaLookupResponse
| FeliCaLookup2Response
| CampaignResponse
| HelloResponse
| LogResponse
+59
View File
@@ -0,0 +1,59 @@
import sql from "sql-bricks-postgres";
import { CardRepository, Repositories } from "./repo";
import { AimeId, generateExtId } from "../model";
import { Transaction } from "../sql";
class CardRepositoryImpl implements CardRepository {
constructor(private readonly _txn: Transaction) {}
async lookup(luid: string, now: Date): Promise<AimeId | undefined> {
const fetchSql = sql
.select("p.id", "p.ext_id")
.from("aime_player p")
.where("p.luid", luid);
const row = await this._txn.fetchRow(fetchSql);
if (row === undefined) {
return undefined;
}
const id = row.id!;
const extId = row.ext_id!;
const touchSql = sql
.update("aime_player")
.set({ access_time: now })
.where("id", id);
await this._txn.modify(touchSql);
return parseInt(extId, 10) as AimeId;
}
async register(luid: string, now: Date): Promise<AimeId> {
const id = this._txn.generateId();
const aimeId = generateExtId() as AimeId;
const insertSql = sql.insert("aime_player", {
id: id,
ext_id: aimeId,
luid: luid,
register_time: now,
access_time: now,
});
await this._txn.modify(insertSql);
return aimeId;
}
}
export class SqlRepositories implements Repositories {
constructor(private readonly _txn: Transaction) {}
cards(): CardRepository {
return new CardRepositoryImpl(this._txn);
}
}
+14 -14
View File
@@ -1,12 +1,13 @@
import addHours = require("date-fns/add_hours");
import express = require("express");
import iconv = require("iconv-lite");
import read = require("raw-body");
import addHours from "date-fns/add_hours";
import express from "express";
import iconv from "iconv-lite";
import logger from "debug";
import read from "raw-body";
import { unzipSync } from "zlib";
import { startupHost, startupUri } from "./switchboard";
const debug = logger("app:allnet");
const hourDelta = parseInt(process.env.HOUR_DELTA || "0");
const app = express();
@@ -14,7 +15,7 @@ const app = express();
// So in the absence of any exotic Transfer-Encoding headers this Content-Type
// is incorrect and we have to override Express' built-in handling.
app.use(async function(req, res, next) {
app.use("/sys/servlet/PowerOn", async function(req, res, next) {
if (req.method !== "POST") {
return res.status(405).end();
}
@@ -58,8 +59,8 @@ app.use(async function(req, res, next) {
return next();
});
app.post("/sys/servlet/PowerOn", function(req, resp) {
console.log("--- Startup Request ---\n\n", req.body);
app.post("/sys/servlet/PowerOn", function(req, res) {
debug("Startup request: %j", req.body);
// Cut milliseconds out of ISO timestamp
@@ -73,14 +74,14 @@ app.post("/sys/servlet/PowerOn", function(req, resp) {
uri: startupUri(req.body.game_id),
host: startupHost(req.body.game_id),
place_id: "123",
name: process.env.SHOP_NAME,
nickname: process.env.SHOP_NAME,
name: process.env.SHOP_NAME || "",
nickname: process.env.SHOP_NAME || "",
region0: "1",
region_name0: "W",
region_name1: "X",
region_name2: "Y",
region_name3: "Z",
country: process.env.SHOP_REGION,
country: process.env.SHOP_REGION || "JPN",
allnet_id: "456",
client_timezone: "+0900",
@@ -90,10 +91,9 @@ app.post("/sys/servlet/PowerOn", function(req, resp) {
token: req.body.token,
};
console.log("\n--- Startup Response ---\n\n", resParams);
console.log("");
debug("Startup response: %j", resParams);
resp.send(resParams);
res.send(resParams);
});
export default app;
+6 -5
View File
@@ -1,5 +1,6 @@
import read = require("raw-body");
import express = require("express");
import express from "express";
import logger from "debug";
import read from "raw-body";
import { createSign } from "crypto";
import { inflateRawSync } from "zlib";
@@ -9,6 +10,7 @@ interface Kvps {
[key: string]: string;
}
const debug = logger("app:billing");
const billingKeyPair = readFileSync("pki/billing.key");
// nearfull: high 16 bits is billing mode, low 16 bits is actual nearfull val.
@@ -60,7 +62,7 @@ app.use(async function(req, res, next) {
});
app.post("/request/", function(req, res) {
console.log("--- Billing Request ---\n\n", req.body);
debug("Billing request: %j", req.body);
const first = req.body[0];
@@ -133,8 +135,7 @@ app.post("/request/", function(req, res) {
playhistory: "000000/0:000000/0:000000/0",
});
console.log("\n--- Billing Response ---\n\n", resItems);
console.log("");
debug("Billing response: %j", resItems);
res.set("content-type", "text/plain");
res.send(resItems);
+138
View File
@@ -0,0 +1,138 @@
import { readdirSync, readFileSync } from "fs";
import logger from "debug";
import { resolve } from "path";
import sql from "sql-bricks-postgres";
import { DataSource, Transaction } from "./sql";
const scripts = [
// Order matters! Later scripts reference schema instantiated earlier.
"meta.sql",
"aime.sql",
"cm.sql",
"idz.sql",
];
const debug = logger("app:checkdb");
const initPath = "./schema/init";
const migratePath = "./schema/migrate";
const migrateRx = new RegExp("^M([0-9]{4})-.+\\.sql$");
async function initdb(txn: Transaction): Promise<void> {
// Get latest schemaver from migration script filenames
const migrations = readdirSync(migratePath);
let schemaver = 0;
for (const filename of migrations) {
const captures = filename.match(migrateRx);
if (captures === null) {
continue;
}
const migrationNo = parseInt(captures[1]);
if (schemaver < migrationNo) {
schemaver = migrationNo;
}
}
// Run init scripts
debug("Initializing database");
for (const script of scripts) {
debug("Executing %s", script);
const scriptPath = resolve(initPath, script);
const scriptSql = readFileSync(scriptPath, "utf-8");
await txn.raw(scriptSql);
}
// Set up metadata table
const metaInsert = sql.insert("meta", { schemaver });
await txn.modify(metaInsert);
debug("Initialized new database to schema version %s", schemaver);
}
async function migratedb(
txn: Transaction,
schemaver: number
): Promise<number | undefined> {
const filenames = readdirSync(migratePath);
let lastMigrationNo: number | undefined;
filenames.sort();
for (const filename of filenames) {
const captures = filename.match(migrateRx);
if (captures === null) {
debug("Warning: Unexpected file %s in SQL migrations dir", filename);
continue;
}
const migrationNo = parseInt(captures[1]);
if (migrationNo <= schemaver) {
continue;
}
debug("Executing database upgrade: %s", filename);
const scriptPath = resolve(migratePath, filename);
const scriptSql = readFileSync(scriptPath, "utf-8");
await txn.raw(scriptSql);
lastMigrationNo = migrationNo;
}
if (lastMigrationNo !== undefined) {
const metaUpdate = sql.update("meta", { schemaver: lastMigrationNo });
await txn.modify(metaUpdate);
}
return lastMigrationNo;
}
export default async function checkdb(db: DataSource): Promise<void> {
let schemaver: number | undefined;
await db.maintenance(async txn => {
const stmt = sql.select("schemaver").from("meta");
try {
const row = await txn.fetchRow(stmt);
if (row !== undefined) {
schemaver = parseInt(row.schemaver!);
}
} catch (e) {
return await initdb(txn);
}
if (schemaver === undefined) {
throw new Error(
"Database corrupted: `meta` table singleton row is missing"
);
}
schemaver = await migratedb(txn, schemaver);
});
if (schemaver !== undefined) {
debug("Upgraded database to version %s", schemaver);
await db.vacuum();
debug("Post-upgrade housekeeping complete");
}
}
-188
View File
@@ -1,188 +0,0 @@
import compression = require("compression");
import express = require("express");
const app = express();
// Thankfully we can use standard middleware for JSON I/O. We have to use a
// zlib middleware as well because compression is non-optional: the client will
// attempt to inflate whatever response we send to it whether there's a
// Transfer-Encoding header or not.
app.use(compression());
app.use(express.json());
// Trace requests and responses
app.use(function(req, resp, next) {
console.log("\n--- Chunithm %s ---\n", req.url);
console.log("Request:", req.body);
const prevJson = resp.json;
resp.json = function(obj) {
console.log("Response:", obj);
resp.json = prevJson;
resp.json.apply(this, arguments);
return resp;
};
next();
});
app.post("/ChuniServlet/GetGameSettingApi", function(req, resp) {
resp.json({
gameSetting: {
dataVersion: 1,
isMaintenance: false,
requestInterval: 10,
rebootStartTime: 0,
rebootEndTime: 0,
isBackgroundDistribute: false,
maxCountCharacter: 999,
maxCountItem: 999,
maxCountMusic: 999,
},
isDumpUpload: false,
isAou: false,
});
});
app.post("/ChuniServlet/UpsertClientSettingApi", function(req, resp) {
resp.json({
returnCode: 1,
apiName: "UpsertClientSettingApi",
});
});
app.post("/ChuniServlet/UpsertClientBookkeepingApi", function(req, resp) {
resp.json({
returnCode: 1,
apiName: "UpsertClientBookkeepingApi",
});
});
app.post("/ChuniServlet/UpsertClientTestmodeApi", function(req, resp) {
resp.json({
returnCode: 1,
apiName: "UpsertClientTestmodeApi",
});
});
app.post("/ChuniServlet/UpsertClientErrorApi", function(req, resp) {
resp.json({
returnCode: 1,
apiName: "UpsertClientErrorApi",
});
});
app.post("/ChuniServlet/UpsertClientDevelopApi", function(req, resp) {
resp.json({
returnCode: 1,
apiName: "UpsertClientDevelopApi",
});
});
app.post("/ChuniServlet/GetGameMessageApi", function(req, resp) {
resp.json({
type: 1,
length: 0,
gameMessageList: [
/* {
type: 2,
id: 1,
message: "true",
startDate: "0",
endDate: "0"
},*/
],
});
});
app.post("/ChuniServlet/GetGameIdlistApi", function(req, resp) {
const { type } = req.body;
resp.json({
type,
length: 0,
gameIdlistList: [],
});
});
app.post("/ChuniServlet/GetGameEventApi", function(req, resp) {
resp.json({
type: 1,
length: 0,
gameEventList: [
/*
{
type: 1,
id: 1102, // data/A000/event/event00001102
startDate: 'STRINGIDK',
endDate: 'STRINGIDK',
},
*/
],
});
});
app.post("/ChuniServlet/GetGameRankingApi", function(req, resp) {
const { type } = req.body;
resp.json({
type,
gameRankingList: [
/*
// QWORD fields maybe?
{
id: 1,
point: 1,
}
*/
],
});
});
app.post("/ChuniServlet/GetGameSaleApi", function(req, resp) {
const { type } = req.body;
resp.json({
type,
length: 0,
gameSaleList: [
/*
{
orderId: 1234,
type,
id: 4321,
rate: 5678,
startDate: 'STRINGIDK',
endDate: 'STRINGIDK',
},
*/
],
});
});
app.post("/ChuniServlet/GetGameChargeApi", function(req, resp) {
resp.json({
length: 0,
gameChargeList: [
/*
{
orderId: 1,
chargeId: 1,
price: 1,
startDate: 'STRINGIDK',
endDate: 'STRINGIDK',
salePrice:
saleStartDate: 'STRINGIDK',
saleEndDate: 'STRINGIDK',
},
*/
],
});
});
export default app;
+13
View File
@@ -0,0 +1,13 @@
// Make the names explicit so that we don't get the order mixed up
interface Params {
maxCount: number;
nextIndex: number;
}
export function paginationCookie<T>(items: T[], params: Params) {
if (items.length < params.maxCount) {
return "-1";
} else {
return (params.nextIndex + params.maxCount).toString();
}
}
+10
View File
@@ -0,0 +1,10 @@
import { Repositories } from "../repo";
import { GameLoginRequest } from "../request/gameLogin";
import { GameLoginResponse } from "../response/gameLogin";
export default async function gameLogin(
rep: Repositories,
req: GameLoginRequest
): Promise<GameLoginResponse> {
return { returnCode: "1" };
}
+10
View File
@@ -0,0 +1,10 @@
import { Repositories } from "../repo";
import { GameLogoutRequest } from "../request/gameLogout";
import { GameLogoutResponse } from "../response/gameLogout";
export default async function gameLogout(
rep: Repositories,
req: GameLogoutRequest
): Promise<GameLogoutResponse> {
return { returnCode: "1" };
}
+30
View File
@@ -0,0 +1,30 @@
import { GameChargeJson } from "../proto/gameCharge";
import { Repositories } from "../repo";
import { GetGameChargeRequest } from "../request/getGameCharge";
import { GetGameChargeResponse } from "../response/getGameCharge";
import { CHARGE_IDS } from "../static/charge";
export default async function getGameCharge(
rep: Repositories,
req: GetGameChargeRequest
): Promise<GetGameChargeResponse> {
const gameChargeList: GameChargeJson[] = [];
for (const [i, charge] of CHARGE_IDS.entries()) {
gameChargeList.push({
chargeId: charge.id.toString(),
orderId: (i+1).toString(),
price: charge.price.toString(),
salePrice: charge.salePrice.toString(),
startDate: "2017-12-05 07:00:00.0",
endDate: "2029-12-31 23:59:59.0",
saleStartDate: "2017-12-05 07:00:00.0",
saleEndDate: "2030-12-31 23:59:59.0"
});
}
return {
length: gameChargeList.length.toString(),
gameChargeList,
};
}
+27
View File
@@ -0,0 +1,27 @@
import { GameEventJson } from "../proto/gameEvent";
import { Repositories } from "../repo";
import { GetGameEventRequest } from "../request/getGameEvent";
import { GetGameEventResponse } from "../response/getGameEvent";
import { EVENT_IDS } from "../static/event";
export default async function getGameEvent(
rep: Repositories,
req: GetGameEventRequest
): Promise<GetGameEventResponse> {
const gameEventList: GameEventJson[] = [];
for (const id of EVENT_IDS) {
gameEventList.push({
type: req.type,
id: id.toString(),
startDate: "2017-12-05 07:00:00.0",
endDate: "2099-12-31 00:00:00.0",
});
}
return {
type: req.type,
length: gameEventList.length.toString(),
gameEventList,
};
}
+14
View File
@@ -0,0 +1,14 @@
import { Repositories } from "../repo";
import { GetGameIdlistRequest } from "../request/getGameIdlist";
import { GetGameIdlistResponse } from "../response/getGameIdlist";
export default async function getGameIdlist(
rep: Repositories,
req: GetGameIdlistRequest
): Promise<GetGameIdlistResponse> {
return {
type: req.type,
length: "0",
gameIdlistList: [],
};
}
+14
View File
@@ -0,0 +1,14 @@
import { Repositories } from "../repo";
import { GetGameMessageRequest } from "../request/getGameMessage";
import { GetGameMessageResponse } from "../response/getGameMessage";
export default async function getGameMessage(
rep: Repositories,
req: GetGameMessageRequest
): Promise<GetGameMessageResponse> {
return {
type: req.type,
length: "0",
gameMessageList: [],
};
}
+13
View File
@@ -0,0 +1,13 @@
import { Repositories } from "../repo";
import { GetGameRankingRequest } from "../request/getGameRanking";
import { GetGameRankingResponse } from "../response/getGameRanking";
export default async function getGameRanking(
rep: Repositories,
req: GetGameRankingRequest
): Promise<GetGameRankingResponse> {
return {
type: req.type,
gameRankingList: [],
};
}
+14
View File
@@ -0,0 +1,14 @@
import { Repositories } from "../repo";
import { GetGameSaleRequest } from "../request/getGameSale";
import { GetGameSaleResponse } from "../response/getGameSale";
export default async function getGameSale(
rep: Repositories,
req: GetGameSaleRequest
): Promise<GetGameSaleResponse> {
return {
type: req.type,
length: "0",
gameSaleList: [],
};
}
+31
View File
@@ -0,0 +1,31 @@
import { writeDate } from "../proto/base";
import { Repositories } from "../repo";
import { GetGameSettingRequest } from "../request/getGameSetting";
import { GetGameSettingResponse } from "../response/getGameSetting";
export default async function getGameSetting(
rep: Repositories,
req: GetGameSettingRequest
): Promise<GetGameSettingResponse> {
const rebootStartTime = new Date();
rebootStartTime.setHours(rebootStartTime.getHours() - 3);
const rebootEndTime = new Date();
rebootEndTime.setHours(rebootEndTime.getHours() - 2);
return {
gameSetting: {
dataVersion: "1",
isMaintenance: "false",
requestInterval: "10",
rebootStartTime: writeDate(rebootStartTime),
rebootEndTime: writeDate(rebootEndTime),
isBackgroundDistribute: "false",
maxCountCharacter: "300",
maxCountItem: "300",
maxCountMusic: "100",
},
isDumpUpload: "false",
isAou: "true",
};
}
+23
View File
@@ -0,0 +1,23 @@
import { readAimeId } from "../proto/base";
import { writeUserActivity } from "../proto/userActivity";
import { Repositories } from "../repo";
import { GetUserActivityRequest } from "../request/getUserActivity";
import { GetUserActivityResponse } from "../response/getUserActivity";
export default async function getUserActivity(
rep: Repositories,
req: GetUserActivityRequest
): Promise<GetUserActivityResponse> {
const aimeId = readAimeId(req.userId);
const kind = parseInt(req.kind);
const profileId = await rep.userData().lookup(aimeId);
const items = await rep.userActivity().load(profileId, kind);
return {
userId: req.userId,
length: items.length.toString(),
kind: kind.toString(),
userActivityList: items.map(writeUserActivity),
};
}
+27
View File
@@ -0,0 +1,27 @@
import { paginationCookie } from "./_util";
import { readAimeId } from "../proto/base";
import { writeUserCharacter } from "../proto/userCharacter";
import { Repositories } from "../repo";
import { GetUserCharacterRequest } from "../request/getUserCharacter";
import { GetUserCharacterResponse } from "../response/getUserCharacter";
export default async function getUserCharacter(
rep: Repositories,
req: GetUserCharacterRequest
): Promise<GetUserCharacterResponse> {
const aimeId = readAimeId(req.userId);
const maxCount = parseInt(req.maxCount);
const nextIndex = parseInt(req.nextIndex);
const profileId = await rep.userData().lookup(aimeId);
const items = await rep
.userCharacter()
.load(profileId, { limit: maxCount, offset: nextIndex });
return {
userId: req.userId,
length: items.length.toString(),
nextIndex: paginationCookie(items, { maxCount, nextIndex }),
userCharacterList: items.map(writeUserCharacter),
};
}
+21
View File
@@ -0,0 +1,21 @@
import { readAimeId } from "../proto/base";
import { writeUserCharge } from "../proto/userCharge";
import { Repositories } from "../repo";
import { GetUserChargeRequest } from "../request/getUserCharge";
import { GetUserChargeResponse } from "../response/getUserCharge";
export default async function getUserCharge(
rep: Repositories,
req: GetUserChargeRequest
): Promise<GetUserChargeResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
const items = await rep.userCharge().load(profileId);
return {
userId: req.userId,
length: items.length.toString(),
userChargeList: items.map(writeUserCharge),
};
}
+27
View File
@@ -0,0 +1,27 @@
import { Repositories } from "../repo";
import { GetUserCourseRequest } from "../request/getUserCourse";
import { GetUserCourseResponse } from "../response/getUserCourse";
import { readAimeId } from "../proto/base";
import { paginationCookie } from "./_util";
import { writeUserCourse } from "../proto/userCourse";
export default async function getUserCourse(
rep: Repositories,
req: GetUserCourseRequest
): Promise<GetUserCourseResponse> {
const aimeId = readAimeId(req.userId);
const maxCount = parseInt(req.maxCount);
const nextIndex = parseInt(req.nextIndex);
const profileId = await rep.userData().lookup(aimeId);
const items = await rep
.userCourse()
.load(profileId, { limit: maxCount, offset: nextIndex });
return {
userId: req.userId,
length: items.length.toString(),
nextIndex: paginationCookie(items, { maxCount, nextIndex }),
userCourseList: items.map(writeUserCourse)
};
}
+20
View File
@@ -0,0 +1,20 @@
import { readAimeId } from "../proto/base";
import { writeUserData } from "../proto/userData";
import { Repositories } from "../repo";
import { GetUserDataRequest } from "../request/getUserData";
import { GetUserDataResponse } from "../response/getUserData";
export default async function getUserData(
rep: Repositories,
req: GetUserDataRequest
): Promise<GetUserDataResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
const userData = await rep.userData().load(profileId);
return {
userId: req.userId,
userData: writeUserData(userData),
};
}
+20
View File
@@ -0,0 +1,20 @@
import { readAimeId } from "../proto/base";
import { writeUserDataEx } from "../proto/userDataEx";
import { Repositories } from "../repo";
import { GetUserDataExRequest } from "../request/getUserDataEx";
import { GetUserDataExResponse } from "../response/getUserDataEx";
export default async function getUserDataEx(
rep: Repositories,
req: GetUserDataExRequest
): Promise<GetUserDataExResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
const userDataEx = await rep.userDataEx().load(profileId);
return {
userId: req.userId,
userDataEx: writeUserDataEx(userDataEx),
};
}
+23
View File
@@ -0,0 +1,23 @@
import { Repositories } from "../repo";
import { GetUserDuelRequest } from "../request/getUserDuel";
import { GetUserDuelResponse } from "../response/getUserDuel";
import { readAimeId } from "../proto/base";
import { writeUserDuelList } from "../proto/userDuelList";
export default async function getUserDuel(
rep: Repositories,
req: GetUserDuelRequest
): Promise<GetUserDuelResponse> {
const aimeId = readAimeId(req.userId);
// Get all duel entities if the client specifies to.
const duelId = req.isAllDuel ? undefined : parseInt(req.duelId);
const profileId = await rep.userData().lookup(aimeId);
const items = await rep.userDuelList().load(profileId, duelId);
return {
userId: req.userId,
length: items.length.toString(),
userDuelList: items.map(writeUserDuelList),
};
}
@@ -0,0 +1,23 @@
import { Repositories } from "../repo";
import { GetUserFavoriteMusicRequest } from "../request/getUserFavoriteMusic";
import { GetUserFavoriteMusicResponse } from "../response/getUserFavoriteMusic";
import { readAimeId } from "../proto/base";
export default async function getUserFavoriteMusic(
rep: Repositories,
req: GetUserFavoriteMusicRequest
): Promise<GetUserFavoriteMusicResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
/*
* `Chunithm Amazon Plus` does not appear to save a favorites list and there
* is no user-accessible favorites function from what I can tell.
*/
return {
userId: req.userId,
length: "0",
userFavoriteMusicList: [],
};
}
+61
View File
@@ -0,0 +1,61 @@
import { readAimeId } from "../proto/base";
import { writeUserItem } from "../proto/userItem";
import { Repositories } from "../repo";
import { GetUserItemRequest } from "../request/getUserItem";
import { GetUserItemResponse } from "../response/getUserItem";
import { UserItemItem } from "../model/userItem";
// I'm not sure why they went to all this trouble just to avoid passing an
// explicit `itemKind` parameter in the request...
const itemKindMul = 10000000000n;
export default async function getUserItem(
rep: Repositories,
req: GetUserItemRequest
): Promise<GetUserItemResponse> {
// Split the "nextIndex" parameter
const compoundIn = BigInt(req.nextIndex);
const itemKindN = compoundIn / itemKindMul;
const nextIndexN = compoundIn % itemKindMul;
// Now unpack the entire request
const aimeId = readAimeId(req.userId);
const maxCount = parseInt(req.maxCount);
const itemKind = parseInt(itemKindN as any);
const nextIndex = parseInt(nextIndexN as any);
// Hit DB
// This gets called for not-yet-created profiles for some reason (probably
// so that the server can force some bonus items into a new profile's
// inventory)? So we need to gracefully handle that condition.
// (maybe some sort of anchor row is created on first GameLogin...)
const profileId = await rep.userData().tryLookup(aimeId);
let items: UserItemItem[];
if (profileId !== undefined) {
items = await rep
.userItem()
.load(profileId, itemKind, { limit: maxCount, offset: nextIndex });
} else {
items = [];
}
// Pack the next pagination cookie into bigint compound form
const xout = itemKindN * itemKindMul + nextIndexN + BigInt(items.length);
// Done:
return {
userId: req.userId,
length: items.length.toString(),
nextIndex: items.length < maxCount ? "-1" : xout.toString(),
itemKind: itemKind.toString(),
userItemList: items.map(writeUserItem),
};
}
+21
View File
@@ -0,0 +1,21 @@
import { readAimeId } from "../proto/base";
import { writeUserMap } from "../proto/userMap";
import { Repositories } from "../repo";
import { GetUserMapRequest } from "../request/getUserMap";
import { GetUserMapResponse } from "../response/getUserMap";
export default async function getUserMap(
rep: Repositories,
req: GetUserMapRequest
): Promise<GetUserMapResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
const items = await rep.userMap().load(profileId);
return {
userId: req.userId,
length: items.length.toString(),
userMapList: items.map(writeUserMap),
};
}
+50
View File
@@ -0,0 +1,50 @@
import { writeUserMusicDetail, UserMusicJson } from "../proto/userMusic";
import { Repositories } from "../repo";
import { GetUserMusicRequest } from "../request/getUserMusic";
import { GetUserMusicResponse } from "../response/getUserMusic";
import { readAimeId } from "../proto/base";
import { UserMusicDetailItem } from "../model/userMusic";
import { paginationCookie } from "./_util";
export default async function getUserMusic(
rep: Repositories,
req: GetUserMusicRequest
): Promise<GetUserMusicResponse> {
const aimeId = readAimeId(req.userId);
const maxCount = parseInt(req.maxCount);
const nextIndex = parseInt(req.nextIndex);
const profileId = await rep.userData().lookup(aimeId);
const flat = await rep
.userMusic()
.load(profileId, { limit: maxCount, offset: nextIndex });
const groupIdx = new Map<number, UserMusicDetailItem[]>();
for (const item of flat) {
const { musicId } = item;
const existing = groupIdx.get(musicId);
if (existing !== undefined) {
existing.push(item);
} else {
groupIdx.set(musicId, [item]);
}
}
const userMusicList = new Array<UserMusicJson>();
for (const group of groupIdx.values()) {
userMusicList.push({
length: group.length.toString(),
userMusicDetailList: group.map(writeUserMusicDetail),
});
}
return {
userId: req.userId,
length: userMusicList.length.toString(),
nextIndex: paginationCookie(flat, { maxCount, nextIndex }),
userMusicList,
};
}
+20
View File
@@ -0,0 +1,20 @@
import { readAimeId } from "../proto/base";
import { writeUserGameOption } from "../proto/userGameOption";
import { Repositories } from "../repo";
import { GetUserOptionRequest } from "../request/getUserOption";
import { GetUserOptionResponse } from "../response/getUserOption";
export default async function getUserOption(
rep: Repositories,
req: GetUserOptionRequest
): Promise<GetUserOptionResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
const userGameOption = await rep.userGameOption().load(profileId);
return {
userId: req.userId,
userGameOption: writeUserGameOption(userGameOption),
};
}
+20
View File
@@ -0,0 +1,20 @@
import { readAimeId } from "../proto/base";
import { writeUserGameOptionEx } from "../proto/userGameOptionEx";
import { Repositories } from "../repo";
import { GetUserOptionExRequest } from "../request/getUserOptionEx";
import { GetUserOptionExResponse } from "../response/getUserOptionEx";
export default async function getUserOptionEx(
rep: Repositories,
req: GetUserOptionExRequest
): Promise<GetUserOptionExResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
const userGameOptionEx = await rep.userGameOptionEx().load(profileId);
return {
userId: req.userId,
userGameOptionEx: writeUserGameOptionEx(userGameOptionEx),
};
}
+58
View File
@@ -0,0 +1,58 @@
import { readAimeId, writeDate } from "../proto/base";
import { writeUserCharacter } from "../proto/userCharacter";
import { Repositories } from "../repo";
import { GetUserPreviewRequest } from "../request/getUserPreview";
import { GetUserPreviewResponse } from "../response/getUserPreview";
export default async function getUserPreview(
rep: Repositories,
req: GetUserPreviewRequest
): Promise<GetUserPreviewResponse | null> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().tryLookup(aimeId);
// Return an empty JSON object if the player lacks a profile; this will
// start the new profile registration flow in the client.
if (profileId === undefined) {
return null;
}
const userData = await rep.userData().load(profileId);
const userCharacter = await rep
.userCharacter()
.loadOne(profileId, userData.characterId);
const userGameOption = await rep.userGameOption().load(profileId);
return {
userId: "1",
// Current login (i.e. profile lock) status
isLogin: "false",
lastLoginDate: "1970-01-01 09:00:00",
// UserData:
userName: userData.userName,
reincarnationNum: userData.reincarnationNum.toString(),
level: userData.level.toString(),
exp: userData.exp.toString(),
playerRating: userData.playerRating.toString(),
lastGameId: userData.lastGameId,
lastRomVersion: userData.lastRomVersion,
lastDataVersion: userData.lastDataVersion,
lastPlayDate: writeDate(userData.lastPlayDate),
trophyId: userData.trophyId.toString(),
// Currently selected UserCharacter:
userCharacter: writeUserCharacter(userCharacter),
// UserGameOption:
playerLevel: userGameOption.playerLevel.toString(),
rating: userGameOption.rating.toString(),
headphone: userGameOption.headphone.toString(),
};
}
@@ -0,0 +1,22 @@
import { Repositories } from "../repo";
import { GetUserRecentRatingRequest } from "../request/getUserRecentRating";
import { GetUserRecentRatingResponse } from "../response/getUserRecentRating";
import { readAimeId } from "../proto/base";
import { writeUserRecentRating } from "../proto/userRecentRating";
export default async function getUserRecentRating(
rep: Repositories,
req: GetUserRecentRatingRequest
): Promise<GetUserRecentRatingResponse> {
const aimeId = readAimeId(req.userId);
const profileId = await rep.userData().lookup(aimeId);
// Return recent 30 plays to calculate rating
const items = await rep.userRecentRating().load(profileId);
return {
userId: req.userId,
length: items.length.toString(),
userRecentRatingList: items.map(writeUserRecentRating),
};
}
+16
View File
@@ -0,0 +1,16 @@
import { Repositories } from "../repo";
import { GetUserRegionRequest } from "../request/getUserRegion";
import { GetUserRegionResponse } from "../response/getUserRegion";
export default async function getUserRegion(
rep: Repositories,
req: GetUserRegionRequest
): Promise<GetUserRegionResponse> {
return {
userId: req.userId,
length: "0",
userRegionList: [
// Doesn't get saved
],
};
}
+131
View File
@@ -0,0 +1,131 @@
import compression from "compression";
import express, { NextFunction, Request, Response } from "express";
import logger from "debug";
import gameLogin from "./gameLogin";
import gameLogout from "./gameLogout";
import getGameCharge from "./getGameCharge";
import getGameEvent from "./getGameEvent";
import getGameIdlist from "./getGameIdlist";
import getGameMessage from "./getGameMessage";
import getGameRanking from "./getGameRanking";
import getGameSale from "./getGameSale";
import getGameSetting from "./getGameSetting";
import getUserActivity from "./getUserActivity";
import getUserCharacter from "./getUserCharacter";
import getUserCharge from "./getUserCharge";
import getUserCourse from "./getUserCourse";
import getUserData from "./getUserData";
import getUserDataEx from "./getUserDataEx";
import getUserDuel from "./getUserDuel";
import getUserFavoriteMusic from "./getUserFavoriteMusic";
import getUserItem from "./getUserItem";
import getUserMap from "./getUserMap";
import getUserMusic from "./getUserMusic";
import getUserOption from "./getUserOption";
import getUserOptionEx from "./getUserOptionEx";
import getUserPreview from "./getUserPreview";
import getUserRecentRating from "./getUserRecentRating";
import getUserRegion from "./getUserRegion";
import upsertClientBookkeeping from "./upsertClientBookkeeping";
import upsertClientDevelop from "./upsertClientDevelop";
import upsertClientError from "./upsertClientError";
import upsertClientSetting from "./upsertClientSetting";
import upsertClientTestmode from "./upsertClientTestmode";
import upsertUserAll from "./upsertUserAll";
import upsertUserChargelogApi from "./upsertUserChargelogApi";
import createSqlWrapper from "../sql";
import { DataSource } from "../../sql";
const debug = logger("app:chuni:io");
// Thankfully we can use standard middleware for JSON I/O. We have to use a
// zlib middleware as well because compression is non-optional: the client will
// attempt to inflate whatever response we send to it whether there's a
// Transfer-Encoding header or not.
function quirks(req: Request, res: Response, next: NextFunction) {
// Chunithm client does not follow the HTTP spec: user agents must indicate
// that they are willing to accept a particular Transfer-Encoding for that
// T-E to be used (unless the T-E in question is "chunked" but w/e), and this
// indication is advisory anyway, the server is permitted to ignore it.
//
// Force this header into the request, since it is not present normally.
// ... unless, that is, the client is actually somebody debugging with curl
// and not the real game client, in which case this forced deflate can be
// annoying. Pass an "X-Debug" request header to suppress forced compression.
if (req.headers["x-debug"] === undefined) {
req.headers["accept-encoding"] = "deflate";
}
return next();
}
function trace(req: Request, res: Response, next: NextFunction) {
debug("\n--- Chunithm %s ---\n", req.url);
debug("Request: %j", req.body);
const prevJson = res.json;
res.json = function(obj) {
debug("Response: %j", obj);
res.json = prevJson;
res.json.apply(this, arguments);
return res;
};
return next();
}
export default function chunithm(db: DataSource) {
const wrapper = createSqlWrapper(db);
wrapper.rpc("/GameLoginApi", gameLogin);
wrapper.rpc("/GameLogoutApi", gameLogout);
wrapper.rpc("/GetGameChargeApi", getGameCharge);
wrapper.rpc("/GetGameEventApi", getGameEvent);
wrapper.rpc("/GetGameIdlistApi", getGameIdlist);
wrapper.rpc("/GetGameMessageApi", getGameMessage);
wrapper.rpc("/GetGameRankingApi", getGameRanking);
wrapper.rpc("/GetGameSaleApi", getGameSale);
wrapper.rpc("/GetGameSettingApi", getGameSetting);
wrapper.rpc("/GetUserActivityApi", getUserActivity);
wrapper.rpc("/GetUserCharacterApi", getUserCharacter);
wrapper.rpc("/GetUserChargeApi", getUserCharge);
wrapper.rpc("/GetUserCourseAPi", getUserCourse);
wrapper.rpc("/GetUserDataApi", getUserData);
wrapper.rpc("/GetUserDataExApi", getUserDataEx);
wrapper.rpc("/GetUserDuelApi", getUserDuel);
wrapper.rpc("/GetUserFavoriteMusicApi", getUserFavoriteMusic);
wrapper.rpc("/GetUserItemApi", getUserItem);
wrapper.rpc("/GetUserMapApi", getUserMap);
wrapper.rpc("/GetUserMusicApi", getUserMusic);
wrapper.rpc("/GetUserOptionApi", getUserOption);
wrapper.rpc("/GetUserOptionExApi", getUserOptionEx);
wrapper.rpc("/GetUserPreviewApi", getUserPreview);
wrapper.rpc("/GetUserRecentPlayerApi", getUserRecentRating);
wrapper.rpc("/GetUserRecentRatingApi", getUserRecentRating);
wrapper.rpc("/GetUserRegionApi", getUserRegion);
wrapper.rpc("/UpsertClientBookkeepingApi", upsertClientBookkeeping);
wrapper.rpc("/UpsertClientDevelopApi", upsertClientDevelop);
wrapper.rpc("/UpsertClientErrorApi", upsertClientError);
wrapper.rpc("/UpsertClientSettingApi", upsertClientSetting);
wrapper.rpc("/UpsertClientTestmodeApi", upsertClientTestmode);
wrapper.rpc("/UpsertUserAllApi", upsertUserAll);
wrapper.rpc("/UpsertUserChargelogApi", upsertUserChargelogApi)
const app = express();
app.use(quirks);
app.use(compression({ threshold: 0 }));
app.use(express.json({ limit: "50mb" })); // that ought to be enough
app.use(trace);
app.use("/ChuniServlet", wrapper);
return app;
}
@@ -0,0 +1,12 @@
import { Repositories } from "../repo";
import { UpsertClientBookkeepingRequest } from "../request/upsertClientBookkeeping";
import { UpsertClientBookkeepingResponse } from "../response/upsertClientBookkeeping";
export default async function upsertClientBookkeeping(
rep: Repositories,
req: UpsertClientBookkeepingRequest
): Promise<UpsertClientBookkeepingResponse> {
return {
returnCode: "1",
};
}
@@ -0,0 +1,12 @@
import { Repositories } from "../repo";
import { UpsertClientDevelopRequest } from "../request/upsertClientDevelop";
import { UpsertClientDevelopResponse } from "../response/upsertClientDevelop";
export default async function upsertClientDevelop(
rep: Repositories,
req: UpsertClientDevelopRequest
): Promise<UpsertClientDevelopResponse> {
return {
returnCode: "1",
};
}
+12
View File
@@ -0,0 +1,12 @@
import { Repositories } from "../repo";
import { UpsertClientErrorRequest } from "../request/upsertClientError";
import { UpsertClientErrorResponse } from "../response/upsertClientError";
export default async function upsertClientError(
rep: Repositories,
req: UpsertClientErrorRequest
): Promise<UpsertClientErrorResponse> {
return {
returnCode: "1",
};
}
@@ -0,0 +1,12 @@
import { Repositories } from "../repo";
import { UpsertClientSettingRequest } from "../request/upsertClientSetting";
import { UpsertClientSettingResponse } from "../response/upsertClientSetting";
export default async function upsertClientSetting(
rep: Repositories,
req: UpsertClientSettingRequest
): Promise<UpsertClientSettingResponse> {
return {
returnCode: "1",
};
}
@@ -0,0 +1,12 @@
import { Repositories } from "../repo";
import { UpsertClientTestmodeRequest } from "../request/upsertClientTestmode";
import { UpsertClientTestmodeResponse } from "../response/upsertClientTestmode";
export default async function upsertClientTestmode(
rep: Repositories,
req: UpsertClientTestmodeRequest
): Promise<UpsertClientTestmodeResponse> {
return {
returnCode: "1",
};
}
+96
View File
@@ -0,0 +1,96 @@
import { Repositories } from "../repo";
import { readAimeId } from "../proto/base";
import { readUserCharacter } from "../proto/userCharacter";
import { readUserData } from "../proto/userData";
import { readUserGameOption } from "../proto/userGameOption";
import { UpsertUserAllRequest } from "../request/upsertUserAll";
import { UpsertUserAllResponse } from "../response/upsertUserAll";
import { readUserGameOptionEx } from "../proto/userGameOptionEx";
import { readUserMap } from "../proto/userMap";
import { readUserItem } from "../proto/userItem";
import { readUserMusicDetail } from "../proto/userMusic";
import { readUserActivity } from "../proto/userActivity";
import { readUserDataEx } from "../proto/userDataEx";
import { readUserDuelList } from "../proto/userDuelList";
import { readUserPlaylog } from "../proto/userPlaylog";
import { readUserCharge } from "../proto/userCharge";
import { readUserRecentRating } from "../proto/userRecentRating";
import { readUserCourse } from "../proto/userCourse";
// It shouldn't need to be said really, but seeing as this message (A) requires
// fairly lengthy processing and (B) is only sent right at the end of a credit,
// you should probably copy the JSON from the server's debug log and then
// validate this implementation using curl if you're planning to make any
// modifications to how things get saved.
export default async function upsertUserAll(
rep: Repositories,
req: UpsertUserAllRequest
): Promise<UpsertUserAllResponse> {
const aimeId = readAimeId(req.userId);
const payload = req.upsertUserAll;
const profile = readUserData(req.upsertUserAll.userData[0]);
const profileId = await rep.userData().save(aimeId, profile);
// Empty lists don't get sent at all. Not sure which are potentially empty
// so let's just assume the answer is "all of them". Hence the use of || []
// everywhere.
for (const item of payload.userGameOption || []) {
await rep.userGameOption().save(profileId, readUserGameOption(item));
}
for (const item of payload.userGameOptionEx || []) {
await rep.userGameOptionEx().save(profileId, readUserGameOptionEx(item));
}
for (const item of payload.userMapList || []) {
await rep.userMap().save(profileId, readUserMap(item));
}
for (const item of payload.userCharacterList || []) {
await rep.userCharacter().save(profileId, readUserCharacter(item));
}
for (const item of payload.userItemList || []) {
await rep.userItem().save(profileId, readUserItem(item));
}
for (const item of payload.userMusicDetailList || []) {
await rep.userMusic().save(profileId, readUserMusicDetail(item));
}
for (const item of payload.userActivityList || []) {
await rep.userActivity().save(profileId, readUserActivity(item));
}
for (const item of payload.userPlaylogList || []) {
await rep.userPlaylog().save(profileId, readUserPlaylog(item));
}
for (const item of payload.userChargeList || []) {
await rep.userCharge().save(profileId, readUserCharge(item));
}
for (const item of payload.userCourseList || []) {
await rep.userCourse().save(profileId, readUserCourse(item));
}
for (const item of payload.userDataEx || []) {
await rep.userDataEx().save(profileId, readUserDataEx(item));
}
for (const item of payload.userDuelList || []) {
await rep.userDuelList().save(profileId, readUserDuelList(item));
}
await rep
.userRecentRating()
.save(
profileId,
(payload.userRecentRatingList || []).map(readUserRecentRating)
);
return { returnCode: "1" };
}
@@ -0,0 +1,12 @@
import { Repositories } from "../repo";
import { UpsertUserChargelogApiRequest } from "../request/upsertUserChargelogApi";
import { UpsertUserChargelogApiResponse } from "../response/upsertUserChargelogApi";
export default async function UpsertUserChargelogApi(
rep: Repositories,
req: UpsertUserChargelogApiRequest
): Promise<UpsertUserChargelogApiResponse> {
return {
returnCode: "1",
};
}
+2
View File
@@ -0,0 +1,2 @@
import chunithm from "./handler";
export default chunithm;
+10
View File
@@ -0,0 +1,10 @@
export interface GameChargeItem {
orderId: number;
chargeId: number;
price: number;
startDate: Date;
endDate: Date;
salePrice: number;
saleStartDate: Date;
saleEndDate: Date;
}
+6
View File
@@ -0,0 +1,6 @@
export interface GameEventItem {
type: number;
id: number;
startDate: Date;
endDate: Date;
}
+7
View File
@@ -0,0 +1,7 @@
export interface GameMessageItem {
type: number;
id: number;
message: string;
startDate: Date;
endDate: Date;
}
+4
View File
@@ -0,0 +1,4 @@
export interface GameRankingItem {
id: bigint;
point: bigint;
}
+8
View File
@@ -0,0 +1,8 @@
export interface GameSaleItem {
orderId: number;
type: number;
id: number;
rate: number;
startDate: Date;
endDate: Date;
}
+9
View File
@@ -0,0 +1,9 @@
export interface UserActivityItem {
kind: number;
activityId: number; // This is just called "id" in the JSON!
sortNumber: number;
param1: number;
param2: number;
param3: number;
param4: number;
}
+11
View File
@@ -0,0 +1,11 @@
export interface UserCharacterItem {
characterId: number;
playCount: number;
level: number;
skillId: number;
friendshipExp: number;
isValid: boolean;
isNewMark: boolean;
param1: number;
param2: number;
}
+9
View File
@@ -0,0 +1,9 @@
export interface UserChargeItem {
chargeId: number;
stock: number;
purchaseDate: Date;
validDate: Date;
param1: number;
param2: number;
paramDate: Date;
}
+17
View File
@@ -0,0 +1,17 @@
export interface UserCourseItem {
courseId: number;
classId: number;
playCount: number;
scoreMax: number;
isFullCombo: boolean;
isAllJustice: boolean;
isSuccess: boolean;
scoreRank: number;
eventId: number;
lastPlayDate: Date;
param1: number;
param2: number;
param3: number;
param4: number;
isClear: boolean;
}
+66
View File
@@ -0,0 +1,66 @@
export interface UserDataItem {
/** NFC LUID */
accessCode: string;
/** Uses wide latin chars */
userName: string;
isWebJoin: boolean;
webLimitDate: Date;
level: number;
reincarnationNum: number;
exp: number;
point: number;
totalPoint: bigint;
playCount: number;
multiPlayCount: number;
multiWinCount: number;
requestResCount: number;
acceptResCount: number;
successResCount: number;
playerRating: number;
highestRating: number;
nameplateId: number;
frameId: number;
characterId: number;
trophyId: number;
playedTutorialBit: number;
firstTutorialCancelNum: number;
masterTutorialCancelNum: number;
totalRepertoireCount: number;
totalMapNum: number;
totalHiScore: bigint;
totalBasicHighScore: bigint;
totalAdvancedHighScore: bigint;
totalExpertHighScore: bigint;
totalMasterHighScore: bigint;
eventWatchedDate: Date;
friendCount: number;
isMaimai: boolean;
firstGameId: string;
firstRomVersion: string;
firstDataVersion: string;
firstPlayDate: Date;
lastGameId: string;
lastRomVersion: string;
lastDataVersion: string;
lastPlayDate: Date;
/** ALLNet place ID */
lastPlaceId: number;
/** ALLNet place name */
lastPlaceName: string;
/** ALLNet "region0" */
lastRegionId: string;
/** ALLNet "region_name0" */
lastRegionName: string;
/** ALLNet "allnet_id" string */
lastAllNetId: string;
/** Keychip ID */
lastClientId: string;
}
+36
View File
@@ -0,0 +1,36 @@
export interface UserDataExItem {
compatibleCmVersion: string;
medal: number;
mapIconId: number;
voiceId: number;
ext1: number;
ext2: number;
ext3: number;
ext4: number;
ext5: number;
ext6: number;
ext7: number;
ext8: number;
ext9: number;
ext10: number;
ext11: number;
ext12: number;
ext13: number;
ext14: number;
ext15: number;
ext16: number;
ext17: number;
ext18: number;
ext19: number;
ext20: number;
extStr1: string;
extStr2: string;
extStr3: string;
extStr4: string;
extStr5: string;
extLong1: bigint;
extLong2: bigint;
extLong3: bigint;
extLong4: bigint;
extLong5: bigint;
}
+11
View File
@@ -0,0 +1,11 @@
export interface UserDuelListItem {
duelId: number;
progress: number;
point: number;
isClear: boolean;
lastPlayDate: Date;
param1: number;
param2: number;
param3: number;
param4: number;
}
+24
View File
@@ -0,0 +1,24 @@
export interface UserGameOptionItem {
bgInfo: number;
fieldColor: number;
guideSound: number;
soundEffect: number;
guideLine: number;
speed: number;
optionSet: number;
matching: number;
judgePos: number;
rating: number;
judgeJustice: number;
judgeAttack: number;
headphone: number;
playerLevel: number;
successTap: number;
successExTap: number;
successSlideHold: number;
successAir: number;
successFlick: number;
successSkill: number;
successTapTimbre: number;
privacy: number;
}

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