Convert tabs to spaces

This commit is contained in:
Les De Ridder 2017-09-29 16:17:28 +02:00
parent d20cc3ee1c
commit b534359325
16 changed files with 308 additions and 308 deletions

View File

@ -15,100 +15,100 @@ import config;
void main() void main()
{ {
auto router = new URLRouter; auto router = new URLRouter;
router.get("*", reverseProxyRequest(serverHost, serverPort)); router.get("*", reverseProxyRequest(serverHost, serverPort));
router.post("/api/v1/*", &onAPIRequest); router.post("/api/v1/*", &onAPIRequest);
auto settings = new HTTPServerSettings; auto settings = new HTTPServerSettings;
settings.port = proxyPortNumber; settings.port = proxyPortNumber;
settings.options = HTTPServerOption.parseURL | HTTPServerOption.errorStackTraces; settings.options = HTTPServerOption.parseURL | HTTPServerOption.errorStackTraces;
listenHTTP(settings, router); listenHTTP(settings, router);
runApplication(); runApplication();
} }
void onAPIRequest(HTTPServerRequest request, HTTPServerResponse response) void onAPIRequest(HTTPServerRequest request, HTTPServerResponse response)
{ {
auto apiEndpoint = request.path.split("/api/v1")[1]; auto apiEndpoint = request.path.split("/api/v1")[1];
auto nonce = request.headers["auth_nonce"]; auto nonce = request.headers["auth_nonce"];
auto requestBody = request.bodyReader.readAll(); auto requestBody = request.bodyReader.readAll();
auto requestJson = getRequestJson(requestBody, apiEndpoint, nonce); auto requestJson = getRequestJson(requestBody, apiEndpoint, nonce);
handleRequest(apiEndpoint, requestJson); handleRequest(apiEndpoint, requestJson);
//TODO: Allow request editing (requires recalculation of signature) //TODO: Allow request editing (requires recalculation of signature)
//NOTE: This would not be a very useful feature, so this might be removed later. //NOTE: This would not be a very useful feature, so this might be removed later.
// One thing it would be useful for is creating a bot for keeping track of API changes. // One thing it would be useful for is creating a bot for keeping track of API changes.
// This would also require quite a bit of refactoring however. // This would also require quite a bit of refactoring however.
auto apiResponse = sendRequest(apiEndpoint, request.headers.dup, requestBody); auto apiResponse = sendRequest(apiEndpoint, request.headers.dup, requestBody);
auto responseBody = apiResponse.bodyReader.readAll(); auto responseBody = apiResponse.bodyReader.readAll();
auto responseJson = getResponseJson(responseBody, apiEndpoint, nonce); auto responseJson = getResponseJson(responseBody, apiEndpoint, nonce);
auto finalResponseJson = handleResponse(apiEndpoint, responseJson); auto finalResponseJson = handleResponse(apiEndpoint, responseJson);
auto finalResponseBody = constructResponseBody(apiEndpoint, finalResponseJson, nonce); auto finalResponseBody = constructResponseBody(apiEndpoint, finalResponseJson, nonce);
sendResponse(response, apiResponse, finalResponseBody); sendResponse(response, apiResponse, finalResponseBody);
import std.stdio : writeln; import std.stdio : writeln;
writeln(apiEndpoint); writeln(apiEndpoint);
import std.file : append; import std.file : append;
struct LogEntry struct LogEntry
{ {
string endpoint; string endpoint;
Json request; Json request;
Json response; Json response;
} }
append("api.log", LogEntry(apiEndpoint, requestJson, finalResponseJson).serializeToJson.toPrettyString ~ "\n\n"); append("api.log", LogEntry(apiEndpoint, requestJson, finalResponseJson).serializeToJson.toPrettyString ~ "\n\n");
} }
Json getRequestJson(ubyte[] input, string endpoint, string nonce) Json getRequestJson(ubyte[] input, string endpoint, string nonce)
{ {
auto originalRequestPipe = Pipe auto originalRequestPipe = Pipe
( (
new Base64Decoder, new Base64Decoder,
getCipher getCipher
( (
cipherName, cipherName,
SymmetricKey((cast(ubyte[]) keyString).ptr, keyString.length), SymmetricKey((cast(ubyte[]) keyString).ptr, keyString.length),
InitializationVector((cast(ubyte[]) nonce).ptr, nonce.length), InitializationVector((cast(ubyte[]) nonce).ptr, nonce.length),
DECRYPTION DECRYPTION
) )
); );
originalRequestPipe.processMsg(input.ptr, input.length); originalRequestPipe.processMsg(input.ptr, input.length);
auto requestJsonString = originalRequestPipe.toString; auto requestJsonString = originalRequestPipe.toString;
return requestJsonString.parseJson; return requestJsonString.parseJson;
} }
Json getResponseJson(ubyte[] input, string endpoint, string nonce) Json getResponseJson(ubyte[] input, string endpoint, string nonce)
{ {
import std.zlib : uncompress; import std.zlib : uncompress;
switch(endpoint) switch(endpoint)
{ {
case "/config/getGameConfig": case "/config/getGameConfig":
return getRequestJson(input, endpoint, nonce); return getRequestJson(input, endpoint, nonce);
case "/master/getMaster": case "/master/getMaster":
auto decompressedInput = cast(ubyte[])uncompress(input); //TODO: Use pipe instead (how?) auto decompressedInput = cast(ubyte[])uncompress(input); //TODO: Use pipe instead (how?)
auto responseJsonString = cast(string) decompressedInput; auto responseJsonString = cast(string) decompressedInput;
return responseJsonString.parseJson; return responseJsonString.parseJson;
default: default:
auto decompressedInput = cast(ubyte[])uncompress(input); //TODO: Use pipe instead (how?) auto decompressedInput = cast(ubyte[])uncompress(input); //TODO: Use pipe instead (how?)
auto responsePipe = Pipe auto responsePipe = Pipe
( (
new Base64Decoder new Base64Decoder
); );
responsePipe.processMsg(decompressedInput.ptr, decompressedInput.length); responsePipe.processMsg(decompressedInput.ptr, decompressedInput.length);
auto responseJsonString = responsePipe.toString; auto responseJsonString = responsePipe.toString;
return responseJsonString.parseJson; return responseJsonString.parseJson;
} }
} }
void handleRequest(string endpoint, Json json) void handleRequest(string endpoint, Json json)
@ -118,127 +118,127 @@ void handleRequest(string endpoint, Json json)
Json handleResponse(string endpoint, Json json) Json handleResponse(string endpoint, Json json)
{ {
if(endpoint == "/master/getMaster") if(endpoint == "/master/getMaster")
{ {
import models.master; import models.master;
auto getMasterResponse = json.deserializeJson!GetMasterResponse; auto getMasterResponse = json.deserializeJson!GetMasterResponse;
import dataMixins : applyMasterTranslation; import dataMixins : applyMasterTranslation;
mixin(applyMasterTranslation!("Character", 0, mixin(applyMasterTranslation!("Character", 0,
5, "characterNames")); 5, "characterNames"));
mixin(applyMasterTranslation!("CharacterSkill", 0, mixin(applyMasterTranslation!("CharacterSkill", 0,
1, "skillNames", 1, "skillNames",
6, "skillDescriptions")); 6, "skillDescriptions"));
mixin(applyMasterTranslation!("Stage", 0, mixin(applyMasterTranslation!("Stage", 0,
1, "stageNames")); 1, "stageNames"));
//TODO: Figure out a way to find the wrong ones //TODO: Figure out a way to find the wrong ones
mixin(applyMasterTranslation!("Mission", 0, mixin(applyMasterTranslation!("Mission", 0,
1, "missionNames", 1, "missionNames",
2, "missionDescriptions", 2, "missionDescriptions",
3, "missionRewards")); 3, "missionRewards"));
mixin(applyMasterTranslation!("Dungeon", 10, mixin(applyMasterTranslation!("Dungeon", 10,
12, "dungeonNames")); 12, "dungeonNames"));
mixin(applyMasterTranslation!("Guest", 0, mixin(applyMasterTranslation!("Guest", 0,
1, "guestPartyNames")); 1, "guestPartyNames"));
mixin(applyMasterTranslation!("Item", 0, mixin(applyMasterTranslation!("Item", 0,
1, "itemNames")); 1, "itemNames"));
mixin(applyMasterTranslation!("WorldMap", 0, mixin(applyMasterTranslation!("WorldMap", 0,
3, "worldMapLocationNames", 3, "worldMapLocationNames",
4, "worldMapLocationCaptions", 4, "worldMapLocationCaptions",
5, "worldMapLocationDescriptions")); 5, "worldMapLocationDescriptions"));
mixin(applyMasterTranslation!("CharacterTextResource", 0, mixin(applyMasterTranslation!("CharacterTextResource", 0,
3, "characterTextResource")); 3, "characterTextResource"));
mixin(applyMasterTranslation!("CharacterLeaderSkill", 0, mixin(applyMasterTranslation!("CharacterLeaderSkill", 0,
1, "characterLeaderSkillNames", 1, "characterLeaderSkillNames",
14, "characterLeaderSkillDescriptions")); 14, "characterLeaderSkillDescriptions"));
mixin(applyMasterTranslation!("CharacterBook", 0, mixin(applyMasterTranslation!("CharacterBook", 0,
5, "characterBookFlowerLanguage")); 5, "characterBookFlowerLanguage"));
mixin(applyMasterTranslation!("CharacterCategory", 0, mixin(applyMasterTranslation!("CharacterCategory", 0,
1, "characterCategories")); 1, "characterCategories"));
return getMasterResponse.serializeToJson; return getMasterResponse.serializeToJson;
} }
return json; return json;
} }
ubyte[] constructResponseBody(string endpoint, Json json, string nonce) ubyte[] constructResponseBody(string endpoint, Json json, string nonce)
{ {
import std.zlib : compress; import std.zlib : compress;
auto jsonStringBytes = cast(ubyte[]) json.toString; auto jsonStringBytes = cast(ubyte[]) json.toString;
switch(endpoint) switch(endpoint)
{ {
case "/config/getGameConfig": case "/config/getGameConfig":
auto responsePipe = Pipe auto responsePipe = Pipe
( (
getCipher getCipher
( (
cipherName, cipherName,
SymmetricKey((cast(ubyte[]) keyString).ptr, keyString.length), SymmetricKey((cast(ubyte[]) keyString).ptr, keyString.length),
InitializationVector((cast(ubyte[]) nonce).ptr, nonce.length), InitializationVector((cast(ubyte[]) nonce).ptr, nonce.length),
ENCRYPTION ENCRYPTION
), ),
new Base64Encoder new Base64Encoder
); );
//TODO: Refactor this //TODO: Refactor this
if(jsonStringBytes.length % 16 != 0) if(jsonStringBytes.length % 16 != 0)
{ {
jsonStringBytes.length += 16 - jsonStringBytes.length % 16; jsonStringBytes.length += 16 - jsonStringBytes.length % 16;
} }
responsePipe.processMsg(jsonStringBytes.ptr, jsonStringBytes.length); responsePipe.processMsg(jsonStringBytes.ptr, jsonStringBytes.length);
return cast(ubyte[]) responsePipe.toString; return cast(ubyte[]) responsePipe.toString;
case "/master/getMaster": case "/master/getMaster":
return compress(jsonStringBytes); //TODO: Use pipe instead (how?) return compress(jsonStringBytes); //TODO: Use pipe instead (how?)
default: default:
auto responsePipe = Pipe auto responsePipe = Pipe
( (
new Base64Encoder new Base64Encoder
); );
responsePipe.processMsg(jsonStringBytes.ptr, jsonStringBytes.length); responsePipe.processMsg(jsonStringBytes.ptr, jsonStringBytes.length);
return compress(responsePipe.toString); return compress(responsePipe.toString);
} }
} }
HTTPClientResponse sendRequest(string endpoint, InetHeaderMap headers, ubyte[] requestBody) HTTPClientResponse sendRequest(string endpoint, InetHeaderMap headers, ubyte[] requestBody)
{ {
return requestHTTP("http://" ~ serverHost ~ "/api/v1" ~ endpoint, return requestHTTP("http://" ~ serverHost ~ "/api/v1" ~ endpoint,
(scope HTTPClientRequest r) (scope HTTPClientRequest r)
{ {
r.method = HTTPMethod.POST; r.method = HTTPMethod.POST;
r.headers = headers.dup; r.headers = headers.dup;
r.writeBody(requestBody, r.headers["Content-Type"]); r.writeBody(requestBody, r.headers["Content-Type"]);
} }
); );
} }
void sendResponse(HTTPServerResponse response, HTTPClientResponse apiResponse, ubyte[] responseBody) void sendResponse(HTTPServerResponse response, HTTPClientResponse apiResponse, ubyte[] responseBody)
{ {
//HACK:This is necessary because of something, but I don't remember what //HACK:This is necessary because of something, but I don't remember what
if("Transfer-Encoding" in apiResponse.headers) if("Transfer-Encoding" in apiResponse.headers)
{ {
apiResponse.headers.remove("Transfer-Encoding"); apiResponse.headers.remove("Transfer-Encoding");
} }
response.statusCode = apiResponse.statusCode; response.statusCode = apiResponse.statusCode;
response.statusPhrase = apiResponse.statusPhrase; response.statusPhrase = apiResponse.statusPhrase;
response.headers = apiResponse.headers.dup; response.headers = apiResponse.headers.dup;
response.writeBody(responseBody); response.writeBody(responseBody);
} }

