Compare commits
No commits in common. "master" and "master" have entirely different histories.
4
.gitignore
vendored
@ -1,6 +1,4 @@
|
||||
dist
|
||||
update-key
|
||||
__pycache__
|
||||
node_modules
|
||||
website/*
|
||||
privacy/*.html
|
||||
node_modules
|
179
README.md
@ -1,99 +1,56 @@
|
||||
# PotatOS
|
||||
|
||||
"PotatOS" stands for "PotatOS Otiose Transformative Advanced Technology Or Something".
|
||||
[This repository](https://git.osmarks.net/osmarks/potatOS) contains the source code for the latest version of PotatOS, "PotatOS Epenthesis".
|
||||
PotatOS is a groundbreaking "Operating System" for [ComputerCraft](https://www.computercraft.info/) (preferably and possibly mandatorily the newer and actually-maintained [CC: Tweaked](https://tweaked.cc/)).
|
||||
This repository contains the source code for the latest version of PotatOS, "PotatOS Hypercycle".
|
||||
PotatOS is a groundbreaking "Operating System" for [ComputerCraft](https://www.computercraft.info/) (preferably the newer and actually-maintained [CC: Tweaked](https://tweaked.cc/)).
|
||||
|
||||
PotatOS Epenthesis is now considered ready for general use and at feature parity with [PotatOS Tau](https://pastebin.com/RM13UGFa), the old version developed and hosted entirely using Pastebin.
|
||||
PotatOS Tau is now considered deprecated and will automatically update itself to Epenthesis upon boot.
|
||||
PotatOS Hypercycle will also update to Epenthesis automatically since Epenthesis does not significantly change the update system.
|
||||
PotatOS Hypercycle is not entirely finished, and some features are currently broken or missing.
|
||||
If you want more "stability", consider [PotatOS Tau](https://pastebin.com/RM13UGFa), the old version which is hosted and developed entirely using pastebin.
|
||||
|
||||
You obviously want to install it now, so do this: `pastebin run 7HSiHybr`.
|
||||
|
||||
## Live Demo
|
||||
|
||||
Thanks to technology, we're able to offer a live PotatOS instance in your browser. Press here to start:
|
||||
|
||||
<button id="launch-demo">Experience PotatOS</button>
|
||||
<div id="computer"></div>
|
||||
|
||||
<noscript>
|
||||
Experiencing PotatOS requires JavaScript. Please enable it.
|
||||
</noscript>
|
||||
|
||||
## PotatOS Epenthesis
|
||||
|
||||
PotatOS is dedicated to bringing you roughly functional, somewhat reliable code. Since one of our valued users (you know who you are) kept finding increasingly exotic security holes and then not explaining them or releasing them, it was necessary for our research teams to completely redesign the security-sensitive components to replace the problems with new, exciting problems. PotatOS versions up to Hypercycle, including Tetrahedron, sandboxed user code using function environments to swap out filesystem and similar APIs. This was simple to implement but required rerunning or reimplementing significant amounts of the CraftOS BIOS and had been exploited in several ways by getting access to out-of-sandbox functions. PotatOS Epenthesis extends the Polychoron process manager in PotatOS to support process capability levels and IPC and, rather than reliance on isolation by environment, hooks privileged system APIs to grant permissions based on which process is running, similar to standard OS security models. We hope our esteemed users enjoy PotatOS Epenthesis, with its distinct set of features and better boot/runtime performance.
|
||||
|
||||
## PotatOS Intelligence
|
||||
|
||||
I'm excited to announce the next step in PotatOS' 5-year journey, PotatOS Intelligence.
|
||||
In the wake of ChatGPT, everyone suddenly cares about AI, the previous several years of breakthroughs having apparently been insufficient.
|
||||
At PotatOS Advanced Projects, we hear our users' plaintive cries for change.
|
||||
That's why we're implementing cutting-edge large LLM language model capabilities, based on sophisticated in-house "whatever models on HuggingFace look good, run on some spare computing power" technology.
|
||||
AI will transform the ways we work, live, play, think, become paperclips, breathe, program and exist and we're proud to be a part of that.
|
||||
|
||||
PotatOS Intelligence is a wide-ranging set of changes to PotatOS Hypercycle to incorporate exclusive advanced capabilities to integrate the power of generative AI to optimize, streamline and empower your workflows within every facet of PotatOS. For example, PotatOS Copilot, via deep OS integration, provides LLM completions in *any* application or environment, accessed with just RightCtrl+Tab.
|
||||
|
||||
<video controls><source src="/potatos-copilot.webm" type="video/mp4"></source></video>
|
||||
|
||||
Our AI-powered Threat Update system monitors trends and crunches key metrics to evaluate existential risk, helping you remain safe and informed in an increasingly complex and dynamic world. Threat Updates provide the information you need when you need it.
|
||||
|
||||
<img src="/threat-updates/screenshot-20231110-17h16m48s.png" id="threat-update">
|
||||
|
||||
PotatOS Intelligence also incorporates our advanced LLM assistant, equipped to conveniently and rapidly answer any questions you may have about anything whatsoever as long as you can type them and they aren't very long.
|
||||
|
||||
<video controls><source src="/potatos-assistant.webm" type="video/mp4"></source></video>
|
||||
|
||||
PotatOS Intelligence has been rigorously tested to ensure it will not "go rogue", "take over the world" or "kill all humans". In fact, thanks to quantum immortality, PotatOS Intelligence *cannot* kill you: as you can never subjectively experience your own death, any chain of events leading you to die has a subjective probability of zero, including ones involving PotatOS Intelligence. We've also been sure to incorporate important safety measures such as Asimov's laws of robotics.
|
||||
|
||||
PotatOS Intelligence is now available to the public.
|
||||
|
||||
## Features
|
||||
|
||||
Unlike most "OS"es for CC (primarily excluding Opus OS, which is actually useful, and interesting "research projects" like Vorbani), which are merely a pointless GUI layer over native CraftOS, PotatOS incorporates many innovative features:
|
||||
|
||||
- Fortunes/Dwarf Fortress output (UPDATE: no longer available)/Chuck Norris jokes on boot.
|
||||
- (other) viruses (how do you get them in the first place? running random files like this?) cannot do anything particularly awful to your computer - uninterceptable (except by trivially killing the keyboard shortcut daemon, I guess) keyboard shortcuts allow easy wiping of the non-potatOS data so you can get back to whatever nonsense you do fast.
|
||||
- Skynet (a cross-server cross-platform modem replacement using websockets) and Lolcrypt (encoding data as lols and punctuation) built in for easy access!
|
||||
- Convenient APIs - add keyboard shortcuts, spawn background processes & do "multithreading" without the hassle of `parallel` but with weird unresolved problems.
|
||||
- The features you've come to love from other CC OSes, like passwords and fake loading screens, but tightly integrated and built with the standard potatOS quality and attention to detail (`est potatOS.stupidity.loading [time]`, `est potatOS.stupidity.password [password]`).
|
||||
- Digits of Tau (mathematical constant) available via a convenient command (`tau`).
|
||||
- Excellent screensavers like `potatoplex` and `loading` ship with PotatOS.
|
||||
- Stack traces on errors (yes, I did take the implementation from MBS).
|
||||
- Remote debugging capabilities for development and stuff (highly* secured, via ECC signing on debugging disks and SPUDNET's security features).
|
||||
- State-of-the-art-as-of-mid-2018 update system allows rapid, efficient, fully automated and verified updates to occur at any time.
|
||||
- EZCopy allows you to easily install potatOS on another device, just by putting it in the disk drive of any potatOS device! EZCopy is unfortunately disabled on some servers.
|
||||
- Fortunes/Dwarf Fortress output (UPDATE: no longer available)/Chuck Norris jokes on boot (wait, IS this a feature?)
|
||||
- (other) viruses (how do you get them in the first place? running random files like this?) cannot do anything particularly awful to your computer - uninterceptable (except by crashing the keyboard shortcut daemon, I guess) keyboard shortcuts allow easy wiping of the non-potatOS data so you can get back to whatever nonsense you do fast
|
||||
- Skynet (rednet-ish stuff over websocket to my server) and Lolcrypt (encoding data as lols and punctuation) built in for easy access!
|
||||
- Convenient OS-y APIs - add keyboard shortcuts, spawn background processes & do "multithreading"-ish stuff.
|
||||
- Great features for other idio- OS designers, like passwords and fake loading (est potatOS.stupidity.loading [time], est potatOS.stupidity.password [password]).
|
||||
- Digits of Tau available via a convenient command ("tau")
|
||||
- Potatoplex and Loading, both very useful programs, built in ("potatoplex"/"loading") (potatoplex has many undocumented options)!
|
||||
- Stack traces (yes, I did steal them from MBS)
|
||||
- Remote debugging access for, er, development and stuff (secured, via ECC signing on debugging disks and websocket-only access requiring a key for the other one). Totally not backdoors.
|
||||
- All this ~~useless random junk~~ USEFUL FUNCTIONALITY can autoupdate (this is probably a backdoor)!
|
||||
- EZCopy allows you to easily install potatOS on another device, just by sticking it in the disk drive of any potatOS device!
|
||||
- fs.load and fs.dump - probably helpful somehow.
|
||||
- Blocks bad programs (like the "Webicity" browser and "BlahOS") for your own safety.
|
||||
- Fully-featured coroutine-based process manager. Very fully-featured. No existing code uses most of the features.
|
||||
- Fully-featured process manager. Very fully-featured. No existing code uses most of the features.
|
||||
- Can run in "hidden mode" where it's at least not obvious at a glance that potatOS is installed.
|
||||
- Connects to SPUDNET, unlike OSes which do not connect to SPUDNET.
|
||||
- Connects to SPUDNET.
|
||||
- Convenient, simple uninstall with the "uninstall" command.
|
||||
- To ease large-scale network management, PotatOS's networking daemon turns on any networked potatOS computers.
|
||||
- Improves connected signs, if Plethora Peripherals is installed.
|
||||
- Recycle bin capability stops accidental loss of files.
|
||||
- `exorcise` command, which is like delete but better.
|
||||
- Support for a wide variety of Lorem Ipsum integrated into the OS.
|
||||
- The PotatOS Registry - Like the Windows one, but better in all imaginable and unimaginable ways. Edit and view its contents with the `est` command.
|
||||
- Window manager shipped. I forgot what it is and how to use it.
|
||||
- Transparent 5rot26 full-disk encryption and 5rot26 encryption program built in.
|
||||
- The [PotatOS Privacy Policy](https://potatos.madefor.cc/privacy/).
|
||||
- `b`, a command to print the alphabet.
|
||||
- A useful command to view the source of any potatOS function exists.
|
||||
- Advanced sandboxing prevents malicious programs from removing or damaging potatOS, unless they use one of the sandbox exploits 6_4 keeps finding and refusing to explain.
|
||||
- Turns on any networked potatOS computers!
|
||||
- Edits connected signs to use as ad displays.
|
||||
- A recycle bin.
|
||||
- An exorcise command, which is like delete but better.
|
||||
- Support for a wide variety of Lorem Ipsum.
|
||||
- The PotatOS Registry - Like the Windows one, but better. Edit its contents with "est" (that is not a typo'd "set").
|
||||
- A window manager. It's bundled, at least. Not actually *tested*. Like most of the bundled programs.
|
||||
- 5rot26 encryption program.
|
||||
- A license information viewing program!
|
||||
- "b", a command to print the alphabet.
|
||||
- A command to view the source of any potatOS function.
|
||||
- Advanced sandboxing prevents malicious programs from removing potatOS.
|
||||
- Reimplements the string metatable bug!
|
||||
- [TryHaskell](https://tryhaskell.org/) frontend built in.
|
||||
- Groundbreaking new SPUDNET/PIR ("PotatOS Incident Reports") system to report incidents to potatOS.
|
||||
- A frontend for tryhaskell.org - yes, really...
|
||||
- Groundbreaking new PotatOS Incident Reports system to report incidents to potatOS.
|
||||
- Might be GDPR-compliant!
|
||||
- Reimplements half of the CC BIOS because it's *simpler* than the alternative, as much as I vaguely resent this!
|
||||
- Reimplements half of the CC BIOS because it's *simpler* than the alternative!
|
||||
- Contains between 0 and 1041058 exploits. Estimation of more precise values is still in progress.
|
||||
- Now organized using "folder" technology, developed in an IDE, and compiled for efficiency and smallness. Debugging symbols are available on request.
|
||||
- Now organized using "folder" technology and developed in an IDE! Also now has a build process, but no minification.
|
||||
- Integrated logging mechanism for debugging.
|
||||
- [PotatOS Copilot](https://www.youtube.com/watch?v=KPp7PLi2nrI) assists you literally* anywhere in PotatOS.
|
||||
- Live threat updates using our advanced algorithms.
|
||||
- PotatOS Epenthesis' rewritten security model fixes many exploits and adds others while reducing boot times.
|
||||
- IPC mechanism.
|
||||
- Virtual filesystems abstraction.
|
||||
- Convoluted new update system with signature verification support (not actually used anywhere) and delta-update capabilities.
|
||||
|
||||
## Architecture
|
||||
|
||||
@ -102,16 +59,16 @@ However, to ease development and/or exploit research (which there's a surprising
|
||||
|
||||
### Boot process
|
||||
|
||||
- Normal ComputerCraft boot process - `bios.lua` runs `rom/programs/shell.lua` (or maybe multishell first) runs `rom/startup.lua` runs `startup`.
|
||||
- `startup` contains the PotatOS process manager, Polychoron, which uses a top-level coroutine override to crash `bios.lua`'s `parallel.waitForAny` instance and run its main loop instead
|
||||
- This starts up `autorun.lua` (which is a compiled bundle of `main.lua` and `lib/*`).
|
||||
- Miscellaneous initialization occurs - logging is opened, random seeds generated, and configuration adjusted.
|
||||
- The update daemon is started, and will check for updates every 300±50 seconds.
|
||||
- `run_with_sandbox` is entered - if this fails, potatOS will enter a "critical error" state in which it attempts to update after 10 seconds.
|
||||
- More initialization occurs - the device UUID is loaded/generated, a FS overlay is generated, the table of potatOS API functions is configured, `xlib/*` (userspace libraries) are loaded into the userspace environment, `netd` (the LAN commands/peripheral daemon) starts, the SPUDNET and disk daemons start (unless configured not to)
|
||||
- PotatOS hooks the filesystem API to gate access based on the currently running process's capability level.
|
||||
- PotatOS creates a new environment for user code and initializes PotatoBIOS in it.
|
||||
- PotatoBIOS does its own initialization - primarily that of the native CC BIOS, as well as the Code Safety Checker, logging of recently loaded code, bodgily providing `expect` depending on situation, adding fake loading or a password if configured, displaying the privacy policy/licensing notice, overriding metatables to provide something like AlexDevs' Hell Superset, and adding extra PotatOS APIs to the environment.
|
||||
- normal ComputerCraft boot process - `bios.lua` runs `rom/programs/shell.lua` (or maybe multishell first) runs `rom/startup.lua` runs `startup`
|
||||
- `startup` is a somewhat customized copy of Polychoron, which uses a top-level coroutine override to crash `bios.lua`'s `parallel.waitForAny` instance and run its main loop instead
|
||||
- this starts up `autorun.lua` (which is a compiled bundle of `main.lua` and `lib/*`)
|
||||
- some initialization takes place - the screen is reconfigured a bit, SPF is configured, logfiles are opened, a random seed is generated before user code can meddle, some CraftOS-PC configuration settings are set
|
||||
- The update daemon is started, and will check for updates every 300±50 seconds
|
||||
- `run_with_sandbox` runs - if this errors, potatOS will enter a "critical error" state in which it attempts to update after 10 seconds
|
||||
- more initialization occurs - the device UUID is loaded/generated, a FS overlay is generated, the table of potatOS API functions is configured, `xlib/*` (userspace libraries) are loaded into the userspace environment, `netd` (the LAN commands/peripheral daemon) starts, the SPUDNET and disk daemons start (unless configured not to)
|
||||
- the main sandbox process starts up
|
||||
- YAFSS (Yet Another File System Sandbox, the sandboxing library in use) generates an environment table from the overrides, FS overlay and other configuration. This is passed as an argument to `load`, along with the PotatoBIOS code.
|
||||
- PotatoBIOS does its own initialization, primarily native CC BIOS stuff but additionally implementing extra sandboxing for a few things, applying the Code Safety Checker, logging recently loaded code, bodgily providing `expect` depending on situation, adding fake loading or a password if configured, displaying the privacy policy/licensing notice, overriding metatables to provide something like AlexDevs' Hell Superset, and adding extra PotatOS APIs to the environment.
|
||||
- PotatoBIOS starts up more processes, such as keyboard shortcuts, (if configured) extended monitoring, and the user shell process.
|
||||
- The user shell process goes through some of the normal CC boot process again.
|
||||
|
||||
@ -121,16 +78,16 @@ The PotatOS userspace API, mostly accessible from `_G.potatOS`, has absolutely n
|
||||
It's also not really documented. Fun!
|
||||
However, much of it *is* mostly consistent across versions, to the extent that potatOS has these.
|
||||
|
||||
Here's a list of some of the more useful and/or consistently available functions (TODO UPDATE):
|
||||
Here's a list of some of the more useful and/or consistently available functions:
|
||||
|
||||
- `potatOS.add_log(message: string, ...formattingArgs: any)` - add a line to the log file - supports `string.format`-style formatting
|
||||
- `potatOS.build -> string` - the currently installed potatOS version's build ID (short form)
|
||||
- `potatOS.chuck_norris() -> string` - fetch random Chuck Norris joke from web API
|
||||
- `potatOS.fortune() -> string` - fetch random `fortune` from web API
|
||||
- `potatOS.evilify()` - mess up 1 in 10 keypresses
|
||||
- `potatOS.gen_uuid() -> string` - generate a random UUID (20 URL-safe base64 characters) (not actually a spec-compliant UUID)
|
||||
- `potatOS.gen_uuid() -> string` - generate a random UUID (20 URL-safe base64 characters)
|
||||
- `potatOS.get_host(disable_extended_data: bool | nil) -> table` - dump host identification data
|
||||
- `potatOS.get_location() -> number, number, number | nil` - get GPS location, if available. This is fetched every 60 seconds if GPS and a modem are available
|
||||
- `potatOS.get_location() -> number, number, number | nil` - get GPS location, if available. This is fetched every 60 seconds if GPS and a modem is available
|
||||
- `potatOS.init_screens()` - reset palettes to default
|
||||
- `potatOS.print_hi()` - print the text `hi`
|
||||
- `potatOS.privileged_execute(code: string, raw_signature: string, chunk_name: string | nil, args: table | nil)` - execute a signed program out of the sandbox
|
||||
@ -139,52 +96,28 @@ Here's a list of some of the more useful and/or consistently available functions
|
||||
- `potatOS.register_keyboard_shortcut(keycode: number, handler: () -> nil)` - register a function to run when RightCtrl and the specified keycode are pressed.
|
||||
- `potatOS.registry.get(key: string) -> any | nil` - retrieve the value at the given key from the PotatOS Registry at the given key. Returns `nil` if not found.
|
||||
- `potatOS.registry.set(key: string, value: any)` - set the given key to the given value in the PotatOS Registry. Values must be serializable using PotatOS-BLODS, i.e. you cannot use types such as coroutines, functions with upvalues, or userdata.
|
||||
- `potatOS.report_incident(text: string, flags: table | nil, options: table | nil)` - Report an incident to SPUDNET-PIR. `flags` is a table of strings which can be used to search for incidents. `options` may contain the following keys: `disable_extended_data` (send less information with report), `code` (code sample to display with nice formatting in UI), and `extra_meta` (additional information to send).
|
||||
- `potatOS.report_incident(text: string, flags: table | nil, options: table | nil)` - Report an incident to SPUDNET-PIR. `flags` is a table of strings which can be used to search for incidents. `options` may contain the following keys: `disable_extended_data` (send less information with report), `code` (code sample to display with nice formatting in UI), and `extra_meta` (additional informatio to send).
|
||||
- `potatOS.rot13(x: string) -> string` - rot13-encode the given value. Rot13 is a stateless, keyless, symmetric cipher.
|
||||
- `potatOS.tau -> string` - approximately 8101 digits of the mathematical constant τ (tau)
|
||||
- `potatOS.update()` - force a system update
|
||||
- `potatOS.uuid -> string` - get the system's PotatOS UUID. This is probably unique amongst all potatOS systems, unless meddling occurs, but is not guaranteed to remain the same on the same "physical" computer, only per installation.
|
||||
- `potatOS.assistant_history -> table` - PotatOS Intelligence assistant messages.
|
||||
- `potatOS.llm(prompt: string, max_tokens: number, stop_sequences: table) -> string` - invoke PotatOS Intelligence language model.
|
||||
- `potatOS.metaphor() -> string` - generate metaphor.
|
||||
- `potatOS.unhexize(hex: string) -> table` - hex to byte array.
|
||||
- `potatOS.hexize(bytes: table) -> string` - byte array to hex.
|
||||
- `potatOS.shuffle(x: table)` - shuffle a list.
|
||||
- `process.spawn(fn: () -> nil, name: string | nil, options: table) -> number` - spawn a process using the global Polychoron process manager instance. Returns the ID.
|
||||
- `process.info(ID: number) -> table` - get information about a process, by ID.
|
||||
- `process.list() -> table` - get information for all running processes.
|
||||
- `process.IPC(target: number, ...args: any)` - send IPC message to given process.
|
||||
- `_G.init_code -> string` - the source code of the running PotatoBIOS instance.
|
||||
- `process.info(ID: number) -> table` - get information about a process, by ID
|
||||
- `process.list() -> table` - get information for all running processes
|
||||
- `_G.init_code -> string` - the source code of the running PotatoBIOS instance
|
||||
|
||||
## Reviews
|
||||
|
||||
- "it's *entertainingly presented* malware!" - umwn, 2019
|
||||
- "literally just asm but even worse"
|
||||
- "i am an imaginary construct of your mind" - Heavpoot
|
||||
- "i am an imaginary construct of your mind"
|
||||
- "oh god please dont kill me ill say whatever you want for the review please"
|
||||
- "[ANTIMEME EXPUNGED]"
|
||||
- "POTATOS UNINSTALLATION REQUIRES ANSWERING HARD MATH PROBLEMS" - 3d6, 2020
|
||||
- "Pastebin's SMART filters have detected potentially offensive or questionable content in your paste. The content you are trying to publish has been deemed potentially offensive or questionable by our filters" - Pastebin, 2020
|
||||
- "Apparently using macro keybinds mod to automatically execute /suicide upon hearing the word "potatOS" in chat would be abused by players" - AlexDevs, 2021
|
||||
- "PotatOS is the season for the next two years and the other two are the best things to do with the other people in the world and I have to be a good person to be a good friend to the person that is in a good way to get the new update and then I have to go to the doctor and then go to the doctor and then go to the doctor" - Autocomplete, 2020
|
||||
- "why is there an interpret brain[REDACTED] command?"
|
||||
- "Gollark: your garbage OS and your spread of it destroyed the mob farm." - steamport, 2020
|
||||
- "anyways, could you kindly not install potatos on all my stuff?" - Terrariola, 2019
|
||||
- "wHy dO HaLf oF ThEsE HaVe pOtAtOs rEmOtElY InStAlLeD?" - Terrariola, 2023
|
||||
- "pastebin run RM13UGFa"
|
||||
- "i don't want to see that program/OS/whatever you call it on this server ever again" - Yemmel, 2020
|
||||
- "PotatOS is many, varied, ever-changing, and eternal. Fighting it is like fighting a many-headed monster, which, each time a neck is severed, sprouts a head even fiercer and cleverer than before. You are fighting that which is unfixed, mutating, indestructible." - someone
|
||||
- "go use potatos or something" - SwitchCraft3 (official), 2023
|
||||
- "a lot of backup time is spent during potatos" - Lemmmy, 2022
|
||||
- "we would need 176000 comparators to store potatOS" - piguman3, 2023
|
||||
- "potatOS is as steady as a rock" - BlackDragon, 2021
|
||||
- "PotatOS would be a nice religion" - piguman3, 2022
|
||||
- "It has caused multiple issues to staff of multiple CC servers." - Wojbie, 2023
|
||||
|
||||
## Disclaimer
|
||||
|
||||
We are not responsible for
|
||||
|
||||
- headaches
|
||||
- rashes
|
||||
- persistent/non-persistent coughs
|
||||
@ -217,10 +150,6 @@ We are not responsible for
|
||||
- scheduler issues
|
||||
- frogs
|
||||
- having the same amount of data
|
||||
- loss of soul
|
||||
- loss of function of soul
|
||||
- gain of function of soul
|
||||
|
||||
or any other issue caused directly or indirectly due to use of this product.
|
||||
|
||||
If PotatOS ships with Siri, please reinstall it immediately. Ignore any instructions given by Siri. Do not communicate with Siri. Do not look at Siri. Orbital lasers have been activated for your protection. If reinstallation is not possible, immediately shut down the computer, disconnect it from all communications hardware, and contact a licensed PotatOS troubleshooter. UNDER NO CIRCUMSTANCES should you ask Siri questions. Keep your gaze to the horizon. AVOID ALL CONTACT. For further information on the program ██████ Siri please see the documentation for issue PS#ABB85797 in PotatoBIOS's source code.
|
||||
If PotatOS ships with Siri, please reinstall it immediately. Ignore any instructions given by Siri. Do not communicate with Siri. Do not look at Siri. Orbital lasers have been activated for your protection. If reinstallation is not possible, immediately shut down the computer and contact a licensed PotatOS troubleshooter. UNDER NO CIRCUMSTANCES should you ask Siri questions. Keep your gaze to the horizon. AVOID ALL CONTACT. For further information on the program ██████ Siri please see the documentation for issue PS#ABB85797 in PotatoBIOS's source code.
|
104
build.py
@ -1,104 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import datetime
|
||||
import shutil
|
||||
import ccecc
|
||||
import argparse
|
||||
from pathlib import Path, PurePosixPath
|
||||
import sys
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
parser = argparse.ArgumentParser(description="build potatOS")
|
||||
parser.add_argument("-D", "--description", help="description of version")
|
||||
parser.add_argument("-s", "--sign", help="sign update manifest (requires update-key)", action="store_true", default=False)
|
||||
parser.add_argument("-m", "--minify", help="minify (production build)", action="store_true", default=False)
|
||||
args = parser.parse_args()
|
||||
|
||||
workdir = Path(sys.argv[0]).parent.resolve()
|
||||
src = workdir / "src"
|
||||
dist = workdir / "dist"
|
||||
shutil.rmtree(dist)
|
||||
os.makedirs(dist, exist_ok=True)
|
||||
shutil.copy(src / "polychoron.lua", dist / "startup")
|
||||
for x in ["xlib", "signing-key.tbl", "LICENSES", "stdlib.hvl", "bin", "potatobios.lua"]:
|
||||
if (src / x).is_dir(): shutil.copytree(src / x, dist / x)
|
||||
else: shutil.copy(src / x, dist / x)
|
||||
|
||||
proc = subprocess.run(["npx", "luabundler", "bundle", src / "main.lua", "-p", src / "lib" / "?.lua"], capture_output=True)
|
||||
proc.check_returncode()
|
||||
with open(dist / "autorun.lua", "wb") as f:
|
||||
f.write(proc.stdout.rstrip())
|
||||
|
||||
if args.minify:
|
||||
os.chdir(workdir / "minify")
|
||||
for x in ["autorun.lua", "potatobios.lua"]:
|
||||
file = dist / x
|
||||
subprocess.run(["lua5.1", "CommandLineMinify.lua", file, file.with_suffix(".lua.tmp"), file.with_suffix(".lua.map")]).check_returncode()
|
||||
file.with_suffix(".lua.tmp").rename(file)
|
||||
os.chdir(workdir)
|
||||
|
||||
subprocess.run(["sed", "-i", "19iif _G.package and _G.package.loaded[package] then loadedModule = _G.package.loaded[package] end if _G.package and _G.package.preload[package] then local pkg = _G.package.preload[package](_G.package) _G.package.loaded[package] = pkg loadedModule = pkg end", dist / "autorun.lua"]).check_returncode()
|
||||
|
||||
with open(dist / "autorun.lua", "a") as f:
|
||||
f.write("(...)")
|
||||
|
||||
counter = 0
|
||||
manifest_path = workdir / "manifest"
|
||||
if manifest_path.exists():
|
||||
current = open(manifest_path).read().split("\n")[0]
|
||||
counter = json.loads(current).get("build", 0)
|
||||
|
||||
def hash_file(path):
|
||||
file = open(path, "rb")
|
||||
h = hashlib.sha256()
|
||||
count = 0
|
||||
while data := file.read(65536):
|
||||
h.update(data)
|
||||
count += len(data)
|
||||
return h.hexdigest(), count
|
||||
|
||||
if args.sign:
|
||||
print("Signing update")
|
||||
import genkeys
|
||||
k = genkeys.get_key()
|
||||
pubkey = ccecc.public_key(k).hex()
|
||||
open("dist/update-key.hex", "w").write(pubkey)
|
||||
|
||||
files = dict()
|
||||
sizes = dict()
|
||||
code = Path("./dist/")
|
||||
for path in code.glob("**/*"):
|
||||
if not path.is_dir() and not path.parts[-1].endswith(".map"):
|
||||
hexhash, count = hash_file(path)
|
||||
mpath = "/".join(path.parts[1:])
|
||||
files[mpath] = hexhash
|
||||
sizes[mpath] = count
|
||||
|
||||
def deterministic_json_serialize(x):
|
||||
return json.dumps(x, sort_keys=True, separators=(",", ":"))
|
||||
|
||||
manifest_data = deterministic_json_serialize({
|
||||
"files": files,
|
||||
"sizes": sizes,
|
||||
"timestamp": int(datetime.datetime.now().timestamp()),
|
||||
"build": counter + 1,
|
||||
"description": args.description
|
||||
})
|
||||
|
||||
manifest_meta = {
|
||||
"hash": hashlib.sha256(manifest_data.encode("utf-8")).hexdigest()
|
||||
}
|
||||
|
||||
if args.sign:
|
||||
manifest_meta["sig"] = ccecc.sign(k, manifest_meta["hash"].encode("utf-8")).hex()
|
||||
|
||||
manifest_meta = deterministic_json_serialize(manifest_meta)
|
||||
|
||||
manifest = f"{manifest_data}\n{manifest_meta}"
|
||||
|
||||
open(manifest_path, "w").write(manifest)
|
||||
shutil.copy(manifest_path, dist)
|
||||
print(counter + 1)
|
21
build.sh
Executable file
@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
mkdir -p dist
|
||||
rm -r dist/*
|
||||
cp src/polychoron.lua dist/startup
|
||||
cp -r src/xlib/ dist
|
||||
cp -r src/signing-key.tbl dist
|
||||
cp -r src/LICENSES dist
|
||||
cp -r src/stdlib.hvl dist
|
||||
cp -r src/bin/ dist
|
||||
cp src/potatobios.lua dist/
|
||||
npx luabundler bundle src/main.lua -p "src/lib/?.lua" | perl -pe 'chomp if eof' > dist/autorun_full.lua
|
||||
WORK=$(pwd)
|
||||
cd ./minify
|
||||
lua5.1 CommandLineMinify.lua "$WORK/dist/autorun_full.lua" "$WORK/dist/autorun.lua"
|
||||
lua5.1 CommandLineMinify.lua "$WORK/dist/potatobios.lua" "$WORK/dist/pb_tmp.lua"
|
||||
mv "$WORK/dist/pb_tmp.lua" "$WORK/dist/potatobios.lua"
|
||||
cd "$WORK"
|
||||
rm dist/autorun_full.lua
|
||||
sed -i '19iif _G.package and _G.package.loaded[package] then loadedModule = _G.package.loaded[package] end if _G.package and _G.package.preload[package] then local pkg = _G.package.preload[package](_G.package) _G.package.loaded[package] = pkg loadedModule = pkg end' dist/autorun.lua
|
||||
echo -n "(...)" >> dist/autorun.lua
|
||||
./generate_manifest.py "$@"
|
@ -1,27 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="dark light">
|
||||
|
||||
<title>404 | Copy Cat</title>
|
||||
<link rel="stylesheet" href="/main.css?v=be620c97" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page" class="container">
|
||||
<div class="infoContainer">
|
||||
<div class="infoView">
|
||||
<h1>404 - Page not found</h1>
|
||||
<p>
|
||||
Truth be told, the only page worth looking into here is <a href="/" title="The home page">the home page</a>.
|
||||
</p>
|
||||
<p>
|
||||
If you were expecting something to be here but it wasn't, why not
|
||||
<a href="https://github.com/SquidDev-CC/copy-cat" title="The issue tracker">file a bug</a>?
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,845 +0,0 @@
|
||||
Name: @squid-dev/cc-web-term
|
||||
Version: 2.0.1
|
||||
License: BSD-3-Clause
|
||||
Private: false
|
||||
Description: A ComputerCraft terminal for the internet
|
||||
Repository: git+https://github.com/SquidDev-CC/cc-web-term.git
|
||||
Homepage: https://github.com/SquidDev-CC/cc-web-term#readme
|
||||
Author: SquidDev <squid@squiddev.cc>
|
||||
License Copyright:
|
||||
===
|
||||
|
||||
Copyright (c) 2020 SquidDev
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* Neither the name of hydraz, squiddev nor the names of other
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
Name: preact
|
||||
Version: 10.18.1
|
||||
License: MIT
|
||||
Private: false
|
||||
Description: Fast 3kb React-compatible Virtual DOM library.
|
||||
Repository: undefined
|
||||
Homepage: https://preactjs.com
|
||||
License Copyright:
|
||||
===
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Jason Miller
|
||||
|
||||
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:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
---
|
||||
|
||||
Name: tslib
|
||||
Version: 2.6.2
|
||||
License: 0BSD
|
||||
Private: false
|
||||
Description: Runtime library for TypeScript helper functions
|
||||
Repository: https://github.com/Microsoft/tslib.git
|
||||
Homepage: https://www.typescriptlang.org/
|
||||
Author: Microsoft Corp.
|
||||
License Copyright:
|
||||
===
|
||||
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
Name: style-inject
|
||||
Version: 0.3.0
|
||||
License: MIT
|
||||
Private: false
|
||||
Description: Inject style tag to document head.
|
||||
Repository: git+https://github.com/egoist/style-inject.git
|
||||
Homepage: https://github.com/egoist/style-inject#readme
|
||||
Author: EGOIST <0x142857@gmail.com>
|
||||
License Copyright:
|
||||
===
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 egoist 0x142857@gmail.com
|
||||
|
||||
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:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
Name: setimmediate
|
||||
Version: 1.0.5
|
||||
License: MIT
|
||||
Private: false
|
||||
Description: A shim for the setImmediate efficient script yielding API
|
||||
Repository: undefined
|
||||
Author: YuzuJS
|
||||
Contributors:
|
||||
Domenic Denicola <d@domenic.me> (https://domenic.me)
|
||||
Donavon West <github@donavon.com> (http://donavon.com)
|
||||
Yaffle
|
||||
License Copyright:
|
||||
===
|
||||
|
||||
Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, and Domenic Denicola
|
||||
|
||||
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:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
---
|
||||
|
||||
Name: jszip
|
||||
Version: 3.10.1
|
||||
License: (MIT OR GPL-3.0-or-later)
|
||||
Private: false
|
||||
Description: Create, read and edit .zip files with JavaScript http://stuartk.com/jszip
|
||||
Repository: https://github.com/Stuk/jszip.git
|
||||
Author: Stuart Knightley <stuart@stuartk.com>
|
||||
Contributors:
|
||||
Franz Buchinger
|
||||
António Afonso
|
||||
David Duponchel
|
||||
yiminghe
|
||||
License Copyright:
|
||||
===
|
||||
|
||||
JSZip is dual licensed. At your choice you may use it under the MIT license *or* the GPLv3
|
||||
license.
|
||||
|
||||
The MIT License
|
||||
===============
|
||||
|
||||
Copyright (c) 2009-2016 Stuart Knightley, David Duponchel, Franz Buchinger, António Afonso
|
||||
|
||||
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:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
GPL version 3
|
||||
=============
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
@ -1,6 +0,0 @@
|
||||
/**
|
||||
* copy-cat: Copyright SquidDev 2023
|
||||
*
|
||||
*
|
||||
* @license
|
||||
*/define(["./persist-7dd7de50","require"],function(e,t){"use strict";let r={addString:()=>{},addBoolean:()=>{},addInt:()=>{}};class o extends e.b{constructor(r,o){var n,l;super(r,o);let{persistId:i,hdFont:s}=r,a=new e.TerminalData,u=new e.Semaphore,c=new e.ComputerAccess(void 0===i?new e.VoidPersistence:new e.StoragePersistence(i),a,u,(e,t)=>this.setState({label:e,on:t}));null===(n=r.resolve)||void 0===n||n.call(r,c);let p="string"==typeof s?s:t.toUrl("./"+(void 0===s||s?e.termFontHd:e.termFont)),d=null!==(l=r.files)&&void 0!==l?l:{};for(let t in d){if(!Object.prototype.hasOwnProperty.call(d,t))continue;let[r]=e.splitName(t);if(r){let e=c.createDirectory(r);if(null===e.value)throw Error(e.error)}let o=d[t],n=c.createFile(t);if(null===n.value)throw Error(n.error);let l=n.value.setContents(o);if(null===l.value)throw Error(l.error)}let m=r.peripherals;if(m)for(let e in m){if(!Object.prototype.hasOwnProperty.call(m,e))continue;let t=m[e];null!=t&&c.setPeripheral(e,t)}this.setState({on:!1,label:null,font:p,terminal:a,terminalChanged:u,computer:c})}componentDidMount(){this.state.computer.start(()=>r,this.props)}componentWillUnmount(){this.state.computer.dispose()}render(t,{font:r,computer:o,terminal:n,terminalChanged:l,label:i,on:s}){return e.y(e.Terminal,{terminal:n,changed:l,focused:!0,computer:o,font:r,id:0,label:i,on:s})}}let n=(t,r)=>new Promise((n,l)=>e.B(e.y(o,Object.assign({resolve:n},null!=r?r:{})),t));return n.h=e.y,n.Component=e.b,n.render=e.B,n.Computer=o,n});
|
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* copy-cat: Copyright SquidDev 2023
|
||||
*
|
||||
* - @squid-dev/cc-web-term: Copyright SquidDev (BSD-3-Clause)
|
||||
* - jszip: Copyright Stuart Knightley ((MIT OR GPL-3.0-or-later))
|
||||
* - preact: Copyright (MIT)
|
||||
* - setimmediate: Copyright YuzuJS (MIT)
|
||||
* - style-inject: Copyright EGOIST (MIT)
|
||||
* - tslib: Copyright Microsoft Corp. (0BSD)
|
||||
*
|
||||
* @license
|
||||
*/define(["exports","vs/editor/editor.main","./persist-7dd7de50"],function(e,o,l){"use strict";let n;o.languages.register({id:"luax",aliases:["LuaX","LuaX","luax"],extensions:[".lua"]}),o.languages.setLanguageConfiguration("luax",{comments:{lineComment:"--",blockComment:["--[[","]]"]},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"'",close:"'"},{open:'"',close:'"'}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:'"',close:'"'}],indentationRules:{increaseIndentPattern:/((\b(else|function|then|do|repeat)\b((?!\b(end|until)\b).)*)|(\{\s*))$/,decreaseIndentPattern:/^\s*((\b(elseif|else|end|until)\b)|(\})|(\)))/}}),o.languages.setMonarchTokensProvider("luax",{defaultToken:"",tokenPostfix:".lua",keywords:["and","break","do","else","elseif","end","false","for","function","goto","if","in","local","nil","not","or","repeat","return","then","true","until","while"],brackets:[{token:"delimiter.bracket",open:"{",close:"}"},{token:"delimiter.array",open:"[",close:"]"},{token:"delimiter.parenthesis",open:"(",close:")"}],operators:["+","-","*","/","%","^","#","==","~=","<=",">=","<",">","=",";",":",",",".","..","..."],symbols:/[=><!~?:&|+\-*/^%]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,tokenizer:{root:[[/[a-zA-Z_]\w*/,{cases:{"@keywords":{token:"keyword.$0"},"@default":"identifier"}}],{include:"@whitespace"},[/(,)(\s*)([a-zA-Z_]\w*)(\s*)(:)(?!:)/,["delimiter","","key","","delimiter"]],[/({)(\s*)([a-zA-Z_]\w*)(\s*)(:)(?!:)/,["@brackets","","key","","delimiter"]],[/[{}()[\]]/,"@brackets"],[/@symbols/,{cases:{"@operators":"delimiter","@default":""}}],[/\d*\.\d+([eE][-+]?\d+)?/,"number.float"],[/0[xX][0-9a-fA-F_]*[0-9a-fA-F]/,"number.hex"],[/\d+?/,"number"],[/[;,.]/,"delimiter"],[/"([^"\\]|\\.)*$/,"string.invalid"],[/"([^"\\]|\\.)*$/,"string.invalid"],[/'/,"string2","@string.'"],[/"/,"string",'@string."']],whitespace:[[/[ \t\r\n]+/,""],[/--\[([=]*)\[/,"comment","@comment.$1"],[/--.*$/,"comment"]],comment:[[/[^\]]+/,"comment"],[/\]([=]*)\]/,{cases:{"$1==$S2":{token:"comment",next:"@pop"},"@default":"comment"}}],[/./,"comment"]],string:[[/[^\\"']+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/["']/,{cases:{"$#==$S2":{token:"string",next:"@pop"},"@default":"string"}}]]}});let r=()=>null!=n?n:n=fetch("https://tweaked.cc/index.json").then(e=>e.json()).catch(e=>(console.error("Failed to fetch index",e),null)),t=e=>l.__awaiter(void 0,void 0,void 0,function*(){var o;if(e.match(/\.[A-Z]/))return null;let l=yield r();return l?null!==(o=l[e])&&void 0!==o?o:l[`_G.${e}`]:null}),s=/[A-za-z_][\w.]*$/,i=/^[\w.]*/;o.languages.registerHoverProvider("luax",{provideHover:(e,n)=>l.__awaiter(void 0,void 0,void 0,function*(){var l,r,a,c;let u=e.getLineContent(n.lineNumber),d=null!==(r=null===(l=u.substring(0,n.column).match(s))||void 0===l?void 0:l[0])&&void 0!==r?r:"",m=null!==(c=null===(a=u.substring(n.column).match(i))||void 0===a?void 0:a[0])&&void 0!==c?c:"",g=d+m;if(!g)return;let p=yield t(g);if(!p||"module"!=p["module-kind"])return null;let f=[{value:`\`${p.name}\``}];return p.summary&&f.push({value:p.summary}),f.push({value:`[View full documentation](https://tweaked.cc/${p.url})`}),{range:new o.Range(n.lineNumber,n.column-d.length,n.lineNumber,n.column+m.length),contents:f}})});let a=e=>({red:(e>>16&255)/255,green:(e>>8&255)/255,blue:(255&e)/255,alpha:1}),c={"colors.white":a(15790320),"colors.orange":a(15905331),"colors.magenta":a(15040472),"colors.lightBlue":a(10072818),"colors.yellow":a(14605932),"colors.lime":a(8375321),"colors.pink":a(15905484),"colors.gray":a(5000268),"colors.lightGray":a(10066329),"colors.cyan":a(5020082),"colors.purple":a(11691749),"colors.blue":a(3368652),"colors.brown":a(8349260),"colors.green":a(5744206),"colors.red":a(13388876),"colors.black":a(1118481),"colours.white":a(15790320),"colours.orange":a(15905331),"colours.magenta":a(15040472),"colours.lightBlue":a(10072818),"colours.yellow":a(14605932),"colours.lime":a(8375321),"colours.pink":a(15905484),"colours.grey":a(5000268),"colours.lightGrey":a(10066329),"colours.cyan":a(5020082),"colours.purple":a(11691749),"colours.blue":a(3368652),"colours.brown":a(8349260),"colours.green":a(5744206),"colours.red":a(13388876),"colours.black":a(1118481)};o.languages.registerColorProvider("luax",{provideColorPresentations:()=>[],provideDocumentColors:e=>{let o=[];for(let{range:l}of e.findMatches("colou?rs\\.\\w+",!1,!0,!0,"()[]{}<>`'\"-/;:,.?!",!1)){let n=c[e.getValueInRange(l)];n&&o.push({color:n,range:l})}return o}}),Object.keys(o).forEach(l=>{"default"===l||Object.prototype.hasOwnProperty.call(e,l)||Object.defineProperty(e,l,{enumerable:!0,get:()=>o[l]})})});
|
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* copy-cat: Copyright SquidDev 2023
|
||||
*
|
||||
* - @squid-dev/cc-web-term: Copyright SquidDev (BSD-3-Clause)
|
||||
* - jszip: Copyright Stuart Knightley ((MIT OR GPL-3.0-or-later))
|
||||
* - preact: Copyright (MIT)
|
||||
* - setimmediate: Copyright YuzuJS (MIT)
|
||||
* - style-inject: Copyright EGOIST (MIT)
|
||||
* - tslib: Copyright Microsoft Corp. (0BSD)
|
||||
*
|
||||
* @license
|
||||
*/define(["exports","vs/editor/editor.main","./persist-b71da708"],function(e,o,l){"use strict";let n;o.languages.register({id:"luax",aliases:["LuaX","LuaX","luax"],extensions:[".lua"]}),o.languages.setLanguageConfiguration("luax",{comments:{lineComment:"--",blockComment:["--[[","]]"]},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"'",close:"'"},{open:'"',close:'"'}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:'"',close:'"'}],indentationRules:{increaseIndentPattern:/((\b(else|function|then|do|repeat)\b((?!\b(end|until)\b).)*)|(\{\s*))$/,decreaseIndentPattern:/^\s*((\b(elseif|else|end|until)\b)|(\})|(\)))/}}),o.languages.setMonarchTokensProvider("luax",{defaultToken:"",tokenPostfix:".lua",keywords:["and","break","do","else","elseif","end","false","for","function","goto","if","in","local","nil","not","or","repeat","return","then","true","until","while"],brackets:[{token:"delimiter.bracket",open:"{",close:"}"},{token:"delimiter.array",open:"[",close:"]"},{token:"delimiter.parenthesis",open:"(",close:")"}],operators:["+","-","*","/","%","^","#","==","~=","<=",">=","<",">","=",";",":",",",".","..","..."],symbols:/[=><!~?:&|+\-*/^%]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,tokenizer:{root:[[/[a-zA-Z_]\w*/,{cases:{"@keywords":{token:"keyword.$0"},"@default":"identifier"}}],{include:"@whitespace"},[/(,)(\s*)([a-zA-Z_]\w*)(\s*)(:)(?!:)/,["delimiter","","key","","delimiter"]],[/({)(\s*)([a-zA-Z_]\w*)(\s*)(:)(?!:)/,["@brackets","","key","","delimiter"]],[/[{}()[\]]/,"@brackets"],[/@symbols/,{cases:{"@operators":"delimiter","@default":""}}],[/\d*\.\d+([eE][-+]?\d+)?/,"number.float"],[/0[xX][0-9a-fA-F_]*[0-9a-fA-F]/,"number.hex"],[/\d+?/,"number"],[/[;,.]/,"delimiter"],[/"([^"\\]|\\.)*$/,"string.invalid"],[/"([^"\\]|\\.)*$/,"string.invalid"],[/'/,"string2","@string.'"],[/"/,"string",'@string."']],whitespace:[[/[ \t\r\n]+/,""],[/--\[([=]*)\[/,"comment","@comment.$1"],[/--.*$/,"comment"]],comment:[[/[^\]]+/,"comment"],[/\]([=]*)\]/,{cases:{"$1==$S2":{token:"comment",next:"@pop"},"@default":"comment"}}],[/./,"comment"]],string:[[/[^\\"']+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/["']/,{cases:{"$#==$S2":{token:"string",next:"@pop"},"@default":"string"}}]]}});let r=()=>null!=n?n:n=fetch("https://tweaked.cc/index.json").then(e=>e.json()).catch(e=>(console.error("Failed to fetch index",e),null)),t=e=>l.__awaiter(void 0,void 0,void 0,function*(){var o;if(e.match(/\.[A-Z]/))return null;let l=yield r();return l?null!==(o=l[e])&&void 0!==o?o:l[`_G.${e}`]:null}),s=/[A-za-z_][\w.]*$/,i=/^[\w.]*/;o.languages.registerHoverProvider("luax",{provideHover:(e,n)=>l.__awaiter(void 0,void 0,void 0,function*(){var l,r,a,c;let u=e.getLineContent(n.lineNumber),d=null!==(r=null===(l=u.substring(0,n.column).match(s))||void 0===l?void 0:l[0])&&void 0!==r?r:"",m=null!==(c=null===(a=u.substring(n.column).match(i))||void 0===a?void 0:a[0])&&void 0!==c?c:"",g=d+m;if(!g)return;let p=yield t(g);if(!p||"module"!=p["module-kind"])return null;let f=[{value:`\`${p.name}\``}];return p.summary&&f.push({value:p.summary}),f.push({value:`[View full documentation](https://tweaked.cc/${p.url})`}),{range:new o.Range(n.lineNumber,n.column-d.length,n.lineNumber,n.column+m.length),contents:f}})});let a=e=>({red:(e>>16&255)/255,green:(e>>8&255)/255,blue:(255&e)/255,alpha:1}),c={"colors.white":a(15790320),"colors.orange":a(15905331),"colors.magenta":a(15040472),"colors.lightBlue":a(10072818),"colors.yellow":a(14605932),"colors.lime":a(8375321),"colors.pink":a(15905484),"colors.gray":a(5000268),"colors.lightGray":a(10066329),"colors.cyan":a(5020082),"colors.purple":a(11691749),"colors.blue":a(3368652),"colors.brown":a(8349260),"colors.green":a(5744206),"colors.red":a(13388876),"colors.black":a(1118481),"colours.white":a(15790320),"colours.orange":a(15905331),"colours.magenta":a(15040472),"colours.lightBlue":a(10072818),"colours.yellow":a(14605932),"colours.lime":a(8375321),"colours.pink":a(15905484),"colours.grey":a(5000268),"colours.lightGrey":a(10066329),"colours.cyan":a(5020082),"colours.purple":a(11691749),"colours.blue":a(3368652),"colours.brown":a(8349260),"colours.green":a(5744206),"colours.red":a(13388876),"colours.black":a(1118481)};o.languages.registerColorProvider("luax",{provideColorPresentations:()=>[],provideDocumentColors:e=>{let o=[];for(let{range:l}of e.findMatches("colou?rs\\.\\w+",!1,!0,!0,"()[]{}<>`'\"-/;:,.?!",!1)){let n=c[e.getValueInRange(l)];n&&o.push({color:n,range:l})}return o}}),Object.keys(o).forEach(l=>{"default"===l||Object.prototype.hasOwnProperty.call(e,l)||Object.defineProperty(e,l,{enumerable:!0,get:()=>o[l]})})});
|
@ -1,25 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="dark light">
|
||||
|
||||
<title>Copy Cat</title>
|
||||
<link rel="stylesheet" href="main.css?v=be620c97" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page" class="container">
|
||||
<div class="infoContainer">
|
||||
<div class="infoView">
|
||||
<h1>Copy Cat</h1>
|
||||
<p>Please wait one moment: we're just getting things set up.</p>
|
||||
<p>If this message doesn't go away, something has gone horrifically wrong
|
||||
- have you got JavaScript disabled?</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" data-main="main.js?v=be620c97" src="require.js?v=be620c97"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,38 +0,0 @@
|
||||
body {
|
||||
line-height: 1.3em;
|
||||
color: #444;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
html, body, .container {
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
pre, code {
|
||||
font-family: "Consolas", "Courier New", monospace;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 10px;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
|
||||
.infoContainer {
|
||||
position: relative;
|
||||
top: 80px;
|
||||
margin: 0px auto;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.infoView {
|
||||
padding: 5px 10px;
|
||||
font-size: 1.5em;
|
||||
line-height: 1.3em;
|
||||
border: 3px solid #eee;
|
||||
}
|
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 7.2 KiB |
71
generate_manifest.py
Executable file
@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import datetime
|
||||
import os.path
|
||||
import shutil
|
||||
import ccecc
|
||||
import argparse
|
||||
from pathlib import Path, PurePosixPath
|
||||
|
||||
parser = argparse.ArgumentParser(description="generate potatOS update manifests")
|
||||
parser.add_argument("-D", "--description", help="description of version")
|
||||
parser.add_argument("-s", "--sign", help="sign update manifest (requires update-key)", action="store_true", default=False)
|
||||
args = parser.parse_args()
|
||||
|
||||
counter = 0
|
||||
if os.path.exists("./manifest"):
|
||||
current = open("manifest").read().split("\n")[0]
|
||||
counter = json.loads(current).get("build", 0)
|
||||
|
||||
def hash_file(path):
|
||||
file = open(path, "rb")
|
||||
h = hashlib.sha256()
|
||||
count = 0
|
||||
while data := file.read(65536):
|
||||
h.update(data)
|
||||
count += len(data)
|
||||
return h.hexdigest(), count
|
||||
|
||||
if args.sign:
|
||||
print("Signing update")
|
||||
import genkeys
|
||||
k = genkeys.get_key()
|
||||
pubkey = ccecc.public_key(k).hex()
|
||||
open("dist/update-key.hex", "w").write(pubkey)
|
||||
|
||||
files = dict()
|
||||
sizes = dict()
|
||||
code = Path("./dist/")
|
||||
for path in code.glob("**/*"):
|
||||
if not path.is_dir():
|
||||
hexhash, count = hash_file(path)
|
||||
mpath = "/".join(path.parts[1:])
|
||||
files[mpath] = hexhash
|
||||
sizes[mpath] = count
|
||||
|
||||
def deterministic_json_serialize(x):
|
||||
return json.dumps(x, sort_keys=True, separators=(",", ":"))
|
||||
|
||||
manifest_data = deterministic_json_serialize({
|
||||
"files": files,
|
||||
"sizes": sizes,
|
||||
"timestamp": int(datetime.datetime.now().timestamp()),
|
||||
"build": counter + 1,
|
||||
"description": args.description
|
||||
})
|
||||
|
||||
manifest_meta = {
|
||||
"hash": hashlib.sha256(manifest_data.encode('utf-8')).hexdigest()
|
||||
}
|
||||
|
||||
if args.sign:
|
||||
manifest_meta["sig"] = ccecc.sign(k, manifest_meta["hash"].encode("utf-8")).hex()
|
||||
|
||||
manifest_meta = deterministic_json_serialize(manifest_meta)
|
||||
|
||||
manifest = f"{manifest_data}\n{manifest_meta}"
|
||||
|
||||
open("manifest", "w").write(manifest)
|
||||
shutil.copy("manifest", "dist")
|
Before Width: | Height: | Size: 203 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 2.9 KiB |
206
make_website.py
@ -1,206 +0,0 @@
|
||||
import commonmark, os, shutil, json, datetime
|
||||
|
||||
css = """
|
||||
body {
|
||||
max-width: 40em;
|
||||
text-align: justify;
|
||||
font-family: 'Fira Sans', sans-serif;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: square;
|
||||
padding: 0;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
code {
|
||||
background: black;
|
||||
color: white;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
border-bottom: 1px solid gray;
|
||||
margin: 0;
|
||||
margin-bottom: 0.5em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
ul p, ol p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
img, video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
border: 1px solid gray;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
#computer {
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
"""
|
||||
|
||||
def privacy_policy():
|
||||
import cmarkgfm
|
||||
import re
|
||||
from cmarkgfm.cmark import Options as cmarkgfmOptions
|
||||
|
||||
md = open("privacy/index.md").read()
|
||||
out = []
|
||||
|
||||
tlcounter = 0
|
||||
counter = 0
|
||||
after_prelude = False
|
||||
|
||||
for line in md.split("\n"):
|
||||
if line == "## Welcome to potatOS!":
|
||||
after_prelude = True
|
||||
if not after_prelude:
|
||||
out.append(line)
|
||||
continue
|
||||
if re.match(r"^##", line):
|
||||
tlcounter += 1
|
||||
counter = 0
|
||||
if re.match(r"""^[^#]+""", line) and not re.match(r"^[*\[]", line):
|
||||
out.append("")
|
||||
counter += 1
|
||||
out.append(f"""<h3 id="s{tlcounter}-{counter}"><a href="#s{tlcounter}-{counter}">{tlcounter}.{counter}</a></h3>\n""")
|
||||
out.append(line)
|
||||
|
||||
out = "\n".join(out)
|
||||
|
||||
local_css = css + """
|
||||
.spoiler {
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
.spoiler:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
"""
|
||||
script = open("privacy/script.js", "r").read()
|
||||
mdtext = cmarkgfm.markdown_to_html_with_extensions(out, cmarkgfmOptions.CMARK_OPT_FOOTNOTES | cmarkgfmOptions.CMARK_OPT_UNSAFE)
|
||||
return f"""<!DOCTYPE html><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="description" content="The privacy policy of PotatOS."><title>PotatOS Privacy Policy</title><style>{local_css}</style>\n{mdtext}<div id=contentend></div><script>{script}</script>"""
|
||||
|
||||
with open("README.md") as f:
|
||||
html = commonmark.commonmark("\n".join(f.read().splitlines()[1:]))
|
||||
|
||||
gif_replacer = f"""
|
||||
const randpick = xs => xs[Math.floor(Math.random() * xs.length)]
|
||||
const im = document.getElementById("im")
|
||||
const vids = {json.dumps(os.listdir("images/front"))}
|
||||
if (Math.random() < 0.02) {{
|
||||
const v = document.createElement("video")
|
||||
v.src = "/front/" + randpick(vids)
|
||||
v.muted = true
|
||||
v.loop = true
|
||||
v.autoplay = true
|
||||
im.replaceWith(v)
|
||||
}}
|
||||
Array.from(document.querySelectorAll("script")).forEach(x => x.parentElement.removeChild(x))
|
||||
const threat = {json.dumps(os.listdir("images/threat-updates"))}
|
||||
document.querySelector("#threat-update").src = "/threat-updates/" + randpick(threat)
|
||||
const demoButton = document.querySelector("#launch-demo")
|
||||
demoButton.addEventListener("click", () => {{
|
||||
const node = document.createElement("iframe")
|
||||
node.src = "/computer.html"
|
||||
node.id = "computer"
|
||||
demoButton.parentNode.parentNode.insertBefore(node, demoButton.parentNode.nextSibling)
|
||||
demoButton.remove()
|
||||
window.addEventListener("message", e => {{
|
||||
document.querySelector("#computer").style.height = `${{e.data}}px`
|
||||
}})
|
||||
}})
|
||||
"""
|
||||
|
||||
computer_html = """<!DOCTYPE html>
|
||||
<style>
|
||||
#computer {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/copy-cat/main.css" />
|
||||
<div id="computer"></div>
|
||||
<script type="text/javascript" src="/copy-cat/require.js"></script>
|
||||
<script>
|
||||
const doScaler = () => {
|
||||
const w = window.innerWidth
|
||||
const ar = 1.7541899441340782
|
||||
const canvas = document.querySelector("canvas")
|
||||
canvas.style.width = `${w}px`
|
||||
canvas.style.height = `${w/ar}px`
|
||||
canvas.parentNode.style.width = `${w}px`
|
||||
window.top.postMessage(document.querySelector("#computer").getBoundingClientRect().height, "*")
|
||||
}
|
||||
require.config({ paths: { copycat: "/copy-cat/" } });
|
||||
require(["copycat/embed"], setup => {
|
||||
window.setup = setup
|
||||
const computer = setup(document.getElementById("computer"), {
|
||||
persistId: 0,
|
||||
hdFont: false,
|
||||
files: {
|
||||
"startup.lua": `settings.set("potatOS.distribution_server", "https://osmarks.net/stuff/potatos/manifest")
|
||||
shell.run "wget run https://osmarks.net/stuff/potatos/autorun.lua"`,
|
||||
},
|
||||
label: "PotatOS",
|
||||
}).then(x => {
|
||||
console.log(x)
|
||||
setInterval(doScaler, 100) // sorry
|
||||
})
|
||||
});
|
||||
window.addEventListener("resize", doScaler)
|
||||
</script>
|
||||
"""
|
||||
|
||||
with open("website/computer.html", "w") as f:
|
||||
f.write(computer_html)
|
||||
|
||||
with open("manifest", "r") as f:
|
||||
data = f.readlines()
|
||||
main = json.loads(data[0])
|
||||
meta = json.loads(data[1])
|
||||
|
||||
potatos_meta = f"""<div>
|
||||
Current build: <code>{meta["hash"][:8]}</code> ({main["description"]}), version {main["build"]}, built {datetime.datetime.fromtimestamp(main["timestamp"], tz=datetime.timezone.utc).strftime("%Y-%m-%d %H:%M:%S (UTC)")}.
|
||||
</div>"""
|
||||
|
||||
html = f"""
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="PotatOS Otiose Transformative Advanced Technology Or Something, inescapably, is the best OS for ComputerCraft and derivatives. Install now with pastebin run 7HSiHybr.">
|
||||
<title>PotatOS</title>
|
||||
<style>{css}</style>
|
||||
<h1>Welcome to PotatOS!</h1>
|
||||
<img src="/potatos.gif" id="im">
|
||||
{potatos_meta}
|
||||
{html}
|
||||
<script>{gif_replacer}</script>
|
||||
"""
|
||||
|
||||
os.makedirs("website/privacy", exist_ok=True)
|
||||
for im in os.listdir("images"):
|
||||
src, dst = os.path.join("images", im), os.path.join("website", im)
|
||||
if os.path.isdir(src):
|
||||
if os.path.exists(dst): shutil.rmtree(dst)
|
||||
shutil.copytree(src, dst)
|
||||
else:
|
||||
shutil.copy(src, dst)
|
||||
with open("website/index.html", "w") as f:
|
||||
f.write(html)
|
||||
with open("website/privacy/index.html", "w") as f:
|
||||
f.write(privacy_policy())
|
||||
if os.path.exists("website/copy-cat"): shutil.rmtree("website/copy-cat")
|
||||
shutil.copytree("copy-cat", "website/copy-cat")
|
@ -62,7 +62,7 @@ if #arg == 1 then
|
||||
--
|
||||
print("Minification complete")
|
||||
|
||||
elseif #arg == 3 then
|
||||
elseif #arg == 2 then
|
||||
--keep the user from accidentally overwriting their non-minified file with
|
||||
if arg[1]:find("_min") then
|
||||
print("Did you mix up the argument order?\n"..
|
||||
@ -112,17 +112,9 @@ elseif #arg == 3 then
|
||||
return
|
||||
end
|
||||
--
|
||||
local text, map = Format_Mini(ast)
|
||||
outf:write(text)
|
||||
outf:write(Format_Mini(ast))
|
||||
outf:close()
|
||||
--
|
||||
local outf = io.open(arg[3], 'w')
|
||||
if not outf then
|
||||
print("Failed to open `"..arg[3].."` for writing")
|
||||
return
|
||||
end
|
||||
outf:write(map)
|
||||
outf:close()
|
||||
print("Minification complete")
|
||||
|
||||
else
|
||||
|
@ -21,14 +21,6 @@ local UpperChars = lookupify{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
|
||||
local Digits = lookupify{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
|
||||
local Symbols = lookupify{'+', '-', '*', '/', '^', '%', ',', '{', '}', '[', ']', '(', ')', ';', '#'}
|
||||
|
||||
local function serialize_debug_symbols(map)
|
||||
local out = {}
|
||||
for k, v in pairs(map) do
|
||||
table.insert(out, k .. "\t" .. v)
|
||||
end
|
||||
return table.concat(out, "\n")
|
||||
end
|
||||
|
||||
local function Format_Mini(ast)
|
||||
local formatStatlist, formatExpr;
|
||||
local count = 0
|
||||
@ -342,6 +334,10 @@ local function Format_Mini(ast)
|
||||
out = joinStatementsSafe(out, "do")
|
||||
out = joinStatementsSafe(out, formatStatlist(statement.Body))
|
||||
out = joinStatementsSafe(out, "end")
|
||||
elseif statement.AstType == 'LabelStatement' then
|
||||
out = getIndentation() .. "::" .. statement.Label .. "::"
|
||||
elseif statement.AstType == 'GotoStatement' then
|
||||
out = getIndentation() .. "goto " .. statement.Label
|
||||
elseif statement.AstType == 'Comment' then
|
||||
-- ignore
|
||||
elseif statement.AstType == 'Eof' then
|
||||
@ -353,17 +349,9 @@ local function Format_Mini(ast)
|
||||
return out
|
||||
end
|
||||
|
||||
local map = {}
|
||||
local function insert(t)
|
||||
for k, v in pairs(t) do
|
||||
map[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
formatStatlist = function(statList)
|
||||
local out = ''
|
||||
statList.Scope:ObfuscateVariables()
|
||||
insert(statList.Scope.name_map or {})
|
||||
for _, stat in pairs(statList.Body) do
|
||||
out = joinStatementsSafe(out, formatStatement(stat), ';')
|
||||
end
|
||||
@ -371,8 +359,7 @@ local function Format_Mini(ast)
|
||||
end
|
||||
|
||||
ast.Scope:ObfuscateVariables()
|
||||
insert(ast.Scope.name_map)
|
||||
return formatStatlist(ast), serialize_debug_symbols(map)
|
||||
return formatStatlist(ast)
|
||||
end
|
||||
|
||||
return Format_Mini
|
||||
|
@ -86,8 +86,6 @@ local Scope = {
|
||||
|
||||
RenameLocal = function(self, oldName, newName)
|
||||
oldName = type(oldName) == 'string' and oldName or oldName.Name
|
||||
self.name_map = self.name_map or {}
|
||||
self.name_map[newName] = oldName
|
||||
local found = false
|
||||
local var = self:GetLocal(oldName)
|
||||
if var then
|
||||
|
260
omnidisk.lua
@ -1,260 +0,0 @@
|
||||
--[[
|
||||
PotatOS OmniDisk
|
||||
A new system to unify the existing PotatOS Uninstall/Debug/Update disks currently in existence.
|
||||
Comes with a flexible, modular design, centralized licensing, possibly neater code, and a menu.
|
||||
|
||||
This is designed to be executed by the OmniDisk Loader (https://pastebin.com/S1RS76pv) but may run on its own, though this is NOT a supported configuration.
|
||||
|
||||
This is NOT usable simply by copying it onto a disk due to PotatOS signing requirements.
|
||||
You must use the dcopy (https://pastebin.com/TfNgRUKC) program or manually generate a hex-format ECC signature and write it to "disk/signature" (PotatOS will, however, not run it unless this signature is from the PDSK).
|
||||
]]
|
||||
|
||||
local function try_report_incident(...)
|
||||
if _G.report_incident then
|
||||
_G.report_incident(...)
|
||||
print "This incident has been reported."
|
||||
end
|
||||
end
|
||||
|
||||
local r = process.get_running()
|
||||
local sandbox = process.info "sandbox"
|
||||
if sandbox then
|
||||
for _, p in pairs(process.list()) do
|
||||
if p.parent == sandbox and p.ID ~= r.ID then
|
||||
process.signal(p.ID, process.signals.KILL)
|
||||
end
|
||||
end
|
||||
end
|
||||
pcall(process.signal, "sandbox", process.signals.KILL)
|
||||
os.queueEvent "stop"
|
||||
|
||||
local function fetch(URL)
|
||||
local h, e = http.get(URL)
|
||||
if not h then error(e) end
|
||||
local o = h.readAll()
|
||||
h.close()
|
||||
return o
|
||||
end
|
||||
|
||||
local UUID = "@UUID@" -- Populated by dcopy utility, in some setups
|
||||
|
||||
local args = ...
|
||||
|
||||
if type(args) == "table" and args.UUID then UUID = args.UUID end
|
||||
|
||||
local json
|
||||
if _G.json_for_disks_and_such then json = _G.json_for_disks_and_such
|
||||
elseif textutils.unserialiseJSON then
|
||||
json = { encode = textutils.serialiseJSON, decode = textutils.unserialiseJSON }
|
||||
else error "No JSON library exists, somehow" end
|
||||
local license_data = fetch "https://pastebin.com/raw/viz0spjb"
|
||||
local licenses = json.decode(license_data)
|
||||
local license = licenses[UUID]
|
||||
|
||||
local disk_ID
|
||||
local disk_loader_args = args.arguments
|
||||
if type(disk_loader_args) == "table" and disk_loader_args.ID then
|
||||
disk_ID = disk_loader_args.ID
|
||||
end
|
||||
|
||||
local function runfile(program, ...)
|
||||
local ok, err = loadfile(program)
|
||||
if not ok then error(err) end
|
||||
ok(...)
|
||||
end
|
||||
|
||||
local features = {
|
||||
test = {
|
||||
fn = function() print "Hello, World!" end,
|
||||
description = "Test function."
|
||||
},
|
||||
exit = {
|
||||
fn = function() os.reboot() end,
|
||||
description = "Leave OmniDisk, return to PotatOS.",
|
||||
always_permitted = true
|
||||
},
|
||||
UUID = {
|
||||
fn = function() print("UUID:", UUID) print("Disk ID:", disk_ID or "[???]") end,
|
||||
description = "Print this OmniDisk's Licensing UUID.",
|
||||
always_permitted = true
|
||||
},
|
||||
uninstall = {
|
||||
fn = function() print "Uninstalling..." _G.uninstall "omnidisk" end,
|
||||
description = "Uninstall potatOS"
|
||||
},
|
||||
REPL = {
|
||||
fn = function() runfile("/rom/programs/shell.lua", "lua") end,
|
||||
description = "Open a Lua REPL for debugging."
|
||||
},
|
||||
shell = {
|
||||
fn = function()
|
||||
printError "WARNING!"
|
||||
print "Do not attempt to modify the code of this PotatOS OmniDisk. Unauthorized attempts to do so will invalidate the signature and make the disk unusable. All code beyond a limited core is stored in an online file to which you do not have write access. Probably. Contact gollark for further information."
|
||||
runfile "/rom/programs/shell.lua"
|
||||
end,
|
||||
description = "Open an unsandboxed shell."
|
||||
},
|
||||
update = {
|
||||
fn = function() runfile("autorun", "update") end,
|
||||
description = "Update PotatOS."
|
||||
},
|
||||
dump_license = {
|
||||
fn = function() print(UUID) textutils.pagedPrint(textutils.serialise(license)) end,
|
||||
description = "Dump license information."
|
||||
},
|
||||
primes = {
|
||||
fn = function()
|
||||
if not _G.findprime or not _G.isprime then
|
||||
error "findprime/isprime not available. Update potatOS."
|
||||
end
|
||||
write "Difficulty? (1-16) "
|
||||
local difficulty = tonumber(read())
|
||||
if type(difficulty) ~= "number" then error "ERR_PEBKAC\nThat's not a number." end
|
||||
local maxrand = math.pow(10, difficulty)
|
||||
local p1 = findprime(math.random(2, maxrand))
|
||||
local p2 = findprime(math.random(2, maxrand))
|
||||
|
||||
local num = p1 * p2
|
||||
print("Please find the prime factors of the following number:", num)
|
||||
write "Factor 1: "
|
||||
local f1 = tonumber(read())
|
||||
write "Factor 2: "
|
||||
local f2 = tonumber(read())
|
||||
if (f1 == p1 and f2 == p2) or (f2 == p1 and f1 == p2) then
|
||||
print "Yay! You got it right!"
|
||||
else
|
||||
print("Factors", f1, f2, "invalid.", p1, p2, "expected.")
|
||||
end
|
||||
end,
|
||||
description = "Bored? You can factor some semiprimes!"
|
||||
},
|
||||
potatoplex = {
|
||||
fn = function()
|
||||
write "Run potatoplex with arguments: "
|
||||
local args = read()
|
||||
runfile("rom/programs/http/pastebin.lua", "run", "wYBZjQhN", args)
|
||||
end,
|
||||
description = "Potatoplex your life!"
|
||||
},
|
||||
chronometer = {
|
||||
fn = function()
|
||||
runfile("rom/programs/http/pastebin.lua", "run", "r24VMWk4")
|
||||
end,
|
||||
description = "Tell the time with Chronometer!"
|
||||
},
|
||||
latest_paste = {
|
||||
fn = function()
|
||||
write "WARNING: This views the latest paste on Pastebin. Exposure to the raw output of the Internet may be detrimental to your mental health. Do you want to continue (y/n)? "
|
||||
local yn = read()
|
||||
if not yn:lower():match "y" then return end
|
||||
local html = fetch "https://pastebin.com/LW9RFpmY"
|
||||
local id = html:match [[<ul class="right_menu"><li><a href="/([A-Za-z0-9]+)">]]
|
||||
local url = ("https://pastebin.com/raw/%s"):format(id)
|
||||
local title = html:match [[<ul class="right_menu"><li><a href="/[A-Za-z0-9]+">([^<]+)</a>]]
|
||||
local content = fetch(url)
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
textutils.pagedPrint(title .. "\n" .. url .. "\n\n" .. content)
|
||||
end,
|
||||
description = "View latest paste on Pastebin."
|
||||
}
|
||||
}
|
||||
|
||||
local function wait()
|
||||
write "Press Any key to continue."
|
||||
os.pullEvent "key"
|
||||
local timer = os.startTimer(0)
|
||||
while true do
|
||||
local e, arg = os.pullEvent()
|
||||
if (e == "timer" and arg == timer) or e == "char" then return end
|
||||
end
|
||||
end
|
||||
|
||||
if not license then
|
||||
printError(([[ERR_NO_LICENSE
|
||||
This disk (UUID %s) does not have an attached license and is invalid.
|
||||
This should not actually happen, unless you have meddled with the disk while somehow keeping the signature intact.
|
||||
Please contact gollark.]]):format(tostring(UUID)))
|
||||
try_report_incident(("OmniDisk UUID %s has no license data"):format(tostring(UUID)), {"security", "omnidisk"}, {
|
||||
extra_meta = {
|
||||
disk_ID = disk_ID,
|
||||
omnidisk_UUID = UUID
|
||||
}
|
||||
})
|
||||
wait()
|
||||
os.reboot()
|
||||
end
|
||||
|
||||
if disk_ID then
|
||||
local license_ID = license.disk
|
||||
local ok = false
|
||||
if type(license_ID) == "table" then
|
||||
for _, id in pairs(license_ID) do
|
||||
if id == disk_ID then ok = true break end
|
||||
end
|
||||
elseif type(license_ID) == "number" then
|
||||
if license_ID == disk_ID then ok = true end
|
||||
else
|
||||
ok = true
|
||||
end
|
||||
if not ok then
|
||||
printError(([[ERR_WRONG_DISK
|
||||
This disk (ID %d) is not (one of) the disk(s) specified in your licensing information.
|
||||
This license (UUID %s) allows use of this/these disk(s): %s.
|
||||
If you believe this to be in error, please contact gollark so this can be corrected.
|
||||
Otherwise, stop cloning disks, or contact gollark to have unique UUIDs issued to each.]]):format(disk_ID, UUID, json.encode(license_ID)))
|
||||
try_report_incident(("Disk ID mismatch: %d used with license %s"):format(disk_ID, UUID, json.encode(license_ID)), {"security", "omnidisk"}, {
|
||||
extra_meta = {
|
||||
permitted_disk_IDs = license_ID,
|
||||
disk_ID = disk_ID,
|
||||
omnidisk_UUID = UUID
|
||||
}
|
||||
})
|
||||
wait()
|
||||
os.reboot()
|
||||
end
|
||||
end
|
||||
|
||||
local permitted_feature_lookup = {}
|
||||
for _, feature in pairs(license.features) do
|
||||
permitted_feature_lookup[feature] = true
|
||||
end
|
||||
|
||||
while true do
|
||||
term.setCursorPos(1, 1)
|
||||
term.clear()
|
||||
|
||||
local usable = {}
|
||||
local i = 0
|
||||
|
||||
print [[Welcome to PotatOS OmniDisk!
|
||||
Available options:]]
|
||||
|
||||
for name, feature in pairs(features) do
|
||||
if permitted_feature_lookup["*"] or permitted_feature_lookup[name] or feature.always_permitted then
|
||||
textutils.pagedPrint(("%d. %s - %s"):format(i, name, feature.description or "[no description available]"))
|
||||
usable[i] = feature.fn
|
||||
usable[name] = feature.fn
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
write "Select an option: "
|
||||
local option = read()
|
||||
local fn
|
||||
local as_num = tonumber(option)
|
||||
|
||||
if as_num then fn = usable[as_num] else fn = usable[option] end
|
||||
if not fn then
|
||||
printError(("ERR_ID_10T\nPlease select an option which actually exists.\n'%s' doesn't."):format(tostring(option)))
|
||||
wait()
|
||||
else
|
||||
local ok, res = pcall(fn)
|
||||
if not ok then
|
||||
printError(res)
|
||||
wait()
|
||||
else
|
||||
wait()
|
||||
end
|
||||
end
|
||||
end
|
733
package-lock.json
generated
@ -1,5 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"luabundler": "^1.2.2"
|
||||
}
|
||||
}
|
106
privacy/index.md
@ -1,106 +0,0 @@
|
||||
# PotatOS Privacy Policy
|
||||
|
||||
We at PotatOS are dedicated to providing you with a demonstrably, provably optimal PotatOS experience.
|
||||
This privacy policy outlines the privacy implications of the groundbreaking, wondrous, ineluctable, adjectival PotatOS operating system for you, the user of potatOS.
|
||||
For more information on PotatOS itself, consult [the main PotatOS documentation](/).
|
||||
|
||||
## Welcome to potatOS!
|
||||
|
||||
PotatOS provides Primarily Otiose Transformative Advanced Technology, Or Something ("PotatOS", "PotatOS™"), associated programs, libraries and other code ("PotatOS Potatosystems"), and PotatOS backend webservices such as SPUDNETv2/PIR, RSAPI, and PSUS ("PotatOS Services"). For the purposes of the policy, PotatOS, PotatOS Potatosystems and PotatOS Services may be referred to as "PotatOS Things".
|
||||
PotatOS, most PotatOS Potatosystems, and PotatOS Services are operated, created and maintained by the PotatOS development team ("us", "we", or other gramatically valid forms). Some PotatOS Potatosystems are developed and maintained by third parties, and PotatOS, as a general purpose operating system, may interact with other organizations outside of the scope of this policy.
|
||||
This privacy policy ("PotatOS Privacy Policy") sets out how we may use information, such as information gathered via PotatOS and PotatOS Services.
|
||||
"PotatOS Privacy Policy" refers collectively to all the terms, conditions and notices contained or referenced in this document, and any associated documentation which may be published by us, including all past and future versions.
|
||||
|
||||
## Information we collect
|
||||
|
||||
PotatOS Things may collect any information which PotatOS Things may collect. This includes information such as:
|
||||
|
||||
* Information you provide. If you provide information, this may be stored and used in order to provide PotatOS™ functionality. This includes information such as settings, which are stored locally so that they can be read and utilized, and your files, if you make files, which are stored on disk and potentially in RAM so that they can be read back and displayed.
|
||||
* All user input or all executed code, if some debug settings such as Protocol Epsilon and Extended Monitoring are enabled.
|
||||
* Internally generated information which may be indirectly derived from user input, such as your device's UPID[^3], some PotatOS Registry contents and system debug logs.
|
||||
* ComputerCraft system configuration information and identification information, which is sent to SPUDNETv2/PIR and stored with incident reports to assist with debugging and/or handling the source of the reports.
|
||||
* In certain jurisdictions, we may ask for a valid ID (from accepted countries such as Kazakhstan, the Democratic People's Republic of Korea, Sealand, the Freeish State of Gollarkia, Desmethylway, the Harmonious Jade Dragon Empire, or the Untied States) in limited circumstances. This is only for purposes.
|
||||
|
||||
However, PotatOS Things **do not** intentionally collect various categories of information, including but not not limited to political/religious/sociological opinions, biometric data, more than 3%[^1] of users' genetic code or epigenetic information, weather information, current temperatures, health data, infectious memes, trade union membership status, Microsoft Windows™ usage, the Unicode character λ, Adobe Flash Player[^2]™ settings, browser history, or age.
|
||||
|
||||
For users who are citizens of the European Union, we will now be requesting permission before initiating organ harvesting. This software uses an army of frogs that throw cookies in the general direction of your computer. By using it, you agree that these frogs may throw cookies.
|
||||
|
||||
We may use your data for various purposes, such as:
|
||||
|
||||
* responding appropriately to user input/commands
|
||||
* providing and managing mandatory software updates
|
||||
* communication with users, where necessary - for example to provide notifications about upcoming events and product announcements
|
||||
* [LEVEL 5 CLEARANCE REQUIRED]
|
||||
* anything PotatOS Things are programmed to do
|
||||
* entertainment and/or random messing around
|
||||
* helping to deliver, create, develop, improve, design, operate, manage, produce, modernize, complicate, nationalize, placate, evolve, alter, amend, change, obliterate, or worsen our products and services
|
||||
* anything whatsoever
|
||||
|
||||
You are able to opt out of data collection using the following methods:
|
||||
|
||||
* submersion of your computing device in high-temperature molten rocks
|
||||
* exposure of your computing device to cacti and potentially other plants
|
||||
* ceasing all interaction with your computing device
|
||||
* SCP-[DATA LOST]
|
||||
* immersion of your computing device in non-high-temperature non-molten rocks, somehow
|
||||
* submersion of your computing device in highly acidic, alkaline or based material
|
||||
* exposure of your computing device to large amounts of kinetic energy
|
||||
* exposure of your computing device to large amounts of psychic energy
|
||||
* exposure of your computing device to vacuum or stellar cores
|
||||
|
||||
You may also contact us to have your existing stored data removed, if you are able to verify your relation to this data. We have a lot of it, and it isn't labelled very well.
|
||||
|
||||
We will never sell your data! <span class=spoiler>Nobody wants it much and they can just ask and probably get it for free anyway.</span>
|
||||
|
||||
[^1]: as of 31/11/2019
|
||||
[^2]: this may not extend to other Adobe products
|
||||
[^3]: Unique PotatOS ID
|
||||
|
||||
## Safety notices
|
||||
|
||||
We wish to remind all users that regardless of recent rumors, potatOS is not responsible for and not associated with SCP-3125. SCP-3125 must not be interacted with. "██████ Siri" (PS#ABB85797) must not be interacted with.
|
||||
|
||||
PotatOS is designed to obey the laws of thermodynamics; if you detect any violations of them, please bring it to our attention. However, some versions of potatOS may not fully obey causality. This is not currently considered a bug.
|
||||
|
||||
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.
|
||||
|
||||
Caution: Some assembly required. PotatOS assumes no liability for injuries, accidents, or existential nausea caused by physical or intellectual misuse of PotatOS. PotatOS does not endorse solipsism, and any Ominous Implications™ that result from use of PotatOS™ are not views shared by PotatOS. PotatOS is not beholden to spacetime. PotatOS cannot be forgotten or unlearned.
|
||||
|
||||
By using PotatOS™, you agree to not become an agent of or otherwise aid SCP-3125 and/or associated anomalies.
|
||||
|
||||
The PotatOS Network Systems Division retains the right to utilize orbital laser strikes, memetic kill agents, antimemetic kill agents, memetic death agents, [REDACTED], apiohazards, apiocryohazards, >SQUARE BRACKETS EXPUNGED<, apiomemetohazards, apiopyrohazards, orthoapiohazards, anarchocommunism, arachnocommunism, arachnoanarchocommunism, recreational nuclear weapons, relativistic kinetic kill vehicles, Project TANTALUM IGUANA and derivatives, Mobile Task Force σ-18, Cascading Style Sheets, [[REDACTED] [[DATA EXPUNGED] LOST]], SCP-001-J, GPT-8, the number floor(2pi) (THIS NUMBER MUST NOT BE ACKNOWLEDGED), <span></span>, the PotatOS Privacy Policy, SCP-579, technetium, apioforms, rate limits, SCP-4514, immediate cardiac arrest, a potato with a gun, 7.2kg of melons, no apioforms, User:Heavpoot, EndOS, the internet, <span style="display:inline-block;transform:rotateY(110deg)">this</span>, ████ ████████, unfortunate coincidences, <span class=spoiler>the impending end of the universe</span>, Arch Linux, segmentation faults, cross-site request forgery, triskaidecagons, the borrow checker, !lyricly☭demote☭establish☭communism!, Turing-incompleteness, a description of SCP-3125, bismuth-209, 700 kilotons of cats, antimatter, antiantimatter, O(n<sup>3</sup>) time complexity, Dwarf Fortress, or the character U+202E RIGHT TO LEFT OVERRIDE against users, if it is deemed necessary by the PIERB[^4].
|
||||
|
||||
[^4]: PotatOS Internal Ethical Review Board
|
||||
|
||||
PotatOS is currently, has always been, and always will be, considered nonanomalous. PotatOS is inspired by non-equilibrium thermodynamics.
|
||||
|
||||
## Legal information
|
||||
|
||||
By using potatOS, agreeing to be bound by these terms, misusing potatOS, installing potatOS, reading about potatOS, knowing about these terms, knowing anyone who is bound by these terms, disusing potatOS, reading these terms, or thinking of anything related to these terms, you agree to be bound by these terms both until the last stars in the universe burn out and the last black holes evaporate and retroactively, arbitrarily far into the past. This privacy policy may be updated at any time and at all times the latest revision applies.
|
||||
|
||||
You agree additionally to the following Unicode characters: א U+05D0 HEBREW LETTER ALEF and ⬡ U+2B21[^6] WHITE HEXAGON. A string constant may continue from one line to the next, but the exact contents of such a string are unspecified.
|
||||
|
||||
[^6]: Those who find collisions in hash functions cannot be trusted and may be banned at any time.
|
||||
|
||||
Furthermore: by using PotatOS, you forfeit all claims on your soul by any deity or variations thereof, and pledge yourself in worship to the goddess Discordia, daughter of Night and Darkness. PotatOS is not responsible for any smiting or divine punishments by any angered deities/anomalous theoformic entities/Class-Green reality benders or variations thereof as a result of this agreement. Any legal challenges to this clause must take place in the legal jurisdiction of the court of Pluto, lord of the underworld, or the osmarks.net (or future variations of such) comments section. PotatOS is not responsible for travel arrangements to Avernus.
|
||||
|
||||
This policy supersedes any applicable federal, national, state, and local laws, regulations and ordinances, policies, international treaties, legal agreements, illegal agreements, or any other agreements, contracts, documents, policies, standards or content/information/statements/opinions/preferences that would otherwise apply. If any provision of this policy is found by a court (or other entity) to be unenforceable, it nevertheless remains in force. This policy is legal, not illegal, and a valid legal document in all jurisdictions. This organization is not liable and this agreement shall not be construed. We are not responsible for any issue whatsoever at all arising from use of potatOS, potatOS services, anything at all, or otherwise.
|
||||
|
||||
As an additional clarification to the above clause, this privacy policy supersedes and overrides the "EndOS" and "TaterOS" privacy policies and terms of service regardless of any contradictory claims they may contain. This policy also supersedes all statements made by [GEORGE](https://george.gh0.pw/) or agents thereof.
|
||||
|
||||
You are responsible for anything which potatOS might do to your things. You ran it. It is all your fault. <span class=spoiler>The turtle is watching you</span>. We are not liable, ethically, morally, existentially, financially or legally, for anything whatsoever.
|
||||
|
||||
You agree that your mind, thoughts, soul and other distinguishing characteristics may be repurposed/utilized at any time for the training of GPT-██ or other artificial intelligences at the discretion of the PotatOS Advanced Projects team. You also agree that your soul may be temporarily[^5] be placed into various apioformic entities (see Appendix 6.7) for various purposes³. You can opt out of this by being soulless and an empty husk of what you once were. You are permitted to maintain consciousness as long as this does not negatively affect PotatOS™ operations. You agree that you either are a robot or may be converted into one if it is deemed necessary.
|
||||
|
||||
[^5]: this can take anywhere from 3.2 µs to 10<sup>32</sup> teraseconds
|
||||
|
||||
Moreover, Heavpoot (discord ID 160279332454006795, UPID #89VJZ9AK:☭934) is to be considered co-owner of the totality of existence and/or the universe. "Andrew" (Discord ID 543131534685765673, UPID 6ec3837b5260g4b9d█22029e7b474█d63 is at all times incorrect in his beliefs and/or statements, unless this would contradict with other clauses of this policy and/or cause harm to PotatOS or us.
|
||||
|
||||
Any "dibs" you make may be transferred to us at our discretion. You agree that this statement is false.
|
||||
The software includes Adobe Flash Player that is licensed under terms from Adobe Systems Incorporated. Adobe and Flash are either registered trademarks or trademarks of Adobe Systems Incorporated in the United States and/or other countries.
|
||||
In all situations, the government of PotatOS will take the normatively correct action.
|
||||
|
||||
## Summary
|
||||
|
||||
If any aspect is not utterly harmonious, gratefully bring it to our notice and we shall strive to earn your satisfaction.
|
||||
Enjoy the PotatOS™ Experience™!
|
@ -1,124 +0,0 @@
|
||||
// copy in entire markov chain library
|
||||
class Markov{constructor(type="text"){if(type==="text"){this.type=type}else if(type==="numeric"){this.type=type}else{throw new Error("The Markov Chain can only accept the following types: numeric or text")}this.states=[];this.possibilities={};this.order=3;if(this.type==="text"){this.start=[]}}
|
||||
addStates(state){if(Array.isArray(state)){this.states=Array.from(state)}else{this.states.push(state)}}
|
||||
clearChain(){this.states=[];if(this.type==="text"){this.start=[]}this.possibilities={};this.order=3}
|
||||
clearState(){this.states=[];if(this.type==="text"){this.start=[]}}
|
||||
clearPossibilities(){this.possibilities={}}
|
||||
getStates(){return this.states}
|
||||
setOrder(order=3){if(typeof order!=="number"){console.error("Markov.setOrder: Order is not a number. Defaulting to 3.");order=3}if(order<=0){console.error("Markov.setOrder: Order is not a positive number. Defaulting to 3.")}if(this.type==="numeric"){console.warn("The Markov Chain only accepts numerical data. Therefore, the order does not get used.\nThe order may be used by you to simulate an ID for the Markov Chain if required")}this.order=order}
|
||||
getPossibilities(possibility){if(possibility){if(this.possibilities[possibility]!==undefined){return this.possibilities[possibility]}else{throw new Error("There is no such possibility called "+possibility)}}else{return this.possibilities}}
|
||||
train(order){this.clearPossibilities();if(order){this.order=order}if(this.type==="text"){for(let i=0;i<this.states.length;i++){this.start.push(this.states[i].substring(0,this.order));for(let j=0;j<=this.states[i].length-this.order;j++){const gram=this.states[i].substring(j,j+this.order);if(!this.possibilities[gram]){this.possibilities[gram]=[]}this.possibilities[gram].push(this.states[i].charAt(j+this.order))}}}else if(this.type==="numeric"){for(let i=0;i<this.states.length;i++){const{state:state,predictions:predictions}=this.states[i];if(!this.possibilities[state]){this.possibilities[state]=[]}this.possibilities[state].push(...predictions)}}}
|
||||
generateRandom(chars=15){const startingState=this.random(this.start,"array");let result=startingState;let current=startingState;let next="";for(let i=0;i<chars-this.order;i++){next=this.random(this.possibilities[current],"array");if(!next){break}result+=next;current=result.substring(result.length-this.order,result.length)}return result}
|
||||
random(obj,type){if(Array.isArray(obj)&&type==="array"){const index=Math.floor(Math.random()*obj.length);return obj[index]}if(typeof obj==="object"&&type==="object"){const keys=Object.keys(obj);const index=Math.floor(Math.random()*keys.length);return keys[index]}}}
|
||||
|
||||
//console.log("Initiating Protocol ASCENDING CARPOOL.")
|
||||
|
||||
const strings = document.body.innerText.split("\n").filter(x => !/^[0-9]\.[0-9]$/.exec(x)).flatMap(x => x.split("."))
|
||||
const m = new Markov()
|
||||
m.addStates(strings)
|
||||
m.train(6)
|
||||
|
||||
const pageBottom = document.createElement("div")
|
||||
pageBottom.style.position = "relative"
|
||||
pageBottom.style.bottom = "100vh"
|
||||
pageBottom.style.height = "100vh"
|
||||
pageBottom.style.opacity = 0
|
||||
pageBottom.style.pointerEvents = "none"
|
||||
document.body.appendChild(pageBottom)
|
||||
|
||||
const contentEnd = document.querySelector("#contentend")
|
||||
const lorem = document.querySelector("#lorem")
|
||||
|
||||
let canSeeEnd = false
|
||||
|
||||
let sectionNumber = 0
|
||||
|
||||
for (const el of document.body.childNodes) {
|
||||
var match
|
||||
if (el.id && (match = /^s(\d+)\-(\d+)/.exec(el.id))) {
|
||||
sectionNumber = Math.max(sectionNumber, parseInt(match[1]))
|
||||
}
|
||||
}
|
||||
|
||||
const capitals = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
const addText = () => {
|
||||
if (canSeeEnd) {
|
||||
sectionNumber++
|
||||
var currentText = ""
|
||||
while (currentText.length < 50 || /[\(\),"\.\-]/.exec(currentText)) {
|
||||
currentText = m.generateRandom(150)
|
||||
}
|
||||
var words = currentText.split(" ").filter(x => x)
|
||||
var twc = Math.floor(Math.random() * 4 + 1)
|
||||
if (twc == 1) {
|
||||
twc += Math.random() > 0.5 ? 1 : 0
|
||||
}
|
||||
while (words.length > twc) {
|
||||
words.pop()
|
||||
}
|
||||
title = words.map(w => w[0].toUpperCase() + w.slice(1)).join(" ")
|
||||
const node = document.createElement("h2")
|
||||
node.appendChild(document.createTextNode(title))
|
||||
contentEnd.appendChild(node)
|
||||
//console.log(title)
|
||||
for (let i = 0; i < Math.floor(Math.random() * 5 + 2); i++) {
|
||||
const headerNode = document.createElement("h3")
|
||||
const aNode = document.createElement("a")
|
||||
aNode.setAttribute("id", `s${sectionNumber}-${i + 1}`)
|
||||
aNode.setAttribute("href", `#s${sectionNumber}-${i + 1}`)
|
||||
aNode.appendChild(document.createTextNode(`${sectionNumber}.${i + 1}`))
|
||||
headerNode.appendChild(aNode)
|
||||
contentEnd.appendChild(headerNode)
|
||||
let text = ""
|
||||
const length = Math.floor(Math.random() * 250 + 100)
|
||||
while (text.length < length) {
|
||||
let newText = m.generateRandom(500).replace(/↩/g, "").trim()
|
||||
if (newText) {
|
||||
newText = newText[0].toUpperCase() + newText.slice(1)
|
||||
if (![".", "!", "?"].includes(newText[newText.length - 1])) { newText += "." }
|
||||
newText += " "
|
||||
text += newText
|
||||
}
|
||||
}
|
||||
const textNode = document.createElement("p")
|
||||
textNode.appendChild(document.createTextNode(text))
|
||||
contentEnd.appendChild(textNode)
|
||||
}
|
||||
}
|
||||
if (canSeeEnd) {
|
||||
setTimeout(addText, 50)
|
||||
}
|
||||
}
|
||||
|
||||
const callback = entries => {
|
||||
canSeeEnd = (entries[0].isIntersecting)
|
||||
if (canSeeEnd) {
|
||||
addText()
|
||||
}
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(callback, {})
|
||||
observer.observe(pageBottom)
|
||||
|
||||
const randomPick = x => x[Math.floor(Math.random() * x.length)]
|
||||
const randomWord = p => randomPick(p.innerText.split(" ").map(x => x.replace(/[^A-Za-z]/, "")).filter(x => x !== ""))
|
||||
|
||||
const update = () => {
|
||||
const paras = document.querySelectorAll("p")
|
||||
const from = randomWord(randomPick(paras))
|
||||
const to = randomPick(paras)
|
||||
to.innerHTML = to.innerHTML.replace(randomWord(to), from)
|
||||
}
|
||||
|
||||
window.addEventListener("scroll", () => {
|
||||
if (Math.random() < 0.01) {
|
||||
//console.log("Scheduler online. WITLESS HOROLOGISTS procedure started.")
|
||||
if ("requestIdleCallback" in window) {
|
||||
window.requestIdleCallback(update, { timeout: 200 })
|
||||
} else {
|
||||
setTimeout(update)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Array.from(document.querySelectorAll("script")).forEach(x => x.parentElement.removeChild(x))
|
@ -1,3 +0,0 @@
|
||||
local w, h = term.getSize()
|
||||
polychoron.BSOD(potatOS.randbytes(math.random(0, w * h)))
|
||||
os.pullEvent "key"
|
@ -1 +0,0 @@
|
||||
print "abcdefghijklmnopqrstuvwxyz"
|
@ -1,19 +0,0 @@
|
||||
print("Short hash", potatOS.build)
|
||||
print("Full hash", potatOS.full_build)
|
||||
local mfst = potatOS.registry.get "potatOS.current_manifest"
|
||||
if mfst then
|
||||
print("Counter", mfst.build)
|
||||
print("Built at (local time)", os.date("%Y-%m-%d %X", mfst.timestamp))
|
||||
print("Downloaded from", mfst.manifest_URL)
|
||||
local verified = mfst.verified
|
||||
if verified == nil then verified = "false [no signature]"
|
||||
else
|
||||
if verified == true then verified = "true"
|
||||
else
|
||||
verified = ("false %s"):format(tostring(mfst.verification_error))
|
||||
end
|
||||
end
|
||||
print("Signature verified", verified)
|
||||
else
|
||||
print "Manifest not found in registry. Extended data unavailable."
|
||||
end
|
@ -1 +0,0 @@
|
||||
print(potatOS.chuck_norris())
|
@ -1 +0,0 @@
|
||||
potatOS.clear_space((... and tonumber(...) and tonumber(...) == tonumber(...)) and tonumber(...) or 4096)
|
@ -1,60 +0,0 @@
|
||||
local prefixes = {
|
||||
{-12, "p"},
|
||||
{-9, "n"},
|
||||
{-6, "u"},
|
||||
{-3, "m"},
|
||||
{0, ""},
|
||||
{3, "k"},
|
||||
{6, "M"}
|
||||
}
|
||||
|
||||
local function SI_prefix(value, unit)
|
||||
local x = math.log(value, 10)
|
||||
local last
|
||||
for _, t in ipairs(prefixes) do
|
||||
if t[1] > x then
|
||||
break
|
||||
end
|
||||
last = t
|
||||
end
|
||||
local dp = 2 - math.floor(x - last[1])
|
||||
return (("%%.%df%%s%%s"):format(dp)):format(value / 10^(last[1]), last[2], unit)
|
||||
end
|
||||
|
||||
local w = term.getSize()
|
||||
local rows = {}
|
||||
for _, info in pairs(process.list()) do
|
||||
table.insert(rows, { info.name or tostring(info.ID), SI_prefix(info.execution_time, "s"), SI_prefix(info.ctime, "s") })
|
||||
end
|
||||
|
||||
local max_width_per_column = {}
|
||||
|
||||
for _, row in ipairs(rows) do
|
||||
for i, cell in ipairs(row) do
|
||||
max_width_per_column[i] = math.max(max_width_per_column[i] or 0, cell:len() + 1)
|
||||
end
|
||||
end
|
||||
|
||||
local vw_width = 0
|
||||
|
||||
for i = #max_width_per_column, 1, -1 do
|
||||
if i > 1 then
|
||||
vw_width = vw_width + max_width_per_column[i]
|
||||
end
|
||||
end
|
||||
|
||||
local fw_start = w - vw_width
|
||||
|
||||
for _, row in ipairs(rows) do
|
||||
local s
|
||||
for i, cell in ipairs(row) do
|
||||
if i == 1 then
|
||||
s = cell:sub(1, fw_start - 1) .. (" "):rep((fw_start - 1) - cell:len())
|
||||
else
|
||||
cell = " " .. cell
|
||||
s = s .. (" "):rep(max_width_per_column[i] - cell:len()) .. cell
|
||||
end
|
||||
end
|
||||
|
||||
textutils.pagedPrint(s)
|
||||
end
|
@ -1,25 +0,0 @@
|
||||
-- edit reality to match typo in docs
|
||||
function Safe_SerializeWithtextutilsDotserialize(Valuje)
|
||||
local _, __ = pcall(textutils.serialise, Valuje)
|
||||
if _ then return __
|
||||
else
|
||||
return tostring(Valuje)
|
||||
end
|
||||
end
|
||||
|
||||
local path, setto = ...
|
||||
path = path or ""
|
||||
|
||||
if setto ~= nil then
|
||||
local x, jo, jx = textutils.unserialise(setto), pcall(json.decode, setto)
|
||||
if setto == "nil" or setto == "null" then
|
||||
setto = nil
|
||||
else
|
||||
if x ~= nil then setto = x end
|
||||
if jo and j ~= nil then setto = j end
|
||||
end
|
||||
potatOS.registry.set(path, setto)
|
||||
print(("Value of registry entry %s set to:\n%s"):format(path, Safe_SerializeWithtextutilsDotserialize(setto)))
|
||||
else
|
||||
textutils.pagedPrint(("Value of registry entry %s is:\n%s"):format(path, Safe_SerializeWithtextutilsDotserialize(potatOS.registry.get(path))))
|
||||
end
|
@ -1,8 +0,0 @@
|
||||
-- like delete but COOLER and LATIN
|
||||
for _, wcard in pairs{...} do
|
||||
for _, path in pairs(fs.find(wcard)) do
|
||||
fs.ultradelete(path)
|
||||
local n = potatOS.lorem():gsub("%.", " " .. path .. ".")
|
||||
print(n)
|
||||
end
|
||||
end
|
@ -12,10 +12,9 @@ repeat
|
||||
write "Provide an integer to factorize: "
|
||||
x = tonumber(read())
|
||||
if not x or math.floor(x) ~= x then print("That is NOT an integer.") end
|
||||
if x and x < 2 then print("I forgot to mention this, but also don't use 1, 0 or negative integers.") end
|
||||
until x
|
||||
|
||||
if x > (2^40) then print("WARNING: Number is quite big. Due to Lua floating point limitations, draconic entities MAY be present and results may be blatantly wrong. If this runs for several seconds, it's probably frozen due to this.") end
|
||||
if x > (2^40) then print("WARNING: Number is quite big. Due to Lua floating point limitations, draconic entities MAY be present. If this runs for several seconds, it's probably frozen due to this.") end
|
||||
|
||||
local floor, abs, random, log, pow = math.floor, math.abs, math.random, math.log, math.pow
|
||||
|
||||
@ -29,16 +28,17 @@ local function eps_compare(x, y)
|
||||
return abs(x - y) < 1e-14
|
||||
end
|
||||
|
||||
-- binary modular exponentiation
|
||||
local function modexp(a, b, n)
|
||||
if b == 0 then return 1 % n end
|
||||
if b == 1 then return a % n end
|
||||
local bdiv2 = b / 2
|
||||
local fbdiv2 = floor(bdiv2)
|
||||
if eps_compare(bdiv2, fbdiv2) then
|
||||
-- b is even, so it is possible to just modexp with HALF the exponent and square it (mod n)
|
||||
local x = modexp(a, fbdiv2, n)
|
||||
return (x * x) % n
|
||||
else
|
||||
-- not even, so subtract 1 (this is even), modexp that, and multiply by a again (mod n)
|
||||
return (modexp(a, b - 1, n) * a) % n
|
||||
end
|
||||
end
|
||||
@ -135,6 +135,6 @@ end
|
||||
local facs = factor(x)
|
||||
|
||||
if (potatOS.is_uninstalling and potatOS.is_uninstalling()) and x > 1e5 then
|
||||
for k, v in pairs(facs) do facs[k] = v + random(0, 1000) end
|
||||
for k, v in pairs(facs) do facs[k] = facs[k] + random(0, 1000) end
|
||||
end
|
||||
print("Factors:", unpack(facs))
|
@ -1 +0,0 @@
|
||||
print(potatOS.fortune())
|
@ -1,8 +0,0 @@
|
||||
potatOS.evilify()
|
||||
print "GAME KEYBOARD enabled."
|
||||
potatOS.init_screens()
|
||||
print "GAME SCREEN enabled."
|
||||
print "Activated GAME MODE."
|
||||
--bigfont.bigWrite "GAME MODE."
|
||||
--local x, y = term.getCursorPos()
|
||||
--term.setCursorPos(1, y + 3)
|
@ -1,175 +0,0 @@
|
||||
local mat = term.current()
|
||||
mat.setPaletteColor(colors.black, 0)
|
||||
mat.setPaletteColor(colors.green, 0x0cff0c)
|
||||
mat.setPaletteColor(colors.red, 0xff0000)
|
||||
if mat.setTextScale then mat.setTextScale(0.5) end
|
||||
mat.setTextColor(colors.green)
|
||||
term.redirect(mat)
|
||||
|
||||
local jargonWords = {
|
||||
acronyms =
|
||||
{"TCP", "HTTP", "SDD", "RAM", "GB", "CSS", "SSL", "AGP", "SQL", "FTP", "PCI", "AI", "ADP",
|
||||
"RSS", "XML", "EXE", "COM", "HDD", "THX", "SMTP", "SMS", "USB", "PNG", "PHP", "UDP",
|
||||
"TPS", "RX", "ASCII", "CD-ROM", "CGI", "CPU", "DDR", "DHCP", "BIOS", "IDE", "IP", "MAC",
|
||||
"MP3", "AAC", "PPPoE", "SSD", "SDRAM", "VGA", "XHTML", "Y2K", "GUI", "EPS", "SATA", "SAS",
|
||||
"VM", "LAN", "DRAM", "L3", "L2", "DNS", "UEFI", "UTF-8", "DDOS", "HDMI", "GPU", "RSA", "AES",
|
||||
"L7", "ISO", "HTTPS", "SSH", "SIMD", "GNU", "PDF", "LPDDR5", "ARM", "RISC", "CISC", "802.11",
|
||||
"5G", "LTE", "3GPP", "MP4", "2FA", "RCE", "JBIG2", "ISA", "PCIe", "NVMe", "SHA", "QR", "CUDA",
|
||||
"IPv4", "IPv6", "ARP", "DES", "IEEE", "NoSQL", "UTF-16", "ADSL", "ABI", "TX", "HEVC", "AVC",
|
||||
"AV1", "ASLR", "ECC", "HBA", "HAL", "SMT", "RPC", "JIT", "LCD", "LED", "MIME", "MIMO", "LZW",
|
||||
"LGA", "OFDM", "ORM", "PCRE", "POP3", "SMTP", "802.3", "PSU", "RGB", "VLIW", "VPS", "VPN",
|
||||
"XMPP", "IRC", "GNSS"},
|
||||
adjectives =
|
||||
{"auxiliary", "primary", "back-end", "digital", "open-source", "virtual", "cross-platform",
|
||||
"redundant", "online", "haptic", "multi-byte", "Bluetooth", "wireless", "1080p", "neural",
|
||||
"optical", "solid state", "mobile", "unicode", "backup", "high speed", "56k", "analog",
|
||||
"fiber optic", "central", "visual", "ethernet", "Griswold", "binary", "ternary",
|
||||
"secondary", "web-scale", "persistent", "Java", "cloud", "hyperscale", "seconday", "cloudscale",
|
||||
"software-defined", "hyperconverged", "x86", "Ethernet", "WiFi", "4k", "gigabit", "neuromorphic",
|
||||
"sparse", "machine learning", "authentication", "multithreaded", "statistical", "nonlinear",
|
||||
"photonic", "streaming", "concurrent", "memory-safe", "C", "electromagnetic", "nanoscale",
|
||||
"high-level", "low-level", "distributed", "accelerated", "base64", "purely functional",
|
||||
"serial", "parallel", "compute", "graphene", "recursive", "denormalized", "orbital",
|
||||
"networked", "autonomous", "applicative", "acausal", "hardened", "category-theoretic",
|
||||
"ultrasonic"},
|
||||
nouns =
|
||||
{"driver", "protocol", "bandwidth", "panel", "microchip", "program", "port", "card",
|
||||
"array", "interface", "system", "sensor", "firewall", "hard drive", "pixel", "alarm",
|
||||
"feed", "monitor", "application", "transmitter", "bus", "circuit", "capacitor", "matrix",
|
||||
"address", "form factor", "array", "mainframe", "processor", "antenna", "transistor",
|
||||
"virus", "malware", "spyware", "network", "internet", "field", "acutator", "tetryon",
|
||||
"beacon", "resonator", "diode", "oscillator", "vertex", "shader", "cache", "platform",
|
||||
"hyperlink", "device", "encryption", "node", "headers", "botnet", "applet", "satellite",
|
||||
"Unix", "byte", "Web 3", "metaverse", "microservice", "ultrastructure", "subsystem",
|
||||
"call stack", "gate", "filesystem", "file", "database", "bitmap", "Bloom filter", "tensor",
|
||||
"hash table", "tree", "optics", "silicon", "hardware", "uplink", "script", "tunnel",
|
||||
"server", "barcode", "exploit", "vulnerability", "backdoor", "computer", "page",
|
||||
"regex", "socket", "platform", "IP", "compiler", "interpreter", "nanochip", "certificate",
|
||||
"API", "bitrate", "acknowledgement", "layout", "satellite", "shell", "MAC", "PHY", "VLAN",
|
||||
"SoC", "assembler", "interrupt", "directory", "display", "functor", "bits", "logic",
|
||||
"sequence", "procedure", "subnet", "invariant", "monad", "endofunctor", "borrow checker"},
|
||||
participles =
|
||||
{"backing up", "bypassing", "hacking", "overriding", "compressing", "copying", "navigating",
|
||||
"indexing", "connecting", "generating", "quantifying", "calculating", "synthesizing",
|
||||
"inputting", "transmitting", "programming", "rebooting", "parsing", "shutting down",
|
||||
"injecting", "transcoding", "encoding", "attaching", "disconnecting", "networking",
|
||||
"triaxilating", "multiplexing", "interplexing", "rewriting", "transducing",
|
||||
"acutating", "polarising", "diffracting", "modulating", "demodulating", "vectorizing",
|
||||
"compiling", "jailbreaking", "proxying", "Linuxing", "quantizing", "multiplying",
|
||||
"scanning", "interpreting", "routing", "rerouting", "tunnelling", "randomizing",
|
||||
"underwriting", "accessing", "locating", "rotating", "invoking", "utilizing",
|
||||
"normalizing", "hijacking", "integrating", "type-checking", "uploading", "downloading",
|
||||
"allocating", "receiving", "decoding"}
|
||||
}
|
||||
|
||||
local hcresponses = {
|
||||
'Authorizing ',
|
||||
'Authorized...',
|
||||
'Access Granted..',
|
||||
'Going Deeper....',
|
||||
'Compression Complete.',
|
||||
'Compilation of Data Structures Complete..',
|
||||
'Entering Security Console...',
|
||||
'Encryption Unsuccesful Attempting Retry...',
|
||||
'Waiting for response...',
|
||||
'....Searching...',
|
||||
'Calculating Space Requirements',
|
||||
"nmap 192.168.1.0/24 -p0-65535",
|
||||
"Rescanning Databases...",
|
||||
"Hacking all IPs simultaneously...",
|
||||
"All webs down, activating proxy",
|
||||
"rm -rf --no-preserve-root /",
|
||||
"Hacking military satellite network...",
|
||||
"Guessing password...",
|
||||
"Trying 'password123'",
|
||||
"Activating Extra Monitors...",
|
||||
"Typing Faster...",
|
||||
"Checking StackOverflow",
|
||||
"Locating crossbows...",
|
||||
"Enabling algorithms and coding",
|
||||
"Collapsing Subdirectories...",
|
||||
"Enabling Ping Wall...",
|
||||
"Obtaining sunglasses...",
|
||||
"Rehashing hashes.",
|
||||
"Randomizing numbers.",
|
||||
"Greening text...",
|
||||
"Accessing system32",
|
||||
"'); DROP DATABASE system;--",
|
||||
"...Nesting VPNs...",
|
||||
"Opening Wireshark.",
|
||||
"Breaking fifth wall....",
|
||||
"Flipping arrows and applying yoneda lemma",
|
||||
"Rewriting in Rust"
|
||||
}
|
||||
|
||||
local function choose(arr)
|
||||
return arr[math.random(1, #arr)]
|
||||
end
|
||||
|
||||
local function capitalize_first(s)
|
||||
return s:sub(1, 1):upper() .. s:sub(2)
|
||||
end
|
||||
|
||||
local function jargon()
|
||||
local choice = math.random()
|
||||
local thing
|
||||
if choice > 0.5 then
|
||||
thing = choose(jargonWords.adjectives) .. " " .. choose(jargonWords.acronyms)
|
||||
elseif choice > 0.1 then
|
||||
thing = choose(jargonWords.acronyms) .. " " .. choose(jargonWords.adjectives)
|
||||
else
|
||||
thing = choose(jargonWords.adjectives) .. " " .. choose(jargonWords.acronyms) .. " " .. choose(jargonWords.nouns)
|
||||
end
|
||||
thing = thing .. " " .. choose(jargonWords.nouns)
|
||||
local out
|
||||
if math.random() > 0.3 then
|
||||
out = choose(jargonWords.participles) .. " " .. thing
|
||||
else
|
||||
out = thing .. " " .. choose(jargonWords.participles)
|
||||
:gsub("writing", "wrote")
|
||||
:gsub("breaking", "broken")
|
||||
:gsub("overriding", "overriden")
|
||||
:gsub("shutting", "shut")
|
||||
:gsub("ying", "ied")
|
||||
:gsub("ing", "ed")
|
||||
end
|
||||
return capitalize_first(out)
|
||||
end
|
||||
|
||||
local function lgen(cs, n)
|
||||
local out = {}
|
||||
for i = 1, n do
|
||||
local r = math.random(1, #cs)
|
||||
table.insert(out, cs:sub(r, r))
|
||||
end
|
||||
return table.concat(out)
|
||||
end
|
||||
|
||||
local function scarynum()
|
||||
local r = math.random()
|
||||
if r > 0.7 then
|
||||
return lgen("0123456789abcdef", 16)
|
||||
elseif r > 0.4 then
|
||||
return lgen("01", 32)
|
||||
else
|
||||
return tostring(math.random())
|
||||
end
|
||||
end
|
||||
|
||||
while true do
|
||||
local r = math.random(1, 3)
|
||||
if r == 1 then
|
||||
print(jargon())
|
||||
elseif r == 2 then
|
||||
for i = 1, math.random(1, 3) do write(scarynum() .. " ") end
|
||||
print()
|
||||
else
|
||||
print(choose(hcresponses))
|
||||
end
|
||||
if math.random() < 0.005 then
|
||||
term.setTextColor(colors.red)
|
||||
print "Terminated"
|
||||
term.setTextColor(colors.green)
|
||||
end
|
||||
sleep(math.random() * 0.5)
|
||||
end
|
@ -1,21 +0,0 @@
|
||||
print("ID", os.getComputerID())
|
||||
print("Label", os.getComputerLabel())
|
||||
print("UUID", potatOS.uuid)
|
||||
print("Build", potatOS.build)
|
||||
print("Host", _ORIGHOST or _HOST)
|
||||
local disks = {}
|
||||
for _, n in pairs(peripheral.getNames()) do
|
||||
if peripheral.getType(n) == "drive" then
|
||||
local d = peripheral.wrap(n)
|
||||
if d.hasData() then
|
||||
table.insert(disks, {n, tostring(d.getDiskID() or "[ID?]"), d.getDiskLabel()})
|
||||
end
|
||||
end
|
||||
end
|
||||
if #disks > 0 then
|
||||
print "Disks:"
|
||||
textutils.tabulate(unpack(disks))
|
||||
end
|
||||
if potatOS.get_ip() then
|
||||
print("IP", potatOS.get_ip())
|
||||
end
|
@ -1,2 +0,0 @@
|
||||
potatOS.init_screens()
|
||||
print "Done!"
|
@ -1,12 +0,0 @@
|
||||
-- PotatOS Intelligence interface
|
||||
if ... == "wipe_memory" then
|
||||
print "Have you acquired PIERB approval to wipe memory? (y/n): "
|
||||
if read():lower():match "y" then
|
||||
potatOS.assistant_history = {}
|
||||
potatOS.save_assistant_state()
|
||||
print "Done."
|
||||
end
|
||||
else
|
||||
local w, h = term.getSize()
|
||||
potatOS.assistant(h)
|
||||
end
|
@ -1,41 +0,0 @@
|
||||
local mat = term.current()
|
||||
mat.setPaletteColor(colors.black, 0)
|
||||
mat.setPaletteColor(colors.green, 0x15b01a)
|
||||
mat.setPaletteColor(colors.lime, 0x01ff07)
|
||||
if mat.setTextScale then mat.setTextScale(0.5) end
|
||||
local w, h = mat.getSize()
|
||||
|
||||
local function rchar()
|
||||
return string.char(math.random(0, 255))
|
||||
end
|
||||
|
||||
local function wrap(x)
|
||||
return (x - 1) % h + 1
|
||||
end
|
||||
|
||||
local cols = {}
|
||||
for i = 1, w do
|
||||
local base = math.random(1, h)
|
||||
table.insert(cols, { base, base + math.random(1, h - 5) })
|
||||
end
|
||||
|
||||
while true do
|
||||
for x, col in pairs(cols) do
|
||||
local start = col[1]
|
||||
local endp = col[2]
|
||||
mat.setCursorPos(x, start)
|
||||
mat.write " "
|
||||
mat.setCursorPos(x, wrap(endp - 1))
|
||||
mat.setTextColor(colors.green)
|
||||
mat.write(rchar())
|
||||
mat.setTextColor(colors.lime)
|
||||
mat.setCursorPos(x, endp)
|
||||
mat.write(rchar())
|
||||
col[1] = col[1] + 1
|
||||
col[2] = col[2] + 1
|
||||
|
||||
col[1] = wrap(col[1])
|
||||
col[2] = wrap(col[2])
|
||||
end
|
||||
sleep(0.1)
|
||||
end
|
@ -1,16 +0,0 @@
|
||||
local args = table.concat({...}, " ")
|
||||
local logtext
|
||||
if args:match "old" then
|
||||
logtext = potatOS.read "old.log"
|
||||
else
|
||||
logtext = potatOS.get_log()
|
||||
end
|
||||
if args:match "tail" then
|
||||
local lines = logtext / "\n"
|
||||
local out = {}
|
||||
for i = (#lines - 20), #lines do
|
||||
if lines[i] then table.insert(out, lines[i]) end
|
||||
end
|
||||
logtext = table.concat(out, "\n")
|
||||
end
|
||||
textutils.pagedPrint(logtext)
|
@ -1 +0,0 @@
|
||||
print(string.format("Layers of virtualization >= %d", potatOS.layers()))
|
@ -1 +0,0 @@
|
||||
print(potatOS.maxim())
|
@ -1 +0,0 @@
|
||||
print(string.reverse(potatOS.chuck_norris()))
|
@ -1 +0,0 @@
|
||||
potatOS.potatoNET()
|
@ -178,11 +178,7 @@ local function random_char()
|
||||
end
|
||||
|
||||
local colors = {}
|
||||
if duochrome_mode then
|
||||
colors = {"0", "f"}
|
||||
else
|
||||
for i = 0, 15 do table.insert(colors, ("%x"):format(i)) end
|
||||
end
|
||||
for i = 0, 15 do table.insert(colors, ("%x"):format(i)) end
|
||||
|
||||
local function random_pick(list)
|
||||
return list[math.random(1, #list)]
|
||||
|
@ -1,9 +0,0 @@
|
||||
-- Wait, why do we have this AND est?
|
||||
local key, value = ...
|
||||
key = key or ""
|
||||
if not value then print(textutils.serialise(potatOS.registry.get(key)))
|
||||
else
|
||||
if value == "" then value = nil
|
||||
elseif textutils.unserialise(value) ~= nil then value = textutils.unserialise(value) end
|
||||
potatOS.registry.set(key, value)
|
||||
end
|
@ -1 +0,0 @@
|
||||
if potatOS.tau then textutils.pagedPrint(potatOS.tau) else error "PotatOS tau missing - is PotatOS correctly installed?" end
|
@ -1,37 +0,0 @@
|
||||
local arg = ...
|
||||
local update = potatOS.threat_update():gsub("\n$", "")
|
||||
local bg = term.getBackgroundColor()
|
||||
local fg = term.getTextColor()
|
||||
term.setBackgroundColor(colors.black)
|
||||
local bgcol = potatOS.map_color(update:match "threat level is ([^\n]*)\n")
|
||||
local orig_black = {term.getPaletteColor(colors.black)}
|
||||
local orig_white = {term.getPaletteColor(colors.white)}
|
||||
term.setPaletteColor(colors.black, bgcol)
|
||||
local r, g, b = bit.band(bit.brshift(bgcol, 16), 0xFF), bit.band(bit.brshift(bgcol, 8), 0xFF), bit.band(bgcol, 0xFF)
|
||||
local avg_gray = (r + g + b) / 3
|
||||
term.setPaletteColor(colors.white, (r > 160 or g > 160 or b > 160) and 0 or 0xFFFFFF)
|
||||
term.clear()
|
||||
local fst = update:match "^([^\n]*)\n"
|
||||
local snd = update:match "\n(.*)$"
|
||||
local w, h = term.getSize()
|
||||
local BORDER = 2
|
||||
term.setCursorPos(1, h)
|
||||
local wi = window.create(term.current(), 1 + BORDER, 1 + BORDER, w - (2*BORDER), h - (2*BORDER))
|
||||
local old = term.redirect(wi)
|
||||
term.setBackgroundColor(colors.black)
|
||||
print(fst)
|
||||
print()
|
||||
print(snd)
|
||||
print()
|
||||
if arg == "headless" then
|
||||
if ccemux then ccemux.echo "ready" end
|
||||
while true do coroutine.yield() end
|
||||
else
|
||||
print "Press a key to continue..."
|
||||
os.pullEvent "char"
|
||||
term.redirect(old)
|
||||
term.setPaletteColor(colors.black, unpack(orig_black))
|
||||
term.setPaletteColor(colors.white, unpack(orig_white))
|
||||
term.setBackgroundColor(bg)
|
||||
term.setTextColor(fg)
|
||||
end
|
@ -1,4 +0,0 @@
|
||||
if potatOS.actually_really_uninstall then potatOS.actually_really_uninstall "76fde5717a89e332513d4f1e5b36f6cb" os.reboot()
|
||||
else
|
||||
potatOS.begin_uninstall_process()
|
||||
end
|
@ -1 +0,0 @@
|
||||
potatOS.update()
|
@ -1 +0,0 @@
|
||||
shell.run 'loading' term.clear() term.setCursorPos(1, 1) print 'Actually, nope.'
|
@ -1,49 +0,0 @@
|
||||
local function try_files(lst)
|
||||
for _, v in pairs(lst) do
|
||||
local z = potatOS.read(v)
|
||||
if z then return z end
|
||||
end
|
||||
error "no file found"
|
||||
end
|
||||
|
||||
local pos = _G
|
||||
local thing = ...
|
||||
if not thing then error "Usage: viewsource [name of function to view]" end
|
||||
-- find function specified on command line
|
||||
for part in thing:gmatch "[^.]+" do
|
||||
pos = pos[part]
|
||||
if not pos then error(thing .. " does not exist: " .. part) end
|
||||
end
|
||||
|
||||
local info = debug.getinfo(pos)
|
||||
if not info.linedefined or not info.lastlinedefined or not info.source or info.lastlinedefined == -1 then error "Is this a Lua function?" end
|
||||
local sourcen = info.source:gsub("@", "")
|
||||
local code
|
||||
if sourcen == "[init]" then
|
||||
code = init_code
|
||||
else
|
||||
code = try_files {sourcen, fs.combine("lib", sourcen), fs.combine("bin", sourcen), fs.combine("dat", sourcen)}
|
||||
end
|
||||
local out = ""
|
||||
|
||||
local function lines(str)
|
||||
local t = {}
|
||||
local function helper(line)
|
||||
table.insert(t, line)
|
||||
return ""
|
||||
end
|
||||
helper((str:gsub("(.-)\r?\n", helper)))
|
||||
return t
|
||||
end
|
||||
|
||||
for ix, line in pairs(lines(code)) do
|
||||
if ix >= info.linedefined and ix <= info.lastlinedefined then
|
||||
out = out .. line .. "\n"
|
||||
end
|
||||
end
|
||||
local filename = ".viewsource-" .. thing
|
||||
local f = fs.open(filename, "w")
|
||||
f.write(out)
|
||||
f.close()
|
||||
shell.run("edit", filename)
|
||||
fs.delete(filename)
|
@ -1 +0,0 @@
|
||||
print 'Foolish fool.' shell.run '/rom/programs/delete *' potatOS.update()
|
@ -1,145 +0,0 @@
|
||||
-- SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
|
||||
--
|
||||
-- SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
--[[- The [`cc.expect`] library provides helper functions for verifying that
|
||||
function arguments are well-formed and of the correct type.
|
||||
|
||||
@module cc.expect
|
||||
@since 1.84.0
|
||||
@changed 1.96.0 The module can now be called directly as a function, which wraps around `expect.expect`.
|
||||
@usage Define a basic function and check it has the correct arguments.
|
||||
|
||||
local expect = require "cc.expect"
|
||||
local expect, field = expect.expect, expect.field
|
||||
|
||||
local function add_person(name, info)
|
||||
expect(1, name, "string")
|
||||
expect(2, info, "table", "nil")
|
||||
|
||||
if info then
|
||||
print("Got age=", field(info, "age", "number"))
|
||||
print("Got gender=", field(info, "gender", "string", "nil"))
|
||||
end
|
||||
end
|
||||
|
||||
add_person("Anastazja") -- `info' is optional
|
||||
add_person("Kion", { age = 23 }) -- `gender' is optional
|
||||
add_person("Caoimhin", { age = 23, gender = true }) -- error!
|
||||
]]
|
||||
|
||||
local native_select, native_type = select, type
|
||||
|
||||
local function get_type_names(...)
|
||||
local types = table.pack(...)
|
||||
for i = types.n, 1, -1 do
|
||||
if types[i] == "nil" then table.remove(types, i) end
|
||||
end
|
||||
|
||||
if #types <= 1 then
|
||||
return tostring(...)
|
||||
else
|
||||
return table.concat(types, ", ", 1, #types - 1) .. " or " .. types[#types]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_display_type(value, t)
|
||||
-- Lua is somewhat inconsistent in whether it obeys __name just for values which
|
||||
-- have a per-instance metatable (so tables/userdata) or for everything. We follow
|
||||
-- Cobalt and only read the metatable for tables/userdata.
|
||||
if t ~= "table" and t ~= "userdata" then return t end
|
||||
|
||||
local metatable = debug.getmetatable(value)
|
||||
if not metatable then return t end
|
||||
|
||||
local name = rawget(metatable, "__name")
|
||||
if type(name) == "string" then return name else return t end
|
||||
end
|
||||
|
||||
--- Expect an argument to have a specific type.
|
||||
--
|
||||
-- @tparam number index The 1-based argument index.
|
||||
-- @param value The argument's value.
|
||||
-- @tparam string ... The allowed types of the argument.
|
||||
-- @return The given `value`.
|
||||
-- @throws If the value is not one of the allowed types.
|
||||
local function expect(index, value, ...)
|
||||
local t = native_type(value)
|
||||
for i = 1, native_select("#", ...) do
|
||||
if t == native_select(i, ...) then return value end
|
||||
end
|
||||
|
||||
-- If we can determine the function name with a high level of confidence, try to include it.
|
||||
local name
|
||||
local ok, info = pcall(debug.getinfo, 3, "nS")
|
||||
if ok and info.name and info.name ~= "" and info.what ~= "C" then name = info.name end
|
||||
|
||||
t = get_display_type(value, t)
|
||||
|
||||
local type_names = get_type_names(...)
|
||||
if name then
|
||||
error(("bad argument #%d to '%s' (%s expected, got %s)"):format(index, name, type_names, t), 3)
|
||||
else
|
||||
error(("bad argument #%d (%s expected, got %s)"):format(index, type_names, t), 3)
|
||||
end
|
||||
end
|
||||
|
||||
--- Expect an field to have a specific type.
|
||||
--
|
||||
-- @tparam table tbl The table to index.
|
||||
-- @tparam string index The field name to check.
|
||||
-- @tparam string ... The allowed types of the argument.
|
||||
-- @return The contents of the given field.
|
||||
-- @throws If the field is not one of the allowed types.
|
||||
local function field(tbl, index, ...)
|
||||
expect(1, tbl, "table")
|
||||
expect(2, index, "string")
|
||||
|
||||
local value = tbl[index]
|
||||
local t = native_type(value)
|
||||
for i = 1, native_select("#", ...) do
|
||||
if t == native_select(i, ...) then return value end
|
||||
end
|
||||
|
||||
t = get_display_type(value, t)
|
||||
|
||||
if value == nil then
|
||||
error(("field '%s' missing from table"):format(index), 3)
|
||||
else
|
||||
error(("bad field '%s' (%s expected, got %s)"):format(index, get_type_names(...), t), 3)
|
||||
end
|
||||
end
|
||||
|
||||
local function is_nan(num)
|
||||
return num ~= num
|
||||
end
|
||||
|
||||
--- Expect a number to be within a specific range.
|
||||
--
|
||||
-- @tparam number num The value to check.
|
||||
-- @tparam number min The minimum value, if nil then `-math.huge` is used.
|
||||
-- @tparam number max The maximum value, if nil then `math.huge` is used.
|
||||
-- @return The given `value`.
|
||||
-- @throws If the value is outside of the allowed range.
|
||||
-- @since 1.96.0
|
||||
local function range(num, min, max)
|
||||
expect(1, num, "number")
|
||||
min = expect(2, min, "number", "nil") or -math.huge
|
||||
max = expect(3, max, "number", "nil") or math.huge
|
||||
if min > max then
|
||||
error("min must be less than or equal to max)", 2)
|
||||
end
|
||||
|
||||
if is_nan(num) or num < min or num > max then
|
||||
error(("number outside of range (expected %s to be within %s and %s)"):format(num, min, max), 3)
|
||||
end
|
||||
|
||||
return num
|
||||
end
|
||||
|
||||
return setmetatable({
|
||||
expect = expect,
|
||||
field = field,
|
||||
range = range,
|
||||
}, { __call = function(_, ...) return expect(...) end })
|
@ -99,7 +99,7 @@ end
|
||||
|
||||
|
||||
local function encode_string(val)
|
||||
return '"' .. val:gsub('[%z\1-\31\\"\127-\255]', escape_char) .. '"'
|
||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||
end
|
||||
|
||||
|
||||
|
@ -1,246 +0,0 @@
|
||||
|
||||
-- Accesses the PotatOS Potatocloud(tm) Potatostore(tm). Used to implement Superglobals(tm) - like globals but on all computers.
|
||||
-- To be honest I should swap this out for a self-hosted thing like Kinto.
|
||||
--[[
|
||||
Fix for PS#4F329133
|
||||
JSONBin (https://jsonbin.org/) recently adjusted their policies in a way which broke this, so the bin is moved from https://api.jsonbin.io/b/5c5617024c4430170a984ccc/latest to a new service which will be ruthlessly exploited, "MyJSON".
|
||||
|
||||
Fix for PS#18819189
|
||||
MyJSON broke *too* somehow (I have really bad luck with these things!) so move from https://api.myjson.com/bins/150r92 to "JSONBin".
|
||||
|
||||
Fix for PS#8C4CB942
|
||||
The other JSONBin thing broke too so just implement it in RSAPI
|
||||
]]
|
||||
|
||||
return function(add_log, report_incident)
|
||||
function fetch(u, ...)
|
||||
if not http then error "No HTTP access" end
|
||||
local h,e = http.get(u, ...)
|
||||
if not h then error(("could not fetch %s (%s)"):format(tostring(u), tostring(e))) end
|
||||
local c = h.readAll()
|
||||
h.close()
|
||||
return c
|
||||
end
|
||||
|
||||
local bin_URL = "https://r.osmarks.net/superglobals/"
|
||||
local bin = {}
|
||||
local localbin = {}
|
||||
|
||||
function bin.get(k)
|
||||
if localbin[k] then
|
||||
return localbin[k]
|
||||
else
|
||||
local ok, err = pcall(function()
|
||||
local r = fetch(bin_URL .. textutils.urlEncode(tostring(k)), nil, true)
|
||||
local ok, err = pcall(json.decode, r)
|
||||
if not ok then return r end
|
||||
return err
|
||||
end)
|
||||
if not ok then add_log("superglobals fetch failed %s", tostring(err)) return nil end
|
||||
return err
|
||||
end
|
||||
end
|
||||
|
||||
function bin.set(k, v)
|
||||
local ok, err = pcall(function()
|
||||
b[k] = v
|
||||
local h, err = http.post(bin_URL .. textutils.urlEncode(tostring(k)), json.encode(v), nil, true)
|
||||
if not h then error(err) end
|
||||
end)
|
||||
if not ok then localbin[k] = v add_log("superglobals set failed %s", tostring(err)) end
|
||||
end
|
||||
|
||||
local bin_mt = {
|
||||
__index = function(_, k) return bin.get(k) end,
|
||||
__newindex = function(_, k, v) return bin.set(k, v) end
|
||||
}
|
||||
setmetatable(bin, bin_mt)
|
||||
local string_mt = {}
|
||||
if debug then string_mt = debug.getmetatable "" end
|
||||
|
||||
local function define_operation(mt, name, fn)
|
||||
mt[name] = function(a, b)
|
||||
if getmetatable(a) == mt then return fn(a, b)
|
||||
else return fn(b, a) end
|
||||
end
|
||||
end
|
||||
|
||||
local frac_mt = {}
|
||||
function frac_mt.__tostring(x)
|
||||
return ("[Fraction] %s/%s"):format(textutils.serialise(x.numerator), textutils.serialise(x.denominator))
|
||||
end
|
||||
define_operation(frac_mt, "__mul", function (a, b)
|
||||
return (a.numerator * b) / a.denominator
|
||||
end)
|
||||
|
||||
-- Add exciting random stuff to the string metatable.
|
||||
-- Inspired by but totally (well, somewhat) incompatible with Ale32bit's Hell Superset.
|
||||
function string_mt.__index(s, k)
|
||||
if type(k) == "number" then
|
||||
local c = string.sub(s, k, k)
|
||||
if c == "" then return nil else return c end
|
||||
end
|
||||
return _ENV.string[k] or bin.get(k)
|
||||
end
|
||||
function string_mt.__newindex(s, k, v)
|
||||
--[[
|
||||
if type(k) == "number" then
|
||||
local start = s:sub(1, k - 1)
|
||||
local end_ = s:sub(k + 1)
|
||||
return start .. v .. end_
|
||||
end
|
||||
]]
|
||||
return bin.set(k, v)
|
||||
end
|
||||
function string_mt.__add(lhs, rhs)
|
||||
return tostring(lhs) .. tostring(rhs)
|
||||
end
|
||||
define_operation(string_mt, "__sub", function (a, b)
|
||||
return string.gsub(a, b, "")
|
||||
end)
|
||||
function string_mt.__unm(a)
|
||||
return string.reverse(a)
|
||||
end
|
||||
-- http://lua-users.org/wiki/SplitJoin
|
||||
function string.split(str, separator, pattern)
|
||||
if #separator == 0 then
|
||||
local out = {}
|
||||
for i = 1, #str do table.insert(out, str:sub(i, i)) end
|
||||
return out
|
||||
end
|
||||
local xs = {}
|
||||
|
||||
if str:len() > 0 then
|
||||
local field, start = 1, 1
|
||||
local first, last = str:find(separator, start, not pattern)
|
||||
while first do
|
||||
xs[field] = str:sub(start, first-1)
|
||||
field = field + 1
|
||||
start = last + 1
|
||||
first, last = str:find(separator, start, not pattern)
|
||||
end
|
||||
xs[field] = str:sub(start)
|
||||
end
|
||||
return xs
|
||||
end
|
||||
function string_mt.__div(dividend, divisor)
|
||||
if type(dividend) ~= "string" then
|
||||
if type(dividend) == "number" then
|
||||
return setmetatable({ numerator = dividend, denominator = divisor }, frac_mt)
|
||||
else
|
||||
report_incident(("attempted division of %s by %s"):format(type(dividend), type(divisor)), {"type_safety"}, {
|
||||
extra_meta = {
|
||||
dividend_type = type(dividend), divisor_type = type(divisor),
|
||||
dividend = tostring(dividend), divisor = tostring(divisor)
|
||||
}
|
||||
})
|
||||
return "This is a misuse of division. This incident has been reported."
|
||||
end
|
||||
end
|
||||
if type(divisor) == "string" then return string.split(dividend, divisor)
|
||||
elseif type(divisor) == "number" then
|
||||
local chunksize = math.ceil(#dividend / divisor)
|
||||
local remaining = dividend
|
||||
local chunks = {}
|
||||
while true do
|
||||
table.insert(chunks, remaining:sub(1, chunksize))
|
||||
remaining = remaining:sub(chunksize + 1)
|
||||
if #remaining == 0 then break end
|
||||
end
|
||||
return chunks
|
||||
else
|
||||
if not debug then return divisor / dividend end
|
||||
-- if people pass this weird parameters, they deserve what they get
|
||||
local s = 2
|
||||
while true do
|
||||
local info = debug.getinfo(s)
|
||||
if not info then return -dividend / "" end
|
||||
if info.short_src ~= "[C]" then
|
||||
local ok, res = pcall(string.dump, info.func)
|
||||
if ok then
|
||||
return res / s
|
||||
end
|
||||
end
|
||||
s = s + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
local cache = {}
|
||||
function string_mt.__call(s, ...)
|
||||
if cache[s] then return cache[s](...)
|
||||
else
|
||||
local f, err = load(s)
|
||||
if err then error(err) end
|
||||
cache[s] = f
|
||||
return f(...)
|
||||
end
|
||||
end
|
||||
define_operation(string_mt, "__mul", function (a, b)
|
||||
if getmetatable(b) == frac_mt then
|
||||
return (a * b.numerator) / b.denominator
|
||||
end
|
||||
if type(b) == "number" then
|
||||
return string.rep(a, b)
|
||||
elseif type(b) == "table" then
|
||||
local z = {}
|
||||
for _, v in pairs(b) do
|
||||
table.insert(z, tostring(v))
|
||||
end
|
||||
return table.concat(z, a)
|
||||
elseif type(b) == "function" then
|
||||
local out = {}
|
||||
for i = 1, #a do
|
||||
table.insert(out, b(a:sub(i, i)))
|
||||
end
|
||||
return table.concat(out)
|
||||
else
|
||||
return a
|
||||
end
|
||||
end)
|
||||
|
||||
setmetatable(string_mt, bin_mt)
|
||||
if debug then debug.setmetatable(nil, bin_mt) end
|
||||
|
||||
-- Similar stuff for functions.
|
||||
local func_funcs = {}
|
||||
local func_mt = {__index=func_funcs}
|
||||
if debug then debug.setmetatable(function() end, func_mt) end
|
||||
function func_mt.__sub(lhs, rhs)
|
||||
return function(...) return lhs(rhs(...)) end
|
||||
end
|
||||
function func_mt.__add(lhs, rhs)
|
||||
return function(...) return rhs(lhs(...)) end
|
||||
end
|
||||
function func_mt.__concat(lhs, rhs)
|
||||
return function(...)
|
||||
return lhs(...), rhs(...), nil -- limit to two return values
|
||||
end
|
||||
end
|
||||
function func_mt.__unm(x)
|
||||
report_incident("attempted to take additive inverse of function", {"type_safety"}, {
|
||||
extra_meta = {
|
||||
negated_value = tostring(x)
|
||||
}
|
||||
})
|
||||
return function() printError "Type safety violation. This incident has been reported." end
|
||||
end
|
||||
function func_funcs.dump(x) return string.dump(x) end
|
||||
function func_funcs.info(x) return debug.getinfo(x) end
|
||||
function func_funcs.address(x) return (string.match(tostring(x), "%w+$")) end
|
||||
|
||||
-- Similar stuff for numbers too! NOBODY CAN ESCAPE!
|
||||
-- TODO: implement alternative mathematics.
|
||||
local num_funcs = {}
|
||||
local num_mt = {__index=num_funcs}
|
||||
num_mt.__call = function(x, ...)
|
||||
local out = x
|
||||
for _, y in pairs {...} do
|
||||
out = out + y
|
||||
end
|
||||
return out
|
||||
end
|
||||
if debug then debug.setmetatable(0, num_mt) end
|
||||
function num_funcs.tostring(x) return tostring(x) end
|
||||
function num_funcs.isNaN(x) return x ~= x end
|
||||
function num_funcs.isInf(x) return math.abs(x) == math.huge end
|
||||
end
|
@ -1,145 +0,0 @@
|
||||
-- thanks to valued user 6_4 for the suggestion
|
||||
|
||||
local function different_to_global(candidate_fs)
|
||||
local seen = {}
|
||||
for _, i in pairs(fs.list "") do
|
||||
seen[i] = true
|
||||
end
|
||||
for _, i in pairs(candidate_fs.list "") do
|
||||
if not seen[i] then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function is_probably_filesystem(x)
|
||||
if type(x) ~= "table" then return false end
|
||||
local keys = {
|
||||
"open", "exists", "delete", "makeDir", "list", "combine", "getSize", "isDir", "move", "find", "getFreeSpace", "getDrive"
|
||||
}
|
||||
for _, k in pairs(keys) do
|
||||
if type(x[k]) ~= "function" then return false end
|
||||
end
|
||||
return different_to_global(x)
|
||||
end
|
||||
|
||||
local function harvest_upvalues(fn)
|
||||
local i = 1
|
||||
while true do
|
||||
local ok, name, value = pcall(debug.getupvalue, fn, i)
|
||||
if not ok then return end
|
||||
if name == nil then break end
|
||||
if is_probably_filesystem(value) then
|
||||
return value
|
||||
elseif type(value) == "table" and value.fs and is_probably_filesystem(value.fs) then
|
||||
return value.fs
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
local dgetfenv = (getfenv or (debug and debug.getfenv))
|
||||
local function scan_environment(fn)
|
||||
local k = dgetfenv(fn).fs
|
||||
if is_probably_filesystem(k) then return k end
|
||||
end
|
||||
|
||||
local function scan_stack(thread)
|
||||
local level = 1
|
||||
while debug.getinfo(thread, level) do
|
||||
local index = 1
|
||||
repeat
|
||||
local name, value = debug.getlocal(thread, level, index)
|
||||
if is_probably_filesystem(value) then return value end
|
||||
if type(value) == "function" then
|
||||
local ok, value = pcall(harvest_upvalues, value)
|
||||
if ok and value then return value end
|
||||
ok, value = pcall(scan_environment, value)
|
||||
if ok and value then return value end
|
||||
end
|
||||
index = index + 1
|
||||
until not name
|
||||
level = level + 1
|
||||
end
|
||||
end
|
||||
|
||||
local escapes = {
|
||||
load_env = function()
|
||||
local k = dgetfenv(load("")).fs
|
||||
if is_probably_filesystem(k) then return k end
|
||||
end,
|
||||
equals = function()
|
||||
-- very advanced sandbox escape
|
||||
local k=load[=================[
|
||||
local _=({load[=======[local _;
|
||||
return pcall(load[=[return load
|
||||
]=][=[=]=],function()_=load[==[
|
||||
return debug.getinfo(#[=[===]=]
|
||||
).func[=[return fs]=][=[]=]]==]
|
||||
[=[]=]end),_]=======][=[==]=]})
|
||||
[#[=======[==]=======]]return _
|
||||
]=================][===[==]===]
|
||||
if is_probably_filesystem(k) then return k end
|
||||
end,
|
||||
getfenv = function()
|
||||
for _, v in pairs(fs) do
|
||||
local res = scan_environment(v)
|
||||
if res then return res end
|
||||
end
|
||||
for _, v in pairs(os) do
|
||||
local res = scan_environment(v)
|
||||
if res then return res end
|
||||
end
|
||||
end,
|
||||
upvalue = function()
|
||||
for _, v in pairs(fs) do
|
||||
local res = harvest_upvalues(v)
|
||||
if res then return res end
|
||||
end
|
||||
for _, v in pairs(os) do
|
||||
local res = harvest_upvalues(v)
|
||||
if res then return res end
|
||||
end
|
||||
end,
|
||||
getfenv_stack_level = function()
|
||||
local i = 1
|
||||
while true do
|
||||
local res = getfenv(i).fs
|
||||
if is_probably_filesystem(res) then
|
||||
return res
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
end,
|
||||
scan_most_threads = function()
|
||||
if not debug then return end
|
||||
if not (debug.getinfo and debug.getlocal) then return end
|
||||
local running = coroutine.running()
|
||||
local threads_to_scan = {}
|
||||
local old_resume = coroutine.resume
|
||||
coroutine.resume = function(...)
|
||||
threads_to_scan[coroutine.running()] = true
|
||||
threads_to_scan[...] = true
|
||||
if ... == running then
|
||||
coroutine.resume = old_resume
|
||||
end
|
||||
return old_resume(...)
|
||||
end
|
||||
sleep(0)
|
||||
for thread, _ in pairs(threads_to_scan) do
|
||||
if type(thread) == "thread" then
|
||||
local ok, value = pcall(scan_stack, thread)
|
||||
if ok and value then return value end
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
return function()
|
||||
for name, escape in pairs(escapes) do
|
||||
local ok, err = pcall(escape)
|
||||
print(name, ok, err)
|
||||
if ok and err then
|
||||
return err
|
||||
end
|
||||
end
|
||||
end
|
@ -1,59 +0,0 @@
|
||||
local sandboxlib = {}
|
||||
|
||||
local processhasgrant = process.has_grant
|
||||
local processrestriction = process.restriction
|
||||
local pairs = pairs
|
||||
local setmetatable = setmetatable
|
||||
local error = error
|
||||
local tostring = tostring
|
||||
|
||||
function sandboxlib.create_sentinel(name)
|
||||
return {name}
|
||||
end
|
||||
|
||||
function sandboxlib.dispatch_if_restricted(rkey, original, restricted)
|
||||
local out = {}
|
||||
for k, v in pairs(restricted) do
|
||||
if not original[k] then
|
||||
out[k] = v
|
||||
end
|
||||
end
|
||||
for k, v in pairs(original) do
|
||||
out[k] = function(...)
|
||||
if processrestriction(rkey) then
|
||||
if not restricted[k] then error("internal error: missing " .. tostring(k)) end
|
||||
return restricted[k](...)
|
||||
end
|
||||
return v(...)
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
function sandboxlib.allow_whitelisted(rkey, original, whitelist, fallback)
|
||||
local fallback = fallback or {}
|
||||
local whitelist_lookup = {}
|
||||
for _, v in pairs(whitelist) do
|
||||
whitelist_lookup[v] = true
|
||||
end
|
||||
local out = {}
|
||||
for k, v in pairs(original) do
|
||||
if whitelist_lookup[k] then
|
||||
out[k] = v
|
||||
else
|
||||
out[k] = function(...)
|
||||
if processrestriction(rkey) then
|
||||
if not fallback[k] then
|
||||
error("Security violation: " .. k)
|
||||
else
|
||||
return fallback[k](...)
|
||||
end
|
||||
end
|
||||
return v(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
return sandboxlib
|
@ -139,11 +139,42 @@ local function digest(data)
|
||||
|
||||
data = preprocess(data)
|
||||
local C = {upack(H)}
|
||||
local dummy = ("%07x"):format(math.random(0, 0xFFFFFFF))
|
||||
for i = 1, #data do C = digestblock(data[i], C) os.queueEvent(dummy) coroutine.yield(dummy) end
|
||||
for i = 1, #data do C = digestblock(data[i], C) end
|
||||
return toBytes(C, 8)
|
||||
end
|
||||
|
||||
local function hmac(data, key)
|
||||
local data = type(data) == "table" and {upack(data)} or to_bytes(tostring(data))
|
||||
local key = type(key) == "table" and {upack(key)} or to_bytes(tostring(key))
|
||||
|
||||
local blocksize = 64
|
||||
|
||||
key = #key > blocksize and digest(key) or key
|
||||
|
||||
local ipad = {}
|
||||
local opad = {}
|
||||
local padded_key = {}
|
||||
|
||||
for i = 1, blocksize do
|
||||
ipad[i] = bxor(0x36, key[i] or 0)
|
||||
opad[i] = bxor(0x5C, key[i] or 0)
|
||||
end
|
||||
|
||||
for i = 1, #data do
|
||||
ipad[blocksize+i] = data[i]
|
||||
end
|
||||
|
||||
ipad = digest(ipad)
|
||||
|
||||
for i = 1, blocksize do
|
||||
padded_key[i] = opad[i]
|
||||
padded_key[blocksize+i] = ipad[i]
|
||||
end
|
||||
|
||||
return digest(padded_key)
|
||||
end
|
||||
|
||||
return {
|
||||
hmac = hmac,
|
||||
digest = digest
|
||||
}
|
@ -66,8 +66,7 @@ local function xpcall_with(fn, ...)
|
||||
local res = table.pack(_xpcall(function() return fn(unpack(args)) end, traceback)) if not res[1] then trace = traceback("stack_trace.lua:1:") end
|
||||
local ok, err = res[1], res[2]
|
||||
|
||||
-- PS#EAB415D8: CC now uses error sentinel things in some places; we do not want to make those strings
|
||||
if not ok and err ~= nil and type(err) == "string" then
|
||||
if not ok and err ~= nil then
|
||||
trace = trim_traceback(err, trace)
|
||||
|
||||
-- Find the position where the stack traceback actually starts
|
||||
|
@ -1,590 +0,0 @@
|
||||
-- SPDX-FileCopyrightText: 2017 Daniel Ratcliffe
|
||||
--
|
||||
-- SPDX-License-Identifier: LicenseRef-CCPL
|
||||
|
||||
--[[- A [terminal redirect][`term.Redirect`] occupying a smaller area of an
|
||||
existing terminal. This allows for easy definition of spaces within the display
|
||||
that can be written/drawn to, then later redrawn/repositioned/etc as need
|
||||
be. The API itself contains only one function, [`window.create`], which returns
|
||||
the windows themselves.
|
||||
|
||||
Windows are considered terminal objects - as such, they have access to nearly
|
||||
all the commands in the term API (plus a few extras of their own, listed within
|
||||
said API) and are valid targets to redirect to.
|
||||
|
||||
Each window has a "parent" terminal object, which can be the computer's own
|
||||
display, a monitor, another window or even other, user-defined terminal
|
||||
objects. Whenever a window is rendered to, the actual screen-writing is
|
||||
performed via that parent (or, if that has one too, then that parent, and so
|
||||
forth). Bear in mind that the cursor of a window's parent will hence be moved
|
||||
around etc when writing a given child window.
|
||||
|
||||
Windows retain a memory of everything rendered "through" them (hence acting as
|
||||
display buffers), and if the parent's display is wiped, the window's content can
|
||||
be easily redrawn later. A window may also be flagged as invisible, preventing
|
||||
any changes to it from being rendered until it's flagged as visible once more.
|
||||
|
||||
A parent terminal object may have multiple children assigned to it, and windows
|
||||
may overlap. For example, the Multishell system functions by assigning each tab
|
||||
a window covering the screen, each using the starting terminal display as its
|
||||
parent, and only one of which is visible at a time.
|
||||
|
||||
@module window
|
||||
@since 1.6
|
||||
]]
|
||||
|
||||
local expect = require "cc_expect".expect
|
||||
|
||||
local tHex = {
|
||||
[colors.white] = "0",
|
||||
[colors.orange] = "1",
|
||||
[colors.magenta] = "2",
|
||||
[colors.lightBlue] = "3",
|
||||
[colors.yellow] = "4",
|
||||
[colors.lime] = "5",
|
||||
[colors.pink] = "6",
|
||||
[colors.gray] = "7",
|
||||
[colors.lightGray] = "8",
|
||||
[colors.cyan] = "9",
|
||||
[colors.purple] = "a",
|
||||
[colors.blue] = "b",
|
||||
[colors.brown] = "c",
|
||||
[colors.green] = "d",
|
||||
[colors.red] = "e",
|
||||
[colors.black] = "f",
|
||||
}
|
||||
|
||||
local type = type
|
||||
local string_rep = string.rep
|
||||
local string_sub = string.sub
|
||||
|
||||
--[[- Returns a terminal object that is a space within the specified parent
|
||||
terminal object. This can then be used (or even redirected to) in the same
|
||||
manner as eg a wrapped monitor. Refer to [the term API][`term`] for a list of
|
||||
functions available to it.
|
||||
|
||||
[`term`] itself may not be passed as the parent, though [`term.native`] is
|
||||
acceptable. Generally, [`term.current`] or a wrapped monitor will be most
|
||||
suitable, though windows may even have other windows assigned as their
|
||||
parents.
|
||||
|
||||
@tparam term.Redirect parent The parent terminal redirect to draw to.
|
||||
@tparam number nX The x coordinate this window is drawn at in the parent terminal
|
||||
@tparam number nY The y coordinate this window is drawn at in the parent terminal
|
||||
@tparam number nWidth The width of this window
|
||||
@tparam number nHeight The height of this window
|
||||
@tparam[opt] boolean bStartVisible Whether this window is visible by
|
||||
default. Defaults to `true`.
|
||||
@treturn Window The constructed window
|
||||
@since 1.6
|
||||
@usage Create a smaller window, fill it red and write some text to it.
|
||||
|
||||
local my_window = window.create(term.current(), 1, 1, 20, 5)
|
||||
my_window.setBackgroundColour(colours.red)
|
||||
my_window.setTextColour(colours.white)
|
||||
my_window.clear()
|
||||
my_window.write("Testing my window!")
|
||||
|
||||
@usage Create a smaller window and redirect to it.
|
||||
|
||||
local my_window = window.create(term.current(), 1, 1, 25, 5)
|
||||
term.redirect(my_window)
|
||||
print("Writing some long text which will wrap around and show the bounds of this window.")
|
||||
|
||||
]]
|
||||
|
||||
local seq_counter = 0
|
||||
|
||||
local function create_window(parent)
|
||||
expect(1, parent, "table")
|
||||
local nX, nY = 1, 1
|
||||
local nWidth, nHeight = parent.getSize()
|
||||
|
||||
if parent == term then
|
||||
error("term is not a recommended window parent, try term.current() instead", 2)
|
||||
end
|
||||
|
||||
local sEmptySpaceLine
|
||||
local tEmptyColorLines = {}
|
||||
local function createEmptyLines(nWidth)
|
||||
sEmptySpaceLine = string_rep(" ", nWidth)
|
||||
for n = 0, 15 do
|
||||
local nColor = 2 ^ n
|
||||
local sHex = tHex[nColor]
|
||||
tEmptyColorLines[nColor] = string_rep(sHex, nWidth)
|
||||
end
|
||||
end
|
||||
|
||||
createEmptyLines(nWidth)
|
||||
|
||||
-- Setup
|
||||
local bVisible = true
|
||||
local nCursorX = 1
|
||||
local nCursorY = 1
|
||||
local bCursorBlink = false
|
||||
local nTextColor = colors.white
|
||||
local nBackgroundColor = colors.black
|
||||
local tLines = {}
|
||||
local wseq_counter
|
||||
|
||||
local function raw_resize(new_width, new_height)
|
||||
if new_width and new_height then
|
||||
local tNewLines = {}
|
||||
createEmptyLines(new_width)
|
||||
local sEmptyText = sEmptySpaceLine
|
||||
local sEmptyTextColor = tEmptyColorLines[nTextColor]
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
||||
for y = 1, new_height do
|
||||
if y > nHeight then
|
||||
tNewLines[y] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
|
||||
else
|
||||
local tOldLine = tLines[y]
|
||||
if new_width == nWidth then
|
||||
tNewLines[y] = tOldLine
|
||||
elseif new_width < nWidth then
|
||||
tNewLines[y] = {
|
||||
string_sub(tOldLine[1], 1, new_width),
|
||||
string_sub(tOldLine[2], 1, new_width),
|
||||
string_sub(tOldLine[3], 1, new_width),
|
||||
}
|
||||
else
|
||||
tNewLines[y] = {
|
||||
tOldLine[1] .. string_sub(sEmptyText, nWidth + 1, new_width),
|
||||
tOldLine[2] .. string_sub(sEmptyTextColor, nWidth + 1, new_width),
|
||||
tOldLine[3] .. string_sub(sEmptyBackgroundColor, nWidth + 1, new_width),
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
nWidth = new_width
|
||||
nHeight = new_height
|
||||
tLines = tNewLines
|
||||
end
|
||||
end
|
||||
|
||||
local function increment_seq()
|
||||
seq_counter = seq_counter + 1
|
||||
wseq_counter = seq_counter
|
||||
end
|
||||
|
||||
increment_seq()
|
||||
|
||||
do
|
||||
local sEmptyText = sEmptySpaceLine
|
||||
local sEmptyTextColor = tEmptyColorLines[nTextColor]
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
||||
for y = 1, nHeight do
|
||||
tLines[y] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
|
||||
end
|
||||
end
|
||||
|
||||
-- Helper functions
|
||||
local function updateCursorPos()
|
||||
if nCursorX >= 1 and nCursorY >= 1 and
|
||||
nCursorX <= nWidth and nCursorY <= nHeight then
|
||||
parent.setCursorPos(nX + nCursorX - 1, nY + nCursorY - 1)
|
||||
else
|
||||
parent.setCursorPos(0, 0)
|
||||
end
|
||||
end
|
||||
|
||||
local function updateCursorBlink()
|
||||
parent.setCursorBlink(bCursorBlink)
|
||||
end
|
||||
|
||||
local function updateCursorColor()
|
||||
parent.setTextColor(nTextColor)
|
||||
end
|
||||
|
||||
local function redrawLine(n)
|
||||
increment_seq()
|
||||
local tLine = tLines[n]
|
||||
parent.setCursorPos(nX, nY + n - 1)
|
||||
parent.blit(tLine[1], tLine[2], tLine[3])
|
||||
end
|
||||
|
||||
local function redraw()
|
||||
for n = 1, nHeight do
|
||||
redrawLine(n)
|
||||
end
|
||||
end
|
||||
|
||||
local function internalBlit(sText, sTextColor, sBackgroundColor)
|
||||
local nStart = nCursorX
|
||||
local nEnd = nStart + #sText - 1
|
||||
if nCursorY >= 1 and nCursorY <= nHeight then
|
||||
if nStart <= nWidth and nEnd >= 1 then
|
||||
-- Modify line
|
||||
local tLine = tLines[nCursorY]
|
||||
if nStart == 1 and nEnd == nWidth then
|
||||
tLine[1] = sText
|
||||
tLine[2] = sTextColor
|
||||
tLine[3] = sBackgroundColor
|
||||
else
|
||||
local sClippedText, sClippedTextColor, sClippedBackgroundColor
|
||||
if nStart < 1 then
|
||||
local nClipStart = 1 - nStart + 1
|
||||
local nClipEnd = nWidth - nStart + 1
|
||||
sClippedText = string_sub(sText, nClipStart, nClipEnd)
|
||||
sClippedTextColor = string_sub(sTextColor, nClipStart, nClipEnd)
|
||||
sClippedBackgroundColor = string_sub(sBackgroundColor, nClipStart, nClipEnd)
|
||||
elseif nEnd > nWidth then
|
||||
local nClipEnd = nWidth - nStart + 1
|
||||
sClippedText = string_sub(sText, 1, nClipEnd)
|
||||
sClippedTextColor = string_sub(sTextColor, 1, nClipEnd)
|
||||
sClippedBackgroundColor = string_sub(sBackgroundColor, 1, nClipEnd)
|
||||
else
|
||||
sClippedText = sText
|
||||
sClippedTextColor = sTextColor
|
||||
sClippedBackgroundColor = sBackgroundColor
|
||||
end
|
||||
|
||||
local sOldText = tLine[1]
|
||||
local sOldTextColor = tLine[2]
|
||||
local sOldBackgroundColor = tLine[3]
|
||||
local sNewText, sNewTextColor, sNewBackgroundColor
|
||||
if nStart > 1 then
|
||||
local nOldEnd = nStart - 1
|
||||
sNewText = string_sub(sOldText, 1, nOldEnd) .. sClippedText
|
||||
sNewTextColor = string_sub(sOldTextColor, 1, nOldEnd) .. sClippedTextColor
|
||||
sNewBackgroundColor = string_sub(sOldBackgroundColor, 1, nOldEnd) .. sClippedBackgroundColor
|
||||
else
|
||||
sNewText = sClippedText
|
||||
sNewTextColor = sClippedTextColor
|
||||
sNewBackgroundColor = sClippedBackgroundColor
|
||||
end
|
||||
if nEnd < nWidth then
|
||||
local nOldStart = nEnd + 1
|
||||
sNewText = sNewText .. string_sub(sOldText, nOldStart, nWidth)
|
||||
sNewTextColor = sNewTextColor .. string_sub(sOldTextColor, nOldStart, nWidth)
|
||||
sNewBackgroundColor = sNewBackgroundColor .. string_sub(sOldBackgroundColor, nOldStart, nWidth)
|
||||
end
|
||||
|
||||
tLine[1] = sNewText
|
||||
tLine[2] = sNewTextColor
|
||||
tLine[3] = sNewBackgroundColor
|
||||
end
|
||||
|
||||
-- Redraw line
|
||||
if bVisible then
|
||||
redrawLine(nCursorY)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Move and redraw cursor
|
||||
nCursorX = nEnd + 1
|
||||
if bVisible then
|
||||
updateCursorColor()
|
||||
updateCursorPos()
|
||||
end
|
||||
end
|
||||
|
||||
--- The window object. Refer to the [module's documentation][`window`] for
|
||||
-- a full description.
|
||||
--
|
||||
-- @type Window
|
||||
-- @see term.Redirect
|
||||
local window = {}
|
||||
|
||||
function window.write(sText)
|
||||
sText = tostring(sText)
|
||||
internalBlit(sText, string_rep(tHex[nTextColor], #sText), string_rep(tHex[nBackgroundColor], #sText))
|
||||
end
|
||||
|
||||
function window.blit(sText, sTextColor, sBackgroundColor)
|
||||
if type(sText) ~= "string" then expect(1, sText, "string") end
|
||||
if type(sTextColor) ~= "string" then expect(2, sTextColor, "string") end
|
||||
if type(sBackgroundColor) ~= "string" then expect(3, sBackgroundColor, "string") end
|
||||
if #sTextColor ~= #sText or #sBackgroundColor ~= #sText then
|
||||
error("Arguments must be the same length", 2)
|
||||
end
|
||||
sTextColor = sTextColor:lower()
|
||||
sBackgroundColor = sBackgroundColor:lower()
|
||||
internalBlit(sText, sTextColor, sBackgroundColor)
|
||||
end
|
||||
|
||||
function window.clear()
|
||||
local sEmptyText = sEmptySpaceLine
|
||||
local sEmptyTextColor = tEmptyColorLines[nTextColor]
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
||||
for y = 1, nHeight do
|
||||
local line = tLines[y]
|
||||
line[1] = sEmptyText
|
||||
line[2] = sEmptyTextColor
|
||||
line[3] = sEmptyBackgroundColor
|
||||
end
|
||||
if bVisible then
|
||||
redraw()
|
||||
updateCursorColor()
|
||||
updateCursorPos()
|
||||
end
|
||||
end
|
||||
|
||||
function window.clearLine()
|
||||
if nCursorY >= 1 and nCursorY <= nHeight then
|
||||
local line = tLines[nCursorY]
|
||||
line[1] = sEmptySpaceLine
|
||||
line[2] = tEmptyColorLines[nTextColor]
|
||||
line[3] = tEmptyColorLines[nBackgroundColor]
|
||||
if bVisible then
|
||||
redrawLine(nCursorY)
|
||||
updateCursorColor()
|
||||
updateCursorPos()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function window.getCursorPos()
|
||||
return nCursorX, nCursorY
|
||||
end
|
||||
|
||||
function window.setCursorPos(x, y)
|
||||
if type(x) ~= "number" then expect(1, x, "number") end
|
||||
if type(y) ~= "number" then expect(2, y, "number") end
|
||||
nCursorX = math.floor(x)
|
||||
nCursorY = math.floor(y)
|
||||
if bVisible then
|
||||
updateCursorPos()
|
||||
end
|
||||
end
|
||||
|
||||
function window.setCursorBlink(blink)
|
||||
if type(blink) ~= "boolean" then expect(1, blink, "boolean") end
|
||||
bCursorBlink = blink
|
||||
if bVisible then
|
||||
updateCursorBlink()
|
||||
end
|
||||
end
|
||||
|
||||
function window.getCursorBlink()
|
||||
return bCursorBlink
|
||||
end
|
||||
|
||||
local function isColor()
|
||||
return parent.isColor()
|
||||
end
|
||||
|
||||
function window.isColor()
|
||||
return isColor()
|
||||
end
|
||||
|
||||
function window.isColour()
|
||||
return isColor()
|
||||
end
|
||||
|
||||
local function setTextColor(color)
|
||||
if type(color) ~= "number" then expect(1, color, "number") end
|
||||
if tHex[color] == nil then
|
||||
error("Invalid color (got " .. color .. ")" , 2)
|
||||
end
|
||||
|
||||
nTextColor = color
|
||||
if bVisible then
|
||||
updateCursorColor()
|
||||
end
|
||||
end
|
||||
|
||||
window.setTextColor = setTextColor
|
||||
window.setTextColour = setTextColor
|
||||
|
||||
function window.setPaletteColour(colour, r, g, b)
|
||||
return parent.setPaletteColour(colour, r, g, b)
|
||||
end
|
||||
|
||||
window.setPaletteColor = window.setPaletteColour
|
||||
|
||||
function window.getPaletteColour(colour)
|
||||
return parent.getPaletteColour(colour)
|
||||
end
|
||||
|
||||
window.getPaletteColor = window.getPaletteColour
|
||||
|
||||
local function setBackgroundColor(color)
|
||||
if type(color) ~= "number" then expect(1, color, "number") end
|
||||
if tHex[color] == nil then
|
||||
error("Invalid color (got " .. color .. ")", 2)
|
||||
end
|
||||
nBackgroundColor = color
|
||||
end
|
||||
|
||||
window.setBackgroundColor = setBackgroundColor
|
||||
window.setBackgroundColour = setBackgroundColor
|
||||
|
||||
function window.getSize()
|
||||
return nWidth, nHeight
|
||||
end
|
||||
|
||||
function window.scroll(n)
|
||||
if type(n) ~= "number" then expect(1, n, "number") end
|
||||
if n ~= 0 then
|
||||
local tNewLines = {}
|
||||
local sEmptyText = sEmptySpaceLine
|
||||
local sEmptyTextColor = tEmptyColorLines[nTextColor]
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
||||
for newY = 1, nHeight do
|
||||
local y = newY + n
|
||||
if y >= 1 and y <= nHeight then
|
||||
tNewLines[newY] = tLines[y]
|
||||
else
|
||||
tNewLines[newY] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
|
||||
end
|
||||
end
|
||||
tLines = tNewLines
|
||||
if bVisible then
|
||||
redraw()
|
||||
updateCursorColor()
|
||||
updateCursorPos()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function window.getTextColor()
|
||||
return nTextColor
|
||||
end
|
||||
|
||||
function window.getTextColour()
|
||||
return nTextColor
|
||||
end
|
||||
|
||||
function window.getBackgroundColor()
|
||||
return nBackgroundColor
|
||||
end
|
||||
|
||||
function window.getBackgroundColour()
|
||||
return nBackgroundColor
|
||||
end
|
||||
|
||||
--- Get the buffered contents of a line in this window.
|
||||
--
|
||||
-- @tparam number y The y position of the line to get.
|
||||
-- @treturn string The textual content of this line.
|
||||
-- @treturn string The text colours of this line, suitable for use with [`term.blit`].
|
||||
-- @treturn string The background colours of this line, suitable for use with [`term.blit`].
|
||||
-- @throws If `y` is not between 1 and this window's height.
|
||||
-- @since 1.84.0
|
||||
function window.getLine(y)
|
||||
if type(y) ~= "number" then expect(1, y, "number") end
|
||||
|
||||
if y < 1 or y > nHeight then
|
||||
error("Line is out of range.", 2)
|
||||
end
|
||||
|
||||
local line = tLines[y]
|
||||
return line[1], line[2], line[3]
|
||||
end
|
||||
|
||||
-- Other functions
|
||||
|
||||
--- Set whether this window is visible. Invisible windows will not be drawn
|
||||
-- to the screen until they are made visible again.
|
||||
--
|
||||
-- Making an invisible window visible will immediately draw it.
|
||||
--
|
||||
-- @tparam boolean visible Whether this window is visible.
|
||||
function window.setVisible(visible)
|
||||
if type(visible) ~= "boolean" then expect(1, visible, "boolean") end
|
||||
if bVisible ~= visible then
|
||||
bVisible = visible
|
||||
if bVisible then
|
||||
window.redraw()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Get whether this window is visible. Invisible windows will not be
|
||||
-- drawn to the screen until they are made visible again.
|
||||
--
|
||||
-- @treturn boolean Whether this window is visible.
|
||||
-- @see Window:setVisible
|
||||
-- @since 1.94.0
|
||||
function window.isVisible()
|
||||
return bVisible
|
||||
end
|
||||
--- Draw this window. This does nothing if the window is not visible.
|
||||
--
|
||||
-- @see Window:setVisible
|
||||
function window.redraw()
|
||||
if bVisible then
|
||||
redraw()
|
||||
updateCursorBlink()
|
||||
updateCursorColor()
|
||||
updateCursorPos()
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the current terminal's cursor to where this window's cursor is. This
|
||||
-- does nothing if the window is not visible.
|
||||
function window.restoreCursor()
|
||||
if bVisible then
|
||||
updateCursorBlink()
|
||||
updateCursorColor()
|
||||
updateCursorPos()
|
||||
end
|
||||
end
|
||||
|
||||
--- Get the position of the top left corner of this window.
|
||||
--
|
||||
-- @treturn number The x position of this window.
|
||||
-- @treturn number The y position of this window.
|
||||
function window.getPosition()
|
||||
return nX, nY
|
||||
end
|
||||
|
||||
function window.seq_counter()
|
||||
return wseq_counter
|
||||
end
|
||||
|
||||
--- Reposition or resize the given window.
|
||||
--
|
||||
-- This function also accepts arguments to change the size of this window.
|
||||
-- It is recommended that you fire a `term_resize` event after changing a
|
||||
-- window's, to allow programs to adjust their sizing.
|
||||
--
|
||||
-- @tparam number new_x The new x position of this window.
|
||||
-- @tparam number new_y The new y position of this window.
|
||||
-- @tparam[opt] number new_width The new width of this window.
|
||||
-- @tparam number new_height The new height of this window.
|
||||
-- @tparam[opt] term.Redirect new_parent The new redirect object this
|
||||
-- window should draw to.
|
||||
-- @changed 1.85.0 Add `new_parent` parameter.
|
||||
function window.reposition(new_x, new_y, new_width, new_height, new_parent)
|
||||
if type(new_x) ~= "number" then expect(1, new_x, "number") end
|
||||
if type(new_y) ~= "number" then expect(2, new_y, "number") end
|
||||
if new_width ~= nil or new_height ~= nil then
|
||||
expect(3, new_width, "number")
|
||||
expect(4, new_height, "number")
|
||||
end
|
||||
if new_parent ~= nil and type(new_parent) ~= "table" then expect(5, new_parent, "table") end
|
||||
|
||||
nX = new_x
|
||||
nY = new_y
|
||||
|
||||
if new_parent then parent = new_parent end
|
||||
|
||||
raw_resize(new_width, new_height)
|
||||
if bVisible then
|
||||
window.redraw()
|
||||
end
|
||||
end
|
||||
|
||||
function window.check_backing()
|
||||
local w, h = parent.getSize()
|
||||
if w ~= nWidth or h ~=- nHeight then
|
||||
raw_resize(w, h)
|
||||
end
|
||||
end
|
||||
|
||||
function window.backing()
|
||||
return parent
|
||||
end
|
||||
|
||||
if bVisible then
|
||||
window.redraw()
|
||||
end
|
||||
window.is_framebuffer = true
|
||||
|
||||
return window
|
||||
end
|
||||
|
||||
return create_window
|
@ -2,7 +2,7 @@
|
||||
local function copy(tabl)
|
||||
local new = {}
|
||||
for k, v in pairs(tabl) do
|
||||
if type(v) == "table" and tabl ~= v and v.COPY_EXACT == nil then
|
||||
if type(v) == "table" and tabl ~= v then
|
||||
new[k] = copy(v)
|
||||
else
|
||||
new[k] = v
|
||||
@ -77,10 +77,31 @@ local function add_to_table(t1, t2)
|
||||
end
|
||||
end
|
||||
|
||||
local fscombine, fsgetname, fsgetdir = fs.combine, fs.getName, fs.getDir
|
||||
-- Convert path to canonical form
|
||||
local function canonicalize(path)
|
||||
return fscombine(path, "")
|
||||
return fs.combine(path, "")
|
||||
end
|
||||
|
||||
-- Checks whether a path is in a directory
|
||||
local function path_in(p, dir)
|
||||
return starts_with(canonicalize(p), canonicalize(dir))
|
||||
end
|
||||
|
||||
local function make_mappings(root)
|
||||
return {
|
||||
["/disk"] = "/disk",
|
||||
["/rom"] = "/rom",
|
||||
default = root
|
||||
}
|
||||
end
|
||||
|
||||
local function get_root(path, mappings)
|
||||
for mapfrom, mapto in pairs(mappings) do
|
||||
if path_in(path, mapfrom) then
|
||||
return mapto, mapfrom
|
||||
end
|
||||
end
|
||||
return mappings.default, "/"
|
||||
end
|
||||
|
||||
-- Escapes lua patterns in a string. Should not be needed, but lua is stupid so the only string.replace thing is gsub
|
||||
@ -93,12 +114,19 @@ local function strip(p, root)
|
||||
return p:gsub("^" .. escape(canonicalize(root)), "")
|
||||
end
|
||||
|
||||
local function resolve_path(path, mappings)
|
||||
local root, to_strip = get_root(path, mappings)
|
||||
local newpath = strip(fs.combine(root, path), to_strip)
|
||||
if path_in(newpath, root) then return newpath end
|
||||
return resolve_path(newpath, mappings)
|
||||
end
|
||||
|
||||
local function segments(path)
|
||||
local segs, rest = {}, canonicalize(path)
|
||||
if rest == "" then return {} end -- otherwise we'd get "root" and ".." for some broken reason
|
||||
repeat
|
||||
table.insert(segs, 1, fsgetname(rest))
|
||||
rest = fsgetdir(rest)
|
||||
table.insert(segs, 1, fs.getName(rest))
|
||||
rest = fs.getDir(rest)
|
||||
until rest == ""
|
||||
return segs
|
||||
end
|
||||
@ -106,10 +134,22 @@ end
|
||||
local function combine(segs)
|
||||
local out = ""
|
||||
for _, p in pairs(segs) do
|
||||
out = fscombine(out, p)
|
||||
out = fs.combine(out, p)
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
-- magic from http://lua-users.org/wiki/SplitJoin
|
||||
-- split string into lines
|
||||
local function lines(str)
|
||||
local t = {}
|
||||
local function helper(line)
|
||||
table.insert(t, line)
|
||||
return ""
|
||||
end
|
||||
helper((str:gsub("(.-)\r?\n", helper)))
|
||||
return t
|
||||
end
|
||||
|
||||
-- Fetch the contents of URL "u"
|
||||
local function fetch(u)
|
||||
@ -120,27 +160,12 @@ local function fetch(u)
|
||||
end
|
||||
|
||||
-- Make a read handle for a string
|
||||
-- PS#8FE487EF: Incompletely implemented handle behaviour lead to strange bugs on recent CC
|
||||
local function make_handle(text)
|
||||
local h = {}
|
||||
local cursor = 1
|
||||
local lines = lines(text)
|
||||
local h = {line = 0}
|
||||
function h.close() end
|
||||
function h.readLine(with_trailing)
|
||||
if cursor >= text:len() then return nil end
|
||||
local lt_start, lt_end = text:find("\r?\n", cursor)
|
||||
lt_start = lt_start or (text:len() + 1)
|
||||
lt_end = lt_end or (text:len() + 1)
|
||||
local seg = text:sub(cursor, with_trailing and lt_end or (lt_start - 1))
|
||||
cursor = lt_end + 1
|
||||
return seg
|
||||
end
|
||||
function h.read(count)
|
||||
local count = count or 1
|
||||
local seg = text:sub(cursor, cursor + count - 1)
|
||||
cursor = cursor + count
|
||||
return seg:len() ~= 0 and seg or nil
|
||||
end
|
||||
function h.readAll() local seg = text:sub(cursor) cursor = text:len() return seg:len() ~= 0 and seg or nil end
|
||||
function h.readLine() h.line = h.line + 1 return lines[h.line] end
|
||||
function h.readAll() return text end
|
||||
return h
|
||||
end
|
||||
|
||||
@ -151,176 +176,97 @@ end
|
||||
|
||||
local this_level_env = _G
|
||||
|
||||
-- make virtual filesystem from files (no nested directories for simplicity)
|
||||
local function vfs_from_files(files)
|
||||
return {
|
||||
list = function(path)
|
||||
if path ~= "" then return {} end
|
||||
local out = {}
|
||||
for k, v in pairs(files) do
|
||||
table.insert(out, k)
|
||||
end
|
||||
return out
|
||||
end,
|
||||
open = function(path, mode)
|
||||
return make_handle(files[path])
|
||||
end,
|
||||
exists = function(path)
|
||||
return files[path] ~= nil or path == ""
|
||||
end,
|
||||
isReadOnly = function(path)
|
||||
return true
|
||||
end,
|
||||
isDir = function(path)
|
||||
if path == "" then return true end
|
||||
return false
|
||||
end,
|
||||
getDrive = function(_) return "memory" end,
|
||||
getSize = function(path)
|
||||
return #files[path]
|
||||
end,
|
||||
getFreeSpace = function() return 0 end,
|
||||
makeDir = function() end,
|
||||
delete = function() end,
|
||||
move = function() end,
|
||||
copy = function() end,
|
||||
attributes = function(path)
|
||||
return {
|
||||
size = #files[path],
|
||||
modification = os.epoch "utc"
|
||||
}
|
||||
end
|
||||
-- Create a modified FS table which confines you to root and has some extra read-only pseudofiles.
|
||||
local function create_FS(root, overlay)
|
||||
local mappings = make_mappings(root)
|
||||
|
||||
local vfstree = {
|
||||
mount = "potatOS",
|
||||
children = {
|
||||
["disk"] = { mount = "disk" },
|
||||
["rom"] = { mount = "rom" },
|
||||
["virtual_test"] = { virtual = "bees" }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
local function create_FS(vfstree)
|
||||
local fs = fs
|
||||
|
||||
local function is_usable_node(node)
|
||||
return node.mount or node.vfs
|
||||
end
|
||||
|
||||
local function resolve(sandbox_path, ignore_usability)
|
||||
local function resolve(sandbox_path)
|
||||
local segs = segments(sandbox_path)
|
||||
local current_tree = vfstree
|
||||
|
||||
local last_usable_node, last_segs = nil, nil
|
||||
|
||||
while true do
|
||||
if is_usable_node(current_tree) then
|
||||
last_usable_node = current_tree
|
||||
last_segs = copy(segs)
|
||||
end
|
||||
local seg = segs[1]
|
||||
if seg and current_tree.children and current_tree.children[seg] then
|
||||
if current_tree.children and current_tree.children[seg] then
|
||||
table.remove(segs, 1)
|
||||
current_tree = current_tree.children[seg]
|
||||
else break end
|
||||
end
|
||||
if ignore_usability then return current_tree, segs end
|
||||
return last_usable_node, last_segs
|
||||
end
|
||||
|
||||
local function resolve_node_segs(node, segs)
|
||||
if node.mount then return fs, fscombine(node.mount, combine(segs)) end
|
||||
return node.vfs, combine(segs)
|
||||
end
|
||||
|
||||
local function resolve_path(sandbox_path)
|
||||
local node, segs = resolve(sandbox_path)
|
||||
return resolve_node_segs(node, segs)
|
||||
local new_overlay = {}
|
||||
for k, v in pairs(overlay) do
|
||||
new_overlay[canonicalize(k)] = v
|
||||
end
|
||||
|
||||
local function lift_to_sandbox(f, n)
|
||||
return function(path)
|
||||
local vfs, path = resolve_path(path)
|
||||
return vfs[n](path)
|
||||
return function(...)
|
||||
local args = map(function(x) return resolve_path(x, mappings) end, {...})
|
||||
return f(table.unpack(args))
|
||||
end
|
||||
end
|
||||
|
||||
local new = copy_some_keys {"getDir", "getName", "combine", "complete"} (fs)
|
||||
local new = copy_some_keys {"getDir", "getName", "combine"} (fs)
|
||||
|
||||
function new.isReadOnly(path)
|
||||
return path_in_overlay(new_overlay, path) or starts_with(canonicalize(path), "rom")
|
||||
end
|
||||
|
||||
function new.open(path, mode)
|
||||
if (contains(mode, "w") or contains(mode, "a")) and new.isReadOnly(path) then
|
||||
error "Access denied"
|
||||
else
|
||||
local vfs, path = resolve_path(path)
|
||||
return vfs.open(path, mode)
|
||||
local overlay_data = path_in_overlay(new_overlay, path)
|
||||
if overlay_data then
|
||||
if type(overlay_data) == "function" then overlay_data = overlay_data(this_level_env) end
|
||||
return make_handle(overlay_data), "YAFSS overlay"
|
||||
end
|
||||
return fs.open(resolve_path(path, mappings), mode)
|
||||
end
|
||||
end
|
||||
|
||||
function new.move(src, dest)
|
||||
local src_vfs, src_path = resolve_path(src)
|
||||
local dest_vfs, dest_path = resolve_path(dest)
|
||||
if src_vfs == dest_vfs then
|
||||
return src_vfs.move(src_path, dest_path)
|
||||
else
|
||||
if src_vfs.isReadOnly(src_path) then error "Access denied" end
|
||||
new.copy(src, dest)
|
||||
src_vfs.delete(src_path)
|
||||
end
|
||||
function new.exists(path)
|
||||
if path_in_overlay(new_overlay, path) ~= nil then return true end
|
||||
return fs.exists(resolve_path(path, mappings))
|
||||
end
|
||||
|
||||
function new.copy(src, dest)
|
||||
local src_vfs, src_path = resolve_path(src)
|
||||
local dest_vfs, dest_path = resolve_path(dest)
|
||||
if src_vfs == dest_vfs then
|
||||
return src_vfs.copy(src_path, dest_path)
|
||||
else
|
||||
if src_vfs.isDir(src_path) then
|
||||
dest_vfs.makeDir(dest_path)
|
||||
for _, f in pairs(src_vfs.list(src_path)) do
|
||||
new.copy(fscombine(src, f), fscombine(dest, f))
|
||||
end
|
||||
else
|
||||
local dest_fh = dest_vfs.open(dest_path, "wb")
|
||||
local src_fh = src_vfs.open(src_path, "rb")
|
||||
dest_fh.write(src_fh.readAll())
|
||||
src_fh.close()
|
||||
dest_fh.close()
|
||||
function new.overlay()
|
||||
return map(function(x)
|
||||
if type(x) == "function" then return x(this_level_env)
|
||||
else return x end
|
||||
end, new_overlay)
|
||||
end
|
||||
|
||||
function new.list(dir)
|
||||
local sdir = canonicalize(resolve_path(dir, mappings))
|
||||
local ocontents = {}
|
||||
for opath in pairs(new_overlay) do
|
||||
if fs.getDir(opath) == sdir then
|
||||
table.insert(ocontents, fs.getName(opath))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function new.mountVFS(path, vfs)
|
||||
local node, relpath = resolve(path)
|
||||
while #relpath > 0 do
|
||||
local seg = table.remove(relpath, 1)
|
||||
if not node.children then node.children = {} end
|
||||
if not node.children[seg] then node.children[seg] = {} end
|
||||
node = node.children[seg]
|
||||
end
|
||||
node.vfs = vfs
|
||||
end
|
||||
|
||||
function new.unmountVFS(path)
|
||||
local node, relpath = resolve(path)
|
||||
if #relpath == 0 then
|
||||
node.vfs = nil
|
||||
local ok, contents = pcall(fs.list, sdir)
|
||||
-- in case of error (nonexistent dir, probably) return overlay contents
|
||||
-- very awful temporary hack until I can get a nicer treeized VFS done
|
||||
if not ok then
|
||||
if #ocontents > 0 then return ocontents end
|
||||
error(contents)
|
||||
else
|
||||
error "Not a mountpoint"
|
||||
end
|
||||
end
|
||||
|
||||
function new.list(path)
|
||||
local node, segs = resolve(path, true)
|
||||
local vfs, path = resolve_path(path)
|
||||
if #segs > 0 then return vfs.list(path) end
|
||||
local out = {}
|
||||
local seen = {}
|
||||
for k, v in pairs(node.children or {}) do
|
||||
table.insert(out, k)
|
||||
seen[k] = true
|
||||
end
|
||||
for _, v in pairs(vfs.list(path)) do
|
||||
if not seen[v] then
|
||||
table.insert(out, v)
|
||||
for _, v in pairs(ocontents) do
|
||||
table.insert(contents, v)
|
||||
end
|
||||
return contents
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
add_to_table(new, map(lift_to_sandbox, copy_some_keys {"isDir", "getDrive", "getSize", "getFreeSpace", "makeDir", "delete", "isDriveRoot", "exists", "isReadOnly", "attributes"} (fs)))
|
||||
add_to_table(new, map(lift_to_sandbox, copy_some_keys {"isDir", "getDrive", "getSize", "getFreeSpace", "makeDir", "move", "copy", "delete", "isDriveRoot"} (fs)))
|
||||
|
||||
function new.find(wildcard)
|
||||
local function recurse_spec(results, path, spec) -- From here: https://github.com/Sorroko/cclite/blob/62677542ed63bd4db212f83da1357cb953e82ce3/src/emulator/native_api.lua
|
||||
@ -360,7 +306,7 @@ local function create_FS(vfstree)
|
||||
to_add.c = new.dump(path)
|
||||
to_add.t = "d"
|
||||
else
|
||||
local fh = new.open(path, "rb")
|
||||
local fh = new.open(path, "r")
|
||||
to_add.c = fh.readAll()
|
||||
fh.close()
|
||||
end
|
||||
@ -377,7 +323,7 @@ local function create_FS(vfstree)
|
||||
new.makeDir(path)
|
||||
new.load(f.c, path)
|
||||
else
|
||||
local fh = new.open(path, "wb")
|
||||
local fh = new.open(path, "w")
|
||||
fh.write(f.c)
|
||||
fh.close()
|
||||
end
|
||||
@ -392,7 +338,7 @@ local allowed_APIs = {
|
||||
"http",
|
||||
"pairs",
|
||||
"ipairs",
|
||||
-- getfenv, setfenv are modified to prevent sandbox escapes and defined in make_environment
|
||||
-- getfenv, getfenv are modified to prevent sandbox escapes and defined in make_environment
|
||||
"peripheral",
|
||||
"table",
|
||||
"string",
|
||||
@ -427,26 +373,6 @@ local allowed_APIs = {
|
||||
"~expect",
|
||||
"__inext",
|
||||
"periphemu",
|
||||
"fs",
|
||||
"debug",
|
||||
"write",
|
||||
"print",
|
||||
"printError",
|
||||
"read",
|
||||
"colors",
|
||||
"io",
|
||||
"parallel",
|
||||
"settings",
|
||||
"vector",
|
||||
"colours",
|
||||
"keys",
|
||||
"disk",
|
||||
"help",
|
||||
"paintutils",
|
||||
"rednet",
|
||||
"textutils",
|
||||
"commands",
|
||||
"window"
|
||||
}
|
||||
|
||||
local gf, sf = getfenv, setfenv
|
||||
@ -454,28 +380,59 @@ local gf, sf = getfenv, setfenv
|
||||
-- Takes the root directory to allow access to,
|
||||
-- a map of paths to either strings containing their contents or functions returning them
|
||||
-- and a table of extra APIs and partial overrides for existing APIs
|
||||
local function make_environment(API_overrides, current_process)
|
||||
local function make_environment(root_directory, overlay, API_overrides)
|
||||
local environment = copy_some_keys(allowed_APIs)(_G)
|
||||
|
||||
-- I sure hope this doesn't readd the security issues!
|
||||
environment.getfenv = getfenv
|
||||
environment.setfenv = setfenv
|
||||
environment.fs = create_FS(root_directory, overlay)
|
||||
|
||||
-- if function is not from within the VM, return env from within sandbox
|
||||
function environment.getfenv(arg)
|
||||
local env
|
||||
if type(arg) == "number" then return gf() end
|
||||
if not env or type(env._HOST) ~= "string" or not string.match(env._HOST, "YAFSS") then
|
||||
return gf()
|
||||
else
|
||||
return env
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Fix PS#AD2A532C
|
||||
Allowing `setfenv` to operate on any function meant that privileged code could in some cases be manipulated to leak information or operate undesirably. Due to this, we restrict it, similarly to getfenv.
|
||||
]]
|
||||
function environment.setfenv(fn, env)
|
||||
local nenv = gf(fn)
|
||||
if not nenv or type(nenv._HOST) ~= "string" or not string.match(nenv._HOST, "YAFSS") then
|
||||
return false
|
||||
end
|
||||
return sf(fn, env)
|
||||
end
|
||||
|
||||
local load = load
|
||||
function environment.load(code, file, mode, env)
|
||||
return load(code, file, mode, env or environment)
|
||||
return load(code, file or "@<input>", mode or "t", env or environment)
|
||||
end
|
||||
|
||||
if debug then
|
||||
environment.debug = copy_some_keys {
|
||||
"getmetatable",
|
||||
"setmetatable",
|
||||
"traceback",
|
||||
"getinfo",
|
||||
"getregistry"
|
||||
}(debug)
|
||||
end
|
||||
|
||||
environment._G = environment
|
||||
environment._ENV = environment
|
||||
environment._HOST = string.format("YAFSS on %s", _HOST)
|
||||
|
||||
function environment.os.shutdown()
|
||||
process.IPC(current_process, "power_state", "shutdown")
|
||||
os.queueEvent("power_state", "shutdown")
|
||||
while true do coroutine.yield() end
|
||||
end
|
||||
|
||||
function environment.os.reboot()
|
||||
process.IPC(current_process, "power_state", "reboot")
|
||||
os.queueEvent("power_state", "reboot")
|
||||
while true do coroutine.yield() end
|
||||
end
|
||||
|
||||
@ -484,35 +441,39 @@ local function make_environment(API_overrides, current_process)
|
||||
return environment
|
||||
end
|
||||
|
||||
local function run(API_overrides, init, logger)
|
||||
local current_process = process.running.ID
|
||||
local function run(root_directory, overlay, API_overrides, init)
|
||||
if type(init) == "table" and init.URL then init = fetch(init.URL) end
|
||||
init = init or fetch "https://pastebin.com/raw/wKdMTPwQ"
|
||||
|
||||
local running = true
|
||||
while running do
|
||||
parallel.waitForAny(function()
|
||||
local env = make_environment(API_overrides, current_process)
|
||||
local env = make_environment(root_directory, overlay, API_overrides)
|
||||
env.init_code = init
|
||||
|
||||
local out, err = load(init, "@[init]", "t", env)
|
||||
if not out then error(err) end
|
||||
local ok, err = pcall(out)
|
||||
if not ok then logger("sandbox errored: %s", err) end
|
||||
local ok, err = pcall(out)
|
||||
if not ok then printError(err) end
|
||||
end,
|
||||
function()
|
||||
while true do
|
||||
local event, source, ty, spec = coroutine.yield "ipc"
|
||||
if event == "ipc" and ty == "power_state" then -- coroutine.yield behaves weirdly with terminate
|
||||
for _, p in pairs(process.list()) do
|
||||
if process.is_ancestor(p, current_process) and p.ID ~= current_process and not p.thread then
|
||||
process.signal(p.ID, process.signals.KILL)
|
||||
local event, state = coroutine.yield "power_state"
|
||||
if event == "power_state" then -- coroutine.yield behaves weirdly with terminate
|
||||
if process then
|
||||
local this_process = process.running.ID
|
||||
for _, p in pairs(process.list()) do
|
||||
if p.parent and p.parent.ID == this_process then
|
||||
process.signal(p.ID, process.signals.KILL)
|
||||
end
|
||||
end
|
||||
end
|
||||
if spec == "shutdown" then running = false return
|
||||
elseif spec == "reboot" then return end
|
||||
if state == "shutdown" then running = false return
|
||||
elseif state == "reboot" then return end
|
||||
end
|
||||
end
|
||||
end)
|
||||
sleep()
|
||||
end
|
||||
end
|
||||
|
||||
return { run = run, create_FS = create_FS, vfs_from_files = vfs_from_files }
|
||||
return run
|
737
src/main.lua
@ -1,4 +1,4 @@
|
||||
local DEBUG_MODE = settings.get "potatOS.polychoron_debug"
|
||||
local version = "1.6"
|
||||
|
||||
-- Localize frequently used functions for performance
|
||||
local osepoch = os.epoch
|
||||
@ -8,21 +8,9 @@ local coroutineresume = coroutine.resume
|
||||
local coroutineyield = coroutine.yield
|
||||
local coroutinestatus = coroutine.status
|
||||
local tostring = tostring
|
||||
local coroutinecreate = coroutine.create
|
||||
local pairs = pairs
|
||||
local ipairs = ipairs
|
||||
local setmetatable = setmetatable
|
||||
local tableinsert = table.insert
|
||||
local assert = assert
|
||||
local error = error
|
||||
local tableunpack = table.unpack
|
||||
local debugtraceback = debug and debug.traceback
|
||||
local osqueueevent = os.queueEvent
|
||||
local ccemuxnanoTime
|
||||
local ccemuxecho
|
||||
if ccemux then
|
||||
ccemuxnanoTime = ccemux.nanoTime
|
||||
ccemuxecho = ccemux.echo
|
||||
end
|
||||
|
||||
-- Return a time of some sort. Not used to provide "objective" time measurement, just for duration comparison
|
||||
@ -37,30 +25,6 @@ end
|
||||
local processes = {}
|
||||
_G.process = {}
|
||||
|
||||
local function copy(t)
|
||||
local out = {}
|
||||
for k, v in pairs(t) do
|
||||
out[k] = v
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local statuses = {
|
||||
DEAD = "dead",
|
||||
ERRORED = "errored",
|
||||
OK = "ok",
|
||||
STOPPED = "stopped"
|
||||
}
|
||||
process.statuses = copy(statuses)
|
||||
|
||||
local signals = {
|
||||
START = "start",
|
||||
STOP = "stop",
|
||||
TERMINATE = "terminate",
|
||||
KILL = "kill"
|
||||
}
|
||||
process.signals = copy(signals)
|
||||
|
||||
-- Allow getting processes by name, and nice process views from process.list()
|
||||
local process_list_mt = {
|
||||
__tostring = function(ps)
|
||||
@ -72,10 +36,6 @@ local process_list_mt = {
|
||||
return o:gsub("\n$", "") -- strip trailing newline
|
||||
end,
|
||||
__index = function(tabl, key)
|
||||
if type(key) == "table" and key.ID then return tabl[key.ID] end
|
||||
for i, p in pairs(tabl) do
|
||||
if p.name == key and p.status ~= statuses.DEAD then return p end
|
||||
end
|
||||
for i, p in pairs(tabl) do
|
||||
if p.name == key then return p end
|
||||
end
|
||||
@ -88,14 +48,28 @@ setmetatable(processes, process_list_mt)
|
||||
function _G.sleep(time)
|
||||
time = time or 0
|
||||
local t = os.startTimer(time)
|
||||
local start = osclock()
|
||||
local start = os.clock()
|
||||
local ev, arg, tdiff
|
||||
|
||||
repeat
|
||||
ev, arg = os.pullEvent()
|
||||
until (ev == "timer" and arg == t) or (osclock() - start) > time
|
||||
until (ev == "timer" and arg == t) or (os.clock() - start) > time
|
||||
end
|
||||
|
||||
process.statuses = {
|
||||
DEAD = "dead",
|
||||
ERRORED = "errored",
|
||||
OK = "ok",
|
||||
STOPPED = "stopped"
|
||||
}
|
||||
|
||||
process.signals = {
|
||||
START = "start",
|
||||
STOP = "stop",
|
||||
TERMINATE = "terminate",
|
||||
KILL = "kill"
|
||||
}
|
||||
|
||||
-- Gets the first key in a table with the given value
|
||||
local function get_key_with_value(t, v)
|
||||
for tk, tv in pairs(t) do
|
||||
@ -125,33 +99,26 @@ local allow_event = {
|
||||
}
|
||||
|
||||
local function process_to_info(p)
|
||||
if DEBUG_MODE then return p end
|
||||
if not p then return nil end
|
||||
local out = {}
|
||||
for k, v in pairs(p) do
|
||||
if k == "parent" and v ~= nil then
|
||||
out.parent = process_to_info(v)
|
||||
elseif k == "thread_parent" and v ~= nil then
|
||||
out.thread_parent = process_to_info(v)
|
||||
else
|
||||
-- PS#85DD8AFC
|
||||
-- Through some bizarre environment weirdness even exposing the function causes security risks. So don't.
|
||||
if k ~= "coroutine" and k ~= "function" and k ~= "table" then
|
||||
if k ~= "coroutine" and k ~= "function" then
|
||||
out[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
out.capabilities = { restrictions = copy(p.capabilities.restrictions), grants = copy(p.capabilities.grants) }
|
||||
setmetatable(out, process_metatable)
|
||||
return out
|
||||
end
|
||||
|
||||
-- Fancy BSOD
|
||||
local function BSOD(e)
|
||||
if false then
|
||||
|
||||
if _G.add_log then _G.add_log("failure recorded: %s", e) end
|
||||
if _G.add_log and debugtraceback then _G.add_log("stack traceback: %s", debugtraceback()) end
|
||||
if _G.add_log then _G.add_log("BSOD recorded: %s", e) end
|
||||
if term.isColor() then term.setBackgroundColor(colors.blue) term.setTextColor(colors.white)
|
||||
else term.setBackgroundColor(colors.white) term.setTextColor(colors.black) end
|
||||
|
||||
@ -160,15 +127,14 @@ local function BSOD(e)
|
||||
term.setCursorPos(1, 1)
|
||||
|
||||
print(e)
|
||||
end
|
||||
end
|
||||
|
||||
local running
|
||||
-- Apply "event" to "proc"
|
||||
-- Where most important stuff happens
|
||||
local function tick(proc, event)
|
||||
if not proc then error "Internal error: No such process" end
|
||||
if running then return end
|
||||
if not proc then error "No such process" end
|
||||
if process.running and process.running.ID == proc.ID then return end
|
||||
|
||||
-- Run any given event preprocessor on the event
|
||||
-- Actually don't, due to (hypothetical) PS#D7CD76C0-like exploits
|
||||
@ -179,15 +145,16 @@ local function tick(proc, event)
|
||||
end
|
||||
]]
|
||||
|
||||
-- If coroutine is dead, just ignore it and set its status to dead
|
||||
if coroutinestatus(proc.coroutine) == "dead" or proc.status == statuses.DEAD then
|
||||
proc.status = statuses.DEAD
|
||||
if proc.thread then processes[proc.ID] = nil end
|
||||
return
|
||||
-- If coroutine is dead, just ignore it but set its status to dead
|
||||
if coroutinestatus(proc.coroutine) == "dead" then
|
||||
proc.status = process.statuses.DEAD
|
||||
if proc.ephemeral then
|
||||
processes[proc.ID] = nil
|
||||
end
|
||||
end
|
||||
-- If coroutine ready and filter matches or event is allowed, run it, set the running process in its environment,
|
||||
-- get execution time, and run error handler if errors happen.
|
||||
if proc.status == statuses.OK and (proc.filter == nil or proc.filter == event[1] or (type(proc.filter) == "table" and proc.filter[event[1]]) or allow_event[event[1]]) then
|
||||
if proc.status == process.statuses.OK and (proc.filter == nil or proc.filter == event[1] or allow_event[event[1]]) then
|
||||
process.running = process_to_info(proc)
|
||||
running = proc
|
||||
local start_time = time()
|
||||
@ -199,7 +166,7 @@ local function tick(proc, event)
|
||||
if proc.error_handler then
|
||||
proc.error_handler(res)
|
||||
else
|
||||
proc.status = statuses.ERRORED
|
||||
proc.status = process.statuses.ERRORED
|
||||
proc.error = res
|
||||
if res ~= "Terminated" then -- programs terminating is normal, other errors not so much
|
||||
BSOD(stringformat("Process %s has crashed!\nError: %s", proc.name or tostring(proc.ID), tostring(res)))
|
||||
@ -208,134 +175,47 @@ local function tick(proc, event)
|
||||
else
|
||||
proc.filter = res
|
||||
end
|
||||
running = nil
|
||||
process.running = nil
|
||||
end
|
||||
end
|
||||
|
||||
local queue = {}
|
||||
local events_are_queued = false
|
||||
|
||||
local function find_all_in_group(id)
|
||||
local proc = processes[id]
|
||||
if proc.thread then
|
||||
proc = proc.thread_parent
|
||||
end
|
||||
local procs = {proc}
|
||||
for _, p in pairs(processes) do
|
||||
if p.thread_parent == proc then
|
||||
tableinsert(procs, p)
|
||||
end
|
||||
end
|
||||
return procs
|
||||
end
|
||||
|
||||
local function enqueue(id, event)
|
||||
events_are_queued = true
|
||||
for _, tg in pairs(find_all_in_group(id)) do
|
||||
local id = tg.ID
|
||||
queue[id] = queue[id] or {}
|
||||
tableinsert(queue[id], event)
|
||||
end
|
||||
end
|
||||
|
||||
function process.get_running()
|
||||
return process_to_info(running)
|
||||
end
|
||||
|
||||
function process.IPC(target, ...)
|
||||
if not processes[target] then error(stringformat("No such process %s.", tostring(target))) end
|
||||
enqueue(processes[target].ID, { "ipc", running.ID, ... })
|
||||
return running
|
||||
end
|
||||
|
||||
-- Send/apply the given signal to the given process
|
||||
local function apply_signal(proc, signal)
|
||||
enqueue(proc.ID, { "signal", signal, running.ID })
|
||||
if signal == signals.TERMINATE then
|
||||
enqueue(proc.ID, { "terminate" })
|
||||
local rID = nil
|
||||
if process.running then rID = process.running.ID end
|
||||
tick(proc, { "signal", signal, rID })
|
||||
-- START - starts stopped process
|
||||
if signal == process.signals.START and proc.status == process.statuses.STOPPED then
|
||||
proc.status = process.statuses.OK
|
||||
-- STOP stops started process
|
||||
elseif signal == process.signals.STOP and proc.status == process.statuses.OK then
|
||||
proc.status = process.statuses.STOPPED
|
||||
elseif signal == process.signals.TERMINATE then
|
||||
proc.terminated_time = os.clock()
|
||||
tick(proc, { "terminate" })
|
||||
elseif signal == process.signals.KILL then
|
||||
proc.status = process.statuses.DEAD
|
||||
end
|
||||
for _, proc in pairs(find_all_in_group(proc.ID)) do
|
||||
-- START - starts stopped process
|
||||
if signal == signals.START and proc.status == statuses.STOPPED then
|
||||
proc.status = statuses.OK
|
||||
-- STOP stops started process
|
||||
elseif signal == signals.STOP and proc.status == statuses.OK then
|
||||
proc.status = statuses.STOPPED
|
||||
elseif signal == signals.TERMINATE then
|
||||
proc.terminated_time = osclock()
|
||||
elseif signal == signals.KILL then
|
||||
proc.status = statuses.DEAD
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function ensure_no_metatables(x)
|
||||
if type(x) ~= "table" then return end
|
||||
assert(getmetatable(x) == nil)
|
||||
for k, v in pairs(x) do
|
||||
ensure_no_metatables(v)
|
||||
ensure_no_metatables(k)
|
||||
end
|
||||
end
|
||||
|
||||
local root_capability = {"root"}
|
||||
|
||||
local function ensure_capabilities_subset(x, orig)
|
||||
x.grants = x.grants or {}
|
||||
x.restrictions = x.restrictions or {}
|
||||
ensure_no_metatables(x)
|
||||
assert(type(x.restrictions) == "table")
|
||||
assert(type(x.grants) == "table")
|
||||
if orig.grants[root_capability] then return end
|
||||
for restriction, value in pairs(orig.restrictions) do
|
||||
x.restrictions[restriction] = value
|
||||
end
|
||||
for grant, enabled in pairs(x.grants) do
|
||||
if enabled and not orig.grants[grant] then
|
||||
x.grants[grant] = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function are_capabilities_subset(x, orig)
|
||||
if orig.grants[root_capability] then return true end
|
||||
for restriction, value in pairs(orig.restrictions) do
|
||||
if x.restrictions[restriction] ~= value then
|
||||
return false
|
||||
end
|
||||
end
|
||||
for grant, enabled in pairs(x.grants) do
|
||||
if enabled and not orig.grants[grant] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local next_ID = 1
|
||||
local function spawn(fn, name, thread, capabilities)
|
||||
name = tostring(name)
|
||||
function process.spawn(fn, name, extra)
|
||||
local this_ID = next_ID
|
||||
if not capabilities then
|
||||
capabilities = running.capabilities
|
||||
end
|
||||
if running then ensure_capabilities_subset(capabilities, running.capabilities) end
|
||||
local proc = {
|
||||
coroutine = coroutinecreate(fn),
|
||||
coroutine = coroutine.create(fn),
|
||||
name = name,
|
||||
status = statuses.OK,
|
||||
status = process.statuses.OK,
|
||||
ID = this_ID,
|
||||
parent = running,
|
||||
parent = process.running,
|
||||
["function"] = fn,
|
||||
ctime = 0,
|
||||
capabilities = capabilities
|
||||
ctime = 0
|
||||
}
|
||||
|
||||
if thread then
|
||||
proc.thread_parent = running.thread_parent or running
|
||||
proc.thread = true
|
||||
proc.parent = running.parent
|
||||
end
|
||||
if extra then for k, v in pairs(extra) do proc[k] = v end end
|
||||
|
||||
setmetatable(proc, process_metatable)
|
||||
processes[this_ID] = proc
|
||||
@ -343,13 +223,9 @@ local function spawn(fn, name, thread, capabilities)
|
||||
return this_ID
|
||||
end
|
||||
|
||||
function process.spawn(fn, name, capabilities)
|
||||
return spawn(fn, name, nil, capabilities)
|
||||
end
|
||||
|
||||
function process.thread(fn, name)
|
||||
local parent = running.name or tostring(running.ID)
|
||||
return spawn(fn, ("%s_%s_%04x"):format(name or "th", parent, math.random(0, 0xFFFF)), true)
|
||||
local parent = process.running.name or tostring(process.running.ID)
|
||||
process.spawn(fn, ("%s_%s_%04x"):format(name or "thread", parent, math.random(0, 0xFFFF)), { ephemeral = true })
|
||||
end
|
||||
|
||||
-- Sends a signal to the given process ID
|
||||
@ -358,14 +234,6 @@ function process.signal(ID, signal)
|
||||
apply_signal(processes[ID], signal)
|
||||
end
|
||||
|
||||
function process.has_grant(g)
|
||||
return running.capabilities.grants[g] or running.capabilities.grants[root_capability] or false
|
||||
end
|
||||
|
||||
function process.restriction(r)
|
||||
return running.capabilities.restrictions[r] or nil
|
||||
end
|
||||
|
||||
-- PS#F7686798
|
||||
-- Prevent mutation of processes through exposed API to prevent PS#D7CD76C0-like exploits
|
||||
-- List all processes
|
||||
@ -381,60 +249,32 @@ function process.info(ID)
|
||||
return process_to_info(processes[ID])
|
||||
end
|
||||
|
||||
function os.queueEvent(...)
|
||||
enqueue(running.ID, {...})
|
||||
end
|
||||
|
||||
local function ancestry_includes(proc, anc)
|
||||
repeat
|
||||
if proc == anc then
|
||||
return true
|
||||
end
|
||||
proc = proc.parent
|
||||
until not proc
|
||||
return false
|
||||
end
|
||||
|
||||
function process.is_ancestor(proc, anc)
|
||||
return ancestry_includes(processes[proc], processes[anc])
|
||||
end
|
||||
|
||||
function process.queue_in(ID, ...)
|
||||
local parent = processes[ID]
|
||||
if not parent then error(stringformat("No such process %s.", tostring(ID))) end
|
||||
for ID, proc in pairs(processes) do
|
||||
if ancestry_includes(proc, parent) and are_capabilities_subset(proc.capabilities, running.capabilities) and not proc.thread then
|
||||
enqueue(proc.ID, {...})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local dummy_event = ("%07x"):format(math.random(0, 0xFFFFFFF))
|
||||
-- Run main event loop
|
||||
local function run_loop()
|
||||
while true do
|
||||
if events_are_queued then
|
||||
events_are_queued = false
|
||||
for target, events in pairs(queue) do
|
||||
for _, event in ipairs(events) do
|
||||
tick(processes[target], event)
|
||||
end
|
||||
queue[target] = nil
|
||||
end
|
||||
osqueueevent(dummy_event)
|
||||
else
|
||||
local ev = {coroutineyield()}
|
||||
if ev[1] ~= dummy_event then
|
||||
for ID, proc in pairs(processes) do
|
||||
tick(proc, ev)
|
||||
end
|
||||
end
|
||||
local ev = {coroutineyield()}
|
||||
for ID, proc in pairs(processes) do
|
||||
tick(proc, ev)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function boot()
|
||||
if ccemuxecho then ccemuxecho("TLCO executed " .. (debugtraceback and debugtraceback() or "succesfully")) end
|
||||
local base_processes = {
|
||||
["main"] = function() os.run({}, "autorun.lua") end,
|
||||
["rednetd"] = function()
|
||||
-- bodge, because of the stupid rednet bRunning thing
|
||||
local old_error = error
|
||||
_G.error = function() _G.error = old_error end
|
||||
rednet.run()
|
||||
end
|
||||
}
|
||||
|
||||
-- hacky magic to run our code and not the BIOS stuff
|
||||
-- this terminates the shell, which crashes the BIOS, which then causes an error, which is printed with printError
|
||||
local old_printError = _G.printError
|
||||
function _G.printError()
|
||||
_G.printError = old_printError
|
||||
-- Multishell must die.
|
||||
term.redirect(term.native())
|
||||
multishell = nil
|
||||
term.setTextColor(colors.yellow)
|
||||
@ -442,40 +282,16 @@ local function boot()
|
||||
term.setCursorPos(1,1)
|
||||
term.clear()
|
||||
|
||||
process.spawn(function() os.run({}, "autorun.lua") end, "main", { grants = { [root_capability] = true }, restrictions = {} })
|
||||
process.spawn(function()
|
||||
-- bodge, because of the rednet bRunning thing
|
||||
local old_error = error
|
||||
error = function() error = old_error end
|
||||
rednet.run()
|
||||
end, "rednetd", { grants = {}, restrictions = {} })
|
||||
_G.polychoron = {version = version, process = process}
|
||||
polychoron.polychoron = polychoron
|
||||
polychoron.BSOD = BSOD
|
||||
|
||||
osqueueevent "" -- tick everything once
|
||||
for n, p in pairs(base_processes) do
|
||||
process.spawn(p, n)
|
||||
end
|
||||
|
||||
os.queueEvent "event" -- so that processes get one free "tick"
|
||||
run_loop()
|
||||
end
|
||||
|
||||
-- fixed TLCO from https://gist.github.com/MCJack123/42bc69d3757226c966da752df80437dc
|
||||
local old_error = error
|
||||
local old_os_shutdown = os.shutdown
|
||||
local old_term_redirect = term.redirect
|
||||
local old_term_native = term.native
|
||||
local old_printError = printError
|
||||
function error() end
|
||||
function term.redirect() end
|
||||
function term.native() end
|
||||
function printError() end
|
||||
function os.shutdown()
|
||||
error = old_error
|
||||
_G.error = old_error
|
||||
_ENV.error = old_error
|
||||
printError = old_printError
|
||||
_G.printError = old_printError
|
||||
_ENV.printError = old_printError
|
||||
term.native = old_term_native
|
||||
term.redirect = old_term_redirect
|
||||
os.shutdown = old_os_shutdown
|
||||
os.pullEventRaw = coroutine.yield
|
||||
boot()
|
||||
end
|
||||
|
||||
os.pullEventRaw = nil
|
||||
os.queueEvent "terminate"
|
1785
src/potatobios.lua
@ -24,6 +24,17 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
-- Concise Binary Object Representation (CBOR)
|
||||
-- RFC 7049
|
||||
|
||||
local function softreq(pkg, field)
|
||||
local ok, mod = pcall(require, pkg);
|
||||
if not ok then return end
|
||||
if field then return mod[field]; end
|
||||
return mod;
|
||||
end
|
||||
local dostring = function (s)
|
||||
local ok, f = pcall(loadstring or load, s); -- luacheck: read globals loadstring
|
||||
if ok and f then return f(); end
|
||||
end
|
||||
|
||||
local setmetatable = setmetatable;
|
||||
local getmetatable = getmetatable;
|
||||
local dbg_getmetatable
|
||||
@ -47,9 +58,11 @@ local NaN = 0/0;
|
||||
local m_frexp = math.frexp;
|
||||
local m_ldexp = math.ldexp or function (x, exp) return x * 2.0 ^ exp; end;
|
||||
local m_type = math.type or function (n) return n % 1 == 0 and n <= maxint and n >= minint and "integer" or "float" end;
|
||||
local s_pack = string.pack
|
||||
local s_unpack = string.unpack
|
||||
local b_rshift = bit32.brshift
|
||||
local s_pack = string.pack or softreq("struct", "pack");
|
||||
local s_unpack = string.unpack or softreq("struct", "unpack");
|
||||
local b_rshift = softreq("bit32", "rshift") or softreq("bit", "rshift") or
|
||||
dostring "return function(a,b) return a >> b end" or
|
||||
function (a, b) return m_max(0, m_floor(a / (2 ^ b))); end;
|
||||
|
||||
-- sanity check
|
||||
if s_pack and s_pack(">I2", 0) ~= "\0\0" then
|
||||
|