1616 along with this program. If not, see <https://www.gnu.org/licenses/>.
1717*/
1818#include " propa_rank.h"
19+ #include " kage.h"
1920#include < dcserver/json.hpp>
2021
2122using namespace std ::chrono_literals;
@@ -66,26 +67,118 @@ void RankConnection::onReceive(const std::error_code& ec, size_t len)
6667 socket.shutdown (asio::socket_base::shutdown_both, ignored);
6768 return ;
6869 }
69- if (len >= 0x34 )
70+ if (len < 0x34 ) {
71+ ERROR_LOG (Game::PropellerA, " Packet too short: %zx" , len);
72+ }
73+ else
7074 {
71- std::string username = (const char *)&recvBuffer[0x14 ];
72- Statement stmt (database, " SELECT kills, wins, games, flightTime, flightDistance, shotDown, points, rank from ranking where user_id = ?" );
73- stmt.bind (1 , username);
74- // total kills, number of wins, number of games, total flight time, total flight distance,
75- // number of times shot down, total points, rank
76- // ranks: 0: none, 1:general, 2:lieutenant general, 3:major general, 4:colonel
77- // 5:lieutenant colonel, 6:major, 7:captain, 8:first lieutenant, 9:second lieutenant,
78- // 10:sergeant, 11:senior airman, 12:airman first class, 13:airman, 14:airman basic, 15:none...
79- // after game: 1st: 10 pts, 2nd: 5 pts, 3rd: 2 pts
80- std::vector<uint32_t > data (8 );
81- if (!stmt.step ()) {
82- data[7 ] = ntohl (14 );
75+ uint32_t op = read32 (recvBuffer.data (), 0 );
76+ if (op == 1 )
77+ {
78+ // Get online ranking
79+ std::string username = (const char *)&recvBuffer[0x14 ];
80+ Statement stmt (database, " SELECT kills, wins, games, flightTime, flightDistance, shotDown, points, rank from ranking where user_id = ?" );
81+ stmt.bind (1 , username);
82+ // total kills, number of wins, number of games, total flight time, total flight distance,
83+ // number of times shot down, total points, rank
84+ // ranks: 0: none, 1:general, 2:lieutenant general, 3:major general, 4:colonel
85+ // 5:lieutenant colonel, 6:major, 7:captain, 8:first lieutenant, 9:second lieutenant,
86+ // 10:sergeant, 11:senior airman, 12:airman first class, 13:airman, 14:airman basic, 15:none...
87+ // after game: 1st: 10 pts, 2nd: 5 pts, 3rd: 2 pts
88+ std::vector<uint32_t > data (8 );
89+ if (!stmt.step ()) {
90+ data[7 ] = ntohl (14 );
91+ }
92+ else {
93+ for (int i = 0 ; i < 8 ; i++)
94+ data[i] = ntohl (stmt.getIntColumn (i));
95+ }
96+ send (data);
8397 }
84- else {
85- for (int i = 0 ; i < 8 ; i++)
86- data[i] = ntohl (stmt.getIntColumn (i));
98+ else if (op == 5 )
99+ {
100+ // Update training data
101+ std::string username = (const char *)&recvBuffer[0x14 ];
102+ uint32_t size = read32 (recvBuffer.data (), 0x38 );
103+ if (size != 3 * (8 + 2 + 1 ) * sizeof (uint32_t )) {
104+ std::vector<uint32_t > data (1 );
105+ send (data);
106+ }
107+ else
108+ {
109+ // 3 categories (Stunt, Challenge, Agility)
110+ // 8 scores
111+ // 2 bonus scores(?)
112+ // total score
113+ uint32_t scores[3 ][8 ];
114+ uint32_t bonuses[3 ][2 ];
115+ uint32_t totals[3 ];
116+ unsigned offset = 0x3c ;
117+ for (unsigned cat = 0 ; cat < 3 ; cat++)
118+ {
119+ for (unsigned score = 0 ; score < 8 ; score++, offset += 4 )
120+ scores[cat][score] = read32 (recvBuffer.data (), offset);
121+ for (unsigned bonus = 0 ; bonus < 2 ; bonus++, offset += 4 )
122+ bonuses[cat][bonus] = read32 (recvBuffer.data (), offset);
123+ totals[cat] = read32 (recvBuffer.data (), offset);
124+ offset += 4 ;
125+ }
126+ uint32_t resp = 0x10 ;
127+ Statement stmt (database, " SELECT cat1total, cat2total, cat3total from training where user_id = ?" );
128+ stmt.bind (1 , username);
129+ const char *sqlString;
130+ if (stmt.step ())
131+ {
132+ if ((int )totals[0 ] > stmt.getIntColumn (0 ))
133+ resp |= 1 ;
134+ if ((int )totals[1 ] < stmt.getIntColumn (1 ))
135+ resp |= 2 ;
136+ if ((int )totals[2 ] < stmt.getIntColumn (2 ))
137+ resp |= 4 ;
138+ sqlString = " UPDATE training SET "
139+ " cat1score1=?, cat1score2=?, cat1score3=?, cat1score4=?, cat1score5=?, cat1score6=?, cat1score7=?, cat1score8=?, "
140+ " cat1bonus1=?, cat1bonus2=?, cat1total=?, "
141+ " cat2score1=?, cat2score2=?, cat2score3=?, cat2score4=?, cat2score5=?, cat2score6=?, cat2score7=?, cat2score8=?, "
142+ " cat2bonus1=?, cat2bonus2=?, cat2total=?, "
143+ " cat3score1=?, cat3score2=?, cat3score3=?, cat3score4=?, cat3score5=?, cat3score6=?, cat3score7=?, cat3score8=?, "
144+ " cat3bonus1=?, cat3bonus2=?, cat3total=? WHERE user_id=?" ;
145+ }
146+ else
147+ {
148+ resp |= 7 ;
149+ sqlString = " INSERT INTO training ("
150+ " cat1score1, cat1score2, cat1score3, cat1score4, cat1score5, cat1score6, cat1score7, cat1score8,"
151+ " cat1bonus1, cat1bonus2, cat1total,"
152+ " cat2score1, cat2score2, cat2score3, cat2score4, cat2score5, cat2score6, cat2score7, cat2score8,"
153+ " cat2bonus1, cat2bonus2, cat2total,"
154+ " cat3score1, cat3score2, cat3score3, cat3score4, cat3score5, cat3score6, cat3score7, cat3score8,"
155+ " cat3bonus1, cat3bonus2, cat3total,"
156+ " user_id) "
157+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" ;
158+ }
159+ {
160+ Statement stmt (database, sqlString);
161+ int column = 1 ;
162+ for (unsigned cat = 0 ; cat < 3 ; cat++)
163+ {
164+ for (unsigned score = 0 ; score < 8 ; score++, offset += 4 )
165+ stmt.bind (column++, scores[cat][score]);
166+ for (unsigned bonus = 0 ; bonus < 2 ; bonus++, offset += 4 )
167+ stmt.bind (column++, bonuses[cat][bonus]);
168+ stmt.bind (column++, totals[cat]);
169+ }
170+ stmt.bind (column++, username);
171+ stmt.step ();
172+ }
173+
174+ std::vector<uint32_t > data (4 );
175+ write32 ((uint8_t *)data.data (), 0 , resp);
176+ write32 ((uint8_t *)data.data (), 4 , totals[0 ]);
177+ write32 ((uint8_t *)data.data (), 8 , totals[1 ]);
178+ write32 ((uint8_t *)data.data (), 12 , totals[2 ]);
179+ send (data);
180+ }
87181 }
88- send (data);
89182 }
90183 receive ();
91184}
@@ -112,23 +205,44 @@ RankAcceptor::RankAcceptor(uint16_t httpPort, asio::io_context& io_context, cons
112205 asio::socket_base::reuse_address option (true );
113206 acceptor.set_option (option);
114207 database.open (dbpath);
115- Statement stmt (database, " SELECT name FROM sqlite_master WHERE type='table' AND name='ranking'" );
116- if (!stmt.step ()) {
117- WARN_LOG (Game::PropellerA, " Table 'ranking' doesn't exist. Creating it" );
118- database.exec (" CREATE TABLE ranking (id INTEGER PRIMARY KEY AUTOINCREMENT, "
119- " user_id VARCHAR(32) UNIQUE, "
120- " kills INTEGER DEFAULT 0, "
121- " wins INTEGER DEFAULT 0, "
122- " games INTEGER DEFAULT 0, "
123- " flightTime INTEGER DEFAULT 0, "
124- " flightDistance INTEGER DEFAULT 0, "
125- " shotDown INTEGER DEFAULT 0, "
126- " points INTEGER DEFAULT 0, "
127- " rank INTEGER DEFAULT 14)" );
208+ {
209+ Statement stmt (database, " SELECT name FROM sqlite_master WHERE type='table' AND name='ranking'" );
210+ if (!stmt.step ())
211+ {
212+ WARN_LOG (Game::PropellerA, " Table 'ranking' doesn't exist. Creating it" );
213+ database.exec (" CREATE TABLE ranking (id INTEGER PRIMARY KEY AUTOINCREMENT, "
214+ " user_id VARCHAR(32) UNIQUE, "
215+ " kills INTEGER DEFAULT 0, "
216+ " wins INTEGER DEFAULT 0, "
217+ " games INTEGER DEFAULT 0, "
218+ " flightTime INTEGER DEFAULT 0, "
219+ " flightDistance INTEGER DEFAULT 0, "
220+ " shotDown INTEGER DEFAULT 0, "
221+ " points INTEGER DEFAULT 0, "
222+ " rank INTEGER DEFAULT 14)" );
223+ }
224+ }
225+ {
226+ Statement stmt (database, " SELECT name FROM sqlite_master WHERE type='table' AND name='training'" );
227+ if (!stmt.step ())
228+ {
229+ WARN_LOG (Game::PropellerA, " Table 'training' doesn't exist. Creating it" );
230+ database.exec (" CREATE TABLE training (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id VARCHAR(32) UNIQUE, "
231+ " cat1score1 INTEGER DEFAULT 0, cat1score2 INTEGER DEFAULT 0, cat1score3 INTEGER DEFAULT 0, cat1score4 INTEGER DEFAULT 0, "
232+ " cat1score5 INTEGER DEFAULT 0, cat1score6 INTEGER DEFAULT 0, cat1score7 INTEGER DEFAULT 0, cat1score8 INTEGER DEFAULT 0,"
233+ " cat1bonus1 INTEGER DEFAULT 0, cat1bonus2 INTEGER DEFAULT 0, cat1total INTEGER DEFAULT 0,"
234+ " cat2score1 INTEGER DEFAULT 0, cat2score2 INTEGER DEFAULT 0, cat2score3 INTEGER DEFAULT 0, cat2score4 INTEGER DEFAULT 0, "
235+ " cat2score5 INTEGER DEFAULT 0, cat2score6 INTEGER DEFAULT 0, cat2score7 INTEGER DEFAULT 0, cat2score8 INTEGER DEFAULT 0,"
236+ " cat2bonus1 INTEGER DEFAULT 0, cat2bonus2 INTEGER DEFAULT 0, cat2total INTEGER DEFAULT 0,"
237+ " cat3score1 INTEGER DEFAULT 0, cat3score2 INTEGER DEFAULT 0, cat3score3 INTEGER DEFAULT 0, cat3score4 INTEGER DEFAULT 0, "
238+ " cat3score5 INTEGER DEFAULT 0, cat3score6 INTEGER DEFAULT 0, cat3score7 INTEGER DEFAULT 0, cat3score8 INTEGER DEFAULT 0,"
239+ " cat3bonus1 INTEGER DEFAULT 0, cat3bonus2 INTEGER DEFAULT 0, cat3total INTEGER DEFAULT 0)" );
240+ }
128241 }
129242
130243 using namespace std ::placeholders;
131- http.addCgiHandler (" /get_rank" , std::bind (&RankAcceptor::handleHttp, this , _1, _2));
244+ http.addCgiHandler (" /get_rank" , std::bind (&RankAcceptor::handleRankHttp, this , _1, _2));
245+ http.addCgiHandler (" /get_training" , std::bind (&RankAcceptor::handleTrainingHttp, this , _1, _2));
132246
133247 Instance = this ;
134248}
@@ -178,7 +292,7 @@ void RankAcceptor::updateRank(const std::string& name, int kills, int wins, int
178292 }
179293}
180294
181- void RankAcceptor::handleHttp (const Request& request, Reply& reply)
295+ void RankAcceptor::handleRankHttp (const Request& request, Reply& reply)
182296{
183297 try {
184298 std::string sortBy = " kills" ;
@@ -229,3 +343,57 @@ void RankAcceptor::handleHttp(const Request& request, Reply& reply)
229343 reply = Reply::stockReply (Reply::internal_server_error);
230344 }
231345}
346+
347+ static std::string formatTime (unsigned time)
348+ {
349+ char buf[16 ];
350+ sprintf (buf, " %d'%02d\" %02d" , time / 60 / 100 , (time / 100 ) % 60 , time % 100 );
351+ return buf;
352+ }
353+
354+ void RankAcceptor::handleTrainingHttp (const Request& request, Reply& reply)
355+ {
356+ try {
357+ std::string sortBy = " cat1total" ;
358+ if (!request.content .empty ())
359+ {
360+ try {
361+ json query = json::parse (request.content );
362+ sortBy = query.at (" sortBy" ).get <std::string>();
363+ } catch (const json::exception& e) {
364+ DEBUG_LOG (Game::PropellerA, " Invalid json: %s" , e.what ());
365+ }
366+ }
367+ std::string select = " SELECT user_id, cat1total, cat2total, cat3total FROM training" ;
368+ if (sortBy == " userName" ) {
369+ select += " ORDER BY user_id" ;
370+ }
371+ else if (sortBy == " cat1total" ) {
372+ select += " ORDER BY " + sortBy + " DESC, user_id" ;
373+ }
374+ else if (sortBy == " cat2total" || sortBy == " cat3total" ) {
375+ select += " ORDER BY " + sortBy + " , user_id" ;
376+ }
377+ else {
378+ reply = Reply::stockReply (Reply::bad_request);
379+ return ;
380+ }
381+ Statement stmt (database, select.c_str ());
382+
383+ json result;
384+ while (stmt.step ())
385+ {
386+ json entry;
387+ entry[" userName" ] = stmt.getStringColumn (0 );
388+ entry[" cat1total" ] = stmt.getIntColumn (1 );
389+ entry[" cat2total" ] = formatTime (stmt.getIntColumn (2 ));
390+ entry[" cat3total" ] = formatTime (stmt.getIntColumn (3 ));
391+ result.push_back (entry);
392+ }
393+ reply.setContent (result.dump (-1 , ' ' , false , json::error_handler_t ::replace), " application/json" );
394+
395+ } catch (const std::exception& e) {
396+ ERROR_LOG (Game::PropellerA, " HTTP handler exception: %s" , e.what ());
397+ reply = Reply::stockReply (Reply::internal_server_error);
398+ }
399+ }
0 commit comments