View File

@ -2,34 +2,34 @@ module dataMixins;
template applyMasterTranslation(string Table, uint IdFieldIndex, Translations...) template applyMasterTranslation(string Table, uint IdFieldIndex, Translations...)
{ {
static assert(Translations.length >= 2 && Translations.length % 2 == 0); static assert(Translations.length >= 2 && Translations.length % 2 == 0);
import std.conv : to; import std.conv : to;
import std.uni : toLower; import std.uni : toLower;
private template getMasterColumnTranslations(Translations...) private template getMasterColumnTranslations(Translations...)
{ {
static if(Translations.length > 0) static if(Translations.length > 0)
{ {
enum getMasterColumnTranslations = "row[" ~ Translations[0].to!string ~ "] = " ~ Translations[1] ~ "[id]; " enum getMasterColumnTranslations = "row[" ~ Translations[0].to!string ~ "] = " ~ Translations[1] ~ "[id]; "
~ getMasterColumnTranslations!(Translations[2 .. $]); ~ getMasterColumnTranslations!(Translations[2 .. $]);
} }
else else
{ {
enum getMasterColumnTranslations = ""; enum getMasterColumnTranslations = "";
} }
} }
enum applyMasterTranslation = " enum applyMasterTranslation = "
foreach(row; getMasterResponse.master" ~ Table ~ ") foreach(row; getMasterResponse.master" ~ Table ~ ")
{ {
import std.conv : to; import std.conv : to;
auto id = row[" ~ IdFieldIndex.to!string ~ "].to!uint; auto id = row[" ~ IdFieldIndex.to!string ~ "].to!uint;
import translations." ~ Table[0].toLower.to!string ~ Table[1 .. $] ~ "; import translations." ~ Table[0].toLower.to!string ~ Table[1 .. $] ~ ";
if(id in " ~ Translations[1] ~ ") if(id in " ~ Translations[1] ~ ")
{ {
" ~ getMasterColumnTranslations!(Translations) ~ " " ~ getMasterColumnTranslations!(Translations) ~ "
} }
}"; }";
} }

View File

@ -6,92 +6,92 @@ import std.typecons : Nullable;
class GetMasterResponse : Response class GetMasterResponse : Response
{ {
Nullable!Base64CSV masterTutorial; Nullable!Base64CSV masterTutorial;
Nullable!Base64CSV masterStory; Nullable!Base64CSV masterStory;
Nullable!Base64CSV masterPanelMission; Nullable!Base64CSV masterPanelMission;
Nullable!Base64CSV masterEquipmentPurificationGroups; Nullable!Base64CSV masterEquipmentPurificationGroups;
Nullable!Base64CSV masterBossAi; Nullable!Base64CSV masterBossAi;
Nullable!Base64CSV masterAlbumDungeon; Nullable!Base64CSV masterAlbumDungeon;
Nullable!Base64CSV masterMissionItem; Nullable!Base64CSV masterMissionItem;
Nullable!Base64CSV masterNavigation; Nullable!Base64CSV masterNavigation;
Nullable!Base64CSV masterCharacterParticular; Nullable!Base64CSV masterCharacterParticular;
Nullable!Base64CSV masterCharacterTrainableLeaderSkill; Nullable!Base64CSV masterCharacterTrainableLeaderSkill;
Nullable!Base64CSV masterDefenseSetting; Nullable!Base64CSV masterDefenseSetting;
Nullable!Base64CSV masterGardenSpecialCharacter; Nullable!Base64CSV masterGardenSpecialCharacter;
Nullable!Base64CSV masterCharacterEquipmentEvolve; Nullable!Base64CSV masterCharacterEquipmentEvolve;
Nullable!Base64CSV masterCharacterSkill; Nullable!Base64CSV masterCharacterSkill;
Nullable!Base64CSV masterCollaboSymphony; Nullable!Base64CSV masterCollaboSymphony;
Nullable!Base64CSV masterCharacterEquipmentLevelGroup; Nullable!Base64CSV masterCharacterEquipmentLevelGroup;
Nullable!Base64CSV masterCharacterQuestItem; Nullable!Base64CSV masterCharacterQuestItem;
Nullable!Base64CSV masterSurvey; Nullable!Base64CSV masterSurvey;
Nullable!Base64CSV masterCharacterMyPagePattern; Nullable!Base64CSV masterCharacterMyPagePattern;
Nullable!Base64CSV masterGift; Nullable!Base64CSV masterGift;
Nullable!Base64CSV masterDungeonRecommendPrioritys; Nullable!Base64CSV masterDungeonRecommendPrioritys;
Nullable!Base64CSV masterAlbumQuestClear; Nullable!Base64CSV masterAlbumQuestClear;
Nullable!Base64CSV masterStageEvaluationItem; Nullable!Base64CSV masterStageEvaluationItem;
Nullable!Base64CSV masterItem; Nullable!Base64CSV masterItem;
Nullable!Base64CSV masterCharacterFlowerings; Nullable!Base64CSV masterCharacterFlowerings;
Nullable!Base64CSV masterPlant; Nullable!Base64CSV masterPlant;
Nullable!Base64CSV masterCharacterTrainableLeaderSkillAcquireCondition; Nullable!Base64CSV masterCharacterTrainableLeaderSkillAcquireCondition;
Nullable!Base64CSV masterEquipmentPurifications; Nullable!Base64CSV masterEquipmentPurifications;
Nullable!Base64CSV masterCharacterEquipment; Nullable!Base64CSV masterCharacterEquipment;
Nullable!Base64CSV masterCharacterTextResource; Nullable!Base64CSV masterCharacterTextResource;
Nullable!Base64CSV masterAlbumTheaterGroup; Nullable!Base64CSV masterAlbumTheaterGroup;
Nullable!Base64CSV masterMission; Nullable!Base64CSV masterMission;
Nullable!Base64CSV masterCharacterMypageVoiceResourceGroup; Nullable!Base64CSV masterCharacterMypageVoiceResourceGroup;
Nullable!Base64CSV masterGuestCharacter; Nullable!Base64CSV masterGuestCharacter;
Nullable!Base64CSV masterCharacterLeaderSkill; Nullable!Base64CSV masterCharacterLeaderSkill;
Nullable!Base64CSV masterCharacterLeaderSkillDescription; Nullable!Base64CSV masterCharacterLeaderSkillDescription;
Nullable!Base64CSV masterCharacterSkinNotExistVoice; Nullable!Base64CSV masterCharacterSkinNotExistVoice;
Nullable!Base64CSV masterStage; Nullable!Base64CSV masterStage;
Nullable!Base64CSV masterCampaignStaminaDungeon; Nullable!Base64CSV masterCampaignStaminaDungeon;
Nullable!Base64CSV masterGardenMakeoverItemShop; Nullable!Base64CSV masterGardenMakeoverItemShop;
Nullable!Base64CSV masterGardenMakeoverItem; Nullable!Base64CSV masterGardenMakeoverItem;
Nullable!Base64CSV masterWhaleHomeBanner; Nullable!Base64CSV masterWhaleHomeBanner;
Nullable!Base64CSV masterSwanBoatRaceGroup; Nullable!Base64CSV masterSwanBoatRaceGroup;
Nullable!Base64CSV masterSynopsis; Nullable!Base64CSV masterSynopsis;
Nullable!Base64CSV masterCharacterLevel; Nullable!Base64CSV masterCharacterLevel;
Nullable!Base64CSV masterPayment; Nullable!Base64CSV masterPayment;
Nullable!Base64CSV masterCharacterBook; Nullable!Base64CSV masterCharacterBook;
Nullable!Base64CSV masterAchievement; Nullable!Base64CSV masterAchievement;
Nullable!Base64CSV masterCharacterCategory; Nullable!Base64CSV masterCharacterCategory;
Nullable!Base64CSV masterAvailableItem; Nullable!Base64CSV masterAvailableItem;
Nullable!Base64CSV masterSwanBoatRaceSchedule; Nullable!Base64CSV masterSwanBoatRaceSchedule;
Nullable!Base64CSV masterCharacterMypageVoiceResource; Nullable!Base64CSV masterCharacterMypageVoiceResource;
Nullable!Base64CSV masterCharacterLevelGroup; Nullable!Base64CSV masterCharacterLevelGroup;
Nullable!Base64CSV masterCharacterQuest; Nullable!Base64CSV masterCharacterQuest;
Nullable!Base64CSV masterCharacterEvolveGroup; Nullable!Base64CSV masterCharacterEvolveGroup;
Nullable!Base64CSV masterCharacterEvolve; Nullable!Base64CSV masterCharacterEvolve;
Nullable!Base64CSV masterKizunaPoint; Nullable!Base64CSV masterKizunaPoint;
Nullable!Base64CSV masterGiftPurifications; Nullable!Base64CSV masterGiftPurifications;
Nullable!Base64CSV masterHomeBgm; Nullable!Base64CSV masterHomeBgm;
Nullable!Base64CSV masterLetter; Nullable!Base64CSV masterLetter;
Nullable!Base64CSV masterPaymentCampaign; Nullable!Base64CSV masterPaymentCampaign;
Nullable!Base64CSV masterLevel; Nullable!Base64CSV masterLevel;
Nullable!Base64CSV masterHomeBanner; Nullable!Base64CSV masterHomeBanner;
Nullable!Base64CSV masterEquipmentPurificationMaterialItems; Nullable!Base64CSV masterEquipmentPurificationMaterialItems;
Nullable!Base64CSV masterGardenPlantFlower; Nullable!Base64CSV masterGardenPlantFlower;
Nullable!Base64CSV masterWorldMap; Nullable!Base64CSV masterWorldMap;
Nullable!Base64CSV masterBoost; Nullable!Base64CSV masterBoost;
Nullable!Base64CSV masterCharacterEquipmentEvolveGroup; Nullable!Base64CSV masterCharacterEquipmentEvolveGroup;
Nullable!Base64CSV masterGardenPlantFlowerBook; Nullable!Base64CSV masterGardenPlantFlowerBook;
Nullable!Base64CSV masterCharacterFloweringGroups; Nullable!Base64CSV masterCharacterFloweringGroups;
Nullable!Base64CSV masterGardenPlantInsectBook; Nullable!Base64CSV masterGardenPlantInsectBook;
Nullable!Base64CSV masterWhaleEvent; Nullable!Base64CSV masterWhaleEvent;
Nullable!Base64CSV masterCharacterTrainableLeaderSkillLevel; Nullable!Base64CSV masterCharacterTrainableLeaderSkillLevel;
Nullable!Base64CSV masterCharacterPurification; Nullable!Base64CSV masterCharacterPurification;
Nullable!Base64CSV masterDungeon; Nullable!Base64CSV masterDungeon;
Nullable!Base64CSV masterShop; Nullable!Base64CSV masterShop;
Nullable!Base64CSV masterGuest; Nullable!Base64CSV masterGuest;
Nullable!Base64CSV masterAlbumTheater; Nullable!Base64CSV masterAlbumTheater;
Nullable!Base64CSV masterCharacter; Nullable!Base64CSV masterCharacter;
Nullable!Base64CSV masterCharacterEquipmentLevel; Nullable!Base64CSV masterCharacterEquipmentLevel;
Nullable!Base64CSV masterStageConcept; Nullable!Base64CSV masterStageConcept;
Nullable!Base64CSV masterDungeonRecommendTotalPowerGroup; Nullable!Base64CSV masterDungeonRecommendTotalPowerGroup;
Nullable!Base64CSV masterCharacterSkin; Nullable!Base64CSV masterCharacterSkin;
Nullable!Base64CSV masterGardenPlantPot; Nullable!Base64CSV masterGardenPlantPot;
Nullable!Base64CSV masterItemPurification; Nullable!Base64CSV masterItemPurification;
Nullable!Base64CSV masterStageTimeSchedule; Nullable!Base64CSV masterStageTimeSchedule;
Nullable!Base64CSV masterGardenFlowerItem; Nullable!Base64CSV masterGardenFlowerItem;
Nullable!Base64CSV masterPlantItem; Nullable!Base64CSV masterPlantItem;
} }

View File

@ -10,36 +10,36 @@ import std.typecons : Nullable;
class Response class Response
{ {
string resultCode; string resultCode;
string buildVersion; string buildVersion;
Nullable!string serverTime; Nullable!string serverTime;
Nullable!string version_; Nullable!string version_;
} }
struct Base64CSV struct Base64CSV
{ {
string[][] records; string[][] records;
alias records this; alias records this;
@safe //I guess @safe //I guess
string toRepresentation() const string toRepresentation() const
{ {
auto csvString = records.map!(r => r.join(",")).array.join("\n").idup ~ "\n"; auto csvString = records.map!(r => r.join(",")).array.join("\n").idup ~ "\n";
return Base64.encode(cast(const(ubyte[])) csvString); return Base64.encode(cast(const(ubyte[])) csvString);
} }
@safe //I guess @safe //I guess
static Base64CSV fromRepresentation(string input) static Base64CSV fromRepresentation(string input)
{ {
auto inputString = cast(string) Base64.decode(input).idup; auto inputString = cast(string) Base64.decode(input).idup;
auto records = inputString.splitLines.map!(line => line.split(',')).array; auto records = inputString.splitLines.map!(line => line.split(',')).array;
auto base64csv = Base64CSV(records); auto base64csv = Base64CSV(records);
assert(base64csv.toRepresentation == input); assert(base64csv.toRepresentation == input);
return base64csv; return base64csv;
} }
} }

View File

@ -4,5 +4,5 @@ string[uint] characterNames;
static this() static this()
{ {
mixin(import("characterNames.data.d")); mixin(import("characterNames.data.d"));
} }

View File

@ -4,5 +4,5 @@ string[uint] characterBookFlowerLanguage;
static this() static this()
{ {
mixin(import("characterBookFlowerLanguage.data.d")); mixin(import("characterBookFlowerLanguage.data.d"));
} }

View File

@ -4,5 +4,5 @@ string[uint] characterCategories;
static this() static this()
{ {
mixin(import("characterCategories.data.d")); mixin(import("characterCategories.data.d"));
} }

View File

@ -5,6 +5,6 @@ string[uint] characterLeaderSkillDescriptions;
static this() static this()
{ {
mixin(import("characterLeaderSkillNames.data.d")); mixin(import("characterLeaderSkillNames.data.d"));
mixin(import("characterLeaderSkillDescriptions.data.d")); mixin(import("characterLeaderSkillDescriptions.data.d"));
} }

View File

@ -5,6 +5,6 @@ string[uint] skillDescriptions;
static this() static this()
{ {
mixin(import("skillNames.data.d")); mixin(import("skillNames.data.d"));
mixin(import("skillDescriptions.data.d")); mixin(import("skillDescriptions.data.d"));
} }

View File

@ -4,5 +4,5 @@ string[uint] characterTextResource;
static this() static this()
{ {
mixin(import("characterTextResource.data.d")); mixin(import("characterTextResource.data.d"));
} }

View File

@ -4,5 +4,5 @@ string[uint] dungeonNames;
static this() static this()
{ {
mixin(import("dungeonNames.data.d")); mixin(import("dungeonNames.data.d"));
} }

View File

@ -4,5 +4,5 @@ string[uint] guestPartyNames;
static this() static this()
{ {
mixin(import("guestPartyNames.data.d")); mixin(import("guestPartyNames.data.d"));
} }

View File

@ -4,5 +4,5 @@ string[uint] itemNames;
static this() static this()
{ {
mixin(import("itemNames.data.d")); mixin(import("itemNames.data.d"));
} }

View File

@ -6,7 +6,7 @@ string[uint] missionRewards;
static this() static this()
{ {
mixin(import("missionNames.data.d")); mixin(import("missionNames.data.d"));
mixin(import("missionDescriptions.data.d")); mixin(import("missionDescriptions.data.d"));
mixin(import("missionRewards.data.d")); mixin(import("missionRewards.data.d"));
} }

View File

@ -4,5 +4,5 @@ string[uint] stageNames;
static this() static this()
{ {
mixin(import("stageNames.data.d")); mixin(import("stageNames.data.d"));
} }

View File

@ -6,7 +6,7 @@ string[uint] worldMapLocationDescriptions;
static this() static this()
{ {
mixin(import("worldMapLocationNames.data.d")); mixin(import("worldMapLocationNames.data.d"));
mixin(import("worldMapLocationCaptions.data.d")); mixin(import("worldMapLocationCaptions.data.d"));
mixin(import("worldMapLocationDescriptions.data.d")); mixin(import("worldMapLocationDescriptions.data.d"));
} }