mirror of
https://github.com/MewoLab/AquaDX.git
synced 2026-02-12 08:27:26 +08:00
[billing] Add billing server
This commit is contained in:
@@ -4,9 +4,13 @@
|
|||||||
aimedb.server.enable=true
|
aimedb.server.enable=true
|
||||||
aimedb.server.port=22345
|
aimedb.server.port=22345
|
||||||
|
|
||||||
|
## Billing server setting
|
||||||
|
billing.server.enable=true
|
||||||
|
billing.server.port=8443
|
||||||
|
|
||||||
## Server host & port return to client when boot up.
|
## Server host & port return to client when boot up.
|
||||||
## By default the same address and port from the client connection is returned.
|
## By default the same address and port from the client connection is returned.
|
||||||
## Please notice DIVA won't work with localhost or 127.0.0.1
|
## Please notice most games won't work with localhost or 127.0.0.1
|
||||||
#allnet.server.host=localhost
|
#allnet.server.host=localhost
|
||||||
#allnet.server.port=80
|
#allnet.server.port=80
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package icu.samnyan.aqua.sega.billing;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import icu.samnyan.aqua.sega.billing.model.response.BillingResponse;
|
||||||
|
import icu.samnyan.aqua.sega.billing.util.Decoder;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.ResourceLoader;
|
||||||
|
import org.springframework.util.FileCopyUtils;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.Signature;
|
||||||
|
import java.security.interfaces.RSAPrivateKey;
|
||||||
|
import java.security.spec.KeySpec;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class BillingController {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(BillingController.class);
|
||||||
|
|
||||||
|
private final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
|
private final ResourceLoader resourceLoader;
|
||||||
|
|
||||||
|
public BillingController(ResourceLoader resourceLoader) {
|
||||||
|
this.resourceLoader = resourceLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/request", produces = "text/plain")
|
||||||
|
public String powerOn(InputStream dataStream, HttpServletRequest req) throws IOException {
|
||||||
|
|
||||||
|
RSAPrivateKey key = loadBillingKey();
|
||||||
|
|
||||||
|
byte[] bytes = dataStream.readAllBytes();
|
||||||
|
Map<String, String> reqMap = Decoder.decode(bytes);
|
||||||
|
|
||||||
|
logger.info("Request: Billing, " + mapper.writeValueAsString(reqMap));
|
||||||
|
|
||||||
|
String keychipId = reqMap.getOrDefault("keychipid", "");
|
||||||
|
|
||||||
|
BillingResponse resp = new BillingResponse(
|
||||||
|
0,
|
||||||
|
100,
|
||||||
|
1,
|
||||||
|
"",
|
||||||
|
1024,
|
||||||
|
signWithKey(key, keychipId, 1024),
|
||||||
|
"1.000",
|
||||||
|
66048, // 0x00010200
|
||||||
|
signWithKey(key, keychipId, 66048),
|
||||||
|
0,
|
||||||
|
5,
|
||||||
|
"000000/0:000000/0:000000/0");
|
||||||
|
logger.info("Response: " + mapper.writeValueAsString(resp));
|
||||||
|
return resp.toString().concat("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String signWithKey(RSAPrivateKey key, String keychipId, int val) {
|
||||||
|
String result = "";
|
||||||
|
|
||||||
|
ByteBuffer sigbytes = ByteBuffer.allocate(15);
|
||||||
|
sigbytes.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
sigbytes.putInt(0, val);
|
||||||
|
sigbytes.put(4, keychipId.getBytes());
|
||||||
|
|
||||||
|
Signature sig;
|
||||||
|
try {
|
||||||
|
sig = Signature.getInstance("SHA1withRSA");
|
||||||
|
sig.initSign(key);
|
||||||
|
sig.update(sigbytes);
|
||||||
|
byte[] signedData = sig.sign();
|
||||||
|
result = bytesToHex(signedData);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to sign with billing key, " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RSAPrivateKey loadBillingKey() {
|
||||||
|
RSAPrivateKey billingKey = null;
|
||||||
|
|
||||||
|
Resource keyRes = resourceLoader.getResource("classpath:billing.der");
|
||||||
|
byte[] key;
|
||||||
|
try {
|
||||||
|
key = FileCopyUtils.copyToByteArray(keyRes.getInputStream());
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||||
|
KeySpec keySpec = new PKCS8EncodedKeySpec(key);
|
||||||
|
billingKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to load billing key file, " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return billingKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String bytesToHex(byte[] in) {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
for(byte b : in) {
|
||||||
|
builder.append(String.format("%02x", b));
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package icu.samnyan.aqua.sega.billing.model.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author samnyan (privateamusement@protonmail.com)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class BillingResponse {
|
||||||
|
private int result;
|
||||||
|
private int waittime;
|
||||||
|
private int linelimit;
|
||||||
|
private String message;
|
||||||
|
private int playlimit;
|
||||||
|
private String playlimitsig;
|
||||||
|
private String protocolver;
|
||||||
|
private int nearfull;
|
||||||
|
private String nearfullsig;
|
||||||
|
private int fixlogcnt;
|
||||||
|
private int fixinterval;
|
||||||
|
private String playhistory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "result=" + result +
|
||||||
|
"&waittime=" + waittime +
|
||||||
|
"&linelimit=" + linelimit +
|
||||||
|
"&message=" + message +
|
||||||
|
"&playlimit=" + playlimit +
|
||||||
|
"&playlimitsig=" + playlimitsig +
|
||||||
|
"&protocolver=" + protocolver +
|
||||||
|
"&nearfull=" + nearfull +
|
||||||
|
"&nearfullsig=" + nearfullsig +
|
||||||
|
"&fixlogcnt=" + fixlogcnt +
|
||||||
|
"&fixinterval=" + fixinterval +
|
||||||
|
"&playhistory=" + playhistory;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package icu.samnyan.aqua.sega.billing.util;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author samnyan (privateamusement@protonmail.com)
|
||||||
|
*/
|
||||||
|
public class Decoder {
|
||||||
|
|
||||||
|
public static Map<String, String> decode(byte[] src) {
|
||||||
|
//byte[] bytes = Base64.getMimeDecoder().decode(src);
|
||||||
|
|
||||||
|
byte[] output = RawCompression.decompress(src);
|
||||||
|
|
||||||
|
String outputString = new String(output, StandardCharsets.UTF_8).trim();
|
||||||
|
String[] split = outputString.split("&");
|
||||||
|
Map<String, String> resultMap = new HashMap<>();
|
||||||
|
for (String s :
|
||||||
|
split) {
|
||||||
|
String[] kv = s.split("=");
|
||||||
|
resultMap.put(kv[0], kv[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package icu.samnyan.aqua.sega.billing.util;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
|
import java.util.zip.DataFormatException;
|
||||||
|
import java.util.zip.Inflater;
|
||||||
|
|
||||||
|
import icu.samnyan.aqua.sega.util.ByteBufUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author samnyan (privateamusement@protonmail.com)
|
||||||
|
*/
|
||||||
|
public class RawCompression {
|
||||||
|
|
||||||
|
public static byte[] decompress(byte[] src) {
|
||||||
|
ByteBuf result = Unpooled.buffer();
|
||||||
|
byte[] buffer = new byte[100];
|
||||||
|
Inflater decompressor = new Inflater(true); // Enable no wrap option
|
||||||
|
decompressor.setInput(src);
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (!decompressor.finished()) {
|
||||||
|
int count = decompressor.inflate(buffer);
|
||||||
|
if (count == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result.writeBytes(buffer, result.readerIndex(), count);
|
||||||
|
}
|
||||||
|
decompressor.end();
|
||||||
|
|
||||||
|
return ByteBufUtil.toBytes(result);
|
||||||
|
} catch (DataFormatException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,20 +1,98 @@
|
|||||||
package icu.samnyan.aqua.spring.configuration;
|
package icu.samnyan.aqua.spring.configuration;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
|
import org.eclipse.jetty.server.Connector;
|
||||||
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
|
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.util.resource.Resource;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer;
|
||||||
|
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
|
||||||
|
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
|
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author samnyan (privateamusement@protonmail.com)
|
* @author samnyan (privateamusement@protonmail.com)
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class Config {
|
public class Config {
|
||||||
|
|
||||||
|
private final int SERVER_PORT;
|
||||||
|
private final boolean ENABLE_BILLING;
|
||||||
|
private final int BILLING_PORT;
|
||||||
|
|
||||||
|
public Config(@Value("${server.port}") int SERVER_PORT,
|
||||||
|
@Value("${billing.server.port}") int BILLING_PORT,
|
||||||
|
@Value("${billing.server.enable}") boolean ENABLE_BILLING) {
|
||||||
|
this.SERVER_PORT = SERVER_PORT;
|
||||||
|
this.BILLING_PORT = BILLING_PORT;
|
||||||
|
this.ENABLE_BILLING = ENABLE_BILLING;
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public CommonsMultipartResolver multipartResolver() {
|
public CommonsMultipartResolver multipartResolver() {
|
||||||
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
|
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
|
||||||
multipartResolver.setMaxUploadSize(-1);
|
multipartResolver.setMaxUploadSize(-1);
|
||||||
return multipartResolver;
|
return multipartResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public WebServerFactoryCustomizer<JettyServletWebServerFactory> webServerFactoryCustomizer() {
|
||||||
|
|
||||||
|
return new WebServerFactoryCustomizer<JettyServletWebServerFactory>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void customize(JettyServletWebServerFactory factory) {
|
||||||
|
|
||||||
|
factory.addServerCustomizers(new JettyServerCustomizer() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void customize(Server server) {
|
||||||
|
|
||||||
|
ServerConnector httpConnector = new ServerConnector(server);
|
||||||
|
httpConnector.setPort(SERVER_PORT);
|
||||||
|
|
||||||
|
if (ENABLE_BILLING) {
|
||||||
|
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
||||||
|
|
||||||
|
// TLS_RSA_* ciphers must be enabled, otherwise Auth NG
|
||||||
|
String[] excludedCiphersWithoutTlsRsaExclusion = Arrays
|
||||||
|
.stream(sslContextFactory.getExcludeCipherSuites())
|
||||||
|
.filter(cipher -> !cipher.equals("^TLS_RSA_.*$")).toArray(String[]::new);
|
||||||
|
|
||||||
|
URL keystoreURL = getClass().getClassLoader().getResource("server.p12");
|
||||||
|
sslContextFactory.setKeyStoreResource(Resource.newResource(keystoreURL));
|
||||||
|
sslContextFactory.setKeyStorePassword("aquaserver");
|
||||||
|
sslContextFactory.setCertAlias("ib");
|
||||||
|
sslContextFactory.setExcludeCipherSuites(excludedCiphersWithoutTlsRsaExclusion);
|
||||||
|
|
||||||
|
HttpConfiguration httpsConfiguration = new HttpConfiguration();
|
||||||
|
httpsConfiguration.addCustomizer(new SecureRequestCustomizer());
|
||||||
|
|
||||||
|
ServerConnector httpsConnector = new ServerConnector(server,
|
||||||
|
new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
|
||||||
|
new HttpConnectionFactory(httpsConfiguration));
|
||||||
|
httpsConnector.setPort(BILLING_PORT);
|
||||||
|
|
||||||
|
server.setConnectors(new Connector[] { httpConnector, httpsConnector });
|
||||||
|
} else {
|
||||||
|
server.setConnectors(new Connector[] { httpConnector });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,19 +27,26 @@ public class AutoChecker {
|
|||||||
private final String AIMEDB_BIND;
|
private final String AIMEDB_BIND;
|
||||||
private final int AIMEDB_PORT;
|
private final int AIMEDB_PORT;
|
||||||
private final boolean AIMEDB_ENABLED;
|
private final boolean AIMEDB_ENABLED;
|
||||||
|
private final boolean BILLING_ENABLED;
|
||||||
|
private final int BILLING_PORT;
|
||||||
|
|
||||||
public AutoChecker(
|
public AutoChecker(
|
||||||
@Value("${server.host:}") String SERVER_PORT,
|
@Value("${server.host:}") String SERVER_PORT,
|
||||||
@Value("${allnet.server.host:}") String ALLNET_HOST,
|
@Value("${allnet.server.host:}") String ALLNET_HOST,
|
||||||
@Value("${allnet.server.port:}") String ALLNET_PORT,
|
@Value("${allnet.server.port:}") String ALLNET_PORT,
|
||||||
@Value("${aimedb.server.address}") String AIMEDB_BIND,
|
@Value("${aimedb.server.address}") String AIMEDB_BIND,
|
||||||
@Value("${aimedb.server.port}") int AIMEDB_PORT,
|
@Value("${aimedb.server.port}") int AIMEDB_PORT,
|
||||||
@Value("${aimedb.server.enable}") boolean AIMEDB_ENABLED) {
|
@Value("${aimedb.server.enable}") boolean AIMEDB_ENABLED,
|
||||||
|
@Value("${billing.server.port}") int BILLING_PORT,
|
||||||
|
@Value("${billing.server.enable}") boolean BILLING_ENABLED) {
|
||||||
this.SERVER_PORT = SERVER_PORT;
|
this.SERVER_PORT = SERVER_PORT;
|
||||||
this.ALLNET_HOST_OVERRIDE = ALLNET_HOST;
|
this.ALLNET_HOST_OVERRIDE = ALLNET_HOST;
|
||||||
this.ALLNET_PORT_OVERRIDE = ALLNET_PORT;
|
this.ALLNET_PORT_OVERRIDE = ALLNET_PORT;
|
||||||
this.AIMEDB_BIND = AIMEDB_BIND;
|
this.AIMEDB_BIND = AIMEDB_BIND;
|
||||||
this.AIMEDB_PORT = AIMEDB_PORT;
|
this.AIMEDB_PORT = AIMEDB_PORT;
|
||||||
this.AIMEDB_ENABLED = AIMEDB_ENABLED;
|
this.AIMEDB_ENABLED = AIMEDB_ENABLED;
|
||||||
|
this.BILLING_PORT = BILLING_PORT;
|
||||||
|
this.BILLING_ENABLED = BILLING_ENABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void check() {
|
public void check() {
|
||||||
@@ -70,6 +77,19 @@ public class AutoChecker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check billing
|
||||||
|
System.out.print(" Billing : ");
|
||||||
|
if(!BILLING_ENABLED) {
|
||||||
|
System.out.println("DISABLED, SKIP");
|
||||||
|
} else {
|
||||||
|
String host = ALLNET_HOST_OVERRIDE.equals("") ? "127.0.0.1" : ALLNET_HOST_OVERRIDE;
|
||||||
|
try (Socket test = new Socket(host, BILLING_PORT)){
|
||||||
|
System.out.println("OK");
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("ERROR!!");
|
||||||
|
System.out.println(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check http part
|
// Check http part
|
||||||
System.out.print(" AllNet : ");
|
System.out.print(" AllNet : ");
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
aimedb.server.enable=true
|
aimedb.server.enable=true
|
||||||
aimedb.server.address=0.0.0.0
|
aimedb.server.address=0.0.0.0
|
||||||
aimedb.server.port=22345
|
aimedb.server.port=22345
|
||||||
|
## Billing server setting
|
||||||
|
billing.server.enable=true
|
||||||
|
billing.server.port=8443
|
||||||
## Server host & port return to client when boot up.
|
## Server host & port return to client when boot up.
|
||||||
## By default the same address and port from the client connection is returned.
|
## By default the same address and port from the client connection is returned.
|
||||||
## Please notice DIVA won't work with localhost or 127.0.0.1
|
## Please notice DIVA won't work with localhost or 127.0.0.1
|
||||||
|
|||||||
BIN
src/main/resources/billing.der
Normal file
BIN
src/main/resources/billing.der
Normal file
Binary file not shown.
BIN
src/main/resources/server.p12
Normal file
BIN
src/main/resources/server.p12
Normal file
Binary file not shown.
@@ -5,6 +5,8 @@ aimedb.server.port=22345
|
|||||||
allnet.server.host=localhost
|
allnet.server.host=localhost
|
||||||
allnet.server.port=80
|
allnet.server.port=80
|
||||||
aimedb.server.address=127.0.0.1
|
aimedb.server.address=127.0.0.1
|
||||||
|
billing.server.enable=true
|
||||||
|
billing.server.port=8443
|
||||||
## Http Server Port
|
## Http Server Port
|
||||||
server.port=80
|
server.port=80
|
||||||
spring.flyway.locations=classpath:db/migration/mysql
|
spring.flyway.locations=classpath:db/migration/mysql
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ aimedb.server.port=22345
|
|||||||
allnet.server.host=localhost
|
allnet.server.host=localhost
|
||||||
allnet.server.port=80
|
allnet.server.port=80
|
||||||
aimedb.server.address=127.0.0.1
|
aimedb.server.address=127.0.0.1
|
||||||
|
billing.server.enable=true
|
||||||
|
billing.server.port=8443
|
||||||
## Http Server Port
|
## Http Server Port
|
||||||
server.port=80
|
server.port=80
|
||||||
spring.datasource.driver-class-name=org.sqlite.JDBC
|
spring.datasource.driver-class-name=org.sqlite.JDBC
|
||||||
|
|||||||
Reference in New Issue
Block a user