commit 9d4722c304d22e9e6d8e6ac0f220399ec5aa4427 Author: alterwain Date: Fri Feb 7 12:24:31 2025 +0300 Initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c123ee4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store + +src/main/resources/application.properties \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..486695a --- /dev/null +++ b/pom.xml @@ -0,0 +1,96 @@ + + + 4.0.0 + + com.alterdekim.server.durak + durakServer + 1.0-SNAPSHOT + jar + + + org.springframework.boot + spring-boot-starter-parent + 3.0.0 + + + + 1.8 + 1.8 + UTF-8 + + + + + log4j + log4j + 1.2.17 + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + + mysql + mysql-connector-java + 8.0.33 + + + org.springframework.boot + spring-boot-starter-security + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.11.1 + + + org.projectlombok + lombok + true + 1.18.28 + provided + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + org.projectlombok + lombok + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 14 + 14 + + + + + \ No newline at end of file diff --git a/src/main/java/com/alterdekim/server/durak/Application.java b/src/main/java/com/alterdekim/server/durak/Application.java new file mode 100644 index 0000000..93f3c03 --- /dev/null +++ b/src/main/java/com/alterdekim/server/durak/Application.java @@ -0,0 +1,23 @@ +package com.alterdekim.server.durak; + +import com.alterdekim.server.durak.storage.StorageService; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.annotation.EnableScheduling; + +@EnableScheduling +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @Bean + CommandLineRunner init(StorageService storageService) { + return (args) -> { + storageService.init(); + }; + } +} \ No newline at end of file diff --git a/src/main/java/com/alterdekim/server/durak/component/GameListener.java b/src/main/java/com/alterdekim/server/durak/component/GameListener.java new file mode 100644 index 0000000..dd30358 --- /dev/null +++ b/src/main/java/com/alterdekim/server/durak/component/GameListener.java @@ -0,0 +1,65 @@ +package com.alterdekim.server.durak.component; + +import com.alterdekim.server.durak.component.durak.ConnectionProcessor; +import com.alterdekim.server.durak.component.durak.ConnectionState; +import com.alterdekim.server.durak.component.durak.HandshakeProcessor; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +public class GameListener extends Thread { + + private Socket sock; + private GameServer parent; + + @Getter + @Setter + private Integer uid; + + private InputStream in; + private OutputStream out; + + private ConnectionState connectionState; + + private final Map connectionProcessors = new HashMap<>(); + + public GameListener(Socket sock, GameServer parent, Integer uid) { + try { + this.sock = sock; + this.parent = parent; + this.uid = uid; + this.in = sock.getInputStream(); + this.out = sock.getOutputStream(); + this.connectionState = ConnectionState.HANDSHAKE; + this.connectionProcessors.put(ConnectionState.HANDSHAKE, new HandshakeProcessor(this.in, this.out, this.sock)); + } catch (IOException e) { + log.error("GameListener constructor error", e); + } + } + + @Override + public void run() { + try { + while(!sock.isClosed()) { + this.connectionState = this.connectionProcessors.get(this.connectionState).process(); + } + this.parent.close(this.getUid()); + } catch( Exception e ) { + log.error("RTMPListener.run() error", e); + } finally { + try { + sock.close(); + } catch (Exception e) { + log.error("RTMPListener.run() error", e); + } + } + } +} diff --git a/src/main/java/com/alterdekim/server/durak/component/GameServer.java b/src/main/java/com/alterdekim/server/durak/component/GameServer.java new file mode 100644 index 0000000..c42cc77 --- /dev/null +++ b/src/main/java/com/alterdekim/server/durak/component/GameServer.java @@ -0,0 +1,51 @@ +package com.alterdekim.server.durak.component; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.net.ServerSocket; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Component +public class GameServer { + /* ip: default.durak.rstgames.com */ + + private final Integer port = 10772; // 80 + + private final ConcurrentHashMap clients; + private ServerSocket serverSocket; + + public GameServer() { + this.clients = new ConcurrentHashMap<>(); + } + + public void close( Integer id ) { + clients.remove(id); + } + + @Scheduled(fixedDelay = 5000) + public void start() { + try { + this.serverSocket = new ServerSocket(port); + log.info("GameServer.start() started"); + while(true) { + Integer guid = (int) (Math.random() * 10000); + GameListener listener = new GameListener(serverSocket.accept(), this, guid); + log.info("GameServer.start() got client"); + this.clients.put(guid, listener); + listener.start(); + } + } catch (IOException e) { + log.error("GameServer.start() error", e); + } finally { + try { + serverSocket.close(); + } catch (IOException e) { + log.error("GameServer.server.close() error", e); + } + } + } +} diff --git a/src/main/java/com/alterdekim/server/durak/component/durak/ConnectionProcessor.java b/src/main/java/com/alterdekim/server/durak/component/durak/ConnectionProcessor.java new file mode 100644 index 0000000..211ee61 --- /dev/null +++ b/src/main/java/com/alterdekim/server/durak/component/durak/ConnectionProcessor.java @@ -0,0 +1,39 @@ +package com.alterdekim.server.durak.component.durak; + +import com.alterdekim.server.durak.utils.SerializerUtils; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.util.List; + +@Slf4j +@Getter +@AllArgsConstructor +public abstract class ConnectionProcessor { + private InputStream inputStream; + private OutputStream outputStream; + private Socket sock; + + public abstract ConnectionState getState(); + public abstract ConnectionState process() throws IOException; + + public void write(List bytes) throws IOException { + this.outputStream.write(SerializerUtils.bytesToPrimitive(bytes)); + this.outputStream.flush(); + } + + public void close() { + log.info("ConnectionProcessor.close()"); + try { + this.sock.close(); + } catch (IOException e) { + log.error("ConnectionProcessor.close()", e); + } + } +} + diff --git a/src/main/java/com/alterdekim/server/durak/component/durak/ConnectionState.java b/src/main/java/com/alterdekim/server/durak/component/durak/ConnectionState.java new file mode 100644 index 0000000..cfc847f --- /dev/null +++ b/src/main/java/com/alterdekim/server/durak/component/durak/ConnectionState.java @@ -0,0 +1,6 @@ +package com.alterdekim.server.durak.component.durak; + +public enum ConnectionState { + HANDSHAKE, + CONNECTED +} diff --git a/src/main/java/com/alterdekim/server/durak/component/durak/HandshakeProcessor.java b/src/main/java/com/alterdekim/server/durak/component/durak/HandshakeProcessor.java new file mode 100644 index 0000000..675a82d --- /dev/null +++ b/src/main/java/com/alterdekim/server/durak/component/durak/HandshakeProcessor.java @@ -0,0 +1,38 @@ +package com.alterdekim.server.durak.component.durak; + +import com.alterdekim.server.durak.utils.HashUtils; +import com.alterdekim.server.durak.utils.SerializerUtils; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +@Slf4j +public class HandshakeProcessor extends ConnectionProcessor { + + public HandshakeProcessor(InputStream inputStream, OutputStream outputStream, Socket sock) { + super(inputStream, outputStream, sock); + } + + @Override + public ConnectionState getState() { + return ConnectionState.HANDSHAKE; + } + + @Override + public ConnectionState process() throws IOException { + byte[] b = new byte[2048]; + if( this.getInputStream().read(b) == -1 ) { + close(); + return getState(); + } + log.info("HandshakeProcessor received first message: {}", HashUtils.bytesToHex(b)); + this.getOutputStream().write("authorized\n".getBytes()); + //this.getOutputStream().write("confirmed\n".getBytes()); + this.getOutputStream().write("server{\"time\":\"2024-04-16T00:59:48.045Z\",\"id\":\"u0\"}\n".getBytes()); + this.getOutputStream().flush(); + return getState(); + } +} \ No newline at end of file diff --git a/src/main/java/com/alterdekim/server/durak/controller/FileServerController.java b/src/main/java/com/alterdekim/server/durak/controller/FileServerController.java new file mode 100644 index 0000000..189c6a1 --- /dev/null +++ b/src/main/java/com/alterdekim/server/durak/controller/FileServerController.java @@ -0,0 +1,49 @@ +package com.alterdekim.server.durak.controller; + +import com.alterdekim.server.durak.storage.StorageFileNotFoundException; +import com.alterdekim.server.durak.storage.StorageProperties; +import com.alterdekim.server.durak.storage.StorageService; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import java.nio.file.Files; +import java.nio.file.Path; + +@Controller +@EnableConfigurationProperties(StorageProperties.class) +@Slf4j +public class FileServerController { + + private final StorageService storageService; + + @Autowired + public FileServerController(StorageService storageService) { + this.storageService = storageService; + } + + @RequestMapping(value = "/durak/{*resPath}", method = RequestMethod.GET) + @ResponseBody + public ResponseEntity serveFile(@PathVariable("resPath") String resPath, HttpServletRequest request) { + try { + String filename = request.getRequestURL().substring(request.getRequestURL().indexOf("/durak/")); + Resource file = storageService.loadAsResource(filename); + return ResponseEntity.ok().contentType(MediaType.parseMediaType("image/png")).body(file); + } catch (Exception e) { + log.error(e.getMessage()); + } + return ResponseEntity.notFound().build(); + } + + @ExceptionHandler(StorageFileNotFoundException.class) + public ResponseEntity handleStorageFileNotFound(StorageFileNotFoundException exc) { + return ResponseEntity.notFound().build(); + } +} diff --git a/src/main/java/com/alterdekim/server/durak/controller/StaticController.java b/src/main/java/com/alterdekim/server/durak/controller/StaticController.java new file mode 100644 index 0000000..23fa8e8 --- /dev/null +++ b/src/main/java/com/alterdekim/server/durak/controller/StaticController.java @@ -0,0 +1,375 @@ +package com.alterdekim.server.durak.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Slf4j +@Controller +public class StaticController { + + @GetMapping("/servers.json") + public ResponseEntity getServers() { + return ResponseEntity.ok("{\n" + + " \"user\": {\n" + + " \"u0\": {\n" + + " \"name\": {\n" + + " \"en\": \"Diamond (rating +50%)\",\n" + + " \"ru\": \"Алмаз (рейтинг +50%)\"\n" + + " },\n" + + " \"image\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/serverimages/s_u0en.png?2\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/serverimages/s_u0ru.png?2\"\n" + + " },\n" + + " \"host\": \"10.66.66.3\",\n" + + " \"port\": 10772,\n" + + " \"android\": 10772,\n" + + " \"huawei\": 10772,\n" + + " \"rustore\": 10772,\n" + + " \"web_url\": \"wss://10.66.66.3/ws0\",\n" + + " \"weight\": 0.0\n" + + " }\n" + + " },\n" + + " \"web_auth\":\"https://10.66.66.3/auth\",\n" + + " \"shuffle_android\":194,\n" + + " \"shuffle_ios\": 194,\n" + + " \"shuffle_huawei\": 194,\n" + + " \"shuffle_rustore\": 194,\n" + + " \"shuffle_web\": 194,\n" + + " \"avatar_size\": 200,\n" + + " \"chat_img_size\": 1024,\n" + + " \"reconnect_interval\": 5000,\n" + + " \"help\": {\n" + + " \"android\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/android/en/help.html\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/android/ru/help.html\",\n" + + " \"hy\": \"http://10.66.66.3/durak/public/android/hy/help.html\",\n" + + " \"az\": \"http://10.66.66.3/durak/public/android/az/help.html\",\n" + + " \"de\": \"http://10.66.66.3/durak/public/android/de/help.html\",\n" + + " \"es\": \"http://10.66.66.3/durak/public/android/es/help.html\",\n" + + " \"fr\": \"http://10.66.66.3/durak/public/android/fr/help.html\",\n" + + " \"it\": \"http://10.66.66.3/durak/public/android/it/help.html\",\n" + + " \"tr\": \"http://10.66.66.3/durak/public/android/tr/help.html\",\n" + + " \"he\": \"http://10.66.66.3/durak/public/android/he/help.html\",\n" + + " \"uk\": \"http://10.66.66.3/durak/public/android/uk/help.html\"\n" + + " },\n" + + " \"huawei\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/huawei/en/help.html\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/huawei/ru/help.html\"\n" + + " },\n" + + " \"rustore\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/rustore/en/help.html\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/rustore/ru/help.html\"\n" + + " },\n" + + " \"web\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/web/en/help.html\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/web/ru/help.html\"\n" + + " },\n" + + " \"ios\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/en/help.html\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/ru/help.html\",\n" + + " \"hy\": \"http://10.66.66.3/durak/public/hy/help.html\",\n" + + " \"az\": \"http://10.66.66.3/durak/public/az/help.html\",\n" + + " \"de\": \"http://10.66.66.3/durak/public/de/help.html\",\n" + + " \"es\": \"http://10.66.66.3/durak/public/es/help.html\",\n" + + " \"fr\": \"http://10.66.66.3/durak/public/fr/help.html\",\n" + + " \"it\": \"http://10.66.66.3/durak/public/it/help.html\",\n" + + " \"tr\": \"http://10.66.66.3/durak/public/tr/help.html\",\n" + + " \"he\": \"http://10.66.66.3/durak/public/he/help.html\",\n" + + " \"uk\": \"http://10.66.66.3/durak/public/uk/help.html\"\n" + + " }\n" + + " },\n" + + " \"tos\": {\n" + + " \"android\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/android/en/tos.html\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/android/ru/tos.html\",\n" + + " \"hy\": \"http://10.66.66.3/durak/public/android/hy/tos.html\",\n" + + " \"az\": \"http://10.66.66.3/durak/public/android/az/tos.html\",\n" + + " \"de\": \"http://10.66.66.3/durak/public/android/de/tos.html\",\n" + + " \"es\": \"http://10.66.66.3/durak/public/android/es/tos.html\",\n" + + " \"fr\": \"http://10.66.66.3/durak/public/android/fr/tos.html\",\n" + + " \"it\": \"http://10.66.66.3/durak/public/android/it/tos.html\",\n" + + " \"tr\": \"http://10.66.66.3/durak/public/android/tr/tos.html\",\n" + + " \"he\": \"http://10.66.66.3/durak/public/android/he/tos.html\",\n" + + " \"uk\": \"http://10.66.66.3/durak/public/android/uk/tos.html\"\n" + + " },\n" + + " \"huawei\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/huawei/en/tos.html\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/huawei/ru/tos.html\"\n" + + " },\n" + + " \"rustore\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/rustore/en/tos.html\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/rustore/ru/tos.html\"\n" + + " },\n" + + " \"web\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/web/en/tos.html\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/web/ru/tos.html\"\n" + + " },\n" + + " \"ios\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/en/tos.html\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/ru/tos.html\",\n" + + " \"hy\": \"http://10.66.66.3/durak/public/hy/tos.html\",\n" + + " \"az\": \"http://10.66.66.3/durak/public/az/tos.html\",\n" + + " \"de\": \"http://10.66.66.3/durak/public/de/tos.html\",\n" + + " \"es\": \"http://10.66.66.3/durak/public/es/tos.html\",\n" + + " \"fr\": \"http://10.66.66.3/durak/public/fr/tos.html\",\n" + + " \"it\": \"http://10.66.66.3/durak/public/it/tos.html\",\n" + + " \"tr\": \"http://10.66.66.3/durak/public/tr/tos.html\",\n" + + " \"he\": \"http://10.66.66.3/durak/public/he/tos.html\",\n" + + " \"uk\": \"http://10.66.66.3/durak/public/uk/tos.html\"\n" + + " }\n" + + " },\n" + + " \"news\": {\n" + + " \"android\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/android/en/news.html\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/android/ru/news.html\",\n" + + " \"hy\": \"http://10.66.66.3/durak/public/android/hy/news.html\",\n" + + " \"az\": \"http://10.66.66.3/durak/public/android/az/news.html\",\n" + + " \"de\": \"http://10.66.66.3/durak/public/android/de/news.html\",\n" + + " \"es\": \"http://10.66.66.3/durak/public/android/es/news.html\",\n" + + " \"fr\": \"http://10.66.66.3/durak/public/android/fr/news.html\",\n" + + " \"it\": \"http://10.66.66.3/durak/public/android/it/news.html\",\n" + + " \"tr\": \"http://10.66.66.3/durak/public/android/tr/news.html\",\n" + + " \"he\": \"http://10.66.66.3/durak/public/android/he/news.html\",\n" + + " \"uk\": \"http://10.66.66.3/durak/public/android/uk/news.html\"\n" + + " },\n" + + " \"huawei\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/huawei/en/news.html\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/huawei/ru/news.html\"\n" + + " },\n" + + " \"rustore\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/rustore/en/news.html\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/rustore/ru/news.html\"\n" + + " },\n" + + " \"web\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/web/en/news.html\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/web/ru/news.html\"\n" + + " },\n" + + " \"ios\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/en/news.html\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/ru/news.html\",\n" + + " \"hy\": \"http://10.66.66.3/durak/public/hy/news.html\",\n" + + " \"az\": \"http://10.66.66.3/durak/public/az/news.html\",\n" + + " \"de\": \"http://10.66.66.3/durak/public/de/news.html\",\n" + + " \"es\": \"http://10.66.66.3/durak/public/es/news.html\",\n" + + " \"fr\": \"http://10.66.66.3/durak/public/fr/news.html\",\n" + + " \"it\": \"http://10.66.66.3/durak/public/it/news.html\",\n" + + " \"tr\": \"http://10.66.66.3/durak/public/tr/news.html\",\n" + + " \"he\": \"http://10.66.66.3/durak/public/he/news.html\",\n" + + " \"uk\": \"http://10.66.66.3/durak/public/uk/news.html\"\n" + + " }\n" + + " },\n" + + " \"share\": {\n" + + " \"android\": {\n" + + " \"en\": \"http://durak.rstgames.com/android/en/index.html\",\n" + + " \"ru\": \"http://durak.rstgames.com/android/ru/index.html\",\n" + + " \"hy\": \"http://durak.rstgames.com/android/hy/index.html\",\n" + + " \"az\": \"http://durak.rstgames.com/android/az/index.html\",\n" + + " \"de\": \"http://durak.rstgames.com/android/de/index.html\",\n" + + " \"es\": \"http://durak.rstgames.com/android/es/index.html\",\n" + + " \"fr\": \"http://durak.rstgames.com/android/fr/index.html\",\n" + + " \"it\": \"http://durak.rstgames.com/android/it/index.html\",\n" + + " \"tr\": \"http://durak.rstgames.com/android/tr/index.html\",\n" + + " \"he\": \"http://durak.rstgames.com/android/he/index.html\",\n" + + " \"uk\": \"http://durak.rstgames.com/android/uk/index.html\"\n" + + " },\n" + + " \"huawei\": {\n" + + " \"en\": \"http://durak.rstgames.com/huawei/en/index.html\",\n" + + " \"ru\": \"http://durak.rstgames.com/huawei/ru/index.html\"\n" + + " },\n" + + " \"rustore\": {\n" + + " \"en\": \"http://durak.rstgames.com/rustore/en/index.html\",\n" + + " \"ru\": \"http://durak.rstgames.com/rustore/ru/index.html\"\n" + + " },\n" + + " \"web\": {\n" + + " \"en\": \"http://durak.rstgames.com/web/en/index.html\",\n" + + " \"ru\": \"http://durak.rstgames.com/web/ru/index.html\"\n" + + " },\n" + + " \"ios\": {\n" + + " \"en\": \"http://durak.rstgames.com/en/index.html\",\n" + + " \"ru\": \"http://durak.rstgames.com/ru/index.html\",\n" + + " \"hy\": \"http://durak.rstgames.com/hy/index.html\",\n" + + " \"az\": \"http://durak.rstgames.com/az/index.html\",\n" + + " \"de\": \"http://durak.rstgames.com/de/index.html\",\n" + + " \"es\": \"http://durak.rstgames.com/es/index.html\",\n" + + " \"fr\": \"http://durak.rstgames.com/fr/index.html\",\n" + + " \"it\": \"http://durak.rstgames.com/it/index.html\",\n" + + " \"tr\": \"http://durak.rstgames.com/tr/index.html\",\n" + + " \"he\": \"http://durak.rstgames.com/he/index.html\",\n" + + " \"uk\": \"http://durak.rstgames.com/uk/index.html\"\n" + + " }\n" + + " },\n" + + " \"flags\": {\n" + + " \"android\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/android/en/en.png\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/android/ru/ru.png\",\n" + + " \"hy\": \"http://10.66.66.3/durak/public/android/hy/hy.png\",\n" + + " \"az\": \"http://10.66.66.3/durak/public/android/az/az.png\",\n" + + " \"de\": \"http://10.66.66.3/durak/public/android/de/de.png\",\n" + + " \"es\": \"http://10.66.66.3/durak/public/android/es/es.png\",\n" + + " \"fr\": \"http://10.66.66.3/durak/public/android/fr/fr.png\",\n" + + " \"it\": \"http://10.66.66.3/durak/public/android/it/it.png\",\n" + + " \"tr\": \"http://10.66.66.3/durak/public/android/tr/tr.png\",\n" + + " \"he\": \"http://10.66.66.3/durak/public/android/he/he.png\",\n" + + " \"uk\": \"http://10.66.66.3/durak/public/android/uk/uk.png\"\n" + + " },\n" + + " \"huawei\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/android/en/en.png\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/android/ru/ru.png\",\n" + + " \"hy\": \"http://10.66.66.3/durak/public/android/hy/hy.png\",\n" + + " \"az\": \"http://10.66.66.3/durak/public/android/az/az.png\",\n" + + " \"de\": \"http://10.66.66.3/durak/public/android/de/de.png\",\n" + + " \"es\": \"http://10.66.66.3/durak/public/android/es/es.png\",\n" + + " \"fr\": \"http://10.66.66.3/durak/public/android/fr/fr.png\",\n" + + " \"it\": \"http://10.66.66.3/durak/public/android/it/it.png\",\n" + + " \"tr\": \"http://10.66.66.3/durak/public/android/tr/tr.png\",\n" + + " \"he\": \"http://10.66.66.3/durak/public/android/he/he.png\",\n" + + " \"uk\": \"http://10.66.66.3/durak/public/android/uk/uk.png\"\n" + + " },\n" + + " \"rustore\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/android/en/en.png\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/android/ru/ru.png\",\n" + + " \"hy\": \"http://10.66.66.3/durak/public/android/hy/hy.png\",\n" + + " \"az\": \"http://10.66.66.3/durak/public/android/az/az.png\",\n" + + " \"de\": \"http://10.66.66.3/durak/public/android/de/de.png\",\n" + + " \"es\": \"http://10.66.66.3/durak/public/android/es/es.png\",\n" + + " \"fr\": \"http://10.66.66.3/durak/public/android/fr/fr.png\",\n" + + " \"it\": \"http://10.66.66.3/durak/public/android/it/it.png\",\n" + + " \"tr\": \"http://10.66.66.3/durak/public/android/tr/tr.png\",\n" + + " \"he\": \"http://10.66.66.3/durak/public/android/he/he.png\",\n" + + " \"uk\": \"http://10.66.66.3/durak/public/android/uk/uk.png\"\n" + + " },\n" + + " \"web\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/android/en/en.png\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/android/ru/ru.png\",\n" + + " \"hy\": \"http://10.66.66.3/durak/public/android/hy/hy.png\",\n" + + " \"az\": \"http://10.66.66.3/durak/public/android/az/az.png\",\n" + + " \"de\": \"http://10.66.66.3/durak/public/android/de/de.png\",\n" + + " \"es\": \"http://10.66.66.3/durak/public/android/es/es.png\",\n" + + " \"fr\": \"http://10.66.66.3/durak/public/android/fr/fr.png\",\n" + + " \"it\": \"http://10.66.66.3/durak/public/android/it/it.png\",\n" + + " \"tr\": \"http://10.66.66.3/durak/public/android/tr/tr.png\",\n" + + " \"he\": \"http://10.66.66.3/durak/public/android/he/he.png\",\n" + + " \"uk\": \"http://10.66.66.3/durak/public/android/uk/uk.png\"\n" + + " },\n" + + " \"ios\": {\n" + + " \"en\": \"http://10.66.66.3/durak/public/en/en.png\",\n" + + " \"ru\": \"http://10.66.66.3/durak/public/ru/ru.png\",\n" + + " \"hy\": \"http://10.66.66.3/durak/public/hy/hy.png\",\n" + + " \"az\": \"http://10.66.66.3/durak/public/az/az.png\",\n" + + " \"de\": \"http://10.66.66.3/durak/public/de/de.png\",\n" + + " \"es\": \"http://10.66.66.3/durak/public/es/es.png\",\n" + + " \"fr\": \"http://10.66.66.3/durak/public/fr/fr.png\",\n" + + " \"it\": \"http://10.66.66.3/durak/public/it/it.png\",\n" + + " \"tr\": \"http://10.66.66.3/durak/public/tr/tr.png\",\n" + + " \"he\": \"http://10.66.66.3/durak/public/he/he.png\",\n" + + " \"uk\": \"http://10.66.66.3/durak/public/uk/uk.png\"\n" + + " }\n" + + " },\n" + + " \"store\": \"10.66.66.3\",\n" + + " \"assets_url\": \"http://10.66.66.3/durak/assets/\",\n" + + " \"assets_url_android\": \"http://10.66.66.3/durak/assets/\",\n" + + " \"assets_count\": 0,\n" + + " \"assets\": {\n" + + " \"smile_classic\": 11,\n" + + " \"smile_g_blond\": 10,\n" + + " \"smile_g_dark\": 10,\n" + + " \"smile_m_blond\": 11,\n" + + " \"smile_m_dark\": 10,\n" + + " \"smile_cat\": 10,\n" + + " \"smile_panda\": 10,\n" + + " \"smile_raccoon\": 10,\n" + + " \"smile_m_long\": 10,\n" + + " \"smile_g_glam\": 10,\n" + + " \"smile_g_veil\": 10,\n" + + " \"shirt_classic\": 15,\n" + + " \"shirt_gzhel\": 15,\n" + + " \"shirt_greenblue\": 13,\n" + + " \"shirt_redblue\": 13,\n" + + " \"shirt_khokhloma\": 15,\n" + + " \"shirt_buta\": 15,\n" + + " \"frame_classic\": 10,\n" + + " \"frame_chain\": 3,\n" + + " \"frame_flower\": 10,\n" + + " \"frame_leafs\": 14,\n" + + " \"frame_nimb\": 12,\n" + + " \"frame_heart\":11,\n" + + " \"frame_heartchain\":11,\n" + + " \"frame_stars\":2,\n" + + " \"smile_suntan\":16,\n" + + " \"shirt_gold\":8,\n" + + " \"shirt_wolf\":8,\n" + + " \"shirt_spider\":8,\n" + + " \"smile_pumkin\":2,\n" + + " \"smile_santa\":2,\n" + + " \"smile_sun\":4,\n" + + " \"smile_spring\":1\n" + + " },\n" + + "\n" + + " \"achieves_url\": \"http://10.66.66.3/durak/achieves/\",\n" + + " \"achieves_url_android\": \"http://10.66.66.3/durak/achieves/\",\n" + + " \"achieves_count\": 58,\n" + + " \"achieves\": {\n" + + " \"no_achieve\" : 1,\n" + + " \"win_pts_100\" : 1,\n" + + " \"win_pts_1000\" : 1,\n" + + " \"win_pts_10000\" : 1,\n" + + " \"win_pts_100000\" : 1,\n" + + " \"win_pts_1000000\" : 1,\n" + + " \"rating_160k\" : 1,\n" + + " \"rating_320k\" : 1,\n" + + " \"rating_480k\" : 1,\n" + + " \"rating_640k\" : 1,\n" + + " \"rating_800k\" : 1,\n" + + " \"friend_1\" : 1,\n" + + " \"friend_10\" : 1,\n" + + " \"points_10k\" : 1,\n" + + " \"points_100k\" : 1,\n" + + " \"points_1m\" : 1,\n" + + " \"points_10m\" : 1,\n" + + " \"points_100m\" : 1,\n" + + " \"points_1b\" : 1,\n" + + " \"streak_3\" : 1,\n" + + " \"streak_5\" : 1,\n" + + " \"streak_7\" : 1,\n" + + " \"streak_10\" : 1,\n" + + " \"streak_20\" : 1,\n" + + " \"streak_50\" : 1,\n" + + " \"wins_100\" : 1,\n" + + " \"wins_250\" : 1,\n" + + " \"wins_500\" : 1,\n" + + " \"wins_1000\" : 1,\n" + + " \"wins_2500\" : 1,\n" + + " \"wins_5000\" : 1,\n" + + " \"wins_10000\" : 1,\n" + + " \"draw_1\" : 1,\n" + + " \"draw_3\" : 1,\n" + + " \"draw_5\" : 1,\n" + + " \"draw_10\" : 1,\n" + + " \"draw_20\" : 1,\n" + + " \"draw_50\" : 1,\n" + + " \"draw_100\" : 1,\n" + + " \"lose_game_ace\" : 1,\n" + + " \"report_cheater\" : 1,\n" + + " \"cheat_success\" : 1,\n" + + " \"quad\" : 1,\n" + + " \"lucky\" : 1,\n" + + " \"frame_medal_bronze\":10,\n" + + " \"frame_medal_silver\":10,\n" + + " \"frame_medal_gold\": 10\n" + + " },\n" + + " \"games\": {\n" + + " \"status_android\": true,\n" + + " \"status_huawei\": true,\n" + + " \"status_rustore\": false,\n" + + " \"status_ios\": true,\n" + + " \"shuffle_android\":12,\n" + + " \"shuffle_huawei\":11,\n" + + " \"shuffle_rustore\":11,\n" + + " \"shuffle_ios\": 12,\n" + + " \"apps\": {}\n" + + " }\n" + + "}"); + } +} diff --git a/src/main/java/com/alterdekim/server/durak/security/SpringSecurity.java b/src/main/java/com/alterdekim/server/durak/security/SpringSecurity.java new file mode 100644 index 0000000..cdd86fc --- /dev/null +++ b/src/main/java/com/alterdekim/server/durak/security/SpringSecurity.java @@ -0,0 +1,27 @@ +package com.alterdekim.server.durak.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.savedrequest.HttpSessionRequestCache; + +@Configuration +@EnableWebSecurity +public class SpringSecurity { + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + HttpSessionRequestCache requestCache = new HttpSessionRequestCache(); + requestCache.setMatchingRequestParameterName(null); + http.csrf().disable() + .authorizeHttpRequests((authorize) -> + authorize.requestMatchers("/**").permitAll() + ) + .requestCache((cache) -> cache + .requestCache(requestCache) + ); + return http.build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/alterdekim/server/durak/storage/FileSystemStorageService.java b/src/main/java/com/alterdekim/server/durak/storage/FileSystemStorageService.java new file mode 100644 index 0000000..305e3bc --- /dev/null +++ b/src/main/java/com/alterdekim/server/durak/storage/FileSystemStorageService.java @@ -0,0 +1,106 @@ +package com.alterdekim.server.durak.storage; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.stream.Stream; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Service; +import org.springframework.util.FileSystemUtils; +import org.springframework.web.multipart.MultipartFile; + +@Service +public class FileSystemStorageService implements StorageService { + + private final Path rootLocation; + + @Autowired + public FileSystemStorageService(StorageProperties properties) { + + if(properties.getLocation().trim().isEmpty()){ + throw new StorageException("File upload location can not be Empty."); + } + + this.rootLocation = Paths.get(new ClassPathResource(properties.getLocation()).getPath()); + } + + @Override + public void store(MultipartFile file) { + try { + if (file.isEmpty()) { + throw new StorageException("Failed to store empty file."); + } + Path destinationFile = this.rootLocation.resolve( + Paths.get(file.getOriginalFilename())) + .normalize().toAbsolutePath(); + if (!destinationFile.getParent().equals(this.rootLocation.toAbsolutePath())) { + throw new StorageException( + "Cannot store file outside current directory."); + } + try (InputStream inputStream = file.getInputStream()) { + Files.copy(inputStream, destinationFile, + StandardCopyOption.REPLACE_EXISTING); + } + } + catch (IOException e) { + throw new StorageException("Failed to store file.", e); + } + } + + @Override + public Stream loadAll() { + try { + return Files.walk(this.rootLocation, 1) + .filter(path -> !path.equals(this.rootLocation)) + .map(this.rootLocation::relativize); + } + catch (IOException e) { + throw new StorageException("Failed to read stored files", e); + } + + } + + @Override + public Path load(String filename) { + return rootLocation.resolve(filename); + } + + @Override + public Resource loadAsResource(String filename) { + try { + Resource resource = new ClassPathResource(filename); + if (resource.exists() || resource.isReadable()) { + return resource; + } + else { + throw new StorageFileNotFoundException( + "Could not read file: " + filename); + + } + } + catch (Exception e) { + throw new StorageFileNotFoundException("Could not read file: " + filename, e); + } + } + + @Override + public void deleteAll() { + FileSystemUtils.deleteRecursively(rootLocation.toFile()); + } + + @Override + public void init() { + // try { + // Files.createDirectories(rootLocation); + // } + // catch (IOException e) { + // throw new StorageException("Could not initialize storage", e); + // } + } +} diff --git a/src/main/java/com/alterdekim/server/durak/storage/StorageException.java b/src/main/java/com/alterdekim/server/durak/storage/StorageException.java new file mode 100644 index 0000000..3a0cb6b --- /dev/null +++ b/src/main/java/com/alterdekim/server/durak/storage/StorageException.java @@ -0,0 +1,12 @@ +package com.alterdekim.server.durak.storage; + +public class StorageException extends RuntimeException { + + public StorageException(String message) { + super(message); + } + + public StorageException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/alterdekim/server/durak/storage/StorageFileNotFoundException.java b/src/main/java/com/alterdekim/server/durak/storage/StorageFileNotFoundException.java new file mode 100644 index 0000000..f43dfd9 --- /dev/null +++ b/src/main/java/com/alterdekim/server/durak/storage/StorageFileNotFoundException.java @@ -0,0 +1,12 @@ +package com.alterdekim.server.durak.storage; + +public class StorageFileNotFoundException extends StorageException { + + public StorageFileNotFoundException(String message) { + super(message); + } + + public StorageFileNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/src/main/java/com/alterdekim/server/durak/storage/StorageProperties.java b/src/main/java/com/alterdekim/server/durak/storage/StorageProperties.java new file mode 100644 index 0000000..b5279e2 --- /dev/null +++ b/src/main/java/com/alterdekim/server/durak/storage/StorageProperties.java @@ -0,0 +1,12 @@ +package com.alterdekim.server.durak.storage; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Setter +@Getter +@ConfigurationProperties("durak") +public class StorageProperties { + private String location = "durak"; +} \ No newline at end of file diff --git a/src/main/java/com/alterdekim/server/durak/storage/StorageService.java b/src/main/java/com/alterdekim/server/durak/storage/StorageService.java new file mode 100644 index 0000000..44536a0 --- /dev/null +++ b/src/main/java/com/alterdekim/server/durak/storage/StorageService.java @@ -0,0 +1,23 @@ +package com.alterdekim.server.durak.storage; + +import org.springframework.core.io.Resource; +import org.springframework.web.multipart.MultipartFile; + +import java.nio.file.Path; +import java.util.stream.Stream; + +public interface StorageService { + + void init(); + + void store(MultipartFile file); + + Stream loadAll(); + + Path load(String filename); + + Resource loadAsResource(String filename); + + void deleteAll(); + +} diff --git a/src/main/java/com/alterdekim/server/durak/utils/HashUtils.java b/src/main/java/com/alterdekim/server/durak/utils/HashUtils.java new file mode 100644 index 0000000..ae713ea --- /dev/null +++ b/src/main/java/com/alterdekim/server/durak/utils/HashUtils.java @@ -0,0 +1,50 @@ +package com.alterdekim.server.durak.utils; + + +import lombok.extern.slf4j.Slf4j; + +import java.nio.charset.StandardCharsets; + +@Slf4j +public class HashUtils { + + public static byte[] decodeHexString(String hexString) { + if (hexString.length() % 2 == 1) { + throw new IllegalArgumentException( + "Invalid hexadecimal String supplied."); + } + + byte[] bytes = new byte[hexString.length() / 2]; + for (int i = 0; i < hexString.length(); i += 2) { + bytes[i / 2] = hexToByte(hexString.substring(i, i + 2)); + } + return bytes; + } + + private static byte hexToByte(String hexString) { + int firstDigit = toDigit(hexString.charAt(0)); + int secondDigit = toDigit(hexString.charAt(1)); + return (byte) ((firstDigit << 4) + secondDigit); + } + + private static int toDigit(char hexChar) { + int digit = Character.digit(hexChar, 16); + if(digit == -1) { + throw new IllegalArgumentException( + "Invalid Hexadecimal Character: "+ hexChar); + } + return digit; + } + + private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); + public static String bytesToHex(byte[] bytes) { + byte[] hexChars = new byte[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars, StandardCharsets.UTF_8); + } +} + diff --git a/src/main/java/com/alterdekim/server/durak/utils/SerializerUtils.java b/src/main/java/com/alterdekim/server/durak/utils/SerializerUtils.java new file mode 100644 index 0000000..c41b1a6 --- /dev/null +++ b/src/main/java/com/alterdekim/server/durak/utils/SerializerUtils.java @@ -0,0 +1,38 @@ +package com.alterdekim.server.durak.utils; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +public class SerializerUtils { + + public static List primitiveToBytes(byte[] bytes) { + List result = new ArrayList<>(); + for( byte b : bytes ) { + result.add(b); + } + return result; + } + + public static byte[] bytesToPrimitive(List bytes) { + byte[] b = new byte[bytes.size()]; + for( int i = 0; i < bytes.size(); i++ ) { + b[i] = bytes.get(i); + } + return b; + } + + public static List getPrivateFields(Class theClass) { + List privateFields = new ArrayList(); + Field[] fields = theClass.getDeclaredFields(); + for(Field field : fields){ + if(Modifier.isPrivate(field.getModifiers())) { + field.setAccessible(true); + privateFields.add(field); + } + } + return privateFields; + } +} + diff --git a/src/main/resources/durak/public/serverimages/s_u0en.png b/src/main/resources/durak/public/serverimages/s_u0en.png new file mode 100644 index 0000000..4412d50 Binary files /dev/null and b/src/main/resources/durak/public/serverimages/s_u0en.png differ