613 lines
18 KiB
C#
613 lines
18 KiB
C#
using System.Diagnostics;
|
|
using System.Net.Mime;
|
|
using System.Text;
|
|
using System.Xml.Linq;
|
|
using ChoETL;
|
|
using EmbedIO;
|
|
using EmbedIO.Routing;
|
|
using EmbedIO.WebApi;
|
|
using GCLocalServerRewrite.common;
|
|
using GCLocalServerRewrite.models;
|
|
using SQLite.Net2;
|
|
using Swan;
|
|
using Swan.Logging;
|
|
|
|
namespace GCLocalServerRewrite.controllers;
|
|
|
|
public class CardServiceController : WebApiController
|
|
{
|
|
private readonly SQLiteConnection cardSqLiteConnection;
|
|
private readonly SQLiteConnection musicSqLiteConnection;
|
|
|
|
public CardServiceController()
|
|
{
|
|
cardSqLiteConnection = DatabaseHelper.ConnectDatabase(Configs.SETTINGS.CardDbName);
|
|
musicSqLiteConnection = DatabaseHelper.ConnectDatabase(Configs.SETTINGS.MusicDbName);
|
|
}
|
|
|
|
[Route(HttpVerbs.Post, "/cardn.cgi")]
|
|
// ReSharper disable once UnusedMember.Global
|
|
public string CardService([FormField] int gid, [FormField("mac_addr")] string mac, [FormField] int type,
|
|
[FormField("card_no")] long cardId, [FormField("data")] string xmlData)
|
|
{
|
|
HttpContext.Response.ContentType = MediaTypeNames.Application.Octet;
|
|
HttpContext.Response.ContentEncoding = new UTF8Encoding(false);
|
|
HttpContext.Response.KeepAlive = true;
|
|
|
|
if (!Enum.IsDefined(typeof(CardRequestType), type))
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(type));
|
|
}
|
|
|
|
var requestType = (CardRequestType)type;
|
|
|
|
switch (requestType)
|
|
{
|
|
#region ReadRequests
|
|
|
|
case CardRequestType.ReadCard:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(Card(cardId));
|
|
}
|
|
case CardRequestType.ReadCardDetail:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(CardDetail(cardId, xmlData));
|
|
}
|
|
case CardRequestType.ReadCardDetails:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(CardDetails(cardId));
|
|
}
|
|
case CardRequestType.ReadCardBData:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(CardBData(cardId));
|
|
}
|
|
case CardRequestType.ReadAvatar:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(
|
|
GetStaticCount<Avatar>(cardId, Configs.SETTINGS.AvatarCount, Configs.AVATAR_XPATH));
|
|
}
|
|
case CardRequestType.ReadItem:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(
|
|
GetStaticCount<Item>(cardId, Configs.SETTINGS.ItemCount, Configs.ITEM_XPATH));
|
|
}
|
|
case CardRequestType.ReadSkin:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(
|
|
GetStaticCount<Skin>(cardId, Configs.SETTINGS.SkinCount, Configs.SKIN_XPATH));
|
|
}
|
|
case CardRequestType.ReadTitle:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(
|
|
GetStaticCount<Title>(cardId, Configs.SETTINGS.TitleCount, Configs.TITLE_XPATH));
|
|
}
|
|
case CardRequestType.ReadMusic:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(MusicUnlock());
|
|
}
|
|
case CardRequestType.ReadEventReward:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(
|
|
GenerateEmptyXML(Configs.EVENT_REWARD));
|
|
}
|
|
case CardRequestType.ReadNavigator:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(
|
|
GetStaticCount<Navigator>(cardId, Configs.SETTINGS.NavigatorCount, Configs.NAVIGATOR_XPATH));
|
|
}
|
|
case CardRequestType.ReadMusicExtra:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(MusicExtra());
|
|
}
|
|
case CardRequestType.ReadMusicAou:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(MusicAouUnlock());
|
|
}
|
|
case CardRequestType.ReadCoin:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(Coin(cardId));
|
|
}
|
|
case CardRequestType.ReadUnlockReward:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(UnlockReward(cardId));
|
|
}
|
|
case CardRequestType.ReadUnlockKeynum:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(UnlockKeynum(cardId));
|
|
}
|
|
case CardRequestType.ReadSoundEffect:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(
|
|
GetStaticCount<SoundEffect>(cardId, Configs.SETTINGS.SeCount, Configs.SE_XPATH));
|
|
}
|
|
case CardRequestType.ReadGetMessage:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(GenerateEmptyXML(Configs.GET_MESSAGE));
|
|
}
|
|
case CardRequestType.ReadCond:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(GenerateEmptyXML(Configs.COND));
|
|
}
|
|
case CardRequestType.ReadTotalTrophy:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(TotalTrophy(cardId));
|
|
}
|
|
case CardRequestType.SessionStart:
|
|
case CardRequestType.SessionGet:
|
|
{
|
|
$"Getting read request, type is {requestType}".Info();
|
|
|
|
return ConstructResponse(GetSession(cardId, mac));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region WriteRequests
|
|
|
|
case CardRequestType.WriteCardDetail:
|
|
{
|
|
$"Getting write request, type is {requestType}\n Data is {xmlData}".Info();
|
|
WriteCardDetail(cardId, xmlData);
|
|
|
|
return ConstructResponse(xmlData);
|
|
}
|
|
case CardRequestType.WriteCardBData:
|
|
{
|
|
$"Getting write request, type is {requestType}\n Data is {xmlData}".Info();
|
|
Write<CardBData>(cardId, xmlData);
|
|
WriteCardPlayCount(cardId);
|
|
|
|
return ConstructResponse(xmlData);
|
|
}
|
|
case CardRequestType.WriteCard:
|
|
{
|
|
$"Getting write request, type is {requestType}\n Data is {xmlData}".Info();
|
|
Write<Card>(cardId, xmlData);
|
|
|
|
return ConstructResponse(xmlData);
|
|
}
|
|
case CardRequestType.WriteAvatar:
|
|
case CardRequestType.WriteItem:
|
|
case CardRequestType.WriteTitle:
|
|
case CardRequestType.WriteMusicDetail:
|
|
case CardRequestType.WriteNavigator:
|
|
case CardRequestType.WriteCoin:
|
|
case CardRequestType.WriteSkin:
|
|
case CardRequestType.WriteUnlockKeynum:
|
|
case CardRequestType.WriteSoundEffect:
|
|
{
|
|
$"Getting write request, type is {requestType}\n Data is {xmlData}".Info();
|
|
|
|
return ConstructResponse(xmlData);
|
|
}
|
|
|
|
#endregion
|
|
|
|
default:
|
|
#pragma warning disable CA2208
|
|
throw new ArgumentOutOfRangeException(nameof(requestType));
|
|
#pragma warning restore CA2208
|
|
}
|
|
}
|
|
|
|
#region ReadImplementation
|
|
|
|
private string Card(long cardId)
|
|
{
|
|
var result = cardSqLiteConnection.Table<Card>().Where(card => card.CardId == cardId);
|
|
|
|
if (!result.Any())
|
|
{
|
|
return GenerateEmptyXML(Configs.CARD);
|
|
}
|
|
|
|
var card = result.First();
|
|
|
|
return GenerateSingleXml(card, Configs.CARD_XPATH);
|
|
}
|
|
|
|
private string CardDetail(long cardId, string xmlData)
|
|
{
|
|
var reader = new ChoXmlReader<CardDetailReadData>(new StringReader(xmlData));
|
|
var data = reader.Read();
|
|
|
|
var result = cardSqLiteConnection.Table<CardDetail>()
|
|
.Where(detail => detail.CardId == cardId &&
|
|
detail.Pcol1 == data.Pcol1 && detail.Pcol2 == data.Pcol2 &&
|
|
detail.Pcol3 == data.Pcol3);
|
|
|
|
if (!result.Any())
|
|
{
|
|
return GenerateEmptyXML(Configs.CARD_DETAIL);
|
|
}
|
|
|
|
var cardDetail = result.First();
|
|
|
|
return GenerateSingleXml(cardDetail, Configs.CARD_DETAIL_SINGLE_XPATH);
|
|
}
|
|
|
|
private string CardDetails(long cardId)
|
|
{
|
|
var result = cardSqLiteConnection.Table<CardDetail>()
|
|
.Where(detail => detail.CardId == cardId);
|
|
|
|
if (!result.Any())
|
|
{
|
|
return GenerateEmptyXML(Configs.CARD_DETAIL);
|
|
}
|
|
|
|
var cardDetails = result.ToList();
|
|
|
|
return GenerateRecordsXml(cardDetails, Configs.CARD_DETAIL_RECORD_XPATH);
|
|
}
|
|
|
|
private string CardBData(long cardId)
|
|
{
|
|
var result = cardSqLiteConnection.Table<CardBData>()
|
|
.Where(detail => detail.CardId == cardId);
|
|
|
|
if (!result.Any())
|
|
{
|
|
return GenerateEmptyXML(Configs.CARD_BDATA);
|
|
}
|
|
var cardBData = result.First();
|
|
|
|
return GenerateSingleXml(cardBData, Configs.CARD_BDATA_XPATH);
|
|
}
|
|
|
|
private static string GetStaticCount<T>(long cardId, int count, string xpath)
|
|
where T : Record, IIdModel, ICardIdModel, new()
|
|
{
|
|
var models = new List<T>();
|
|
|
|
for (var id = 1; id <= count; id++)
|
|
{
|
|
var model = new T();
|
|
model.SetId(id);
|
|
model.SetCardId(cardId);
|
|
models.Add(model);
|
|
}
|
|
|
|
return GenerateRecordsXml(models, xpath);
|
|
}
|
|
|
|
private static string GetSession(long cardId, string mac)
|
|
{
|
|
var session = new Session
|
|
{
|
|
CardId = cardId,
|
|
Mac = mac,
|
|
SessionId = "12345678901234567890123456789012",
|
|
Expires = 9999,
|
|
PlayerId = 1
|
|
};
|
|
|
|
return GenerateSingleXml(session, Configs.SESSION_XPATH);
|
|
}
|
|
|
|
private static string TotalTrophy(long cardId)
|
|
{
|
|
var trophy = new TotalTrophy
|
|
{
|
|
CardId = cardId,
|
|
TrophyNum = 9
|
|
};
|
|
|
|
return GenerateSingleXml(trophy, Configs.TOTAL_TROPHY_XPATH);
|
|
}
|
|
|
|
private static string Coin(long cardId)
|
|
{
|
|
var coin = new Coin
|
|
{
|
|
CardId = cardId,
|
|
CurrentCoins = 999999,
|
|
TotalCoins = 999999,
|
|
MonthlyCoins = 999999
|
|
};
|
|
|
|
return GenerateSingleXml(coin, Configs.COIN_XPATH);
|
|
}
|
|
|
|
private static string UnlockReward(long cardId)
|
|
{
|
|
var unlockRewards = new List<UnlockReward>
|
|
{
|
|
new()
|
|
{
|
|
CardId = cardId,
|
|
RewardType = 1,
|
|
RewardId = 1,
|
|
TargetId = 1,
|
|
TargetNum = 1,
|
|
KeyNum = 3
|
|
}
|
|
};
|
|
|
|
return GenerateRecordsXml(unlockRewards, Configs.UNLOCK_REWARD_XPATH);
|
|
}
|
|
|
|
private static string UnlockKeynum(long cardId)
|
|
{
|
|
var unlockKeynums = new List<UnlockKeynum>
|
|
{
|
|
new()
|
|
{
|
|
CardId = cardId,
|
|
RewardId = 1,
|
|
KeyNum = 0,
|
|
RewardCount = 1
|
|
}
|
|
};
|
|
|
|
return GenerateRecordsXml(unlockKeynums, Configs.UNLOCK_KEYNUM_XPATH);
|
|
}
|
|
|
|
private string MusicUnlock()
|
|
{
|
|
var result = musicSqLiteConnection.Table<Music>().ToList();
|
|
|
|
return GenerateRecordsXml(result, Configs.MUSIC_XPATH);
|
|
}
|
|
|
|
private string MusicAouUnlock()
|
|
{
|
|
var result = musicSqLiteConnection.Table<MusicAou>().ToList();
|
|
|
|
return !result.Any() ? GenerateEmptyXML(Configs.MUSIC_AOU) : GenerateRecordsXml(result, Configs.MUSIC_AOU_XPATH);
|
|
}
|
|
|
|
private string MusicExtra()
|
|
{
|
|
var result = musicSqLiteConnection.Table<MusicExtra>().ToList();
|
|
|
|
return GenerateRecordsXml(result, Configs.MUSIC_EXTRA_XPATH);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region HelperMethods
|
|
|
|
private static string ConstructResponse(string xml)
|
|
{
|
|
return "1\n" +
|
|
"10,10\n" +
|
|
xml;
|
|
}
|
|
|
|
private static string GenerateEmptyXML(string fieldName)
|
|
{
|
|
var xml = new XDocument(new XElement("root",
|
|
new XElement(fieldName)));
|
|
xml.Declaration = new XDeclaration("1.0", "UTF-8", null);
|
|
|
|
return xml.ToString();
|
|
}
|
|
|
|
private static string GenerateSingleXml<T>(T obj, string xpath) where T: class
|
|
{
|
|
var sb = new StringBuilder();
|
|
|
|
using (var writer = new ChoXmlWriter<T>(sb))
|
|
{
|
|
writer.Configuration.OmitXmlDeclaration = false;
|
|
writer.Configuration.UseXmlSerialization = true;
|
|
writer.WithXPath(xpath);
|
|
|
|
writer.Write(obj);
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string GenerateRecordsXml<T>(IReadOnlyList<T> list, string xpath) where T : Record
|
|
{
|
|
var stringBuilder = new StringBuilder();
|
|
|
|
for (var i = 0; i < list.Count; i++)
|
|
{
|
|
var obj = list[i];
|
|
obj.RecordId = i + 1;
|
|
}
|
|
|
|
using (var writer = new ChoXmlWriter<T>(stringBuilder))
|
|
{
|
|
writer.Configuration.OmitXmlDeclaration = false;
|
|
writer.Configuration.UseXmlSerialization = true;
|
|
writer.WithXPath(xpath);
|
|
writer.Write(list);
|
|
}
|
|
|
|
return stringBuilder.ToString();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region WriteImplementation
|
|
|
|
private void Write<T>(long cardId, string xmlData) where T : class, ICardIdModel
|
|
{
|
|
var reader = new ChoXmlReader<T>(new StringReader(xmlData)).WithXPath(Configs.DATA_XPATH);
|
|
var writeObject = reader.Read();
|
|
|
|
if (writeObject == null)
|
|
{
|
|
throw new HttpRequestException();
|
|
}
|
|
|
|
writeObject.SetCardId(cardId);
|
|
var rowsAffected = cardSqLiteConnection.InsertOrReplace(writeObject, typeof(T));
|
|
|
|
if (rowsAffected == 0)
|
|
{
|
|
throw new ApplicationException("Update database failed!");
|
|
}
|
|
|
|
$"Updated {typeof(T)}".Info();
|
|
}
|
|
|
|
private void WriteCardPlayCount(long cardId)
|
|
{
|
|
var record = cardSqLiteConnection.Table<CardPlayCount>().Where(count => count.CardId == cardId);
|
|
|
|
if (!record.Any())
|
|
{
|
|
$"Created new play count data for card {cardId}".Info();
|
|
var playCount = new CardPlayCount
|
|
{
|
|
CardId = cardId,
|
|
PlayCount = 1,
|
|
LastPlayed = DateTime.Now
|
|
};
|
|
cardSqLiteConnection.InsertOrReplace(playCount);
|
|
return;
|
|
}
|
|
|
|
var data = record.First();
|
|
var now = DateTime.Now;
|
|
var lastPlayedTime = data.LastPlayed;
|
|
|
|
if (now <= lastPlayedTime)
|
|
{
|
|
$"Current time {now} is less than or equal to last played time! Clock skew detected!".Warn();
|
|
data.PlayCount = 0;
|
|
data.LastPlayed = DateTime.Now;
|
|
cardSqLiteConnection.InsertOrReplace(data);
|
|
return;
|
|
}
|
|
|
|
DateTime start;
|
|
DateTime end;
|
|
|
|
if (now.Hour >= 8)
|
|
{
|
|
start = DateTime.Today.AddHours(8);
|
|
end = start.AddHours(24);
|
|
}
|
|
else
|
|
{
|
|
end = DateTime.Today.AddHours(8);
|
|
start = end.AddHours(-24);
|
|
}
|
|
|
|
data.PlayCount = lastPlayedTime.IsBetween(start, end) ? data.PlayCount + 1 : 0;
|
|
cardSqLiteConnection.InsertOrReplace(data);
|
|
$"Updated card play count, current count is {data.PlayCount}".Info();
|
|
}
|
|
|
|
private void WriteCardDetail(long cardId, string xmlData)
|
|
{
|
|
var result = cardSqLiteConnection.Table<CardDetail>()
|
|
.Where(detail => detail.CardId == cardId);
|
|
|
|
// Unlock all unlockable songs in card details table when write for the first time
|
|
if (!result.Any())
|
|
{
|
|
var unlockableSongIds = Configs.SETTINGS.UnlockableSongIds;
|
|
|
|
if (unlockableSongIds is null)
|
|
{
|
|
unlockableSongIds = Configs.DEFAULT_UNLOCKABLE_SONGS;
|
|
}
|
|
var detailList = unlockableSongIds!.Select(id => new CardDetail
|
|
{
|
|
CardId = cardId,
|
|
Pcol1 = 10,
|
|
Pcol2 = id,
|
|
Pcol3 = 0,
|
|
ScoreUi2 = 1,
|
|
ScoreUi6 = 1
|
|
})
|
|
.ToList();
|
|
|
|
cardSqLiteConnection.InsertOrIgnoreAll(detailList);
|
|
}
|
|
|
|
Write<CardDetail>(cardId, xmlData);
|
|
}
|
|
|
|
#endregion
|
|
|
|
private enum CardRequestType
|
|
{
|
|
// Read data
|
|
ReadCard = 259,
|
|
ReadCardDetail = 260,
|
|
ReadCardDetails = 261,
|
|
ReadCardBData = 264,
|
|
ReadAvatar = 418,
|
|
ReadItem = 420,
|
|
ReadSkin = 422,
|
|
ReadTitle = 424,
|
|
ReadMusic = 428,
|
|
ReadEventReward = 441,
|
|
ReadNavigator = 443,
|
|
ReadMusicExtra = 465,
|
|
ReadMusicAou = 467,
|
|
ReadCoin = 468,
|
|
ReadUnlockReward = 507,
|
|
ReadUnlockKeynum = 509,
|
|
ReadSoundEffect = 8458,
|
|
ReadGetMessage = 8461,
|
|
ReadCond = 8465,
|
|
ReadTotalTrophy = 8468,
|
|
|
|
// Sessions
|
|
SessionGet = 401,
|
|
SessionStart = 402,
|
|
|
|
// Write data
|
|
WriteCard = 771,
|
|
WriteCardDetail = 772,
|
|
WriteCardBData = 776,
|
|
WriteAvatar = 929,
|
|
WriteItem = 931,
|
|
WriteTitle = 935,
|
|
WriteMusicDetail = 941,
|
|
WriteNavigator = 954,
|
|
WriteCoin = 980,
|
|
WriteSkin = 933,
|
|
WriteUnlockKeynum = 1020,
|
|
WriteSoundEffect = 8969
|
|
}
|
|
} |