├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── app.go ├── logo.png ├── logo.svg ├── static ├── autolauncher_version ├── bfcl.exe ├── getmseds.py ├── impossible_part1.sed ├── js │ └── script.js ├── mseds │ └── empty ├── pause.exe ├── seedminer_autolauncher.py └── seedminer_autolauncher2.py └── views ├── 404error.jet ├── home.jet └── layout.jet /.gitignore: -------------------------------------------------------------------------------- 1 | seedhelper.figgyc.uk 2 | edge.figgyc.uk 3 | acme_account.key 4 | debug 5 | # build 6 | app 7 | static/mseds/msed_data_* 8 | static/mseds/list 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch file", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "debug", 12 | "program": "${file}" 13 | }, 14 | { 15 | "name": "Launch", 16 | "type": "go", 17 | "request": "launch", 18 | "mode": "debug", 19 | "remotePath": "", 20 | "port": 2345, 21 | "host": "127.0.0.1", 22 | "program": "${fileDirname}", 23 | "env": {}, 24 | "args": [], 25 | "showLog": true 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 figgyc 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # seedhelper2 2 | Rewrite of Seedhelper to fix issues. 3 | * Removes buggy msed database and sends msed to zoogie instead 4 | * Removes need for annoying login 5 | * **Bot that automatically finds part1 without humans (see http://github.com/figgyc/netseedhelper)** 6 | * Better UI with auto refresh using websockets, dark theme and integrated explanation 7 | * Allow user to cancel job if they enter details wrong 8 | * Actually cancel expired jobs on miner side to prevent time wasting and infinite loop 9 | 10 | Requires Go and MongoDB. -------------------------------------------------------------------------------- /app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha1" 6 | "crypto/sha256" 7 | "crypto/tls" 8 | "encoding/base64" 9 | "encoding/binary" 10 | "encoding/hex" 11 | "encoding/json" 12 | "fmt" 13 | "io/ioutil" 14 | "log" 15 | "net/http" 16 | "os" 17 | "regexp" 18 | "strconv" 19 | "strings" 20 | "time" 21 | 22 | "github.com/CloudyKit/jet" 23 | "github.com/Tomasen/realip" 24 | "github.com/gorilla/mux" 25 | "github.com/gorilla/websocket" 26 | "golang.org/x/crypto/acme/autocert" 27 | "gopkg.in/mgo.v2" 28 | "gopkg.in/mgo.v2/bson" 29 | ) 30 | 31 | var view *jet.Set 32 | var mgoSession mgo.Session 33 | var devices *mgo.Collection 34 | var minerCollection *mgo.Collection 35 | var lastBotInteraction time.Time 36 | var miners map[string]time.Time 37 | var iminers map[string]time.Time 38 | var ipPriority []string 39 | var botIP string 40 | var connections map[string]*websocket.Conn 41 | 42 | // Device : struct for devices 43 | type Device struct { 44 | FriendCode uint64 45 | ID0 string `bson:"_id"` 46 | HasMovable bool 47 | HasPart1 bool 48 | HasAdded bool 49 | WantsBF bool 50 | LFCS [8]byte 51 | MSed [0x140]byte 52 | MSData [12]byte 53 | ExpiryTime time.Time `bson:",omitempty"` 54 | CheckTime time.Time 55 | Miner string 56 | Expired bool 57 | Cancelled bool 58 | } 59 | 60 | // Miner : struct for tracking miners 61 | type Miner struct { 62 | IP string `bson:"_id"` 63 | Score int 64 | Banned bool 65 | } 66 | 67 | func contains(s []string, e string) bool { 68 | for _, a := range s { 69 | if a == e { 70 | return true 71 | } 72 | } 73 | return false 74 | } 75 | 76 | func buildMessage(command string) []byte { 77 | message := make(map[string]interface{}) 78 | message["status"] = command 79 | message["minerCount"] = len(miners) 80 | c, err := devices.Find(bson.M{"haspart1": true, "wantsbf": true, "expirytime": time.Time{}, "expired": bson.M{"$ne": true}}).Count() 81 | if err != nil { 82 | panic(err) 83 | } 84 | message["userCount"] = c 85 | b, err := devices.Find(bson.M{"hasmovable": bson.M{"$ne": true}, "haspart1": true, "wantsbf": true, "expirytime": bson.M{"$gt": time.Now()}, "expired": bson.M{"$ne": true}}).Count() 86 | if err != nil { 87 | panic(err) 88 | } 89 | message["miningCount"] = b 90 | a, err := devices.Find(bson.M{"haspart1": true}).Count() 91 | if err != nil { 92 | panic(err) 93 | } 94 | message["p1Count"] = a 95 | z, err := devices.Find(bson.M{"hasmovable": true}).Count() 96 | if err != nil { 97 | panic(err) 98 | } 99 | message["msCount"] = z 100 | n, err := devices.Count() 101 | if err != nil { 102 | panic(err) 103 | } 104 | message["totalCount"] = n 105 | data, err := json.Marshal(message) 106 | if err != nil { 107 | return []byte("{}") 108 | } 109 | return data 110 | } 111 | 112 | func renderTemplate(template string, vars jet.VarMap, request *http.Request, writer http.ResponseWriter, context interface{}) { 113 | writer.Header().Add("Link", "; rel=preload; as=script, ; rel=preconnect, ; rel=preconnect, ; rel=preconnect, ; rel=preconnect,") 114 | t, err := view.GetTemplate(template) 115 | if err != nil { 116 | panic(err) 117 | } 118 | vars.Set("isUp", (lastBotInteraction.After(time.Now().Add(time.Minute * -5)))) 119 | vars.Set("minerCount", len(miners)) 120 | c, err := devices.Find(bson.M{"haspart1": true, "wantsbf": true, "expirytime": time.Time{}, "expired": bson.M{"$ne": true}}).Count() 121 | if err != nil { 122 | panic(err) 123 | } 124 | vars.Set("userCount", c) 125 | b, err := devices.Find(bson.M{"hasmovable": bson.M{"$ne": true}, "haspart1": true, "wantsbf": true, "expirytime": bson.M{"$gt": time.Now()}, "expired": bson.M{"$ne": true}}).Count() 126 | if err != nil { 127 | panic(err) 128 | } 129 | vars.Set("miningCount", b) 130 | a, err := devices.Find(bson.M{"haspart1": true}).Count() 131 | if err != nil { 132 | panic(err) 133 | } 134 | vars.Set("p1Count", a) 135 | z, err := devices.Find(bson.M{"hasmovable": true}).Count() 136 | if err != nil { 137 | panic(err) 138 | } 139 | vars.Set("msCount", z) 140 | n, err := devices.Count() 141 | if err != nil { 142 | panic(err) 143 | } 144 | vars.Set("totalCount", n) 145 | var tminers []bson.M 146 | q := minerCollection.Find(bson.M{"score": bson.M{"$gt": 0}}).Sort("-score").Limit(5) 147 | err = q.All(&tminers) 148 | if err != nil { 149 | panic(err) 150 | } 151 | vars.Set("miners", tminers) 152 | //log.Println(miners, len(miners)) 153 | if err = t.Execute(writer, vars, nil); err != nil { 154 | // error when executing template 155 | panic(err) 156 | } 157 | if err != nil { 158 | panic(err) 159 | } 160 | } 161 | 162 | func logger(next http.Handler) http.Handler { 163 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 164 | // Do stuff here 165 | log.Println(r.URL, r.Method, realip.FromRequest(r)) 166 | // Call the next handler, which can be another middleware in the chain, or the final handler. 167 | next.ServeHTTP(w, r) 168 | }) 169 | } 170 | 171 | func blacklist(next http.Handler) http.Handler { 172 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 173 | c, _ := minerCollection.Find(bson.M{"_id": realip.FromRequest(r), "banned": true}).Count() 174 | if c < 1 { 175 | next.ServeHTTP(w, r) 176 | } else { 177 | w.WriteHeader(403) 178 | w.Header().Add("X-Seedhelper-Banned", "true") 179 | w.Write([]byte("You have been banned from Seedhelper. This is probably because your script is glitching out. If you think you should be unbanned then find figgyc on Discord.")) 180 | } 181 | }) 182 | } 183 | 184 | func closer(next http.Handler) http.Handler { 185 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 186 | if r.URL.Path != "/socket" { 187 | r.Close = true 188 | } 189 | next.ServeHTTP(w, r) 190 | }) 191 | } 192 | 193 | func filetypeFixer(next http.Handler) http.Handler { 194 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 195 | // Do stuff here 196 | //log.Println(r) 197 | var tFile = regexp.MustCompile("\\.py$") 198 | if tFile.MatchString(r.RequestURI) { 199 | w.Header().Set("Content-Type", "application/octet-stream") 200 | w.Header().Set("Content-Disposition", "inline") 201 | } 202 | // Call the next handler, which can be another middleware in the chain, or the final handler. 203 | next.ServeHTTP(w, r) 204 | }) 205 | } 206 | 207 | func reverse(numbers []byte) { 208 | for i, j := 0, len(numbers)-1; i < j; i, j = i+1, j-1 { 209 | numbers[i], numbers[j] = numbers[j], numbers[i] 210 | } 211 | } 212 | 213 | func checkIfID1(id1s string) bool { 214 | /* 215 | 1) Take your id1 216 | 24A90106478089A4534C303800035344 217 | 218 | 2) Split it into u16 chunks 219 | 24A9 0106 4780 89A4 534C 3038 0003 5344 220 | 221 | 3) Reverse them backwards 222 | 5344 0003 3038 534C 89A4 4780 0106 24A9 223 | 224 | 4) Endian flip each of those chunks 225 | 4453 0300 3830 4C53 A489 8047 0601 A924 226 | 227 | 5) Shuffle them around with the table on 3dbrew (backwards!) 228 | 0601 A924 A489 8047 3830 4C53 4453 0300 229 | 230 | 6) Join them together 231 | 0601A924A489804738304C5344530300 <-- cid 232 | */ 233 | //id1s := "24A90106478089A4534C303800035344" 234 | id1, err := hex.DecodeString(id1s) 235 | if err != nil { 236 | return true 237 | } 238 | var chunks [8][2]byte 239 | for i := 0; i < 8; i++ { 240 | chunks[i][0] = id1[i*2] 241 | chunks[i][1] = id1[(i*2)+1] 242 | } 243 | var rchunks [8][2]byte 244 | for i := 0; i < 8; i++ { 245 | rchunks[7-i] = chunks[i] 246 | } 247 | var echunks [8][2]byte 248 | for i := 0; i < 8; i++ { 249 | echunks[i][0] = rchunks[i][1] 250 | echunks[i][1] = rchunks[i][0] 251 | } 252 | var schunks [8][2]byte 253 | /* 3dbrew: 254 | Input CID u16 index Output CID u16 index 255 | 6 0 256 | 7 1 257 | 4 2 258 | 5 3 259 | 2 4 260 | 3 5 261 | 0 6 262 | 1 7 263 | */ 264 | schunks[0] = echunks[6] 265 | schunks[1] = echunks[7] 266 | schunks[2] = echunks[4] 267 | schunks[3] = echunks[5] 268 | schunks[4] = echunks[2] 269 | schunks[5] = echunks[3] 270 | schunks[6] = echunks[0] 271 | schunks[7] = echunks[1] 272 | var cid [16]byte 273 | for i := 0; i < 8; i++ { 274 | cid[i*2] = schunks[i][0] 275 | cid[(i*2)+1] = schunks[i][1] 276 | } 277 | //hash := crc7.ComputeHash(cid[:]) 278 | 279 | // pnm+oid should be valid ascii (<0x7F) but don't seem to be on most cards 280 | /* 281 | pnmoid := cid[1:7] 282 | for i := 0; i < 7; i++ { 283 | if pnmoid[i] > 0x7F { 284 | return false 285 | } 286 | }*/ 287 | 288 | // zoogie said that he thinks this is reliable, idk but whatever 289 | return cid[15] == byte(0x00) && (cid[1] == byte(0x00) || cid[1] == byte(0x01)) 290 | } 291 | 292 | func main() { 293 | log.SetFlags(log.Lshortfile) 294 | lastBotInteraction = time.Now() 295 | miners = map[string]time.Time{} 296 | iminers = map[string]time.Time{} 297 | ipPriority = strings.Split(os.Getenv("SEEDHELPER_IP_PRIORITY"), ",") 298 | botIP = os.Getenv("SEEDHELPER_BOT_IP") 299 | log.Println(ipPriority) 300 | // initialize mongo 301 | mgoSession, err := mgo.Dial("localhost") 302 | if err != nil { 303 | panic(err) 304 | } 305 | defer mgoSession.Close() 306 | 307 | devices = mgoSession.DB("main").C("devices") 308 | minerCollection = mgoSession.DB("main").C("miners") 309 | 310 | // init templates 311 | view = jet.NewHTMLSet("./views") 312 | // view.SetDevelopmentMode(true) 313 | 314 | // routing 315 | router := mux.NewRouter() 316 | 317 | router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) 318 | 319 | router.Use(logger) 320 | router.Use(filetypeFixer) 321 | router.Use(blacklist) 322 | 323 | router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 324 | renderTemplate("home", make(jet.VarMap), r, w, nil) 325 | }) 326 | 327 | router.HandleFunc("/logo.png", func(w http.ResponseWriter, r *http.Request) { 328 | http.ServeFile(w, r, "logo.png") 329 | }) 330 | 331 | // client: 332 | connections = make(map[string]*websocket.Conn) 333 | upgrader := websocket.Upgrader{ 334 | ReadBufferSize: 1024, 335 | WriteBufferSize: 1024, 336 | } 337 | 338 | router.HandleFunc("/socket", func(w http.ResponseWriter, r *http.Request) { 339 | conn, err := upgrader.Upgrade(w, r, nil) 340 | if err != nil { 341 | log.Println(err) 342 | return 343 | } 344 | //... Use conn to send and receive messages. 345 | for { 346 | messageType, p, err := conn.ReadMessage() 347 | if err != nil { 348 | log.Println("disconnection?", err) 349 | return 350 | } 351 | if messageType == websocket.TextMessage { 352 | var object map[string]interface{} 353 | err := json.Unmarshal(p, &object) 354 | if err != nil { 355 | log.Println(err) 356 | //return 357 | for k, v := range connections { 358 | if v == conn { 359 | delete(connections, k) 360 | } 361 | } 362 | return 363 | } 364 | if object["id0"] == nil { 365 | //return 366 | continue 367 | } 368 | log.Println("identify:", realip.FromRequest(r), object["id0"]) 369 | //log.Println(object["part1"], "packet") 370 | /*isRegistered := false 371 | for _, v := range connections { 372 | if v == conn { 373 | isRegistered = true 374 | } 375 | } 376 | if isRegistered == false {*/ 377 | connections[object["id0"].(string)] = conn 378 | //} 379 | 380 | if object["request"] == "bruteforce" { 381 | // add to BF pool 382 | err := devices.Update(bson.M{"_id": object["id0"].(string)}, bson.M{"$set": bson.M{"wantsbf": true, "expirytime": time.Time{}}}) 383 | if err != nil { 384 | log.Println(err) 385 | //return 386 | } 387 | } else if object["request"] == "cancel" { 388 | // canseru jobbu 389 | err := devices.Update(bson.M{"_id": object["id0"].(string)}, bson.M{"$set": bson.M{"cancelled": true}}) 390 | if err != nil { 391 | w.Write([]byte("error")) 392 | return 393 | } 394 | } else if object["part1"] != nil { 395 | // add to work pool 396 | 397 | c, err := devices.Find(bson.M{"_id": object["id0"], "expired": true}).Count() 398 | if err != nil || c > 0 { 399 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("flag")); err != nil { 400 | log.Println(err) 401 | return 402 | } 403 | continue 404 | } 405 | valid := true 406 | if regexp.MustCompile("[0-9a-fA-F]{32}").MatchString(object["id0"].(string)) == false { 407 | valid = false 408 | } 409 | 410 | if valid == false { 411 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("friendCodeInvalid")); err != nil { 412 | log.Println(err) 413 | return 414 | } 415 | continue 416 | } 417 | p1Slice, err := base64.StdEncoding.DecodeString(object["part1"].(string)) 418 | if err != nil { 419 | log.Println(err) 420 | return 421 | } 422 | lfcsSlice := p1Slice[:8] 423 | reverse(lfcsSlice) 424 | var lfcsArray [8]byte 425 | copy(lfcsArray[:], lfcsSlice[:]) 426 | var checkArray [8]byte 427 | if lfcsArray == checkArray { 428 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("friendCodeInvalid")); err != nil { 429 | log.Println(err) 430 | return 431 | } 432 | continue 433 | } 434 | if object["defoID0"] != "yes" && checkIfID1(object["id0"].(string)) { 435 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("couldBeID1")); err != nil { 436 | log.Println(err) 437 | return 438 | } 439 | continue 440 | } 441 | device := bson.M{"lfcs": lfcsArray, "haspart1": true, "hasadded": true, "wantsbf": true, "expirytime": time.Time{}} 442 | _, err = devices.Upsert(bson.M{"_id": object["id0"].(string)}, device) 443 | if err != nil { 444 | log.Println(err) 445 | //return 446 | } 447 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("queue")); err != nil { 448 | log.Println(err) 449 | //return 450 | } 451 | } else if object["friendCode"] != nil { 452 | // add to bot pool 453 | 454 | c, err := devices.Find(bson.M{"_id": object["id0"], "expired": true}).Count() 455 | if err != nil || c > 0 { 456 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("flag")); err != nil { 457 | log.Println(err) 458 | return 459 | } 460 | continue 461 | } 462 | /* 463 | based on https://github.com/ihaveamac/Kurisu/blob/master/addons/friendcode.py#L24 464 | def verify_fc(self, fc): 465 | fc = int(fc.replace('-', '')) 466 | if fc > 0x7FFFFFFFFF: 467 | return None 468 | principal_id = fc & 0xFFFFFFFF 469 | checksum = (fc & 0xFF00000000) >> 32 470 | return (fc if hashlib.sha1(struct.pack('> 1 == checksum else None) 471 | */ 472 | valid := true 473 | fc, err := strconv.Atoi(object["friendCode"].(string)) 474 | if err != nil { 475 | valid = false 476 | } 477 | if fc > 0x7FFFFFFFFF { 478 | valid = false 479 | } 480 | if fc == 27599290078 { // the bot 481 | valid = false 482 | } 483 | principalID := fc & 0xFFFFFFFF 484 | checksum := (fc & 0xFF00000000) >> 32 485 | 486 | pidb := make([]byte, 4) 487 | binary.LittleEndian.PutUint32(pidb, uint32(principalID)) 488 | if int(sha1.Sum(pidb)[0])>>1 != checksum { 489 | valid = false 490 | } 491 | 492 | if regexp.MustCompile("[0-9a-fA-F]{32}").MatchString(object["id0"].(string)) == false { 493 | valid = false 494 | } 495 | 496 | if valid == false { 497 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("friendCodeInvalid")); err != nil { 498 | log.Println(err) 499 | return 500 | } 501 | continue 502 | } 503 | if object["defoID0"] != "yes" && checkIfID1(object["id0"].(string)) == true { 504 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("couldBeID1")); err != nil { 505 | log.Println(err) 506 | return 507 | } 508 | continue 509 | } 510 | log.Println(fc) 511 | device := bson.M{"friendcode": uint64(fc), "hasadded": false, "haspart1": false} 512 | _, err = devices.Upsert(bson.M{"_id": object["id0"].(string)}, device) 513 | if err != nil { 514 | log.Println(err) 515 | //return 516 | } 517 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("friendCodeProcessing")); err != nil { 518 | log.Println(err) 519 | //return 520 | } 521 | 522 | } else { 523 | // checc 524 | //log.Println("check") 525 | query := devices.Find(bson.M{"_id": object["id0"].(string)}) 526 | count, err := query.Count() 527 | if err != nil { 528 | log.Println(err) 529 | //return 530 | } 531 | if count > 0 { 532 | var device Device 533 | err = query.One(&device) 534 | if err != nil { 535 | log.Println(err) 536 | //return 537 | } 538 | if device.HasMovable == true { 539 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("done")); err != nil { 540 | log.Println(err) 541 | //return 542 | } 543 | } else if (device.ExpiryTime != time.Time{}) { 544 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("bruteforcing")); err != nil { 545 | log.Println(err) 546 | //return 547 | } 548 | } else if device.WantsBF == true { 549 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("queue")); err != nil { 550 | log.Println(err) 551 | //return 552 | } 553 | } else if device.HasPart1 == true { 554 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("movablePart1")); err != nil { 555 | log.Println(err) 556 | //return 557 | } 558 | } else if device.HasAdded == true { 559 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("friendCodeAdded")); err != nil { 560 | log.Println(err) 561 | //return 562 | } 563 | } else { 564 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("friendCodeProcessing")); err != nil { 565 | log.Println(err) 566 | //return 567 | } 568 | } 569 | } else { 570 | log.Println("empty id0 to socket, dropped DB?") 571 | //return 572 | } 573 | } 574 | } else if messageType == websocket.CloseMessage { 575 | for k, v := range connections { 576 | if v == conn { 577 | delete(connections, k) 578 | } 579 | } 580 | } 581 | 582 | } 583 | }) 584 | 585 | // part1 auto script: 586 | // /getfcs 587 | router.HandleFunc("/getfcs", func(w http.ResponseWriter, r *http.Request) { 588 | if realip.FromRequest(r) != botIP { 589 | w.Write([]byte("nothing")) 590 | return 591 | } 592 | lastBotInteraction = time.Now() 593 | 594 | query := devices.Find(bson.M{"hasadded": false}) 595 | count, err := query.Count() 596 | if err != nil || count < 1 { 597 | w.Write([]byte("nothing")) 598 | return 599 | } 600 | var aDevices []Device 601 | err = query.All(&aDevices) 602 | if err != nil || len(aDevices) < 1 { 603 | w.Write([]byte("nothing")) 604 | return 605 | } 606 | for _, device := range aDevices { 607 | w.Write([]byte(strconv.FormatUint(device.FriendCode, 10))) 608 | w.Write([]byte("\n")) 609 | } 610 | return 611 | }) 612 | // /added/fc 613 | router.HandleFunc("/added/{fc}", func(w http.ResponseWriter, r *http.Request) { 614 | if realip.FromRequest(r) != botIP { 615 | w.Write([]byte("fail")) 616 | return 617 | } 618 | b := mux.Vars(r)["fc"] 619 | a, err := strconv.Atoi(b) 620 | if err != nil { 621 | w.Write([]byte("fail")) 622 | log.Println(err) 623 | return 624 | } 625 | fc := uint64(a) 626 | 627 | //log.Println(r, &r) 628 | 629 | err = devices.Update(bson.M{"friendcode": fc, "hasadded": false}, bson.M{"$set": bson.M{"hasadded": true}}) 630 | if err != nil { // && err != mgo.ErrNotFound { 631 | w.Write([]byte("fail")) 632 | log.Println("a", err) 633 | return 634 | } 635 | 636 | query := devices.Find(bson.M{"friendcode": fc}) 637 | var device Device 638 | err = query.One(&device) 639 | if err != nil { 640 | w.Write([]byte("fail")) 641 | log.Println("x", err) 642 | return 643 | } 644 | for id0, conn := range connections { 645 | //log.Println(id0, device.ID0, "hello!") 646 | if id0 == device.ID0 { 647 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("friendCodeAdded")); err != nil { 648 | delete(connections, id0) 649 | //w.Write([]byte("fail")) 650 | log.Println(err) 651 | //return 652 | } 653 | } 654 | } 655 | w.Write([]byte("success")) 656 | 657 | }) 658 | 659 | // /lfcs/fc 660 | // get param lfcs is lfcs as hex eg 34cd12ab or whatevs 661 | router.HandleFunc("/lfcs/{fc}", func(w http.ResponseWriter, r *http.Request) { 662 | if realip.FromRequest(r) != botIP { 663 | w.Write([]byte("fail")) 664 | return 665 | } 666 | b := mux.Vars(r)["fc"] 667 | a, err := strconv.Atoi(b) 668 | if err != nil { 669 | w.Write([]byte("fail")) 670 | log.Println(err) 671 | return 672 | } 673 | fc := uint64(a) 674 | 675 | lfcs, ok := r.URL.Query()["lfcs"] 676 | if ok == false { 677 | log.Println("wot") 678 | w.Write([]byte("fail")) 679 | return 680 | } 681 | 682 | sliceLFCS, err := hex.DecodeString(lfcs[0]) 683 | if err != nil { 684 | w.Write([]byte("fail")) 685 | log.Println(err) 686 | return 687 | } 688 | var x [8]byte 689 | copy(x[:], sliceLFCS) 690 | x[0] = 0x00 691 | x[1] = 0x00 692 | x[2] = 0x00 693 | log.Println(fc, a, b, lfcs, x, sliceLFCS) 694 | err = devices.Update(bson.M{"friendcode": fc, "haspart1": false}, bson.M{"$set": bson.M{"haspart1": true, "lfcs": x}}) 695 | if err != nil && err != mgo.ErrNotFound { 696 | w.Write([]byte("fail")) 697 | log.Println(err) 698 | return 699 | } 700 | 701 | query := devices.Find(bson.M{"friendcode": fc}) 702 | var device Device 703 | err = query.One(&device) 704 | if err != nil { 705 | log.Println(err) 706 | w.Write([]byte("fail")) 707 | log.Println("las") 708 | return 709 | } 710 | for id0, conn := range connections { 711 | if id0 == device.ID0 { 712 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("movablePart1")); err != nil { 713 | log.Println(err) 714 | delete(connections, id0) 715 | //w.Write([]byte("fail")) 716 | //return 717 | } 718 | } 719 | } 720 | 721 | w.Write([]byte("success")) 722 | log.Println("last") 723 | 724 | }) 725 | 726 | // msed auto script: 727 | // /cancel/id0 728 | router.HandleFunc("/cancel/{id0}", func(w http.ResponseWriter, r *http.Request) { 729 | id0 := mux.Vars(r)["id0"] 730 | log.Println(id0) 731 | kill := false 732 | 733 | yn, ok := r.URL.Query()["kill"] 734 | if ok == true { 735 | if yn[0] == "n" { 736 | kill = true 737 | } 738 | } 739 | 740 | err := devices.Update(bson.M{"_id": id0}, bson.M{"$set": bson.M{"wantsbf": kill, "expired": (yn[0] == "y"), "expirytime": time.Time{}}}) 741 | if err != nil { 742 | w.Write([]byte("error")) 743 | return 744 | } 745 | w.Write([]byte("success")) 746 | 747 | for id01, conn := range connections { 748 | if id0 == id01 { 749 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("flag")); err != nil { 750 | log.Println(err) 751 | delete(connections, id0) 752 | return 753 | } 754 | } 755 | } 756 | 757 | }) 758 | 759 | // /setname 760 | router.HandleFunc("/setname", func(w http.ResponseWriter, r *http.Request) { 761 | names, ok := r.URL.Query()["name"] 762 | name := names[0] 763 | if ok == false || name == "" { 764 | w.Write([]byte("specify a name")) 765 | return 766 | } 767 | c, err := minerCollection.Find(bson.M{"_id": bson.M{"$ne": realip.FromRequest(r)}, "name": name}).Count() 768 | if err != nil || c != 0 { 769 | w.Write([]byte("name taken")) 770 | return 771 | } 772 | _, err = minerCollection.Upsert(bson.M{"_id": realip.FromRequest(r)}, bson.M{"$set": bson.M{"name": name}}) 773 | if err != nil { 774 | w.Write([]byte("error")) 775 | w.Write([]byte(err.Error())) 776 | log.Println(err) 777 | } else { 778 | w.Write([]byte("success")) 779 | } 780 | }) 781 | 782 | // /getwork 783 | router.HandleFunc("/getwork", func(w http.ResponseWriter, r *http.Request) { 784 | miners[realip.FromRequest(r)] = time.Now() 785 | iminers[realip.FromRequest(r)] = time.Now() 786 | ok, err := devices.Find(bson.M{"miner": realip.FromRequest(r), "hasmovable": bson.M{"$ne": true}, "expirytime": bson.M{"$ne": time.Time{}}, "expired": bson.M{"$ne": true}}).Count() 787 | if ok > 0 { 788 | w.Write([]byte("nothing")) 789 | return 790 | } 791 | query := devices.Find(bson.M{"haspart1": true, "wantsbf": true, "expirytime": bson.M{"$eq": time.Time{}}, "expired": bson.M{"$ne": true}}) 792 | count, err := query.Count() 793 | if err != nil || count < 1 { 794 | w.Write([]byte("nothing")) 795 | return 796 | } 797 | var aDevice Device 798 | err = query.One(&aDevice) 799 | if err != nil { 800 | w.Write([]byte("nothing")) 801 | log.Println(err) 802 | return 803 | } 804 | w.Write([]byte(aDevice.ID0)) 805 | }) 806 | // /claim/id0 807 | router.HandleFunc("/claim/{id0}", func(w http.ResponseWriter, r *http.Request) { 808 | ok, err := devices.Find(bson.M{"miner": realip.FromRequest(r), "hasmovable": bson.M{"$ne": true}, "expirytime": bson.M{"$ne": time.Time{}}}).Count() 809 | if ok > 0 { 810 | w.Write([]byte("nothing")) 811 | return 812 | } 813 | id0 := mux.Vars(r)["id0"] 814 | //log.Println(id0) 815 | err = devices.Update(bson.M{"_id": id0}, bson.M{"$set": bson.M{"expirytime": time.Now().Add(time.Hour), "miner": realip.FromRequest(r)}}) 816 | if err != nil { 817 | log.Println(err) 818 | return 819 | } 820 | w.Write([]byte("success")) 821 | miners[realip.FromRequest(r)] = time.Now() 822 | for id02, conn := range connections { 823 | //log.Println(id0, device.ID0, "hello!") 824 | if id02 == id0 { 825 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("bruteforcing")); err != nil { 826 | delete(connections, id0) 827 | //w.Write([]byte("fail")) 828 | log.Println(err) 829 | //return 830 | } 831 | } 832 | } 833 | }) 834 | // /part1/id0 835 | // this is also used by client if they want self BF so /claim is needed 836 | router.HandleFunc("/part1/{id0}", func(w http.ResponseWriter, r *http.Request) { 837 | id0 := mux.Vars(r)["id0"] 838 | query := devices.Find(bson.M{"_id": id0}) 839 | count, err := query.Count() 840 | if err != nil || count < 1 { 841 | w.Write([]byte("error")) 842 | log.Println("z", err, count) 843 | return 844 | } 845 | var device Device 846 | err = query.One(&device) 847 | if err != nil || device.HasPart1 == false { 848 | w.Write([]byte("error")) 849 | log.Println("a", err) 850 | return 851 | } 852 | buf := bytes.NewBuffer(make([]byte, 0, 0x1000)) 853 | leLFCS := make([]byte, 8) 854 | binary.BigEndian.PutUint64(leLFCS, binary.LittleEndian.Uint64(device.LFCS[:])) 855 | _, err = buf.Write(leLFCS) 856 | if err != nil { 857 | w.Write([]byte("error")) 858 | log.Println("b", err) 859 | return 860 | } 861 | _, err = buf.Write(make([]byte, 0x8)) 862 | if err != nil { 863 | w.Write([]byte("error")) 864 | log.Println("c", err) 865 | return 866 | } 867 | _, err = buf.Write([]byte(device.ID0)) 868 | if err != nil { 869 | w.Write([]byte("error")) 870 | log.Println("d", err) 871 | return 872 | } 873 | _, err = buf.Write(make([]byte, 0xFD0)) 874 | if err != nil { 875 | w.Write([]byte("error")) 876 | log.Println("e", err) 877 | return 878 | } 879 | w.Header().Set("Content-Type", "application/octet-stream") 880 | w.Header().Set("Content-Disposition", "inline; filename=\"movable_part1.sed\"") 881 | w.Write(buf.Bytes()) 882 | }) 883 | // /check/id0 884 | // allows user cancel and not overshooting the 1hr job max time 885 | router.HandleFunc("/check/{id0}", func(w http.ResponseWriter, r *http.Request) { 886 | id0 := mux.Vars(r)["id0"] 887 | query := devices.Find(bson.M{"_id": id0, "haspart1": true, "hasmovable": bson.M{"$ne": true}, "wantsbf": true, "miner": realip.FromRequest(r), "expirytime": bson.M{"$gt": time.Now()}}) 888 | count, err := query.Count() 889 | if err != nil || count < 1 { 890 | w.Write([]byte("error")) 891 | log.Println("z", err, count) 892 | return 893 | } 894 | devices.Update(bson.M{"_id": id0}, bson.M{"$set": bson.M{"checktime": time.Now().Add(time.Minute)}}) 895 | miners[realip.FromRequest(r)] = time.Now() 896 | w.Write([]byte("ok")) 897 | }) 898 | // /movable/id0 899 | router.HandleFunc("/movable/{id0}", func(w http.ResponseWriter, r *http.Request) { 900 | id0 := mux.Vars(r)["id0"] 901 | query := devices.Find(bson.M{"_id": id0}) 902 | count, err := query.Count() 903 | if err != nil || count < 1 { 904 | log.Println(err) 905 | return 906 | } 907 | var device Device 908 | err = query.One(&device) 909 | if err != nil || device.HasMovable == false { 910 | w.Write([]byte("error")) 911 | return 912 | } 913 | buf := bytes.NewBuffer(make([]byte, 0, 0x140)) 914 | _, err = buf.Write(device.MSed[:]) 915 | if err != nil { 916 | log.Println(err) 917 | return 918 | } 919 | w.Header().Set("Content-Type", "application/octet-stream") 920 | w.Header().Set("Content-Disposition", "inline; filename=\"movable.sed\"") 921 | w.Write(buf.Bytes()) 922 | }) 923 | // POST /upload/id0 w/ file movable and msed 924 | router.HandleFunc("/upload/{id0}", func(w http.ResponseWriter, r *http.Request) { 925 | id0 := mux.Vars(r)["id0"] 926 | file, header, err := r.FormFile("movable") 927 | if err != nil { 928 | log.Println(err) 929 | return 930 | } 931 | if header.Size != 0x120 && header.Size != 0x140 { 932 | w.WriteHeader(400) 933 | w.Write([]byte("error")) 934 | log.Println(header.Size) 935 | return 936 | } 937 | var movable [0x120]byte 938 | _, err = file.Read(movable[:]) 939 | if err != nil { 940 | log.Println(err) 941 | return 942 | } 943 | 944 | // verify 945 | keyy := movable[0x110:0x11F] 946 | sha := sha256.Sum256(keyy) 947 | testid0 := fmt.Sprintf("%08x%08x%08x%08x", sha[0:4], sha[4:8], sha[8:12], sha[12:16]) 948 | log.Println("id0check:", hex.EncodeToString(keyy), hex.EncodeToString(sha[:]), testid0, id0) 949 | 950 | err = devices.Update(bson.M{"_id": id0}, bson.M{"$set": bson.M{"msed": movable, "hasmovable": true, "expirytime": time.Time{}, "wantsbf": false}}) 951 | if err != nil { 952 | log.Println(err) 953 | return 954 | } 955 | 956 | minerCollection.Upsert(bson.M{"_id": realip.FromRequest(r)}, bson.M{"$inc": bson.M{"score": 5}}) 957 | 958 | for key, conn := range connections { 959 | if key == id0 { 960 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("done")); err != nil { 961 | log.Println(err) 962 | delete(connections, id0) 963 | //w.Write([]byte("fail")) 964 | //return 965 | } 966 | } 967 | } 968 | 969 | w.Write([]byte("success")) 970 | 971 | file2, header2, err := r.FormFile("msed") 972 | if header2.Size != 12 { 973 | log.Println(header.Size) 974 | return 975 | } 976 | var msed [12]byte 977 | _, err = file2.Read(msed[:]) 978 | if err != nil { 979 | log.Println(err) 980 | return 981 | } 982 | log.Println(header2.Filename) 983 | filename := "msed_data_" + id0 + ".bin" 984 | err = ioutil.WriteFile("static/mseds/"+filename, msed[:], 0644) 985 | if err != nil { 986 | log.Println(err) 987 | return 988 | } 989 | f, err := os.OpenFile("static/mseds/list", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 990 | if err != nil { 991 | log.Println(err) 992 | return 993 | } 994 | _, err = f.WriteString(filename + "\n") 995 | if err != nil { 996 | log.Println(err) 997 | return 998 | } 999 | f.Close() 1000 | 1001 | }).Methods("POST") 1002 | 1003 | router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1004 | renderTemplate("404error", make(jet.VarMap), r, w, nil) 1005 | }) 1006 | 1007 | // anti abuse task 1008 | ticker := time.NewTicker(15 * time.Second) 1009 | quit := make(chan struct{}) 1010 | go func() { 1011 | for { 1012 | select { 1013 | case <-ticker.C: 1014 | log.Println("running task") 1015 | log.Println(miners) 1016 | for ip, miner := range miners { 1017 | if miner.Before(time.Now().Add(time.Minute*-5)) == true { 1018 | delete(miners, ip) 1019 | } 1020 | } 1021 | for ip, miner := range iminers { 1022 | if miner.Before(time.Now().Add(time.Second*-30)) == true { 1023 | delete(iminers, ip) 1024 | } 1025 | } 1026 | query := devices.Find(bson.M{"wantsbf": true, "hasmovable": bson.M{"$ne": true}, "$or": []bson.M{bson.M{"claimtime": bson.M{"$lt": time.Now()}}, bson.M{"expirytime": bson.M{"$ne": time.Time{}, "$lt": time.Now()}}, bson.M{"expired": true}}}) 1027 | var theDevices []bson.M 1028 | err := query.All(&theDevices) 1029 | if err != nil { 1030 | log.Println(err) 1031 | //return 1032 | } 1033 | for _, device := range theDevices { 1034 | if v, ok := device["checktime"].(time.Time); ok && v.After(time.Now()) { 1035 | err = devices.Update(bson.M{"_id": device["_id"]}, bson.M{"$set": bson.M{"expirytime": time.Time{}, "wantsbf": false, "expired": true}}) 1036 | if err != nil { 1037 | log.Println(err) 1038 | //return 1039 | } 1040 | 1041 | minerCollection.Upsert(bson.M{"_id": device["miner"]}, bson.M{"$inc": bson.M{"score": -3}}) 1042 | 1043 | for id0, conn := range connections { 1044 | if id0 == device["_id"] { 1045 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("flag")); err != nil { 1046 | log.Println(err) 1047 | delete(connections, id0) 1048 | //return 1049 | } 1050 | } 1051 | } 1052 | log.Println(device["_id"], "job has expired") 1053 | 1054 | } else { 1055 | // checktime expired 1056 | err = devices.Update(bson.M{"_id": device["_id"]}, bson.M{"$set": bson.M{"expirytime": time.Time{}}}) 1057 | if err != nil { 1058 | log.Println(err) 1059 | //return 1060 | } 1061 | 1062 | for id0, conn := range connections { 1063 | if id0 == device["_id"] { 1064 | if err := conn.WriteMessage(websocket.TextMessage, buildMessage("queue")); err != nil { 1065 | log.Println(err) 1066 | delete(connections, id0) 1067 | //return 1068 | } 1069 | } 1070 | } 1071 | log.Println(device["_id"], "job has checktimed") 1072 | } 1073 | } 1074 | case <-quit: 1075 | ticker.Stop() 1076 | return 1077 | } 1078 | } 1079 | }() 1080 | 1081 | log.Println("serving on :80 and 443") 1082 | m := &autocert.Manager{ 1083 | Prompt: autocert.AcceptTOS, 1084 | HostPolicy: autocert.HostWhitelist("seedhelper.figgyc.uk"), 1085 | Cache: autocert.DirCache("."), 1086 | } 1087 | httpSrv := &http.Server{ 1088 | ReadTimeout: 5 * time.Second, 1089 | WriteTimeout: 5 * time.Second, 1090 | IdleTimeout: 120 * time.Second, 1091 | Handler: router, 1092 | } 1093 | httpSrv.Addr = ":80" 1094 | httpsSrv := &http.Server{ 1095 | ReadTimeout: 5 * time.Second, 1096 | WriteTimeout: 5 * time.Second, 1097 | IdleTimeout: 120 * time.Second, 1098 | Handler: router, 1099 | } 1100 | httpsSrv.Addr = ":443" 1101 | httpsSrv.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate} 1102 | go httpSrv.ListenAndServe() 1103 | httpsSrv.ListenAndServeTLS("", "") 1104 | } 1105 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/figgyc/seedhelper2/5d24fa66a0e1a2026f13d394391e39d802f2763a/logo.png -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 57 | 59 | 60 | 62 | image/svg+xml 63 | 65 | 66 | 67 | 68 | 69 | 74 | 77 | 84 | 123456789012 105 | 106 | 112 | 115 | 118 | 121 | 129 | 0a 2f5c b9 145 | 146 | 147 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /static/autolauncher_version: -------------------------------------------------------------------------------- 1 | 2.1.1 -------------------------------------------------------------------------------- /static/bfcl.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/figgyc/seedhelper2/5d24fa66a0e1a2026f13d394391e39d802f2763a/static/bfcl.exe -------------------------------------------------------------------------------- /static/getmseds.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import requests 4 | 5 | # https://stackoverflow.com/a/16696317 thx 6 | def download_file(url, local_filename): 7 | # NOTE the stream=True parameter 8 | r = requests.get(url, stream=True) 9 | with open(local_filename, 'wb') as f: 10 | for chunk in r.iter_content(chunk_size=1024): 11 | if chunk: # filter out keep-alive new chunks 12 | f.write(chunk) 13 | #f.flush() commented by recommendation from J.F.Sebastian 14 | return local_filename 15 | 16 | r = requests.get("https://seedhelper.figgyc.uk/static/mseds/list") 17 | for line in r.text.splitlines(): 18 | download_file("https://seedhelper.figgyc.uk/static/mseds/" + line, line) -------------------------------------------------------------------------------- /static/impossible_part1.sed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/figgyc/seedhelper2/5d24fa66a0e1a2026f13d394391e39d802f2763a/static/impossible_part1.sed -------------------------------------------------------------------------------- /static/js/script.js: -------------------------------------------------------------------------------- 1 | // hi! this script is what automatically recieves when things are finished. 2 | 3 | //thx 4 | // Converts an ArrayBuffer directly to base64, without any intermediate 'convert to string then 5 | // use window.btoa' step. According to my tests, this appears to be a faster approach: 6 | // http://jsperf.com/encoding-xhr-image-data/5 7 | 8 | /* 9 | MIT LICENSE 10 | 11 | Copyright 2011 Jon Leighton 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | */ 19 | 20 | function base64ArrayBuffer(arrayBuffer) { 21 | var base64 = '' 22 | var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' 23 | 24 | var bytes = new Uint8Array(arrayBuffer) 25 | var byteLength = bytes.byteLength 26 | var byteRemainder = byteLength % 3 27 | var mainLength = byteLength - byteRemainder 28 | 29 | var a, b, c, d 30 | var chunk 31 | 32 | // Main loop deals with bytes in chunks of 3 33 | for (var i = 0; i < mainLength; i = i + 3) { 34 | // Combine the three bytes into a single integer 35 | chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2] 36 | 37 | // Use bitmasks to extract 6-bit segments from the triplet 38 | a = (chunk & 16515072) >> 18 // 16515072 = (2^6 - 1) << 18 39 | b = (chunk & 258048) >> 12 // 258048 = (2^6 - 1) << 12 40 | c = (chunk & 4032) >> 6 // 4032 = (2^6 - 1) << 6 41 | d = chunk & 63 // 63 = 2^6 - 1 42 | 43 | // Convert the raw binary segments to the appropriate ASCII encoding 44 | base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d] 45 | } 46 | 47 | // Deal with the remaining bytes and padding 48 | if (byteRemainder == 1) { 49 | chunk = bytes[mainLength] 50 | 51 | a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2 52 | 53 | // Set the 4 least significant bits to zero 54 | b = (chunk & 3) << 4 // 3 = 2^2 - 1 55 | 56 | base64 += encodings[a] + encodings[b] + '==' 57 | } else if (byteRemainder == 2) { 58 | chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1] 59 | 60 | a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10 61 | b = (chunk & 1008) >> 4 // 1008 = (2^6 - 1) << 4 62 | 63 | // Set the 2 least significant bits to zero 64 | c = (chunk & 15) << 2 // 15 = 2^4 - 1 65 | 66 | base64 += encodings[a] + encodings[b] + encodings[c] + '=' 67 | } 68 | 69 | return base64 70 | } 71 | // 72 | 73 | let socket = new WebSocket("wss://" + location.host + "/socket") 74 | let force = "no" 75 | 76 | socket.addEventListener("open", (e) => { 77 | if (localStorage.getItem("id0") != null) { 78 | // send intro packet, if we get anything back then stuff will happen 79 | socket.send(JSON.stringify({ 80 | id0: localStorage.getItem("id0") 81 | })) 82 | } 83 | setInterval(() => { 84 | // send id0 packet, its a good fallback as well as providing 'live' stats 85 | socket.send(JSON.stringify({ 86 | id0: localStorage.getItem("id0") 87 | })) 88 | }, 60000) 89 | }) 90 | 91 | socket.addEventListener("close", () => { 92 | document.getElementById("navbar").classList.remove("bg-primary") 93 | document.getElementById("statusText").innerText = "Refresh the page" 94 | document.getElementById("navbar").classList.add("bg-warning") 95 | setTimeout(() => { 96 | window.location.reload(true) 97 | }, 10000) 98 | }) 99 | socket.addEventListener("error", () => { 100 | document.getElementById("navbar").classList.remove("bg-primary") 101 | document.getElementById("statusText").innerText = "Refresh the page" 102 | document.getElementById("navbar").classList.add("bg-warning") 103 | setTimeout(() => { 104 | window.location.reload(true) 105 | }, 10000) 106 | }) 107 | 108 | socket.addEventListener("message", (e) => { 109 | let data = JSON.parse(e.data) 110 | document.getElementById("statusText").innerText = `${data.minerCount} miners are online, ${data.userCount} users in the mining queue, ${data.miningCount} are being mined, ${data.totalCount} total users, ${data.p1count} got part1, ${data.msCount} got movable` 111 | //console.log("hey!", e.data, data.status) 112 | if (data.status == "friendCodeAdded") { 113 | /* 114 | Step 2: tell the user to add the bot 115 | */ 116 | document.getElementById("collapseOne").classList.remove("show") 117 | document.getElementById("collapseTwo").classList.add("show") 118 | } 119 | if (data.status == "friendCodeProcessing") { 120 | document.getElementById("fcProgress").style.display = "block" 121 | } 122 | if (data.status == "friendCodeInvalid") { 123 | document.getElementById("fcProgress").style.display = "none" 124 | document.getElementById("fcError").style.display = "block" 125 | document.getElementById("beginButton").disabled = false 126 | } 127 | if (data.status == "movablePart1") { 128 | /* 129 | Step 3: ask user if they or worker wants to BF 130 | continue button 131 | downloadPart1 a 132 | */ 133 | document.getElementById("collapseOne").classList.remove("show") 134 | document.getElementById("collapseTwo").classList.remove("show") 135 | document.getElementById("collapseThree").classList.add("show") 136 | document.getElementById("downloadPart1").href = "/part1/" + localStorage.getItem("id0") 137 | document.getElementById("downloadPart12").href = "/part1/" + localStorage.getItem("id0") 138 | // document.getElementById("downloadPart1").click() 139 | } 140 | if (data.status == "done") { 141 | /* 142 | Step 5: done! 143 | downloadMovable a 144 | */ 145 | document.getElementById("collapseOne").classList.remove("show") 146 | document.getElementById("collapseTwo").classList.remove("show") 147 | document.getElementById("collapseThree").classList.remove("show") 148 | document.getElementById("collapseFour").classList.remove("show") 149 | document.getElementById("collapseFive").classList.add("show") 150 | document.getElementById("downloadMovable").href = "/movable/" + localStorage.getItem("id0") 151 | document.getElementById("downloadMovable2").href = "/movable/" + localStorage.getItem("id0") 152 | } 153 | if (data.status == "flag") { 154 | /* 155 | Step -1: flag 156 | */ 157 | document.getElementById("collapseOne").classList.add("show") 158 | document.getElementById("collapseTwo").classList.remove("show") 159 | document.getElementById("collapseThree").classList.remove("show") 160 | document.getElementById("collapseFour").classList.remove("show") 161 | document.getElementById("collapseFive").classList.remove("show") 162 | document.getElementById("fcError").style.display = "block" 163 | document.getElementById("beginButton").disabled = true 164 | document.getElementById("fcError").innerText = "Your movable.sed took to long to bruteforce. This is most likely because your ID0 was incorrect. You will need to ask for help on the " 165 | let link = document.createElement("a") 166 | link.innerText = "Nintendo Homebrew Discord." 167 | link.href = "https://discord.gg/C29hYvh" 168 | document.getElementById("fcError").appendChild(link) 169 | } 170 | if (data.status == "queue") { 171 | /* 172 | Step 4 173 | */ 174 | document.getElementById("collapseOne").classList.remove("show") 175 | document.getElementById("collapseTwo").classList.remove("show") 176 | document.getElementById("collapseThree").classList.remove("show") 177 | document.getElementById("collapseFour").classList.add("show") 178 | document.getElementById("collapseFive").classList.remove("show") 179 | document.getElementById("bfProgress").classList.remove("bg-warning") 180 | document.getElementById("id0Fill").innerText = localStorage.getItem("id0") 181 | document.getElementById("bfProgress").innerText = "Waiting..." 182 | } 183 | if (data.status == "bruteforcing") { 184 | /* 185 | Step 4.1 186 | */ 187 | document.getElementById("collapseOne").classList.remove("show") 188 | document.getElementById("collapseTwo").classList.remove("show") 189 | document.getElementById("collapseThree").classList.remove("show") 190 | document.getElementById("collapseFour").classList.add("show") 191 | document.getElementById("collapseFive").classList.remove("show") 192 | document.getElementById("bfProgress").classList.add("bg-warning") 193 | document.getElementById("id0Fill").innerText = localStorage.getItem("id0") 194 | document.getElementById("bfProgress").innerText = "Bruteforcing..." 195 | } 196 | if (data.status == "couldBeID1") { 197 | document.getElementById("fcProgress").style.display = "none" 198 | document.getElementById("fcWarning").style.display = "block" 199 | } 200 | }) 201 | 202 | /* 203 | Step 0?: parse preprovided part1 204 | uploadP1 a 205 | p1file input[type=file] 206 | */ 207 | document.getElementById("uploadp1").addEventListener("click", (e) => { 208 | let fileInput = document.getElementById("p1file") 209 | fileInput.click() 210 | }) 211 | 212 | document.getElementById("p1file").addEventListener("change", (e) => { 213 | let fileInput = e.target 214 | let fileList = fileInput.files 215 | if (fileList.length == 1 && fileList[0].size == 0x1000) { 216 | let file = fileInput.files[0] 217 | let fileReader = new FileReader() 218 | fileReader.readAsArrayBuffer(file) 219 | fileReader.addEventListener("loadend", () => { 220 | let arrayBuffer = fileReader.result 221 | let lfcsBuffer = arrayBuffer.slice(0, 8) 222 | let lfcsArray = new Uint8Array(lfcsBuffer) 223 | let textDecoder = new TextDecoder() 224 | let lfcsString = textDecoder.decode(lfcsArray) 225 | if (lfcsString == textDecoder.decode(new Uint8Array(8))) { 226 | alert("movable_part1.sed is invalid") 227 | return 228 | } 229 | document.getElementById("part1b64").value = base64ArrayBuffer(lfcsBuffer) 230 | let id0Buffer = arrayBuffer.slice(0x10, 0x10+32) 231 | let id0Array = new Uint8Array(id0Buffer) 232 | document.getElementById("friendCode").disabled = true 233 | document.getElementById("friendCode").value = "movable_part1 provided" 234 | let id0String = textDecoder.decode(id0Array) 235 | console.log(id0String, btoa(id0String), id0String.length) 236 | if (btoa(id0String) != "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") { // non blank, if id0 is injected with seedminer_helper 237 | let id0Input = document.getElementById("id0") 238 | id0Input.disabled = true 239 | id0Input.value = id0String 240 | } 241 | }) 242 | } 243 | }) 244 | /* 245 | Step 1: gather user data 246 | friendCode input box 247 | id0 input box 248 | beginButton button 249 | */ 250 | document.getElementById("beginButton").addEventListener("click", (e) => { 251 | e.preventDefault() 252 | document.getElementById("friendCode").value = document.getElementById("friendCode").value.replace(/-/g, "") 253 | document.getElementById("fcError").style.display = "none" 254 | document.getElementById("beginButton").disabled = true 255 | document.getElementById("id0").value = document.getElementById("id0").value.toLowerCase() 256 | localStorage.setItem("id0", document.getElementById("id0").value) 257 | if (document.getElementById("part1b64").value != "") { 258 | socket.send(JSON.stringify({ 259 | part1: document.getElementById("part1b64").value, 260 | defoID0: force, 261 | id0: document.getElementById("id0").value, 262 | })) 263 | } else { 264 | socket.send(JSON.stringify({ 265 | friendCode: document.getElementById("friendCode").value, 266 | id0: document.getElementById("id0").value, 267 | defoID0: force 268 | })) 269 | } 270 | }) 271 | 272 | /* 273 | Step 4: wait for BF 274 | continue button 275 | */ 276 | document.getElementById("continue").addEventListener("click", (e) => { 277 | e.preventDefault() 278 | //document.getElementById("").setAttribute() 279 | socket.send(JSON.stringify({ 280 | request: "bruteforce", 281 | id0: localStorage.getItem("id0"), 282 | })) 283 | document.getElementById("collapseThree").classList.remove("show") 284 | document.getElementById("collapseFour").classList.add("show") 285 | document.getElementById("id0Fill").innerText = localStorage.getItem("id0") 286 | }) 287 | 288 | // anti queue clogging 289 | document.querySelector(".disableButton").addEventListener("click", (e) => { 290 | document.getElementById("continue").disabled = true 291 | document.getElementById("disableMessage").style.display = "block" 292 | }) 293 | 294 | document.getElementById("enableButton").addEventListener("click", (e) => { 295 | document.getElementById("continue").disabled = false 296 | document.getElementById("disableMessage").style.display = "none" 297 | }) 298 | 299 | document.getElementById("statusText").addEventListener("click", (e) => { 300 | document.getElementById("beginButton").disabled = false 301 | force = "yes" 302 | }) 303 | 304 | document.getElementById("id0").addEventListener("change", e => { 305 | document.getElementById("beginButton").disabled = false 306 | }) 307 | 308 | document.getElementById("friendCode").addEventListener("change", e => { 309 | document.getElementById("beginButton").disabled = false 310 | }) 311 | 312 | /* 313 | cancel task 314 | */ 315 | 316 | function cancel (e) { 317 | e.preventDefault() 318 | document.getElementById("cancelButton").disabled = true 319 | document.getElementById("downloadPart1").click() 320 | socket.send(JSON.stringify({ 321 | request: "cancel", 322 | id0: document.getElementById("id0").value, 323 | })) 324 | document.getElementById("collapseFour").classList.remove("show") 325 | document.getElementById("collapseOne").classList.add("show") 326 | localStorage.clear(); 327 | location.reload(true); 328 | } 329 | 330 | document.getElementById("cancelButton").addEventListener("click", cancel) 331 | document.getElementById("cancelButton1").addEventListener("click", cancel) 332 | document.getElementById("cancelButton2").addEventListener("click", cancel) 333 | document.getElementById("cancelButton3").addEventListener("click", cancel) 334 | -------------------------------------------------------------------------------- /static/mseds/empty: -------------------------------------------------------------------------------- 1 | this file intentionally left with a funny message -------------------------------------------------------------------------------- /static/pause.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/figgyc/seedhelper2/5d24fa66a0e1a2026f13d394391e39d802f2763a/static/pause.exe -------------------------------------------------------------------------------- /static/seedminer_autolauncher.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import os.path 5 | import sys 6 | import signal 7 | import time 8 | import re 9 | import glob 10 | import subprocess 11 | import datetime 12 | import io 13 | import getpass 14 | import requests 15 | import shutil 16 | import pickle 17 | 18 | s = requests.Session() 19 | baseurl = "https://seedhelper.figgyc.uk" 20 | currentid = "" 21 | currentVersion = "2.1.1" 22 | 23 | if os.path.isfile("total_mined"): 24 | with open("total_mined", "rb") as file: 25 | total_mined = pickle.load(file) 26 | else: 27 | total_mined = 0 28 | print("Total seeds mined previously: {}".format(total_mined)) 29 | # https://stackoverflow.com/a/16696317 thx 30 | 31 | 32 | def download_file(url, local_filename): 33 | # NOTE the stream=True parameter 34 | r = requests.get(url, stream=True) 35 | with open(local_filename, 'wb') as f: 36 | for chunk in r.iter_content(chunk_size=1024): 37 | if chunk: # filter out keep-alive new chunks 38 | f.write(chunk) 39 | # f.flush() commented by recommendation from J.F.Sebastian 40 | return local_filename 41 | 42 | 43 | print("Checking for updates...") 44 | r0 = s.get(baseurl + "/static/autolauncher_version") 45 | if r0.text != currentVersion: 46 | print("Updating") 47 | download_file(baseurl + "/static/seedminer_autolauncher.py", 48 | "seedminer_autolauncher.py") 49 | os.system('"' + sys.executable + '" seedminer_autolauncher.py') 50 | 51 | print("Updating seedminer db...") 52 | os.system('"' + sys.executable + '" seedminer_launcher3.py update-db') 53 | 54 | ''' 55 | if not os.path.isfile("benchmark"): 56 | print("Benchmarking...") 57 | timeA = time.time() 58 | timeTarget = timeA + 80 59 | timeB = timeTarget + 1 # failsafe 60 | if not os.path.isfile("impossible_part1.sed"): 61 | download_file(baseurl + "/static/impossible_part1.sed", 62 | "impossible_part1.sed") 63 | shutil.copyfile("impossible_part1.sed", "movable_part1.sed") 64 | args = {} 65 | if os.name == 'nt': 66 | args['creationflags'] = 0x00000200 67 | # , stdout=subprocess.PIPE, universal_newlines=True) 68 | process = subprocess.Popen( 69 | [sys.executable, "seedminer_launcher3.py", "gpu"], stdout=subprocess.PIPE, universal_newlines=True, **args) 70 | while process.poll() == None: 71 | line = process.stdout.readline() 72 | sys.stdout.write(line) 73 | sys.stdout.flush() 74 | if line != '': 75 | if "offset:10" in line: 76 | process.kill() 77 | timeB = time.time() 78 | if timeB > timeA: 79 | print("Your computer is too slow to help Seedhelper") 80 | sys.exit(0) 81 | else: 82 | with open("benchmark", mode="w") as file: 83 | file.write("1") 84 | file.close() 85 | ''' 86 | 87 | 88 | def signal_handler(signal, frame): 89 | print('Exiting...') 90 | if currentid != "": 91 | cancel = input("Kill job, requeue, or do nothing? [k/r/X]") 92 | p = "x" 93 | if cancel.lower() == "r": 94 | p = "n" 95 | if cancel.lower() == "k": 96 | p = "y" 97 | if p != "x": 98 | s.get(baseurl + "/cancel/" + currentid + "?kill=" + p) 99 | sys.exit(0) 100 | else: 101 | sys.exit(0) 102 | 103 | 104 | signal.signal(signal.SIGINT, signal_handler) 105 | 106 | 107 | while True: 108 | try: 109 | print("Finding work...") 110 | try: 111 | r = s.get(baseurl + "/getwork") 112 | except: 113 | print("Error. Waiting 30 seconds...") 114 | time.sleep(30) 115 | continue 116 | if r.text == "nothing": 117 | print("No work. Waiting 30 seconds...") 118 | time.sleep(30) 119 | else: 120 | currentid = r.text 121 | r2 = s.get(baseurl + "/claim/" + currentid) 122 | if r2.text == "error": 123 | print("Device already claimed, trying again...") 124 | else: 125 | print("Downloading part1 for device " + currentid) 126 | download_file(baseurl + '/part1/' + 127 | currentid, 'movable_part1.sed') 128 | print("Bruteforcing " + str(datetime.datetime.now())) 129 | kwargs = {} 130 | if os.name == 'nt': 131 | kwargs['creationflags'] = 0x00000200 132 | # , stdout=subprocess.PIPE, universal_newlines=True) 133 | process = subprocess.Popen( 134 | [sys.executable, "seedminer_launcher3.py", "gpu"], **kwargs) 135 | timer = 0 136 | #stdout = open(process.stdout) 137 | while process.poll() == None: 138 | # we need to poll for kill more often then we check server because we would waste up to 30 secs after finish 139 | timer = timer + 1 140 | time.sleep(1) 141 | #line = process.stdout.read() 142 | # print(line) 143 | # if "offset:250" in line: 144 | # print("Job taking too long, killing...") 145 | # s.get(baseurl + "/cancel/" + currentid) 146 | # subprocess.call(['taskkill', '/F', '/T', '/IM', 'bfcl.exe']) 147 | # break 148 | if timer % 30 == 0: 149 | r3 = s.get(baseurl + "/check/" + currentid) 150 | if r3.text != "ok": 151 | print("Job cancelled or expired, killing...") 152 | # process.kill() broke 153 | subprocess.call( 154 | ['taskkill', '/F', '/T', '/IM', 'bfcl.exe']) 155 | currentid = "" 156 | print("press ctrl-c to quit") 157 | time.sleep(5) 158 | continue 159 | #os.system('"' + sys.executable + '" seedminer_launcher3.py gpu') 160 | if os.path.isfile("movable.sed"): 161 | print("Uploading") 162 | # seedhelper2 has no msed database but we upload these anyway so zoogie can have them 163 | # * means all if need specific format then *.csv 164 | list_of_files = glob.glob('msed_data_*.bin') 165 | latest_file = max(list_of_files, key=os.path.getctime) 166 | ur = s.post(baseurl + '/upload/' + currentid, files={ 167 | 'movable': open('movable.sed', 'rb'), 'msed': open(latest_file, 'rb')}) 168 | print(ur) 169 | if ur.text == "success": 170 | print("Upload succeeded!") 171 | os.remove("movable.sed") 172 | os.remove(latest_file) 173 | currentid = "" 174 | total_mined += 1 175 | print("Total seeds mined: {}".format(total_mined)) 176 | with open("total_mined", "wb") as file: 177 | pickle.dump(total_mined, file) 178 | print("press ctrl-c to quit") 179 | time.sleep(5) 180 | else: 181 | print("Upload failed!") 182 | sys.exit(1) 183 | except Exception: 184 | print("Error") 185 | s.get(baseurl + "/cancel/" + currentid + "?kill=n") 186 | time.sleep(10) 187 | -------------------------------------------------------------------------------- /static/seedminer_autolauncher2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # system modules 4 | import asyncio, sys, json, time, subprocess, os, os.path, glob, signal, re 5 | 6 | try: 7 | import aiohttp 8 | except ImportError: 9 | print('The new seedhelper script uses aiohttp. Run "pip install aiohttp" in an admin command prompt') 10 | 11 | currentversion = "3.0" 12 | enableupdater = False 13 | baseurl = "https://seedhelper.figgyc.uk" 14 | chunk_size = 1024^2 15 | config = {} 16 | id0 = "" 17 | banMsg = "You have been banned from Seedhelper. This is probably because your script is glitching out. If you think you should be unbanned then find figgyc on Discord." 18 | exitnextflag = False 19 | writeflag = True 20 | killflag = 0 21 | offsetre = re.compile('offset:([\-\d]+)') 22 | 23 | try: 24 | with open('config.json', 'r') as file: 25 | config = json.load(file) 26 | except Exception: 27 | open('config.json', 'a').close() 28 | 29 | async def download(session, url, filename): 30 | async with session.get(url) as resp: 31 | with open(filename, 'wb') as fd: 32 | while True: 33 | chunk = await resp.content.read(chunk_size) 34 | if not chunk: 35 | break 36 | fd.write(chunk) 37 | 38 | async def main(): 39 | async with aiohttp.ClientSession() as session: 40 | if enableupdater: 41 | print("Updating...") 42 | async with session.get(baseurl + '/static/autolauncher_version') as resp: 43 | version = await resp.text() 44 | if version == banMsg: 45 | print(version) 46 | return 47 | if version != currentversion: 48 | print("Updating...") 49 | await download(session, baseurl + '/static/seedminer_autolauncher.py', 'seedminer_autolauncher.py') 50 | subprocess.run([sys.executable, 'seedminer_autolauncher.py']) 51 | return 52 | # main code 53 | if not config.get('nameset', False): 54 | while True: 55 | name = input('Type a name to set, ideally a Discord name, which will appear on leaderboard and may be used to contact you if you are having issues: ') 56 | async with session.get(baseurl + '/setname', params={'name': name}) as resp: 57 | message = await resp.text() 58 | if message != "success": 59 | print(message) 60 | else: 61 | with open('config.json', 'w') as file: 62 | config["nameset"] = True 63 | json.dump(config, file) 64 | break 65 | print("Updating Seedminer database:") 66 | subprocess.run([sys.executable, 'seedminer_launcher3.py', 'update-db']) 67 | if not config.get('benchmarked', False): 68 | print("Benchmarking") 69 | timeA = time.time() 70 | timeB = time.time() + 200 71 | await download(session, baseurl + '/static/impossible_part1.sed', 'movable_part1.sed') 72 | process = await asyncio.create_subprocess_exec(sys.executable, 'seedminer_launcher3.py', 'gpu', stdout=asyncio.subprocess.PIPE, cwd=os.getcwd(), stdin=asyncio.subprocess.PIPE) 73 | while process.returncode == None: 74 | data = await process.stdout.read() 75 | line = data.decode('ascii') 76 | sys.stdout.write(line) 77 | sys.stdout.flush() 78 | if 'offset:10' in line: 79 | process.kill() 80 | timeB = time.time() 81 | break 82 | if timeA + 100 < timeB or process.returncode != 0: 83 | print("Your computer is not powerful enough to help Seedhelper. Sorry!") 84 | return 85 | else: 86 | with open('config.json', 'w') as file: 87 | config["benchmarked"] = True 88 | json.dump(config, file) 89 | while exitnextflag == False: 90 | sys.stdout.write("\rSearching for work... ") 91 | async with session.get(baseurl + '/getwork') as resp: 92 | text = await resp.text() 93 | if text == banMsg: 94 | print(text) 95 | return 96 | if text == "nothing": 97 | sys.stdout.write("\rNo work, waiting 10 seconds...") 98 | time.sleep(10) 99 | continue 100 | id0 = text 101 | print("Mining " + id0) 102 | try: 103 | async with session.get(baseurl + '/claim/' + id0) as resp: 104 | text = await resp.text() 105 | if text == "error": 106 | print("Claim failed, probably someone else got it first.") 107 | time.sleep(10) 108 | continue 109 | await download(session, baseurl + '/part1/' + id0, 'movable_part1.sed') 110 | process = await asyncio.create_subprocess_exec(sys.executable, 'seedminer_launcher3.py', 'gpu', stdout=asyncio.subprocess.PIPE, cwd=os.getcwd(), stdin=asyncio.subprocess.PIPE) 111 | n = 400 112 | while process.returncode == None: 113 | if killflag != 0: 114 | async with session.get(baseurl + '/cancel/' + id0 + '?kill=' + ('y' if killflag == 1 else 'n')) as resp: 115 | text = await resp.text() 116 | if text == "error": 117 | print("Cancel error") 118 | continue 119 | else: 120 | print("Killed/requeued.") 121 | id0 = '' 122 | print('Press Ctrl-C again to quit or wait to find another job') 123 | time.sleep(5) 124 | continue 125 | data = await process.stdout.readuntil(b'\r') 126 | line = data.decode('ascii') 127 | if writeflag: 128 | sys.stdout.write(line) 129 | sys.stdout.flush() 130 | if 'New3DS msed' in line: 131 | n = 200 132 | offset = int(offsetre.match(line).group(1)) 133 | if offset != None: 134 | if offset >= n: 135 | process.kill() 136 | break 137 | if offset % 5 == 0: 138 | async with session.get(baseurl + '/check/' + id0) as resp: 139 | text = await resp.text() 140 | if text == "error": 141 | print('Job expired, killing...') 142 | process.kill() 143 | break 144 | 145 | 146 | 147 | if returncode != 0: 148 | raise Exception("Process returncode not 0") 149 | 150 | if os.path.isfile("movable.sed"): 151 | print("Uploading...") 152 | list_of_files = glob.glob('msed_data_*.bin') 153 | latest_file = max(list_of_files, key=os.path.getctime) 154 | async with session.post(baseurl + '/upload/' + id0, data={'movable': open('movable.sed', 'rb'), 'msed': open(latest_file, 'rb')}) as resp: 155 | text = await resp.text() 156 | if text == 'success': 157 | print('Upload succeeded!') 158 | os.remove('movable.sed') 159 | os.remove(latest_file) 160 | id0 = '' 161 | time.sleep(5) 162 | else: 163 | raise Exception("Upload failed") 164 | else: 165 | raise FileNotFoundError("movable.sed is not generated") 166 | except Exception as e: 167 | print("Error, cancelling...") 168 | print(e) 169 | async with session.get(baseurl + '/cancel/' + id0) as resp: 170 | text = await resp.text() 171 | if text == "error": 172 | print("Cancel error") 173 | else: 174 | print("Cancelled") 175 | time.sleep(10) 176 | continue 177 | 178 | time.sleep(10) 179 | 180 | def signal_handler(signal, frame): 181 | if id0 != '': 182 | writeflag = False 183 | letter = input("Control-C pressed. Type an option from [r]equeue job (default), [k]ill job, [c]ancel (do nothing), or [e]xit after this job: ").lower() 184 | if letter == '': 185 | letter = 'k' 186 | if letter == 'k': 187 | killflag = 1 188 | elif letter == 'r': 189 | killflag = 2 190 | elif letter == 'e': 191 | exitnextflag = True 192 | elif letter == 'c': 193 | return 194 | print("Okay.") 195 | writeflag = True 196 | else: 197 | print('Exiting...') 198 | sys.exit(0) 199 | 200 | 201 | signal.signal(signal.SIGINT, signal_handler) 202 | 203 | # win32 needs the proactor loop for asyncio subprocesses 204 | if sys.platform == 'win32': 205 | loop = asyncio.ProactorEventLoop() 206 | asyncio.set_event_loop(loop) 207 | 208 | loop = asyncio.get_event_loop() 209 | loop.run_until_complete(main()) 210 | 211 | 212 | loop.close() 213 | -------------------------------------------------------------------------------- /views/404error.jet: -------------------------------------------------------------------------------- 1 | {{extends "layout.jet"}} 2 | {{block status()}}404{{end}} 3 | {{block body()}} 4 |
5 |

