mirror of https://github.com/hykilpikonna/AquaDX
[maimai2] Support user portrait
parent
75932c4b75
commit
709b977e73
|
@ -47,6 +47,10 @@ game.ongeki.version=1.05.00
|
||||||
## Set this true if you are using old version of Splash network patch and have no other choice.
|
## Set this true if you are using old version of Splash network patch and have no other choice.
|
||||||
## This is a dirty workaround. If enabled, you probably won't able to play other versions.
|
## This is a dirty workaround. If enabled, you probably won't able to play other versions.
|
||||||
game.maimai2.splash-old-patch=false
|
game.maimai2.splash-old-patch=false
|
||||||
|
## Allow users take photo as their avatar/portrait photo.
|
||||||
|
game.maimai2.userPhoto.enable=true
|
||||||
|
## Specify folder path that user portrait photo and its (.json) data save to.
|
||||||
|
game.maimai2.userPhoto.picSavePath=data/userPhoto
|
||||||
|
|
||||||
## Logging
|
## Logging
|
||||||
spring.servlet.multipart.max-file-size=10MB
|
spring.servlet.multipart.max-file-size=10MB
|
||||||
|
|
|
@ -59,7 +59,6 @@ Only JP variant is supported.
|
||||||
|
|
||||||
### Non-working features
|
### Non-working features
|
||||||
* KOP related
|
* KOP related
|
||||||
* User portrait
|
|
||||||
* Tournament mode
|
* Tournament mode
|
||||||
|
|
||||||
### Additional notes
|
### Additional notes
|
||||||
|
|
|
@ -47,6 +47,8 @@ public class Maimai2ServletController {
|
||||||
private final UploadUserPlaylogHandler uploadUserPlaylogHandler;
|
private final UploadUserPlaylogHandler uploadUserPlaylogHandler;
|
||||||
private final GetGameNgMusicIdHandler getGameNgMusicIdHandler;
|
private final GetGameNgMusicIdHandler getGameNgMusicIdHandler;
|
||||||
private final GetUserFriendSeasonRankingHandler getUserFriendSeasonRankingHandler;
|
private final GetUserFriendSeasonRankingHandler getUserFriendSeasonRankingHandler;
|
||||||
|
private final GetUserPortraitHandler getUserPortraitHandler;
|
||||||
|
private final UploadUserPortraitHandler uploadUserPortraitHandler;
|
||||||
private final CMGetUserPreviewHandler cmGetUserPreviewHandler;
|
private final CMGetUserPreviewHandler cmGetUserPreviewHandler;
|
||||||
private final CMGetSellingCardHandler cmGetSellingCardHandler;
|
private final CMGetSellingCardHandler cmGetSellingCardHandler;
|
||||||
private final GetUserCardPrintErrorHandler getUserCardPrintErrorHandler;
|
private final GetUserCardPrintErrorHandler getUserCardPrintErrorHandler;
|
||||||
|
@ -60,7 +62,7 @@ public class Maimai2ServletController {
|
||||||
GetUserLoginBonusHandler getUserLoginBonusHandler, GetUserMapHandler getUserMapHandler, GetUserFavoriteHandler getUserFavoriteHandler,
|
GetUserLoginBonusHandler getUserLoginBonusHandler, GetUserMapHandler getUserMapHandler, GetUserFavoriteHandler getUserFavoriteHandler,
|
||||||
GetUserCardHandler getUserCardHandler, GetUserMusicHandler getUserMusicHandler, GetUserRatingHandler getUserRatingHandler, GetUserRegionHandler getUserRegionHandler,
|
GetUserCardHandler getUserCardHandler, GetUserMusicHandler getUserMusicHandler, GetUserRatingHandler getUserRatingHandler, GetUserRegionHandler getUserRegionHandler,
|
||||||
GetGameChargeHandler getGameChargeHandler, GetUserChargeHandler getUserChargeHandler, GetUserCourseHandler getUserCourseHandler, UploadUserPhotoHandler uploadUserPhotoHandler,
|
GetGameChargeHandler getGameChargeHandler, GetUserChargeHandler getUserChargeHandler, GetUserCourseHandler getUserCourseHandler, UploadUserPhotoHandler uploadUserPhotoHandler,
|
||||||
UploadUserPlaylogHandler uploadUserPlaylogHandler, GetGameNgMusicIdHandler getGameNgMusicIdHandler, GetUserFriendSeasonRankingHandler getUserFriendSeasonRankingHandler,
|
UploadUserPlaylogHandler uploadUserPlaylogHandler, UploadUserPortraitHandler uploadUserPortraitHandler, GetGameNgMusicIdHandler getGameNgMusicIdHandler,GetUserPortraitHandler getUserPortraitHandler, GetUserFriendSeasonRankingHandler getUserFriendSeasonRankingHandler,
|
||||||
CMGetUserPreviewHandler cmGetUserPreviewHandler, CMGetSellingCardHandler cmGetSellingCardHandler, GetUserCardPrintErrorHandler getUserCardPrintErrorHandler, CMGetUserCharacterHandler cmGetUserCharacterHandler,
|
CMGetUserPreviewHandler cmGetUserPreviewHandler, CMGetSellingCardHandler cmGetSellingCardHandler, GetUserCardPrintErrorHandler getUserCardPrintErrorHandler, CMGetUserCharacterHandler cmGetUserCharacterHandler,
|
||||||
UpsertUserPrintHandler upsertUserPrintHandler) {
|
UpsertUserPrintHandler upsertUserPrintHandler) {
|
||||||
this.getGameSettingHandler = getGameSettingHandler;
|
this.getGameSettingHandler = getGameSettingHandler;
|
||||||
|
@ -93,6 +95,8 @@ public class Maimai2ServletController {
|
||||||
this.uploadUserPlaylogHandler = uploadUserPlaylogHandler;
|
this.uploadUserPlaylogHandler = uploadUserPlaylogHandler;
|
||||||
this.getGameNgMusicIdHandler = getGameNgMusicIdHandler;
|
this.getGameNgMusicIdHandler = getGameNgMusicIdHandler;
|
||||||
this.getUserFriendSeasonRankingHandler = getUserFriendSeasonRankingHandler;
|
this.getUserFriendSeasonRankingHandler = getUserFriendSeasonRankingHandler;
|
||||||
|
this.getUserPortraitHandler = getUserPortraitHandler;
|
||||||
|
this.uploadUserPortraitHandler = uploadUserPortraitHandler;
|
||||||
this.cmGetUserPreviewHandler = cmGetUserPreviewHandler;
|
this.cmGetUserPreviewHandler = cmGetUserPreviewHandler;
|
||||||
this.cmGetSellingCardHandler = cmGetSellingCardHandler;
|
this.cmGetSellingCardHandler = cmGetSellingCardHandler;
|
||||||
this.getUserCardPrintErrorHandler = getUserCardPrintErrorHandler;
|
this.getUserCardPrintErrorHandler = getUserCardPrintErrorHandler;
|
||||||
|
@ -188,10 +192,9 @@ public class Maimai2ServletController {
|
||||||
return getUserOptionHandler.handle(request);
|
return getUserOptionHandler.handle(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
// No support
|
|
||||||
@PostMapping("GetUserPortraitApi")
|
@PostMapping("GetUserPortraitApi")
|
||||||
public String getUserPortraitHandler(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
|
public String getUserPortraitHandler(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
|
||||||
return "{\"length\":0,\"userPortraitList\":[]}";
|
return getUserPortraitHandler.handle(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("GetUserPreviewApi")
|
@PostMapping("GetUserPreviewApi")
|
||||||
|
@ -226,10 +229,9 @@ public class Maimai2ServletController {
|
||||||
return uploadUserPlaylogHandler.handle(request);
|
return uploadUserPlaylogHandler.handle(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
// No support, return error code
|
|
||||||
@PostMapping("UploadUserPortraitApi")
|
@PostMapping("UploadUserPortraitApi")
|
||||||
public String uploadUserPortraitHandler(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
|
public String uploadUserPortraitHandler(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
|
||||||
return "{\"returnCode\":-1,\"apiName\":\"com.sega.maimai2servlet.api.UploadUserPortraitApi\"}";
|
return uploadUserPortraitHandler.handle(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("UserLoginApi")
|
@PostMapping("UserLoginApi")
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
package icu.samnyan.aqua.sega.maimai2.handler.impl;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonEncoding;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import icu.samnyan.aqua.sega.maimai2.handler.BaseHandler;
|
||||||
|
import icu.samnyan.aqua.sega.maimai2.model.request.UploadUserPortrait;
|
||||||
|
import icu.samnyan.aqua.sega.maimai2.model.request.data.UserPortrait;
|
||||||
|
import icu.samnyan.aqua.sega.util.jackson.BasicMapper;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.crypto.codec.Utf8;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author samnyan (privateamusement@protonmail.com)
|
||||||
|
*/
|
||||||
|
@Component("Maimai2GetUserPortraitHandler")
|
||||||
|
public class GetUserPortraitHandler implements BaseHandler {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(GetUserPortraitHandler.class);
|
||||||
|
|
||||||
|
private final BasicMapper mapper;
|
||||||
|
private final String picSavePath;
|
||||||
|
private final boolean enable;
|
||||||
|
|
||||||
|
public GetUserPortraitHandler(BasicMapper mapper, @Value("${game.maimai2.userPhoto.enable:}") boolean enable, @Value("${game.maimai2.userPhoto.picSavePath:}") String picSavePath) {
|
||||||
|
this.mapper = mapper;
|
||||||
|
this.picSavePath = picSavePath;
|
||||||
|
this.enable = enable;
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(Paths.get(picSavePath));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String handle(Map<String, Object> request) throws JsonProcessingException {
|
||||||
|
if (enable) {
|
||||||
|
var userId = ((Number) request.get("userId")).longValue();
|
||||||
|
var list = new ArrayList<UserPortrait>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var filePath = Paths.get(picSavePath, userId + "-up.jpg");
|
||||||
|
|
||||||
|
var templateJsonStr = Files.readString(Paths.get(picSavePath, userId + "-up.json"));
|
||||||
|
var templateUserPortrait = mapper.read(templateJsonStr, UserPortrait.class);
|
||||||
|
|
||||||
|
var buffer = new byte[10240];
|
||||||
|
|
||||||
|
if (Files.exists(filePath)) {
|
||||||
|
var stream = new FileInputStream(filePath.toFile());
|
||||||
|
while (stream.available() > 0) {
|
||||||
|
var read = stream.read(buffer, 0, 10240);
|
||||||
|
|
||||||
|
var encodeBuffer = read == 10240 ? buffer : Arrays.copyOfRange(buffer, 0, read);
|
||||||
|
|
||||||
|
var userPortrait = new UserPortrait();
|
||||||
|
|
||||||
|
userPortrait.setFileName(templateUserPortrait.getFileName());
|
||||||
|
userPortrait.setPlaceId(templateUserPortrait.getPlaceId());
|
||||||
|
userPortrait.setUserId(templateUserPortrait.getUserId());
|
||||||
|
userPortrait.setClientId(templateUserPortrait.getClientId());
|
||||||
|
userPortrait.setUploadDate(templateUserPortrait.getUploadDate());
|
||||||
|
userPortrait.setDivData(Utf8.decode(Base64.getEncoder().encode(encodeBuffer)));
|
||||||
|
|
||||||
|
userPortrait.setDivNumber(list.size());
|
||||||
|
|
||||||
|
list.add(userPortrait);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.close();
|
||||||
|
for (var i = 0; i < list.size(); i++) {
|
||||||
|
var userPortrait = list.get(i);
|
||||||
|
userPortrait.setDivLength(list.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
var map = new HashMap<String, Object>();
|
||||||
|
map.put("length", list.size());
|
||||||
|
map.put("userPortraitList", list);
|
||||||
|
|
||||||
|
var respJson = mapper.write(map);
|
||||||
|
return respJson;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Result: User photo save failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{\"length\":0,\"userPortraitList\":[]}";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package icu.samnyan.aqua.sega.maimai2.handler.impl;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import icu.samnyan.aqua.sega.maimai2.handler.BaseHandler;
|
||||||
|
import icu.samnyan.aqua.sega.maimai2.model.request.UploadUserPortrait;
|
||||||
|
import icu.samnyan.aqua.sega.util.jackson.BasicMapper;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author samnyan (privateamusement@protonmail.com)
|
||||||
|
*/
|
||||||
|
@Component("Maimai2UploadUserPortraitHandler")
|
||||||
|
public class UploadUserPortraitHandler implements BaseHandler {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(UploadUserPortraitHandler.class);
|
||||||
|
|
||||||
|
private final BasicMapper mapper;
|
||||||
|
|
||||||
|
private final String picSavePath;
|
||||||
|
private final boolean enable;
|
||||||
|
|
||||||
|
public UploadUserPortraitHandler(BasicMapper mapper, @Value("${game.maimai2.userPhoto.enable:}") boolean enable, @Value("${game.maimai2.userPhoto.picSavePath:}") String picSavePath) {
|
||||||
|
this.mapper = mapper;
|
||||||
|
this.picSavePath = picSavePath;
|
||||||
|
this.enable = enable;
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(Paths.get(picSavePath));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String handle(Map<String, Object> request) throws JsonProcessingException {
|
||||||
|
/*
|
||||||
|
Maimai DX sends splited base64 data for one jpeg image.
|
||||||
|
So, make a temp file and keep append bytes until last part received.
|
||||||
|
If finished, rename it to other name so user can keep save multiple score cards in a single day.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
var uploadUserPhoto = mapper.convert(request, UploadUserPortrait.class);
|
||||||
|
var userPhoto = uploadUserPhoto.getUserPortrait();
|
||||||
|
|
||||||
|
long userId = userPhoto.getUserId();
|
||||||
|
int divNumber = userPhoto.getDivNumber();
|
||||||
|
int divLength = userPhoto.getDivLength();
|
||||||
|
String divData = userPhoto.getDivData();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var tmp_filename = Paths.get(picSavePath, userId + "-up.tmp");
|
||||||
|
if (divNumber == 0)
|
||||||
|
Files.deleteIfExists(tmp_filename);
|
||||||
|
|
||||||
|
byte[] imageData = Base64.getDecoder().decode(divData);
|
||||||
|
Files.write(tmp_filename, imageData, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
|
||||||
|
|
||||||
|
logger.info(String.format("received user %d photo data %d/%d", userId, divNumber + 1, divLength));
|
||||||
|
|
||||||
|
if (divNumber == (divLength - 1)) {
|
||||||
|
var filename = Paths.get(picSavePath, userId + "-up.jpg");
|
||||||
|
Files.move(tmp_filename, filename, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
|
||||||
|
userPhoto.setDivData("");
|
||||||
|
var userPortaitMetaJson = mapper.write(userPhoto);
|
||||||
|
var json_filename = Paths.get(picSavePath, userId + "-up.json");
|
||||||
|
Files.write(json_filename, userPortaitMetaJson.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
|
|
||||||
|
logger.info(String.format("saved user %d photo data", userId));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Result: User photo save failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "{\"returnCode\":1,\"apiName\":\"com.sega.maimai2servlet.api.UploadUserPortraitApi\"}";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package icu.samnyan.aqua.sega.maimai2.model.request;
|
||||||
|
|
||||||
|
import icu.samnyan.aqua.sega.maimai2.model.request.data.UserPortrait;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author samnyan (privateamusement@protonmail.com)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class UploadUserPortrait implements Serializable {
|
||||||
|
private UserPortrait userPortrait;
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package icu.samnyan.aqua.sega.maimai2.model.request.data;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author samnyan (privateamusement@protonmail.com)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class UserPortrait implements Serializable {
|
||||||
|
private long userId;
|
||||||
|
private int divNumber;
|
||||||
|
private int divLength;
|
||||||
|
private String divData;
|
||||||
|
private int placeId;
|
||||||
|
private String clientId;
|
||||||
|
private String uploadDate;
|
||||||
|
private String fileName;
|
||||||
|
}
|
|
@ -38,6 +38,14 @@ public class BasicMapper {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <T> T read(String jsonStr, Class<T> toClass) throws JsonProcessingException {
|
||||||
|
return mapper.readValue(jsonStr, toClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T read(String jsonStr, TypeReference<T> toValueTypeRef) throws JsonProcessingException {
|
||||||
|
return mapper.readValue(jsonStr, toValueTypeRef);
|
||||||
|
}
|
||||||
|
|
||||||
public <T> T convert(Object map, Class<T> toClass) {
|
public <T> T convert(Object map, Class<T> toClass) {
|
||||||
return mapper.convertValue(map, toClass);
|
return mapper.convertValue(map, toClass);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue