all repos — nds-converter @ 80af50e6b6c541e74a50bd8a18304c15a91823fa

A lightweight save converter for NDS and DeSmuME.

add frontend
BiRabittoh andronacomarco@gmail.com
Thu, 28 Mar 2024 16:30:54 +0100
commit

80af50e6b6c541e74a50bd8a18304c15a91823fa

parent

5b2bb6a5c8061db447746fa0fe7d300d0dd6c019

6 files changed, 186 insertions(+), 85 deletions(-)

jump to
M .gitignore.gitignore

@@ -1,3 +1,4 @@

.env *.sav *.dsv +.vscode
D .vscode/launch.json

@@ -1,15 +0,0 @@

-{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Launch Package", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${fileDirname}" - } - ] -}
A backend.go

@@ -0,0 +1,81 @@

+package main + +import ( + "io" + "net/http" + "path/filepath" + "strconv" +) + +type ConverterFunction func([]byte) []byte + +const ( + MB = 1 << 20 + DefaultMaxFileSize = 2 + TrimBytes = 122 + AdditionalBytesHex = "7C3C2D2D536E69702061626F7665206865726520746F2063726561746520612072617720736176206279206578636C7564696E672074686973204465536D754D4520736176656461746120666F6F7465723A0000010000000100030000000200000000000100000000007C2D4445534D554D4520534156452D7C" +) + +var ( + MaxFileSize int64 + AdditionalBytes []byte + ConverterFunctions = map[string]ConverterFunction{ + ".dsv": DsvToSav, + ".sav": SavToDsv, + } +) + +func DsvToSav(input []byte) []byte { + return input[:len(input)-TrimBytes] +} + +func SavToDsv(input []byte) []byte { + return append(input, AdditionalBytes...) +} + +func postHandler(w http.ResponseWriter, r *http.Request) { + // Parse the multipart form data + err := r.ParseMultipartForm(MaxFileSize) + if err != nil { + http.Error(w, "Error parsing form", http.StatusBadRequest) + return + } + + r.Body = http.MaxBytesReader(w, r.Body, MaxFileSize) + + // Get the file from the request + file, h, err := r.FormFile("file") + if err != nil { + http.Error(w, "Error retrieving file from form data", http.StatusBadRequest) + return + } + defer file.Close() + + // Check file size + if h.Size > MaxFileSize { + http.Error(w, "File size exceeds 1MB", http.StatusBadRequest) + return + } + + var outputContent []byte + + // Check file extension + fileExt := filepath.Ext(h.Filename) + converterFunction, exists := ConverterFunctions[fileExt] + if !exists { + http.Error(w, "Invalid file format, only .dsv and .sav files are allowed", http.StatusBadRequest) + return + } + + content, err := io.ReadAll(file) + if err != nil { + http.Error(w, "Error reading file content", http.StatusInternalServerError) + return + } + outputContent = converterFunction(content) + + w.Header().Set("Content-Length", strconv.Itoa(len(outputContent))) + w.Header().Set("Content-Type", "application/octet-stream") + w.WriteHeader(http.StatusOK) + w.Write(outputContent) +}
A frontend.go

@@ -0,0 +1,27 @@

+package main + +import ( + "bytes" + "embed" + "net/http" + "text/template" +) + +const templatesDirectory = "templates/" + +var ( + //go:embed templates/index.html + templates embed.FS + indexTemplate = template.Must(template.ParseFS(templates, templatesDirectory+"index.html")) +) + +func getHandler(w http.ResponseWriter) { + buf := &bytes.Buffer{} + err := indexTemplate.Execute(buf, nil) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + buf.WriteTo(w) +}
M nds-converter.gonds-converter.go

@@ -2,32 +2,15 @@ package main

import ( "encoding/hex" - "io" "net/http" "os" - "path/filepath" "strconv" "github.com/joho/godotenv" ) -type ConverterFunction func([]byte) []byte - const ( - MB = 1 << 20 - DefaultMaxFileSize = 2 - DefaultPort = "1111" - TrimBytes = 122 - AdditionalBytesHex = "7C3C2D2D536E69702061626F7665206865726520746F2063726561746520612072617720736176206279206578636C7564696E672074686973204465536D754D4520736176656461746120666F6F7465723A0000010000000100030000000200000000000100000000007C2D4445534D554D4520534156452D7C" -) - -var ( - MaxFileSize int64 - AdditionalBytes []byte - ConverterFunctions = map[string]ConverterFunction{ - ".dsv": DsvToSav, - ".sav": SavToDsv, - } + DefaultPort = "1111" ) func main() {

@@ -51,62 +34,19 @@ MaxFileSize *= MB

AdditionalBytes, _ = hex.DecodeString(AdditionalBytesHex) - http.HandleFunc("/", uploadHandler) + http.HandleFunc("/", mainHandler) println("Listening on port " + port) http.ListenAndServe(":"+port, nil) } -func DsvToSav(input []byte) []byte { - return input[:len(input)-TrimBytes] -} - -func SavToDsv(input []byte) []byte { - return append(input, AdditionalBytes...) -} - -func uploadHandler(w http.ResponseWriter, r *http.Request) { - // Parse the multipart form data - err := r.ParseMultipartForm(MaxFileSize) - if err != nil { - http.Error(w, "Error parsing form", http.StatusBadRequest) - return - } - - r.Body = http.MaxBytesReader(w, r.Body, MaxFileSize) - - // Get the file from the request - file, h, err := r.FormFile("file") - if err != nil { - http.Error(w, "Error retrieving file from form data", http.StatusBadRequest) - return - } - defer file.Close() - - // Check file size - if h.Size > MaxFileSize { - http.Error(w, "File size exceeds 1MB", http.StatusBadRequest) - return - } - - var outputContent []byte - - // Check file extension - fileExt := filepath.Ext(h.Filename) - converterFunction, exists := ConverterFunctions[fileExt] - if !exists { - http.Error(w, "Invalid file format, only .dsv and .sav files are allowed", http.StatusBadRequest) - return - } - - content, err := io.ReadAll(file) - if err != nil { - http.Error(w, "Error reading file content", http.StatusInternalServerError) +func mainHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + getHandler(w) return + case http.MethodPost: + postHandler(w, r) + default: + http.Error(w, "Method not supported", http.StatusBadRequest) } - outputContent = converterFunction(content) - - w.Header().Set("Content-Length", strconv.Itoa(len(outputContent))) - w.Header().Set("Content-Type", "application/octet-stream") - w.WriteHeader(http.StatusOK) - w.Write(outputContent) }
A templates/index.html

@@ -0,0 +1,67 @@

+<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta http-equiv="X-UA-Compatible" content="ie=edge"> + <title>NDS Converter</title> + <style> + body, a { + background-color: #212529; + color: #dee2e6; + font-family: sans-serif; + } + hr { + width: 400px; + text-align: left; + margin-left: 0px; + margin-top: 20px; + } + </style> +</head> + +<body> + <h2>NDS Converter</h2> + <p>Please, select one or more <code>.sav</code> or <code>.dsv</code> files.</p> + <input type="file" accept=".dsv,.sav" multiple onchange="convertFiles(this)"> + <hr> + <p><a href="https://github.com/BiRabittoh/nds-converter">Source code</a></p> + + <script> + async function convertFiles(element) { + for (f of element.files) { + await convert(f); + } + } + + async function convert(file) { + const formData = new FormData(); + formData.append('file', file); + + const response = await fetch('/', { + method: 'POST', + body: formData, + }); + + if (!response.ok) { + alert(`Error ${response.status}: ${await response.text()}`); + return; + } + + const newExtension = file.name.endsWith('.dsv') ? '.sav' : '.dsv'; + + const blob = await response.blob(); + + const link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = file.name.replace(/\.[^.]+$/, '') + newExtension; + + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + </script> +</body> + +</html>