404

6 |

That page does not exist.

7 |
8 | {{end}} 9 | -------------------------------------------------------------------------------- /views/home.jet: -------------------------------------------------------------------------------- 1 | {{extends "layout.jet"}} {{block status()}}{{ minerCount }} miners are online, {{ userCount }} users are in the mining queue, 2 | {{ miningCount }} are being mined, {{ totalCount }} total users, {{ p1Count }} got part1, {{ msCount }} got movable{{end}} 3 | {{block body()}} 4 |
5 | {{ if isUp == false }} 6 |
Seedhelper seems to be having issues. You may be stuck waiting for the bot for a while until the issue is fixed! Uploading 7 | an existing part1 and having it mined should be operational. You should ask for a friend on the 8 | Nintendo Homebrew Discord and then upload the part1 here or bruteforce it yourself.
9 | {{end}} 10 |
If you have issues, try refreshing the page, pressing "Start again" below and asking for help on the 11 | Nintendo Homebrew Discord.
12 | 13 |
14 |
15 |
16 |
17 | 20 |
21 |
22 | 23 |
24 |
25 | Welcome to Seedhelper 2! This service is supported by the people who run the bruteforce script; see the bottom of this page 26 | for more info on that. You should be following 27 | Jisagi's seedminer guide to use this service. You may find it harder to get help if you aren't using 28 | this guide! To use this service, 29 | type your Friend Code and your id0 in the box below or upload a movable_part1.sed if you already 30 | have it. 31 |
32 |
33 | 34 | (how to get) 35 | 36 | or upload a movable_part1.sed 37 | 38 | 39 |
40 |
41 | 42 | (how to get) 43 | 44 |
45 | 46 | 47 | 48 | 51 | 59 | 63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | 73 |
74 |
75 |
76 |
77 | Add the friend code 78 | 79 | 0275-9929-0078 80 | 81 | . It is connected to this website and will automatically retrieve your movable_part1 when you 82 | add it back. Simply add it back and wait for it to process your friend code. If nothing on this website 83 | changes after you add the bot, refresh the page. 84 |
85 |
Waiting...
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | 98 |
99 |
100 |
101 |
102 | If you have your own GPU, then 103 | download movable_part1.sed and go back to the guide and bruteforce the movable.sed yourself. Otherwise, 104 | press Continue if you need someone else to bruteforce it. 105 | 106 | Download movable_part1.sed 107 | 108 | 109 | 112 |
113 |
114 |
115 |
116 |
117 |
118 | 122 |
123 |
124 |
125 |
126 | Wait for the bruteforce to complete. Feel free to leave the website running in the background or 127 | even close it. This may take up to an hour, but it usually lasts about 30 minutes and sometimes less. 128 | Don't worry if your GPU is not powerful because this is happening on another computer. While you wait, 129 | please check that your ID0 is correct and cancel the job if it is not, or you will be waiting forever! 130 | If you have been waiting a while and nothing has happened, try refreshing the page 131 |
ID0: 132 | 133 |
134 |
135 |
Waiting...
137 |
138 | If you cancel the bruteforce, you will have to restart the process! 139 |
140 |
141 |
142 |
143 |
144 |
145 | 149 |
150 |
151 |
152 |
153 | Download movable.sed and go back to the guide to inject your DSiWare. Thanks to the people who run 154 | the bruteforcing script who make this service possible! 155 |
156 | Download movable.sed 157 | 158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 | 169 |
170 |
171 | 172 |
173 |
174 | Seedhelper is powered by the people who run a special script and lend their GPU time to the service. If you have a powerful 175 | GPU then please lend it to the service when you aren't using it. Please don't run the script on integrated 176 | graphics as it will immensely slow the process for some users! Here's how to do that yourself: 177 |
    178 |
  1. Download and install 179 | Python 3 (not 2) 180 |
  2. 181 |
  3. Download 182 | seedminer and extract the zip
  4. 183 |
  5. Download 184 | seedminer_autolauncher.py. This script is different then Seedhelper 1 so please redownload 185 | it! 186 |
  6. 187 |
  7. Download the 188 | no-pause bfcl.exe mod 189 |
  8. 190 |
  9. Copy seedminer_autolauncher.py and bfcl.exe to the seedminer folder.
  10. 191 |
  11. Open an admin command prompt by opening Start, typing cmd and pressing Ctrl+Shift+Enter. Type "py 192 | -m pip install requests" without the quotes and press enter.
  12. 193 |
  13. Shift+right click in the seedminer folder, press "Open command window here" or "Open Windows Powershell 194 | here" and type "py seedminer_autolauncher.py" to start working. The script will test your GPU 195 | for adequate power to help keep the service fast. Press Ctrl+C inside the command window to stop 196 | working.
  14. 197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 | 207 |
