Compare commits

...

13 commits
1.0 ... master

Author SHA1 Message Date
Superwhiskers
9290244d36 i might actually end up using this 2018-04-23 19:37:49 -05:00
superwhiskers
7d0a52729d prettify the config before writing 2018-04-22 21:35:50 +00:00
Superwhiskers
54a3e0a528 another attempt at fixing this 2018-04-22 00:28:11 -05:00
Superwhiskers
7bba87e8cf no dev until i can get the 3ds to accept the certificate and key 2018-04-21 22:59:33 -05:00
Superwhiskers
f018ad5aa8 begin implementing a system to create a romfs patch so that https actually works... 2018-04-21 16:29:40 -05:00
superwhiskers
c19cdfe57d fix a few issues with text display 2018-04-19 18:04:50 +00:00
superwhiskers
1a617784ac
Merge pull request #1 from ThatNerdyPikachu/patch-1
sure, i guess.
2018-04-15 01:22:54 -05:00
Pika
2155d355cb
Oh come on...
Even though I’m dead, doesn’t mean I won’t stop working on Pretendo and fixing what I see...
2018-04-14 21:01:50 -04:00
Superwhiskers
0b5315df6d better readme 2018-04-10 18:31:39 -05:00
superwhiskers
872eee960c fix the directory 2018-04-10 18:36:30 +00:00
Superwhiskers
edd1f33eec Merge branch 'master' of https://github.com/PretendoNetwork/maryo 2018-04-09 19:34:55 -05:00
Superwhiskers
193baebbe6 some optimizations 2018-04-09 19:34:34 -05:00
superwhiskers
2a7efd62de updated the readme 2018-03-27 14:02:14 -05:00
8 changed files with 365 additions and 100 deletions

2
.gitignore vendored
View file

@ -5,4 +5,4 @@ maryo.exe
config.json
.vscode
maryo/
maryo-data/

View file

