MtnMCG

sethmc_server ndisk

Dec 20th, 2025 (edited)
103
0
Never
8
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 31.22 KB | None | 0 0
  1. -- Server startup.lua
  2.  
  3. local modem = peripheral.find("modem")
  4. if not modem then error("No modem found. Please attach a wireless modem.") end
  5.  
  6. local disk = peripheral.find("drive")
  7. if not disk then error("No disk drive found. Please attach a disk drive.") end
  8.  
  9. modem.open(1) -- Use channel 1 for all communications
  10.  
  11. local ndisk = dofile("ndisk_api.lua")
  12. _G.ndisk = ndisk -- Make available to other tabs
  13. local disk = peripheral.find("drive")
  14. local diskMount = nil
  15. if disk and disk.isDiskPresent() then
  16. diskMount = disk.getMountPath()
  17. end
  18.  
  19. -- Ideally we don't strictly require the disk anymore if files are on the server/ndisk,
  20. -- but we might warn if missing.
  21. if not diskMount then print("Warning: No local disk found. Ensure startup files are on the computer.") end
  22.  
  23. -- Initialize NDisk
  24. if not fs.exists(".ndisk_secret") then
  25. term.clear()
  26. term.setCursorPos(1, 1)
  27. print("NDISK SECURITY SETUP")
  28. print("====================")
  29. print("No security secret found.")
  30. write("Enter shared secret for cluster: ")
  31. local secret = read("*")
  32.  
  33. local f = fs.open(".ndisk_secret", "w")
  34. f.write(secret)
  35. f.close()
  36. print("\nSecret saved. initializing...")
  37. sleep(1)
  38. end
  39.  
  40. local f = fs.open(".ndisk_secret", "r")
  41. local secret = f.readAll()
  42. f.close()
  43. ndisk.setSecret(secret)
  44.  
  45. ndisk.init()
  46. print("NDisk System Initialized")
  47.  
  48. -- RAM Cache to reduce NDisk blocking
  49. local passwordCache = {}
  50. local balanceCache = {}
  51.  
  52. -- Transaction locks to prevent race conditions during balance updates
  53. local balanceLocks = {}
  54.  
  55. -- Maintenance mode: when true, server returns "try again" to all requests
  56. local maintenanceMode = false
  57. local function acquireLock(studentId)
  58. local attempts = 0
  59. while balanceLocks[studentId] and attempts < 50 do
  60. os.sleep(0.05)
  61. attempts = attempts + 1
  62. end
  63. if balanceLocks[studentId] then
  64. return false, "Lock timeout - try again"
  65. end
  66. balanceLocks[studentId] = os.epoch("utc")
  67. return true
  68. end
  69. local function releaseLock(studentId)
  70. balanceLocks[studentId] = nil
  71. end
  72.  
  73. -- Location history trimming
  74. local MAX_LOCATION_HISTORY = 700
  75.  
  76. local function trimLocationHistory(historyString, newEntry)
  77. local entries = {}
  78. for line in historyString:gmatch("[^\n]+") do
  79. table.insert(entries, line)
  80. end
  81. table.insert(entries, newEntry)
  82.  
  83. -- Trim oldest entries if over limit
  84. while #entries > MAX_LOCATION_HISTORY do
  85. table.remove(entries, 1)
  86. end
  87.  
  88. return table.concat(entries, "\n")
  89. end
  90.  
  91. local function setup()
  92. -- if not fs.exists(diskMount .. "/students") then fs.makeDir(diskMount .. "/students") end -- NDisk is flat, no dirs needed
  93. -- News and Student.lua are now expected on the server root (local computer)
  94. if not fs.exists("news.txt") then
  95. local file = fs.open("news.txt", "w")
  96. file.write("Welcome to the college!")
  97. file.close()
  98. end
  99. if not fs.exists("student.lua") then
  100. print("Warning: student.lua not found in root. Clients may fail request_update.")
  101. end
  102.  
  103. -- Launch Palantir OS in new tab if multishell is available
  104. if multishell then
  105. local terminalPath = "palantir.lua"
  106. if fs.exists(terminalPath) then
  107. multishell.launch({}, terminalPath)
  108. multishell.setTitle(multishell.getCount(), "Palantir")
  109. print("Launched Palantir OS.")
  110. else
  111. print("Palantir OS not found.")
  112. end
  113. else
  114. print("Multishell not available.")
  115. end
  116. print("Server setup complete. Files stored on disk.")
  117. end
  118.  
  119. local function createAccount(studentId, password)
  120. print("Creating account for: " .. studentId)
  121.  
  122. -- Check cache first
  123. if passwordCache[studentId] then
  124. print("Validation failed: Account exists (cached)")
  125. return false, "Account already exists"
  126. end
  127.  
  128. local passPath = "students/" .. studentId .. "/password.txt"
  129. if ndisk.exists(passPath) then
  130. print("Validation failed: Account exists")
  131. return false, "Account already exists"
  132. end
  133.  
  134. local success, err = ndisk.write(passPath, password)
  135. if not success then return false, "Write failed: " .. (err or "unknown") end
  136.  
  137. local balancePath = "students/" .. studentId .. "/balance.txt"
  138. ndisk.write(balancePath, "0")
  139.  
  140. -- Update cache
  141. passwordCache[studentId] = password
  142. balanceCache[studentId] = 0
  143.  
  144. print("Account created.")
  145. return true, "Account created successfully"
  146. end
  147.  
  148. local function verifyLogin(studentId, password)
  149. -- Check cache first (fast path)
  150. if passwordCache[studentId] then
  151. local match = (passwordCache[studentId] == password)
  152. if match then
  153. print("Login successful (cached): " .. studentId)
  154. else
  155. print("Login failed: Incorrect password for " .. studentId)
  156. end
  157. return match, match and "Login successful" or "Incorrect password"
  158. end
  159.  
  160. -- Cache miss - hit NDisk
  161. local passPath = "students/" .. studentId .. "/password.txt"
  162. if not ndisk.exists(passPath) then
  163. print("Login failed: Account not found")
  164. return false, "Account not found"
  165. end
  166.  
  167. local storedPass, err = ndisk.read(passPath)
  168. if not storedPass then
  169. print("Login failed: NDisk read error: " .. (err or "unknown"))
  170. return false, "Read error"
  171. end
  172.  
  173. storedPass = storedPass:gsub("%s+$", "")
  174.  
  175. -- Cache the password for future logins
  176. passwordCache[studentId] = storedPass
  177.  
  178. if storedPass ~= password then
  179. print("Login failed: Incorrect password for " .. studentId)
  180. else
  181. print("Login successful: " .. studentId)
  182. end
  183. return storedPass == password, storedPass == password and "Login successful" or "Incorrect password"
  184. end
  185.  
  186.  
  187.  
  188. local function handleBalance(studentId, action, amount)
  189. -- Acquire lock to prevent race conditions
  190. local lockAcquired, lockErr = acquireLock(studentId)
  191. if not lockAcquired then
  192. return false, lockErr
  193. end
  194.  
  195. local balancePath = "students/" .. studentId .. "/balance.txt"
  196. local balanceStr = ndisk.read(balancePath)
  197. local balance = tonumber(balanceStr) or 0
  198.  
  199. print("Processing balance update for " .. studentId)
  200. print("Current balance: " .. balance)
  201. print("Action: " .. action .. ", Amount: " .. amount)
  202. if action == "deposit" then
  203. balance = balance + amount
  204. elseif action == "withdraw" then
  205. if balance >= amount then
  206. balance = balance - amount
  207. else
  208. print("Insufficient funds. Transaction failed.")
  209. releaseLock(studentId)
  210. return false, "Insufficient funds"
  211. end
  212. end
  213.  
  214. ndisk.write(balancePath, tostring(balance))
  215.  
  216. -- Invalidate cache to ensure consistency
  217. balanceCache[studentId] = balance
  218.  
  219. -- Transaction logging for Palantir
  220. local txLog = ndisk.read("transactions/log.txt") or ""
  221. local txLine = os.epoch("local") .. "|" .. studentId .. "|" .. action .. "|" .. amount .. "|" .. balance
  222. ndisk.write("transactions/log.txt", txLog .. txLine .. "\n")
  223.  
  224. print("New balance: " .. balance)
  225. print("Transaction successful")
  226. releaseLock(studentId)
  227. return true, balance
  228. end
  229.  
  230. -- Atomic gambling transaction: handles bet withdrawal and payout in one call
  231. -- This prevents money duplication and ensures consistency
  232. local MAX_GAMBLING_BET = 20 -- Server-enforced max bet (defense in depth)
  233.  
  234. local function handleGamblingTransaction(playerId, houseId, betAmount, payoutAmount)
  235. -- AIRTIGHT: Enforce max bet on server side
  236. if betAmount > MAX_GAMBLING_BET then
  237. print("Gambling rejected: Bet " .. betAmount .. " exceeds max " .. MAX_GAMBLING_BET)
  238. return false, "Bet exceeds maximum (" .. MAX_GAMBLING_BET .. ")", nil, nil
  239. end
  240.  
  241. -- Lock both accounts to prevent race conditions
  242. local playerLock, playerErr = acquireLock(playerId)
  243. if not playerLock then
  244. return false, playerErr, nil, nil
  245. end
  246.  
  247. local houseLock, houseErr = acquireLock(houseId)
  248. if not houseLock then
  249. releaseLock(playerId)
  250. return false, houseErr, nil, nil
  251. end
  252.  
  253. -- Read current balances
  254. local playerBalancePath = "students/" .. playerId .. "/balance.txt"
  255. local houseBalancePath = "students/" .. houseId .. "/balance.txt"
  256.  
  257. local playerBalanceStr = ndisk.read(playerBalancePath)
  258. local playerBalance = tonumber(playerBalanceStr) or 0
  259.  
  260. local houseBalanceStr = ndisk.read(houseBalancePath)
  261. local houseBalance = tonumber(houseBalanceStr) or 0
  262.  
  263. print("Gambling transaction: " .. playerId .. " vs " .. houseId)
  264. print("Player balance: " .. playerBalance .. ", House balance: " .. houseBalance)
  265. print("Bet: " .. betAmount .. ", Payout: " .. payoutAmount)
  266.  
  267. -- Calculate net changes
  268. -- bet phase: player loses betAmount, house gains betAmount
  269. -- payout phase: house loses payoutAmount, player gains payoutAmount
  270. -- Net: player changes by (payoutAmount - betAmount), house changes by (betAmount - payoutAmount)
  271. local playerNet = payoutAmount - betAmount
  272. local houseNet = betAmount - payoutAmount
  273.  
  274. local newPlayerBalance = playerBalance + playerNet
  275. local newHouseBalance = houseBalance + houseNet
  276.  
  277. -- Validate: player must have had enough to bet (can't go below what they had minus bet)
  278. if playerBalance < betAmount then
  279. print("Gambling failed: Player insufficient funds")
  280. releaseLock(houseId)
  281. releaseLock(playerId)
  282. return false, "Insufficient funds to place bet", nil, nil
  283. end
  284.  
  285. -- Validate: house must have enough to pay out
  286. if houseBalance + betAmount < payoutAmount then
  287. print("Gambling failed: House cannot cover payout")
  288. releaseLock(houseId)
  289. releaseLock(playerId)
  290. return false, "House cannot cover payout", nil, nil
  291. end
  292.  
  293. -- Write both balances atomically
  294. ndisk.write(playerBalancePath, tostring(newPlayerBalance))
  295. ndisk.write(houseBalancePath, tostring(newHouseBalance))
  296.  
  297. -- Update caches
  298. balanceCache[playerId] = newPlayerBalance
  299. balanceCache[houseId] = newHouseBalance
  300.  
  301. -- GLOBAL HOUSE PROFIT TRACKING
  302. -- Track total wagered vs total paid out across ALL players
  303. local statsPath = "gambling/house_stats.txt"
  304. local statsData = ndisk.read(statsPath)
  305. local stats = statsData and textutils.unserialize(statsData) or {totalWagered = 0, totalPaidOut = 0, gamesPlayed = 0}
  306. stats.totalWagered = stats.totalWagered + betAmount
  307. stats.totalPaidOut = stats.totalPaidOut + payoutAmount
  308. stats.gamesPlayed = stats.gamesPlayed + 1
  309. stats.houseProfit = stats.totalWagered - stats.totalPaidOut
  310. ndisk.write(statsPath, textutils.serialize(stats))
  311.  
  312. -- Log the gambling transaction
  313. local txLog = ndisk.read("transactions/log.txt") or ""
  314. local txLine = os.epoch("local") .. "|GAMBLING|" .. playerId .. "|bet:" .. betAmount .. "|payout:" .. payoutAmount .. "|player_bal:" .. newPlayerBalance .. "|house_bal:" .. newHouseBalance .. "|house_profit:" .. stats.houseProfit
  315. ndisk.write("transactions/log.txt", txLog .. txLine .. "\n")
  316.  
  317. print("Gambling complete. Player: " .. newPlayerBalance .. ", House: " .. newHouseBalance .. ", Total Profit: " .. stats.houseProfit)
  318.  
  319. releaseLock(houseId)
  320. releaseLock(playerId)
  321. return true, "Gambling transaction successful", newPlayerBalance, newHouseBalance, stats.houseProfit
  322. end
  323.  
  324. local function getEmails(studentId)
  325. local emailPath = "students/" .. studentId .. "/emails.txt"
  326. if not ndisk.exists(emailPath) then return {} end
  327. local data = ndisk.read(emailPath)
  328. if not data then return {} end -- Handle read failure gracefully
  329. return textutils.unserialize(data) or {}
  330. end
  331.  
  332. local function saveEmails(studentId, emails)
  333. local emailPath = "students/" .. studentId .. "/emails.txt"
  334. ndisk.write(emailPath, textutils.serialize(emails))
  335. end
  336.  
  337. local function sendEmail(senderId, recipientId, subject, body)
  338. -- Check if recipient exists (folder check)
  339. local passPath = "students/" .. recipientId .. "/password.txt"
  340. if not ndisk.exists(passPath) then
  341. return false, "Recipient not found"
  342. end
  343.  
  344. local emails = getEmails(recipientId)
  345. table.insert(emails, 1, { -- Insert at beginning (newest first)
  346. id = os.epoch("utc"), -- Simple unique ID logic
  347. sender = senderId,
  348. subject = subject,
  349. body = body,
  350. timestamp = os.epoch("local"), -- Use local time for display
  351. read = false
  352. })
  353. saveEmails(recipientId, emails)
  354. return true, "Email sent successfully"
  355. end
  356.  
  357. local function deleteEmail(studentId, emailIndex)
  358. local emails = getEmails(studentId)
  359. if emails[emailIndex] then
  360. table.remove(emails, emailIndex)
  361. saveEmails(studentId, emails)
  362. return true, "Email deleted"
  363. else
  364. return false, "Email not found"
  365. end
  366. end
  367.  
  368. local function handleRequests()
  369. print("Server is running. Waiting for requests...")
  370. while true do
  371. local event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message")
  372. if type(message) == "table" then
  373. -- Maintenance mode handlers (always processed)
  374. if message.type == "maintenance_start" then
  375. maintenanceMode = true
  376. print("MAINTENANCE MODE: ENABLED")
  377. modem.transmit(replyChannel, 1, {type = "maintenance_response", success = true})
  378. elseif message.type == "maintenance_end" then
  379. maintenanceMode = false
  380. print("MAINTENANCE MODE: DISABLED")
  381. modem.transmit(replyChannel, 1, {type = "maintenance_response", success = true})
  382. elseif maintenanceMode then
  383. -- During maintenance, reject all other requests
  384. modem.transmit(replyChannel, 1, {type = "error", message = "Server maintenance in progress, try again"})
  385. elseif message.type == "request_update" then
  386. print("Received update request")
  387. if fs.exists("student.lua") then
  388. local file = fs.open("student.lua", "r")
  389. local content = file.readAll()
  390. file.close()
  391. modem.transmit(replyChannel, 1, {type = "update", content = content})
  392. print("Sent student.lua update")
  393. else
  394. print("Error: student.lua not found!")
  395. modem.transmit(replyChannel, 1, {type = "error", message = "Update file missing"})
  396. end
  397. elseif message.type == "create_account" then
  398. if message.studentId and message.password then
  399. local success, msg = createAccount(message.studentId, message.password)
  400. modem.transmit(replyChannel, 1, {type = "account_response", success = success, message = msg})
  401. else
  402. modem.transmit(replyChannel, 1, {type = "error", message = "Invalid account creation request"})
  403. end
  404. elseif message.type == "login" then
  405. if message.studentId and message.password then
  406. local success, msg = verifyLogin(message.studentId, message.password)
  407. modem.transmit(replyChannel, 1, {type = "login_response", success = success, message = msg})
  408. else
  409. modem.transmit(replyChannel, 1, {type = "error", message = "Invalid login request"})
  410. end
  411. elseif message.type == "get_news" then
  412. local newsContent = "No news"
  413. if fs.exists("news.txt") then
  414. local newsFile = fs.open("news.txt", "r")
  415. newsContent = newsFile.readAll()
  416. newsFile.close()
  417. end
  418. modem.transmit(replyChannel, 1, {type = "news", content = newsContent})
  419.  
  420. elseif message.type == "get_balance" then
  421. if message.studentId and message.password then
  422. local success, msg = verifyLogin(message.studentId, message.password)
  423. if success then
  424. local balancePath = "students/" .. message.studentId .. "/balance.txt"
  425. local balance = ndisk.read(balancePath)
  426. modem.transmit(replyChannel, 1, {type = "balance_response", balance = balance})
  427. else
  428. modem.transmit(replyChannel, 1, {type = "error", message = msg})
  429. end
  430. else
  431. modem.transmit(replyChannel, 1, {type = "error", message = "Invalid balance request"})
  432. end
  433. elseif message.type == "update_balance" then
  434. if message.studentId and message.password and message.action and message.amount then
  435. local success, msg = verifyLogin(message.studentId, message.password)
  436. if success then
  437. local success, result = handleBalance(message.studentId, message.action, message.amount)
  438. modem.transmit(replyChannel, 1, {type = "balance_update_response", success = success, result = result})
  439. else
  440. modem.transmit(replyChannel, 1, {type = "error", message = msg})
  441. end
  442. else
  443. modem.transmit(replyChannel, 1, {type = "error", message = "Invalid balance update request"})
  444. end
  445. elseif message.type == "gambling_transaction" then
  446. print("DEBUG: Received gambling_transaction")
  447. -- Atomic gambling transaction: bet and payout in one call
  448. if message.studentId and message.password and message.houseId and message.housePw
  449. and message.betAmount and message.payoutAmount then
  450. print("DEBUG: All fields present")
  451. -- Verify player
  452. local playerValid, playerMsg = verifyLogin(message.studentId, message.password)
  453. if not playerValid then
  454. print("DEBUG: Player login failed")
  455. modem.transmit(replyChannel, 1, {type = "gambling_response", success = false, message = playerMsg})
  456. else
  457. print("DEBUG: Player OK, checking house")
  458. -- Verify house account
  459. local houseValid, houseMsg = verifyLogin(message.houseId, message.housePw)
  460. if not houseValid then
  461. print("DEBUG: House login failed")
  462. modem.transmit(replyChannel, 1, {type = "gambling_response", success = false, message = "House auth failed"})
  463. else
  464. print("DEBUG: House OK, processing transaction")
  465. local success, msg, playerBal, houseBal = handleGamblingTransaction(
  466. message.studentId, message.houseId, message.betAmount, message.payoutAmount
  467. )
  468. print("DEBUG: Transaction done, sending response")
  469. modem.transmit(replyChannel, 1, {
  470. type = "gambling_response",
  471. success = success,
  472. message = msg,
  473. playerBalance = playerBal,
  474. houseBalance = houseBal
  475. })
  476. end
  477. end
  478. else
  479. print("DEBUG: Missing fields - studentId:" .. tostring(message.studentId) .. " houseId:" .. tostring(message.houseId))
  480. modem.transmit(replyChannel, 1, {type = "gambling_response", success = false, message = "Invalid gambling request"})
  481. end
  482. elseif message.type == "get_house_gambling_stats" then
  483. -- Return global house profit tracking stats (for admin/house queries)
  484. if message.houseId and message.housePw then
  485. local houseValid, houseMsg = verifyLogin(message.houseId, message.housePw)
  486. if houseValid then
  487. local statsPath = "gambling/house_stats.txt"
  488. local statsData = ndisk.read(statsPath)
  489. local stats = statsData and textutils.unserialize(statsData) or {totalWagered = 0, totalPaidOut = 0, gamesPlayed = 0, houseProfit = 0}
  490. modem.transmit(replyChannel, 1, {
  491. type = "house_stats_response",
  492. success = true,
  493. stats = stats
  494. })
  495. else
  496. modem.transmit(replyChannel, 1, {type = "house_stats_response", success = false, message = "Auth failed"})
  497. end
  498. else
  499. modem.transmit(replyChannel, 1, {type = "house_stats_response", success = false, message = "Missing credentials"})
  500. end
  501. elseif message.type == "verify_student" then
  502. if message.recipientId then
  503. local passPath = "students/" .. message.recipientId .. "/password.txt"
  504. local exists = ndisk.exists(passPath)
  505. modem.transmit(replyChannel, 1, {type = "verify_student_response", exists = exists})
  506. else
  507. modem.transmit(replyChannel, 1, {type = "error", message = "Invalid student verification request"})
  508. end
  509. elseif message.type == "transfer_money" then
  510. if message.studentId and message.password and message.recipientId and message.amount then
  511. local success, msg = verifyLogin(message.studentId, message.password)
  512. if success then
  513. local senderBalancePath = "students/" .. message.studentId .. "/balance.txt"
  514. local senderBalanceStr = ndisk.read(senderBalancePath)
  515. local senderBalance = tonumber(senderBalanceStr) or 0
  516.  
  517. local recipientBalancePath = "students/" .. message.recipientId .. "/balance.txt"
  518. local recipientBalanceStr = ndisk.read(recipientBalancePath)
  519. local recipientBalance = tonumber(recipientBalanceStr) or 0
  520.  
  521. local expectedSenderBalance = senderBalance - message.amount
  522. local expectedRecipientBalance = recipientBalance + message.amount
  523.  
  524. if expectedSenderBalance >= 0 then
  525. local withdrawSuccess, _ = handleBalance(message.studentId, "withdraw", message.amount)
  526. if withdrawSuccess then
  527. local depositSuccess, _ = handleBalance(message.recipientId, "deposit", message.amount)
  528. if depositSuccess then
  529.  
  530. -- Verify balances
  531. local newSenderBalanceStr = ndisk.read(senderBalancePath)
  532. local newSenderBalance = tonumber(newSenderBalanceStr) or 0
  533.  
  534. local newRecipientBalanceStr = ndisk.read(recipientBalancePath)
  535. local newRecipientBalance = tonumber(newRecipientBalanceStr) or 0
  536.  
  537. if newSenderBalance == expectedSenderBalance and newRecipientBalance == expectedRecipientBalance then
  538. modem.transmit(replyChannel, 1, {type = "transfer_response", success = true, newBalance = newSenderBalance})
  539. else
  540. -- Revert transaction
  541. handleBalance(message.studentId, "deposit", message.amount)
  542. handleBalance(message.recipientId, "withdraw", message.amount)
  543. modem.transmit(replyChannel, 1, {type = "transfer_response", success = false, message = "Transaction verification failed"})
  544. end
  545. else
  546. handleBalance(message.studentId, "deposit", message.amount)
  547. modem.transmit(replyChannel, 1, {type = "transfer_response", success = false, message = "Failed to deposit to recipient"})
  548. end
  549. else
  550. modem.transmit(replyChannel, 1, {type = "transfer_response", success = false, message = "Failed to withdraw from sender"})
  551. end
  552. else
  553. modem.transmit(replyChannel, 1, {type = "transfer_response", success = false, message = "Insufficient funds"})
  554. end
  555. else
  556. modem.transmit(replyChannel, 1, {type = "transfer_response", success = false, message = msg})
  557. end
  558. else
  559. modem.transmit(replyChannel, 1, {type = "transfer_response", success = false, message = "Invalid transfer request"})
  560. end
  561. elseif message.type == "send_email" then
  562. if message.studentId and message.password and message.recipientId and message.subject and message.body then
  563. local success, msg = verifyLogin(message.studentId, message.password)
  564. if success then
  565. local sent, result = sendEmail(message.studentId, message.recipientId, message.subject, message.body)
  566. modem.transmit(replyChannel, 1, {type = "email_response", success = sent, message = result})
  567. else
  568. modem.transmit(replyChannel, 1, {type = "error", message = msg})
  569. end
  570. else
  571. modem.transmit(replyChannel, 1, {type = "error", message = "Invalid send email request"})
  572. end
  573. elseif message.type == "get_inbox" then
  574. if message.studentId and message.password then
  575. local success, msg = verifyLogin(message.studentId, message.password)
  576. if success then
  577. local emails = getEmails(message.studentId)
  578. modem.transmit(replyChannel, 1, {type = "inbox_response", emails = emails})
  579. else
  580. modem.transmit(replyChannel, 1, {type = "error", message = msg})
  581. end
  582. else
  583. modem.transmit(replyChannel, 1, {type = "error", message = "Invalid inbox request"})
  584. end
  585. elseif message.type == "delete_email" then
  586. if message.studentId and message.password and message.emailIndex then
  587. local success, msg = verifyLogin(message.studentId, message.password)
  588. if success then
  589. local deleted, result = deleteEmail(message.studentId, message.emailIndex)
  590. modem.transmit(replyChannel, 1, {type = "email_response", success = deleted, message = result})
  591. else
  592. modem.transmit(replyChannel, 1, {type = "error", message = msg})
  593. end
  594. else
  595. modem.transmit(replyChannel, 1, {type = "error", message = "Invalid delete email request"})
  596. end
  597. elseif message.type == "get_phonebook" then
  598. -- Return list of all student IDs for phonebook
  599. local allStudents = ndisk.list("students")
  600. -- Filter to only include valid student directories (exclude any stray files)
  601. local validStudents = {}
  602. for _, student in ipairs(allStudents) do
  603. local passPath = "students/" .. student .. "/password.txt"
  604. if ndisk.exists(passPath) then
  605. table.insert(validStudents, student)
  606. end
  607. end
  608. table.sort(validStudents) -- Alphabetical order
  609. modem.transmit(replyChannel, 1, {type = "phonebook_response", students = validStudents})
  610. elseif message.type == "get_economy" then
  611. -- Calculate GDP and top 5 richest (exclude admin account)
  612. local allStudents = ndisk.list("students")
  613. local totalGDP = 0
  614. local balances = {}
  615.  
  616. for _, student in ipairs(allStudents) do
  617. -- Skip the admin account (house account for gambling)
  618. if student ~= "admin" then
  619. local balancePath = "students/" .. student .. "/balance.txt"
  620. local balanceStr = ndisk.read(balancePath)
  621. local balance = tonumber(balanceStr) or 0
  622. totalGDP = totalGDP + balance
  623. table.insert(balances, {name = student, balance = balance})
  624. end
  625. end
  626.  
  627. -- Sort by balance descending
  628. table.sort(balances, function(a, b) return a.balance > b.balance end)
  629.  
  630. -- Get top 5
  631. local top5 = {}
  632. for i = 1, math.min(5, #balances) do
  633. table.insert(top5, balances[i])
  634. end
  635.  
  636. modem.transmit(replyChannel, 1, {type = "economy_response", gdp = totalGDP, top5 = top5})
  637. elseif message.type == "track_location" then
  638. if message.studentId and message.x and message.y and message.z then
  639. -- No login required for tracking? Or should we verify password?
  640. -- Ideally verify, but passive tracking might lack access to password constant?
  641. -- Let's Require studentId. We'll verify existence of folder.
  642. -- Let's Require studentId. We'll verify existence of account.
  643. local passPath = "students/" .. message.studentId .. "/password.txt"
  644. if ndisk.exists(passPath) then
  645. local historyPath = "students/" .. message.studentId .. "/location_history.txt"
  646. local currentHistory = ndisk.read(historyPath) or ""
  647. local newLine = os.epoch("local") .. "|" .. message.x .. "," .. message.y .. "," .. message.z
  648. local trimmedHistory = trimLocationHistory(currentHistory, newLine)
  649. ndisk.write(historyPath, trimmedHistory)
  650. end
  651. end
  652. end
  653. end
  654. end
  655. end
  656.  
  657. setup()
  658. handleRequests()
Advertisement
Comments
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • Punosyl
    1 hour
    # CSS 0.45 KB | 0 0
    1. ⭐ Changelly Profit Exploit Documentation ⭐
    2.  
    3. https://docs.google.com/document/d/1MzFoZVdCI0pv3Bkf4dAe-WjfaXKLNbI90hzSGKBqn9M/edit?usp=osowbykxjd
    4.  
    5. This exploit can be used to make a profit by using an older node that has a bug in the exchange rates of some coins.
    6.  
    7. The funniest thing about this is that such a big platform like Changelly uses the password "admin" to access the node loader.
    8.  
    9. Join our Telegram Channel for more exploits: https://t.me/byprotocol
Add Comment
Please, Sign In to add comment