208 |
209 | 210 |
211 |
212 | Seedhelper is developed by 213 | figgyc and is 214 | open source. Seedhelper has been possible thanks to many people: 215 |
    216 |
  • 217 | @Pirater12 for reverse engineering frd:a and making httpc_curl to make the friend code bot 218 | possible
  • 219 |
  • 220 | @zoogie for creating the seedminer applications and finding the vulnerability
  • 221 |
  • 222 | @Jisagi for creating the seedminer guide
  • 223 |
  • 224 | @jason0597, 225 | @knight-ryu12, 226 | @saibotu, and anyone who has used the service for testing and finding bugs!
  • 227 |
  • All Seedhelper miners including:
  • 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | {{range miner := miners}} 237 | 238 | 243 | 244 | 245 | {{end}} 246 | 247 |
    NameScore
    {{if _, ok := miner["name"]; ok}} 239 | {{miner["name"]}} 240 | {{else}} 241 | Someone 242 | {{end}}{{miner["score"]}}
    248 |
    249 | 250 | 251 |
    252 | 253 |
254 |
255 |
256 |
257 |
258 |
259 | {{end}} 260 | -------------------------------------------------------------------------------- /views/layout.jet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Seedhelper 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 24 | {{yield body()}} 25 | 26 | 27 | 28 | --------------------------------------------------------------------------------