@ -1,22 +1,28 @@
# maryo
## a proxy server used for accessing Pretendo, the open source Nintendo Network replacement
## a proxy server used for accessing pretendo, the open source nintendo network replacement
## *(also known as the terminal app your grandma can use)*
## about
maryo is a proxy program intended to be as easy as possible to use, the user interface comes first, then the features. if i were to focus more on the features, i could end up with a program that works, but i'd then have a bunch of people asking me how to use it.
## instructions
### using precompiled binaries:
go to the releases, and download the
correct binary for your system. just double-click
it and follow the instructions
### using precompiled binaries
go to the releases, and download the correct binary for your system. just double-click it and follow the instructions (*it's that easy!*)
### building from source ~~(WARNING: EXTREMELY HARDCORE)~~
#### prerequisites
- [golang](https://golang.org/)
- [goproxy](https://github.com/elazarl/goproxy)
- [ansicolor](https://github.com/shiena/ansicolor)
- [httpscerts](https://github.com/kabukky/httpscerts)
- [go-asyncserver](https://github.com/hectane/go-asyncserver)
#### building
1. clone this repository
2. it's easy, just `go build` in the source directory
3. you can then double click your own binaries instead of ours (i don't care)

4
fs.go
View file

@ -263,10 +263,10 @@ func readJSONFile(file string) map[string]interface{} {
}
// write to a json file
func writeJSONFile(file string, data map[string]int) {
func writeJSONFile(file string, data map[string]interface{}) {
// turn go map into valid JSON
fileData, err := json.Marshal(data)
fileData, err := json.MarshalIndent(data, "", " ")
// handle errors
if err != nil {

View file

@ -23,7 +23,7 @@ func main() {
consoleSequence(fmt.Sprintf("%s", code("reset")))
// parse some flags here
config := flag.String("config", "maryo/config.json", "value for config file path (default is maryo/config.json)")
config := flag.String("config", "maryo-data/config.json", "value for config file path (default is maryo/config.json)")
logging := flag.Bool("logging", false, "if set, the proxy will log all request data (only needed for debugging)")
doSetup := flag.Bool("setup", false, "if set, maryo will go through setup again")
generateCerts := flag.Bool("regencerts", false, "if set, maryo will generate self-signed certificates for private use")
@ -36,20 +36,20 @@ func main() {
if *generateCerts == true {
// that's it, really
doCertGen()
doCertGen(*config)
// clear the screen
clear()
// give the user a message
fmt.Printf("your certificate and key have been generated")
fmt.Printf("reload the program to use them.")
fmt.Printf("your certificate and key pair have been generated\n")
fmt.Printf("reload the program to use them.\n")
// close the program
os.Exit(0)
}
// if not forced to do setup
if *doSetup == false {
@ -60,10 +60,10 @@ func main() {
fileMap := make(map[string]string)
// config.json -- if nonexistent, it follows the user's instruction to create one, or use a builtin copy
// set it to nonexistent beforehand
fileMap["config"] = "ne"
// check if config exists
if doesFileExist(*config) != false {
@ -72,28 +72,33 @@ func main() {
// set fileMap to have the correct status for the file
fileMap["config"] = "iv"
// if it isn't
// if it is valid
} else {
// "ditto"
fileMap["config"] = "va"
}
}
// cert.pem and key.pem -- if nonexistent, just do setup
fileMap["cert"] = "ne"
// check the cert
if doesFileExist("maryo/cert.pem") != false {
if doesFileExist("maryo-data/cert.pem") != false {
// check the key
if doesFileExist("maryo/key.pem") != false {
// check the pubkey
if doesFileExist("maryo-data/public-key.pem") != false {
// check the privatekey
if doesFileExist("maryo-data/private-key.pem") != false {
// say it is valid if it is there
fileMap["cert"] = "va"
// say it is valid if it is there
fileMap["cert"] = "va"
}
}
@ -104,8 +109,8 @@ func main() {
// perform setup
setup(fileMap)
// if it's invalid
// if it's invalid
} else if fileMap["config"] == "iv" {
// i'm not just going to perform autosetup because they might have some stuff in there
@ -115,26 +120,26 @@ func main() {
fmt.Printf(" 2. delete the config and run this program\n")
fmt.Printf(" 3. fix the config\n")
os.Exit(1)
// if the certificates don't exist
} else if fileMap["cert"] == "ne" {
// i'm not going to force you to set it up again
fmt.Printf("you don't have any certs in the maryo folder\n")
fmt.Printf("you don't have any certs in the maryo-data folder\n")
fmt.Printf("you have three different options:\n")
fmt.Printf(" 1. run this program with the --regencerts flag\n")
fmt.Printf(" 2. run this program with the --setup flag")
fmt.Printf(" 2. run this program with the --setup flag\n")
fmt.Printf(" 3. provide your own certs\n")
os.Exit(1)
// otherwise, start the proxy
// otherwise, start the proxy
} else {
// start the proxy
startProxy(*config, *logging)
}
// run setup function
} else {
@ -149,5 +154,5 @@ func main() {
setup(fileMap)
}
}

View file

@ -5,7 +5,7 @@ maryo/proxy.go
the proxy that makes this program a reverse proxy
made by that magical 3ds person
written by Superwhiskers, licensed under gnu gplv3.
written by superwhiskers, licensed under gnu gplv3.
if you want a copy, go to http://www.gnu.org/licenses/
*/
@ -40,16 +40,16 @@ func startProxy(configName string, logging bool) {
decryptAll := config["config"].(map[string]interface{})["decryptOutgoing"].(string)
// check if log file exists
if doesFileExist("maryo/proxy.log") == false {
if doesFileExist("maryo-data/proxy.log") == false {
// make it then
createFile("maryo/proxy.log")
createFile("maryo-data/proxy.log")
}
// write current timestamp to log
t := time.Now().Format("20060102150405")
writeFile("maryo/proxy.log", fmt.Sprintf("-> started log [%s]\n", t))
writeFile("maryo-data/proxy.log", fmt.Sprintf("-> started log [%s]\n", t))
// get ip
ip := getIP()
@ -58,7 +58,7 @@ func startProxy(configName string, logging bool) {
fmt.Printf("-- proxy log --\n")
consoleSequence(fmt.Sprintf("-> local IP address is %s%s%s\n", code("green"), ip, code("reset")))
consoleSequence(fmt.Sprintf("-> hosting proxy on %s:9437%s\n", code("green"), code("reset")))
writeFile("maryo/proxy.log", fmt.Sprintf("-> got local ip as %s, hosting on port :9437", ip))
writeFile("maryo-data/proxy.log", fmt.Sprintf("-> got local ip as %s, hosting on port :9437", ip))
// load that proxy
proxy := goproxy.NewProxyHttpServer()
@ -85,7 +85,7 @@ func startProxy(configName string, logging bool) {
// log the request
consoleSequence(fmt.Sprintf("-> request to %s%s%s\n", code("green"), r.URL.Host, code("reset")))
writeFile("maryo/proxy.log", fmt.Sprintf("-> got request to %s\n", r.URL.Host))
writeFile("maryo-data/proxy.log", fmt.Sprintf("-> got request to %s\n", r.URL.Host))
// get prettified request
@ -125,9 +125,9 @@ func startProxy(configName string, logging bool) {
}
// always log to file
writeFile("maryo/proxy.log", fmt.Sprintf("-> request data to %s\n", r.URL.Host))
writeFile("maryo/proxy.log", fmt.Sprintf("%s", string(reqData[:])))
writeFile("maryo/proxy.log", fmt.Sprintf("\n\n"))
writeFile("maryo-data/proxy.log", fmt.Sprintf("-> request data to %s\n", r.URL.Host))
writeFile("maryo-data/proxy.log", fmt.Sprintf("%s", string(reqData[:])))
writeFile("maryo-data/proxy.log", fmt.Sprintf("\n\n"))
// attempt to proxy it to the servers listed in config
@ -153,7 +153,7 @@ func startProxy(configName string, logging bool) {
// log the redirect
consoleSequence(fmt.Sprintf("-> proxying %s%s%s to %s%s%s\n", code("green"), r.URL.Host, code("reset"), code("green"), redirTo, code("reset")))
writeFile("maryo/proxy.log", fmt.Sprintf("-> proxying %s to %s", r.URL.Host, redirTo))
writeFile("maryo-data/proxy.log", fmt.Sprintf("-> proxying %s to %s", r.URL.Host, redirTo))
// redirect it
r.URL.Host = redirTo

356
setup.go
View file

@ -19,71 +19,328 @@ import (
"strconv"
"strings"
"time"
// externals
"github.com/kabukky/httpscerts"
"math/big"
"crypto/x509"
"crypto/x509/pkix"
"io/ioutil"
"crypto/rsa"
"crypto/rand"
)
// cert generation function here so i don't need to rewrite it in maryo.go
func doCertGen() {
// (adapted from https://www.socketloop.com/tutorials/golang-create-x509-certificate-private-and-public-keys)
func doCertGen(config string) {
// clear the screen because why not?
clear()
// get the ip address for the cert
ip := getIP()
// show a neat info snippet
fmt.Printf("- generating certificate and key for %s\n", ip)
fmt.Printf("- generating certificate and key pair...\n")
// create necissary directories for this
// maryo folder
if doesDirExist("maryo") == false {
if doesDirExist("maryo-data") == false {
// make it
makeDirectory("maryo")
makeDirectory("maryo-data")
}
// clean the cert and key if they exist
// clean the cert and key pair if they exist
// cert.pem
if doesFileExist("maryo/cert.pem") == true {
if doesFileExist("maryo-data/cert.pem") {
// delete the cert
deleteFile("maryo/cert.pem")
deleteFile("maryo-data/cert.pem")
}
// key.pem
if doesFileExist("maryo/key.pem") == true {
// private-key.pem
if doesFileExist("maryo-data/private-key.pem") {
// delete the key
deleteFile("maryo/key.pem")
// delete the private key
deleteFile("maryo-data/private-key.pem")
}
// generate the needed cert and key
err := httpscerts.Generate("maryo/cert.pem", "maryo/key.pem", fmt.Sprintf("%s:9437", ip))
// public-key.pem
if doesFileExist("maryo-data/public-key.pem") {
// handle the error (if there is one)
// delete the pubkey
deleteFile("maryo-data/public-key.pem")
}
// populate certificate with data
template := &x509.Certificate {
IsCA: true,
BasicConstraintsValid: true,
SubjectKeyId: []byte{ 1, 2, 3 },
SerialNumber: big.NewInt(1234),
Subject: pkix.Name{
CommonName: "maryo-cert",
Organization: []string{"pretendo"},
Country: []string{"US"},
},
NotBefore: time.Now(),
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature|x509.KeyUsageCertSign,
}
// generate private key
privatekey, err := rsa.GenerateKey(rand.Reader, 2048)
// check for errors
if err != nil {
// display error
fmt.Printf("[err]: error while generating key pair...\n")
// panic
panic(err)
}
// get the public key
publickey := &privatekey.PublicKey
// create a self-signed certificate. template = parent
var parent = template
cert, err := x509.CreateCertificate(rand.Reader, template, parent, publickey, privatekey)
// check for errors
if err != nil {
// show error message
fmt.Printf("[err]: error while generating a certificate and key for %s\n", ip)
// show traceback
// display error
fmt.Printf("[err]: error while generating certificate...\n")
// panic
panic(err)
}
// save private key
pkey := x509.MarshalPKCS1PrivateKey(privatekey)
ioutil.WriteFile("maryo-data/private-key.pem", pkey, 0777)
fmt.Printf("private key saved...\n")
// save public key
pubkey, _ := x509.MarshalPKIXPublicKey(publickey)
ioutil.WriteFile("maryo-data/public-key.pem", pubkey, 0777)
fmt.Printf("public key saved...\n")
// save cert
ioutil.WriteFile("maryo-data/cert.pem", cert, 0777)
fmt.Printf("certificate saved...\n")
// then, say they were made
fmt.Printf("finished generating the cert and key pair...\n")
fmt.Printf("\npress enter to continue...\n")
_ = input("")
// then ask if they would like to enable https
// on the server
var enableHTTPS string
for true {
// clear the screen
clear()
// display the message
fmt.Printf("would you like to enable https on the server?\n")
fmt.Printf("-> (y|n)\n")
enableHTTPS = input(": ")
// make sure it is a valid option
if (enableHTTPS == "y") || (enableHTTPS == "n") {
// exit loop if it is
break
// if it isn't
} else {
// show a message showing valid options
fmt.Printf("-> please enter y or n\n")
// stop the event loop to give them time to read
time.Sleep(1500 * time.Millisecond)
}
}
// then say they were made
fmt.Printf(" finished\n")
fmt.Printf("\npress enter to continue...\n")
// clear for a sec
clear()
// check if the config even exists
if !doesFileExist(config) {
// if it doesn't exist
// send a message
fmt.Printf("[err]: there is no config at %s...\n", config)
fmt.Printf(" please generate one by passing the setup flag\n")
fmt.Printf(" when running maryo. or, maryo may detect that one has\n")
fmt.Printf(" has not been created. or, you can just pass the path\n")
fmt.Printf(" to one...\n")
// exit
os.Exit(1)
}
// if it does, check if it is valid
if !checkJSONValidity(config) {
// send a message
fmt.Printf("[err]: your config at %s is invalid...\n", config)
fmt.Printf(" please regenerate it and fix it, which the former\n")
fmt.Printf(" can be done by setting up maryo again...\n")
// exit
os.Exit(1)
}
// load it if it is
configData := readJSONFile(config)
// do as requested
if enableHTTPS == "y" {
// enable https in the config
configData["config"].(map[string]interface{})["https"] = true
} else if enableHTTPS == "n" {
// keep https disabled
configData["config"].(map[string]interface{})["https"] = false
}
// write the log back
writeJSONFile(config, configData)
// let the user know it's done, and exit on enter
fmt.Printf("finished modifying the config...\n")
fmt.Printf("press enter to continue...\n")
_ = input("")
}
func generateRomFSPatch(encryptionKeyPath string) {
// clear the screen
clear()
// check for the data directory, the certificates,
// and the keys
// alert about it
fmt.Printf("checking for files...\n")
// directory checking
if !doesDirExist("maryo-data") {
// warn the user
fmt.Printf("[err]: the maryo-data directory does not exist...\n")
fmt.Printf(" please generate it by going through setup again\n")
fmt.Printf(" or by passing the regencerts flag when running maryo.\n")
// exit
os.Exit(1)
}
// check the files exist
filesInDataDir := []bool {
doesFileExist("maryo-data/private-key.pem"),
doesFileExist("maryo-data/public-key.pem"),
doesFileExist("maryo-data/cert.pem"),
}
// check if any are false
for _, fileStatus := range filesInDataDir {
// check if that file exists
if fileStatus == false {
// if it doesn't, then exit
fmt.Printf("[err]: one of the required files in the maryo-data\n")
fmt.Printf(" directory is nonexistent. please regenerate it\n")
fmt.Printf(" by going through setup again or passing the regencerts\n")
fmt.Printf(" flag when running maryo again...\n")
// exit
os.Exit(1)
}
}
// check for the aes key required for the
// console to accept the cert and pubkey
if !doesFileExist("maryo-data/0x0D.key") {
// if it doesn't
fmt.Printf("[err]: slot key 0x0D not found in maryo-data...\n")
fmt.Printf(" please dm me for my magnet link, torrent it,\n")
fmt.Printf(" and place it in the maryo-data directory as 0x0D.key...\n")
// exit
os.Exit(1)
}
// now, we can begin generating the patch
// alert
fmt.Printf("generating patch...")
// create directory for the patch
// but check for it first
if doesDirExist("patch-out") {
// remove it if it already does
deleteFile("patch-out")
}
// then create it
makeDirectory("patch-out")
// and then proceed to make the
// require subdirectories
// title id folder
makeDirectory("patch-out/0004001b00010002")
// romfs folder
makeDirectory("patch-out/0004001b00010002/romfs")
// and now all we have to do
// is encrypt the cert and key with
// the 0x0D aes key
/*
aaannnd... since i can't figure out how to get past this point...
there won't be any dev here for a bit...
*/
}
// setup function goes here now
func setup(fileMap map[string]string) {
@ -114,7 +371,7 @@ func setup(fileMap map[string]string) {
fmt.Printf(" welcome to the maryo setup wizard. > intro \n")
fmt.Printf(" this program will walk you through config creation \n")
fmt.Printf(" setting up your very own Pretendo confirm prefs \n")
fmt.Printf(" proxy server for accessing the server. generate cert \n")
fmt.Printf(" proxy server for accessing the server. make https work \n")
fmt.Printf(" -> press enter profit??? \n")
fmt.Printf(" \n")
fmt.Printf(" \n")
@ -131,7 +388,7 @@ func setup(fileMap map[string]string) {
fmt.Printf(" how would you like to configure the intro \n")
fmt.Printf(" proxy? > config creation \n")
fmt.Printf(" 1. automatic confirm prefs \n")
fmt.Printf(" 2. custom generate cart \n")
fmt.Printf(" 2. custom make https work \n")
fmt.Printf(" 3. template profit??? \n")
fmt.Printf(" 4. skip this \n")
fmt.Printf(" \n")
@ -549,7 +806,7 @@ func setup(fileMap map[string]string) {
if method != "4" {
// prettify the JSON
pretty, err := json.MarshalIndent(config, "", " ")
stringifiedConfig, err := json.MarshalIndent(config, "", " ")
// error handling
if err != nil {
@ -563,7 +820,7 @@ func setup(fileMap map[string]string) {
}
// turn it into a string
prettifiedJSON := string(pretty[:])
stringifiedJSON := string(stringifiedConfig[:])
// confirm the preferences
var areSettingsOkay string
@ -580,10 +837,10 @@ func setup(fileMap map[string]string) {
fmt.Printf(" are you okay with the settings below? intro \n")
fmt.Printf(" config creation \n")
fmt.Printf(" > confirm prefs \n")
fmt.Printf(" generate cert \n")
fmt.Printf(" make https work \n")
fmt.Printf(" profit??? \n")
fmt.Printf(" \n")
fmt.Printf(prettifiedJSON)
fmt.Printf(stringifiedJSON)
fmt.Printf("\n \n")
fmt.Printf("-> (y|n) \n")
fmt.Printf("=============================================================\n")
@ -617,9 +874,6 @@ func setup(fileMap map[string]string) {
os.Exit(0)
}
// convert golang map to json
stringifiedConfig, err := json.Marshal(config)
// error handling
if err != nil {
@ -633,10 +887,10 @@ func setup(fileMap map[string]string) {
}
// make sure the maryo folder exists
if doesDirExist("maryo") == false {
if doesDirExist("maryo-data") == false {
// make it if it doesn't
makeDirectory("maryo")
makeDirectory("maryo-data")
}
@ -644,44 +898,44 @@ func setup(fileMap map[string]string) {
if fileMap["config"] == "iv" {
// delete the existing config
deleteFile("maryo/config.json")
deleteFile("maryo-data/config.json")
// create a new one
createFile("maryo/config.json")
createFile("maryo-data/config.json")
// write the data to the file
writeByteToFile("maryo/config.json", stringifiedConfig)
writeByteToFile("maryo-data/config.json", stringifiedConfig)
} else if fileMap["config"] == "ne" {
// create the config
createFile("maryo/config.json")
createFile("maryo-data/config.json")
// write the config to the file
writeByteToFile("maryo/config.json", stringifiedConfig)
writeByteToFile("maryo-data/config.json", stringifiedConfig)
} else if fileMap["config"] == "uk" {
// detect status of config and do the
// things to write to it.
if doesFileExist("maryo/config.json") == true {
if doesFileExist("maryo-data/config.json") == true {
// delete existing config
deleteFile("maryo/config.json")
deleteFile("maryo-data/config.json")
// create a new one
createFile("maryo/config.json")
createFile("maryo-data/config.json")
// write the config to it
writeByteToFile("maryo/config.json", stringifiedConfig)
writeByteToFile("maryo-data/config.json", stringifiedConfig)
} else {
// create the config
createFile("maryo/config.json")
createFile("maryo-data/config.json")
// write the config to the file
writeByteToFile("maryo/config.json", stringifiedConfig)
writeByteToFile("maryo-data/config.json", stringifiedConfig)
}
@ -697,7 +951,7 @@ func setup(fileMap map[string]string) {
fmt.Printf(" now, it is time to generate a https intro \n")
fmt.Printf(" cert to encrypt your data config creation \n")
fmt.Printf(" -> press enter confirm prefs \n")
fmt.Printf(" > generate cert \n")
fmt.Printf(" > make https work \n")
fmt.Printf(" profit??? \n")
fmt.Printf(" \n")
fmt.Printf(" \n")
@ -706,7 +960,7 @@ func setup(fileMap map[string]string) {
_ = input("")
// generate the certificates
doCertGen()
doCertGen("maryo-data/config.json")
// show them the finished screen
clear()
@ -717,7 +971,7 @@ func setup(fileMap map[string]string) {
fmt.Printf(" congratulations, you are finished intro \n")
fmt.Printf(" setting up maryo! config creation \n")
fmt.Printf(" -> press enter confirm prefs \n")
fmt.Printf(" generate cert \n")
fmt.Printf(" make https work \n")
fmt.Printf(" > profit??? \n")
fmt.Printf(" \n")
fmt.Printf(" \n")

View file

@ -1,10 +1,10 @@
/*
maryo/shell.go
juxtaposition/shell.go
a small collection of terminal utilities
shell interaction
written by Superwhiskers, licensed under gnu gplv3.
written by superwhiskers, licensed under gnu agpl.
if you want a copy, go to http://www.gnu.org/licenses/
*/
@ -181,7 +181,7 @@ func code(index string) string {
if runtime.GOOS == "darwin" { ansiTrick(); }
// fix prefix for windows
if isWindows() {
if runtime.GOOS == "windows" {
// prefix that the terminal
// color lib uses
@ -224,7 +224,7 @@ func consoleSequence(message string) {
// things are different for windows
// *shrug*
if isWindows() {
if runtime.GOOS == "windows" {
// make that writer
Writer := ansicolor.NewAnsiColorWriter(os.Stdout)

View file

@ -49,7 +49,7 @@ func printTitle() {
fmt.Printf(" __/ | \n")
fmt.Printf(" |___/ \n")
fmt.Fprintf(colorizer, "%s╔════════════════════════════════════════════════════════════════════════════════════════╗\n", "\x1b[91m")
fmt.Fprintf(colorizer, "%s‖ %sPretendo %sServer Satus %s‖\n", "\x1b[91m", "\x1b[35m", "\x1b[96m", "\x1b[91m")
fmt.Fprintf(colorizer, "%s‖ %sPretendo %sServer Status %s‖\n", "\x1b[91m", "\x1b[35m", "\x1b[96m", "\x1b[91m")
fmt.Fprintf(colorizer, "%s‖ ‖\n", "\x1b[91m")
fmt.Fprintf(colorizer, "%s‖ %saccount.pretendo.cc %s- %sOnline √ %s‖\n", "\x1b[91m", "\x1b[35m", "\x1b[37m", "\x1b[32m", "\x1b[91m")
fmt.Fprintf(colorizer, "%s‖ %sendpoint1.pretendo.cc %s- %sOffline × %s‖\n", "\x1b[91m", "\x1b[35m", "\x1b[37m", "\x1b[91m", "\x1b[91m")
@ -61,4 +61,4 @@ func printTitle() {
func main() {
printTitle()
}
}