diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..355d32e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +package-lock.json +package.json diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d97065d --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +# npm install github:Ponali/luamin.js +LUAS = $(shell find src -type f -name '*.lua') +HELPS = $(shell find ./src/halyde/apps/helpdb/ src -type f -name '*') +JSONS = $(shell find src -type f -name '*.json') +ANSS = $(shell find src -type f -name '*.ans') + +LZ4S = $(patsubst src/%.lua, release/%.lua, $(LUAS)) +COPIED = $(patsubst src/%, release/%, $(JSONS)) $(patsubst src/%, release/%, $(ANSS)) +COMPRESSED = $(patsubst src/%, release/%, $(HELPS)) + +all: $(LZ4S) $(COPIED) $(COMPRESSED) + +#minify = ~/noommin/noommin +minify = node -e "console.log(require('lua-format').Minify(require('fs').readFileSync(process.argv[1],'utf-8'),{RenameVariables:true,RenameGlobals:false,SolveMath:false,Indentation:'\t'}))" + +compress = python3 -c "import sys, lz4.block; sys.stdout.buffer.write(lz4.block.compress(sys.stdin.buffer.read(), store_size=False, compression=12))" + +release/init.lua: src/init.lua + mkdir -p $(dir $@) + $(minify) "$<" > "$@" + +release/webinstall.lua: src/webinstall.lua + mkdir -p $(dir $@) + $(minify) "$<" > "$@" + +release/halyde/apps/helpdb/%: src/halyde/apps/helpdb/% + mkdir -p $(dir $@) + cat "$<" | $(compress) > "$@" + +release/%.lua: src/%.lua + mkdir -p $(dir $@) + $(minify) "$<" | $(compress) > "$@" + +release/%: src/% + mkdir -p $(dir $@) + cp "$<" "$@" + +clean: + rm -rf release diff --git a/README.md b/README.md index 3514d7e..74df6fa 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -nothing yet +halyde but did you KNOW!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! diff --git a/release/.editorconfig b/release/.editorconfig new file mode 100644 index 0000000..9a6cc75 --- /dev/null +++ b/release/.editorconfig @@ -0,0 +1,3 @@ +[*.lua] +indent_style = space +indent_size = 2 diff --git a/release/.gitignore b/release/.gitignore new file mode 100644 index 0000000..5aecd00 --- /dev/null +++ b/release/.gitignore @@ -0,0 +1,5 @@ +.stfolder +.idea +home/* +halyde/logs/* +*.kate-swp diff --git a/release/.luarc.json b/release/.luarc.json new file mode 100644 index 0000000..4dfa398 --- /dev/null +++ b/release/.luarc.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://raw.githubusercontent.com/LuaLS/lua-language-server/master/locale/en-us/setting.lua", + "runtime": { + "version": "Lua 5.3", + "special": {}, + "unicodeName": false, + "nonstandardSymbol": [] + }, + "workspace": { + "checkThirdParty": false, + "maxPreload": 5000, + "preloadFileSize": 500 + }, + "diagnostics": { + "disable": [ + "undefined-global", + "lowercase-global", + "missing-fields" + ], + "globals": [ + "_G", + "_VERSION", + "assert", + "error", + "getmetatable", + "ipairs", + "load", + "next", + "pairs", + "pcall", + "rawequal", + "rawget", + "rawlen", + "rawset", + "select", + "setmetatable", + "tonumber", + "tostring", + "type", + "xpcall", + "checkArg", + "bit32", + "coroutine", + "debug", + "math", + "os", + "string", + "table", + "component", + "computer", + "unicode", + "utf8" + ] + }, + "completion": { + "enable": true, + "callSnippet": "Both", + "keywordSnippet": "Both", + "displayContext": 6 + }, + "hover": { + "enable": true, + "viewString": true, + "viewStringMax": 1000, + "viewNumber": true, + "fieldInfer": 3000, + "previewFields": 50, + "enumsLimit": 5 + }, + "semantic": { + "enable": true, + "variable": true, + "annotation": true, + "keyword": false + }, + "format": { + "enable": true, + "defaultConfig": { + "indent_style": "space", + "indent_size": "2", + "tab_width": "2" + } + }, + "spell": { + "dict": [] + }, + "telemetry": { + "enable": false + }, + "IntelliSense": { + "traceLocalSet": false, + "traceReturn": false, + "traceBeSetted": false, + "traceFieldInject": false + }, + "type": { + "castNumberToInteger": false, + "weakUnionCheck": false, + "weakNilCheck": false + } +} diff --git a/release/LICENSE b/release/LICENSE new file mode 100644 index 0000000..3877ae0 --- /dev/null +++ b/release/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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 + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/release/README.md b/release/README.md new file mode 100644 index 0000000..d0c8ce0 --- /dev/null +++ b/release/README.md @@ -0,0 +1,25 @@ +# Halyde +### If you are viewing on GitHub: Halyde has moved to [Gitea](https://git.sting.lt/Cerulean-Blue/Halyde). This repository is now a read-only mirror. Contributions, issues and pull requests will not be processed here. Please go to [the Gitea instance](https://git.sting.lt/Cerulean-Blue/Halyde) for that. + +A universal, customizable and feature-packed operating system for OpenComputers. + +

+ + + + + + +

+ +## Installation +To install Halyde from OpenOS, simply run: + +`pastebin run MB21BTMv` + +If for some reason that doesn't work, try: + +`wget -f https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/webinstall.lua /tmp/webinstall.lua && /tmp/webinstall.lua` + +## Docs +Halyde is [documented on DokuWiki](https://wiki.sting.lt/), however there is an alternative documentation on [GitBook](https://cerulean-blue.gitbook.io/halyde-docs/). diff --git a/release/ag2/registry.json b/release/ag2/registry.json new file mode 100644 index 0000000..e1881f6 --- /dev/null +++ b/release/ag2/registry.json @@ -0,0 +1,9 @@ +{ + "halyde": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/", + "argentum": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/", + "edit": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/", + "package1": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/", + "package2": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/", + "vpkg": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/", + "grouper": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/" +} diff --git a/release/argentum.cfg b/release/argentum.cfg new file mode 100644 index 0000000..4656d31 --- /dev/null +++ b/release/argentum.cfg @@ -0,0 +1,117 @@ +local agcfg = { + ["halyde"] = { + ["maindir"] = "", + ["version"] = "2.8.1", + ["description"] = "A universal, customizable and feature-packed operating system for OpenComputers.", + ["directories"] = { + "halyde/apps", + "halyde/apps/helpdb", + "halyde/config/generate", + "halyde/core", + "halyde/drivers", + "halyde/lib", + "halyde", + "home", + "mnt", + "tmp", + "special/eeprom", + "special/drive", + "special" + }, + ["files"] = { + "init.lua", + "halyde/apps/helpdb/cat.txt", + "halyde/apps/helpdb/cd.txt", + "halyde/apps/helpdb/clear.txt", + "halyde/apps/helpdb/cp.txt", + "halyde/apps/helpdb/default.txt", + "halyde/apps/helpdb/download.txt", + "halyde/apps/helpdb/echo.txt", + "halyde/apps/helpdb/fetch.txt", + "halyde/apps/helpdb/help.txt", + "halyde/apps/helpdb/ls.txt", + "halyde/apps/helpdb/lscor.txt", + "halyde/apps/helpdb/lua.txt", + "halyde/apps/helpdb/mv.txt", + "halyde/apps/helpdb/reboot.txt", + "halyde/apps/helpdb/rm.txt", + "halyde/apps/helpdb/shutdown.txt", + "halyde/apps/boot.lua", + "halyde/apps/cat.lua", + "halyde/apps/cd.lua", + "halyde/apps/clear.lua", + "halyde/apps/cp.lua", + "halyde/apps/download.lua", + "halyde/apps/echo.lua", + "halyde/apps/fetch.lua", + "halyde/apps/help.lua", + "halyde/apps/label.lua", + "halyde/apps/ls.lua", + "halyde/apps/lscor.lua", + "halyde/apps/lsdrv.lua", + "halyde/apps/lua.lua", + "halyde/apps/maindrv.lua", + "halyde/apps/mkdir.lua", + "halyde/apps/mv.lua", + "halyde/apps/reboot.lua", + "halyde/apps/rm.lua", + "halyde/apps/shutdown.lua", + "halyde/config/oslogo.ans", + "halyde/config/generate/shell.json", + "halyde/config/generate/startupapps.json", + "halyde/core/boot.lua", + "halyde/core/cormgr.lua", + "halyde/core/datatools.lua", + "halyde/core/drvload.lua", + "halyde/core/evmgr.lua", + "halyde/core/fullkb.lua", + "halyde/core/shell.lua", + "halyde/core/termlib.lua", + "halyde/lib/component.lua", + "halyde/lib/computer.lua", + "halyde/lib/event.lua", + "halyde/lib/filesystem.lua", + "halyde/lib/json.lua", + "halyde/lib/raster.lua", + "halyde/lib/unicode.lua", + "halyde/lib/serialize.lua" + } + }, + ["argentum"] = { + ["maindir"] = "", + ["version"] = "1.3.1", + ["description"] = "The default package manager for Halyde.", + ["directories"] = { + "argentum", + "argentum/store" + }, + ["files"] = { + "argentum/registry.cfg", + "halyde/apps/argentum.lua", + "halyde/apps/helpdb/argentum.txt" + } + }, + ["edit"] = { + ["maindir"] = "", + ["version"] = "1.2.2", + ["description"] = "The default text editor for Halyde.", + ["files"] = { + "halyde/apps/edit.lua", + "halyde/apps/helpdb/edit.txt" + } + }, + ["webinstall-extras"] = { -- not actually a package + ["directories"] = { + "argentum/store/halyde/files", + "argentum/store/edit/files", + "argentum/store/argentum/files" + }, + ["files"] = { + "argentum/store/halyde/package.cfg", + "argentum/store/edit/package.cfg", + "argentum/store/argentum/package.cfg", + } + } +} + +return agcfg diff --git a/release/argentum/registry.cfg b/release/argentum/registry.cfg new file mode 100644 index 0000000..66d670d --- /dev/null +++ b/release/argentum/registry.cfg @@ -0,0 +1,18 @@ + +local agregistry = { + ["halyde"] = "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/", + ["edit"] = "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/", + ["argentum"] = "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/", + ["donut"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/", + ["libocif"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/", + ["ocif-tools"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/", + ["hextra"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/", + ["utape"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/", + ["libctif"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/", + ["ctif-viewer"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/", + ["libsha256"] = "https://raw.githubusercontent.com/tema5002/ag-packages/refs/heads/main/", + ["sha256sum"] = "https://raw.githubusercontent.com/tema5002/ag-packages/refs/heads/main/", + ["base64"] = "https://raw.githubusercontent.com/mcplayer3/AgPackages/refs/heads/main/" +} + +return agregistry diff --git a/release/argentum/store/argentum/package.cfg b/release/argentum/store/argentum/package.cfg new file mode 100644 index 0000000..62b884b --- /dev/null +++ b/release/argentum/store/argentum/package.cfg @@ -0,0 +1,6 @@ +Aargentum/store/ +Aargentum/ +V1.2.0 +Aargentum/registry.cfg +Ahalyde/apps/argentum.lua +Ahalyde/apps/helpdb/argentum.txt diff --git a/release/argentum/store/edit/package.cfg b/release/argentum/store/edit/package.cfg new file mode 100644 index 0000000..c1465a4 --- /dev/null +++ b/release/argentum/store/edit/package.cfg @@ -0,0 +1,3 @@ +Ahalyde/apps/edit.lua +Ahalyde/apps/helpdb/edit.txt +V1.1.0 diff --git a/release/argentum/store/halyde/package.cfg b/release/argentum/store/halyde/package.cfg new file mode 100644 index 0000000..af1e431 --- /dev/null +++ b/release/argentum/store/halyde/package.cfg @@ -0,0 +1,53 @@ +Amnt/ +Ahome/ +Ahalyde/lib/ +Ahalyde/drivers/ +Ahalyde/core/ +Ahalyde/config/generate/ +Ahalyde/apps/helpdb/ +Ahalyde/apps/ +V1.11.1 +Ainit.lua +Ahalyde/apps/helpdb/cat.txt +Ahalyde/apps/helpdb/cd.txt +Ahalyde/apps/helpdb/clear.txt +Ahalyde/apps/helpdb/cp.txt +Ahalyde/apps/helpdb/default.txt +Ahalyde/apps/helpdb/echo.txt +Ahalyde/apps/helpdb/fetch.txt +Ahalyde/apps/helpdb/help.txt +Ahalyde/apps/helpdb/ls.txt +Ahalyde/apps/helpdb/lscor.txt +Ahalyde/apps/helpdb/lua.txt +Ahalyde/apps/helpdb/mv.txt +Ahalyde/apps/helpdb/rm.txt +Ahalyde/apps/cat.lua +Ahalyde/apps/cd.lua +Ahalyde/apps/clear.lua +Ahalyde/apps/cp.lua +Ahalyde/apps/echo.lua +Ahalyde/apps/fetch.lua +Ahalyde/apps/help.lua +Ahalyde/apps/ls.lua +Ahalyde/apps/lscor.lua +Ahalyde/apps/lua.lua +Ahalyde/apps/mkdir.lua +Ahalyde/apps/mv.lua +Ahalyde/apps/rm.lua +Ahalyde/config/oslogo.ans +Ahalyde/config/generate/shell.json +Ahalyde/config/generate/startupapps.json +Ahalyde/core/boot.lua +Ahalyde/core/cormgr.lua +Ahalyde/core/datatools.lua +Ahalyde/core/drvload.lua +Ahalyde/core/evmgr.lua +Ahalyde/core/fullkb.lua +Ahalyde/core/shell.lua +Ahalyde/core/termlib.lua +Ahalyde/lib/component.lua +Ahalyde/lib/computer.lua +Ahalyde/lib/event.lua +Ahalyde/lib/filesystem.lua +Ahalyde/lib/json.lua +Ahalyde/lib/raster.lua diff --git a/release/halyde/apps/ag2.lua b/release/halyde/apps/ag2.lua new file mode 100644 index 0000000..8c30f5c Binary files /dev/null and b/release/halyde/apps/ag2.lua differ diff --git a/release/halyde/apps/argentum.lua b/release/halyde/apps/argentum.lua new file mode 100644 index 0000000..a17321d Binary files /dev/null and b/release/halyde/apps/argentum.lua differ diff --git a/release/halyde/apps/bedit.lua b/release/halyde/apps/bedit.lua new file mode 100644 index 0000000..63c3134 Binary files /dev/null and b/release/halyde/apps/bedit.lua differ diff --git a/release/halyde/apps/beep.lua b/release/halyde/apps/beep.lua new file mode 100644 index 0000000..ca90319 Binary files /dev/null and b/release/halyde/apps/beep.lua differ diff --git a/release/halyde/apps/boing.lua b/release/halyde/apps/boing.lua new file mode 100644 index 0000000..2617eeb Binary files /dev/null and b/release/halyde/apps/boing.lua differ diff --git a/release/halyde/apps/boot.lua b/release/halyde/apps/boot.lua new file mode 100644 index 0000000..cbc9131 Binary files /dev/null and b/release/halyde/apps/boot.lua differ diff --git a/release/halyde/apps/cat.lua b/release/halyde/apps/cat.lua new file mode 100644 index 0000000..f1d1a95 Binary files /dev/null and b/release/halyde/apps/cat.lua differ diff --git a/release/halyde/apps/cd.lua b/release/halyde/apps/cd.lua new file mode 100644 index 0000000..beda3a1 Binary files /dev/null and b/release/halyde/apps/cd.lua differ diff --git a/release/halyde/apps/clear.lua b/release/halyde/apps/clear.lua new file mode 100644 index 0000000..3985fe2 --- /dev/null +++ b/release/halyde/apps/clear.lua @@ -0,0 +1 @@ +ðterminal.clear() diff --git a/release/halyde/apps/cp.lua b/release/halyde/apps/cp.lua new file mode 100644 index 0000000..3d44ddc Binary files /dev/null and b/release/halyde/apps/cp.lua differ diff --git a/release/halyde/apps/download.lua b/release/halyde/apps/download.lua new file mode 100644 index 0000000..160bd21 Binary files /dev/null and b/release/halyde/apps/download.lua differ diff --git a/release/halyde/apps/echo.lua b/release/halyde/apps/echo.lua new file mode 100644 index 0000000..a8d0e86 Binary files /dev/null and b/release/halyde/apps/echo.lua differ diff --git a/release/halyde/apps/edit.lua b/release/halyde/apps/edit.lua new file mode 100644 index 0000000..5b9864e Binary files /dev/null and b/release/halyde/apps/edit.lua differ diff --git a/release/halyde/apps/fetch.lua b/release/halyde/apps/fetch.lua new file mode 100644 index 0000000..38eba7c Binary files /dev/null and b/release/halyde/apps/fetch.lua differ diff --git a/release/halyde/apps/help.lua b/release/halyde/apps/help.lua new file mode 100644 index 0000000..07bea98 Binary files /dev/null and b/release/halyde/apps/help.lua differ diff --git a/release/halyde/apps/helpdb/ag2 b/release/halyde/apps/helpdb/ag2 new file mode 100644 index 0000000..fe0212b Binary files /dev/null and b/release/halyde/apps/helpdb/ag2 differ diff --git a/release/halyde/apps/helpdb/argentum b/release/halyde/apps/helpdb/argentum new file mode 100644 index 0000000..9b021b6 Binary files /dev/null and b/release/halyde/apps/helpdb/argentum differ diff --git a/release/halyde/apps/helpdb/beep b/release/halyde/apps/helpdb/beep new file mode 100644 index 0000000..c014105 Binary files /dev/null and b/release/halyde/apps/helpdb/beep differ diff --git a/release/halyde/apps/helpdb/boot b/release/halyde/apps/helpdb/boot new file mode 100644 index 0000000..2068edc Binary files /dev/null and b/release/halyde/apps/helpdb/boot differ diff --git a/release/halyde/apps/helpdb/cat b/release/halyde/apps/helpdb/cat new file mode 100644 index 0000000..0cacb3d Binary files /dev/null and b/release/halyde/apps/helpdb/cat differ diff --git a/release/halyde/apps/helpdb/cd b/release/halyde/apps/helpdb/cd new file mode 100644 index 0000000..226d2d5 Binary files /dev/null and b/release/halyde/apps/helpdb/cd differ diff --git a/release/halyde/apps/helpdb/clear b/release/halyde/apps/helpdb/clear new file mode 100644 index 0000000..a844e90 Binary files /dev/null and b/release/halyde/apps/helpdb/clear differ diff --git a/release/halyde/apps/helpdb/cp b/release/halyde/apps/helpdb/cp new file mode 100644 index 0000000..362f346 Binary files /dev/null and b/release/halyde/apps/helpdb/cp differ diff --git a/release/halyde/apps/helpdb/default b/release/halyde/apps/helpdb/default new file mode 100644 index 0000000..93f129b Binary files /dev/null and b/release/halyde/apps/helpdb/default differ diff --git a/release/halyde/apps/helpdb/download b/release/halyde/apps/helpdb/download new file mode 100644 index 0000000..3466606 Binary files /dev/null and b/release/halyde/apps/helpdb/download differ diff --git a/release/halyde/apps/helpdb/echo b/release/halyde/apps/helpdb/echo new file mode 100644 index 0000000..dc0417d Binary files /dev/null and b/release/halyde/apps/helpdb/echo differ diff --git a/release/halyde/apps/helpdb/edit b/release/halyde/apps/helpdb/edit new file mode 100644 index 0000000..2118546 Binary files /dev/null and b/release/halyde/apps/helpdb/edit differ diff --git a/release/halyde/apps/helpdb/fetch b/release/halyde/apps/helpdb/fetch new file mode 100644 index 0000000..589ac8f Binary files /dev/null and b/release/halyde/apps/helpdb/fetch differ diff --git a/release/halyde/apps/helpdb/help b/release/halyde/apps/helpdb/help new file mode 100644 index 0000000..fcd9ce3 Binary files /dev/null and b/release/halyde/apps/helpdb/help differ diff --git a/release/halyde/apps/helpdb/label b/release/halyde/apps/helpdb/label new file mode 100644 index 0000000..8a964aa Binary files /dev/null and b/release/halyde/apps/helpdb/label differ diff --git a/release/halyde/apps/helpdb/log b/release/halyde/apps/helpdb/log new file mode 100644 index 0000000..f48ebd7 Binary files /dev/null and b/release/halyde/apps/helpdb/log differ diff --git a/release/halyde/apps/helpdb/ls b/release/halyde/apps/helpdb/ls new file mode 100644 index 0000000..5d7a8a5 Binary files /dev/null and b/release/halyde/apps/helpdb/ls differ diff --git a/release/halyde/apps/helpdb/lscor b/release/halyde/apps/helpdb/lscor new file mode 100644 index 0000000..8749b8f Binary files /dev/null and b/release/halyde/apps/helpdb/lscor differ diff --git a/release/halyde/apps/helpdb/lsdrv b/release/halyde/apps/helpdb/lsdrv new file mode 100644 index 0000000..7329a34 Binary files /dev/null and b/release/halyde/apps/helpdb/lsdrv differ diff --git a/release/halyde/apps/helpdb/lua b/release/halyde/apps/helpdb/lua new file mode 100644 index 0000000..ced1cd5 Binary files /dev/null and b/release/halyde/apps/helpdb/lua differ diff --git a/release/halyde/apps/helpdb/maindrv b/release/halyde/apps/helpdb/maindrv new file mode 100644 index 0000000..d9c1cdc Binary files /dev/null and b/release/halyde/apps/helpdb/maindrv differ diff --git a/release/halyde/apps/helpdb/mkdir b/release/halyde/apps/helpdb/mkdir new file mode 100644 index 0000000..c7476d6 Binary files /dev/null and b/release/halyde/apps/helpdb/mkdir differ diff --git a/release/halyde/apps/helpdb/mv b/release/halyde/apps/helpdb/mv new file mode 100644 index 0000000..12f12c6 Binary files /dev/null and b/release/halyde/apps/helpdb/mv differ diff --git a/release/halyde/apps/helpdb/reboot b/release/halyde/apps/helpdb/reboot new file mode 100644 index 0000000..d759f7b Binary files /dev/null and b/release/halyde/apps/helpdb/reboot differ diff --git a/release/halyde/apps/helpdb/res b/release/halyde/apps/helpdb/res new file mode 100644 index 0000000..f4b141d Binary files /dev/null and b/release/halyde/apps/helpdb/res differ diff --git a/release/halyde/apps/helpdb/rm b/release/halyde/apps/helpdb/rm new file mode 100644 index 0000000..79e9daa Binary files /dev/null and b/release/halyde/apps/helpdb/rm differ diff --git a/release/halyde/apps/helpdb/rtest b/release/halyde/apps/helpdb/rtest new file mode 100644 index 0000000..5d4fe21 Binary files /dev/null and b/release/halyde/apps/helpdb/rtest differ diff --git a/release/halyde/apps/helpdb/shutdown b/release/halyde/apps/helpdb/shutdown new file mode 100644 index 0000000..b7af932 Binary files /dev/null and b/release/halyde/apps/helpdb/shutdown differ diff --git a/release/halyde/apps/helpdb/touch b/release/halyde/apps/helpdb/touch new file mode 100644 index 0000000..589bd02 Binary files /dev/null and b/release/halyde/apps/helpdb/touch differ diff --git a/release/halyde/apps/label.lua b/release/halyde/apps/label.lua new file mode 100644 index 0000000..7d1a73a Binary files /dev/null and b/release/halyde/apps/label.lua differ diff --git a/release/halyde/apps/log.lua b/release/halyde/apps/log.lua new file mode 100644 index 0000000..52bbc87 Binary files /dev/null and b/release/halyde/apps/log.lua differ diff --git a/release/halyde/apps/ls.lua b/release/halyde/apps/ls.lua new file mode 100644 index 0000000..11a2991 Binary files /dev/null and b/release/halyde/apps/ls.lua differ diff --git a/release/halyde/apps/lsdrv.lua b/release/halyde/apps/lsdrv.lua new file mode 100644 index 0000000..e5c31fd Binary files /dev/null and b/release/halyde/apps/lsdrv.lua differ diff --git a/release/halyde/apps/lstsk.lua b/release/halyde/apps/lstsk.lua new file mode 100644 index 0000000..17cf9ff Binary files /dev/null and b/release/halyde/apps/lstsk.lua differ diff --git a/release/halyde/apps/lua.lua b/release/halyde/apps/lua.lua new file mode 100644 index 0000000..4d9f950 Binary files /dev/null and b/release/halyde/apps/lua.lua differ diff --git a/release/halyde/apps/maindrv.lua b/release/halyde/apps/maindrv.lua new file mode 100644 index 0000000..5e31442 Binary files /dev/null and b/release/halyde/apps/maindrv.lua differ diff --git a/release/halyde/apps/mkdir.lua b/release/halyde/apps/mkdir.lua new file mode 100644 index 0000000..4245c81 Binary files /dev/null and b/release/halyde/apps/mkdir.lua differ diff --git a/release/halyde/apps/mv.lua b/release/halyde/apps/mv.lua new file mode 100644 index 0000000..281310c Binary files /dev/null and b/release/halyde/apps/mv.lua differ diff --git a/release/halyde/apps/profile.lua b/release/halyde/apps/profile.lua new file mode 100644 index 0000000..228dc21 Binary files /dev/null and b/release/halyde/apps/profile.lua differ diff --git a/release/halyde/apps/reboot.lua b/release/halyde/apps/reboot.lua new file mode 100644 index 0000000..1d46d1f --- /dev/null +++ b/release/halyde/apps/reboot.lua @@ -0,0 +1 @@ +ðrequire("computer").shutdown(true) diff --git a/release/halyde/apps/res.lua b/release/halyde/apps/res.lua new file mode 100644 index 0000000..ba05f6e Binary files /dev/null and b/release/halyde/apps/res.lua differ diff --git a/release/halyde/apps/rm.lua b/release/halyde/apps/rm.lua new file mode 100644 index 0000000..d59e931 Binary files /dev/null and b/release/halyde/apps/rm.lua differ diff --git a/release/halyde/apps/rtest.lua b/release/halyde/apps/rtest.lua new file mode 100644 index 0000000..35dac5b Binary files /dev/null and b/release/halyde/apps/rtest.lua differ diff --git a/release/halyde/apps/rtest3d.lua b/release/halyde/apps/rtest3d.lua new file mode 100644 index 0000000..4244d74 Binary files /dev/null and b/release/halyde/apps/rtest3d.lua differ diff --git a/release/halyde/apps/shutdown.lua b/release/halyde/apps/shutdown.lua new file mode 100644 index 0000000..96e442e --- /dev/null +++ b/release/halyde/apps/shutdown.lua @@ -0,0 +1 @@ +ðrequire("computer").shutdown() diff --git a/release/halyde/apps/touch.lua b/release/halyde/apps/touch.lua new file mode 100644 index 0000000..5af8fab Binary files /dev/null and b/release/halyde/apps/touch.lua differ diff --git a/release/halyde/config/generate/shell.json b/release/halyde/config/generate/shell.json new file mode 100644 index 0000000..33b0ae4 --- /dev/null +++ b/release/halyde/config/generate/shell.json @@ -0,0 +1 @@ +{"prompt":"\u001b[92m%s > \u001b[0m","aliases":{"move":"mv","copy":"cp","ag":"argentum","rename":"mv","..":"cd ..","man":"help","del":"rm","delete":"rm","ren":"mv","remove":"rm","list":"ls","wget":"download","dir":"ls","ps":"lscor","poweroff":"shutdown","restart":"reboot","resolution":"res"},"splashMessages":["Made by John Haly- I mean Cerulean Blue.","Welcome! Type \"help\" to get started.","Also try KOCOS!","Welcome back, Commander. We have no idea what we're doing.","99.9% bug-free. The remaining 0.1% are features.","0 days since last error.","Everything is fine. The fire is decorative.","Please don't feed the background processes.","Also has fetch!","Anything red is no man's land. Trust me.","Machine...","Abort, Retry, Fail?","What's the deal with /argentum/store?","So cutting-edge you can't hold it in your hand.","Americans are the reason you see colors on-screen. I'm talking about ANSI escape codes, not politics.","Shoutout to Ponali!"],"path":["/halyde/apps/"],"startupMessage":"\n │\n │ %s\n │ %s\n │\n ","defaultWorkingDirectory":"/home/"} diff --git a/release/halyde/config/generate/startupapps.json b/release/halyde/config/generate/startupapps.json new file mode 100644 index 0000000..5609ad6 --- /dev/null +++ b/release/halyde/config/generate/startupapps.json @@ -0,0 +1 @@ +["/halyde/core/fullkb.lua","/halyde/core/evmgr.lua","/halyde/core/drvload.lua","/halyde/core/shell.lua"] diff --git a/release/halyde/config/oslogo.ans b/release/halyde/config/oslogo.ans new file mode 100644 index 0000000..0c92872 --- /dev/null +++ b/release/halyde/config/oslogo.ans @@ -0,0 +1,17 @@ +           @@@@@@@@@@           +      /@@@@@@@@@@@@@@@@@@/      +    @@@@@@#**********#@@@@@@    +  /@@@@%*********/(#%(**%@@@@/  + @@@@@***********@@@@@****@@@@@ +@@@@%*#@@@@#*****#@@@@#****%@@@@ +@@@@***@@@@@******@@@@@*****@@@@ +@@@#***#@@@@&@@@@@@@@@@#****#@@@ +@@@#****@@@@@@@@@@@@@@@@****#@@@ +@@@%****#@@@@@&%#(*(@@@@%***%@@@ +@@@@/****@@@@@******&@@@@/*/@@@@ +@@@@@/***(@@@@%*****(@@&%//@@@@@ +  @@@@#***&@@@@/*********#@@@@  +   @@@@@%*//***********%@@@@@   +     @@@@@@@%(****(%@@@@@@@     +        @@@@@@@@@@@@@@@@        + diff --git a/release/halyde/config/shell.json b/release/halyde/config/shell.json new file mode 100644 index 0000000..8eca52e --- /dev/null +++ b/release/halyde/config/shell.json @@ -0,0 +1 @@ +{"prompt":"\u001b[92m%s > \u001b[0m","aliases":{"move":"mv","copy":"cp","ag":"argentum","rename":"mv","..":"cd ..","man":"help","del":"rm","delete":"rm","ren":"mv","remove":"rm","list":"ls","wget":"download","dir":"ls","ps":"lstsk","poweroff":"shutdown","restart":"reboot","lsblk":"lsdrv","lscor":"lstsk"},"splashMessages":["Made by John Haly- I mean Cerulean Blue.","Welcome! Type \"help\" to get started.","Also try KOCOS!","Welcome back, Commander. We have no idea what we're doing.","99.9% bug-free. The remaining 0.1% are features.","0 days since last error.","Everything is fine. The fire is decorative.","Please don't feed the background processes.","Also has fetch!","Anything red is no man's land. Trust me.","Machine...","Abort, Retry, Fail?","What's the deal with /argentum/store?","So cutting-edge you can't hold it in your hand.","Americans are the reason you see colors on-screen. I'm talking about ANSI escape codes, not politics.","Shoutout to Ponali!","Now i% more secure!"],"path":["/halyde/apps/"],"startupMessage":"\n │\n │ %s\n │ %s\n │\n ","defaultWorkingDirectory":"/home/"} diff --git a/release/halyde/config/startupapps.json b/release/halyde/config/startupapps.json new file mode 100644 index 0000000..fb54b34 --- /dev/null +++ b/release/halyde/config/startupapps.json @@ -0,0 +1 @@ +["/halyde/scripts/login.lua"] diff --git a/release/halyde/kernel/boot.lua b/release/halyde/kernel/boot.lua new file mode 100644 index 0000000..1842679 Binary files /dev/null and b/release/halyde/kernel/boot.lua differ diff --git a/release/halyde/kernel/datatools.lua b/release/halyde/kernel/datatools.lua new file mode 100644 index 0000000..4673f89 Binary files /dev/null and b/release/halyde/kernel/datatools.lua differ diff --git a/release/halyde/kernel/modload.lua b/release/halyde/kernel/modload.lua new file mode 100644 index 0000000..df0d7ec Binary files /dev/null and b/release/halyde/kernel/modload.lua differ diff --git a/release/halyde/kernel/modules/defenv.lua b/release/halyde/kernel/modules/defenv.lua new file mode 100644 index 0000000..3a10be2 Binary files /dev/null and b/release/halyde/kernel/modules/defenv.lua differ diff --git a/release/halyde/kernel/modules/evmgr.lua b/release/halyde/kernel/modules/evmgr.lua new file mode 100644 index 0000000..9af460c Binary files /dev/null and b/release/halyde/kernel/modules/evmgr.lua differ diff --git a/release/halyde/kernel/modules/ipc.lua b/release/halyde/kernel/modules/ipc.lua new file mode 100644 index 0000000..7675eba Binary files /dev/null and b/release/halyde/kernel/modules/ipc.lua differ diff --git a/release/halyde/kernel/modules/keyboard.lua b/release/halyde/kernel/modules/keyboard.lua new file mode 100644 index 0000000..f81ea07 Binary files /dev/null and b/release/halyde/kernel/modules/keyboard.lua differ diff --git a/release/halyde/kernel/modules/terminal.lua b/release/halyde/kernel/modules/terminal.lua new file mode 100644 index 0000000..fff4993 Binary files /dev/null and b/release/halyde/kernel/modules/terminal.lua differ diff --git a/release/halyde/kernel/modules/tsched.lua b/release/halyde/kernel/modules/tsched.lua new file mode 100644 index 0000000..71e2f21 Binary files /dev/null and b/release/halyde/kernel/modules/tsched.lua differ diff --git a/release/halyde/kernel/modules/user.lua b/release/halyde/kernel/modules/user.lua new file mode 100644 index 0000000..7045ec8 Binary files /dev/null and b/release/halyde/kernel/modules/user.lua differ diff --git a/release/halyde/kernel/tsched.lua b/release/halyde/kernel/tsched.lua new file mode 100644 index 0000000..dc9000f Binary files /dev/null and b/release/halyde/kernel/tsched.lua differ diff --git a/release/halyde/kernel/userreg.json b/release/halyde/kernel/userreg.json new file mode 100644 index 0000000..1663859 --- /dev/null +++ b/release/halyde/kernel/userreg.json @@ -0,0 +1 @@ +[{"name":"admin","hash":"c0e024d9200b5705bc4804722636378a"},{"name":"user","hash":"c040e68c4d44c8f8c48746bca89c1b21"}] diff --git a/release/halyde/scripts/login.lua b/release/halyde/scripts/login.lua new file mode 100644 index 0000000..2db3618 Binary files /dev/null and b/release/halyde/scripts/login.lua differ diff --git a/release/halyde/scripts/shell.lua b/release/halyde/scripts/shell.lua new file mode 100644 index 0000000..e6f11bf Binary files /dev/null and b/release/halyde/scripts/shell.lua differ diff --git a/release/init.lua b/release/init.lua new file mode 100644 index 0000000..a4cb01b --- /dev/null +++ b/release/init.lua @@ -0,0 +1 @@ +local a=component.proxy(component.list("gpu")())local b,c=a.getResolution()local function d(a)assert(type(a)=="string","bad argument #1 to 'decompress' (string expected, got "..type(a)..")")local b,c={},1;local d=#a;local e=1;while e<=d do local f=string.byte(a,e)e=e+1;local g=f//16;if g==15 then repeat local a=string.byte(a,e)e=e+1;g=g+a until a<255 end;for d=0,g-1 do b[c+d]=string.char(string.byte(a,e+d))end;c=c+g;e=e+g;if e>d then break end;local d=f&15;local f,g=string.byte(a,e,e+1)local f=f+g*256;e=e+2;if d==15 then repeat local a=string.byte(a,e)e=e+1;d=d+a until a<255 end;d=d+4;for a=0,d-1 do b[c+a]=b[c-f+a]end;c=c+d end;return table.concat(b)end;local function e(a)checkArg(1,a,"string")local b=component.invoke(computer.getBootAddress(),"open",a,"r")local c=""repeat local a=component.invoke(computer.getBootAddress(),"read",b,math.huge or math.maxinteger)c=c..(a or"")until not a;component.invoke(computer.getBootAddress(),"close",b)return assert(load(d(c),"="..a))end;local function f(a)return(a.."\n \n"..debug.traceback())end;function loadthething()local b=false;for a,a in pairs(computer.getArchitectures())do if a=="Lua 5.3"then b=true;break end end;if b then local a,a=computer.setArchitecture("Lua 5.3")if errorMessage then error(errorMessage)end else a.set(1,1,"Required architecture (Lua 5.3) is not supported.")a.set(1,2,"Halting.")while true do computer.pullSignal()end end;e("/halyde/kernel/boot.lua")(e,d)end;a.setBackground(0)a.fill(1,1,b,c," ")local d=computer.pullSignal;local e=computer.beep;local g=computer.shutdown;local h=unicode;local f,i=xpcall(loadthething,f)local j={}if not f then b,c=a.getResolution()local f;local k,l=false,nil;if _G.require then k,l=pcall(function()f=_G.require("log")f.kernel.error("Halyde has crashed!\n"..tostring(i or"unknown error"))end)end;i="A fatal error has occurred.\nHalyde cannot continue.\n \n"..tostring(i or"unknown error"):gsub("\t"," ")if not f then i="WARNING: This error has occured early enough in the boot process that no log entry could be made.\n\n"..i elseif not k then if type(l)=="nil"then l=""else l="\n │ "..tostring(l)end;i="WARNING: An error has occured when making a log entry for this crash."..l.."\n\n"..i end;local f;if a.getDepth()==1 then f=0 else f=128 end;a.setBackground(f)a.fill(1,1,b,c," ")for a in string.gmatch(i,"([^\n]*)\n?")do table.insert(j,a)end;local function i()a.setForeground(16777215)for b=1,#j do a.set(2,b+1,j[b])end;a.fill(1,c-1,b,1,"─")a.fill(1,c,b,1," ")a.setForeground(f)a.setBackground(16777215)a.set(2,c,"🠅 🠄 🠇 🠆 âŽ")a.setForeground(16777215)a.setBackground(f)a.set(4,c," / ")a.set(9,c," / ")a.set(14,c," / ")a.set(19,c," Scroll ")a.set(29,c," Reboot")end;local function f(b,c,d)a.set(math.max(b,1),c,h.sub(d,math.max(2-b,1)))end;local k=0;local l=0;local function m()if l>=#j-c+2 then return end;a.copy(1,2,b,c-3,0,-1)a.fill(1,c-2,b,1," ")local a=j[l+c-2]if type(a)=="string"then f(2-k,c-2,a)end;l=l+1 end;local function n()if l<=0 then return end;a.copy(1,1,b,c-3,0,1)a.fill(1,1,b,1," ")local a=j[l-1]if type(a)=="string"then f(2-k,1,a)end;l=l-1 end;local o=0;for a=1,#j do o=math.max(o,h.len(j[a]))end;local function h()for d=1,#j do local e=d-l+1;if e>0 and e<=c-2 then a.fill(1,e,b,1," ")f(2-k,e,j[d])end end end;local function a()if k>=o-b+2 then return end;k=k+1;h()end;local function b()if k<=0 then return end;k=k-1;h()end;i()e(440,0.2)e(465,0.2)e(440,0.2)e(370,0.5)while true do local c={d()}if c[1]=="key_down"then if c[4]==200 then n()end;if c[4]==208 then m()end;if c[4]==203 then b()end;if c[4]==205 then a()end;if c[4]==28 then g(true)end end;if c[1]=="scroll"then if c[5]>0 then n()else m()end end end end diff --git a/release/lib/cliparse.lua b/release/lib/cliparse.lua new file mode 100644 index 0000000..fa48374 Binary files /dev/null and b/release/lib/cliparse.lua differ diff --git a/release/lib/component.lua b/release/lib/component.lua new file mode 100644 index 0000000..238be34 Binary files /dev/null and b/release/lib/component.lua differ diff --git a/release/lib/computer.lua b/release/lib/computer.lua new file mode 100644 index 0000000..2de0998 Binary files /dev/null and b/release/lib/computer.lua differ diff --git a/release/lib/event.lua b/release/lib/event.lua new file mode 100644 index 0000000..e604c33 Binary files /dev/null and b/release/lib/event.lua differ diff --git a/release/lib/filesystem.lua b/release/lib/filesystem.lua new file mode 100644 index 0000000..ebca09d Binary files /dev/null and b/release/lib/filesystem.lua differ diff --git a/release/lib/json.lua b/release/lib/json.lua new file mode 100644 index 0000000..aeabe35 Binary files /dev/null and b/release/lib/json.lua differ diff --git a/release/lib/log.lua b/release/lib/log.lua new file mode 100644 index 0000000..635fcff Binary files /dev/null and b/release/lib/log.lua differ diff --git a/release/lib/md5.lua b/release/lib/md5.lua new file mode 100644 index 0000000..754034f Binary files /dev/null and b/release/lib/md5.lua differ diff --git a/release/lib/profiler.lua b/release/lib/profiler.lua new file mode 100644 index 0000000..e0f6503 Binary files /dev/null and b/release/lib/profiler.lua differ diff --git a/release/lib/raster.lua b/release/lib/raster.lua new file mode 100644 index 0000000..61adc88 Binary files /dev/null and b/release/lib/raster.lua differ diff --git a/release/lib/serialize.lua b/release/lib/serialize.lua new file mode 100644 index 0000000..9049904 Binary files /dev/null and b/release/lib/serialize.lua differ diff --git a/release/lib/shell.lua b/release/lib/shell.lua new file mode 100644 index 0000000..8c24dce Binary files /dev/null and b/release/lib/shell.lua differ diff --git a/release/lib/solvit.lua b/release/lib/solvit.lua new file mode 100644 index 0000000..c6f5f53 Binary files /dev/null and b/release/lib/solvit.lua differ diff --git a/release/lib/solvitdb.lua b/release/lib/solvitdb.lua new file mode 100644 index 0000000..f86cced Binary files /dev/null and b/release/lib/solvitdb.lua differ diff --git a/release/lib/unicode.lua b/release/lib/unicode.lua new file mode 100644 index 0000000..f2f104e Binary files /dev/null and b/release/lib/unicode.lua differ diff --git a/release/webinstall.lua b/release/webinstall.lua new file mode 100644 index 0000000..d9162f1 --- /dev/null +++ b/release/webinstall.lua @@ -0,0 +1 @@ +local a=require("io")local b=require("component")if not b.isAvailable("internet")then a.stderr.write("This program requires an internet card to run.")return end;local c=b.internet;local d=require("computer")local d=require("filesystem")local e=b.gpu;local f,g=e.getResolution()local h=require("event")local i=require("keyboard")local j;local k;local l={}local m={}for a in d.list("/mnt/")do local c=b.get(a:sub(1,3),"filesystem")if not b.invoke(c,"isReadOnly")then table.insert(l,a)table.insert(m,c)end end;local function n()e.setBackground(0)e.setForeground(16777215)e.fill(1,1,f,g," ")require("tty").setCursor(1,1)end;local function o()local a={}for c,d in ipairs(l)do local c=m[c]table.insert(a,b.invoke(c,"getLabel"))end;e.set(1,1,"Please select a drive to install Halyde to:")local b=1;local function c(b,c)if c then e.setBackground(16777215)e.setForeground(0)else e.setBackground(0)e.setForeground(16777215)end;e.fill(4,2+b,f-3,1," ")local a=a[b]or"No label"e.set(4,2+b,a)e.set(4+#a+1,2+b,m[b]:sub(1,5).."...")end;local function a(a)local a=math.max(math.min(b+a,#l),1)if b==a then return end;c(a,true)c(b,false)b=a end;e.fill(2,3,1,#l,"*")for a=1,#l do c(a,b==a)end;while true do local b={h.pull("key_down")}if not b or not b[1]or not b[4]then goto continue end;local b=i.keys[b[4]]if b=="up"then a(-1)elseif b=="down"then a(1)elseif b=="right"or b=="enter"then e.setBackground(0)e.setForeground(16777215)e.fill(1,1,f,g-1," ")break elseif b=="left"or b=="back"then return false end::continue::end;j="/mnt/"..l[b]k=m[b]return true end;if#l==0 then a.stderr.write("All drives are read-only.\nHalyde cannot be installed.")elseif#l==1 then j="/mnt/"..l[1]k=m[1]end;e.fill(1,1,f,g," ")e.setBackground(16777215)e.setForeground(0)e.fill(1,g,f,1," ")e.set(1,g,"Halyde Web Installer (OpenOS)")e.setBackground(0)e.setForeground(16777215)if#l>1 then if not o()then n()return end end;if not j then n()a.stderr.write("All drives are read-only.\nHalyde cannot be installed.")return end;if f<80 then e.set(1,1,"Are you sure you would like to install Halyde?")else e.set(1,1,"Are you sure you would like to install Halyde to "..j.."?")end;e.set(1,2,"This will erase all data on this disk.")e.set(1,g-1,"Press Y to accept, or N to cancel.")e.set(3,4,"Capacity: ")e.set(3,5,"Used: ")e.set(3,6,"ID: ")e.set(3,7,"Label: ")e.setForeground(65280)if f>=80 then e.set(50,1,j)end;e.set(13,4,math.floor(b.invoke(k,"spaceTotal")/1024).." KiB")e.set(9,5,math.floor(b.invoke(k,"spaceUsed")/1024).." KiB")e.set(7,6,k)e.set(10,7,b.invoke(k,"getLabel")or"No label")if i.keys[({h.pull("key_down")})[4]]=="n"then return n()end;local a=require("computer")local function h(a)local b,d,e=nil,"",nil;local a,c=pcall(function()b=c.request(a)b:finishConnect()end)if not a then return false,c end;local a=b:response()if a and a~=200 then return false,a end;repeat e=b.read(math.huge)d=d..(e or"")until not e;return d end;local c;local i={"halyde","edit","argentum","webinstall-extras"}e.setBackground(0)e.setForeground(16777215)e.fill(1,1,f,g," ")local function k(a,b,c)a=tostring(a)if c==nil then c=' 'end;return string.rep(c,b-#a)..a end;local function l(a,b)local d=0;if c and type(c)=="table"then for a,a in ipairs(i)do local a=c[a]d=d+#(a.directories or{})+#(a.files or{})end else d=1 end;local h=""local j=1;if type(a)=="string"then j=b;h=string.format("%s %s%%",a,k(math.floor(b*100),2))else j=0;for d=1,a do local c=c[i[d]]if d==a then j=j+b else local a=#(c.directories or{})+#c.files;j=j+a end end;j=j/d;local c=c[i[a]]b=b/(#(c.directories or{})+#c.files)local a=i[a].." "..k(math.floor(b*100),2).."%"h=string.format("%s%% [%s]",k(math.floor(j*100),2),a)end;h=h..string.rep(" ",f-#h)local a=math.floor(j*f)e.setBackground(65280)e.setForeground(0)e.set(1,g,h:sub(1,a))e.setBackground(0)e.setForeground(16777215)e.set(a+1,g,h:sub(a+1))end;local k=1;local function m(a)if k>=g then e.copy(1,2,f,g-2,0,-1)e.fill(1,g-1,f,1," ")k=k-1 end;e.set(1,k,a)k=k+1 end;m("Fetching Argentum configuration for Halyde")l("Preparing",0)c=h("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/argentum.cfg")m("Loading Argentum configuration")l("Preparing",0.5)c=load(c)c=c()m("Looking for outdated files in the drive")l("Preparing",1)local k={}for a in d.list(j)do local b=false;for d=1,3 do for c,c in pairs(c[i[d]].files)do if a==c then b=true end end;if c[i[d]].directories then for c,c in pairs(c[i[d]].directories)do if a==c.."/"then b=true end end end end;if a=="halyde/"then b=true end;if not b then table.insert(k,a)end end;m("Found "..#k)l(1,0)for a=1,4 do local b=c[i[a]]local c=0;if b.directories then c=#b.directories;for b,c in ipairs(b.directories)do m("Creating "..c.."...")l(a,b-1)d.makeDirectory(j..c)end end;for b,e in ipairs(b.files)do m("Downloading "..e.."...")l(a,b-1+c)local a=d.open(j..e,"w")a:write(h("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/"..e))a:close()end end;for a,b in ipairs(k)do m("Removing "..b)l("Finishing up",(a-1)/#k*1)d.remove(j..b)end;m("Setting boot address")l("Finishing up",1)a.setBootAddress(b.get(j:sub(6,-2)))m("Setting label to Halyde")b.invoke(b.get(j:sub(6,-2)),"setLabel","Halyde")e.fill(1,1,f,g," ")a.shutdown(true) diff --git a/src/.editorconfig b/src/.editorconfig new file mode 100644 index 0000000..9a6cc75 --- /dev/null +++ b/src/.editorconfig @@ -0,0 +1,3 @@ +[*.lua] +indent_style = space +indent_size = 2 diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..5aecd00 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,5 @@ +.stfolder +.idea +home/* +halyde/logs/* +*.kate-swp diff --git a/src/.luarc.json b/src/.luarc.json new file mode 100644 index 0000000..4dfa398 --- /dev/null +++ b/src/.luarc.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://raw.githubusercontent.com/LuaLS/lua-language-server/master/locale/en-us/setting.lua", + "runtime": { + "version": "Lua 5.3", + "special": {}, + "unicodeName": false, + "nonstandardSymbol": [] + }, + "workspace": { + "checkThirdParty": false, + "maxPreload": 5000, + "preloadFileSize": 500 + }, + "diagnostics": { + "disable": [ + "undefined-global", + "lowercase-global", + "missing-fields" + ], + "globals": [ + "_G", + "_VERSION", + "assert", + "error", + "getmetatable", + "ipairs", + "load", + "next", + "pairs", + "pcall", + "rawequal", + "rawget", + "rawlen", + "rawset", + "select", + "setmetatable", + "tonumber", + "tostring", + "type", + "xpcall", + "checkArg", + "bit32", + "coroutine", + "debug", + "math", + "os", + "string", + "table", + "component", + "computer", + "unicode", + "utf8" + ] + }, + "completion": { + "enable": true, + "callSnippet": "Both", + "keywordSnippet": "Both", + "displayContext": 6 + }, + "hover": { + "enable": true, + "viewString": true, + "viewStringMax": 1000, + "viewNumber": true, + "fieldInfer": 3000, + "previewFields": 50, + "enumsLimit": 5 + }, + "semantic": { + "enable": true, + "variable": true, + "annotation": true, + "keyword": false + }, + "format": { + "enable": true, + "defaultConfig": { + "indent_style": "space", + "indent_size": "2", + "tab_width": "2" + } + }, + "spell": { + "dict": [] + }, + "telemetry": { + "enable": false + }, + "IntelliSense": { + "traceLocalSet": false, + "traceReturn": false, + "traceBeSetted": false, + "traceFieldInject": false + }, + "type": { + "castNumberToInteger": false, + "weakUnionCheck": false, + "weakNilCheck": false + } +} diff --git a/src/LICENSE b/src/LICENSE new file mode 100644 index 0000000..3877ae0 --- /dev/null +++ b/src/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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 + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..d0c8ce0 --- /dev/null +++ b/src/README.md @@ -0,0 +1,25 @@ +# Halyde +### If you are viewing on GitHub: Halyde has moved to [Gitea](https://git.sting.lt/Cerulean-Blue/Halyde). This repository is now a read-only mirror. Contributions, issues and pull requests will not be processed here. Please go to [the Gitea instance](https://git.sting.lt/Cerulean-Blue/Halyde) for that. + +A universal, customizable and feature-packed operating system for OpenComputers. + +

+ + + + + + +

+ +## Installation +To install Halyde from OpenOS, simply run: + +`pastebin run MB21BTMv` + +If for some reason that doesn't work, try: + +`wget -f https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/webinstall.lua /tmp/webinstall.lua && /tmp/webinstall.lua` + +## Docs +Halyde is [documented on DokuWiki](https://wiki.sting.lt/), however there is an alternative documentation on [GitBook](https://cerulean-blue.gitbook.io/halyde-docs/). diff --git a/src/ag2/registry.json b/src/ag2/registry.json new file mode 100644 index 0000000..e1881f6 --- /dev/null +++ b/src/ag2/registry.json @@ -0,0 +1,9 @@ +{ + "halyde": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/", + "argentum": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/", + "edit": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/", + "package1": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/", + "package2": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/", + "vpkg": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/", + "grouper": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/" +} diff --git a/src/argentum.cfg b/src/argentum.cfg new file mode 100644 index 0000000..4656d31 --- /dev/null +++ b/src/argentum.cfg @@ -0,0 +1,117 @@ +local agcfg = { + ["halyde"] = { + ["maindir"] = "", + ["version"] = "2.8.1", + ["description"] = "A universal, customizable and feature-packed operating system for OpenComputers.", + ["directories"] = { + "halyde/apps", + "halyde/apps/helpdb", + "halyde/config/generate", + "halyde/core", + "halyde/drivers", + "halyde/lib", + "halyde", + "home", + "mnt", + "tmp", + "special/eeprom", + "special/drive", + "special" + }, + ["files"] = { + "init.lua", + "halyde/apps/helpdb/cat.txt", + "halyde/apps/helpdb/cd.txt", + "halyde/apps/helpdb/clear.txt", + "halyde/apps/helpdb/cp.txt", + "halyde/apps/helpdb/default.txt", + "halyde/apps/helpdb/download.txt", + "halyde/apps/helpdb/echo.txt", + "halyde/apps/helpdb/fetch.txt", + "halyde/apps/helpdb/help.txt", + "halyde/apps/helpdb/ls.txt", + "halyde/apps/helpdb/lscor.txt", + "halyde/apps/helpdb/lua.txt", + "halyde/apps/helpdb/mv.txt", + "halyde/apps/helpdb/reboot.txt", + "halyde/apps/helpdb/rm.txt", + "halyde/apps/helpdb/shutdown.txt", + "halyde/apps/boot.lua", + "halyde/apps/cat.lua", + "halyde/apps/cd.lua", + "halyde/apps/clear.lua", + "halyde/apps/cp.lua", + "halyde/apps/download.lua", + "halyde/apps/echo.lua", + "halyde/apps/fetch.lua", + "halyde/apps/help.lua", + "halyde/apps/label.lua", + "halyde/apps/ls.lua", + "halyde/apps/lscor.lua", + "halyde/apps/lsdrv.lua", + "halyde/apps/lua.lua", + "halyde/apps/maindrv.lua", + "halyde/apps/mkdir.lua", + "halyde/apps/mv.lua", + "halyde/apps/reboot.lua", + "halyde/apps/rm.lua", + "halyde/apps/shutdown.lua", + "halyde/config/oslogo.ans", + "halyde/config/generate/shell.json", + "halyde/config/generate/startupapps.json", + "halyde/core/boot.lua", + "halyde/core/cormgr.lua", + "halyde/core/datatools.lua", + "halyde/core/drvload.lua", + "halyde/core/evmgr.lua", + "halyde/core/fullkb.lua", + "halyde/core/shell.lua", + "halyde/core/termlib.lua", + "halyde/lib/component.lua", + "halyde/lib/computer.lua", + "halyde/lib/event.lua", + "halyde/lib/filesystem.lua", + "halyde/lib/json.lua", + "halyde/lib/raster.lua", + "halyde/lib/unicode.lua", + "halyde/lib/serialize.lua" + } + }, + ["argentum"] = { + ["maindir"] = "", + ["version"] = "1.3.1", + ["description"] = "The default package manager for Halyde.", + ["directories"] = { + "argentum", + "argentum/store" + }, + ["files"] = { + "argentum/registry.cfg", + "halyde/apps/argentum.lua", + "halyde/apps/helpdb/argentum.txt" + } + }, + ["edit"] = { + ["maindir"] = "", + ["version"] = "1.2.2", + ["description"] = "The default text editor for Halyde.", + ["files"] = { + "halyde/apps/edit.lua", + "halyde/apps/helpdb/edit.txt" + } + }, + ["webinstall-extras"] = { -- not actually a package + ["directories"] = { + "argentum/store/halyde/files", + "argentum/store/edit/files", + "argentum/store/argentum/files" + }, + ["files"] = { + "argentum/store/halyde/package.cfg", + "argentum/store/edit/package.cfg", + "argentum/store/argentum/package.cfg", + } + } +} + +return agcfg diff --git a/src/argentum/registry.cfg b/src/argentum/registry.cfg new file mode 100644 index 0000000..66d670d --- /dev/null +++ b/src/argentum/registry.cfg @@ -0,0 +1,18 @@ + +local agregistry = { + ["halyde"] = "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/", + ["edit"] = "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/", + ["argentum"] = "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/", + ["donut"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/", + ["libocif"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/", + ["ocif-tools"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/", + ["hextra"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/", + ["utape"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/", + ["libctif"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/", + ["ctif-viewer"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/", + ["libsha256"] = "https://raw.githubusercontent.com/tema5002/ag-packages/refs/heads/main/", + ["sha256sum"] = "https://raw.githubusercontent.com/tema5002/ag-packages/refs/heads/main/", + ["base64"] = "https://raw.githubusercontent.com/mcplayer3/AgPackages/refs/heads/main/" +} + +return agregistry diff --git a/src/argentum/store/argentum/package.cfg b/src/argentum/store/argentum/package.cfg new file mode 100644 index 0000000..62b884b --- /dev/null +++ b/src/argentum/store/argentum/package.cfg @@ -0,0 +1,6 @@ +Aargentum/store/ +Aargentum/ +V1.2.0 +Aargentum/registry.cfg +Ahalyde/apps/argentum.lua +Ahalyde/apps/helpdb/argentum.txt diff --git a/src/argentum/store/edit/package.cfg b/src/argentum/store/edit/package.cfg new file mode 100644 index 0000000..c1465a4 --- /dev/null +++ b/src/argentum/store/edit/package.cfg @@ -0,0 +1,3 @@ +Ahalyde/apps/edit.lua +Ahalyde/apps/helpdb/edit.txt +V1.1.0 diff --git a/src/argentum/store/halyde/package.cfg b/src/argentum/store/halyde/package.cfg new file mode 100644 index 0000000..af1e431 --- /dev/null +++ b/src/argentum/store/halyde/package.cfg @@ -0,0 +1,53 @@ +Amnt/ +Ahome/ +Ahalyde/lib/ +Ahalyde/drivers/ +Ahalyde/core/ +Ahalyde/config/generate/ +Ahalyde/apps/helpdb/ +Ahalyde/apps/ +V1.11.1 +Ainit.lua +Ahalyde/apps/helpdb/cat.txt +Ahalyde/apps/helpdb/cd.txt +Ahalyde/apps/helpdb/clear.txt +Ahalyde/apps/helpdb/cp.txt +Ahalyde/apps/helpdb/default.txt +Ahalyde/apps/helpdb/echo.txt +Ahalyde/apps/helpdb/fetch.txt +Ahalyde/apps/helpdb/help.txt +Ahalyde/apps/helpdb/ls.txt +Ahalyde/apps/helpdb/lscor.txt +Ahalyde/apps/helpdb/lua.txt +Ahalyde/apps/helpdb/mv.txt +Ahalyde/apps/helpdb/rm.txt +Ahalyde/apps/cat.lua +Ahalyde/apps/cd.lua +Ahalyde/apps/clear.lua +Ahalyde/apps/cp.lua +Ahalyde/apps/echo.lua +Ahalyde/apps/fetch.lua +Ahalyde/apps/help.lua +Ahalyde/apps/ls.lua +Ahalyde/apps/lscor.lua +Ahalyde/apps/lua.lua +Ahalyde/apps/mkdir.lua +Ahalyde/apps/mv.lua +Ahalyde/apps/rm.lua +Ahalyde/config/oslogo.ans +Ahalyde/config/generate/shell.json +Ahalyde/config/generate/startupapps.json +Ahalyde/core/boot.lua +Ahalyde/core/cormgr.lua +Ahalyde/core/datatools.lua +Ahalyde/core/drvload.lua +Ahalyde/core/evmgr.lua +Ahalyde/core/fullkb.lua +Ahalyde/core/shell.lua +Ahalyde/core/termlib.lua +Ahalyde/lib/component.lua +Ahalyde/lib/computer.lua +Ahalyde/lib/event.lua +Ahalyde/lib/filesystem.lua +Ahalyde/lib/json.lua +Ahalyde/lib/raster.lua diff --git a/src/halyde/apps/ag2.lua b/src/halyde/apps/ag2.lua new file mode 100644 index 0000000..37fb02f --- /dev/null +++ b/src/halyde/apps/ag2.lua @@ -0,0 +1,520 @@ +local cliparse = require("cliparse") +local fs = require("filesystem") +local component = require("component") +local json = require("json") + +local function getFile(path) + checkArg(1, path, "string") + if path:sub(1, 7) == "http://" or path:sub(1, 8) == "https://" then + if not component.list("internet")() then + return false, "Internet card required but not found." + end + local handle, data, tmpdata = component.internet.request(path), "", nil + local success, errorMessage = pcall(function() + handle:finishConnect() + end) + if not success then + return false, errorMessage + end + local code, message = handle:response() + if code and code ~= 200 then + return false, ("%d %s"):format(code, message) + end + repeat + tmpdata = handle.read(math.huge or math.maxinteger) + data = data .. (tmpdata or "") + until not tmpdata + return true, data + elseif path:sub(1, 1) == "/" then + if not fs.exists(path) then + return false, "No such file or directory: " .. path + end + if fs.isDirectory(path) then + return false, "Expected file, found directory: " .. path + end + local handle, data, tmpdata = fs.open(path, "r", false), "", nil + if not handle then + return false, data + end + repeat + tmpdata = handle:read(math.huge or math.maxinteger) + data = data .. (tmpdata or "") + until not tmpdata + return true, data + else + return false, "Unsupported path: " .. path + end +end + +cliparse.config({ + ["x"] = 0, + ["exclude-deps"] = 0, + ["u"] = 0, + ["update-registry"] = 0, + ["f"] = 0, + ["force"] = 0, + ["c"] = 0, + ["clean"] = 0, + ["s"] = 1, + ["source"] = 1, + ["C"] = 0, + ["cascade"] = 0, +}) + +local parsed, errorMessage = cliparse.parse(...) + +if not parsed then + print(("\27[91m%s\n\27[0mExiting."):format(errorMessage)) + return +end + +local command = parsed.args[1] + +if not command then + print("\27[91mNo command specified.\n\27[0mExiting.") + return +end + +if + not ( + command == "install" + or command == "remove" + or command == "update" + or command == "list" + or command == "repo-list" + or command == "repo-add" + or command == "repo-remove" + or command == "info" + ) +then + print(("\27[91mInvalid command: %s\n\27[0mExiting."):format(command)) + return +end + +local packages = parsed.args +table.remove(packages, 1) +-- Remove the command from the actual package list +-- local result, data, failure +do + local function check(condition, message) + if not condition then + print(message) + failure = true + end + end + + if parsed.flags.u or parsed.flags["update-registry"] then + terminal.write("Updating registry...") + result, data = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/ag2/registry.json") + check(result, "\27[91mFailed to get registry: " .. data .. "\27[0m") + local handle, errorMessage = fs.open("/ag2/registry.json", "w") + check(handle, "\27[91mFailed to open write handle to registry: " .. errorMessage .. "\27[0m") + local success, errorMessage = handle:write(data) + check(success, "\27[91mFailed to write to registry: " .. errorMessage .. "\27[0m") + handle:close() + else + result, data = getFile("/ag2/registry.json") + check(result, "\27[91mFailed to get registry: " .. data .. "\27[0m") + end +end + +local success, registry = pcall(function() + return json.decode(data) +end) +if not success then + print(("\27[91mFailed to parse registry: %s\n\27[0mExiting."):format(registry)) + return +end + +local function getServersidePackageConfig(source, package) + local success, data = getFile(fs.concat(source, "/ag2.json")) + if not success then + return false, ("\27[91mFailed to get package config (ag2.json) of package '%s': " .. data .. "\27[0m"):format(package) + end + local success, packageConfig = pcall(function() + return json.decode(data) + end) + if not success then + return false, ("\27[91mFailed to parse package config (ag2.json) of package '%s': " .. packageConfig .. "\27[0m"):format(package) + end + if not packageConfig[package] then + return false, ("\27[91mRepository package config (ag2.json) does not contain package '%s'.\27[0m"):format(package) + end + return packageConfig[package] +end + +-- Check if everything is valid +failure = false +local dependencyCounter = 0 +local previousI = 1 -- See 190 +::RESETLOOP:: +if command == "install" then + for i = previousI, #packages do + if not packages[i] then + -- When packages are removed, the last packages can end up reading as nil + goto SKIP + end + local otherPackages = table.copy(packages) + table.remove(otherPackages, i) + -- This is to check if the package can be found in the others, or in other words, checking for duplicates + if table.find(otherPackages, packages[i]) then + print(("\27[93mDuplicate package specified (%s), skipping\27[0m"):format(packages[i])) + table.remove(packages, i) + i = i - 1 + goto SKIP + end + local source + if parsed.s or parsed.source then + source = parsed.s or parsed.source + else + source = registry[packages[i]] + end + if not source then + print("\27[91mCould not find package in registry and no source provided: " .. packages[i] .. "\27[0m") + failure = true + goto SKIP + end + local packageConfig, errorMessage = getServersidePackageConfig(source, packages[i]) + if not packageConfig then + failure = true + print(errorMessage) + goto SKIP + end + if packageConfig.type == "group" then + table.remove(packages, i) + for _, package in ipairs(packageConfig.packages) do + table.insert(packages, package) + end + previousI = i + goto RESETLOOP + -- Apparently "for i = 1, n" loops don't change when n changes. So when the table gets extended, packages near the end won't get picked up. + -- This is why it is needed to restart the loop. + elseif packageConfig.type == "virtual-package" then + print(("Installing virtual package %s"):format(packages[i])) + local pkgAskText = ("Select a package by typing in its number: 1) %s"):format(packageConfig.packages[1]) + -- This is all a silly workaround to place commas correctly + for i = 2, #packageConfig.packages do + pkgAskText = pkgAskText .. (", %d) %s"):format(i, packageConfig.packages[i]) + end + print(pkgAskText) + ::RETRY:: + local packageSel = terminal.read() + if not tonumber(packageSel) or tonumber(packageSel) % 1 ~= 0 then + -- Is there really no better way to check for an int..? + print("\27[93mNon-integer received - try again\27[0m") + goto RETRY + end + if tonumber(packageSel) < 1 or tonumber(packageSel) > #packageConfig.packages then + print("\27[93mInteger out of range - try again\27[0m") + end + packages[i] = packageConfig.packages[tonumber(packageSel)] + i = i - 1 + goto SKIP + end + if fs.exists(("/ag2/pkg/%s.json"):format(packages[i])) then + print(("\27[93mPackage %s is already installed, skipping\27[0m"):format(packages[i])) + table.remove(packages, i) + i = i - 1 + goto SKIP + end + if packageConfig.dependencies then + for _, dependency in ipairs(packageConfig.dependencies) do + table.insert(packages, i + 1, dependency) + dependencyCounter = dependencyCounter + 1 + end + end + -- TODO: Add checks for conflicting packages + ::SKIP:: + end +elseif command == "remove" then + ::JUMPBACK:: + local doJumpBack = false + for i = 1, #packages do + if not fs.exists(("/ag2/pkg/%s.json"):format(packages[i])) then + if parsed.s or parsed.source then + source = parsed.s or parsed.source + else + source = registry[package] + end + if source then + local packageConfig = getServersidePackageConfig(source, packages[i]) + if packageConfig then + if packageConfig.type == "virtual-package" or packageConfig.type == "group" then + table.remove(packages, i) + for _, groupPackage in ipairs(packageConfig.packages) do + table.insert(packages, groupPackage) + goto GOAHEAD + end + end + end + end + print(("\27[93mPackage %s is not installed, skipping\27[0m"):format(packages[i])) + table.remove(packages, i) + i = i - 1 + ::GOAHEAD:: + end + end + + -- I was originally gonna add this in the dependency cascade section, but realized it could shorten the normal dependency check code a bit + local dependencyList = {} + for _, packageConfig in ipairs(fs.list("/ag2/pkg/")) do + local package = packageConfig:sub(1, -6) + -- I'm not adding error handling here because if this fails then fuck you for touching the files by hand and good luck figuring this shit out + local _, data = getFile(("/ag2/pkg/%s.json"):format(package)) + data = json.decode(data) + dependencyList[package] = data.dependencies + end + + for _, package in ipairs(packages) do + if dependencyList[package] then + -- Check if all the deps are no longer needed and if they're auto-installed and stuff + for _, dependency in pairs(dependencyList[package]) do + if fs.exists(("/ag2/pkg/%s.json"):format(dependency)) then + local _, data = getFile(("/ag2/pkg/%s.json"):format(dependency)) + data = json.decode(data) + if data.autoInstalled + and not table.find(packages, dependency) -- Just to prevent dependency loops and issues when re-checking the packages after jumpback + then + dependencyCounter = dependencyCounter + 1 + table.insert(packages, dependency) + end + else + -- It could still be a group or a vpackage, and checking that is the job of the loop 2 loops back + table.insert(packages, dependency) + doJumpBack = true + end + end + end + end + + -- Check for cascading dependencies + for packageName, dependencies in pairs(dependencyList) do + if not table.find(packages, packageName) then + for _, dependency in ipairs(dependencies) do + if table.find(packages, dependency) then + if parsed.flags.cascade or parsed.flags.C then + table.insert(packages, packageName) + dependencyCounter = dependencyCounter + 1 + doJumpBack = true + -- Listen, I'm so sorry for this abhorrent bullshit code, but the newly added packages have to get checked one way or another and hopefully this is readable enough. + else + -- The Pyramids of Giza were built entirely out of silver. No they weren't... + print(("\27[93mPackage %s is depended on by %s, cannot uninstall without --cascade\27[0m"):format(dependency, packageName)) + failure = true + end + end + end + end + end + if doJumpBack then + goto JUMPBACK + -- IT'S NOT SPAGHETTI SHUT UP SHUT UP SHU + end +end +-- TODO: Add checks for the other commands + +if #packages == 0 then + print("\27[93mNo packages selected.\n\27[0mExiting.") + return +end +if failure then + print("Exiting.") + return +end + +if command == "install" then + if dependencyCounter == 1 then + print("\27[93m1 dependency pulled in.\27[0m") + elseif dependencyCounter >= 2 then + print(("\27[93m%d dependencies pulled in.\27[0m"):format(dependencyCounter)) + end + print("Packages that will be installed:") + print(table.concat(packages, ", ")) + local answer = terminal.read({prefix = "\nContinue? [Y/n] "}) + if answer:lower() == "n" then + print("Exiting.") + return + end + + for _, package in ipairs(packages) do + local source + if parsed.s or parsed.source then + source = parsed.s or parsed.source + else + source = registry[package] + end + print(("Installing %s..."):format(package)) + local _, data = getFile(fs.concat(source, "/ag2.json")) + local packageConfig = json.decode(data)[package] + if packageConfig.directories then + for _, directory in ipairs(packageConfig.directories) do + print((" Creating directory %s..."):format(directory)) + fs.makeDirectory(directory) + end + end + if packageConfig.files then + for _, file in ipairs(packageConfig.files) do + ::RETRY:: + print((" Downloading file %s..."):format(file)) + local success, data = getFile(fs.concat(source, file)) + + if not success then + print(("\27[91mFailed to get file '%s' of package '%s': " .. data .. "\27[0m"):format(file, package)) + local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"}) + if answer:lower() == "a" then + print("Exiting.") + return + elseif answer:lower() == "s" then + print((" \27[93mSkipped file %s.\27[0m"):format(file)) + goto SKIP + else + goto RETRY + end + end + + if fs.exists(file) then + print(("\27[93mFile '%s' already exists.\27[0m"):format(file)) + local answer = terminal.read({prefix = "Abort, Overwrite, Skip? [a/O/s]"}) + if answer:lower() == "a" then + print("Exiting.") + return + elseif answer:lower() == "s" then + print((" \27[93mSkipped file %s.\27[0m"):format(file)) + goto SKIP + end + end + + local handle, errorMessage = fs.open(file, "w") + if not handle then + print(("\27[91mFailed to open write handle to file '%s': " .. errorMessage .. "\27[0m"):format(file)) + local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"}) + if answer:lower() == "a" then + print("Exiting.") + return + elseif answer:lower() == "s" then + print((" \27[93mSkipped file %s.\27[0m"):format(file)) + goto SKIP + else + goto RETRY + end + end + + local success, errorMessage = handle:write(data) + if not success then + handle:close() + print(("\27[91mFailed to write to file '%s': " .. errorMessage .. "\27[0m"):format(file)) + local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"}) + if answer:lower() == "a" then + print("Exiting.") + return + elseif answer:lower() == "s" then + print((" \27[93mSkipped file %s.\27[0m"):format(file)) + goto SKIP + else + goto RETRY + end + end + + handle:close() + + ::SKIP:: + end + end + + print(" Writing tracking file...") + if not fs.exists("/ag2/pkg/") then + fs.makeDirectory("/ag2/pkg/") + -- Technically this would break if /ag2/pkg/ was a file, but... why would it be a file? + end + + -- TODO: Make functions for reading from and writing to a file with error handling since this is really repetitive + ::RETRY:: + local handle, errorMessage = fs.open(("/ag2/pkg/%s.json"):format(package), "w") + if not handle then + print(("\27[91mFailed to open write handle to file '/ag2/pkg/%s.json': " .. errorMessage .. "\27[0m"):format(package)) + local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"}) + if answer:lower() == "a" then + print("Exiting.") + return + elseif answer:lower() == "s" then + print((" \27[93mSkipped file /ag2/pkg/%s.json.\27[0m"):format(package)) + goto SKIP + else + goto RETRY + end + end + + local packageData = { + name = package, + version = packageConfig.version, + autoInstalled = false, + -- TODO: Make the above actually work + dependencies = packageConfig.dependencies, + conflicts = packageConfig.conflicts, + files = packageConfig.files, + directories = packageConfig.directories, + config = packageConfig.config + } + + local trackingFile = json.encode(packageData) + + local success, errorMessage = handle:write(trackingFile) + if not success then + handle:close() + print(("\27[91mFailed to write to file '/ag2/pkg/%s.json': " .. errorMessage .. "\27[0m"):format(package)) + local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"}) + if answer:lower() == "a" then + print("Exiting.") + return + elseif answer:lower() == "s" then + print((" \27[93mSkipped file /ag2/pkg/%s.json.\27[0m"):format(package)) + goto SKIP + else + goto RETRY + end + else + handle:close() + end + ::SKIP:: + end +elseif command == "remove" then + if dependencyCounter == 1 then + print("\27[93m1 orphaned dependency will be removed.\27[0m") + elseif dependencyCounter >= 2 then + print(("\27[93m%d orphaned dependencies will be removed.\27[0m"):format(dependencyCounter)) + end + print("Packages that will be removed:") + print(table.concat(packages, ", ")) + local answer = terminal.read({prefix = "\nContinue? [Y/n] "}) + if answer:lower() == "n" then + print("Exiting.") + return + end + for _, package in ipairs(packages) do + -- See line 263 + local _, data = getFile(("/ag2/pkg/%s.json"):format(package)) + data = json.decode(data) + if data.files then + for _, file in ipairs(data.files) do + print((" Removing file %s..."):format(file)) + fs.remove(file) -- I cannot think of how this could fail. If you can, please add error handling here + end + end + if data.directories then + for _, directory in ipairs(data.directories) do + if fs.isDirectory(directory) then + if next(fs.list(directory)) == nil then + -- Apparently THAT's the best way to check if a table is empty in Lua. Alright I guess. + print((" Removing directory %s..."):format(directory)) + fs.remove(directory) + else + print((" Directory %s specified by package %s still has something in it, skipping"):format(directory)) + end + end + end + end + print(" Removing tracking file...") + fs.remove(("/ag2/pkg/%s.json"):format(package)) -- See line 500 + end +end +print("Operation completed successfully.") diff --git a/src/halyde/apps/argentum.lua b/src/halyde/apps/argentum.lua new file mode 100644 index 0000000..247010d --- /dev/null +++ b/src/halyde/apps/argentum.lua @@ -0,0 +1,688 @@ +local packages = {...} +local command = table.remove(packages,1) +local shell = require("shell") +local fs = require("filesystem") +local component = require("component") +local agReg = require("/argentum/registry.cfg") +if not command then + shell.run("help argentum") + return +end +if not component.list("internet")() then + print("\27[91mThis program requires an internet card to run.\27[0m") + return +end +local internet = component.internet +local source +if table.find(packages, "-s") then + source = table.remove(packages, table.find(packages, "-s") + 1) + table.remove(packages, table.find(packages, "-s")) + print("Using " .. source .. " as package source") +elseif table.find(packages, "--source") then + source = table.remove(packages, table.find(packages, "--source") + 1) + table.remove(packages, table.find(packages, "--source")) + print("Using " .. source .. " as package source") +else + print("Using main registry as package source") +end +if source and source:sub(1, 1) == "/" and source:sub(-1, -1) ~= "/" then + source = source .. "/" +end +local packageList = table.copy(packages) + +local function getFile(path) + if path:sub(1,1) == "/" then + if not fs.exists(path) then + return false, "file does not exist" + end + local handle, data, tmpdata = fs.open(path, "r"), "", nil + repeat + tmpdata = handle:read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + return data + else + local request, data, tmpdata = nil, "", nil + local status, errorMessage = pcall(function() + request = internet.request(path) + request:finishConnect() + end) + if not status then + return false, errorMessage + end + local responseCode = request:response() + if responseCode and responseCode ~= 200 then + return false, responseCode + end + repeat + tmpdata = request.read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + return data + end +end + +local i = 1 + +local function getAgConfig(package, source) + source = source or agReg[package] + local data, errorMessage = getFile(source .. "argentum.cfg") + if not data or data == "" then + print("\27[91mCould not fetch Ag config: " .. (errorMessage or "returned nil data") .. "\27[0m") + return false + end + local func, errorMessage = load(data, "=argentum.cfg", "bt", {}) + if not func then + print("\27[91mCould not fetch Ag config: " .. errorMessage .. "\nPlease contact the package owner.\27[0m") + return false + end + local agcfg + local status, errorMessage = pcall(function() + agcfg = func() + end) + if not status then + print("\27[91mCould not fetch Ag config: " .. errorMessage .. "\nPlease contact the package owner.\27[0m") + return false + end + if not agcfg[package] or not agcfg[package].maindir or not agcfg[package].directories or not agcfg[package].files or not agcfg[package].version then + local response = "\27[91mAg config of " .. package .. " is improperly configured.\nPlease contact the package owner.\27[0m" + end + return agcfg +end + +local function doChecks(package) + if not agReg[package] and not source then + print("\27[91mPackage " .. package .. " does not exist.\27[0m") + return false + end + if fs.exists("/argentum/store/" .. package) then + print("\27[91mPackage " .. package .. " is already installed.\27[0m") + return false + end + agcfg = getAgConfig(package, source) + if not agcfg then + return false + end + if agcfg[package].dependencies then + for _, dependency in ipairs(agcfg[package].dependencies) do + if not agReg[dependency] and not agcfg[dependency] then + local response = terminal.read({prefix = "\27[91mPackage " .. package .. " requires dependency " .. dependency .. " that does not exist.\n[A - Abort/s - Skip]\27[0m"}) + if response:lower() ~= "s" then + fs.remove("/argentum/store/" .. package) + return false + end + end + end + for _, dependency in pairs(agcfg[package].dependencies) do + print(package .. " depends on " .. dependency) + if not table.find(packages, dependency) and doChecks(dependency) then + table.insert(packages, table.find(packages, package), dependency) + table.insert(packageList, dependency) + i = i + 1 + end + end + end + return true +end + +local function lpad(str, len, char) + str=tostring(str) + if char == nil then char = ' ' end + return string.rep(char, len - #str) .. str +end + +local width, height = terminal.getResolution() + +local function progress(package, progress) + terminal.write("\x1b[s") + local info = string.format("%s %s%%", package, lpad(math.floor(progress * 100), 2)) + info = info .. string.rep(" ", width - #info) + local progX = math.floor(progress * width) + terminal.write("\x1b[42m\x1b[30m" .. info:sub(1, progX) .. "\x1b[40m\x1b[37m" .. info:sub(progX + 1) .. "\x1b[0m") + terminal.write("\x1b[u") +end + +local function clearProgress() + terminal.write("\x1b[s") + terminal.write("\r\x1b[40m" .. string.rep(" ", width) .. "\x1b[0m\r") + terminal.write("\x1b[u") +end + +local function installPackage(package, overwriteFlag) + if not overwriteFlag then + overwriteFlag = false + end + if not overwriteFlag then print("Installing " .. package .. "...") end + local agcfg = getAgConfig(package, source) + if not agcfg then + return false + end + local source = source or agReg[package] + local packageStore = "V" .. agcfg[package].version + if agcfg[package].dependencies then + for _, dependency in ipairs(agcfg[package].dependencies) do + if not agReg[dependency] and not agcfg[dependency] then + local response = terminal.read({prefix = "\27[91mPackage " .. package .. " requires dependency " .. dependency .. " that does not exist.\n[A - Abort/s - Skip]\27[0m"}) + if response:lower() ~= "s" then + fs.remove("/argentum/store/" .. package) + return false + end + end + end + for _, dependency in pairs(agcfg[package].dependencies) do + if agReg[dependency] or agcfg[dependency] then + --installPackage(dependency) + packageStore = packageStore .. "\nD" .. dependency + end + end + end + if agcfg[package].directories then + for _, directory in pairs(agcfg[package].directories) do + if directory:sub(-1, -1) ~= "/" then + directory = directory .. "/" + end + packageStore = "A" .. directory .. "\n" .. packageStore + if not fs.exists(directory) then + fs.makeDirectory(directory) + end + end + end + for idx, file in ipairs(agcfg[package].files) do + clearProgress() + ::retry:: + print(" Downloading " .. file .. "...") + progress(package,idx/#agcfg[package].files) + local data, errorMessage = getFile(source .. agcfg[package].maindir .. file) + if not data then + clearProgress() + local response = terminal.read({prefix = "\27[91mCould not fetch " .. file .. ": " .. errorMessage .. "\n\27[0m[a - Abort/R - Retry/s - Skip]"}) + if response:lower() == "a" then + fs.remove("/argentum/store/" .. package) + return false + elseif response:lower() == "s" then + goto skip + else + goto retry + end + end + if fs.exists(file) and not overwriteFlag then + if not fs.exists("/argentum/store/" .. package .. "/files/" .. file:match("(.*/)")) then + fs.makeDirectory("/argentum/store/" .. package .. "/files/" .. file:match("(.*/)")) + end + fs.copy(file, "/argentum/store/" .. package .. "/files/" .. file) + packageStore = packageStore .. "\nM" .. file + else + packageStore = packageStore .. "\nA" .. file + end + local handle, err = fs.open(file, "w") + handle:write(data) + handle:close() + ::skip:: + end + clearProgress() + fs.makeDirectory("/argentum/store/" .. package) + local handle = fs.open("/argentum/store/" .. package .. "/package.cfg", "w") + handle:write(packageStore) + handle:close() + return true +end + +local function removePackage(package) + print("Removing " .. package .. "...") + if not fs.exists("/argentum/store/" .. package .. "/package.cfg") then + print("\27[91mLocal Ag config of " .. package .. " does not exist.\27[0m") + return false + end + local handle, data, tmpdata = fs.open("/argentum/store/" .. package .. "/package.cfg", "r"), "", nil + repeat + tmpdata = handle:read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + data = data .. "\n" + local idx = 0 + for line in data:gmatch("(.-)\n") do + idx=idx+1 + if line:sub(1, 1) == "A" then + clearProgress() + ::retry:: + print(" Removing " .. line:sub(2) .. "...") + if line:sub(-1, -1) == "/" and fs.list(line:sub(2))[1] then + print(" There are still files in " .. line:sub(2) .. ". Skipping.") + else + progress(package,idx/select(2,data:gsub("\n","\n"))) + local result, errorMessage = fs.remove(line:sub(2)) + if not result then + clearProgress() + local response = terminal.read({prefix = "\27[91mFailed to remove " .. line:sub(2) .. ": " .. errorMessage .. "\n\27[0m[a - Abort/r - Retry/S - Skip]"}) + if response:lower() == "a" then + return false + elseif response:lower() == "r" then + goto retry + end + end + end + elseif line:sub(1, 1) == "M" then + clearProgress() + ::retry:: + print(" Reverting " .. line:sub(2) .. "...") + progress(package,idx/select(2,data:gsub("\n","\n"))) + local handle, data, tmpdata = fs.open("/argentum/store/" .. package .. "/files/" .. line:sub(2), "r"), "", nil + if not handle then + clearProgress() + local response = terminal.read({prefix = "\27[91mFailed to revert " .. line:sub(2) .. ": " .. data .. "\n\27[0m[a - Abort/R - Retry/s - Skip]"}) -- this is pretty stupid but i think the error message would get pushed to data + if response:lower() == "a" then + return false + elseif response:lower() == "s" then + goto skip + else + goto retry + end + end + repeat + tmpdata = handle:read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + local handle = fs.open(line:sub(2), "w") + handle:write(data) + handle:close() + ::skip:: + end + end + clearProgress() + fs.remove("/argentum/store/" .. package .. "/") + return true +end + +local function updatePackage(package) + print("Updating " .. package .. "...") + local agcfg = getAgConfig(package, source) + if not agcfg then + return false + end + local handle, data, tmpdata = fs.open("/argentum/store/" .. package .. "/package.cfg", "r"), "", nil + repeat + tmpdata = handle:read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + local oldFiles = {} + for line in (data .. "\n"):gmatch("(.-)\n") do + if line:sub(1, 1) == "A" or line:sub(1, 1) == "M" then + if agcfg[package].directories then + if not table.find(agcfg[package].files, line:sub(2)) and not table.find(agcfg[package].directories, line:sub(2, -2)) then + table.insert(oldFiles, line:sub(2)) + end + else + if not table.find(agcfg[package].files, line:sub(2)) then + table.insert(oldFiles, line:sub(2)) + end + end + end + end + for _, oldFile in pairs(oldFiles) do + print(" Removing " .. oldFile .. "...") + end + return installPackage(package, true) +end + +local fails = {} +if command == "install" then + if not packages or not packages[1] then + print("Please specify packages to install.") + return + end + print("Fetching Ag registry...") + local newRegistry, errorMessage = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/argentum/registry.cfg") + if newRegistry then + local handle = fs.open("/argentum/registry.cfg", "w") + handle:write(newRegistry) + handle:close() + else + print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil") .. "\27[0m") + end + agReg = require("/argentum/registry.cfg") + while true do + if not doChecks(packages[i]) then + table.insert(fails, packages[i]) + table.remove(packageList, table.find(packageList, packages[i])) + end + i = i + 1 + if i > #packages then + break + end + end + local answer + if #fails == 0 then + print("Packages that will be installed: " .. table.concat(packageList, ", ")) + if terminal.read({prefix = "Would you like to proceed? [Y/n] "}):lower() == "n" then + return + end + elseif #packageList == 0 then + print("None of the packages can be installed.") + return + else + print("Some packages cannot be installed.") + print("Packages that will be installed: " .. table.concat(packageList, ", ")) + print("Packages that cannot be installed: " .. table.concat(fails, ", ")) + if terminal.read({prefix = "Would you like to proceed? [y/N] "}):lower() ~= "y" then + return + end + end + for _, failedPackage in pairs(fails) do + table.remove(packages, table.find(packages, failedPackage)) + end + fails = {} + for _, package in ipairs(packages) do + if not installPackage(package) then + table.insert(fails, package) + table.remove(packageList, table.find(packageList, package)) + end + end + if #fails == 0 then + print("Installation completed successfully.") + print("Packages installed: " .. table.concat(packageList, ", ")) + elseif #packageList == 0 then + print("All packages failed to install.") + print("Packages that could not be installed: " .. table.concat(fails, ", ")) + else + print("Some packages failed to install.") + print("Packages installed: " .. table.concat(packageList, ", ")) + print("Packages that could not be installed: " .. table.concat(fails, ", ")) + end +elseif command == "remove" then + if not packages or not packages[1] then + print("Please specify packages to remove.") + return + end + while true do + if not fs.exists("/argentum/store/" .. packages[i]) then + print("\27[91mPackage " .. packages[i] .. " is not installed.\27[0m") + table.insert(fails, packages[i]) + table.remove(packageList, table.find(packageList, packages[i])) + table.remove(packages, table.find(packages, packages[i])) + i = i - 1 + elseif packages[i] == "halyde" then -- yes, this stuff is hard-coded. + print("\27[91mFor obvious reasons, you can't uninstall Halyde.\27[0m") + table.insert(fails, packages[i]) + table.remove(packageList, table.find(packageList, packages[i])) + table.remove(packages, table.find(packages, packages[i])) + i = i - 1 + elseif packages[i] == "argentum" then + print("\27[91mFor obvious reasons, you can't uninstall Argentum.\27[0m") + table.insert(fails, packages[i]) + table.remove(packageList, table.find(packageList, packages[i])) + table.remove(packages, table.find(packages, packages[i])) + i = i - 1 + end + i = i + 1 + if i > #packages then + break + end + end + -- do dependency checks + local packagesInstalled = fs.list("/argentum/store") + for _, currentPackage in pairs(packagesInstalled) do + if currentPackage:sub(-1, -1) == "/" and fs.exists("/argentum/store/" .. currentPackage .. "package.cfg") then + local handle, data, tmpdata = fs.open("/argentum/store/" .. currentPackage .. "package.cfg", "r"), "", nil + repeat + tmpdata = handle:read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + for line in (data.."\n"):gmatch("(.-)\n") do + for i = 1, #packages do + if line == "D" .. packages[i] then + print(packages[i] .. " depends on " .. currentPackage:sub(1, -2)) + if not table.find(packages, currentPackage:sub(1, -2)) then + table.insert(packages, table.find(packages, packages[i]), currentPackage:sub(1, -2)) + table.insert(packageList, currentPackage:sub(1, -2)) + i = i + 1 + end + end + end + end + end + end + local answer + if #fails == 0 then + print("Packages that will be removed: " .. table.concat(packageList, ", ")) + if terminal.read({prefix = "Would you like to proceed? [Y/n] "}):lower() == "n" then + return + end + elseif #packageList == 0 then + print("None of the packages can be removed.") + return + else + print("Some packages cannot be removed.") + print("Packages that will be removed: " .. table.concat(packageList, ", ")) + print("Packages that cannot be removed: " .. table.concat(fails, ", ")) + if terminal.read({prefix = "Would you like to proceed? [y/N] "}):lower() ~= "y" then + return + end + end + for _, failedPackage in pairs(fails) do + table.remove(packages, table.find(packages, failedPackage)) + end + fails = {} + for _, package in ipairs(packages) do + if not removePackage(package) then + table.insert(fails, package) + table.remove(packageList, table.find(packageList, package)) + end + end + if #fails == 0 then + print("Removal completed successfully.") + print("Packages removed: " .. table.concat(packageList, ", ")) + elseif #packageList == 0 then + print("All packages failed to be removed.") + print("Packages that could not be removed: " .. table.concat(fails, ", ")) + else + print("Some packages failed to be removed.") + print("Packages removed: " .. table.concat(packageList, ", ")) + print("Packages that could not be removed: " .. table.concat(fails, ", ")) + end +elseif command == "update" then + print("Fetching Ag registry...") + local newRegistry, errorMessage = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/argentum/registry.cfg") + if newRegistry then + local handle = fs.open("/argentum/registry.cfg", "w") + handle:write(newRegistry) + handle:close() + else + print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil") .. "\27[0m") + end + agReg = require("/argentum/registry.cfg") + if not packages[1] then + local packagesInstalled = fs.list("/argentum/store/") + for _, currentPackage in pairs(packagesInstalled) do + if currentPackage:sub(-1, -1) == "/" and fs.exists("/argentum/store/" .. currentPackage .. "package.cfg") then + table.insert(packages, currentPackage:sub(1, -2)) + table.insert(packageList, currentPackage:sub(1, -2)) + end + end + end + while true do + local nonexistent = false -- I couldn't figure out a better way to do this, so I have to use a flag if the package doesn't exist + if not fs.exists("/argentum/store/" .. packages[i]) then + print("\27[91mPackage " .. packages[i] .. " is not installed.") + table.insert(fails, packages[i]) + table.remove(packageList, table.find(packageList, packages[i])) + table.remove(packages, table.find(packages, packages[i])) + i = i - 1 + nonexistent = true + end + if not nonexistent then + -- Check if up to date + local agcfg = getAgConfig(packages[i], source) + if not agcfg then + return false + end + local handle, data, tmpdata = fs.open("/argentum/store/" .. packages[i] .. "/package.cfg", "r"), "", nil + repeat + tmpdata = handle:read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + local version = "0.0.0" + for line in (data.."\n"):gmatch("(.-)\n") do + if line:sub(1, 1) == "V" then + version = line:sub(2) + break + end + end + if agcfg[packages[i]].version == version then + table.remove(packageList, table.find(packageList, packages[i])) + table.remove(packages, table.find(packages, packages[i])) + i = i - 1 + else + print(packages[i].." is out of date [\x1b[93m"..version.."\x1b[0m < \x1b[92m"..agcfg[packages[i]].version.."\x1b[0m") + end + end + i = i + 1 + if i > #packages then + break + end + end + local answer + if #packageList == 0 then + if #fails == 0 then + print("All packages are up to date.") + return + else + print("None of the packages can be updated.") + return + end + elseif #fails == 0 then + print("Packages that will be updated: " .. table.concat(packageList, ", ")) + if terminal.read({prefix = "Would you like to proceed? [Y/n] "}):lower() == "n" then + return + end + else + print("Some packages cannot be updated.") + print("Packages that will be updated: " .. table.concat(packageList, ", ")) + print("Packages that cannot be updated: " .. table.concat(fails, ", ")) + if terminal.read({prefix = "Would you like to proceed? [y/N] "}):lower() ~= "y" then + return + end + end + for _, failedPackage in pairs(fails) do + table.remove(packages, table.find(packages, failedPackage)) + end + fails = {} + for _, package in pairs(packages) do + -- Previous up-to-date check + + --local agcfg = getAgConfig(package, source) + --if not agcfg then + -- return false + --end + --local handle, data, tmpdata = fs.open("/argentum/store/" .. package .. "/package.cfg", "r"), "", nil + --repeat + -- tmpdata = handle:read(math.huge) + -- data = data .. (tmpdata or "") + --until not tmpdata + --handle:close() + --local version = "0.0.0" + --for line in (data.."\n"):gmatch("(.-)\n") do + -- if line:sub(1, 1) == "V" then + -- version = line:sub(2) + -- break + -- end + --end + --if agcfg[package].version == version then + -- print(package .. " is up to date") + -- goto skip + --end + if not updatePackage(package) then + table.insert(fails, packages[i]) + table.remove(packageList, table.find(packageList, packages[i])) + table.remove(packages, table.find(packages, packages[i])) + goto skip + end + ::skip:: + end + if #fails == 0 then + print("Update completed successfully.") + print("Packages updated: " .. table.concat(packageList, ", ")) + elseif #packageList == 0 then + print("All packages failed to update.") + print("Packages that could not update: " .. table.concat(fails, ", ")) + else + print("Some packages failed to update.") + print("Packages updated: " .. table.concat(packageList, ", ")) + print("Packages that could not update: " .. table.concat(fails, ", ")) + end +elseif command == "info" then + if not packages[1] then + print("Please specify a package to show information about.") + return + end + print("Fetching Ag registry...") + local newRegistry, errorMessage = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/argentum/registry.cfg") + if newRegistry then + local handle = fs.open("/argentum/registry.cfg", "w") + handle:write(newRegistry) + handle:close() + else + print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil") .. "\27[0m") + end + agReg = require("/argentum/registry.cfg") + if not agReg[packages[1]] and not source then + print("\27[91mPackage " .. packages[1] .. " does not exist.") + return + end + local agcfg = getAgConfig(packages[1], source) + if not agcfg then + return false + end + print("\27[93m" .. packages[1] .. "\27[0m v" .. agcfg[packages[1]].version .. "\n " .. (agcfg[packages[1]].description or "No description."):gsub("\n", " \n")) +elseif command == "search" then + if not packages[1] then + print("Please specify a search term.") + return + end + print("Fetching Ag registry...") + local newRegistry, errorMessage = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/argentum/registry.cfg") + if newRegistry then + local handle = fs.open("/argentum/registry.cfg", "w") + handle:write(newRegistry) + handle:close() + else + print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil")) + end + agReg = require("/argentum/registry.cfg") + local searchResults = {} + for packageName, _ in pairs(agReg) do + if packageName:find(packages[1], 1, true) then + table.insert(searchResults, packageName) + end + end + if not searchResults[1] then + print("No search results found for " .. packages[1] .. ".") + return + end + table.sort(searchResults) + print("Search results: \n " .. table.concat(searchResults, "\n ")) +elseif command == "list" then + print("Fetching Ag registry...") + local newRegistry, errorMessage = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/argentum/registry.cfg") + if newRegistry then + local handle = fs.open("/argentum/registry.cfg", "w") + handle:write(newRegistry) + handle:close() + else + print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil") .. "\27[0m") + end + agReg = require("/argentum/registry.cfg") + local sortedPackages = {} + for packageName, _ in pairs(agReg) do + table.insert(sortedPackages, packageName) + end + table.sort(sortedPackages) + print("List of available Ag packages: \n " .. table.concat(sortedPackages, "\n ")) +else + shell.run("help ag") +end diff --git a/src/halyde/apps/bedit.lua b/src/halyde/apps/bedit.lua new file mode 100644 index 0000000..ff6b4ea --- /dev/null +++ b/src/halyde/apps/bedit.lua @@ -0,0 +1,334 @@ +local fs = require("filesystem") +local shell = require("shell") +local gpu = require("component").gpu +local event = require("event") +local computer = require("computer") +local ocelot = require("component").ocelot + +local resX, resY = gpu.getResolution() +local textBuffer = gpu.allocateBuffer(resX, resY - 1) + +local args = {...} +local file = args[1] +if not file then + print("\x1b[91mEnter a file name.") + return +end +if fs.isDirectory(file) then + print("\x1b[91mThe specified file is a directory.") + return +end + +if file:sub(1, 1) ~= "/" then + file = fs.concat(shell.getWorkingDirectory(), file) +end + +local data = "" +if fs.exists(file) then + local handle = fs.open(file) + local tmpdata + repeat + tmpdata = handle:read(math.huge or math.maxinteger) + data = data .. (tmpdata or "") + until not tmpdata +end + +local lines = {} +for line in data:gmatch("[^\r\n]+") do + table.insert(lines, line) +end + +local function renderText(xOffset, yOffset) + gpu.setActiveBuffer(textBuffer) + gpu.setBackground(0x000000) + gpu.setForeground(0xFFFFFF) + gpu.fill(1, 1, resX, resY - 1, " ") + for i = yOffset + 1, #lines do + gpu.set(1, i - yOffset, lines[i]:sub(xOffset + 1)) + end + gpu.setActiveBuffer(0) + gpu.bitblt(0, 1, 1, resX, resY - 1, textBuffer, 1, 1) +end + +local function addLine(pos) + if pos <= #lines then + for i = #lines, pos, -1 do + lines[i + 1] = lines[i] + end + lines[pos] = "" + else + table.insert(lines, "") + end +end + +local function removeLine(pos) + length = #lines + for i = pos + 1, length do + lines[i - 1] = lines[i] + end + lines[length] = nil +end + +local function save() + gpu.setForeground(0xFFFFFF) + gpu.setBackground(0x000000) + gpu.set(1, resY, "Enter a location to save:") + + local savepath = file or "" + local saveCursorX = 27 + #savepath + local ready = false + local eventArgs = {} + + gpu.fill(27, resY, resX - 27, 1, " ") + gpu.set(27, resY, savepath) + gpu.setForeground(0x000000) + gpu.setBackground(0xFFFFFF) + gpu.set(saveCursorX, resY, " ") + gpu.setForeground(0xFFFFFF) + gpu.setBackground(0x000000) + + repeat + local shouldRender = false + coroutine.yield() + + eventArgs = {event.pull("key_down", 0)} + if keyboard.keys[eventArgs[4]] == "enter" then + ready = true + end + if keyboard.keys[eventArgs[4]] == "back" then + savepath = string.sub(savepath, 1, #savepath - 1) + if saveCursorX > 27 then + saveCursorX = saveCursorX - 1 + end + shouldRender = true + end + if not keyboard.keys.special[eventArgs[4]] and keyboard.keys[eventArgs[4]] then + savepath = savepath .. (string.char(eventArgs[3]) or "") + saveCursorX = saveCursorX + 1 + shouldRender = true + end + + if shouldRender then + gpu.fill(27, resY, resX - 27, 1, " ") + gpu.set(27, resY, savepath) + gpu.setForeground(0x000000) + gpu.setBackground(0xFFFFFF) + gpu.set(saveCursorX, resY, " ") + gpu.setForeground(0xFFFFFF) + gpu.setBackground(0x000000) + end + until ready + + gpu.fill(1, resY, resX, 1, " ") + gpu.setForeground(0x000000) + gpu.setBackground(0xFFFFFF) + gpu.set(1, resY, "^X") + gpu.set(10, resY, "^S") + gpu.setForeground(0xFFFFFF) + gpu.setBackground(0x000000) + gpu.set(4, resY, "Exit") + gpu.set(13, resY, "Save") + + local text = table.concat(lines, "\n") + local handle = fs.open(savepath, "w") + handle:write(text) + handle:close() + file = savepath +end + +-- Initialize screen +renderText(0, 0) +gpu.setForeground(0x000000) +gpu.setBackground(0xFFFFFF) +gpu.set(1, resY, "^X") +gpu.set(10, resY, "^S") +gpu.setForeground(0xFFFFFF) +gpu.setBackground(0x000000) +gpu.set(4, resY, "Exit") +gpu.set(13, resY, "Save") + +local scrollX = 0 +local scrollY = 0 +local cursorX = 1 -- Absolute position, not accounting for scrolling +local cursorY = 1 +local cursorWhite = true +local oldTime = computer.uptime() + +while true do + local renderBufferFlag = false -- Flag to render the whole text buffer + -- Handle events + local previousCursorX -- Used for blackening the previous cursor location when the cursor is moved + local previousCursorY + coroutine.yield() + local eventArgs = {} + repeat + eventArgs = {event.pull("key_down", 0)} + + -- The logical solution here for flashing the cursor would be to set the timeout to 0.5, and, if the timeout is reached, change the color. + -- However, that makes scrolling freeze the screen up completely. + -- Thus, for flashing the cursor, a timer is needed. + + if computer.uptime() >= oldTime + 0.5 then + oldTime = computer.uptime() + cursorWhite = not cursorWhite + end + + if next(eventArgs) ~= nil then + cursorWhite = true + oldTime = computer.uptime() + end + + if eventArgs[1] == "key_down" then + -- Mouse events might be added later, that's why this if statement is here + if keyboard.getCtrlDown() then + -- Special commands + if keyboard.keys[eventArgs[4]] == "x" then + goto exit + end + if keyboard.keys[eventArgs[4]] == "s" then + save() + end + else + if keyboard.keys[eventArgs[4]] == "up" and cursorY > 1 then + if cursorY - scrollY <= 1 then + renderBufferFlag = true + scrollY = scrollY - 1 + else + if not previousCursorX and not previousCursorY then + previousCursorX = cursorX + previousCursorY = cursorY + end + end + cursorY = cursorY - 1 + -- The cursor absolute position still has to be moved, even if on-screen it won't move + if cursorX > string.len(lines[cursorY]) + 1 then -- cursor snapping, +1 to be 1 char in front of end of line + cursorX = string.len(lines[cursorY]) + 1 + end + end + if keyboard.keys[eventArgs[4]] == "down" then + if cursorY - scrollY >= resY - 1 then + renderBufferFlag = true + scrollY = scrollY + 1 + else + if not previousCursorX and not previousCursorY then + previousCursorX = cursorX + previousCursorY = cursorY + end + end + cursorY = cursorY + 1 + if cursorX > string.len(lines[cursorY]) + 1 then + cursorX = string.len(lines[cursorY]) + 1 + end + end + if keyboard.keys[eventArgs[4]] == "left" and cursorX > 1 then + if cursorX - scrollX <= 1 then + renderBufferFlag = true + scrollX = scrollX - 1 + else + if not previousCursorX and not previousCursorY then + previousCursorX = cursorX + previousCursorY = cursorY + end + end + cursorX = cursorX - 1 + end + if keyboard.keys[eventArgs[4]] == "right" and cursorX < string.len(lines[cursorY]) + 1 then + if cursorX - scrollX >= resX then + renderBufferFlag = true + scrollX = scrollX + 1 + else + if not previousCursorX and not previousCursorY then + previousCursorX = cursorX + previousCursorY = cursorY + end + end + cursorX = cursorX + 1 + end + if not keyboard.keys.special[eventArgs[4]] then + local line = lines[cursorY] or "" + line = string.sub(line, 1, cursorX - 1) .. string.char(eventArgs[3]) .. string.sub(line, cursorX, -1) + lines[cursorY] = line + cursorX = cursorX + 1 + renderBufferFlag = true + end + if keyboard.keys[eventArgs[4]] == "back" then + local line = lines[cursorY] + local prevline = lines[cursorY - 1] + if cursorX > 1 then + line = string.sub(line, 1, cursorX - 2) .. string.sub(line, cursorX, -1) -- remove 1 char + elseif prevline then + prevline = prevline .. line -- copy line up to the next before removing it + ocelot.log(prevline) + line = nil + end + if line then + lines[cursorY] = line + else + removeLine(cursorY) + end + lines[cursorY - 1] = prevline + if not line then + cursorY = cursorY - 1 -- this can only trigger if the line is not the first + end + if cursorX > 1 then + cursorX = cursorX - 1 + end + renderBufferFlag = true + end + if keyboard.keys[eventArgs[4]] == "enter" then + addLine(cursorY + 1) + cursorY = cursorY + 1 + cursorX = 1 + renderBufferFlag = true + end + end + end + until next(eventArgs) == nil + local displayedCursorX = cursorX - scrollX + local displayedCursorY = cursorY - scrollY + local previousDisplayedCursorX -- What a mouthful + local previousDisplayedCursorY + if previousCursorX and previousCursorY then + previousDisplayedCursorX = previousCursorX - scrollX + previousDisplayedCursorY = previousCursorY - scrollY + end + + if renderBufferFlag then + renderText(scrollX, scrollY) + if cursorWhite then + -- If the cursor is black, then there's no need to do anything because there is no cursor after calling renderText(). + gpu.setForeground(0x000000) + gpu.setBackground(0xFFFFFF) + local letter = gpu.get(displayedCursorX, displayedCursorY) + gpu.set(displayedCursorX, displayedCursorY, letter) + -- TODO: Account for scrolling + end + else + if cursorWhite then + if previousCursorX or previousCursorY then + -- Remove old cursor + gpu.setForeground(0xFFFFFF) + gpu.setBackground(0x000000) + local letter = gpu.get(previousDisplayedCursorX, previousDisplayedCursorY) + gpu.set(previousDisplayedCursorX, previousDisplayedCursorY, letter) + end + + gpu.setForeground(0x000000) + gpu.setBackground(0xFFFFFF) + local letter = gpu.get(displayedCursorX, displayedCursorY) + gpu.set(displayedCursorX, displayedCursorY, letter) + else + -- If renderText() hasn't been called, the cursor may still be white and need to be turned black. + gpu.setForeground(0xFFFFFF) + gpu.setBackground(0x000000) + local letter = gpu.get(displayedCursorX, displayedCursorY) + gpu.set(displayedCursorX, displayedCursorY, letter) + end + end +end + +-- Cleanup +::exit:: +gpu.freeBuffer(textBuffer) +gpu.setActiveBuffer(0) +terminal.clear() diff --git a/src/halyde/apps/beep.lua b/src/halyde/apps/beep.lua new file mode 100644 index 0000000..7be3a74 --- /dev/null +++ b/src/halyde/apps/beep.lua @@ -0,0 +1,19 @@ +local computer = require("computer") +local cliparse = require("cliparse") + +cliparse.config({ + ["f"] = 1, + ["frequency"] = 1, + ["t"] = 1, + ["time"] = 1, +}) +local parsed, err = cliparse.parse(...) +if not parsed then + return print("\x1b[91m" .. err .. "\x1b[0m") +end + +local freq = + tonumber(parsed.flags.f and parsed.flags.f[1] or parsed.flags.frequency and parsed.flags.frequency[1] or "440") +local time = tonumber(parsed.flags.t and parsed.flags.t[1] or parsed.flags.time and parsed.flags.time[1] or "0.1") + +computer.beep(freq, time) diff --git a/src/halyde/apps/boing.lua b/src/halyde/apps/boing.lua new file mode 100644 index 0000000..a1fc9ae --- /dev/null +++ b/src/halyde/apps/boing.lua @@ -0,0 +1,585 @@ +local raster = require("raster") +local event = require("event") + +local color_table = { + 0xAAAAAA, 0x666666, 0xFF0000, 0xFF0000, 0xFFDDDD, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, + 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, + 0xAA00AA, 0x660066, 0xFF0000, 0xFF0000, 0xFFDDDD, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, + 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000 +} + +local ball_bitplanes = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0007, 0xff80, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x001f, 0xffff, 0xfc00, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x00f8, 0x007f, 0xffff, 0xff80, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0fe6, 0x03ff, 0xffff, 0xfff0, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0xfd1b, 0x980f, 0xffff, 0xfffc, 0x0000, 0x0000, + 0x0000, 0x0000, 0x000f, 0xc14e, 0xb410, 0xffff, 0xffff, 0x8000, 0x0000, + 0x0000, 0x0000, 0x00fc, 0x927a, 0xe92a, 0x7fff, 0xffff, 0xf000, 0x0000, + 0x0000, 0x0000, 0x00c5, 0x09db, 0xa26f, 0x6fff, 0xffff, 0xfe00, 0x0000, + 0x0000, 0x0000, 0x01d0, 0x46ee, 0xc4d2, 0x0fff, 0xffff, 0xff80, 0x0000, + 0x0000, 0x0000, 0x0bba, 0x3bbd, 0x99b2, 0xd5bf, 0xffff, 0xffc0, 0x0000, + 0x0000, 0x0000, 0x0d76, 0x5ef6, 0x3324, 0xd57f, 0xffff, 0xffe0, 0x0000, + 0x0000, 0x0000, 0x5dec, 0x8fdc, 0x4665, 0xd55f, 0xffff, 0xfff0, 0x0000, + 0x0000, 0x0000, 0xab99, 0x18f8, 0x8ccd, 0x9455, 0xffff, 0xfff8, 0x0000, + 0x0000, 0x0002, 0xd772, 0x3103, 0x19c9, 0x96aa, 0xffff, 0xfffc, 0x0000, + 0x0000, 0x000d, 0xaecc, 0x6238, 0x3199, 0x96aa, 0x6fff, 0xfffe, 0x0000, + 0x0000, 0x001a, 0xbd98, 0xc673, 0x8399, 0xb695, 0x97ff, 0xffff, 0x8000, + 0x0000, 0x006d, 0x7331, 0x8c73, 0x1b39, 0xb255, 0xbbff, 0xffff, 0xc000, + 0x0000, 0x00d2, 0xe663, 0x18e7, 0x30f3, 0x324a, 0x9dff, 0xffff, 0xe000, + 0x0000, 0x0fa5, 0xccc3, 0x31ce, 0x31c3, 0x324b, 0xebff, 0xffff, 0xf000, + 0x0000, 0x1917, 0xb986, 0x23ce, 0x618c, 0x336d, 0x71ff, 0xffff, 0xf800, + 0x0000, 0x1ade, 0x670c, 0x639c, 0x639c, 0xc325, 0xa8ff, 0xffff, 0xfc00, + 0x0000, 0x32a3, 0xce18, 0xc73c, 0xc319, 0xcf26, 0xf37f, 0xffff, 0xfe00, + 0x0000, 0x35a6, 0x7c31, 0x8e38, 0xc719, 0xccf2, 0xd5df, 0xffff, 0xff00, + 0x0000, 0x6946, 0xc463, 0x1e71, 0x8739, 0x8ccf, 0x7a6f, 0xffff, 0xff00, + 0x0000, 0xcacc, 0xc707, 0x1c71, 0x8e31, 0x9ccc, 0x6d37, 0xffff, 0xff80, + 0x0000, 0xd69d, 0x8e7e, 0x38e3, 0x0e33, 0x98cc, 0xcd4b, 0xffff, 0xff80, + 0x0001, 0x9599, 0x8e73, 0xb9e3, 0x1c73, 0x98cc, 0xc9b7, 0xffff, 0xffc0, + 0x0001, 0xad33, 0x1ce3, 0x81c6, 0x1c63, 0x98cc, 0xc92b, 0xffff, 0xffc0, + 0x0003, 0x4b33, 0x1ce7, 0x0dc6, 0x3c63, 0x18cc, 0xc92b, 0xffff, 0xffe0, + 0x0003, 0x5666, 0x39c7, 0x1c4c, 0x38e7, 0x18cc, 0xccaa, 0x3fff, 0xffe0, + 0x0006, 0x96e6, 0x39ce, 0x1872, 0x78c7, 0x19cc, 0xc495, 0x3fff, 0xfff0, + 0x000d, 0x2ccc, 0x738e, 0x38f3, 0xb0c7, 0x19cc, 0xc495, 0x1fff, 0xfff0, + 0x000d, 0x5dcc, 0x739c, 0x30e7, 0x89c7, 0x39ce, 0xc495, 0x1fff, 0xfff0, + 0x001a, 0x5b98, 0xe71c, 0x71e7, 0x0f8e, 0x31ce, 0x64d4, 0xffff, 0xfff8, + 0x001a, 0xbb18, 0xe738, 0x71cf, 0x1e4e, 0x31ce, 0x64ca, 0x8fff, 0xfff8, + 0x0008, 0xb731, 0xce38, 0xe1ce, 0x1c71, 0x31ce, 0x664a, 0x8fff, 0xfffc, + 0x000a, 0xf631, 0xce78, 0xe38e, 0x3cf1, 0xf1ce, 0x664a, 0x77ff, 0xfffc, + 0x000a, 0x9e63, 0x9c71, 0xc39e, 0x3ce3, 0xc9ce, 0x6649, 0x47ff, 0xfffe, + 0x000a, 0x9063, 0x9cf1, 0xc71c, 0x38e3, 0x8ece, 0x6261, 0x5fff, 0xfffe, + 0x0004, 0x9327, 0x38e3, 0x873c, 0x78e3, 0x9e36, 0x6265, 0x1bff, 0xffff, + 0x0015, 0x933f, 0x39e3, 0x8e38, 0x79e3, 0x9c30, 0x6264, 0x23ff, 0xffff, + 0x0015, 0x3339, 0xb1c7, 0x8e38, 0xf1c7, 0x9c71, 0x9324, 0xafff, 0xffff, + 0x0015, 0x2639, 0x83c7, 0x0c78, 0xf1c7, 0x1c61, 0x9f30, 0x8dff, 0xffff, + 0x0015, 0x2671, 0x8d8f, 0x1c70, 0xe3c7, 0x3c63, 0x9cf2, 0x91ff, 0xffff, + 0x0015, 0x2673, 0x8c76, 0x1871, 0xe38f, 0x3863, 0x1cce, 0x16ff, 0xffff, + 0x0009, 0x2673, 0x1c71, 0x38f1, 0xe38e, 0x38e3, 0x38cd, 0xc6ff, 0xffff, + 0x000b, 0x2673, 0x1861, 0xf0e1, 0xc78e, 0x78c3, 0x39cd, 0xbbff, 0xffff, + 0x000b, 0x6663, 0x18e3, 0xc9e3, 0xc70e, 0x70c7, 0x399d, 0xb47f, 0xffff, + 0x000b, 0x6c67, 0x18e3, 0x8ec3, 0xc71e, 0x71c6, 0x3999, 0xb4ff, 0xffff, + 0x004b, 0x6ce7, 0x38e3, 0x9e27, 0x871c, 0x71c6, 0x3199, 0x34ff, 0xffff, + 0x002b, 0x6ce7, 0x30c3, 0x9c38, 0x4f1c, 0xf1c6, 0x339b, 0x2cff, 0xfffe, + 0x0012, 0x6ce6, 0x31c7, 0x9c78, 0x761c, 0xe186, 0x739b, 0x6aff, 0xfffe, + 0x0016, 0x4cc6, 0x31c7, 0x1c70, 0xf13c, 0xe38e, 0x739b, 0x69ff, 0xfffe, + 0x0096, 0xccce, 0x3187, 0x3c70, 0xe1d8, 0xe38c, 0x733b, 0x69ff, 0xfffe, + 0x00e6, 0xd8ce, 0x718f, 0x3871, 0xe3c1, 0xe38c, 0x6333, 0x69ff, 0xfffe, + 0x0068, 0xd9ce, 0x618e, 0x38f1, 0xc386, 0xc30c, 0x6732, 0x69ff, 0xfffe, + 0x0071, 0x198e, 0x638e, 0x78e1, 0xc78e, 0x271c, 0x6732, 0x59ff, 0xfffe, + 0x0074, 0x219c, 0x630e, 0x70e3, 0xc70c, 0x3898, 0xe732, 0x55ff, 0xfffe, + 0x0034, 0xa61c, 0xe31e, 0x71e3, 0x871c, 0x78f8, 0xc732, 0x55ff, 0xfffe, + 0x0038, 0x866c, 0xc31c, 0x71c7, 0x8f18, 0x71e4, 0xce72, 0x55ff, 0xfffe, + 0x003a, 0x9263, 0xc71c, 0xf1c7, 0x0e38, 0xf1c7, 0x0e66, 0xd5ff, 0xfffe, + 0x001a, 0x1323, 0x261c, 0xe3c7, 0x1e38, 0xe3ce, 0x7664, 0xd5ff, 0xfffe, + 0x001c, 0x5323, 0x393c, 0xe38f, 0x1c70, 0xe38e, 0x71e4, 0xb3ff, 0xfffc, + 0x0015, 0x4323, 0x39d8, 0xe38e, 0x1c71, 0xc79c, 0xe304, 0xabff, 0xfffc, + 0x0015, 0x0933, 0x39c1, 0xe38e, 0x3ce1, 0xc71c, 0xe33b, 0xabff, 0xfff8, + 0x000f, 0x2933, 0x39c6, 0xc79e, 0x38e3, 0x8f39, 0xc636, 0x6bff, 0xfff8, + 0x000a, 0xa933, 0x39c6, 0x3b1c, 0x39c3, 0x8e39, 0xc676, 0x97ff, 0xfff0, + 0x000a, 0xa993, 0x39c6, 0x383c, 0x79c7, 0x0e73, 0x8c6c, 0xafff, 0xfff0, + 0x0007, 0x9591, 0x39ce, 0x30d8, 0x73c7, 0x1c73, 0x8ccd, 0x2fff, 0xffe0, + 0x0005, 0x5491, 0xb9cc, 0x71c4, 0xf386, 0x1ce7, 0x19db, 0x5fff, 0xffe0, + 0x0005, 0x5491, 0x99cc, 0x7187, 0x678e, 0x38e7, 0x1992, 0x9fff, 0xffe0, + 0x0002, 0xd491, 0x998c, 0x718e, 0x1b0c, 0x39ce, 0x33b4, 0xbfff, 0xffc0, + 0x0001, 0x2a99, 0x998c, 0x638e, 0x389c, 0x71ce, 0x3325, 0x7fff, 0xffc0, + 0x0000, 0x9249, 0x998c, 0x631c, 0x31f8, 0x739c, 0x6669, 0x7fff, 0xff80, + 0x0000, 0x4849, 0x998c, 0xe31c, 0x71c4, 0xe39c, 0x665a, 0xffff, 0xff80, + 0x0000, 0x26b9, 0x998c, 0xe718, 0x63cf, 0x0738, 0xccd4, 0xffff, 0xff00, + 0x0000, 0x095f, 0x999c, 0xc638, 0xe38e, 0x3f38, 0xd8b5, 0xffff, 0xff00, + 0x0000, 0x065b, 0x1998, 0xc630, 0xc71c, 0x71b1, 0x9969, 0xffff, 0xfe00, + 0x0000, 0x032f, 0x6198, 0xce71, 0xc73c, 0xe301, 0xb153, 0xffff, 0xfe00, + 0x0000, 0x01d5, 0xa619, 0xcc61, 0x8e38, 0xc61c, 0x32d7, 0xffff, 0xfc00, + 0x0000, 0x02e7, 0xb279, 0x8c63, 0x9c71, 0xcc39, 0x82a7, 0xffff, 0xf800, + 0x0000, 0x0112, 0xd367, 0x9cc3, 0x1ce3, 0x9873, 0x3baf, 0xffff, 0xe000, + 0x0000, 0x0085, 0x5b26, 0x78c7, 0x39e7, 0x30e6, 0x748f, 0xffff, 0xc000, + 0x0000, 0x004b, 0xe926, 0x6186, 0x39ce, 0x619d, 0xe96f, 0xffff, 0x8000, + 0x0000, 0x001c, 0xad26, 0x664e, 0x739c, 0x6333, 0xa6df, 0xffff, 0x0000, + 0x0000, 0x000e, 0xd5a6, 0xcefc, 0x6718, 0xc667, 0x4b7f, 0xfffe, 0x0000, + 0x0000, 0x0004, 0xd4b6, 0xcce3, 0xe739, 0x8cce, 0xb6ff, 0xfff8, 0x0000, + 0x0000, 0x000d, 0x2ab4, 0xcdc6, 0x3e73, 0x19bd, 0x6bff, 0xfff0, 0x0000, + 0x0000, 0x0003, 0x6a94, 0xd9cc, 0x63e6, 0x3374, 0xb7ff, 0xffe0, 0x0000, + 0x0000, 0x0000, 0xd954, 0xdb98, 0xc60c, 0x4eeb, 0x5fff, 0xffc0, 0x0000, + 0x0000, 0x0000, 0x36d5, 0xd331, 0x1860, 0x99d6, 0x7fff, 0xff00, 0x0000, + 0x0000, 0x0000, 0x0d85, 0x9662, 0x31ce, 0xf7ba, 0xffff, 0xfe00, 0x0000, + 0x0000, 0x0000, 0x0313, 0xa6cc, 0x473b, 0x1eb3, 0xffff, 0xf800, 0x0000, + 0x0000, 0x0000, 0x00c1, 0x4d99, 0x9cec, 0x42e7, 0xffff, 0xe000, 0x0000, + 0x0000, 0x0000, 0x0030, 0x7722, 0x7362, 0x087f, 0xffff, 0x8000, 0x0000, + 0x0000, 0x0000, 0x000c, 0xa185, 0xcb90, 0x81ff, 0xfffc, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0003, 0x0fef, 0x2e49, 0x1fff, 0xfff0, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0803, 0x7281, 0xffff, 0xff80, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0038, 0x481f, 0xffff, 0xfe00, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0xf100, 0x007f, 0xf800, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x00fe, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0f7b, 0xfc00, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0xf7ec, 0x3ff0, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x000f, 0x7e70, 0xc4ff, 0x8000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x00f7, 0xe383, 0x0fcd, 0xa000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x057e, 0x0e1c, 0x3f8f, 0x9800, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x1a60, 0x78f0, 0xff1c, 0xde00, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x2cc3, 0xc3c1, 0xfe3c, 0xeb80, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xd187, 0xdf07, 0xfc38, 0xe7e0, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0001, 0x660f, 0xf01f, 0xf879, 0xe798, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0006, 0xcc1f, 0xe0ff, 0xf0f1, 0xe798, 0x0000, 0x0000, 0x0000, + 0x0000, 0x001b, 0x187f, 0xc1ff, 0xe1f1, 0xe7cd, 0x0000, 0x0000, 0x0000, + 0x0000, 0x002e, 0x30ff, 0x83c1, 0xc1e1, 0xe7cc, 0xa000, 0x0000, 0x0000, + 0x0000, 0x00dc, 0xc1ff, 0x0783, 0xe3e1, 0xc7e6, 0xd000, 0x0000, 0x0000, + 0x0000, 0x0171, 0x83fe, 0x0f83, 0xe3c1, 0xc3e6, 0x5800, 0x0000, 0x0000, + 0x0000, 0x06e3, 0x07fc, 0x1f07, 0xc0c3, 0xc3f3, 0x2c00, 0x0000, 0x0000, + 0x0000, 0x0bc6, 0x0ffc, 0x3e0f, 0xc1f3, 0xc3f3, 0x3500, 0x0000, 0x0000, + 0x0000, 0x1198, 0x3ff8, 0x3c0f, 0x81ff, 0xc3f1, 0x9a80, 0x0000, 0x0000, + 0x0000, 0x13e0, 0x7ff0, 0x7c1f, 0x83ff, 0x03f9, 0xcd40, 0x0000, 0x0000, + 0x0000, 0x23c3, 0xffe0, 0xf83f, 0x03fe, 0x0ff8, 0xc5a0, 0x0000, 0x0000, + 0x0000, 0x27c7, 0x9fc1, 0xf03f, 0x07fe, 0x0f3c, 0xe6c0, 0x0000, 0x0000, + 0x0000, 0x4f87, 0x0783, 0xe07e, 0x07fe, 0x0f0c, 0x6360, 0x0000, 0x0000, + 0x0000, 0x8f0f, 0x07c7, 0xe07e, 0x0ffe, 0x1f0f, 0x71b0, 0x0000, 0x0000, + 0x0000, 0x9f1e, 0x0f8f, 0xc0fc, 0x0ffc, 0x1f0f, 0x0198, 0x0000, 0x0000, + 0x0001, 0x1e1e, 0x0f83, 0xc1fc, 0x1ffc, 0x1f0f, 0x0fda, 0x0000, 0x0000, + 0x0001, 0x3e3c, 0x1f03, 0xf1f8, 0x1ffc, 0x1f0f, 0x0fcd, 0x0000, 0x0000, + 0x0002, 0x7c3c, 0x1f07, 0xfff8, 0x3ffc, 0x1f0f, 0x0fcc, 0x8000, 0x0000, + 0x0002, 0x7878, 0x3e07, 0xffb0, 0x3ff8, 0x1f0f, 0x0fcc, 0x8000, 0x0000, + 0x0004, 0xf8f8, 0x3e0f, 0xff82, 0x7ff8, 0x1e0f, 0x07e6, 0x8000, 0x0000, + 0x0009, 0xf0f0, 0x7c0f, 0xff03, 0xfff8, 0x1e0f, 0x07e6, 0x4000, 0x0000, + 0x0009, 0xe1f0, 0x7c1f, 0xff07, 0xf7f8, 0x3e0f, 0x07e6, 0x4000, 0x0000, + 0x0013, 0xe3e0, 0xf81f, 0xfe07, 0xf1f0, 0x3e0f, 0x87e7, 0x4000, 0x0000, + 0x0013, 0xc3e0, 0xf83f, 0xfe0f, 0xe070, 0x3e0f, 0x87f3, 0x2000, 0x0000, + 0x002f, 0xc7c1, 0xf03f, 0xfe0f, 0xe07e, 0x3e0f, 0x87f3, 0x2000, 0x0000, + 0x002c, 0x87c1, 0xf07f, 0xfc0f, 0xc0fe, 0x3e0f, 0x87f3, 0xa800, 0x0000, + 0x002c, 0xef83, 0xe07f, 0xfc1f, 0xc0fc, 0x0e0f, 0x87f1, 0x9000, 0x0000, + 0x002c, 0xe383, 0xe0ff, 0xf81f, 0xc0fc, 0x0f0f, 0x83f9, 0x9000, 0x0000, + 0x0028, 0xe3e7, 0xc0ff, 0xf83f, 0x80fc, 0x1fff, 0x83f9, 0xd400, 0x0000, + 0x0059, 0xe3ff, 0xc1ff, 0xf03f, 0x81fc, 0x1fff, 0x83f8, 0xcc00, 0x0000, + 0x0059, 0xc3fe, 0x01ff, 0xf03f, 0x01f8, 0x1ffe, 0x13f8, 0xc800, 0x0000, + 0x0059, 0xc7fe, 0x03ff, 0xf07f, 0x01f8, 0x1ffe, 0x1ffc, 0xea00, 0x0000, + 0x0059, 0xc7fe, 0x0fff, 0xe07f, 0x03f8, 0x3ffc, 0x1f3c, 0xe600, 0x0000, + 0x0059, 0xc7fc, 0x0f87, 0xe07e, 0x03f0, 0x3ffc, 0x1f0c, 0x6500, 0x0000, + 0x0051, 0xc7fc, 0x1f81, 0xc0fe, 0x03f0, 0x3ffc, 0x3f0e, 0x7500, 0x0000, + 0x0053, 0xc7fc, 0x1f81, 0xc0fe, 0x07f0, 0x7ffc, 0x3e0e, 0x3200, 0x0000, + 0x0053, 0x87fc, 0x1f03, 0xf1fc, 0x07f0, 0x7ff8, 0x3e1e, 0x3e80, 0x0000, + 0x0053, 0x8ff8, 0x1f03, 0xf0fc, 0x07e0, 0x7ff8, 0x3e1e, 0x3e00, 0x0000, + 0x0093, 0x8ff8, 0x3f03, 0xe038, 0x07e0, 0x7ff8, 0x3e1e, 0x3e00, 0x0000, + 0x00b3, 0x8ff8, 0x3f03, 0xe03f, 0xcfe0, 0xfff8, 0x3c1c, 0x3e00, 0x0000, + 0x00a3, 0x8ff8, 0x3e07, 0xe07f, 0xffe0, 0xfff8, 0x7c1c, 0x7c00, 0x0000, + 0x00a7, 0x8ff8, 0x3e07, 0xe07f, 0xfec0, 0xfff0, 0x7c1c, 0x7d00, 0x0000, + 0x00a7, 0x0ff0, 0x3e07, 0xc07f, 0xfe00, 0xfff0, 0x7c3c, 0x7c00, 0x0000, + 0x00b7, 0x1ff0, 0x7e0f, 0xc07f, 0xfc01, 0xfff0, 0x7c3c, 0x7c00, 0x0000, + 0x00ff, 0x1ff0, 0x7e0f, 0xc0ff, 0xfc07, 0xfff0, 0x783c, 0x7c00, 0x0000, + 0x005e, 0x1ff0, 0x7c0f, 0x80ff, 0xf80f, 0xdfe0, 0x783c, 0x7c00, 0x0000, + 0x005f, 0x3fe0, 0x7c0f, 0x80ff, 0xf80f, 0xc0e0, 0xf83c, 0x7800, 0x0000, + 0x003f, 0x3860, 0xfc1f, 0x81ff, 0xf81f, 0x80e0, 0xf83c, 0x7800, 0x0000, + 0x002f, 0x1870, 0xfc1f, 0x81ff, 0xf01f, 0x81f8, 0xf07c, 0x7800, 0x0000, + 0x002f, 0x1c7c, 0xf81f, 0x01ff, 0xf03f, 0x01f8, 0x3078, 0xf800, 0x0000, + 0x001f, 0x9c3c, 0x381f, 0x03ff, 0xe03f, 0x03f0, 0x7878, 0xf800, 0x0000, + 0x0017, 0x9c3c, 0x3f3f, 0x03ff, 0xe07f, 0x03f0, 0x7ff8, 0xf800, 0x0000, + 0x001f, 0x8c3c, 0x3fff, 0x03ff, 0xe07e, 0x07e0, 0xfff8, 0xf000, 0x0000, + 0x001f, 0xce3c, 0x3ffe, 0x03ff, 0xc0fe, 0x07e0, 0xffc3, 0xf000, 0x0000, + 0x000b, 0xce3c, 0x3ff8, 0x07ff, 0xc0fc, 0x0fc1, 0xffc7, 0xb000, 0x0000, + 0x000f, 0xce3c, 0x3ff8, 0x3fff, 0xc1fc, 0x0fc1, 0xff87, 0x1800, 0x0000, + 0x000f, 0xce1c, 0x3ff8, 0x3fff, 0x81f8, 0x0f83, 0xff8f, 0x3000, 0x0000, + 0x0005, 0xe61e, 0x3ff0, 0x3f1f, 0x83f8, 0x1f83, 0xff0e, 0x3000, 0x0000, + 0x0007, 0xe71e, 0x3ff0, 0x7e07, 0x03f8, 0x1f07, 0xfe1c, 0x6000, 0x0000, + 0x0007, 0xe71e, 0x1ff0, 0x7e07, 0x87f0, 0x3f07, 0xfe1c, 0xe000, 0x0000, + 0x0003, 0xe71e, 0x1ff0, 0x7e0f, 0xe3f0, 0x3e0f, 0xfc38, 0xc000, 0x0000, + 0x0002, 0xf31e, 0x1ff0, 0x7c0f, 0xc0e0, 0x7e0f, 0xfc39, 0x8000, 0x0000, + 0x0001, 0x5b8e, 0x1ff0, 0x7c1f, 0xc1e0, 0x7c1f, 0xf871, 0x8000, 0x0000, + 0x0000, 0xad8e, 0x1ff0, 0xfc1f, 0x81fc, 0xfc1f, 0xf863, 0x0000, 0x0000, + 0x0000, 0x54fe, 0x1ff0, 0xf81f, 0x83ff, 0xf83f, 0xf0e7, 0x0000, 0x0000, + 0x0000, 0x2278, 0x1fe0, 0xf83f, 0x03ff, 0xc03f, 0xe0c6, 0x0000, 0x0000, + 0x0000, 0x137c, 0x1fe0, 0xf83f, 0x07ff, 0x81ff, 0xe18e, 0x0000, 0x0000, + 0x0000, 0x09bc, 0x7fe0, 0xf07e, 0x07ff, 0x03ff, 0xc19c, 0x0000, 0x0000, + 0x0000, 0x049e, 0x3861, 0xf07e, 0x0fff, 0x07e0, 0xc318, 0x0000, 0x0000, + 0x0000, 0x054e, 0x3c61, 0xf07c, 0x1ffe, 0x0fc1, 0xe338, 0x0000, 0x0000, + 0x0000, 0x02a7, 0x1c79, 0xe0fc, 0x1ffc, 0x1f83, 0xc230, 0x0000, 0x0000, + 0x0000, 0x0157, 0x9c38, 0x60f8, 0x3ff8, 0x3f07, 0x87f0, 0x0000, 0x0000, + 0x0000, 0x00a3, 0x8e38, 0x79f8, 0x3ff0, 0x7e1e, 0x0f80, 0x0000, 0x0000, + 0x0000, 0x0049, 0xce38, 0x7fb0, 0x7fe0, 0x7c3c, 0x3f00, 0x0000, 0x0000, + 0x0000, 0x0024, 0xe638, 0xff00, 0x7fe0, 0xf878, 0x7c00, 0x0000, 0x0000, + 0x0000, 0x0012, 0xe738, 0xff03, 0xffc1, 0xf0f0, 0xf800, 0x0000, 0x0000, + 0x0000, 0x000c, 0x7338, 0xfe07, 0xcf83, 0xe1c1, 0xf000, 0x0000, 0x0000, + 0x0000, 0x0003, 0x7318, 0xfe0f, 0x8307, 0xc387, 0xc000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xdd98, 0xfc1f, 0x07cf, 0x8f0f, 0x8000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x3799, 0xfc3e, 0x1f87, 0x1e1f, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0db9, 0xf87c, 0x3e0f, 0xf83c, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x033b, 0xf8f0, 0x783f, 0xe0f8, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x00cd, 0x91e1, 0xe0ff, 0x83f0, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0032, 0xbbc3, 0x83fc, 0x0c40, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x000c, 0xb1c6, 0x0fe0, 0xe100, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0003, 0x4ff0, 0x3f8e, 0x1000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0802, 0xfcf1, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0038, 0x6f90, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0xfd00, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x00ec, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0fbc, 0xf800, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0xf9f0, 0x3ff0, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x000f, 0x8f80, 0xfb1f, 0x8000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x00f8, 0xfc03, 0xf1ce, 0xa000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0687, 0xf01f, 0xc3f1, 0xd800, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x1c7f, 0x80ff, 0x07e0, 0xea00, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x30fc, 0x03fe, 0x1fc0, 0xf2c0, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xe1f8, 0x5ff8, 0x3fc0, 0xf9b0, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0001, 0x87f0, 0xffe0, 0x7f81, 0xf9ec, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0007, 0x0fe1, 0xff00, 0xff01, 0xf8e3, 0x0000, 0x0000, 0x0000, + 0x0000, 0x001c, 0x1f83, 0xfe03, 0xfe01, 0xf8f1, 0xc000, 0x0000, 0x0000, + 0x0000, 0x0030, 0x3f0f, 0xfc01, 0xfe01, 0xf8f0, 0xf000, 0x0000, 0x0000, + 0x0000, 0x00e0, 0xfe1f, 0xf803, 0xfc01, 0xf8f8, 0xf800, 0x0000, 0x0000, + 0x0000, 0x0181, 0xfc3f, 0xf003, 0xfc01, 0xfc78, 0x6c00, 0x0000, 0x0000, + 0x0000, 0x0703, 0xf87f, 0xe007, 0xff03, 0xfc7c, 0x3600, 0x0000, 0x0000, + 0x0000, 0x0c07, 0xf0ff, 0xc00f, 0xfe03, 0xfc7c, 0x3a00, 0x0000, 0x0000, + 0x0000, 0x1e1f, 0xc1ff, 0xc00f, 0xfe0f, 0xfc7e, 0x1d00, 0x0000, 0x0000, + 0x0000, 0x1cff, 0x87ff, 0x801f, 0xfc1f, 0xfc3e, 0x0e80, 0x0000, 0x0000, + 0x0000, 0x3cfc, 0x0fff, 0x003f, 0xfc1f, 0xf03f, 0x0640, 0x0000, 0x0000, + 0x0000, 0x39f8, 0x1ffe, 0x003f, 0xf81f, 0xf03f, 0x0760, 0x0000, 0x0000, + 0x0000, 0x71f8, 0x07fc, 0x007f, 0xf83f, 0xf00f, 0x83b0, 0x0000, 0x0000, + 0x0000, 0xf3f0, 0x07f8, 0x007f, 0xf03f, 0xe00f, 0x81d8, 0x0000, 0x0000, + 0x0000, 0xe7e0, 0x0ff0, 0x00ff, 0xf03f, 0xe00f, 0xf1ec, 0x0000, 0x0000, + 0x0001, 0xe7e0, 0x0ffc, 0x01ff, 0xe07f, 0xe00f, 0xf1e4, 0x0000, 0x0000, + 0x0001, 0xcfc0, 0x1ffc, 0x01ff, 0xe07f, 0xe00f, 0xf1f2, 0x0000, 0x0000, + 0x0003, 0x8fc0, 0x1ff8, 0x0fff, 0xc07f, 0xe00f, 0xf1f1, 0x0000, 0x0000, + 0x0003, 0x9f80, 0x3ff8, 0x1fff, 0xc0ff, 0xe00f, 0xf0f0, 0xc000, 0x0000, + 0x0007, 0x1f00, 0x3ff0, 0x1ffd, 0x80ff, 0xe00f, 0xf8f8, 0xc000, 0x0000, + 0x000e, 0x3f00, 0x7ff0, 0x3ffc, 0x00ff, 0xe00f, 0xf8f8, 0x6000, 0x0000, + 0x000e, 0x7e00, 0x7fe0, 0x3ff8, 0x01ff, 0xc00f, 0xf8f8, 0x6000, 0x0000, + 0x001c, 0x7c00, 0xffe0, 0x7ff8, 0x01ff, 0xc00f, 0xf8f8, 0x6000, 0x0000, + 0x001c, 0xfc00, 0xffc0, 0x7ff0, 0x007f, 0xc00f, 0xf8fc, 0x3000, 0x0000, + 0x0030, 0xf801, 0xffc0, 0xfff0, 0x007f, 0xc00f, 0xf87c, 0x3000, 0x0000, + 0x0030, 0xf801, 0xff80, 0xfff0, 0x00ff, 0xc00f, 0xf87c, 0x3000, 0x0000, + 0x0030, 0xf003, 0xff81, 0xffe0, 0x00ff, 0xf00f, 0xf87e, 0x1800, 0x0000, + 0x0030, 0xfc03, 0xff01, 0xffe0, 0x00ff, 0xf00f, 0xfc7e, 0x1800, 0x0000, + 0x0030, 0xfc27, 0xff03, 0xffc0, 0x00ff, 0xe03f, 0xfc7e, 0x1800, 0x0000, + 0x0061, 0xfc3f, 0xfe03, 0xffc0, 0x01ff, 0xe03f, 0xfc7f, 0x0800, 0x0000, + 0x0061, 0xfc3f, 0xfe07, 0xffc0, 0x01ff, 0xe07f, 0xec3f, 0x0c00, 0x0000, + 0x0061, 0xf83f, 0xfc07, 0xff80, 0x01ff, 0xe07f, 0xe03f, 0x0c00, 0x0000, + 0x0061, 0xf87f, 0xf00f, 0xff80, 0x03ff, 0xc07f, 0xe03f, 0x0400, 0x0000, + 0x0061, 0xf87f, 0xf007, 0xff80, 0x03ff, 0xc07f, 0xe00f, 0x8600, 0x0000, + 0x0061, 0xf87f, 0xe001, 0xff00, 0x03ff, 0xc0ff, 0xc00f, 0x8600, 0x0000, + 0x0063, 0xf87f, 0xe001, 0xff00, 0x07ff, 0x80ff, 0xc00f, 0xc300, 0x0000, + 0x0063, 0xf87f, 0xe003, 0xfe00, 0x07ff, 0x80ff, 0xc01f, 0xc700, 0x0000, + 0x0063, 0xf07f, 0xe003, 0xff00, 0x07ff, 0x81ff, 0xc01f, 0xc700, 0x0000, + 0x00e3, 0xf0ff, 0xc003, 0xffc0, 0x07ff, 0x81ff, 0xc01f, 0xc700, 0x0000, + 0x00c3, 0xf0ff, 0xc003, 0xffc0, 0x4fff, 0x01ff, 0xc01f, 0xcf00, 0x0000, + 0x00c3, 0xf0ff, 0xc007, 0xff80, 0x7fff, 0x01ff, 0x801f, 0x8f00, 0x0000, + 0x00c7, 0xf0ff, 0xc007, 0xff80, 0xffff, 0x03ff, 0x801f, 0x8e00, 0x0000, + 0x00c7, 0xf0ff, 0xc007, 0xff80, 0xffff, 0x03ff, 0x803f, 0x8e00, 0x0000, + 0x00c7, 0xe0ff, 0x800f, 0xff81, 0xfffe, 0x03ff, 0x803f, 0x8e00, 0x0000, + 0x00cf, 0xe1ff, 0x800f, 0xff01, 0xfff8, 0x03ff, 0x803f, 0x8e00, 0x0000, + 0x0067, 0xe1ff, 0x800f, 0xff01, 0xfff0, 0x07ff, 0x803f, 0x9e00, 0x0000, + 0x0067, 0xc1ff, 0x800f, 0xff03, 0xfff0, 0x00ff, 0x003f, 0x9e00, 0x0000, + 0x0027, 0xc07f, 0x001f, 0xfe03, 0xffe0, 0x00ff, 0x003f, 0x9e00, 0x0000, + 0x0033, 0xe07f, 0x001f, 0xfe07, 0xffe0, 0x01ff, 0x007f, 0x9e00, 0x0000, + 0x0033, 0xe07f, 0x001f, 0xfe07, 0xffc0, 0x01ff, 0xc07f, 0x1e00, 0x0000, + 0x0013, 0xe03f, 0xc01f, 0xfc07, 0xffc0, 0x03ff, 0x807f, 0x1e00, 0x0000, + 0x0019, 0xe03f, 0xc13f, 0xfc0f, 0xff80, 0x03ff, 0x81ff, 0x3c00, 0x0000, + 0x0019, 0xf03f, 0xc1ff, 0xfc0f, 0xff80, 0x07ff, 0x03ff, 0x3c00, 0x0000, + 0x0019, 0xf03f, 0xc1ff, 0xfc0f, 0xff00, 0x07ff, 0x03fc, 0x3c00, 0x0000, + 0x000d, 0xf03f, 0xc1ff, 0xf81f, 0xff00, 0x0ffe, 0x07f8, 0x3c00, 0x0000, + 0x000c, 0xf03f, 0xc1ff, 0xc01f, 0xfe00, 0x0ffe, 0x07f8, 0x1c00, 0x0000, + 0x000c, 0xf01f, 0xc1ff, 0xc03f, 0xfe00, 0x0ffc, 0x0ff0, 0x3800, 0x0000, + 0x0006, 0xf81f, 0xc1ff, 0xc01f, 0xfc00, 0x1ffc, 0x0ff0, 0x3800, 0x0000, + 0x0006, 0x781f, 0xc1ff, 0x8007, 0xfc00, 0x1ff8, 0x1fe0, 0x7000, 0x0000, + 0x0006, 0x781f, 0xe1ff, 0x8007, 0xf800, 0x3ff8, 0x1fe0, 0xf000, 0x0000, + 0x0003, 0x781f, 0xe1ff, 0x800f, 0xfc00, 0x3ff0, 0x3fc0, 0xe000, 0x0000, + 0x0003, 0x3c1f, 0xe1ff, 0x800f, 0xff00, 0x7ff0, 0x3fc1, 0xc000, 0x0000, + 0x0001, 0x9c0f, 0xe1ff, 0x801f, 0xfe00, 0x7fe0, 0x7f81, 0xc000, 0x0000, + 0x0000, 0xce0f, 0xe1ff, 0x001f, 0xfe04, 0xffe0, 0x7f83, 0x8000, 0x0000, + 0x0000, 0x673f, 0xe1ff, 0x001f, 0xfc0f, 0xffc0, 0xff07, 0x8000, 0x0000, + 0x0000, 0x339f, 0xe1ff, 0x003f, 0xfc0f, 0xffc0, 0xff07, 0x0000, 0x0000, + 0x0000, 0x1b9f, 0xe1ff, 0x003f, 0xf81f, 0xfe01, 0xfe0f, 0x0000, 0x0000, + 0x0000, 0x0dcf, 0x81ff, 0x007f, 0xf83f, 0xfc01, 0xfe1e, 0x0000, 0x0000, + 0x0000, 0x06e7, 0xc07e, 0x007f, 0xf03f, 0xf800, 0xfc1c, 0x0000, 0x0000, + 0x0000, 0x0677, 0xc07e, 0x007f, 0xe07f, 0xf001, 0xfc3c, 0x0000, 0x0000, + 0x0000, 0x033b, 0xe07e, 0x00ff, 0xe0ff, 0xe003, 0xfc38, 0x0000, 0x0000, + 0x0000, 0x0199, 0xe03f, 0x80ff, 0xc1ff, 0xc007, 0xf8f8, 0x0000, 0x0000, + 0x0000, 0x00cd, 0xf03f, 0x81ff, 0xc1ff, 0x801f, 0xf1f0, 0x0000, 0x0000, + 0x0000, 0x006e, 0xf03f, 0x87ff, 0x83ff, 0x803f, 0xc7e0, 0x0000, 0x0000, + 0x0000, 0x0037, 0xf83f, 0x0fff, 0x87ff, 0x007f, 0x8f80, 0x0000, 0x0000, + 0x0000, 0x001b, 0x783f, 0x0ffc, 0x07fe, 0x00ff, 0x3f00, 0x0000, 0x0000, + 0x0000, 0x000f, 0xbc3f, 0x0ff8, 0x0ffc, 0x01fe, 0x7c00, 0x0000, 0x0000, + 0x0000, 0x0003, 0xbc1f, 0x1ff0, 0x03f8, 0x03f8, 0xf800, 0x0000, 0x0000, + 0x0000, 0x0000, 0xee1f, 0x1fe0, 0x07f0, 0x0ff3, 0xe000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x3b1e, 0x1fc0, 0x1ff8, 0x1fe7, 0x8000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0ede, 0x1f80, 0x3ff0, 0xffdf, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x03ac, 0x3f00, 0x7fc3, 0xff3c, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x00ee, 0x1e01, 0xff0f, 0xfcf8, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0039, 0x3c03, 0xfc7f, 0xf060, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x000e, 0x3e07, 0xf3ff, 0x0100, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0003, 0x77ff, 0xcff0, 0x1000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x09fc, 0x7f01, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0008, 0x7010, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0500, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x000f, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x003f, 0xfc00, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x01ff, 0xc7f0, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0fff, 0x030f, 0x8000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0xfffc, 0x01f0, 0xe000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0707, 0xffe0, 0x03f9, 0xe800, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x1f9f, 0xff00, 0x07ff, 0xf200, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x3f03, 0xfc00, 0x1fff, 0x0c80, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0xfe00, 0x6000, 0x3fff, 0x0020, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0001, 0xf800, 0xfc00, 0x7ffe, 0x01c8, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0007, 0xf001, 0xff80, 0xfffe, 0x00fe, 0x0000, 0x0000, 0x0000, + 0x0000, 0x001f, 0xe003, 0xffe3, 0xfffe, 0x00fe, 0x0000, 0x0000, 0x0000, + 0x0000, 0x003f, 0xc00f, 0xffff, 0xfffe, 0x00ff, 0x3000, 0x0000, 0x0000, + 0x0000, 0x00ff, 0x001f, 0xfffc, 0x1ffe, 0x00ff, 0x1800, 0x0000, 0x0000, + 0x0000, 0x01fe, 0x003f, 0xfffc, 0x03fe, 0x007f, 0x8c00, 0x0000, 0x0000, + 0x0000, 0x07fc, 0x007f, 0xfff8, 0x003c, 0x007f, 0xc600, 0x0000, 0x0000, + 0x0000, 0x03f8, 0x00ff, 0xfff0, 0x000c, 0x007f, 0xc300, 0x0000, 0x0000, + 0x0000, 0x0060, 0x01ff, 0xfff0, 0x000f, 0x007f, 0xe180, 0x0000, 0x0000, + 0x0000, 0x00f0, 0x07ff, 0xffe0, 0x001f, 0xf03f, 0xf0c0, 0x0000, 0x0000, + 0x0000, 0x00ff, 0x0fff, 0xffc0, 0x001f, 0xfc3f, 0xf860, 0x0000, 0x0000, + 0x0000, 0x01ff, 0xffff, 0xffc0, 0x001f, 0xffff, 0xf860, 0x0000, 0x0000, + 0x0000, 0x01ff, 0xfbff, 0xff80, 0x003f, 0xfff3, 0xfc30, 0x0000, 0x0000, + 0x0000, 0x03ff, 0xf83f, 0xff80, 0x003f, 0xfff0, 0xfe18, 0x0000, 0x0000, + 0x0000, 0x07ff, 0xf00f, 0xff00, 0x003f, 0xfff0, 0x0e0c, 0x0000, 0x0000, + 0x0000, 0x07ff, 0xf000, 0x3e00, 0x007f, 0xfff0, 0x0106, 0x0000, 0x0000, + 0x0000, 0x0fff, 0xe000, 0x0e00, 0x007f, 0xfff0, 0x01e3, 0x0000, 0x0000, + 0x0000, 0x0fff, 0xe000, 0x0e00, 0x007f, 0xfff0, 0x01ff, 0x8000, 0x0000, + 0x0000, 0x1fff, 0xc000, 0x1fc0, 0x00ff, 0xfff0, 0x00ff, 0x0000, 0x0000, + 0x0000, 0x1fff, 0xc000, 0x1ffe, 0x00ff, 0xfff0, 0x00ff, 0x0000, 0x0000, + 0x0000, 0x3fff, 0x8000, 0x3fff, 0xc0ff, 0xfff0, 0x00ff, 0x8000, 0x0000, + 0x0000, 0x7fff, 0x8000, 0x3fff, 0xf9ff, 0xfff0, 0x00ff, 0x8000, 0x0000, + 0x0000, 0x7fff, 0x0000, 0x7fff, 0xffff, 0xfff0, 0x00ff, 0x8000, 0x0000, + 0x0000, 0xffff, 0x0000, 0x7fff, 0xffbf, 0xfff0, 0x00ff, 0xc000, 0x0000, + 0x003c, 0xfffe, 0x0000, 0xffff, 0xff80, 0xfff0, 0x007f, 0xc000, 0x0000, + 0x003f, 0x7ffe, 0x0000, 0xffff, 0xff00, 0x3ff0, 0x007f, 0xc000, 0x0000, + 0x003f, 0x0ffc, 0x0001, 0xffff, 0xff00, 0x07f0, 0x007f, 0xe000, 0x0000, + 0x003f, 0x03fc, 0x0001, 0xffff, 0xff00, 0x00f0, 0x007f, 0xe000, 0x0000, + 0x003f, 0x0038, 0x0003, 0xffff, 0xff00, 0x0038, 0x007f, 0xe000, 0x0000, + 0x007e, 0x0038, 0x0003, 0xffff, 0xfe00, 0x003e, 0x007f, 0xf000, 0x0000, + 0x007e, 0x003f, 0xc007, 0xffff, 0xfe00, 0x007f, 0xf03f, 0xf000, 0x0000, + 0x007e, 0x003f, 0xf007, 0xffff, 0xfe00, 0x007f, 0xfc3f, 0xf000, 0x0000, + 0x007e, 0x007f, 0xfe0f, 0xffff, 0xfc00, 0x007f, 0xffff, 0xf800, 0x0000, + 0x007e, 0x007f, 0xffff, 0xffff, 0xfc00, 0x007f, 0xfff3, 0xf800, 0x0000, + 0x007e, 0x007f, 0xfffe, 0xffff, 0xfc00, 0x00ff, 0xfff0, 0x7800, 0x0000, + 0x007c, 0x007f, 0xfffe, 0x3fff, 0xf800, 0x00ff, 0xfff0, 0x0c00, 0x0000, + 0x007c, 0x007f, 0xfffc, 0x07ff, 0xf800, 0x00ff, 0xffe0, 0x0700, 0x0000, + 0x007c, 0x007f, 0xfffc, 0x00ff, 0xf800, 0x01ff, 0xffe0, 0x0780, 0x0000, + 0x00fc, 0x00ff, 0xfffc, 0x001f, 0xf800, 0x01ff, 0xffe0, 0x0780, 0x0000, + 0x00fc, 0x00ff, 0xfffc, 0x0000, 0x7000, 0x01ff, 0xffe0, 0x0f80, 0x0000, + 0x00fc, 0x00ff, 0xfff8, 0x0000, 0x7800, 0x01ff, 0xffe0, 0x0f00, 0x0000, + 0x00f8, 0x00ff, 0xfff8, 0x0000, 0xff00, 0x03ff, 0xffe0, 0x0f00, 0x0000, + 0x0078, 0x00ff, 0xfff8, 0x0000, 0xffe0, 0x03ff, 0xffc0, 0x0f00, 0x0000, + 0x0008, 0x00ff, 0xfff0, 0x0001, 0xfff8, 0x03ff, 0xffc0, 0x0f00, 0x0000, + 0x000e, 0x01ff, 0xfff0, 0x0001, 0xffff, 0x03ff, 0xffc0, 0x0f00, 0x0000, + 0x0007, 0xc1ff, 0xfff0, 0x0001, 0xffff, 0xe7ff, 0xffc0, 0x1f00, 0x0000, + 0x0007, 0xf9ff, 0xfff0, 0x0003, 0xffff, 0xff7f, 0xffc0, 0x1e00, 0x0000, + 0x0007, 0xffff, 0xffe0, 0x0003, 0xffff, 0xff1f, 0xffc0, 0x1e00, 0x0000, + 0x0003, 0xff8f, 0xffe0, 0x0007, 0xffff, 0xfe03, 0xff80, 0x1e00, 0x0000, + 0x0003, 0xff80, 0xffe0, 0x0007, 0xffff, 0xfe00, 0x3f80, 0x1e00, 0x0000, + 0x0003, 0xffc0, 0x1fe0, 0x0007, 0xffff, 0xfc00, 0x0780, 0x1e00, 0x0000, + 0x0001, 0xffc0, 0x01c0, 0x000f, 0xffff, 0xfc00, 0x0180, 0x3e00, 0x0000, + 0x0001, 0xffc0, 0x01e0, 0x000f, 0xffff, 0xf800, 0x03e0, 0x3c00, 0x0000, + 0x0001, 0xffc0, 0x01f8, 0x000f, 0xffff, 0xf800, 0x03ff, 0x3c00, 0x0000, + 0x0001, 0xffc0, 0x01ff, 0x001f, 0xffff, 0xf000, 0x07ff, 0xfc00, 0x0000, + 0x0000, 0xffc0, 0x01ff, 0xfc1f, 0xffff, 0xf000, 0x07ff, 0xe000, 0x0000, + 0x0000, 0xffe0, 0x01ff, 0xff3f, 0xffff, 0xf000, 0x0fff, 0xc000, 0x0000, + 0x0000, 0xffe0, 0x01ff, 0xffff, 0xffff, 0xe000, 0x0fff, 0xc000, 0x0000, + 0x0000, 0x7fe0, 0x01ff, 0xfffb, 0xffff, 0xe000, 0x1fff, 0x8000, 0x0000, + 0x0000, 0x7fe0, 0x01ff, 0xfff8, 0x7fff, 0xc000, 0x1fff, 0x0000, 0x0000, + 0x0000, 0x7fe0, 0x01ff, 0xfff0, 0x03ff, 0xc000, 0x3fff, 0x0000, 0x0000, + 0x0003, 0xbfe0, 0x01ff, 0xfff0, 0x007f, 0x8000, 0x3ffe, 0x0000, 0x0000, + 0x0001, 0xe7f0, 0x01ff, 0xffe0, 0x001f, 0x8000, 0x7ffe, 0x0000, 0x0000, + 0x0000, 0xf1f0, 0x01ff, 0xffe0, 0x0007, 0x0000, 0x7ffc, 0x0000, 0x0000, + 0x0000, 0x7830, 0x01ff, 0xffe0, 0x000f, 0xe000, 0xfff8, 0x0000, 0x0000, + 0x0000, 0x3c1e, 0x01ff, 0xffc0, 0x000f, 0xf800, 0xfff8, 0x0000, 0x0000, + 0x0000, 0x1c1f, 0x81ff, 0xffc0, 0x001f, 0xffc1, 0xfff0, 0x0000, 0x0000, + 0x0000, 0x0e0f, 0xf9ff, 0xff80, 0x003f, 0xfff1, 0xffe0, 0x0000, 0x0000, + 0x0000, 0x0707, 0xffff, 0xff80, 0x003f, 0xffff, 0xffe0, 0x0000, 0x0000, + 0x0000, 0x0787, 0xff9f, 0xff80, 0x007f, 0xfffe, 0x1fc0, 0x0000, 0x0000, + 0x0000, 0x03c3, 0xff81, 0xff00, 0x00ff, 0xfffc, 0x01c0, 0x0000, 0x0000, + 0x0000, 0x01e1, 0xffc0, 0x1f00, 0x01ff, 0xfff8, 0x00c0, 0x0000, 0x0000, + 0x0000, 0x00f1, 0xffc0, 0x0600, 0x01ff, 0xffe0, 0x01f0, 0x0000, 0x0000, + 0x0000, 0x0070, 0xffc0, 0x07c0, 0x03ff, 0xffc0, 0x07e0, 0x0000, 0x0000, + 0x0000, 0x0038, 0xffc0, 0x0ff0, 0x07ff, 0xff80, 0x0f80, 0x0000, 0x0000, + 0x0000, 0x001c, 0x7fc0, 0x0fff, 0x07ff, 0xff00, 0x3f00, 0x0000, 0x0000, + 0x0000, 0x0000, 0x3fc0, 0x0fff, 0xffff, 0xfe00, 0x7c00, 0x0000, 0x0000, + 0x0000, 0x0000, 0x7fe0, 0x1fff, 0xfcff, 0xfc00, 0xf800, 0x0000, 0x0000, + 0x0000, 0x0000, 0x13e0, 0x1fff, 0xf83f, 0xf003, 0xe000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0460, 0x1fff, 0xe007, 0xe007, 0x8000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0110, 0x1fff, 0xc000, 0xc01f, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x004e, 0x3fff, 0x8003, 0xf03c, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0017, 0xfffe, 0x000f, 0xfff8, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0005, 0xc3fc, 0x007f, 0xff80, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0001, 0xc038, 0x03ff, 0xfe00, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x87f8, 0x0fff, 0xe000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x07ff, 0x7ffe, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0007, 0x8fe0, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0600, 0x0000, 0x0000, 0x0000, 0x0000 +} + +--local SCREEN_W = 336 +--local SCREEN_H = 216 +local SCREEN_W = 320 +local SCREEN_H = 200 +local function l(x1, y1, x2, y2, c) + raster.drawLine(x1-8, y1-8, x2-8, y2-8, c) +end +local function g(x, y) + return raster.get(x-8, y-8) +end +local function s(x, y, c) + raster.set(x-8, y-8, c) +end +local BALL_W = 144 +local BALL_H = 100 +local ONE_PLANE_LEN = BALL_W * BALL_H / 16 + +local function ball() + local color_cycle = 2 + local pos = { x = 0.0, y = 0.0 } + local scroll = { x = -1, y = -1 } + local pixel_index = {} + local is_shadow = {} + local this = {} + function this.pos() + return -pos.x + 77, -pos.y + end + function this.step() + pos.x = pos.x + scroll.x + if pos.x <= -95 or pos.x >= 95 then scroll.x = -scroll.x end + local adj = scroll.y + if pos.y > -10.0 then adj = adj * 1.0 + elseif pos.y > -30.0 then adj = adj * 2.0 + elseif pos.y > -60.0 then adj = adj * 3.0 + else adj = adj * 4.0 end + pos.y = pos.y + adj + if pos.y <= -100 or pos.y >= 0 then scroll.y = -scroll.y end + end + local function orderbits(value) + local result = 0 + for i = 0, 15 do + if (value >> i) & 1 == 1 then + result = result | (1 << (15 - i)) + end + end + return result + end + function this.decode() + for y = 0, BALL_H - 1 do + for x = 0, BALL_W - 1 do + local pos = y * BALL_W + x + local offset = pos // 16 + local bitpos = pos % 16 + + local value = 0 + for plane = 0, 3 do + local word = orderbits(ball_bitplanes[offset + plane * ONE_PLANE_LEN + 1]) + if (word & (1 << bitpos)) ~= 0 then + value = value | (1 << plane) + end + end + + if (value == 1) then + is_shadow[pos + 1] = true + pixel_index[pos + 1] = 0 + else + is_shadow[pos + 1] = false + pixel_index[pos + 1] = value + end + end + end + end + function this.cycle_palette() + if scroll.x > 0 then color_cycle = color_cycle - 1 + else color_cycle = color_cycle + 1 end + + if color_cycle == -1 then color_cycle = 13 + elseif color_cycle == 14 then color_cycle = 0 end + + for i = 0, 6 do + if color_cycle + i < 14 then + color_table[color_cycle + i + 3] = 0xFFFFFF + color_table[color_cycle + i + 19] = 0xFFFFFF + else + color_table[color_cycle + i - 11] = 0xFFFFFF + color_table[color_cycle + i + 5] = 0xFFFFFF + end + end + for i = 7, 13 do + if color_cycle + i < 14 then + color_table[color_cycle + i + 3] = 0xFF0000 + color_table[color_cycle + i + 19] = 0xFF0000 + else + color_table[color_cycle + i - 11] = 0xFF0000 + color_table[color_cycle + i + 5] = 0xFF0000 + end + end + if scroll.x > -1 then + color_table[color_cycle + 3] = 0xFFDDDD + color_table[color_cycle + 19] = 0xFFDDDD + elseif color_cycle + 6 < 14 then + color_table[color_cycle + 9] = 0xFFDDDD + color_table[color_cycle + 25] = 0xFFDDDD + else + color_table[color_cycle - 5] = 0xFFDDDD + color_table[color_cycle + 11] = 0xFFDDDD + end + end + function this.draw(x, y, shadow) + for oy = 0, BALL_H - 1 do + for ox = 0, BALL_W - 1 do + local pos = oy * BALL_W + ox + 1 + if shadow then + if is_shadow[pos] then + local c = g(ox+x, oy+y) + if c == 0xAAAAAA then s(ox+x, oy+y, 0x666666) end + end + else + local idx = pixel_index[pos] + if idx ~= 0 then + local c = color_table[idx + 1] + s(ox+x, oy+y, c) + end + end + end + end + end + return this +end + +function draw_background() + raster.clear() + + local lc = color_table[17] + + local k = 20 + for j = 48, 300, 16 do + l(j, 0, j, 192, lc) + l(j, 192, k, 215, lc) + k = k + 20 + end + for j = 0, 200, 16 do + l(48, j, 288, j, lc) + end + l(45, 194, 291, 194, lc) + l(41, 197, 295, 197, lc) + l(37, 201, 300, 201, lc) + l(30, 207, 308, 207, lc) + l(20, 215, 319, 215, lc) +end + +local b = ball() +b.decode() +raster.init(SCREEN_W/2, SCREEN_H/4, color_table[1]) +local i = 0 +while true do + i = i + 1 + draw_background() + b.cycle_palette() + b.step() + local x, y = b.pos() + b.draw(x, y, true) + b.draw(x, y) + raster.update() + + if event.pull("key_down", 0.01) then + break + end +end + +raster.free() +terminal.clear() \ No newline at end of file diff --git a/src/halyde/apps/boot.lua b/src/halyde/apps/boot.lua new file mode 100644 index 0000000..fd1b658 --- /dev/null +++ b/src/halyde/apps/boot.lua @@ -0,0 +1,60 @@ +local component = require("component") +local computer = require("computer") +local args = {...} + +local force = false + +local forceArgIdx = table.find(args,"-f") or table.find(args,"--force") +if forceArgIdx then + table.remove(args,forceArgIdx) + force = true +end + +local function getComponentID(str) + local function fromSlot(slot) + for i,v in component.list() do + if component.slot(i)==slot then + return i + end + end + end + if str=="hdd1" or str=="#1" then return fromSlot(5) end + if str=="hdd2" or str=="#2" then return fromSlot(6) end + if str=="floppy" or str=="#3" then return fromSlot(7) end + + if #str<3 then return nil,"Abbreviated ID must atleast have 3 characters" end + return component.get(str) +end + +local function fileExists(compID,file) + return component.invoke(compID,"exists",file) and not component.invoke(compID,"isDirectory",file) +end + +if type(args[1])=="string" then + local compID,err = getComponentID(args[1]) + if not compID then + print("\x1b[91mCould not get component ID from '"..args[1].."'.") + if type(err)=="string" then print("\x1b[91m"..err.."\x1b[0m") end + return + end + if not force then + if component.virtual.check(compID) then + return print("\x1b[91mThis component is virtual and cannot be booted from directly.\nID: "..compID.."\x1b[0m") + end + local type = component.type(compID) + if type~="filesystem" and type~="drive" then + return print("\x1b[91mThis component is not a storage medium.\nID: "..compID.."\x1b[0m") + end + if type=="filesystem" and not fileExists(compID,"/init.lua") then + return print("\x1b[91mThis storage medium doesn't have an \"init.lua\" file.\nID: "..compID.."\x1b[0m") + end + end + + computer.setBootAddress(compID) + if computer.getBootAddress()~=compID then + return print("\x1b[91mFailed to set the boot address.\x1b[0m") + end + computer.shutdown(true) +else + require("shell").run("help boot") +end diff --git a/src/halyde/apps/cat.lua b/src/halyde/apps/cat.lua new file mode 100644 index 0000000..df69585 --- /dev/null +++ b/src/halyde/apps/cat.lua @@ -0,0 +1,30 @@ +local fs = require("filesystem") +local shell = require("shell") + +local args = {...} + +if not args[1] then + return shell.run("help cat") +end + +for _, file in pairs(args) do + file = shell.resolvePath(file) + + local handle = fs.open(file, "r") + if handle == nil then + terminal.write("\27[91mCan't open " .. file .. "\27[0m\n") + goto continue + end + local enddata + while true do + local data = handle:read(math.huge or math.maxinteger) + if data == nil then break end + terminal.write(data) + enddata = data + end + handle:close() + if string.sub(enddata, -1, -1) ~= "\n" then + print("") + end + ::continue:: +end diff --git a/src/halyde/apps/cd.lua b/src/halyde/apps/cd.lua new file mode 100644 index 0000000..0de8a0e --- /dev/null +++ b/src/halyde/apps/cd.lua @@ -0,0 +1,26 @@ +local args = {...} + +if args[2] then + terminal.write("\27[91mToo many arguments.\27[0m") +end + +if not args[1] then + return +end + +local fs = require("filesystem") +local shell = require("shell") + +local directory = shell.resolvePath(args[1]) + +if not fs.exists(directory) then + terminal.write("\27[91mError: " .. directory .. ": No such file or directory\27[0m\n") + return +end + +if not fs.isDirectory(directory) then + terminal.write("\27[91mError: " .. directory .. ": Not a directory\27[0m\n") + return +end + +shell.setWorkingDirectory(fs.canonical(directory)) diff --git a/src/halyde/apps/clear.lua b/src/halyde/apps/clear.lua new file mode 100644 index 0000000..b8e56a7 --- /dev/null +++ b/src/halyde/apps/clear.lua @@ -0,0 +1,3 @@ +terminal.clear() +-- truly so much going on here +-- meow diff --git a/src/halyde/apps/cp.lua b/src/halyde/apps/cp.lua new file mode 100644 index 0000000..32d56e2 --- /dev/null +++ b/src/halyde/apps/cp.lua @@ -0,0 +1,70 @@ +local fs = require("filesystem") +local shell = require("shell") + +local args = {...} + +if not args[1] then + return shell.run("help cp") +end + +if not args[2] then + terminal.write("\27[91mError: No destination\27[0m\n") + return +end + +local dest = shell.resolvePath(args[#args]) + +if fs.isFile(dest) then + if #args ~= 2 then + terminal.write("\27[91mError: Destination is not a directory\27[0m\n") + return + end + local src = shell.resolvePath(args[1]) + if not fs.exists(src) then + terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n") + return + end + if fs.isDirectory(src) then + terminal.write("\27[91mError: Cannot write directory " .. src .. " to file " .. dest .. "\27[0m\n") + return + end + fs.copy(src, dest) +elseif fs.isDirectory(dest) then + for i = 1, #args - 1 do + local src = shell.resolvePath(args[i]) + if src == dest then + terminal.write("\27[91mError: Source and destination are the same\27[0m\n") + goto continue + end + + if not fs.exists(src) then + terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n") + goto continue + end + + fs.copy(src, fs.concat(dest, fs.basename(src))) + ::continue:: + end +elseif not fs.exists(dest) then + if #args ~= 2 then + terminal.write("\27[91mError: " .. dest .. ": No such file or directory\27[0m\n") + return + end + local src = shell.resolvePath(args[1]) + if not fs.exists(src) then + terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n") + return + end + local destp = fs.parent(dest) + if not fs.exists(destp) then + terminal.write("\27[91mError: " .. destp .. ": No such file or directory\27[0m\n") + return + end + if not fs.isDirectory(destp) then + terminal.write("\27[91mError: " .. destp .. ": Not a directory\27[0m\n") + return + end + fs.copy(src, dest) +else + terminal.write("\27[91mUnknown error\27[0m\n") +end diff --git a/src/halyde/apps/download.lua b/src/halyde/apps/download.lua new file mode 100644 index 0000000..b7e40e3 --- /dev/null +++ b/src/halyde/apps/download.lua @@ -0,0 +1,57 @@ +local url = ... + +local component = require("component") +local fs = require("filesystem") + +if not component.list("internet")() then + print("\27[91mThis program requires an internet card to run.\27[0m") + return +end + +if not url then + print("Please enter a URL to download from.") + require("shell").run("help download") + return +end + +if url:sub(-1, -1) == "/" then + url = url:sub(1, -2) +end + +local internet = component.internet + +local request, data, tmpdata = nil, "", nil +local status, errorMessage = pcall(function() + request = internet.request(url) + request:finishConnect() +end) +if not status then + print("\27[91mDownload failed: " .. errorMessage .. "\27[0m") +end +local responseCode = request:response() +if responseCode and responseCode ~= 200 then + print("\27[91mDownload failed: " .. tostring(responseCode) .. "\27[0m") +end +repeat + tmpdata = request.read(math.huge) + data = data .. (tmpdata or "") +until not tmpdata +local saveLocation +local saveLocationOK = false +repeat + saveLocation = terminal.read(nil, "File save location: ", fs.concat(require("shell").getWorkingDirectory(), url:match("/([^/]+)$"))) + if fs.isDirectory(saveLocation) then + print("\27[91mThe specified location is a directory.\27[0m") + elseif fs.exists(saveLocation) then + local answer = terminal.read({prefix = "\27[91mThere is already a file at the specified directory. Overwrite it? [Y/n]\27[0m"}) + if answer:lower() ~= "n" then + saveLocationOK = true + end + else + saveLocationOK = true + end +until saveLocationOK +local handle = fs.open(saveLocation, "w") +handle:write(data) +handle:close() +print("File downloaded successfully.") diff --git a/src/halyde/apps/echo.lua b/src/halyde/apps/echo.lua new file mode 100644 index 0000000..97b1171 --- /dev/null +++ b/src/halyde/apps/echo.lua @@ -0,0 +1,7 @@ +local args = {...} +local concatText = args[1] +table.remove(args, 1) +for _, item in pairs(args) do + concatText = concatText .. " " .. item +end +print(concatText) diff --git a/src/halyde/apps/edit.lua b/src/halyde/apps/edit.lua new file mode 100644 index 0000000..013e3a4 --- /dev/null +++ b/src/halyde/apps/edit.lua @@ -0,0 +1,348 @@ +local file = ... +local fs = require("filesystem") +local event = require("event") +local component = require("component") +local unicode = require("unicode") +local workingDirectory = require("shell").getWorkingDirectory() +local gpu = component.gpu +local width, height = gpu.getResolution() +local scrollPosX, scrollPosY = 1, 1 +local cursorPosX, cursorPosY = 1, 1 +local cursorWhite = true +local changesMade = false +local renderBuffer = gpu.allocateBuffer() +local scrollSpeed = 5 +local tab = " " +--local ocelot = component.ocelot + +local function rawset(x, y, text) + terminal.write("\x1b[".. tostring(y) .. ";" .. tostring(x) .. "H" .. text) +end + +local filestring, filepath, handle, data, tmpdata +if file then + if file:sub(1, 1) == "/" then + filepath = file + else + filepath = workingDirectory .. file + end + handle, data, tmpdata = fs.open(filepath, "r"), "", nil + if fs.exists(filepath) then + filestring = filepath + repeat + tmpdata = handle:read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + tmpdata = {} + if data:gmatch("(.-)\n")() then + for line in data:gmatch("(.-)\n") do + local newLine = line:gsub("\r", "") -- this took me SO LONG TO FIGURE OUT AAAAAAAA I HATE CRLF I HATE CRLF I HATE CRLF + table.insert(tmpdata, newLine) + end + else + tmpdata = {data} + end + else + filepath = workingDirectory .. file + filestring = "[NEW FILE]" + tmpdata = {""} + end +else + filepath = "" + filestring = "[NEW FILE]" + tmpdata = {""} +end +local function render() + gpu.setActiveBuffer(renderBuffer) + terminal.clear() + --ocelot.log(tostring(scrollPosY)) + local realCursorX = math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2) + if realCursorX < 1 then + scrollPosX = scrollPosX + realCursorX - 1 + cursorPosX = 1 + realCursorX = math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2) + end + for i = scrollPosY, height + scrollPosY - 3 do + gpu.set(1, i - scrollPosY + 1, (tmpdata[i] or ""):sub(scrollPosX)) + end + rawset(1, height - 1, "\27[107m\27[30m" .. filestring .. string.rep(" ", width)) + rawset(1, height, "\27[107m\27[30m^X\27[0m Exit \27[107m\27[30m^S\27[0m Save" .. string.rep(" ", width)) + local char = gpu.get(realCursorX, cursorPosY) + if cursorWhite then + gpu.setForeground(0) + gpu.setBackground(0xFFFFFF) + end + gpu.set(realCursorX, cursorPosY, char) + gpu.bitblt() + gpu.setActiveBuffer(0) +end + +local renderFlag, cursorRenderFlag = false, false + +local function scrollUp() + cursorPosY = cursorPosY - 1 + cursorRenderFlag = true + cursorWhite = true + if cursorPosY < 1 then + renderFlag = true + scrollPosY = scrollPosY - 1 + cursorPosY = 1 + end + if scrollPosY < 1 then + renderFlag = false + scrollPosY = 1 + end + if math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2) < 1 then + renderFlag = true + end +end + +local function scrollDown() + cursorPosY = cursorPosY + 1 + cursorRenderFlag = true + cursorWhite = true + if cursorPosY + scrollPosY - 1 > #tmpdata then + renderFlag = false + cursorPosY = #tmpdata - scrollPosY + 1 + end + if cursorPosY > height - 2 then + renderFlag = true + scrollPosY = scrollPosY + 1 + cursorPosY = height - 2 + end + if math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2) < 1 then + renderFlag = true + end +end + +local function scrollLeft() + cursorRenderFlag = true + cursorWhite = true + if cursorPosX > 1 then + if cursorPosX <= unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2 then + cursorPosX = cursorPosX - 1 + elseif unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 1 > 1 then + cursorPosX = unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 1 + end + elseif scrollPosX > 1 then + scrollPosX = scrollPosX - 1 + renderFlag = true + end + if math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2) < 1 then + renderFlag = true + end +end + +local function scrollRight() + cursorRenderFlag = true + cursorWhite = true + if cursorPosX <= unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 1 then + cursorPosX = cursorPosX + 1 + end + if cursorPosX > width then + cursorPosX = width + scrollPosX = scrollPosX + 1 + renderFlag = true + end +end + +local function processEvent(args) + renderFlag, cursorRenderFlag = false, false + if args[1] == "key_down" then + local keycode = args[4] + local key = keyboard.keys[keycode] + if keyboard.getCtrlDown() then + return false, false, key + end + if key == "down" and cursorPosY < #tmpdata then + scrollDown() + end + if key == "up" then + scrollUp() + end + if key == "left" then + scrollLeft() + end + if key == "right" then + scrollRight() + end + if key == "enter" then + changesMade = true + renderFlag = true + cursorWhite = true + table.insert(tmpdata, cursorPosY + 1, tmpdata[cursorPosY + scrollPosY - 1]:sub(cursorPosX)) + tmpdata[cursorPosY + scrollPosY - 1] = tmpdata[cursorPosY + scrollPosY - 1]:sub(1, cursorPosX - 1) + cursorPosX = 1 + cursorPosY = cursorPosY + 1 + scrollPosX = 1 + if cursorPosY > height - 2 then + scrollPosY = scrollPosY + 1 + cursorPosY = height - 2 + end + end + if key == "back" then + changesMade = true + cursorRenderFlag = true + cursorWhite = true + if cursorPosX == 1 and cursorPosY + scrollPosY - 1 > 1 then + cursorPosY = cursorPosY - 1 + if cursorPosY < 1 then + scrollPosY = scrollPosY - 1 + cursorPosY = 1 + end + if scrollPosY < 1 then + scrollPosY = 1 + end + cursorPosX = unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2 + if cursorPosX > width then + scrollPosX = cursorPosX - width + 1 + cursorPosX = width + end + tmpdata[cursorPosY + scrollPosY - 1] = tmpdata[cursorPosY + scrollPosY - 1] .. tmpdata[cursorPosY + 1] + table.remove(tmpdata, cursorPosY + 1) + renderFlag = true + else + tmpdata[cursorPosY + scrollPosY - 1] = tmpdata[cursorPosY + scrollPosY - 1]:sub(1, cursorPosX + scrollPosX - 3) .. tmpdata[cursorPosY + scrollPosY - 1]:sub(cursorPosX + scrollPosX - 1) + cursorPosX = math.min(cursorPosX - 1, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) + 1) + if cursorPosX < 1 then + cursorPosX = 1 + scrollPosX = scrollPosX - 1 + renderFlag = true + else + gpu.set(1, cursorPosY, tmpdata[cursorPosY + scrollPosY - 1]:sub(scrollPosX) .. " ") + end + end + end + if key == "tab" then + changesMade = true + cursorRenderFlag = true + cursorWhite = true + tmpdata[cursorPosY + scrollPosY - 1] = tmpdata[cursorPosY + scrollPosY - 1]:sub(1, cursorPosX + scrollPosX - 2) .. tab .. tmpdata[cursorPosY + scrollPosY - 1]:sub(cursorPosX + scrollPosX - 1) + cursorPosX = cursorPosX + unicode.wlen(tab) + if cursorPosX > width then + scrollPosX = scrollPosX + cursorPosX - width + cursorPosX = width + renderFlag = true + else + gpu.set(1, cursorPosY, tmpdata[cursorPosY + scrollPosY - 1]:sub(scrollPosX)) + end + end + if args[3] >= 32 and args[3] <= 126 then + changesMade = true + cursorRenderFlag = true + cursorWhite = true + tmpdata[cursorPosY + scrollPosY - 1] = tmpdata[cursorPosY + scrollPosY - 1]:sub(1, cursorPosX + scrollPosX - 2) .. unicode.char(args[3]) .. tmpdata[cursorPosY + scrollPosY - 1]:sub(cursorPosX + scrollPosX - 1) + cursorPosX = math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1])) + 1 + --ocelot.log(tostring(cursorPosX)) + if cursorPosX > width then + cursorPosX = width + scrollPosX = scrollPosX + 1 + renderFlag = true + else + gpu.set(1, cursorPosY, tmpdata[cursorPosY + scrollPosY - 1]:sub(scrollPosX)) + end + end + elseif args[1] == "scroll" then + if args[5] == 1 then + for i = 1, scrollSpeed do + scrollUp() + end + elseif args[5] == -1 and cursorPosY < #tmpdata then + for i = 1, scrollSpeed do + scrollDown() + end + end + end + return renderFlag, cursorRenderFlag +end + +local function save() + gpu.setBackground(0xFFFFFF) + gpu.setForeground(0) + gpu.set(1, height - 1, string.rep(" ", width)) + rawset(1, height - 1) + local savepath = terminal.read({prefix = "\27[107m\27[30mSave location: ", defaultText = filepath}) + gpu.setBackground(0xFFFFFF) + gpu.setForeground(0) + if fs.exists(savepath) then + gpu.set(1, height - 1, string.rep(" ", width)) + local answer = terminal.read({prefix = "\27[107m\27[30mFile already exists. Overwrite it? [Y/n] "}) + if answer:lower() == "n" then + gpu.setBackground(0xFFFFFF) + gpu.setForeground(0) + gpu.set(1, height - 1, filestring .. string.rep(" ", width)) + return + end + end + local handle, errorMessage = fs.open(savepath, "w") + if handle then + if table.concat(tmpdata, "\n"):sub(-1, -1) == "\n" then + handle:write(table.concat(tmpdata, "\n")) + else + handle:write(table.concat(tmpdata, "\n") .. "\n") -- add a newline at the end to follow POSIX standards + end + handle:close() + gpu.set(1, height - 1, filestring .. string.rep(" ", width)) + else + gpu.set(1, height - 1, "ERROR: " .. errorMessage:gsub("\n", "") .. string.rep(" ", width)) + end + changesMade = false +end + +render() +while true do + local args = {event.pull(0.5)} + local renderFlag, cursorRenderFlag, specialKey = false, false, nil + local previousCursorX, previousCursorY = math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2), cursorPosY + if args and args[1] then + cursorWhite = true + renderFlag, cursorRenderFlag, specialKey = processEvent(args) + if specialKey == "x" then + if changesMade then + rawset(1, height - 1) + local response = terminal.read({prefix = "\27[107m\27[30mWould you like to save changes? [Y/n] "}) + if response:lower() ~= "n" then + save() + end + end + gpu.freeAllBuffers() + terminal.clear() + return + end + if specialKey == "s" then + save() + end + repeat + args = {event.pull("key_down", 0)} + if args and args[1] then + processEvent(args) + end + until not args or not args[1] + else + cursorWhite = not cursorWhite + cursorRenderFlag = true + end + if cursorRenderFlag then + local char = gpu.get(previousCursorX, previousCursorY) + gpu.set(previousCursorX, previousCursorY, char) + local realCursorX = math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2) + if realCursorX < 1 then + scrollPosX = scrollPosX + realCursorX - 1 + cursorPosX = 1 + realCursorX = math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2) + end + local char = gpu.get(realCursorX, cursorPosY) + if cursorWhite then + gpu.setBackground(0xFFFFFF) + gpu.setForeground(0) + end + gpu.set(realCursorX, cursorPosY, char) + if cursorWhite then + gpu.setForeground(0xFFFFFF) + gpu.setBackground(0) + end + end + if renderFlag then + render() + end +end diff --git a/src/halyde/apps/fetch.lua b/src/halyde/apps/fetch.lua new file mode 100644 index 0000000..2ab0e92 --- /dev/null +++ b/src/halyde/apps/fetch.lua @@ -0,0 +1,66 @@ +local component = require("component") +local computer = require("computer") +local filesystem = require("filesystem") + +local function convert(value, fromUnit, toUnit) + local units = {B = 1, KiB = 1024, MiB = 1024^2, GiB = 1024^3} + return value * units[fromUnit] / units[toUnit] +end + +local function printstat(text) + terminal.write("\27[35G" .. text .. "\n") +end + +local logo = "" +local handle, tmpdata = filesystem.open("/halyde/config/oslogo.ans", "r"), nil +repeat + tmpdata = handle:read(math.huge) + logo = logo .. (tmpdata or "") +until not tmpdata +handle:close() + +terminal.write(logo) + +terminal.write("\27[17A") + +printstat("\27[92mOS\27[0m: " .. _OSVERSION) +printstat("\27[92mArchitecture\27[0m: " .. _VERSION) + +local componentCounter = 0 +for _ in component.list() do + componentCounter = componentCounter + 1 +end +printstat("\27[92mComponents\27[0m: " .. tostring(componentCounter)) +printstat("\27[92mCoroutines\27[0m: " .. tostring(#tsched.getTasks())) +printstat("\27[92mBattery\27[0m: " .. tostring(math.floor(computer.energy() / computer.maxEnergy() * 1000 + 0.5) / 10) .. "%") + +local totalMemory = computer.totalMemory() +local usedMemory = computer.totalMemory() - computer.freeMemory() + +local function formatBytes(bytes) + if convert(bytes, "B", "GiB") >= 1 then + return tostring(math.floor(convert(bytes, "B", "GiB") * 100 + 0.5) / 100) .. " GiB" + elseif convert(bytes, "B", "MiB") >= 1 then + return tostring(math.floor(convert(bytes, "B", "MiB") * 100 + 0.5) / 100) .. " MiB" + elseif convert(bytes, "B", "KiB") >= 1 then + return tostring(math.floor(convert(bytes, "B", "KiB") * 100 + 0.5) / 100) .. " KiB" + else + return tostring(bytes) .. " B" + end +end + +printstat("\27[92mMemory\27[0m: " .. formatBytes(usedMemory) .. " / " .. formatBytes(totalMemory)) + +local totalDisk = component.invoke(computer.getBootAddress(), "spaceTotal") +local usedDisk = component.invoke(computer.getBootAddress(), "spaceUsed") + +printstat("\27[92mDisk\27[0m: " .. formatBytes(usedDisk) .. " / " .. formatBytes(totalDisk)) + +local gpuComponent = component.list("gpu")() +local width, height = component.invoke(gpuComponent, "getResolution") +printstat("\27[92mResolution\27[0m: " .. tostring(width) .. "x" .. tostring(height) .. "\n") + +printstat("\27[40m \27[41m \27[42m \27[43m \27[44m \27[45m \27[46m \27[47m ") +printstat("\27[100m \27[101m \27[102m \27[103m \27[104m \27[105m \27[106m \27[107m ") + +terminal.write("\27[5B\27[0m") diff --git a/src/halyde/apps/help.lua b/src/halyde/apps/help.lua new file mode 100644 index 0000000..1c837dd --- /dev/null +++ b/src/halyde/apps/help.lua @@ -0,0 +1,293 @@ +local fs = require("filesystem") +local shell = require("shell") + +local arg = ... or "default" +local what = arg + +local aliases = shell.getAliases() +if aliases[what] then + what = aliases[what] +end +local path = "/halyde/apps/helpdb/" .. what +if not fs.exists(path) then + print("Could not find help file for: " .. arg .. ".") + return +end +if path == "/halyde/apps/helpdb/default" then + local handle = fs.open(path, "r") + local shi = "" + while true do + local tmpdata = handle:read(math.huge or math.maxinteger) + if not tmpdata then break end + shi = shi .. tmpdata + end + shi = lz4(shi) + terminal.write(shi) + return +end +local handle = fs.open(path, "r") +local data = { + command = "", + usage = "", + description = "", + args = {}, + examples = {} +} + +local shi = "" +while true do + local tmpdata = handle:read(math.huge or math.maxinteger) + if not tmpdata then break end + shi = shi .. tmpdata +end +shi = lz4(shi) + +local shit = 1 +local function r() + if shit > #shi then return nil end + local v = shi:sub(shit, shit) + shit = shit + 1 + return v +end + +while true do + local line = "" + while true do + local char = r() + if not char then + if line == "" then + line = nil + break + end + break + end + if char == "\n" then + break + end + if char == "\r" then + local next_char = r() + if next_char and next_char == "\n" then + break + elseif next_char then + local pos = file:seek("cur") + if pos then + file:seek("set", pos - 1) + end + break + end + break + end + line = line .. char + end + if line == nil then + break + end + line = line:match("^%s*(.-)%s*$") + if line then + local key, value = line:match("^(%w+)%s+(.*)$") + if not key then goto continue end + if key:lower() == "command" then + data.command = value + end + + if key:lower() == "usage" then + data.usage = value + end + + if key:lower() == "description" then + data.description = value + end + + if key:lower():match("^arg%d+$") then + local num = key:lower():match("^arg(%d+)$") + if not data.args[tonumber(num)] then data.args[tonumber(num)] = {} end + data.args[tonumber(num)].name = value + end + + if key:lower():match("^arg%d+description$") then + local num = key:lower():match("^arg(%d+)description$") + if not data.args[tonumber(num)] then data.args[tonumber(num)] = {} end + data.args[tonumber(num)].description = value + end + + if key:lower():match("^arg%d+sub%d+$") then + local main_num, sub_num = key:lower():match("^arg(%d+)sub(%d+)$") + if main_num and sub_num then + if not data.args[tonumber(main_num)] then data.args[tonumber(main_num)] = {} end + if not data.args[tonumber(main_num)].subflags then data.args[tonumber(main_num)].subflags = {} end + if not data.args[tonumber(main_num)].subflags[tonumber(sub_num)] then + data.args[tonumber(main_num)].subflags[tonumber(sub_num)] = {} + end + data.args[tonumber(main_num)].subflags[tonumber(sub_num)].name = value + end + end + + if key:lower():match("^arg%d+sub%d+description$") then + local main_num, sub_num = key:lower():match("^arg(%d+)sub(%d+)description$") + if main_num and sub_num then + if not data.args[tonumber(main_num)] then data.args[tonumber(main_num)] = {} end + if not data.args[tonumber(main_num)].subflags then data.args[tonumber(main_num)].subflags = {} end + if not data.args[tonumber(main_num)].subflags[tonumber(sub_num)] then + data.args[tonumber(main_num)].subflags[tonumber(sub_num)] = {} + end + data.args[tonumber(main_num)].subflags[tonumber(sub_num)].description = value + end + end + + if key:lower():match("^example%d+$") then + local num = key:lower():match("^example(%d+)$") + if not data.examples[tonumber(num)] then data.examples[tonumber(num)] = {} end + data.examples[tonumber(num)].name = value + end + + if key:lower():match("^example%d+description$") then + local num = key:lower():match("^example(%d+)description$") + if not data.examples[tonumber(num)] then data.examples[tonumber(num)] = {} end + data.examples[tonumber(num)].description = value + end + ::continue:: + end +end + +handle:close() + +--print(require("serialize")(data, "\t")) + +-- Halyde terminal doesn't support bold (CSI 1 m) but who cares + +if data.command then + terminal.write("\27[1mUsage: \27[0m\n") + terminal.write(" \27[96m" .. data.command) + if data.usage then + terminal.write("\27[93m " .. data.usage) + end + terminal.write("\27[0m\n\n") +end + +local width, height = terminal.getResolution() + +local function wrap_text(text, indent) + if not text then return "" end + local words = {} + for word in text:gmatch("%S+") do + table.insert(words, word) + end + + local lines = {} + local current_line = "" + + for i, word in ipairs(words) do + if #current_line + #word + 1 <= width * 0.66 - indent then + if current_line == "" then + current_line = word + else + current_line = current_line .. " " .. word + end + else + table.insert(lines, current_line) + current_line = word + end + end + if current_line ~= "" then + table.insert(lines, current_line) + end + + local result = {} + for i, line in ipairs(lines) do + if i == 1 then + table.insert(result, line) + else + table.insert(result, string.rep(" ", indent) .. line) + end + end + return table.concat(result, "\n") +end + +if data.description then + terminal.write("\27[1mDescription:\27[0m\n") + terminal.write(" " .. wrap_text(data.description, 2)) + terminal.write("\n\n") +end + +if #data.args > 0 then + terminal.write("\27[1mArguments:\27[0m\n") + local max_len = 0 + for _, flag in ipairs(data.args) do + if flag.name then + max_len = math.max(max_len, #flag.name) + end + for _, subf in ipairs(flag.subflags or {}) do + if subf.name then + max_len = math.max(max_len, #subf.name + 2) + end + end + end + + for _, flag in ipairs(data.args) do + terminal.write(" \27[93m" .. (flag.name or "") .. "\27[0m" .. string.rep(" ", max_len - (flag.name and #flag.name or 0) + 2) .. wrap_text(flag.description, 4 + max_len) .. "\n") + for _, subf in ipairs(flag.subflags or {}) do + terminal.write(" \27[92m" .. (subf.name or "") .. "\27[0m" .. string.rep(" ", max_len - (subf.name and #subf.name or 0)) .. wrap_text(subf.description, 4 + max_len) .. "\n") + end + end + terminal.write("\n") +end + +local function formatExampleName(name, utility) + if not name then return name end + + local contains = false + if name:find(utility, 1, true) then + contains = true + else + for alias, cmd in pairs(aliases) do + if cmd == utility and name:find(alias, 1, true) then + contains = true + break + end + end + end + + if not contains then + return "\27[92m" .. name + end + + local formatted = name + formatted = formatted:gsub("(" .. utility .. ")", "\27[96m%1\27[92m") + for alias, cmd in pairs(aliases) do + if cmd == utility then + formatted = formatted:gsub("(" .. alias .. ")", "\27[96m%1\27[92m") + end + end + + return formatted +end + +if #data.examples > 0 then + terminal.write("\27[1mExamples:\27[0m\n") + local max_len = 0 + for _, flag in ipairs(data.examples) do + max_len = math.max(max_len, #flag.name) + end + + for _, flag in ipairs(data.examples) do + terminal.write(" " .. formatExampleName(flag.name, arg) .. "\27[0m" .. string.rep(" ", max_len - #flag.name + 2) .. wrap_text(flag.description, 4 + max_len) .. "\n") + end + terminal.write("\n") +end + +local first = true +for k, v in pairs(aliases) do + if v == arg then + if first then + terminal.write("\27[1mAliases:\27[0m\n ") + end + terminal.write("\27[96m" .. k) + if not first then + terminal.write("\27[0m, ") + end + first = false + end +end +if not first then + terminal.writec(0xa) +end diff --git a/src/halyde/apps/helpdb/ag2 b/src/halyde/apps/helpdb/ag2 new file mode 100644 index 0000000..95de780 --- /dev/null +++ b/src/halyde/apps/helpdb/ag2 @@ -0,0 +1,47 @@ +COMMAND ag2 +USAGE [COMMAND] [PACKAGES] [FLAGS] +DESCRIPTION Uses the Argentum 2 package manager. +ARG1 COMMAND +ARG1SUB1 install +ARG1SUB2 remove +ARG1SUB3 update +ARG1SUB4 list +ARG1SUB5 repo-list +ARG1SUB6 repo-add +ARG1SUB7 repo-remove +ARG1SUB8 info +ARG2 PACKAGES +ARG3 FLAGS +ARG3SUB1 -x, --exclude-deps +ARG3SUB2 -u, --update-repos +ARG3SUB3 -f, --force +ARG3SUB4 -c, --clean +ARG3SUB5 -s, --source [URL] +ARG3SUB6 -C, --cascade +ARG1DESCRIPTION Specifies the operation for Argentum 2 to do. +ARG1SUB1DESCRIPTION Installs packages. +ARG1SUB2DESCRIPTION Removes packages. +ARG1SUB3DESCRIPTION Updates packages. +ARG1SUB4DESCRIPTION Lists all available packages. +ARG1SUB5DESCRIPTION Lists all installed repositories. +ARG1SUB6DESCRIPTION Adds a custom repository. +ARG1SUB7DESCRIPTION Removes a repository. +ARG1SUB8DESCRIPTION Shows a packages version, description and other relevant information. +ARG2DESCRIPTION Packages to apply operations to. +ARG3DESCRIPTION These flags are available and can be inserted anywhere +ARG3SUB1DESCRIPTION Ignore dependencies. WARNING: Using this can and will leave you with broken packages. Use it at your own risk and only when truly necessary. +ARG3SUB2DESCRIPTION Update the list of repositories. +ARG3SUB3DESCRIPTION Force the operation, even if there are conflicts or unresolvable dependencies. WARNING: Using this can and will leave you with broken packages. Use it at your own risk and only when truly necessary. +ARG3SUB4DESCRIPTION Clean up now-unnecessary packages (previous dependencies). +ARG3SUB5DESCRIPTION Use a custom source for the operation. +ARG3SUB6DESCRIPTION When removing a package that other packages depend on, remove those packages too instead of aborting. +EXAMPLE1 ag2 install halyde +EXAMPLE2 ag2 list +EXAMPLE3 ag2 info halyde +EXAMPLE4 ag2 remove -x edit +EXAMPLE5 ag2 remove -c hal-draw +EXAMPLE1DESCRIPTION Installs the halyde package. +EXAMPLE2DESCRIPTION Lists all packages. +EXAMPLE3DESCRIPTION Shows information about the halyde package. +EXAMPLE4DESCRIPTION Removes edit, but does not remove any packages that depend on it. +EXAMPLE5DESCRIPTION Removes hal-draw and any dependencies that are no longer needed. diff --git a/src/halyde/apps/helpdb/argentum b/src/halyde/apps/helpdb/argentum new file mode 100644 index 0000000..56a64ca --- /dev/null +++ b/src/halyde/apps/helpdb/argentum @@ -0,0 +1,29 @@ +COMMAND argentum +USAGE [COMMAND] [PACKAGES] +DESCRIPTION Uses the Argentum package manager. +ARG1 COMMAND +ARG1SUB1 install +ARG1SUB2 remove +ARG1SUB3 update +ARG1SUB4 list +ARG1SUB5 search +ARG1SUB6 info +ARG2 PACKAGES +ARG1DESCRIPTION Specifies the operation for Ag to do. +ARG1SUB1DESCRIPTION Installs packages. +ARG1SUB2DESCRIPTION Removes packages. +ARG1SUB3DESCRIPTION Updates packages. +ARG1SUB4DESCRIPTION Lists all available packages. +ARG1SUB5DESCRIPTION Searches all available packages. +ARG1SUB6DESCRIPTION Shows information on a specific package. +ARG2DESCRIPTION Packages to apply operations to. +EXAMPLE1 ag install hal-draw +EXAMPLE2 ag list +EXAMPLE3 ag info hal-draw +EXAMPLE4 ag update hal-draw +EXAMPLE5 ag update hal-draw +EXAMPLE1DESCRIPTION Installs the hal-draw package. +EXAMPLE2DESCRIPTION Lists all packages. +EXAMPLE3DESCRIPTION Shows information about hal-draw. +EXAMPLE4DESCRIPTION Updates the hal-draw package if it's not at the newest version. +EXAMPLE5DESCRIPTION Updates all packages. diff --git a/src/halyde/apps/helpdb/beep b/src/halyde/apps/helpdb/beep new file mode 100644 index 0000000..bf870eb --- /dev/null +++ b/src/halyde/apps/helpdb/beep @@ -0,0 +1,8 @@ +COMMAND beep +USAGE [FLAGS] +DESCRIPTION Make the computer beep. +ARG1 FLAGS +ARG1SUB1 -f, --frequency +ARG1SUB2 -t, --time +ARG1SUB1DESCRIPTION Specifies the frequency, in Hz. Defaults to 440Hz. +ARG1SUB2DESCRIPTION Specifies how long, in seconds, the computer should beep. Defaults to 0.1s. diff --git a/src/halyde/apps/helpdb/boot b/src/halyde/apps/helpdb/boot new file mode 100644 index 0000000..b18f793 --- /dev/null +++ b/src/halyde/apps/helpdb/boot @@ -0,0 +1,23 @@ +COMMAND boot +USAGE [ADDRESS] [FLAGS] +DESCRIPTION Restarts and automatically boots into any storage medium. Meant to be used for systems using a Lua BIOS EEPROM. +ARG1 ADDRESS +ARG1SUB1 hdd1 +ARG1SUB2 hdd2 +ARG1SUB3 floppy +ARG1SUB4 +ARG2 FLAGS +ARG2SUB1 -f, --force +ARG1DESCRIPTION The storage medium to boot to. +ARG1SUB1DESCRIPTION The first hard drive inserted in the computer. +ARG1SUB2DESCRIPTION The second hard drive inserted in the computer. +ARG1SUB3DESCRIPTION The floppy disk that is inserted in the computer. +ARG1SUB4DESCRIPTION The ID of the component, abbreviated. Must have three or more characters. +ARG2DESCRIPTION Specifies extra options when executing the command. +ARG2SUB1DESCRIPTION Forces booting into the storage medium. +EXAMPLE1 boot hdd1 +EXAMPLE2 boot hdd2 +EXAMPLE3 boot floppy +EXAMPLE1DESCRIPTION Boot into the first hard drive inserted in the computer. +EXAMPLE2DESCRIPTION Boot into the second hard drive inserted in the computer. +EXAMPLE3DESCRIPTION Boot into the floppy disk inserted in the comuter. diff --git a/src/halyde/apps/helpdb/cat b/src/halyde/apps/helpdb/cat new file mode 100644 index 0000000..c3d8faf --- /dev/null +++ b/src/halyde/apps/helpdb/cat @@ -0,0 +1,9 @@ +COMMAND cat +USAGE [FILES]... +DESCRIPTION Concatenates and prints a file. +ARG1 FILES +ARG1DESCRIPTION Specifies the paths to the files to print. +EXAMPLE1 cat /init.lua +EXAMPLE2 cat help.lua cat.lua +EXAMPLE1DESCRIPTION Concatenates and prints init.lua in the root directory. +EXAMPLE2DESCRIPTION Concatenates and prints help.lua and cat.lua in the current working directory. diff --git a/src/halyde/apps/helpdb/cd b/src/halyde/apps/helpdb/cd new file mode 100644 index 0000000..98c6222 --- /dev/null +++ b/src/halyde/apps/helpdb/cd @@ -0,0 +1,13 @@ +COMMAND cd +USAGE [PATH] +DESCRIPTION Sets the shell working directory. +ARG1 PATH +ARG1DESCRIPTION Specifies the path to set the shell working directory to. +EXAMPLE1 cd /home/ +EXAMPLE2 cd halyde +EXAMPLE3 cd .. +EXAMPLE4 .. +EXAMPLE1DESCRIPTION Sets the shell working directory to /home/. +EXAMPLE2DESCRIPTION Sets the shell working directory to a directory named "halyde" in the current working directory. +EXAMPLE3DESCRIPTION Sets the shell working directory back one directory. +EXAMPLE4DESCRIPTION Equivalent of "cd ..". diff --git a/src/halyde/apps/helpdb/clear b/src/halyde/apps/helpdb/clear new file mode 100644 index 0000000..58108ae --- /dev/null +++ b/src/halyde/apps/helpdb/clear @@ -0,0 +1,4 @@ +COMMAND clear +DESCRIPTION Clears the screen. +EXAMPLE1 clear +EXAMPLE1DESCRIPTION Clears the screen. diff --git a/src/halyde/apps/helpdb/cp b/src/halyde/apps/helpdb/cp new file mode 100644 index 0000000..a09e7b1 --- /dev/null +++ b/src/halyde/apps/helpdb/cp @@ -0,0 +1,11 @@ +COMMAND cp +USAGE [SOURCES]... [DESTINATION] +DESCRIPTION Copy files and directories. +ARG1 SOURCES +ARG2 DESTINATION +ARG1DESCRIPTION Specifies the files and directories to be copied. +ARG2DESCRIPTION Specifies the path or a directory to copy to. +EXAMPLE1 cp /home/a.txt /b.txt +EXAMPLE2 cp c.lua /halyde/apps . +EXAMPLE1DESCRIPTION Copies the file at /home/a.txt to /b.txt. +EXAMPLE2DESCRIPTION Copies c.lua and /halyde/apps to the shell working directory. diff --git a/src/halyde/apps/helpdb/default b/src/halyde/apps/helpdb/default new file mode 100644 index 0000000..3c30d3c --- /dev/null +++ b/src/halyde/apps/helpdb/default @@ -0,0 +1,28 @@ +All default Halyde shell commands: + argentum Uses the Argentum package manager. + boot Boots to another OS. + cat Concatenates and prints a file. + cd Changes directory. + clear Clears the screen. + cp Copies a file. + download Downloads a file from the internet. + echo Prints a message. + edit Opens the text editor. + fetch Displays system information. + help Shows this. + label Labels storage devices and EEPROMS. + ls Lists files. + lscor Lists coroutines. + lua Starts the Lua shell. + mkdir Makes a directory. + mv Moves/renames a file. + reboot Reboots the computer. + resolution Sets the resolution. + rm Deletes a file. + shutdown Shuts down the computer. + +You can get additional information on any app or command by running: + help [COMMAND] + +In the help documentation, an asterisk (*) next to an argument means it is optional. +This is excluding flags, which are all optional. diff --git a/src/halyde/apps/helpdb/download b/src/halyde/apps/helpdb/download new file mode 100644 index 0000000..bdec905 --- /dev/null +++ b/src/halyde/apps/helpdb/download @@ -0,0 +1,7 @@ +COMMAND download +USAGE [URL] +DESCRIPTION Downloads a file from the internet. +ARG1 URL +ARG1DESCRIPTION Specifies the URL from which to download the file from. +EXAMPLE1 download https://github.com/ +EXAMPLE1DESCRIPTION Downloads github.com. diff --git a/src/halyde/apps/helpdb/echo b/src/halyde/apps/helpdb/echo new file mode 100644 index 0000000..6a54326 --- /dev/null +++ b/src/halyde/apps/helpdb/echo @@ -0,0 +1,9 @@ +COMMAND echo +USAGE [TEXT]... +DESCRIPTION Concatenates and prints text to the standard output. +ARG1 TEXT +ARG1DESCRIPTION Text to print. +EXAMPLE1 echo test +EXAMPLE2 echo Hello World! +EXAMPLE1DESCRIPTION Prints "test" to the standard output. +EXAMPLE2DESCRIPTION Prints "Hello World!" to the standard output. diff --git a/src/halyde/apps/helpdb/edit b/src/halyde/apps/helpdb/edit new file mode 100644 index 0000000..ae38b8c --- /dev/null +++ b/src/halyde/apps/helpdb/edit @@ -0,0 +1,9 @@ +COMMAND edit +USAGE [PATH] +DESCRIPTION Opens a file with the text editor, or a new blank file if not specified. +ARG1 PATH +ARG1DESCRIPTION Specifies the file to be opened. +EXAMPLE1 edit +EXAMPLE2 edit /LICENSE +EXAMPLE1DESCRIPTION Opens a new blank file in the text editor. +EXAMPLE2DESCRIPTION Opens /LICENSE in the text editor. diff --git a/src/halyde/apps/helpdb/fetch b/src/halyde/apps/helpdb/fetch new file mode 100644 index 0000000..4ffeed4 --- /dev/null +++ b/src/halyde/apps/helpdb/fetch @@ -0,0 +1,4 @@ +COMMAND fetch +DESCRIPTION Displays system information including OS version, Lua version, memory, etc. +EXAMPLE1 fetch +EXAMPLE1DESCRIPTION Displays system information. diff --git a/src/halyde/apps/helpdb/help b/src/halyde/apps/helpdb/help new file mode 100644 index 0000000..7320a8e --- /dev/null +++ b/src/halyde/apps/helpdb/help @@ -0,0 +1,9 @@ +COMMAND help +USAGE [COMMAND] +DESCRIPTION Displays info on the command specified, or a list of commands if one is not specified. +ARG1 COMMAND +ARG1DESCRIPTION Command to display information on. +EXAMPLE1 help +EXAMPLE2 help cp +EXAMPLE1DESCRIPTION Displays a list of all default commands available. +EXAMPLE2DESCRIPTION Displays information about the cp command. diff --git a/src/halyde/apps/helpdb/label b/src/halyde/apps/helpdb/label new file mode 100644 index 0000000..37f94bf --- /dev/null +++ b/src/halyde/apps/helpdb/label @@ -0,0 +1,24 @@ +COMMAND label +USAGE [ADDRESS] [LABEL] +DESCRIPTION Get or set a label of a component that supports labelling. +ARG1 ADDRESS +ARG1SUB1 eeprom +ARG1SUB2 halyde +ARG1SUB3 slotN +ARG1SUB4 #N +ARG2 LABEL +ARG1DESCRIPTION The component to use for getting or setting the label. +ARG1SUB1DESCRIPTION The computer's EEPROM. +ARG1SUB2DESCRIPTION The drive where the Halyde installation resides in. +ARG1SUB3DESCRIPTION The slot number of the drive, in top-to-bottom order (range 7-9) +ARG1SUB4DESCRIPTION The slot number of the drive, in drive space (range 1-3) +ARG1SUB5DESCRIPTION The ID of the component, abbreviated. Must have three or more characters. +ARG2DESCRIPTION The label to set the component to. If not found, the current label will be printed out. +EXAMPLE1 label #3 +EXAMPLE2 label eeprom +EXAMPLE3 label slot8 Storage +EXAMPLE4 label halyde Halyde +EXAMPLE1DESCRIPTION Get the label of the third drive in the computer. +EXAMPLE2DESCRIPTION Get the label of the EEPROM inserted in the computer. +EXAMPLE3DESCRIPTION Set the drive at slot 8 to have the label "Storage" +EXAMPLE4DESCRIPTION Set the label of the Halyde installation to "Halyde" diff --git a/src/halyde/apps/helpdb/log b/src/halyde/apps/helpdb/log new file mode 100644 index 0000000..7be4caa --- /dev/null +++ b/src/halyde/apps/helpdb/log @@ -0,0 +1,19 @@ +COMMAND log +USAGE [OPERATION] [ARGS] +DESCRIPTION Tool to manage system logs. +ARG1 OPERATION +ARG1SUB1 view [LOG] +ARG1SUB2 list +ARG1SUB3 clear [LOG] +ARG1SUB4 info/warn/error [LOG] [TEXT] +ARG2 ARGS +ARG1DESCRIPTION Operation to do with the system logs. +ARG1SUB1DESCRIPTION View a log file. +ARG1SUB2DESCRIPTION List all logs. +ARG1SUB3DESCRIPTION Clear a log file, or all if none specified. +ARG1SUB4DESCRIPTION Create a log entry for the specified log at the specified log level. +ARG2DESCRIPTION Arguments (specified under OPERATION) +EXAMPLE1 log view example +EXAMPLE2 log list +EXAMPLE3 log clear example +EXAMPLE4 log info example This is an example. diff --git a/src/halyde/apps/helpdb/ls b/src/halyde/apps/helpdb/ls new file mode 100644 index 0000000..d8d1361 --- /dev/null +++ b/src/halyde/apps/helpdb/ls @@ -0,0 +1,11 @@ +COMMAND ls +USAGE [PATH]... +DESCRIPTION Lists all files and directories in the specified path, or in the shell working directory if the path isn't specified. Directories are shown in yellow, executable files are shown in green, and other files are shown in white. +ARG1 PATH +ARG1DESCRIPTION Path to the directories to list files and subdirectories from. +EXAMPLE1 ls +EXAMPLE2 ls /halyde +EXAMPLE3 ls apps +EXAMPLE1DESCRIPTION Lists all files and directories from the current shell working directory. +EXAMPLE2DESCRIPTION Lists all files and directories from /halyde. +EXAMPLE3DESCRIPTION Lists all files and directories from the apps directory in the shell working directory. diff --git a/src/halyde/apps/helpdb/lscor b/src/halyde/apps/helpdb/lscor new file mode 100644 index 0000000..237e935 --- /dev/null +++ b/src/halyde/apps/helpdb/lscor @@ -0,0 +1,4 @@ +COMMAND lscor +DESCRIPTION Lists every active coroutine by ID and name. +EXAMPLE1 lscor +EXAMPLE1DESCRIPTION Lists every active coroutine by ID and name. diff --git a/src/halyde/apps/helpdb/lsdrv b/src/halyde/apps/helpdb/lsdrv new file mode 100644 index 0000000..b76c682 --- /dev/null +++ b/src/halyde/apps/helpdb/lsdrv @@ -0,0 +1,37 @@ +COMMAND lsdrv +USAGE [FLAGS] +DESCRIPTION Shows all drives that are inserted into the computer. +ARG1 FLAGS +ARG1SUB1 -a, --all +ARG1SUB2 -o, --output [COLS] +ARG1SUB3 -s, --show [EXPR] +ARG1SUB4 -S, --sort [EXPR] +ARG1SUB5 EXPR +ARG2 PACKAGES +ARG1DESCRIPTION Specifies extra options when executing the command. +ARG1SUB1DESCRIPTION Shows every column and every component. Acts the same as '-o all -s all'. Possible columns are: "slot", "capacity", "managed", "readOnly", "id", "mount", "bootable", and "label". If the list of columns start with a "+", the default columns will appear first. Default columns are slots, capacity, the entire ID, the mount point, and the drive label. +ARG1SUB2DESCRIPTION Specifies the columns to output in the output table. +ARG1SUB3DESCRIPTION Only list drives when the expression returns 'true'. +ARG1SUB4DESCRIPTION Sort the output by an expression that returns a number. The higher the number, the lower the drive is displayed, and vice-versa. +ARG1SUB5DESCRIPTION An expression in Lua, for filtering or sorting output. If this expression contains spaces, make sure to put quotation marks on them! +ARG1SUB6DESCRIPTION Built-in variables are: "component", "computer", "type", "id", "readonly", "capacity", "managed", "eeprom", "halyde", "tmp", "proxy", "slot", and "all" (true). +EXAMPLE1 lsdrv +EXAMPLE2 lsdrv -a +EXAMPLE3 lsdrv -o +bootable +EXAMPLE4 lsdrv -o slot,label -s halyde +EXAMPLE5 lsdrv -o mount,capacity,label -s "not halyde" +EXAMPLE6 lsdrv -s type=='filesystem' +EXAMPLE7 lsdrv -s slot==1 +EXAMPLE8 lsdrv -S capacity +EXAMPLE9 lsdrv -S -capacity +EXAMPLE10 lsdrv -o +managed -S managed +EXAMPLE1DESCRIPTION Show regular drives, with the default columns. +EXAMPLE2DESCRIPTION Show all storage components, with every column. +EXAMPLE3DESCRIPTION Show drives, with an added "bootable" category. +EXAMPLE4DESCRIPTION Show the slot and the label of the drive where Halyde is installed. +EXAMPLE5DESCRIPTION Show the mount points, capacities and labels of all drives other than Halyde. +EXAMPLE6DESCRIPTION Only show managed drives. +EXAMPLE7DESCRIPTION Show all drives that aren't physical (Virtual components, tmpfs) +EXAMPLE8DESCRIPTION Sort the drives by capacity, in ascending order. +EXAMPLE9DESCRIPTION Sort the drives by capacity, in descending order. +EXAMPLE10DESCRIPTION Show managed drives first, then unmanaged drives second, with an extra "managed" column. diff --git a/src/halyde/apps/helpdb/lua b/src/halyde/apps/helpdb/lua new file mode 100644 index 0000000..a16a940 --- /dev/null +++ b/src/halyde/apps/helpdb/lua @@ -0,0 +1,4 @@ +COMMAND lua +DESCRIPTION Starts the Lua shell, where you can type commands to interpret them in real time. +EXAMPLE1 lua +EXAMPLE1DESCRIPTION Starts the Lua shell. diff --git a/src/halyde/apps/helpdb/maindrv b/src/halyde/apps/helpdb/maindrv new file mode 100644 index 0000000..660b4c4 --- /dev/null +++ b/src/halyde/apps/helpdb/maindrv @@ -0,0 +1,2 @@ +COMMAND maindrv +DESCRIPTION Shows the entire ID of the drive where Halyde is installed to. diff --git a/src/halyde/apps/helpdb/mkdir b/src/halyde/apps/helpdb/mkdir new file mode 100644 index 0000000..10e5cd9 --- /dev/null +++ b/src/halyde/apps/helpdb/mkdir @@ -0,0 +1,7 @@ +COMMAND mkdir +USAGE [PATH]... +DESCRIPTION Makes a directory. +ARG1 PATH +ARG1DESCRIPTION Specifies the path to create the directory in. +EXAMPLE1 mkdir a +EXAMPLE1DESCRIPTION Creates a directory named a in the current shell working directory. diff --git a/src/halyde/apps/helpdb/mv b/src/halyde/apps/helpdb/mv new file mode 100644 index 0000000..38207e6 --- /dev/null +++ b/src/halyde/apps/helpdb/mv @@ -0,0 +1,11 @@ +COMMAND mv +USAGE [SOURCE].. [DESTINATION] +DESCRIPTION Moves/renames a file. +ARG1 SOURCE +ARG2 DESTINATION +ARG1DESCRIPTION Specifies the files and directories to be moved. +ARG2DESCRIPTION Specifies the path or a directory to copy to. +EXAMPLE1 mv /home/a.txt /b.txt +EXAMPLE2 mv ../c.lua /halyde/apps . +EXAMPLE1DESCRIPTION Moves the file at /home/a.txt to /b.txt. +EXAMPLE2DESCRIPTION Moves c.lua from a subdirectory and /halyde/apps to the shell working directory. diff --git a/src/halyde/apps/helpdb/reboot b/src/halyde/apps/helpdb/reboot new file mode 100644 index 0000000..9a5d7bc --- /dev/null +++ b/src/halyde/apps/helpdb/reboot @@ -0,0 +1,4 @@ +COMMAND reboot +DESCRIPTION Reboots the computer. +EXAMPLE1 reboot +EXAMPLE1DESCRIPTION Reboots the computer. diff --git a/src/halyde/apps/helpdb/res b/src/halyde/apps/helpdb/res new file mode 100644 index 0000000..b8a00e1 --- /dev/null +++ b/src/halyde/apps/helpdb/res @@ -0,0 +1,11 @@ +COMMAND res +USAGE [FLAGS] +DESCRIPTION Gets or sets the current resolution. +ARG1 FLAGS +ARG1SUB1 [no flags] +ARG1SUB2 -x [number] +ARG1SUB3 -y [number] +ARG1DESCRIPTION Specifies extra options when executing the command. +ARG1SUB1DESCRIPTION Displays the current and maximum resolution. +ARG1SUB2DESCRIPTION Displays the current and maximum resolution on the x-axis. A new x-axis resolution can be specified as a number. +ARG1SUB3DESCRIPTION Displays the current and maximum resolution on the y-axis. A new y-axis resolution can be specified as a number. diff --git a/src/halyde/apps/helpdb/rm b/src/halyde/apps/helpdb/rm new file mode 100644 index 0000000..1405949 --- /dev/null +++ b/src/halyde/apps/helpdb/rm @@ -0,0 +1,7 @@ +COMMAND rm +USAGE [PATH]... +DESCRIPTION Removes files and directories. +ARG1 PATH +ARG1DESCRIPTION Specifies the files and directories to be moved/renamed. +EXAMPLE1 rm a.txt +EXAMPLE1DESCRIPTION Removes a.txt in the current shell working directory. diff --git a/src/halyde/apps/helpdb/rtest b/src/halyde/apps/helpdb/rtest new file mode 100644 index 0000000..07ad3b3 --- /dev/null +++ b/src/halyde/apps/helpdb/rtest @@ -0,0 +1,3 @@ +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDD +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEFF diff --git a/src/halyde/apps/helpdb/shutdown b/src/halyde/apps/helpdb/shutdown new file mode 100644 index 0000000..9e8efad --- /dev/null +++ b/src/halyde/apps/helpdb/shutdown @@ -0,0 +1,4 @@ +COMMAND shutdown +DESCRIPTION Shuts down the computer. +EXAMPLE1 shutdown +EXAMPLE1DESCRIPTION Shuts down the computer. diff --git a/src/halyde/apps/helpdb/touch b/src/halyde/apps/helpdb/touch new file mode 100644 index 0000000..783169f --- /dev/null +++ b/src/halyde/apps/helpdb/touch @@ -0,0 +1,5 @@ +COMMAND touch +USAGE [FILE]... +DESCRIPTION Create an empty file. +ARG1 FILE +ARG1DESCRIPTION The path of the files to create. diff --git a/src/halyde/apps/label.lua b/src/halyde/apps/label.lua new file mode 100644 index 0000000..b3caf34 --- /dev/null +++ b/src/halyde/apps/label.lua @@ -0,0 +1,92 @@ +local component = require("component") +local computer = require("computer") +local args = {...} +if not args then return print("\x1b[91mCannot get arguments.\x1b[0m") end +if not args[1] then + return require("shell").run("help label") +end +local inputID = args[1] +local comp + +local function componentFromSlot(slotNum) + if slotNum>=5 and slotNum<=8 then slotNum=slotNum-1 end + for i,v in component.list() do + if component.slot(i)==slotNum then + comp=component.proxy(i) + end + end +end + +if inputID=="eeprom" then + comp = component.eeprom +elseif inputID=="halyde" then + comp = component.proxy(computer.getBootAddress()) +elseif inputID:sub(1,4)=="slot" and tonumber(inputID:sub(5)) then + local slotNum = tonumber(inputID:sub(5))-1 + componentFromSlot(slotNum) +elseif inputID:sub(1,1)=="#" and tonumber(inputID:sub(2)) then + local slotNum = tonumber(inputID:sub(2))+5 + componentFromSlot(slotNum) +elseif #inputID>=3 then + local fullID = component.get(inputID) + if not fullID then return print("\x1b[91mCould not find entire component ID from \""..inputID.."\".\x1b[0m") end + comp = component.proxy(fullID) +else + print("\x1b[91mAddress must have atleast 3 characters\x1b[0m") + return require("shell").run("help label") +end +if not comp then + return print("\x1b[91mCould not find component from \""..inputID.."\".\x1b[0m") +end +local compID = comp.address + +local function formatID(id) + return id:sub(1,8).."\x1b[37m"..id:sub(9).."\x1b[0m" +end + +local function unsupported(act) + print("This \x1b[92m"..(comp.type or "unknown").."\x1b[0m component doesn't support "..act.." labels.\nID: "..formatID(compID).."\x1b[0m") +end + +local function compError(act,reason) + print("\x1b[91mAn error occured while "..act.." the label of this component.\nComponent: "..(compID or "unknown id").." ("..((comp or {}).type or "unknown type")..")\n\n"..reason.."\x1b[0m") +end + +local function formatLabel(label) + local res = "No label defined" + if label then res="\""..label.."\"" end + return res +end + +if type(args[2])~="string" then + if not comp.getLabel then + return unsupported("getting") + end + local label + local success,reason = pcall(function() + label = comp.getLabel() + end) + if success then + print("Label of "..formatID(compID)..((comp.type and comp.type~="filesystem") and " ("..comp.type..")" or "")..":\n \x1b[92m"..formatLabel(label).."\x1b[0m") + else + compError("getting",reason) + end +else + if not comp.setLabel then + return unsupported("setting") + end + local newLabel = "" + for i=2,#args do + newLabel=newLabel..tostring(args[i]) + if i<#args then newLabel=newLabel.." " end + end + local label + local success,reason = pcall(function() + label = comp.setLabel(newLabel) + end) + if success then + print("Successfully set label of "..formatID(compID)..(comp.type and " ("..comp.type..")" or "").." to:\n \x1b[92m"..formatLabel(label).."\x1b[0m") + else + compError("setting",reason) + end +end diff --git a/src/halyde/apps/log.lua b/src/halyde/apps/log.lua new file mode 100644 index 0000000..990e04e --- /dev/null +++ b/src/halyde/apps/log.lua @@ -0,0 +1,110 @@ +local log = require("log") +local shell = require("shell") +local fs = require("filesystem") + +local args = {...} +if #args == 0 then + shell.run("help log") + return +end + +local function viewlog(logname) + local logpath = "/halyde/logs/" .. logname .. ".log" + if not fs.exists(logpath) then + print("Log not found.") + return + end + local handle = fs.open(logpath) + local entry = "" + local byte + while true do + byte = handle:read(1) + if not byte then return end + if string.byte(byte) == 0x0a then --check for newline + if string.byte(string.sub(entry, -1, -1)) == 0x0d then --failsafe in case line endings are CRLF + entry = string.sub(entry, 1, -2) + else + entry = string.sub(entry, 1, -1) + end + if entry:sub(1, 4) == "WARN" then + print("\x1b[93m" .. entry .. "\x1b[0m") + elseif entry:sub(1, 5) == "ERROR" then + print("\x1b[91m" .. entry .. "\x1b[0m") + else + print(entry) + end + entry = "" + else + entry = entry .. byte + end + end +end + +local function listlogs() + local files = fs.list("/halyde/logs") + local logs = {} + local j = 1 + for i in ipairs(files) do + if not(string.sub(files[i], -1, -1) == "/") and string.sub(files[i], -4, -1) == ".log" then + logs[j] = string.sub(files[i], 1, -5) + j = j + 1 + end + end + return logs +end + +local function listlogs2() + local logs = listlogs() + print("Found \x1b[93m" .. #logs .. "\x1b[0m logs.") + for i in ipairs(logs) do + if i == #logs then + print("\x1b[93mâ”” \x1b[0m" .. logs[i] .. "\x1b[90m.log\x1b[0m") + else + print("\x1b[93m├ \x1b[0m" .. logs[i] .. "\x1b[90m.log\x1b[0m") + end + end +end + +local function clearlog(logname) + if logname then + local logpath = "/halyde/logs/" .. logname .. ".log" + if not fs.exists(logpath) then + print("Log file not found.") + return + end + local success, err = fs.remove(logpath) + if not success then + print("Failed to remove log file: " .. err) + return + end + else + local logs = listlogs() + local j + for i in ipairs(logs) do + local success, err = fs.remove("/halyde/logs/" .. logs[i] .. ".log") + if not success then + print("Failed to remove log " .. logs[i] .. ": " .. err) + print("Removed" .. i - 1 .. "logs.") + return + end + j = i + end + print("Removed " .. j .. " log(s) successfully.") + end +end + +if args[1] == "view" then + viewlog(args[2]) +elseif args[1] == "list" then + listlogs2() +elseif args[1] == "clear" then + clearlog(args[2]) +elseif args[1] == "info" or args[1] == "warn" or args[1] == "error" then + local loglevel = args[1] + local logname = args[2] + local logtext = args[3] + for i = 4, #args do + logtext = logtext .. " " .. args[i] + end + log[logname][loglevel](logtext) +end diff --git a/src/halyde/apps/ls.lua b/src/halyde/apps/ls.lua new file mode 100644 index 0000000..5ce2c94 --- /dev/null +++ b/src/halyde/apps/ls.lua @@ -0,0 +1,58 @@ +local fs = require("filesystem") +local shell = require("shell") + +local function formatSize(size, isDir) + if isDir then return "[DIR]" end + if size >= 1024^3 then return string.format("%.1fGiB", size / 1024^3) end + if size >= 1024^2 then return string.format("%.1fMiB", size / 1024^2) end + if size >= 1024 then return string.format("%.1fKiB", size / 1024) end + return size.."B" +end + +local function getFileColor(name, isDir) + if isDir then return "\27[93m" end + if name:match("%.lua$") then return "\27[92m" end + return "\27[0m" +end + +local args = {...} +if not args[1] then + args = {require("shell").getWorkingDirectory()} +end + +for _, path in pairs(args) do + path = shell.resolvePath(path) + local files = fs.list(path) + + if not files then + terminal.write("\27[91mError: " .. path .. ": No such file or directory\27[0m\n") + goto continue + end + + local fileList = {} + for _, file in pairs(files) do + local isDir = file:sub(-1) == "/" + local name = isDir and file:sub(1, -2) or file + local size = isDir and 0 or fs.size(fs.concat(path, file)) + table.insert(fileList, {name = name, isDir = isDir, size = size}) + end + -- directories first + -- then files + table.sort(fileList, function(a, b) + if a.isDir ~= b.isDir then return a.isDir end + return a.name < b.name + end) + local maxSizeLen = 0 + for _, item in ipairs(fileList) do + maxSizeLen = math.max(maxSizeLen, #formatSize(item.size, item.isDir)) + end + + terminal.write(path.."\n") + for _, item in ipairs(fileList) do + local sizeStr = formatSize(item.size, item.isDir) + sizeStr = string.rep(" ", maxSizeLen - #sizeStr) .. sizeStr + local color = getFileColor(item.name, item.isDir) + terminal.write(string.format("%s %s%s\27[0m\n", sizeStr, color, item.name)) + end + ::continue:: +end diff --git a/src/halyde/apps/lsdrv.lua b/src/halyde/apps/lsdrv.lua new file mode 100644 index 0000000..ea7bb4c --- /dev/null +++ b/src/halyde/apps/lsdrv.lua @@ -0,0 +1,333 @@ +local serialize = require("serialize") +local component = require("component") +local computer = require("computer") +local unicode = require("unicode") + +local width,height = component.gpu.getResolution() + +local args = {...} +local showAll = not not (table.find(args,"-a") or table.find(args,"--all")) + +local tablePos = {} +local tableOut = {} +local headers = { + ["slot"]="SLOT", + ["capacity"]="CAPACITY", + ["managed"]="MANAGED", + ["readOnly"]="READ-ONLY", + ["id"]="FULL COMPONENT ID", + ["mount"]="MOUNT", + ["bootable"]="BOOTABLE", + ["label"]="LABEL" +} +local headerAlign = { + ["slot"]=false, + ["capacity"]=true, + ["managed"]=false, + ["readOnly"]=false, + ["id"]=false, + ["mount"]=false, + ["bootable"]=false, + ["label"]=false +} +local function addHeader(id) + table.insert(tableOut,{headerAlign[id],headers[id]}) + tablePos[id]=#tableOut +end +for i,v in pairs(tablePos) do print(i,v) end + +local function everyHeader() + addHeader("slot") + addHeader("capacity") + addHeader("managed") + addHeader("readOnly") + addHeader("id") + addHeader("mount") + addHeader("bootable") + addHeader("label") +end + +local function defaultHeaders() + if width>100 then addHeader("slot") end + addHeader("capacity") + if width>80 then addHeader("id") end + addHeader("mount") + addHeader("label") +end + +local function invalidArgSyntax(err) + print(err) + return shell.run("help lsdrv") +end + +local outArgIdx = table.find(args,"-o") or table.find(args,"--output") +if showAll then + everyHeader() +elseif outArgIdx then + table.remove(args,outArgIdx) + local arg = table.remove(args,outArgIdx) + if not arg then return invalidArgSyntax("Argument -o must have a value") end + if arg=="all" then + everyHeader() + else + if arg:sub(1,1)=="+" then + defaultHeaders() + end + for word in string.gmatch(arg:sub(2),"([^,]+)") do + if headers[word] then + addHeader(word) + else + print("\x1b[93mCategory \""..word.."\" doesn't exist\x1b[0m") + end + end + end +else + defaultHeaders() +end + +local function headerUsed(id) + return not not tablePos[id] +end + +local bibytes = not (table.find(args,"-H") or table.find(args,"--si")) +local function formatSize(mem) + local units = bibytes and {"B","KiB","MiB","GiB"} or {"B","KB","MB","GB"} + local unit = 1 + local unitStep = bibytes and 1024 or 1000 + while mem > unitStep and units[unit] do + unit = unit + 1 + mem = mem/unitStep + end + local memStr = tostring(mem):sub(1,5) + if unicode.sub(memStr,-2)==".0" then + memStr=unicode.sub(memStr,1,-3) + end + return memStr.." "..units[unit] +end + +local function fileExists(proxy,path) + return proxy.exists and proxy.isDirectory and proxy.exists(path) and not proxy.isDirectory(path) +end + +local function getBootCode(proxy) + local sector1 = proxy.readSector(1) + for i = 1,#sector1 do + if sector1:sub(i,i)=="\0" then + sector1 = sector1:sub(1,i-1) + break + end + end + return sector1 +end + +local function isBootable(proxy,type) + if type=="drive" then + return not not load(getBootCode(proxy)) + elseif type=="filesystem" then + return not not (fileExists(proxy,"/init.lua") or fileExists(proxy,"/OS.lua")) + end +end + +local function formatBoolean(bool) + return ({[true]="Yes",[false]="No"})[bool] +end + +local function handleComponent(id,type) + if not id then return end + local proxy = component.proxy(id) + if not proxy then return end + -- local out = {} + -- for i=1,#tableOut do table.insert(out,"unknown") end + local slot,capacity,managed,readOnly,mount,bootable,label + + local virtual,vproc = component.virtual.check(id) + local cslot = component.slot(id) + if virtual then + if vproc and vproc.name then + slot="Virtual ("..vproc.name..")" + else + slot="Virtual (unknown)" + end + elseif cslot==-1 then + slot="External" + elseif cslot==9 then + slot="EEPROM" + elseif cslot==5 or cslot==6 then + slot="HDD #"..(cslot-4) + elseif cslot==7 then + slot="Floppy" + else + slot="#"..(cslot+2) + end + + managed="No" + readOnly="No" + if type=="drive" then + capacity=formatSize(proxy.getCapacity()) + mount="/special/drive/"..id:sub(1,3) + elseif type=="filesystem" then + managed="Yes" + if proxy.spaceTotal then + capacity=formatSize(proxy.spaceTotal()) + end + if not proxy.isReadOnly or proxy.isReadOnly() then + readOnly="Yes" + end + mount="/mnt/"..id:sub(1,3) + if computer.getBootAddress()==id then mount="/" end + if computer.tmpAddress()==id then + mount="/tmp" + slot="Temporary" + end + elseif type=="eeprom" then + capacity=formatSize(proxy.getSize()+proxy.getDataSize()) + mount="/special/eeprom/" + end + + if headerUsed("bootable") then + bootable = formatBoolean(isBootable(proxy,type)) + end + + if proxy.getLabel then + local clabel = proxy.getLabel() + label=clabel and serialize(clabel) or "None" + else + label="Unsupported" + end + + local function insertElement(i,v) + if not tablePos[i] then return end + table.insert(tableOut[tablePos[i]],v or "unknown") + end + insertElement("slot",slot) + insertElement("capacity",capacity) + insertElement("managed",managed) + insertElement("readOnly",readOnly) + insertElement("id",id) + insertElement("mount",mount) + insertElement("bootable",bootable) + insertElement("label",label) +end + +local function filter(tbl,test) + local i=1 + while i<=#tbl do + if not test(tbl[i]) then + table.remove(tbl,i) + i=i-1 + end + i=i+1 + end +end + +local function luaExpr(arg) + local func,err = load("local component,computer,type,id,readonly,capacity=... local managed,eeprom,halyde,tmp,proxy,slot,all=type==\"drive\",type==\"eeprom\",id==\""..computer.getBootAddress().."\",id==\""..computer.tmpAddress().."\",component.proxy(id),component.slot(id)+2,true return "..arg) + if func then + return function(comp) + local readOnly,capacity=false,nil + if comp[2]=="filesystem" then + readOnly=component.invoke(comp[1],"isReadOnly") + capacity=component.invoke(comp[1],"spaceTotal") + elseif comp[2]=="drive" then + capacity=component.invoke(comp[1],"getCapacity") + elseif comp[2]=="eeprom" then + capacity=component.invoke(comp[1],"getSize")+component.invoke(comp[1],"getDataSize") + end + return func(component,computer,comp[2],comp[1],readOnly,capacity) + end + else + return nil,err + end +end + +local function boolToNum(val) + if type(val)=="boolean" then + if val then + return 1 + else + return 0 + end + end + return val +end + +local comps = {} +for i,v in component.list("filesystem") do table.insert(comps,{i,v}) end +for i,v in component.list("drive") do table.insert(comps,{i,v}) end +for i,v in component.list("eeprom") do table.insert(comps,{i,v}) end + +if not showAll then + local showArgIdx = table.find(args,"-s") or table.find(args,"--show") + if showArgIdx then + table.remove(args,showArgIdx) + local arg = table.remove(args,showArgIdx) + if not arg then return invalidArgSyntax("Argument -s must have a value") end + local func,err = luaExpr(arg) + if func then + filter(comps,func) + else + return print("\x1b[91mInvalid component filter:\n\n"..tostring(err).."\x1b[0m") + end + else + filter(comps,function(comp) + return comp[2]~="eeprom" and comp[1]~=computer.tmpAddress() + end) + end +end + +local sortArgIdx = table.find(args,"-S") or table.find(args,"--sort") +if sortArgIdx then + table.remove(args,sortArgIdx) + local arg = table.remove(args,sortArgIdx) + if not arg then return invalidArgSyntax("Argument -S must have a value") end + local func,err = luaExpr(arg) + if func then + table.sort(comps,function(a,b) + return (boolToNum(func(a)) or 0)<(boolToNum(func(b)) or 0) + end) + else + return print("\x1b[91mInvalid sort expression:\n\n"..tostring(err).."\x1b[0m") + end +else + table.sort(comps,function(a,b) + return a[1]0 then + for i,v in ipairs(comps) do handleComponent(v[1],v[2]) end +else + return print("Could not find storage components for this filter.") +end + +local function renderTableOutput() + local lines = {} + for i=1,#tableOut[1]-1 do + table.insert(lines,"") + end + + for i=1,#tableOut do + local length = 1 + for j=1,#lines do + tableOut[i][j+1]=tableOut[i][j+1] or "[nil]" + length = math.max(length,unicode.wlen(tableOut[i][j+1])) + end + for j=1,#lines do + if tableOut[i][1] then + lines[j]=lines[j]..string.rep(" ",length-unicode.wlen(tableOut[i][j+1]))..tableOut[i][j+1] + elseif i<#tableOut then + lines[j]=lines[j]..tableOut[i][j+1]..string.rep(" ",length-unicode.wlen(tableOut[i][j+1])) + else + lines[j]=lines[j]..tableOut[i][j+1] + end + if i<#tableOut then lines[j]=lines[j].." " end + end + end + + print(lines[1].."\n") + for i=2,#lines do + print(lines[i]) + end +end + +renderTableOutput() diff --git a/src/halyde/apps/lstsk.lua b/src/halyde/apps/lstsk.lua new file mode 100644 index 0000000..77d6eec --- /dev/null +++ b/src/halyde/apps/lstsk.lua @@ -0,0 +1,8 @@ +local tasks = tsched.getTasks() +print("\27[93m"..tostring(#tasks).."\27[0m tasks active") +for i=1, #tasks do + local pipeChar = "├ " + if i==#tasks then pipeChar = "â”” " end + local task = tasks[i] + print("\27[93m"..pipeChar..(task.id or i).."\27[0m - "..task.name.."\27[37m "..table.concat(task.args or {}," ").." \27[0m") +end diff --git a/src/halyde/apps/lua.lua b/src/halyde/apps/lua.lua new file mode 100644 index 0000000..24228e6 --- /dev/null +++ b/src/halyde/apps/lua.lua @@ -0,0 +1,83 @@ +-- terminal.readHistory["lua"] = {""} +local fs = require("filesystem") +local computer = require("computer") +local log = require("log") + +local bootTime = computer.uptime() +local libList = fs.list("/lib/") +local failed = false +for _, lib in pairs(libList) do + local status, err = xpcall(function() + if lib:match("(.+)%.lua") then + local name = lib:match("(.+)%.lua") + _G[name] = require(name) + end + end, function(errMsg) + return errMsg .. "\n\n" .. debug.traceback() + end) + if not status then + local firstLine = tostring(err):match("^[^\n]*") + print( + string.format( + "\x1b[91mLibrary %s has failed loading:\n │ %s\x1b[0m", + lib:match("(.+)%.lua") or lib, + firstLine or "unknown error" + ) + ) + log.lua.error( + string.format( + 'The library located at "%s" has failed loading:\n%s', + lib, + type(err) ~= "nil" and tostring(err) or "unknown error" + ) + ) + failed = true + end +end + +if failed then + print( + string.format( + '\x1b[93mOne or more libraries failed to load. For more information, check the log entries located at "%s".\x1b[0m', + tostring(log.lua.logpath or "[unknown]") + ) + ) +end + +print(string.format("\27[37mLoaded %d libraries in %.2f seconds\27[0m", #libList, computer.uptime() - bootTime)) +print(string.format("\27[44m%s\27[0m shell", _VERSION)) +print('Type "exit" to exit.') + +while true do + local command = terminal.read({readHistoryType = "lua", prefix = "\27[44mlua>\27[0m "}) + if command == "exit" then + coroutine.yield() + return + elseif command ~= "" then + local function runCommand() + local func, err = load("return " .. command, "=stdin") + local returns = true + if not func then + func, err = load(command, "=stdin") + returns = false + end + if not func then + return print("\x1b[91msyntax error: " .. (err or "unknown error") .. "\x1b[0m") + end + local res = { func() } + if returns then + if res and type(res[1]) ~= "nil" then + print(table.unpack(res)) + elseif res and type(res[2]) ~= "nil" then + print("nil", table.unpack(res)) + end + end + end + local result, reason = xpcall(runCommand, function(errMsg) + return errMsg .. "\n\n" .. debug.traceback() + end) + if not result then + print("\27[91m" .. reason .. "\27[0m") + end + end +end diff --git a/src/halyde/apps/maindrv.lua b/src/halyde/apps/maindrv.lua new file mode 100644 index 0000000..fb06969 --- /dev/null +++ b/src/halyde/apps/maindrv.lua @@ -0,0 +1,10 @@ +-- WTF: What the fuck is this for?? Why does it print the computer library??? +-- TODO: Integrate this into lsdrv. +local computer = require("computer") + +if type(computer)~="table" then + return print("\x1b[91mComputer library returned '"..type(computer).."' type\x1b[0m") +end + +local address = computer.getBootAddress() +print(address) diff --git a/src/halyde/apps/mkdir.lua b/src/halyde/apps/mkdir.lua new file mode 100644 index 0000000..ead5f03 --- /dev/null +++ b/src/halyde/apps/mkdir.lua @@ -0,0 +1,23 @@ +local fs = require("filesystem") +local shell = require("shell") + +local args = {...} + +if not args[1] then + return shell.run("help mkdir") +end + +for _, directory in pairs(args) do + directory = shell.resolvePath(directory) + + if fs.exists(directory) then + terminal.write("\27[91mError: " .. directory ..": An object already exists\27[0m\n") + goto continue + end + local what, err = fs.makeDirectory(directory) + if err ~= nil then + terminal.write("\27[91mError: " .. err .. "\27[0m\n") + goto continue + end + ::continue:: +end diff --git a/src/halyde/apps/mv.lua b/src/halyde/apps/mv.lua new file mode 100644 index 0000000..b6f4ca5 --- /dev/null +++ b/src/halyde/apps/mv.lua @@ -0,0 +1,70 @@ +local fs = require("filesystem") +local shell = require("shell") + +local args = {...} + +if not args[1] then + return shell.run("help mv") +end + +if not args[2] then + terminal.write("\27[91mError: No destination\27[0m\n") + return +end + +local dest = shell.resolvePath(args[#args]) + +if fs.isFile(dest) then + if #args ~= 2 then + terminal.write("\27[91mError: Destination is not a directory\27[0m\n") + return + end + local src = shell.resolvePath(args[1]) + if not fs.exists(src) then + terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n") + return + end + if fs.isDirectory(src) then + terminal.write("\27[91mError: Cannot write directory " .. src .. " to file " .. dest .. "\27[0m\n") + return + end + fs.rename(src, dest) +elseif fs.isDirectory(dest) then + for i = 1, #args - 1 do + local src = shell.resolvePath(args[i]) + if src == dest then + terminal.write("\27[91mError: Source and destination are the same\27[0m\n") + goto continue + end + + if not fs.exists(src) then + terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n") + goto continue + end + + fs.rename(src, fs.concat(dest, fs.basename(src))) + ::continue:: + end +elseif not fs.exists(dest) then + if #args ~= 2 then + terminal.write("\27[91mError: " .. dest .. ": No such file or directory\27[0m\n") + return + end + local src = shell.resolvePath(args[1]) + if not fs.exists(src) then + terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n") + return + end + local destp = fs.parent(dest) + if not fs.exists(destp) then + terminal.write("\27[91mError: " .. destp .. ": No such file or directory\27[0m\n") + return + end + if not fs.isDirectory(destp) then + terminal.write("\27[91mError: " .. destp .. ": Not a directory\27[0m\n") + return + end + fs.rename(src, dest) +else + terminal.write("\27[91mUnknown error\27[0m\n") +end diff --git a/src/halyde/apps/profile.lua b/src/halyde/apps/profile.lua new file mode 100644 index 0000000..8976b5a --- /dev/null +++ b/src/halyde/apps/profile.lua @@ -0,0 +1,222 @@ +-- TODO: make it somehow not depend on ESC s and ESC u +local profiler = require("profiler") +local PAL = { + { fg = "\27[31m", bg = "\27[41m" }, + { fg = "\27[32m", bg = "\27[42m" }, + { fg = "\27[33m", bg = "\27[43m" }, + { fg = "\27[34m", bg = "\27[44m" }, + { fg = "\27[35m", bg = "\27[45m" }, + { fg = "\27[36m", bg = "\27[46m" }, + { fg = "\27[37m", bg = "\27[47m" } +} +local function pal(i) return PAL[((i - 1) % #PAL) + 1] end + +local results = profiler.results() +if not results then + print("No profiling data") + return +end + +local radius +local W = terminal.getResolution() +if (W == 160) then radius = 20 +elseif (W == 80) then radius = 6 +else radius = 0; PAL = {{fg = "", bg = ""}} end + +local MIN_PC = math.min(3, 100 / (radius * 2)) + +local total = 0 +for _, r in ipairs(results) do total = total + r.time end + +local main = {} +local other = {} +local ot = 0 +local on_ = 0 +for _, r in ipairs(results) do + if r.time / total * 100 >= MIN_PC then + table.insert(main, r) + else + table.insert(other, r) + ot = ot + r.time; + on_ = on_ + 1 + end +end +if on_ then + table.insert(main, { label = "other (" .. on_ .. ")", time = ot }) +end + +local START = -math.pi / 2 +local slices = {} +local cur = START +for i, m in ipairs(main) do + local sw = m.time / total * 2 * math.pi + slices[i] = { + label = m.label, + time = m.time, + pc = ("%.1f%%"):format(m.time / total * 100), + color = pal(i), + sa = cur, + sw = sw + } + cur = cur + sw +end +if on_ then slices[#slices].color={fg = "\27[30m", bg = "\27[40m"} end + +local ASP = 2 +local cx = radius * ASP +local cy = radius + +local function slice_at(sx, sy) + local dy = sy - cy + local dx = (sx - cx) / ASP + if dx * dx + dy * dy > radius * radius then return nil end + local a = math.atan2(dy, dx) + if a < START then a = a + 2 * math.pi end + for i, sl in ipairs(slices) do + if a >= sl.sa and a < sl.sa + sl.sw then return i end + end + return #slices +end + +local SUB = { + { dx = -0.25, dy = -0.375 }, + { dx = -0.25, dy = -0.125 }, + { dx = -0.25, dy = 0.125 }, + { dx = 0.25, dy = -0.375 }, + { dx = 0.25, dy = -0.125 }, + { dx = 0.25, dy = 0.125 }, + { dx = -0.25, dy = 0.375 }, + { dx = 0.25, dy = 0.375 }, +} + +for y = 0, radius * 2 do + local lx, rx = math.ceil(cx - radius * ASP), math.floor(cx + radius * ASP) + + terminal.write(("\27[%dC"):format(lx)) + for x = lx, rx do + local c = {} + local n = 0 + for k = 1, 8 do + local s = slice_at(x + SUB[k].dx, y + SUB[k].dy) + c[k] = s + if s then n = n + 1 end + end + + if n == 0 then + terminal.write("\27[0m ") + else + local counts = {} + local order = {} + for k = 1, 8 do + if c[k] then + local key = c[k] + if not counts[key] then + counts[key] = 0 + table.insert(order, key) + end + counts[key] = counts[key] + 1 + end + end + table.sort(order, function(a, b) return counts[a] > counts[b] end) + local dom = order[1] + local sub = order[2] + + if n == 8 then + terminal.write(slices[dom].color.bg) + if sub == nil then + terminal.write(" ") + else + local mask = 0 + for k = 1, 8 do + if c[k] == sub then mask = mask + (1 << (k - 1)) end + end + terminal.write(slices[sub].color.fg) + terminal.write(utf8.char(0x2800 + mask)) + end + else + local mask = 0 + for k = 1, 8 do + if c[k] == dom then mask = mask + (1 << (k - 1)) end + end + terminal.write("\27[0m") + terminal.write(slices[dom].color.fg) + terminal.write(utf8.char(0x2800 + mask)) + end + end + end + terminal.write('\n') +end + +terminal.write("\27[" .. radius * 2 + 1 .. "A\27[s") +local function draw_text(col, row, s) + terminal.write("\27[u\27[" .. col .. "C\27[" .. row - 1 .. "B" .. s) +end + +terminal.write("\27[0m") +if radius == 0 then goto tier1gpu end +for _, sl in ipairs(slices) do + local y = math.floor(cy + radius * 0.62 * math.sin(sl.sa + sl.sw / 2) + 0.5) + + local function in_slice(x, row) + local a = math.atan2(row - cy, (x - cx) / ASP) + if a < START then + a = a + 2 * math.pi + end + return a >= sl.sa and a < sl.sa + sl.sw + end + local function centered_draw(row, text) + local dy = row - cy + local half_w = math.sqrt(radius * radius - dy * dy) * ASP + local lx, rx = math.ceil(cx - half_w), math.floor(cx + half_w) + while lx <= rx and not in_slice(lx, row) do + lx = lx + 1 + end + while rx >= lx and not in_slice(rx, row) do + rx = rx - 1 + end + local avail = rx - lx + 1 + if avail < 3 then + return + end + local t = #text > avail and text:sub(1, avail - 3) .. "..." or text + local start_x = lx + math.floor((avail - #t) / 2) + draw_text(start_x, row, sl.color.bg .. t) + end + centered_draw(y, sl.label) + centered_draw(y + 1, sl.pc) +end +::tier1gpu:: + +local max_w = 0 +for _, sl in pairs(slices) do + max_w = math.max(#sl.label + 1, max_w) +end +for _, sl in pairs(other) do + max_w = math.max(#sl.label + 3, max_w) +end +local x = radius * 2 * ASP + 2 +if radius == 0 then x = 0 end +local cw = W - x - 1 +local suffix_w = 18 +local max_lbl = math.max(4, cw - suffix_w) +terminal.write("\27[u\27[" .. x .. "C" .. ("\27[0m%" .. max_w .. "s %9s %6s"):format("label", "time", "share")) +terminal.write("\n") +for _, sl in ipairs(slices) do + local lbl = sl.label + if #lbl > max_lbl then + lbl = sl.label:sub(1, max_lbl - 1) .. "…" + end + terminal.write("\n\27[" .. x .. "C" .. ("%s%" .. max_w .. "s\27[0m %9.4fs %6s"):format(sl.color.bg, lbl, sl.time, sl.pc)) +end +for _, sl in ipairs(other) do + local lbl = sl.label + if #lbl > max_lbl - 2 then + lbl = sl.label:sub(1, max_lbl - 3) .. "…" + end + terminal.write("\n\27[" .. x .. "C" .. ("%s%" .. max_w .. "s\27[0m %9.4fs %6s"):format(slices[#slices].color.bg, lbl, sl.time, ("%.1f%%"):format(sl.time / total * 100))) +end + +if radius * 2 + 2 > #slices + #other then + terminal.write("\27[" .. radius * 2 + 2 .. "B") +end +terminal.write("\n") diff --git a/src/halyde/apps/reboot.lua b/src/halyde/apps/reboot.lua new file mode 100644 index 0000000..af9b0cd --- /dev/null +++ b/src/halyde/apps/reboot.lua @@ -0,0 +1 @@ +require("computer").shutdown(true) diff --git a/src/halyde/apps/res.lua b/src/halyde/apps/res.lua new file mode 100644 index 0000000..61152b9 --- /dev/null +++ b/src/halyde/apps/res.lua @@ -0,0 +1,96 @@ +local component = require("component") +local gpu = component.gpu +local shell = require("shell") + +local args = {...} + +local maxX, maxY = gpu.maxResolution() +local curX, curY = gpu.getResolution() + +local function setRes() + if not(args[1] == "-x" or args[1] == "-y") then + print("\x1b[91mUnknown argument. \x1b[0mTry running \x1b[92m\"help res\"\x1b[0m") + return + end + + local lastarg = "" + local x, y + for i = 1, 3, 2 do + if args[i] == "-x" then + if lastarg ~= "x" then + x = tonumber(args[i + 1]) + lastarg = "x" + else + print("\x1b[91mValue \"x\" was set more than once. \x1b[0mTry running \x1b[92m\"help res\"\x1b[0m") + return + end + elseif args[i] == "-y" then + if lastarg ~= "y" then + y = tonumber(args[i + 1]) + lastarg = "y" + else + print("\x1b[91mValue \"y\" was set more than once. \x1b[0mTry running \x1b[92m\"help res\"\x1b[0m") + return + end + end + end + + if x then + if x > maxX then + print("\x1b[91mGPU does not support x higher than " .. maxX .. "\x1b[0m.") + return + end + end + if y then + if y > maxY then + print("\x1b[91mGPU does not support y higher than " .. maxY .. "\x1b[0m.") + return + end + end + + if x and not(y) then + gpu.setResolution(x, curY) + print("Successfully set X resolution from \x1b[93m" .. curX .. "\x1b[0m to \x1b[92m" .. x .. "\x1b[0m.") + return + elseif not(x) and y then + gpu.setResolution(curX, y) + print("Successfully set Y resolution from \x1b[93m" .. curY .. "\x1b[0m to \x1b[92m" .. y .. "\x1b[0m.") + return + else + gpu.setResolution(x, y) + print("Successfully set resolution from \x1b[93m" .. curX .. "x" .. curY .. "\x1b[0m to \x1b[92m" .. x .. "x" .. y .. "\x1b[0m.") + return + end +end + +local function getRes(val) + if val == "x" then + print("Current X resolution: \x1b[93m" .. curX .. "\x1b[0m") + print("Maximum supported X resolution: \x1b[92m" .. maxX .. "\x1b[0m") + elseif val == "y" then + print("Current Y resolution: \x1b[93m" .. curY .. "\x1b[0m") + print("Maximum supported Y resolution: \x1b[92m" .. maxY .. "\x1b[0m") + else + print("Current resolution: \x1b[93m" .. curX .. "x" .. curY .. "\x1b[0m") + print("Maximum supported resolution: \x1b[92m" .. maxX .. "x" .. maxY .. "\x1b[0m") + end +end + +if #args == 0 then + getRes() + return +end + +if not(#args == 1) then + setRes() + return +end + +local axis = args[1] +if axis == "-x" then + getRes("x") +elseif axis == "-y" then + getRes("y") +else + print("\x1b[91mUnknown argument. \x1b[0mTry running \x1b[92m\"help res\"\x1b[0m") +end diff --git a/src/halyde/apps/rm.lua b/src/halyde/apps/rm.lua new file mode 100644 index 0000000..3a81074 --- /dev/null +++ b/src/halyde/apps/rm.lua @@ -0,0 +1,17 @@ +local fs = require("filesystem") +local shell = require("shell") + +local args = {...} + +if not args[1] then + return shell.run("help rm") +end + +for _, file in pairs(args) do + file = shell.resolvePath(file) + + local result = fs.remove(file) + if result == false then + terminal.write("\27[91mError: cannot delete " .. file .. "\27[0m\n") + end +end diff --git a/src/halyde/apps/rtest.lua b/src/halyde/apps/rtest.lua new file mode 100644 index 0000000..d3d202e --- /dev/null +++ b/src/halyde/apps/rtest.lua @@ -0,0 +1,91 @@ +local raster = require("raster") + +raster.init() + +--[[for i=4,20 do + raster.set(i,i) + raster.set(i,i+4,0xFF00FF) +end]] + +--[[ for x=4,20 do + for y=4,20 do + if (x+y)%2==0 then + raster.set(x,y,0xFF00FF) + end + end +end ]] + +local event = require("event") +local x=0 +local y=0 +local vx=1 +local vy=1 +local col = 0x808080 +local i=0 + +while event.pull("key_down",0)==nil do + i = i + 1 + raster.set(x,y,col) + + x = x + vx + y = y + vy + + if x>raster.displayWidth then + x=raster.displayWidth + vx = -math.abs(vx) + col = math.random(0,0xFFFFFF) + end + if x<1 then + x=1 + vx = math.abs(vx) + col = math.random(0,0xFFFFFF) + end + if y>raster.displayHeight-6 then + y=raster.displayHeight-6 + vy = -math.abs(vy) + col = math.random(0,0xFFFFFF) + end + if y<1 then + y=1 + vy = math.abs(vy) + col = math.random(0,0xFFFFFF) + end + + if i>10 and i%15>0 then + while true do + local tries=0 + local dx,dy=math.random(1,raster.displayWidth),math.random(1,raster.displayHeight-6) + if raster.get(dx,dy)~=0 then + raster.set(dx,dy,0) + break + end + tries = tries + 1 + if tries>20 then + break + end + end + end + + if i%10==0 then + raster.update() + coroutine.yield() + end +end + +--[[ for i=0,360,4 do + local angle = i/180*math.pi + if false then + local x1,y1,x2,y2=raster.displayWidth/2,raster.displayHeight/2,raster.displayWidth/2+math.sin(angle)*80,raster.displayHeight/2+math.cos(angle)*80 + raster.fillEllipse(x1,y1,x2,y2,0xFF00FF) + raster.update() + raster.fillEllipse(x1,y1,x2,y2,0x000000) + else + local x,y,c=raster.displayWidth/2,raster.displayHeight/2,math.abs(math.sin(angle)*100) + raster.drawCircle(x,y,c,0xFF00FF) + raster.update() + raster.drawCircle(x,y,c,0x000000) + end +end ]] + +raster.free() +terminal.clear() diff --git a/src/halyde/apps/rtest3d.lua b/src/halyde/apps/rtest3d.lua new file mode 100644 index 0000000..b8a8702 --- /dev/null +++ b/src/halyde/apps/rtest3d.lua @@ -0,0 +1,141 @@ +local component = require("component") +local computer = require("computer") +local raster = require("raster") +local event = require("event") + +-- Initialize the 3D renderer for a spinning cube +-- Using the raster library for drawing + +-- Screen dimensions +local SCREEN_WIDTH, SCREEN_HEIGHT = component.invoke(component.list("gpu")(), "getResolution") +SCREEN_WIDTH, SCREEN_HEIGHT = SCREEN_WIDTH * 2, SCREEN_HEIGHT * 4 +local CENTER_X = SCREEN_WIDTH / 2 +local CENTER_Y = SCREEN_HEIGHT / 2 + +-- Cube properties +local CUBE_SIZE = 10 +local increment = 0 +local WHITE = 0xFFFFFF +local ROTATION_SPEED = 0.1 + +-- 3D cube vertices (centered at origin) +local vertices = { + {-CUBE_SIZE, -CUBE_SIZE, -CUBE_SIZE}, -- 0: left bottom back + {CUBE_SIZE, -CUBE_SIZE, -CUBE_SIZE}, -- 1: right bottom back + {CUBE_SIZE, CUBE_SIZE, -CUBE_SIZE}, -- 2: right top back + {-CUBE_SIZE, CUBE_SIZE, -CUBE_SIZE}, -- 3: left top back + {-CUBE_SIZE, -CUBE_SIZE, CUBE_SIZE}, -- 4: left bottom front + {CUBE_SIZE, -CUBE_SIZE, CUBE_SIZE}, -- 5: right bottom front + {CUBE_SIZE, CUBE_SIZE, CUBE_SIZE}, -- 6: right top front + {-CUBE_SIZE, CUBE_SIZE, CUBE_SIZE} -- 7: left top front +} + +-- Cube edges defined by vertex indices +local edges = { + {0, 1}, {1, 2}, {2, 3}, {3, 0}, -- back face + {4, 5}, {5, 6}, {6, 7}, {7, 4}, -- front face + {0, 4}, {1, 5}, {2, 6}, {3, 7} -- connecting edges +} + +-- Projection parameters +local FOV = 256 -- Field of view (distance from camera to screen) +local Z_OFFSET = 300 -- Distance from camera to cube center + +-- Initialize rotation angles +local angleX, angleY, angleZ = 0, 0, 0 + +-- Matrix multiplication function (apply rotation to a 3D point) +local function rotatePoint(x, y, z) + -- Rotation around X axis + local cosX, sinX = math.cos(angleX), math.sin(angleX) + local y1 = y * cosX - z * sinX + local z1 = y * sinX + z * cosX + + -- Rotation around Y axis + local cosY, sinY = math.cos(angleY), math.sin(angleY) + local x1 = x * cosY + z1 * sinY + local z2 = -x * sinY + z1 * cosY + + -- Rotation around Z axis + local cosZ, sinZ = math.cos(angleZ), math.sin(angleZ) + local x2 = x1 * cosZ - y1 * sinZ + local y2 = x1 * sinZ + y1 * cosZ + + return x2, y2, z2 +end + +-- Perspective projection function (3D to 2D) +local function projectPoint(x, y, z) + -- Apply perspective projection + local scale = FOV / (z + Z_OFFSET) + local x2d = x * scale + CENTER_X + local y2d = y * scale + CENTER_Y + + return x2d, y2d +end + +-- Render a single frame +local function renderFrame() + local time = computer.uptime()*20 + increment = time*0.05 -- increment + 0.05 + CUBE_SIZE = (math.sin(increment) + 1) * 25 + vertices = { + {-CUBE_SIZE, -CUBE_SIZE, -CUBE_SIZE}, -- 0: left bottom back + {CUBE_SIZE, -CUBE_SIZE, -CUBE_SIZE}, -- 1: right bottom back + {CUBE_SIZE, CUBE_SIZE, -CUBE_SIZE}, -- 2: right top back + {-CUBE_SIZE, CUBE_SIZE, -CUBE_SIZE}, -- 3: left top back + {-CUBE_SIZE, -CUBE_SIZE, CUBE_SIZE}, -- 4: left bottom front + {CUBE_SIZE, -CUBE_SIZE, CUBE_SIZE}, -- 5: right bottom front + {CUBE_SIZE, CUBE_SIZE, CUBE_SIZE}, -- 6: right top front + {-CUBE_SIZE, CUBE_SIZE, CUBE_SIZE} -- 7: left top front + } + -- Update rotation angles + raster.clear() + angleX = time * ROTATION_SPEED -- angleX + angleY = time * ROTATION_SPEED * 0.7 -- angleY + angleZ = time * ROTATION_SPEED * 0.5 -- angleZ + + -- Project all vertices + local projectedPoints = {} + for i, vertex in ipairs(vertices) do + -- Rotate the point + local x, y, z = rotatePoint(vertex[1], vertex[2], vertex[3]) + + -- Project the point to 2D + local x2d, y2d = projectPoint(x, y, z) + projectedPoints[i] = {x2d, y2d} + end + + -- Draw all edges + for _, edge in ipairs(edges) do + local p1 = projectedPoints[edge[1] + 1] -- +1 because Lua indices start at 1 + local p2 = projectedPoints[edge[2] + 1] + + -- Draw the line + raster.drawLine(p1[1], p1[2], p2[1], p2[2], WHITE) + end + + -- Render the frame + raster.update() +end + +-- Main program +function main() + -- Initialize raster engine + raster.init() + + -- Main loop (assume this is called repeatedly by the host environment) + while true do + renderFrame() + coroutine.yield() + if event.pull("key_down", 0) then + raster.free() + break + end + end + -- Return a reference to renderFrame so it can be called for animation + return renderFrame +end + +-- Start the program +return main() diff --git a/src/halyde/apps/shutdown.lua b/src/halyde/apps/shutdown.lua new file mode 100644 index 0000000..6c79ab6 --- /dev/null +++ b/src/halyde/apps/shutdown.lua @@ -0,0 +1 @@ +require("computer").shutdown() diff --git a/src/halyde/apps/touch.lua b/src/halyde/apps/touch.lua new file mode 100644 index 0000000..0040be5 --- /dev/null +++ b/src/halyde/apps/touch.lua @@ -0,0 +1,23 @@ +-- TODO: Rename this to something else (while making an alias from the original command). +-- Touch seems kind of a silly name for a command to make a file. +-- Maybe something like mkfile would be better? +local fs = require("filesystem") +local shell = require("shell") + +local args = {...} + +if not args[1] then + return shell.run("help touch") +end + +for _, file in pairs(args) do + file = shell.resolvePath(file) + + local handle, err = fs.open(file, "a") + if err ~= nil then + terminal.write("\27[91mError: " .. err .. "\27[0m\n") + goto continue + end + handle:close() + ::continue:: +end diff --git a/src/halyde/config/generate/shell.json b/src/halyde/config/generate/shell.json new file mode 100644 index 0000000..33b0ae4 --- /dev/null +++ b/src/halyde/config/generate/shell.json @@ -0,0 +1 @@ +{"prompt":"\u001b[92m%s > \u001b[0m","aliases":{"move":"mv","copy":"cp","ag":"argentum","rename":"mv","..":"cd ..","man":"help","del":"rm","delete":"rm","ren":"mv","remove":"rm","list":"ls","wget":"download","dir":"ls","ps":"lscor","poweroff":"shutdown","restart":"reboot","resolution":"res"},"splashMessages":["Made by John Haly- I mean Cerulean Blue.","Welcome! Type \"help\" to get started.","Also try KOCOS!","Welcome back, Commander. We have no idea what we're doing.","99.9% bug-free. The remaining 0.1% are features.","0 days since last error.","Everything is fine. The fire is decorative.","Please don't feed the background processes.","Also has fetch!","Anything red is no man's land. Trust me.","Machine...","Abort, Retry, Fail?","What's the deal with /argentum/store?","So cutting-edge you can't hold it in your hand.","Americans are the reason you see colors on-screen. I'm talking about ANSI escape codes, not politics.","Shoutout to Ponali!"],"path":["/halyde/apps/"],"startupMessage":"\n │\n │ %s\n │ %s\n │\n ","defaultWorkingDirectory":"/home/"} diff --git a/src/halyde/config/generate/startupapps.json b/src/halyde/config/generate/startupapps.json new file mode 100644 index 0000000..5609ad6 --- /dev/null +++ b/src/halyde/config/generate/startupapps.json @@ -0,0 +1 @@ +["/halyde/core/fullkb.lua","/halyde/core/evmgr.lua","/halyde/core/drvload.lua","/halyde/core/shell.lua"] diff --git a/src/halyde/config/oslogo.ans b/src/halyde/config/oslogo.ans new file mode 100644 index 0000000..0c92872 --- /dev/null +++ b/src/halyde/config/oslogo.ans @@ -0,0 +1,17 @@ +           @@@@@@@@@@           +      /@@@@@@@@@@@@@@@@@@/      +    @@@@@@#**********#@@@@@@    +  /@@@@%*********/(#%(**%@@@@/  + @@@@@***********@@@@@****@@@@@ +@@@@%*#@@@@#*****#@@@@#****%@@@@ +@@@@***@@@@@******@@@@@*****@@@@ +@@@#***#@@@@&@@@@@@@@@@#****#@@@ +@@@#****@@@@@@@@@@@@@@@@****#@@@ +@@@%****#@@@@@&%#(*(@@@@%***%@@@ +@@@@/****@@@@@******&@@@@/*/@@@@ +@@@@@/***(@@@@%*****(@@&%//@@@@@ +  @@@@#***&@@@@/*********#@@@@  +   @@@@@%*//***********%@@@@@   +     @@@@@@@%(****(%@@@@@@@     +        @@@@@@@@@@@@@@@@        + diff --git a/src/halyde/config/shell.json b/src/halyde/config/shell.json new file mode 100644 index 0000000..8eca52e --- /dev/null +++ b/src/halyde/config/shell.json @@ -0,0 +1 @@ +{"prompt":"\u001b[92m%s > \u001b[0m","aliases":{"move":"mv","copy":"cp","ag":"argentum","rename":"mv","..":"cd ..","man":"help","del":"rm","delete":"rm","ren":"mv","remove":"rm","list":"ls","wget":"download","dir":"ls","ps":"lstsk","poweroff":"shutdown","restart":"reboot","lsblk":"lsdrv","lscor":"lstsk"},"splashMessages":["Made by John Haly- I mean Cerulean Blue.","Welcome! Type \"help\" to get started.","Also try KOCOS!","Welcome back, Commander. We have no idea what we're doing.","99.9% bug-free. The remaining 0.1% are features.","0 days since last error.","Everything is fine. The fire is decorative.","Please don't feed the background processes.","Also has fetch!","Anything red is no man's land. Trust me.","Machine...","Abort, Retry, Fail?","What's the deal with /argentum/store?","So cutting-edge you can't hold it in your hand.","Americans are the reason you see colors on-screen. I'm talking about ANSI escape codes, not politics.","Shoutout to Ponali!","Now i% more secure!"],"path":["/halyde/apps/"],"startupMessage":"\n │\n │ %s\n │ %s\n │\n ","defaultWorkingDirectory":"/home/"} diff --git a/src/halyde/config/startupapps.json b/src/halyde/config/startupapps.json new file mode 100644 index 0000000..fb54b34 --- /dev/null +++ b/src/halyde/config/startupapps.json @@ -0,0 +1 @@ +["/halyde/scripts/login.lua"] diff --git a/src/halyde/kernel/boot.lua b/src/halyde/kernel/boot.lua new file mode 100644 index 0000000..0fd302f --- /dev/null +++ b/src/halyde/kernel/boot.lua @@ -0,0 +1,92 @@ +local loadfile, lz4 = ... +local filesystem = assert(loadfile("/lib/filesystem.lua")(loadfile)) +_G._OSVERSION = "HALYDE VERSION" -- TODO: Put this in a separate config file +_G._PUBLIC = {} +_G._PUBLIC.unicode = assert(loadfile("/lib/unicode.lua")(loadfile)) +local component = assert(loadfile("/lib/component.lua")(loadfile)) +local gpu = component.gpu +local screenAddress = component.list("screen")() + +_G.lz4 = lz4 +_G._PUBLIC.lz4 = lz4 + +gpu.bind(screenAddress) +gpu.setResolution(gpu.maxResolution()) + +local log = assert(loadfile("/lib/log.lua")(loadfile)) +_G.profiler = assert(loadfile("/lib/profiler.lua")()) + +log.kernel.info("Bound GPU to screen " .. tostring(screenAddress)) + +_G.package = { ["preloaded"] = {} } + +function _G.reqgen(load) + return function(module, ...) + local args = table.pack(...) + if package.preloaded[module] then + return package.preloaded[module] + end + local modulepath + if filesystem.exists(module) and not filesystem.isDirectory(module) then + modulepath = module + elseif + filesystem.exists("/lib/" .. module .. ".lua") and not filesystem.isDirectory("/lib/" .. module .. ".lua") + then + modulepath = "/lib/" .. module .. ".lua" + elseif + shell + and shell.workingDirectory + and filesystem.exists(filesystem.concat(shell.workingDirectory, module .. ".lua")) + and not filesystem.isDirectory(filesystem.concat(shell.workingDirectory, module .. ".lua")) + then + modulepath = shell.workingDirectory .. module .. ".lua" + end + assert(modulepath, "Module not found\nPossible locations:\n/lib/" .. module .. ".lua") -- FIXME: When providing an absolute path, this spits out some weird stuff. + local handle, data, tmpdata = filesystem.open(modulepath), "", nil + repeat + tmpdata = handle:read(math.huge or math.maxinteger) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + return (assert(load(lz4(data), "=" .. modulepath))(table.unpack(args))) + end +end + +_G.require = reqgen(_G.load) +log.kernel.info("Generated userland require function") + +function _G.package.preload(module) + local handle, data, tmpdata = assert(filesystem.open("/lib/" .. module .. ".lua", "r")), "", nil + repeat + tmpdata = handle:read(math.huge or math.maxinteger) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + package.preloaded[module] = assert(load(lz4(data), "=" .. module))() + _G[module] = nil + log.kernel.info(string.format("Pre-loaded /lib/%s.lua", module)) +end + +require("/halyde/kernel/datatools.lua") -- If this is not imported BEFORE modload gets run, modload requires filesystem which requires computer which requires datatools. TODO: When VFS is implemented, make the pre-VFS loading of filesystem load a more basic version. And remove this. +log.kernel.info("Loading modules") +require("/halyde/kernel/modload.lua") + +local toPreload = { "component", "computer", "log", "event" } +for _, p in pairs(toPreload) do + profiler.profile("pre-loading " .. p, package.preload, p) +end + +local computer = require("computer") +function wait(seconds) + local oldTime = computer.uptime() + while computer.uptime() < oldTime + seconds do + coroutine.yield() + end +end + +if not filesystem.exists("/halyde/config/startupapps.json") then + filesystem.copy("/halyde/config/generate/startupapps.json", "/halyde/config/startupapps.json") +end + +log.kernel.info("Starting tsched") +require("/halyde/kernel/tsched.lua") diff --git a/src/halyde/kernel/datatools.lua b/src/halyde/kernel/datatools.lua new file mode 100644 index 0000000..0f57d9e --- /dev/null +++ b/src/halyde/kernel/datatools.lua @@ -0,0 +1,46 @@ +local conversionTables = { + ["bytes"] = { + ["B"] = 1, + ["KB"] = 1000, + ["MB"] = 1000000, + ["GB"] = 1000000000 + }, ["bibytes"] = { + ["B"] = 1, + ["KiB"] = 1024, + ["MiB"] = 1048576, + ["GiB"] = 1073741824 + } +} + +function table.find(tab, item) + checkArg(1,tab,"table") + for k, v in pairs(tab) do + if v == item then + return k + end + end +end + +function table.copy(orig) + local orig_type = type(orig) + local copy + if orig_type == 'table' then + copy = {} + for orig_key, orig_value in next, orig, nil do + copy[table.copy(orig_key)] = table.copy(orig_value) + end + setmetatable(copy, table.copy(getmetatable(orig))) + else -- number, string, boolean, etc + copy = orig + end + return copy +end + +function convert(amount, fromUnit, toUnit) + for _, convTable in pairs(conversionTables) do + if convTable[toUnit] then + return amount / convTable[toUnit] * convTable[fromUnit] + end + end + return false, "unit does not exist" +end diff --git a/src/halyde/kernel/modload.lua b/src/halyde/kernel/modload.lua new file mode 100644 index 0000000..4d5f569 --- /dev/null +++ b/src/halyde/kernel/modload.lua @@ -0,0 +1,192 @@ +local log = require("log") +local fs = require("filesystem") + +local modulePath = "/halyde/kernel/modules" + +if not (fs.exists(modulePath) or fs.isDirectory(modulePath)) then + return log.kernel.warn( + string.format("Module directory (%s) does not exist and/or has been detected as a file - skipping", modulePath) + ) +end +local moduleList, err = fs.list(modulePath) +if not moduleList then + return log.kernel.warn( + string.format("Could not get list of modules (from %s): %s", modulePath, tostring(err or "unknown error")) + ) +end +local modules = {} +local moduleTypes = {} +local modulesLoaded = {} + +local function loadModule(modName) + local stop = profiler.start("loadModule(" .. tostring(modName) .. ")") + if table.find(modulesLoaded, modName) then + log.kernel.warn(string.format("[modload: %s] Module was already loaded - skipping", modName)) + stop() + return true + end + + local moduleData = modules[modName] + if not moduleData then + log.kernel.warn(string.format("[modload: %s] Could not find module data.", modName)) + table.remove(moduleList, table.find(moduleList, modName)) + stop() + return true + end + local ready = false + local status, err = xpcall(function() + ready = moduleData.check() + end, debug.traceback) + if not status then + ready = false + log.kernel.error( + string.format("[modload: %s] Could not check if module was ready: %s", modName, tostring(err or "unknown error")) + ) + end + if not ready then + log.kernel.info(string.format("[modload: %s] Module not ready - skipping", modName)) + stop() + return false + end + if type(moduleData.dependencies) == "table" then + for _, dependency in pairs(moduleData.dependencies) do + if table.find(moduleList, dependency) then + loadModule(dependency) + elseif table.find(moduleList, dependency .. ".lua") then + loadModule(dependency .. ".lua") + else + for typeLookupDrvName, typeLookupDrvType in pairs(moduleTypes) do + if typeLookupDrvType == dependency then + loadModule(typeLookupDrvName) + -- Don't break, because there can be multiple modules of the correct type + end + end + end + end + end + --print(modName) + log.kernel.info(string.format("[modload: %s] Loading module", modName)) + if moduleData.init then -- I have no idea why this would not exist, but it's a failsafe + local status, err = xpcall(function() + moduleData.init() + end, debug.traceback) + if not status then + log.kernel.error( + string.format( + "[modload: %s] An error has occured while initiating this module: %s", + modName, + tostring(err or "unknown error") + ) + ) + stop() + return false + else + table.insert(modulesLoaded, modName) + table.remove(moduleList, table.find(moduleList, modName)) + end + end + stop() + return true +end + +for _, modName in pairs(moduleList) do -- Get all the module types + local stop = profiler.start("getting module " .. modName .. ")") + log.kernel.info(string.format("[modload: %s] Getting data from module", modName)) + local moduleData + local status, err = pcall(function() + moduleData = require(fs.concat(modulePath, modName)) + end) + if not status then + log.kernel.error( + string.format( + "[modload: %s] Module returned error while getting data: %s", + modName, + tostring(err or "unknown error") + ) + ) + goto continue + end + if type(moduleData) ~= "table" then + log.kernel.error( + string.format("[modload: %s] Module returned invalid type (%s) - skipping", modName, type(moduleData)) + ) + goto continue + end + if type(moduleData.check) ~= "function" then + log.kernel.error(string.format('[modload: %s] Module doesn\'t contain a "check" function', modName)) + goto continue + end + if type(moduleData.init) ~= "function" then + log.kernel.error(string.format('[modload: %s] Module doesn\'t contain an "init" function', modName)) + goto continue + end + if type(moduleData.exit) ~= "function" then + log.kernel.error(string.format('[modload: %s] Module doesn\'t contain an "exit" function', modName)) + goto continue + end + modules[modName] = moduleData + if moduleData.type then + --print(moduleData.type) + moduleTypes[modName] = moduleData.type -- Not the other way around because there can be multiple modules of the same type, but there can't be multiple entries with the same key + end + ::continue:: + stop() +end + +local function loadAllModules() -- attempt at loading all modules, unless if they're not ready + local notReadyModules = {} + while moduleList[1] do + if moduleList[1]:sub(-1, -1) ~= "/" then -- Check if it's not a directory. If it is, it might be module config + local ready = loadModule(moduleList[1]) + if not ready then + table.insert(notReadyModules, table.remove(moduleList, 1)) + end + end + end + moduleList = notReadyModules + -- log.kernel.info("debug: modload finished attempting loading modules. remaining: " .. table.concat(moduleList, ",")) +end + +loadAllModules() + +local function checkModules() + log.kernel.info("[modload] Updating module availability.") + loadAllModules() -- load modules that haven't returned true before + -- exit modules that haven't returned false before (check if this is right first) + for _, v in pairs(table.copy(modulesLoaded)) do + local ready = false + local status, err = xpcall(function() + ready = modules[v].check() + end, debug.traceback) + if not status then + ready = false + log.kernel.error( + string.format( + "[modload: %s] Could not check if module was ready: %s", + modName, + tostring(err or "unknown error") + ) + ) + end + if not ready then + log.kernel.info(string.format("[modload: %s] Module is no longer ready: exiting", v)) + local status, err = xpcall(function() + modules[v].exit() + end, debug.traceback) + if not status then + log.kernel.error(string.format("[modload: %s] Could not exit module: %s", v, tostring(err or "unknown error"))) + end + table.insert(moduleList, table.remove(modulesLoaded, table.find(modulesLoaded, v))) + end + end +end + +if _PUBLIC.tsched then + _PUBLIC.tsched.addTask(function() + local event = require("event") + while true do + event.pull("component_added", "component_removed") -- wait until a component gets added or removed + checkModules() + end + end, "modload") +end diff --git a/src/halyde/kernel/modules/defenv.lua b/src/halyde/kernel/modules/defenv.lua new file mode 100644 index 0000000..854f3ef --- /dev/null +++ b/src/halyde/kernel/modules/defenv.lua @@ -0,0 +1,53 @@ +local module = {} + +module.dependencies = { "terminal" } + +function module.check() + return true -- This module should always be loaded +end + +function module.init() + local publicTable = { + "print", + "_VERSION", + "_OSVERSION", + "assert", + "error", + "getmetatable", + "ipairs", + "load", + "next", + "pairs", + "pcall", + "rawequal", + "rawget", + "rawlen", + "rawset", + "select", + "setmetatable", + "tonumber", + "tostring", + "type", + "xpcall", + "bit32", + "coroutine", + "debug", + "math", + "os", + "string", + "table", + "checkArg", + "utf8", + "convert", + "wait" + } + for _, value in ipairs(publicTable) do + _G._PUBLIC[value] = table.copy(_G[value]) + end +end + +function module.exit() + _G._PUBLIC = nil +end + +return module diff --git a/src/halyde/kernel/modules/evmgr.lua b/src/halyde/kernel/modules/evmgr.lua new file mode 100644 index 0000000..e3d03b3 --- /dev/null +++ b/src/halyde/kernel/modules/evmgr.lua @@ -0,0 +1,90 @@ +local module = {} + +module.dependencies = { "tsched", "keyboard" } + +function module.check() + return true +end + +local process + +function module.init() + _G.evmgr = {} + _G.evmgr.eventQueue = { kernel = {} } + local maxEventQueueLength = 10 -- increase if events start getting dropped + + local computer = require("computer") + + local ctrlDown = false + local altDown = false + local shiftDown = false + + function _G._PUBLIC.keyboard.getCtrlDown() + return ctrlDown + end + function _G._PUBLIC.keyboard.getAltDown() + return altDown + end + function _G._PUBLIC.keyboard.getShiftDown() + return shiftDown + end + + _, process = _PUBLIC.tsched.addTask(function() + while true do + -- check for events + local args + repeat + args = { computer.uptime(), computer.pullSignal(0) } + if args and args[2] then + for pid in pairs(evmgr.eventQueue) do + table.insert(evmgr.eventQueue[pid], args) + end + if _PUBLIC.keyboard then + if args[2] == "key_down" then + local keycode = args[5] + local key = _PUBLIC.keyboard.keys[keycode] + if key == "lcontrol" then + ctrlDown = true + elseif key == "lmenu" then + altDown = true + elseif key == "lshift" then + shiftDown = true + elseif key == "c" and ctrlDown and altDown then + if print then + print("\n\27[91mCoroutine " .. tostring(#tsched.tasks) .. " killed.") + end + table.remove(tsched.tasks, #tsched.tasks) + end + elseif args[2] == "key_up" then + local keycode = args[5] + local key = _PUBLIC.keyboard.keys[keycode] + if key == "lcontrol" then + ctrlDown = false + elseif key == "lmenu" then + altDown = false + elseif key == "lshift" then + shiftDown = true + end + end + end + for pid in pairs(evmgr.eventQueue) do + while #evmgr.eventQueue[pid] > maxEventQueueLength do + --ocelot.log("Queue length breach, removing first signal") + table.remove(evmgr.eventQueue[pid], 1) + end + end + end + until not args or not args[1] + -- TODO: check for processes that have ended + -- run other tasks + coroutine.yield() + end + end, "evmgr") +end + +function module.exit() + _G.evmgr = nil + _PUBLIC.tsched.removeTask(process.id) +end + +return module diff --git a/src/halyde/kernel/modules/ipc.lua b/src/halyde/kernel/modules/ipc.lua new file mode 100644 index 0000000..49c0999 --- /dev/null +++ b/src/halyde/kernel/modules/ipc.lua @@ -0,0 +1,219 @@ +local module = {} +module.dependencies = { "tsched" } + +function module.check() + return true -- IPC should always be loaded +end + +local checkProcess +function module.init() + _G.ipc = {} + _G.ipc.shared = {} + _PUBLIC.ipc = {} + + function _PUBLIC.ipc.shareWithAll() + local shareTable = {} + setmetatable(shareTable, { + ["__newindex"] = function(_, key, value) + local currentPID = _PUBLIC.tsched.getCurrentTask().id + if not _G.ipc.shared[currentPID] then + _G.ipc.shared[currentPID] = {} + end + local globalTable + for _, tab in pairs(_G.ipc.shared[currentPID]) do + if tab.sharedWith == "all" then + globalTable = tab + end + end + if not globalTable then + globalTable = { ["sharedWith"] = "all" } + table.insert(_G.ipc.shared[currentPID], globalTable) + end + if not globalTable.vars then + globalTable.vars = {} + end + globalTable.vars[key] = value + end, + ["__index"] = function(_, key) + local currentPID = _PUBLIC.tsched.getCurrentTask().id + if not _G.ipc.shared[currentPID] then + return nil + end + local globalTable + for _, tab in pairs(_G.ipc.shared[currentPID]) do + if tab.sharedWith == "all" then + globalTable = tab + end + end + if not globalTable then + return nil + end + if not globalTable.vars then + return nil + end + return globalTable.vars[key] + end, + ["__pairs"] = function() + if not _G.ipc.shared[currentPID] then + return pairs({}) + end + local globalTable + for _, tab in pairs(_G.ipc.shared[currentPID]) do + if tab.sharedWith == pid then + globalTable = tab + end + end + if not globalTable then + return pairs({}) + end + if not globalTable.vars then + return pairs({}) + end + + return pairs(table.copy(globalTable.vars)) + end, + }) + return shareTable + end + + function _PUBLIC.ipc.shareWith(pid) + checkArg(1, pid, "number") + local shareTable = {} + setmetatable(shareTable, { + ["__newindex"] = function(_, key, value) + local currentPID = _PUBLIC.tsched.getCurrentTask().id + if not _G.ipc.shared[currentPID] then + _G.ipc.shared[currentPID] = {} + end + local globalTable + for _, tab in pairs(_G.ipc.shared[currentPID]) do + if tab.sharedWith == "all" then + globalTable = tab + end + end + if not globalTable then + globalTable = { ["sharedWith"] = pid } + table.insert(_G.ipc.shared[currentPID], globalTable) + end + if not globalTable.vars then + globalTable.vars = {} + end + globalTable.vars[key] = value + end, + ["__index"] = function(_, key) + print(_G.ipc.shared) + + local currentPID = _PUBLIC.tsched.getCurrentTask().id + if not _G.ipc.shared[currentPID] then + return nil + end + local globalTable + for _, tab in pairs(_G.ipc.shared[currentPID]) do + if tab.sharedWith == pid then + globalTable = tab + end + end + if not globalTable then + return nil + end + if not globalTable.vars then + return nil + end + return globalTable.vars[key] + end, + ["__pairs"] = function() + if not _G.ipc.shared[currentPID] then + return pairs({}) + end + local globalTable + for _, tab in pairs(_G.ipc.shared[currentPID]) do + if tab.sharedWith == pid then + globalTable = tab + end + end + if not globalTable then + return pairs({}) + end + if not globalTable.vars then + return pairs({}) + end + + return pairs(table.copy(globalTable.vars)) + end, + }) + + -- check if the reverse is also available + --[[ if not _G.ipc.shared[pid] then + _G.ipc.shared[pid]={} + end + for _, tab in pairs(_G.ipc.shared[pid]) do + if tab.sharedWith == currentPID then + return -- it's already added + end + end + local reverseTable = {} + reverseTable.vars = globalTable.vars + reverseTable.sharedWith = currentPID + table.insert(_G.ipc.shared[pid],reverseTable) ]] + + return shareTable + end + + _PUBLIC.ipc.shared = {} + setmetatable(_PUBLIC.ipc.shared, { + ["__index"] = function(_, pid) + local currentPID = _PUBLIC.tsched.getCurrentTask().id + local returnTable = {} + for _, shareTable in pairs(ipc.shared[pid] or {}) do + if shareTable.sharedWith == currentPID then + for key, value in pairs(shareTable.vars) do + returnTable[key] = table.copy(value) + end + elseif shareTable.sharedWith == "all" then + for key, value in pairs(shareTable.vars) do + if not returnTable[key] then + returnTable[key] = table.copy(value) + end + end + end + end + return returnTable + end, + ["__pairs"] = function() + local ftbl = {} + for i in pairs(_G.ipc.shared) do + ftbl[i] = _PUBLIC.ipc.shared[i] + end + return pairs(ftbl) + end, + }) + + _, checkProcess = _PUBLIC.tsched.addTask(function() + while true do + -- get all PIDs that exists + local tasks = _PUBLIC.tsched.getTasks() + local pids = {} + for _, v in pairs(tasks) do + table.insert(pids, v.id) + end + + -- get all shares from unexistant processes and delete them + for i in pairs(_G.ipc.shared) do + if not table.find(pids, i) then + _G.ipc.shared[i] = nil + end + end + + -- let the other processes run + coroutine.yield() + end + end, "ipc") +end + +function module.exit() + _G.ipc = nil + _PUBLIC.ipc = nil + _PUBLIC.tsched.removeTask(checkProcess.id) +end + +return module diff --git a/src/halyde/kernel/modules/keyboard.lua b/src/halyde/kernel/modules/keyboard.lua new file mode 100644 index 0000000..b34c7d7 --- /dev/null +++ b/src/halyde/kernel/modules/keyboard.lua @@ -0,0 +1,216 @@ +local module = {} + +function module.check() + return true -- This module should always be loaded +end + +function module.init() + _G._PUBLIC.keyboard = {["keys"] = {}} + + _PUBLIC.keyboard.keys["1"] = 0x02 + _PUBLIC.keyboard.keys["2"] = 0x03 + _PUBLIC.keyboard.keys["3"] = 0x04 + _PUBLIC.keyboard.keys["4"] = 0x05 + _PUBLIC.keyboard.keys["5"] = 0x06 + _PUBLIC.keyboard.keys["6"] = 0x07 + _PUBLIC.keyboard.keys["7"] = 0x08 + _PUBLIC.keyboard.keys["8"] = 0x09 + _PUBLIC.keyboard.keys["9"] = 0x0A + _PUBLIC.keyboard.keys["0"] = 0x0B + _PUBLIC.keyboard.keys.a = 0x1E + _PUBLIC.keyboard.keys.b = 0x30 + _PUBLIC.keyboard.keys.c = 0x2E + _PUBLIC.keyboard.keys.d = 0x20 + _PUBLIC.keyboard.keys.e = 0x12 + _PUBLIC.keyboard.keys.f = 0x21 + _PUBLIC.keyboard.keys.g = 0x22 + _PUBLIC.keyboard.keys.h = 0x23 + _PUBLIC.keyboard.keys.i = 0x17 + _PUBLIC.keyboard.keys.j = 0x24 + _PUBLIC.keyboard.keys.k = 0x25 + _PUBLIC.keyboard.keys.l = 0x26 + _PUBLIC.keyboard.keys.m = 0x32 + _PUBLIC.keyboard.keys.n = 0x31 + _PUBLIC.keyboard.keys.o = 0x18 + _PUBLIC.keyboard.keys.p = 0x19 + _PUBLIC.keyboard.keys.q = 0x10 + _PUBLIC.keyboard.keys.r = 0x13 + _PUBLIC.keyboard.keys.s = 0x1F + _PUBLIC.keyboard.keys.t = 0x14 + _PUBLIC.keyboard.keys.u = 0x16 + _PUBLIC.keyboard.keys.v = 0x2F + _PUBLIC.keyboard.keys.w = 0x11 + _PUBLIC.keyboard.keys.x = 0x2D + _PUBLIC.keyboard.keys.y = 0x15 + _PUBLIC.keyboard.keys.z = 0x2C + + _PUBLIC.keyboard.keys.apostrophe = 0x28 + _PUBLIC.keyboard.keys.at = 0x91 + _PUBLIC.keyboard.keys.back = 0x0E -- backspace + _PUBLIC.keyboard.keys.backslash = 0x2B + _PUBLIC.keyboard.keys.capital = 0x3A -- capslock + _PUBLIC.keyboard.keys.colon = 0x92 + _PUBLIC.keyboard.keys.comma = 0x33 + _PUBLIC.keyboard.keys.enter = 0x1C + _PUBLIC.keyboard.keys.equals = 0x0D + _PUBLIC.keyboard.keys.grave = 0x29 -- accent grave + _PUBLIC.keyboard.keys.lbracket = 0x1A + _PUBLIC.keyboard.keys.lcontrol = 0x1D + _PUBLIC.keyboard.keys.lmenu = 0x38 -- left Alt + _PUBLIC.keyboard.keys.lshift = 0x2A + _PUBLIC.keyboard.keys.minus = 0x0C + _PUBLIC.keyboard.keys.numlock = 0x45 + _PUBLIC.keyboard.keys.pause = 0xC5 + _PUBLIC.keyboard.keys.period = 0x34 + _PUBLIC.keyboard.keys.rbracket = 0x1B + _PUBLIC.keyboard.keys.rcontrol = 0x9D + _PUBLIC.keyboard.keys.rmenu = 0xB8 -- right Alt + _PUBLIC.keyboard.keys.rshift = 0x36 + _PUBLIC.keyboard.keys.scroll = 0x46 -- Scroll Lock + _PUBLIC.keyboard.keys.semicolon = 0x27 + _PUBLIC.keyboard.keys.slash = 0x35 -- / on main _PUBLIC.keyboard + _PUBLIC.keyboard.keys.space = 0x39 + _PUBLIC.keyboard.keys.stop = 0x95 + _PUBLIC.keyboard.keys.tab = 0x0F + _PUBLIC.keyboard.keys.underline = 0x93 + + -- Keypad (and numpad with numlock off) + _PUBLIC.keyboard.keys.up = 0xC8 + _PUBLIC.keyboard.keys.down = 0xD0 + _PUBLIC.keyboard.keys.left = 0xCB + _PUBLIC.keyboard.keys.right = 0xCD + _PUBLIC.keyboard.keys.home = 0xC7 + _PUBLIC.keyboard.keys["end"] = 0xCF + _PUBLIC.keyboard.keys.pageUp = 0xC9 + _PUBLIC.keyboard.keys.pageDown = 0xD1 + _PUBLIC.keyboard.keys.insert = 0xD2 + _PUBLIC.keyboard.keys.delete = 0xD3 + + -- Function keys + _PUBLIC.keyboard.keys.f1 = 0x3B + _PUBLIC.keyboard.keys.f2 = 0x3C + _PUBLIC.keyboard.keys.f3 = 0x3D + _PUBLIC.keyboard.keys.f4 = 0x3E + _PUBLIC.keyboard.keys.f5 = 0x3F + _PUBLIC.keyboard.keys.f6 = 0x40 + _PUBLIC.keyboard.keys.f7 = 0x41 + _PUBLIC.keyboard.keys.f8 = 0x42 + _PUBLIC.keyboard.keys.f9 = 0x43 + _PUBLIC.keyboard.keys.f10 = 0x44 + _PUBLIC.keyboard.keys.f11 = 0x57 + _PUBLIC.keyboard.keys.f12 = 0x58 + _PUBLIC.keyboard.keys.f13 = 0x64 + _PUBLIC.keyboard.keys.f14 = 0x65 + _PUBLIC.keyboard.keys.f15 = 0x66 + _PUBLIC.keyboard.keys.f16 = 0x67 + _PUBLIC.keyboard.keys.f17 = 0x68 + _PUBLIC.keyboard.keys.f18 = 0x69 + _PUBLIC.keyboard.keys.f19 = 0x71 + + -- Japanese keyboards + _PUBLIC.keyboard.keys.kana = 0x70 + _PUBLIC.keyboard.keys.kanji = 0x94 + _PUBLIC.keyboard.keys.convert = 0x79 + _PUBLIC.keyboard.keys.noconvert = 0x7B + _PUBLIC.keyboard.keys.yen = 0x7D + _PUBLIC.keyboard.keys.circumflex = 0x90 + _PUBLIC.keyboard.keys.ax = 0x96 + + -- Numpad + _PUBLIC.keyboard.keys.numpad0 = 0x52 + _PUBLIC.keyboard.keys.numpad1 = 0x4F + _PUBLIC.keyboard.keys.numpad2 = 0x50 + _PUBLIC.keyboard.keys.numpad3 = 0x51 + _PUBLIC.keyboard.keys.numpad4 = 0x4B + _PUBLIC.keyboard.keys.numpad5 = 0x4C + _PUBLIC.keyboard.keys.numpad6 = 0x4D + _PUBLIC.keyboard.keys.numpad7 = 0x47 + _PUBLIC.keyboard.keys.numpad8 = 0x48 + _PUBLIC.keyboard.keys.numpad9 = 0x49 + _PUBLIC.keyboard.keys.numpadmul = 0x37 + _PUBLIC.keyboard.keys.numpaddiv = 0xB5 + _PUBLIC.keyboard.keys.numpadsub = 0x4A + _PUBLIC.keyboard.keys.numpadadd = 0x4E + _PUBLIC.keyboard.keys.numpaddecimal = 0x53 + _PUBLIC.keyboard.keys.numpadcomma = 0xB3 + _PUBLIC.keyboard.keys.numpadenter = 0x9C + _PUBLIC.keyboard.keys.numpadequals = 0x8D + + -- Separate list for special keys + _PUBLIC.keyboard.keys.special = {} + _PUBLIC.keyboard.keys.special.back = 0x0E -- backspace + _PUBLIC.keyboard.keys.special.capital = 0x3A -- capslock + _PUBLIC.keyboard.keys.special.enter = 0x1C + _PUBLIC.keyboard.keys.special.lcontrol = 0x1D + _PUBLIC.keyboard.keys.special.lmenu = 0x38 -- left Alt + _PUBLIC.keyboard.keys.special.lshift = 0x2A + _PUBLIC.keyboard.keys.special.rcontrol = 0x9D + _PUBLIC.keyboard.keys.special.rmenu = 0xB8 -- right Alt + _PUBLIC.keyboard.keys.special.rshift = 0x36 + _PUBLIC.keyboard.keys.special.scroll = 0x46 -- Scroll Lock + _PUBLIC.keyboard.keys.special.stop = 0x95 + + _PUBLIC.keyboard.keys.special.up = 0xC8 + _PUBLIC.keyboard.keys.special.down = 0xD0 + _PUBLIC.keyboard.keys.special.left = 0xCB + _PUBLIC.keyboard.keys.special.right = 0xCD + _PUBLIC.keyboard.keys.special.home = 0xC7 + _PUBLIC.keyboard.keys.special["end"] = 0xCF + _PUBLIC.keyboard.keys.special.pageUp = 0xC9 + _PUBLIC.keyboard.keys.special.pageDown = 0xD1 + _PUBLIC.keyboard.keys.special.insert = 0xD2 + _PUBLIC.keyboard.keys.special.delete = 0xD3 + + _PUBLIC.keyboard.keys.special.f1 = 0x3B + _PUBLIC.keyboard.keys.special.f2 = 0x3C + _PUBLIC.keyboard.keys.special.f3 = 0x3D + _PUBLIC.keyboard.keys.special.f4 = 0x3E + _PUBLIC.keyboard.keys.special.f5 = 0x3F + _PUBLIC.keyboard.keys.special.f6 = 0x40 + _PUBLIC.keyboard.keys.special.f7 = 0x41 + _PUBLIC.keyboard.keys.special.f8 = 0x42 + _PUBLIC.keyboard.keys.special.f9 = 0x43 + _PUBLIC.keyboard.keys.special.f10 = 0x44 + _PUBLIC.keyboard.keys.special.f11 = 0x57 + _PUBLIC.keyboard.keys.special.f12 = 0x58 + _PUBLIC.keyboard.keys.special.f13 = 0x64 + _PUBLIC.keyboard.keys.special.f14 = 0x65 + _PUBLIC.keyboard.keys.special.f15 = 0x66 + _PUBLIC.keyboard.keys.special.f16 = 0x67 + _PUBLIC.keyboard.keys.special.f17 = 0x68 + _PUBLIC.keyboard.keys.special.f18 = 0x69 + _PUBLIC.keyboard.keys.special.f19 = 0x71 + + _PUBLIC.keyboard.keys.special.numpadenter = 0x9C + + -- Create inverse mapping for name lookup. + setmetatable(_PUBLIC.keyboard.keys, + { + __index = function(tbl, k) + if type(k) ~= "number" then return end + for name,value in pairs(tbl) do + if value == k then + return name + end + end + end + }) + + setmetatable(_PUBLIC.keyboard.keys.special, + { + __index = function(tbl, k) + if type(k) ~= "number" then return end + for name,value in pairs(tbl) do + if value == k then + return name + end + end + end + }) +end + +function module.exit() + _G._PUBLIC.keyboard = nil +end + +return module diff --git a/src/halyde/kernel/modules/terminal.lua b/src/halyde/kernel/modules/terminal.lua new file mode 100644 index 0000000..28c6918 --- /dev/null +++ b/src/halyde/kernel/modules/terminal.lua @@ -0,0 +1,817 @@ +--[[ +TODO: +```bash +echo -e "\033[?25l" # hide +echo -e "\033[?25h" # show +``` +]] +local module = {} + +function module.check() + return true -- Usually always loaded, but maybe it would be worth it to check if the computer has a GPU or not? I'm not sure. +end + +function module.init() + local serialize = require("serialize") + local unicode = require("unicode") + local event = require("event") + + local component = require("component") + local gpu = component.gpu + _G._PUBLIC.terminal = {} + + local readHistory = {} + function _PUBLIC.terminal.getHistory(id) + checkArg(1,id,"string") + return table.copy(readHistory[id]) + end + function _PUBLIC.terminal.setHistory(id,hist) + checkArg(1,id,"string") + checkArg(2,hist,"table") + for i=1,#hist do + hist[i]=tostring(hist[i]) + end + readHistory[id]=hist + end + function _PUBLIC.terminal.addToHistory(id,hist) + checkArg(1,id,"string") + checkArg(2,hist,"string") + table.insert(readHistory[id],hist) + end + + local function getColorPalette(depth) + if depth == 1 then + return { + ["dark"] = { + [0] = 0x000000, + [1] = 0xffffff, + [2] = 0xffffff, + [3] = 0xffffff, + [4] = 0xffffff, + [5] = 0xffffff, + [6] = 0xffffff, + [7] = 0xffffff, + }, + ["bright"] = { + [0] = 0x000000, + [1] = 0xffffff, + [2] = 0xffffff, + [3] = 0xffffff, + [4] = 0xffffff, + [5] = 0xffffff, + [6] = 0xffffff, + [7] = 0xffffff, + } + } + end + if depth == 4 then + return { + -- Closest colors to the 4 bit OC pallete + -- Better than outright failure + ["dark"] = { + [0] = 0x000000, -- black + [1] = 0x663300, -- brown (dark red) + [2] = 0x336600, -- green (dark green) + [3] = 0x336600, -- green (dark yellow) + [4] = 0x333399, -- blue (dark blue) + [5] = 0x9933CC, -- purple (dark purple) + [6] = 0x333399, -- blue (dark cyan) + [7] = 0xCCCCCC -- silver (dark white) + }, + ["bright"] = { + [0] = 0x333333, -- gray (bright black) + [1] = 0xff3333, -- red + [2] = 0x33cc33, -- lime (green) + [3] = 0xffff33, -- yellow + [4] = 0x333399, -- blue + [5] = 0xcc66cc, -- magenta (purple) + [6] = 0x336699, -- cyan + [7] = 0xffffff -- white + } + } + end + if depth == 8 then + return { + ["dark"] = { + [0] = 0x0f0f0f, -- black + [1] = 0xcc2424, -- dark red + [2] = 0x339280, -- dark green + [3] = 0x996d00, -- dark yellow + [4] = 0x004980, -- dark blue + [5] = 0x9949c0, -- dark purple + [6] = 0x33b6c0, -- dark cyan + [7] = 0xc3c3c3 -- dark white + }, + ["bright"] = { + [0] = 0x666d80, -- brighter black + [1] = 0xff6d40, -- red + [2] = 0x33db80, -- green + [3] = 0xffb600, -- yellow + [4] = 0x336dff, -- blue + [5] = 0xcc6dc0, -- purple + [6] = 0x33dbc0, -- cyan + [7] = 0xffffff -- white + } + } + end + --[[ Original color palette: + { + ["dark"] = { + [0] = 0x171421, + [1] = 0xc01c28, + [2] = 0x26a269, + [3] = 0xa2734c, + [4] = 0x12488b, + [5] = 0xa347ba, + [6] = 0x2aa1b3, + [7] = 0xd0cfcc + }, + ["bright"] = { + [0] = 0x5e5c64, + [1] = 0xf66151, + [2] = 0x33d17a, + [3] = 0xe9ad0c, + [4] = 0x2a7bde, + [5] = 0xc061cb, + [6] = 0x33c7de, + [7] = 0xffffff + } + } + ]] + -- Shouldn't reach here + error() + end + + local ANSIColorPalette = getColorPalette(gpu.maxDepth()) + + local cursor = { x = 1, y = 1, X = nil, Y = nil } -- X and Y are managed by ESC s and ESC u + local printState = 0 -- 0:none 1:in ESC 2:in CSI + local color = { + FG = ANSIColorPalette["bright"][7], BG = ANSIColorPalette["dark"][0], + fg = nil, bg = nil, reverse = false + } + color.fg = color.FG + color.bg = color.BG + local current_codepoint = 0 + local bytes_remaining = 0 + local seq = {} + + local writeBuf = {} + + local function update_gpu_colors() + if color.reverse then + gpu.setForeground(color.bg) + gpu.setBackground(color.fg) + else + gpu.setForeground(color.fg) + gpu.setBackground(color.bg) + end + end + + local width, height = gpu.getResolution() + + function _G._PUBLIC.terminal.getResolution() + return width, height + end + + gpu.setForeground(color.fg) + gpu.setBackground(color.bg) + + local function scroll() + if gpu.copy(1, 2, width, height - 1, 0, -1) then + gpu.setForeground(color.FG) + gpu.setBackground(color.BG) + gpu.fill(1, height, width, 1, " ") + gpu.setForeground(color.fg) + gpu.setBackground(color.bg) + cursor.y = height + end + end + + local function check_wrap_and_scroll() + if cursor.x > width then + cursor.x = 1 + cursor.y = cursor.y + 1 + end + + while cursor.y > height do + scroll() + end + end + + local function exec_csi() + local params = {} + local op = 0 + local current_num = 0 + local have_num = false + + for i = 1, #seq do + local byte = seq[i] + + if 0x30 <= byte and byte <= 0x39 then + current_num = current_num * 10 + (byte - 0x30) + have_num = true + elseif byte == 0x3b then + table.insert(params, have_num and current_num or 0) + current_num = 0 + have_num = false + else + if have_num then + table.insert(params, current_num) + end + if 0x40 <= byte and byte <= 0x7e then + op = byte + end + break + end + end + + local function get_param(idx, default) + if idx <= #params and params[idx] ~= nil then + return params[idx] + end + return default + end + + if op == 0x48 or op == 0x66 then + local row = get_param(1, 1) + local col = get_param(2, 1) + cursor.y = row + cursor.x = col + if cursor.x < 1 then cursor.x = 1 end + if cursor.y < 1 then cursor.y = 1 end + if cursor.x > width then cursor.x = width end + if cursor.y > height then cursor.y = height end + return + end + + if op == 0x41 then + local n = get_param(1, 1) + cursor.y = cursor.y - n + if cursor.y < 1 then cursor.y = 1 end + return + end + + if op == 0x42 then + local n = get_param(1, 1) + cursor.y = cursor.y + n + if cursor.y > height then cursor.y = height end + return + end + + if op == 0x43 then + local n = get_param(1, 1) + cursor.x = cursor.x + n + if cursor.x > width then cursor.x = width end + return + end + + if op == 0x44 then + local n = get_param(1, 1) + cursor.x = cursor.x - n + if cursor.x < 1 then cursor.x = 1 end + return + end + + if op == 0x47 then + local col = get_param(1, 1) + cursor.x = col + if cursor.x < 1 then cursor.x = 1 end + if cursor.x > width then cursor.x = width end + return + end + + if op == 0x4a then + local mode = get_param(1, 0) + + if mode == 0 then + update_gpu_colors() + gpu.fill(cursor.x, cursor.y, width - cursor.x + 1, height - cursor.y + 1, " ") + elseif mode == 1 then + update_gpu_colors() + gpu.fill(1, 1, cursor.x, cursor.y, " ") + elseif mode == 2 then + update_gpu_colors() + gpu.fill(1, 1, width, height, " ") + cursor.x = 1 + cursor.y = 1 + end + return + end + + if op == 0x4b then + local mode = get_param(1, 0) + + if mode == 0 then + update_gpu_colors() + gpu.fill(cursor.x, cursor.y, width - cursor.x + 1, 1, " ") + elseif mode == 1 then + update_gpu_colors() + gpu.fill(1, cursor.y, cursor.x, 1, " ") + elseif mode == 2 then + update_gpu_colors() + gpu.fill(1, cursor.y, width, 1, " ") + end + return + end + + if op == 0x60 then + local col = get_param(1, 1) + cursor.x = col + if cursor.x < 1 then cursor.x = 1 end + if cursor.x > width then cursor.x = width end + return + end + + if op == 0x64 then + local row = get_param(1, 1) + cursor.y = row + if cursor.y < 1 then cursor.y = 1 end + if cursor.y > height then cursor.y = height end + return + end + + if op == 0x6d then + local j = 1 + local function parse_extended_color() + local mode = get_param(j + 1, -1) + if mode == 5 then + local idx = get_param(j + 2, 0) + j = j + 2 + if idx < 8 then + return ANSIColorPalette["dark"][idx] + elseif idx < 16 then + return ANSIColorPalette["bright"][idx - 8] + elseif idx < 232 then + local i = idx - 16 + local b = (i % 6) * 51 + local g = ((i // 6) % 6) * 51 + local r = (i // 36) * 51 + return (r << 16) | (g << 8) | b + else + local v = (idx - 232) * 10 + 8 + return (v << 16) | (v << 8) | v + end + elseif mode == 2 then + local r = get_param(j + 2, 0) + local g = get_param(j + 3, 0) + local b = get_param(j + 4, 0) + j = j + 4 + return (r << 16) | (g << 8) | b + end + return nil + end + + if #params == 0 then + color.reverse = false + color.fg = color.FG + color.bg = color.BG + update_gpu_colors() + return + end + + while j <= #params do + local p = params[j] or 0 + + if p == 0 then + color.reverse = false + color.fg = color.FG + color.bg = color.BG + elseif p == 1 then + elseif p == 2 then + elseif p == 3 then + elseif p == 4 then + elseif p == 5 or p == 6 then + elseif p == 7 then + color.reverse = true + elseif p == 8 then + color.fg = color.bg + elseif p == 9 then + elseif p == 21 then + elseif p == 22 then + elseif p == 23 then + elseif p == 24 then + elseif p == 25 then + elseif p == 27 then + color.reverse = false + elseif p == 28 then + color.fg = color.FG + elseif p == 29 then + elseif 30 <= p and p <= 37 then + color.fg = ANSIColorPalette["dark"][p - 30] + elseif p == 38 then + local c = parse_extended_color() + if c then color.fg = c end + elseif p == 39 then + color.fg = color.FG + elseif 40 <= p and p <= 47 then + color.bg = ANSIColorPalette["dark"][p - 40] + elseif p == 48 then + local c = parse_extended_color() + if c then color.bg = c end + elseif p == 49 then + color.bg = color.BG + elseif p == 58 then + parse_extended_color() + elseif p == 59 then + elseif 90 <= p and p <= 97 then + color.fg = ANSIColorPalette["bright"][p - 90] + elseif 100 <= p and p <= 107 then + color.bg = ANSIColorPalette["bright"][p - 100] + end + + j = j + 1 + end + update_gpu_colors() + return + end + + if op == 0x73 then + cursor.X = cursor.x + cursor.Y = cursor.y + return + end + + if op == 0x75 then + if cursor.X and cursor.Y then + cursor.x = cursor.X + cursor.y = cursor.Y + if cursor.x < 1 then cursor.x = 1 end + if cursor.y < 1 then cursor.y = 1 end + if cursor.x > width then cursor.x = width end + if cursor.y > height then cursor.y = height end + end + return + end + end + + function _G._PUBLIC.terminal.writec(byte) + if byte == 0x1b then + _PUBLIC.terminal.flush() + printState = 1 + seq = {} + return + end + + if printState == 1 then + if byte == 0x5b then + printState = 2 + else + printState = 0 + end + return + end + + if printState == 2 then + table.insert(seq, byte) + if 0x40 <= byte and byte <= 0x7e then + exec_csi() + printState = 0 + seq = {} + end + return + end + + if byte == 0xa then + _PUBLIC.terminal.flush() + cursor.y = cursor.y + 1 + cursor.x = 1 + check_wrap_and_scroll() + return + end + + if byte == 0xd then + _PUBLIC.terminal.flush() + cursor.x = 1 + return + end + + if byte == 0x8 then + _PUBLIC.terminal.flush() + if cursor.x > 1 then + cursor.x = cursor.x - 1 + end + return + end + + if byte == 0x9 then + _PUBLIC.terminal.flush() + cursor.x = ((cursor.x - 1) // 8) * 8 + 9 + if cursor.x < 1 then cursor.x = 1 end + if cursor.x > width then cursor.x = width end + return + end + + if byte >= 0x20 and byte <= 0x7F then + table.insert(writeBuf, string.char(byte)) + cursor.x = cursor.x + 1 + check_wrap_and_scroll() + if cursor.y ~= writeBufY or #writeBuf >= 32 then + _PUBLIC.terminal.flush() + end + elseif byte >= 0xC2 and byte <= 0xDF then + current_codepoint = (byte & 0x1F) + bytes_remaining = 1 + elseif byte >= 0xE0 and byte <= 0xEF then + current_codepoint = (byte & 0x0F) + bytes_remaining = 2 + elseif byte >= 0xF0 and byte <= 0xF7 then + current_codepoint = (byte & 0x07) + bytes_remaining = 3 + elseif byte >= 0x80 and byte <= 0xBF and bytes_remaining > 0 then + current_codepoint = (current_codepoint << 6) | (byte & 0x3F) + bytes_remaining = bytes_remaining - 1 + if bytes_remaining == 0 then + table.insert(writeBuf, utf8.char(current_codepoint)) + cursor.x = cursor.x + 1 + check_wrap_and_scroll() + if cursor.y ~= writeBufY or #writeBuf >= 32 then + _PUBLIC.terminal.flush() + end + current_codepoint = 0 + end + else + current_codepoint = 0 + bytes_remaining = 0 + end + end + + function _G._PUBLIC.terminal.write(text) + text = tostring(text) + for i = 1, #text do + _PUBLIC.terminal.writec(string.byte(text, i)) + end + end + + function _G._PUBLIC.terminal.clear() + update_gpu_colors() + gpu.fill(1, 1, width, height, " ") + writeBuf = {} + cursor.x = 1 + cursor.y = 1 + end + + function _G._PUBLIC.terminal.flush() + if #writeBuf == 0 then return end + update_gpu_colors() + gpu.set(cursor.x - #writeBuf, cursor.y, table.concat(writeBuf)) + writeBuf = {} + end + + function _G.print(...) + local args = {...} + local stringArgs = {} + for _, arg in pairs(args) do + if type(arg) == "table" then + table.insert(stringArgs, serialize(arg)) + elseif tostring(arg) then + table.insert(stringArgs, tostring(arg)) + end + end + _PUBLIC.terminal.write(table.concat(stringArgs, "\t") .. "\n") + end + + function _G._PUBLIC.terminal.read(options) + checkArg(1, options, "table", "nil") + local function checkOption(name, value, neededType) + assert(not value or type(value) == neededType, ("%s option must be %s, %s provided"):format(name, neededType, type(value))) + end + if not options then + options = {} + end + checkOption("readHistoryType", options.readHistoryType, "string") + checkOption("prefix", options.prefix, "string") + checkOption("maxChars", options.maxChars, "number") + checkOption("defaultText", options.defaultText, "string") + checkOption("censor", options.censor, "string") + + options.maxChars = options.maxChars or math.huge + + local text = options.defaultText or "" + + _G._PUBLIC.terminal.flush() + + local historyIdx + if options.readHistoryType then + if not readHistory[options.readHistoryType] then + readHistory[options.readHistoryType] = {text} + elseif readHistory[options.readHistoryType][#readHistory[options.readHistoryType] ] ~= text then + table.insert(readHistory[options.readHistoryType], text) + end + historyIdx = #readHistory[options.readHistoryType] + end + + local function updateHistory() + if not options.readHistoryType then return end + if historyIdx ~= #readHistory[options.readHistoryType] then return end + readHistory[options.readHistoryType][historyIdx]=text + end + + local cur = unicode.len(text)+1 + if options.prefix then _PUBLIC.terminal.write(options.prefix) end + local startX, startY = cursor.x, cursor.y + local fg, bg = gpu.getForeground(), gpu.getBackground() + local cursorBlink = true + local function checkScroll(y) + for i=1,y-height do + scrollDown() + startY=startY-1 + end + return math.min(y,height) + end + local function set(index, character, invertedColors) -- HACK: Currently, this will uncensor all spaces in the inputted text. + if character==nil or character=="" then return end + if options.censor then + character = character:gsub("[^ ]", options.censor) + end + if invertedColors then + gpu.setForeground(bg) + gpu.setBackground(fg) + else + gpu.setForeground(fg) + gpu.setBackground(bg) + end + index=startX+index-1 + local setX, setY = (index-1)%width+1, startY+((index-1)//width+1)-1 + setY = checkScroll(setY) + gpu.set(setX,setY,unicode.sub(character,1,width-setX+1)) + for i=1,math.ceil((#character+setX-1)/width)+1 do + gpu.set(1,setY+i,unicode.sub(character,2-setX+i*width,width+i*width-setX)) + setY = checkScroll(setY) + end + end + local function strDef(a,b) + if #a==0 then return b end + return a + end + local function curPos(cur) + return unicode.wlen(unicode.sub(text,1,cur-1))+1 + end + local function add(chr) + if type(chr)~="string" or #chr==0 then return end + if unicode.len(text)>=options.maxChars then return end + if options.maxChars1 then + text=unicode.sub(text,1,cur-2)..unicode.sub(text,cur) + cur=cur-1 + set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true) + cursorBlink = true + set(curPos(cur)+1,unicode.sub(text,cur+1).." ",false) + updateHistory() + elseif key=="delete" then + text = unicode.sub(text,1,cur-1)..unicode.sub(text,cur+1) + set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true) + cursorBlink = true + if cur<=unicode.len(text) then + set(curPos(cur+1),unicode.sub(text,cur+1).." ",false) + end + updateHistory() + elseif key=="enter" then + set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),false) + break + elseif not (args[3]<32 or (args[3]>0x7F and args[3]<=0x9F)) then + add(unicode.char(args[3]) or " ") + updateHistory() + end + elseif args and args[1]=="clipboard" then + local clip = args[3] + if not args[3] then goto continue end + while isLine(unicode.sub(clip,1,1)) do clip=unicode.sub(clip,2) end + while isLine(unicode.sub(clip,-1)) do clip=unicode.sub(clip,1,-2) end + add(clip) + updateHistory() + else + cursorBlink=not cursorBlink + set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),cursorBlink) + end + ::continue:: + end + + if options.readHistoryType then + if readHistory[options.readHistoryType][#readHistory[options.readHistoryType]]=="" then + table.remove(readHistory[options.readHistoryType],#readHistory[options.readHistoryType]) + end + if historyIdx<#readHistory[options.readHistoryType] then + -- table.remove(readHistory[options.readHistoryType],historyIdx) + table.insert(readHistory[options.readHistoryType],text) + end + while #readHistory[options.readHistoryType] > 50 do + table.remove(readHistory[options.readHistoryType], 1) + end + end + + cursor.x=1 + cursor.y=cursor.y+math.ceil((unicode.wlen(text)+startX-1)/width) + if cursor.y>height then scroll() end + + return text + end +end + +function module.exit() + _G._PUBLIC.terminal = nil +end + +return module diff --git a/src/halyde/kernel/modules/tsched.lua b/src/halyde/kernel/modules/tsched.lua new file mode 100644 index 0000000..2ad493f --- /dev/null +++ b/src/halyde/kernel/modules/tsched.lua @@ -0,0 +1,126 @@ +local module = {} + +module.dependencies = {} + +function module.check() + return true +end + +function module.init() + _G._PUBLIC.tsched = {} + _G.tsched = {} + _G.tsched.tasks = {} + + local component = require("component") + local filesystem = require("filesystem") + local gpu = component.gpu + local log = require("log") + + tsched.idCounter = 1 + + function _G._PUBLIC.tsched.runAsTask(path, ...) + checkArg(1, path, "string") + local args = { ... } + local function taskFunction() + local result, errorMessage = xpcall(function(...) + local args = table.pack(...) + if not filesystem.exists(path) then + error("No such file: " .. path) + end + local handle, data, tmpdata = filesystem.open(path), "", nil + repeat + tmpdata = handle:read(math.huge or math.maxinteger) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + + -- Userland environment definition + local userland = table.copy(_PUBLIC) + userland._G = userland + userland.load = function(chunk, chunkname, mode, env) + if not env or env == _G then + env = userland + end -- if they SOMEHOW get the kernel environment they're not running jack shit + return load(chunk, chunkname, mode, env) + end + userland.require = reqgen(userland.load) + + assert(load(lz4(data), "=" .. path, "t", userland))(table.unpack(args)) + end, function(errorMessage) + return errorMessage .. "\n \n" .. debug.traceback() + end, --[[ path,]] table.unpack(args)) + if not result then + if print then + gpu.freeAllBuffers() + print("\n\27[91m" .. errorMessage) + else + error(errorMessage) + end + end + --require(path, table.unpack(args)) + end + local _, taskInfo = _PUBLIC.tsched.addTask(taskFunction, string.match(tostring(path), "([^/]+)%.lua$")) + taskInfo.path = path + taskInfo.args = table.copy(args) + end + + function _G._PUBLIC.tsched.addTask(func, name) + checkArg(1, func, "function") + checkArg(2, name, "string") + local task = coroutine.create(func) + local taskInfo = { ["task"] = task, ["name"] = name, ["id"] = tsched.idCounter } + if type(tsched.currentTask) == "table" and type(tsched.currentTask.id) == "number" then + taskInfo.parent = tsched.currentTask.id + taskInfo.user = tsched.currentTask.user + end + table.insert(tsched.tasks, taskInfo) + tsched.idCounter = tsched.idCounter + 1 + if taskInfo.parent then + log.kernel.info( + ("[tsched] Created task %s (PID %d) by parent PID %d as UID %d"):format( + name, + tsched.idCounter - 1, + taskInfo.parent, + taskInfo.user + ) + ) + else + taskInfo.user = 1 -- It's probably being run from kernel level + log.kernel.info( + string.format("[tsched] Created task %s (PID %d) as UID 1 (no parent found)", name, tsched.idCounter - 1) + ) + end + return task, taskInfo + end + + function _G._PUBLIC.tsched.removeTask(id) + checkArg(1, id, "number") + -- TODO: Check for user permissions before running + for index, task in pairs(tsched.tasks) do + if task.id == id then + table.remove(tsched.tasks, index) + log.kernel.info(string.format("[tsched] Removed task with PID %d", id)) + return true + end + end + log.kernel.warn(string.format("[tsched] Tried to remove task that doesn't exist - PID %d", id)) + return false + end + + function _G._PUBLIC.tsched.getCurrentTask() + return table.copy(tsched.currentTask) + end + + function _G._PUBLIC.tsched.getTasks() + return table.copy(tsched.tasks) + end +end + +function module.exit() + -- why would you even want to + _G._PUBLIC.tsched = nil + _G.tsched = nil + _G.tsched.tasks = nil +end + +return module diff --git a/src/halyde/kernel/modules/user.lua b/src/halyde/kernel/modules/user.lua new file mode 100644 index 0000000..2c7df33 --- /dev/null +++ b/src/halyde/kernel/modules/user.lua @@ -0,0 +1,75 @@ +local module = {} + +function module.check() + return true -- The user system is kind of essential all the time... +end + +function module.init() + local fs = require("filesystem") + local md5 = require("md5") + local json = require("json") + local log = require("log") + + _PUBLIC.user = {} + + function _PUBLIC.user.addTask(func, name, userId, userPassword) + checkArg(1, func, "function") + checkArg(2, name, "string") + checkArg(3, userId, "number") + checkArg(4, userPassword, "string") + + local handle, data, tmpdata = fs.open("/halyde/kernel/userreg.json"), "", nil + repeat + tmpdata = handle:read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + + local userRegistry = json.decode(data) + + if not userRegistry[userId] then + return false, "No such UID." + end + + local salt = md5.sumhexa(userRegistry[userId].name) -- A little bit of salt and pepper + local passwordHash = md5.sumhexa(userPassword .. salt) + if passwordHash ~= userRegistry[userId].hash then + wait(3) -- Something to hopefully shove away brute forcers + return false, "Password incorrect." + end + + local task = coroutine.create(func) + local taskInfo = { ["task"] = task, ["name"] = name, ["id"] = tsched.idCounter, ["user"] = userId } + if type(tsched.currentTask) == "table" and type(tsched.currentTask.id) == "number" then + taskInfo.parent = tsched.currentTask.id + else + log.kernel.info("debug: tsched.currentTask is " .. require("serialize")(tsched.currentTask)) + end + table.insert(tsched.tasks, taskInfo) + if taskInfo.parent then + log.kernel.info( + ("[tsched (user)] Created task %s (PID %d) by parent PID %d as UID %d"):format( + name, + #_PUBLIC.tsched.getTasks(), + taskInfo.parent, + taskInfo.user + ) + ) + log.kernel.info( + string.format( + "[tsched (user)] Created task %s (PID %d) as UID %d (no parent found)", + name, + #_PUBLIC.tsched.getTasks(), + taskInfo.user + ) + ) + end + tsched.idCounter = tsched.idCounter + 1 + return task, taskInfo + end +end + +function module.exit() -- Ok bro + _PUBLIC.user = nil +end + +return module diff --git a/src/halyde/kernel/tsched.lua b/src/halyde/kernel/tsched.lua new file mode 100644 index 0000000..bd39ca8 --- /dev/null +++ b/src/halyde/kernel/tsched.lua @@ -0,0 +1,72 @@ +local lz4 = ... +local computer = require("computer") +local filesystem = require("filesystem") +local json = require("json") +local log = require("log") + +function handleError(errormsg) + local traceback = debug.traceback() + if errormsg == nil then -- TODO: Replace with proper error handling + print("\27[91munknown error" .. "\n \n" .. traceback) + log.kernel.error(string.format("[tsched] Process ID %d has crashed!\n\n%s", tsched.currentTask.id, traceback)) + else + print("\27[91m" .. tostring(errormsg) .. "\n \n" .. traceback) + log.kernel.error( + string.format( + "[tsched] Process ID %d has crashed: %s\n\n%s", + tsched.currentTask.id, + tostring(errormsg), + traceback + ) + ) + end +end + +local function runTasks() + for i = 1, #_G.tsched.tasks do + if tsched.tasks[i] then + tsched.currentTask = tsched.tasks[i] + local result, errorMessage = coroutine.resume(tsched.tasks[i].task) + if not result then + handleError(errorMessage) + end + if not tsched.tasks[i] then + log.kernel.warn("[tsched] Attempted to update a non-existent task. This is likely because it was removed.") + elseif coroutine.status(tsched.tasks[i].task) == "dead" then + _PUBLIC.tsched.removeTask(tsched.tasks[i].id) + --ocelot.log("Removed coroutine") + i = i - 1 + end + --computer.pullSignal(0) + --coroutine.yield() + end + end +end + +log.kernel.info("[tsched] Starting startup apps...") +local handle, data, tmpdata = filesystem.open("/halyde/config/startupapps.json", "r"), "", nil +repeat + tmpdata = handle:read(math.huge or math.maxinteger) + data = data .. (tmpdata or "") +until not tmpdata +handle:close() +for _, line in ipairs(json.decode(data)) do + if line ~= "" then + --[[ if _G.print then + print(line) + end ]] + _G._PUBLIC.tsched.runAsTask(line) + runTasks() + end +end +-- _G.cormgr.loadCoroutine("/halyde/core/shell.lua") + +log.setPrintLogs(false) + +while true do + runTasks() + if #_G.tsched.tasks == 0 then + log.kernel.warn("[tsched] No more tasks left! Shutting down...") + computer.shutdown() + end +end diff --git a/src/halyde/kernel/userreg.json b/src/halyde/kernel/userreg.json new file mode 100644 index 0000000..1663859 --- /dev/null +++ b/src/halyde/kernel/userreg.json @@ -0,0 +1 @@ +[{"name":"admin","hash":"c0e024d9200b5705bc4804722636378a"},{"name":"user","hash":"c040e68c4d44c8f8c48746bca89c1b21"}] diff --git a/src/halyde/scripts/login.lua b/src/halyde/scripts/login.lua new file mode 100644 index 0000000..662edec --- /dev/null +++ b/src/halyde/scripts/login.lua @@ -0,0 +1,60 @@ +local fs = require("filesystem") +local json = require("json") + +terminal.clear() + +::retry:: + +local username = terminal.read({ + prefix = "Username: " +}) + +local handle, data, tmpdata = fs.open("/halyde/kernel/userreg.json"), "", nil +repeat + tmpdata = handle:read(math.huge or math.maxinteger) + data = data .. (tmpdata or "") +until not tmpdata +handle:close() + +local userRegistry = json.decode(data) + +local foundUser, uid = false, nil +for i, user in pairs(userRegistry) do + if user.name == username then + foundUser = true + uid = i + break + end +end + +if not foundUser then + print("User does not exist.") + goto retry +end + +local password = terminal.read({ + prefix = "Password: ", + censor = "*" +}) + +local shellPath = "/halyde/scripts/shell.lua" -- TODO: Add shell selection (perhaps in a config file or user prompt?) + +local handle, data, tmpdata = fs.open(shellPath), "", nil +repeat + tmpdata = handle:read(math.huge or math.maxinteger) + data = data .. (tmpdata or "") +until not tmpdata +handle:close() + +-- Prepare userland environment +local temporaryGlobals = _G +_G = nil -- This is so copying doesn't cause an infinite loop +local userland = table.copy(temporaryGlobals) +_G = temporaryGlobals +userland._G = userland + +local result, errorMessage = user.addTask(assert(load(lz4(data), "=" .. shellPath, "t", userland)), "shell", uid, password) +if not result then + print(errorMessage) + goto retry +end diff --git a/src/halyde/scripts/shell.lua b/src/halyde/scripts/shell.lua new file mode 100644 index 0000000..5348d0a --- /dev/null +++ b/src/halyde/scripts/shell.lua @@ -0,0 +1,150 @@ +local fs = require("filesystem") +local json = require("json") +if not fs.exists("/halyde/config/shell.json") then -- Auto-generate configs + fs.copy("/halyde/config/generate/shell.json", "/halyde/config/shell.json") +end +local handle, data, tmpdata = fs.open("/halyde/config/shell.json", "r"), "", nil +repeat + tmpdata = handle:read(math.huge) + data = data .. (tmpdata or "") +until not tmpdata +handle:close() +local shellcfg = json.decode(data) +local component = require("component") +local gpu = component.gpu + +local workingDirectory = shellcfg["defaultWorkingDirectory"] +local aliases = shellcfg["aliases"] + +_G.shell = {} + +function _G.shell.getWorkingDirectory() + return workingDirectory +end + +function _G.shell.resolvePath(path) + if path:sub(1, 1) == "/" then return path end + return fs.concat(workingDirectory, path) +end + +function _G.shell.setWorkingDirectory(dir) + checkArg(1, dir, "string") + workingDirectory = dir +end + +function _G.shell.getAliases() + return table.copy(aliases) +end + +function _G.shell.addAlias(executable, aliasName) + checkArg(1, executable, "string") + checkArg(2, aliasName, "string") + aliases[aliasName] = executable +end + +function _G.shell.removeAlias(aliasName) + checkArg(1, aliasName, "string") + aliases[aliasName] = nil +end + +local function runAsTask(path, ...) + --ocelot.log("running " .. path .. " as coroutine") + tsched.runAsTask(path, ...) + local pid = tsched.getTasks()[#tsched.getTasks()].id + while true do + local foundTask = false + for _, task in pairs(tsched.getTasks()) do + if task.id == pid then + foundTask = true + break + end + end + if foundTask then + coroutine.yield() + else + break + end + end +end + +function _G.shell.run(command) + checkArg(1, command, "string") + if aliases[command:match("[^ ]+")] then + local _, cmdend = command:find("[^ ]+") + command = aliases[command:match("[^ ]+")] .. command:sub(cmdend + 1) + end + local gm, result, args, trimmedCommand = command:gmatch("[^ ]+"), nil, {}, command + while true do + result = gm() + if not result then + break + end + if result:find('"') then + local location = trimmedCommand:find('"') + local argBefore = result:sub(1, result:find('"') - 1) -- edge case where there is no space before the quote, get the argument there + if argBefore and argBefore ~= "" then + table.insert(args, argBefore) + end + trimmedCommand = trimmedCommand:sub(location + 1) + if trimmedCommand:find('"') then + table.insert(args, trimmedCommand:sub(1, trimmedCommand:find('"') - 1)) + trimmedCommand = trimmedCommand:sub(trimmedCommand:find('"') + 1) + gm = trimmedCommand:gmatch("[^ ]+") + else + print("\27[91mmalformed shell command") + return + end + else + table.insert(args, result) + end + end + -- execute the program + local PATH = table.copy(shellcfg.path) + table.insert(PATH, workingDirectory) + if not args[1] then + return + end + if fs.exists(args[1]) and not fs.isDirectory(args[1]) then + local path = args[1] + table.remove(args, 1) + runAsTask(path, table.unpack(args)) + return + end + for _, item in pairs(PATH) do + if fs.exists(item .. args[1]) and not fs.isDirectory(item .. args[1]) then + local path = fs.concat(item, args[1]) + table.remove(args, 1) + runAsTask(path, table.unpack(args)) + return + else -- try to look for it without the file extension + local files = fs.list(item) or {} + for _, file in pairs(files) do + -- previous pattern: (.+)%.[^%.]+$ + if args[1] == file:match("(.+)%.[^%.]+$") and not fs.isDirectory(item .. file) then + table.remove(args, 1) + runAsTask(item .. file, table.unpack(args)) + return + end + end + end + end + print("No such file or command: " .. args[1]) +end + +local shareTable = ipc.shareWithAll() +shareTable.shell = _G.shell + +print(shellcfg["startupMessage"]:format(_OSVERSION, shellcfg.splashMessages[math.random(1, #shellcfg.splashMessages)])) +while true do + coroutine.yield() + -- print(shell.workingDirectory .. " >") + --print(shellcfg["prompt"]:format(shell.workingDirectory),false) + -- termlib.cursorPosX = #(shell.workingDirectory .. " > ") + -- termlib.cursorPosY = termlib.cursorPosY - 1 + if workingDirectory:sub(-1, -1) ~= "/" then + workingDirectory = workingDirectory .. "/" + end + local shellCommand = terminal.read({readHistoryType = "shell", prefix = shellcfg.prompt:format(workingDirectory)}) + shell.run(shellCommand) + gpu.freeAllBuffers() +end diff --git a/src/init.lua b/src/init.lua new file mode 100644 index 0000000..8840b21 --- /dev/null +++ b/src/init.lua @@ -0,0 +1,254 @@ +local gpu = component.proxy(component.list("gpu")()) +local resX, resY = gpu.getResolution() + +local function lz4(data) + assert(type(data) == "string", "bad argument #1 to 'decompress' (string expected, got " .. type(data) .. ")") + + local out, outNext = {}, 1 + + local dataLen = #data + local pos = 1 -- 1-indexed + while pos <= dataLen do + local token = string.byte(data, pos) + pos = pos + 1 + + local literalCount = token // 16 + + if literalCount == 15 then + repeat + local lenPart = string.byte(data, pos) + pos = pos + 1 + literalCount = literalCount + lenPart + until lenPart < 0xFF + end + + -- Copy literals (if any) + for i = 0, literalCount - 1 do + out[outNext + i] = string.char(string.byte(data, pos + i)) + end + outNext = outNext + literalCount + pos = pos + literalCount + + if pos > dataLen then + break -- This was the last sequence (which has no match part) + end + + -- Match -- + local matchLength = token & 15 + + local offsetA, offsetB = string.byte(data, pos, pos + 1) + local matchOffset = offsetA + offsetB * 256 + pos = pos + 2 + + if matchLength == 15 then + repeat + local lenPart = string.byte(data, pos) + pos = pos + 1 + matchLength = matchLength + lenPart + until lenPart < 0xFF + end + + matchLength = matchLength + 4 + + for i = 0, matchLength - 1 do + out[outNext + i] = out[outNext - matchOffset + i] + end + outNext = outNext + matchLength + end + return table.concat(out) +end + +local function loadfile(file) + checkArg(1, file, "string") + local handle = component.invoke(computer.getBootAddress(), "open", file, "r") + local data = "" + repeat + local tmpdata = component.invoke(computer.getBootAddress(), "read", handle, math.huge or math.maxinteger) + data = data .. (tmpdata or "") + until not tmpdata + component.invoke(computer.getBootAddress(), "close", handle) + return assert(load(lz4(data), "=" .. file)) +end + +local function handleError(errorMessage) + return (errorMessage .. "\n \n" .. debug.traceback()) +end + +function loadthething() + local foundArchitecture = false + for _, arch in pairs(computer.getArchitectures()) do + if arch == "Lua 5.3" then + foundArchitecture = true + break + end + end + + if foundArchitecture then + local _, errorMesage = computer.setArchitecture("Lua 5.3") + if errorMessage then + error(errorMessage) + end + else + gpu.set(1, 1, "Required architecture (Lua 5.3) is not supported.") + gpu.set(1, 2, "Halting.") + while true do + computer.pullSignal() + end + end + loadfile("/halyde/kernel/boot.lua")(loadfile, lz4) +end + +gpu.setBackground(0x000000) +gpu.fill(1, 1, resX, resY, " ") + +-- Copying low-level functions in case of post-preload failure +local pullSignal = computer.pullSignal +local beep = computer.beep +local shutdown = computer.shutdown +local unicode = unicode + +local result, reason = xpcall(loadthething, handleError) +local lines = {} +if not result then + resX, resY = gpu.getResolution() -- doing it again because boot.lua changes the resolution + local log + local logSuccess, logError = false, nil + if _G.require then + logSuccess, logError = pcall(function() + log = _G.require("log") + log.kernel.error("Halyde has crashed!\n" .. tostring(reason or "unknown error")) + end) + end + reason = "A fatal error has occurred.\nHalyde cannot continue.\n \n" + .. tostring(reason or "unknown error"):gsub("\t", " ") + if not log then + reason = "WARNING: This error has occured early enough in the boot process that no log entry could be made.\n\n" + .. reason + elseif not logSuccess then + if type(logError) == "nil" then + logError = "" + else + logError = "\n │ " .. tostring(logError) + end + reason = "WARNING: An error has occured when making a log entry for this crash." .. logError .. "\n\n" .. reason + end + local bgColor + if gpu.getDepth() == 1 then + bgColor = 0x000000 + else + bgColor = 0x000080 + end + gpu.setBackground(bgColor) + gpu.fill(1, 1, resX, resY, " ") + for line in string.gmatch(reason, "([^\n]*)\n?") do + table.insert(lines, line) + end + local function render() + gpu.setForeground(0xFFFFFF) + for i = 1, #lines do + gpu.set(2, i + 1, lines[i]) + end + gpu.fill(1, resY - 1, resX, 1, "─") + gpu.fill(1, resY, resX, 1, " ") + gpu.setForeground(bgColor) + gpu.setBackground(0xFFFFFF) + gpu.set(2, resY, "🠅 🠄 🠇 🠆 âŽ") + gpu.setForeground(0xFFFFFF) + gpu.setBackground(bgColor) + gpu.set(4, resY, " / ") + gpu.set(9, resY, " / ") + gpu.set(14, resY, " / ") + gpu.set(19, resY, " Scroll ") + gpu.set(29, resY, " Reboot") + end + local function cropset(x, y, txt) + gpu.set(math.max(x, 1), y, unicode.sub(txt, math.max(2 - x, 1))) + end + local scrollX = 0 + local scrollY = 0 + local function scrollDown() + if scrollY >= #lines - resY + 2 then + return + end + gpu.copy(1, 2, resX, resY - 3, 0, -1) + gpu.fill(1, resY - 2, resX, 1, " ") + local line = lines[scrollY + resY - 2] + if type(line) == "string" then + cropset(2 - scrollX, resY - 2, line) + end + scrollY = scrollY + 1 + end + local function scrollUp() + if scrollY <= 0 then + return + end + gpu.copy(1, 1, resX, resY - 3, 0, 1) + gpu.fill(1, 1, resX, 1, " ") + local line = lines[scrollY - 1] + if type(line) == "string" then + cropset(2 - scrollX, 1, line) + end + scrollY = scrollY - 1 + end + local width = 0 + for i = 1, #lines do + width = math.max(width, unicode.len(lines[i])) + end + local function rerender() + for i = 1, #lines do + local y = i - scrollY + 1 + if y > 0 and y <= resY - 2 then + gpu.fill(1, y, resX, 1, " ") + cropset(2 - scrollX, y, lines[i]) + end + end + end + local function scrollRight() + if scrollX >= width - resX + 2 then + return + end + scrollX = scrollX + 1 + rerender() + end + local function scrollLeft() + if scrollX <= 0 then + return + end + scrollX = scrollX - 1 + rerender() + end + render() + beep(440, 0.2) + beep(465, 0.2) + beep(440, 0.2) + beep(370, 0.5) + while true do + local ev = { pullSignal() } + if ev[1] == "key_down" then + if ev[4] == 200 then + scrollUp() + end + if ev[4] == 208 then + scrollDown() + end + if ev[4] == 203 then + scrollLeft() + end + if ev[4] == 205 then + scrollRight() + end + if ev[4] == 28 then + -- Next time copy this function too doofus + -- computer.shutdown(true) + shutdown(true) + end + end + if ev[1] == "scroll" then + if ev[5] > 0 then + scrollUp() + else + scrollDown() + end + end + end +end diff --git a/src/lib/cliparse.lua b/src/lib/cliparse.lua new file mode 100644 index 0000000..44c5a49 --- /dev/null +++ b/src/lib/cliparse.lua @@ -0,0 +1,120 @@ +local cliParse = {} +local cliParseConfig + +function cliParse.config(config) + -- Check if config is valid + checkArg(1, config, "table") + for flagName, argCount in pairs(config) do + if type(flagName) ~= "string" then + error("Flag name " .. tostring(flagName) .. " must be a string") + end + if type(argCount) == "table" then -- Min and max arg count specified + if type(argCount[1]) ~= "number" then + error("Min args for flag " .. flagName .. " must be a number") + end + if argCount[1] < 0 then + error("Min args for flag " .. flagName .. " must not be lower than 0") + end + if type(argCount[2]) ~= "number" then + error("Max args for flag " .. flagName .. " must be a number") + end + if argCount[2] < 0 then + error("Max args for flag " .. flagName .. " must not be lower than 0") + end + if argCount[2] < argCount[1] then + error("Max args for flag " .. flagName .. " must be more than or equal to min args") + end + elseif type(argCount) == "number" then -- Required arg count specified + if argCount < 0 then + error("Required args for flag " .. flagName .. " must not be lower than 0") + end + else + error("Arg count for flag " .. flagName .. " must be either a table of 2 numbers or a number") + end + -- Config is all good, set it + cliParseConfig = config + end +end + +function cliParse.parse(...) + local args = { ... } + local returnTable = { ["flags"] = {}, ["args"] = {} } -- This will be filled out and returned in the end + + for i = 1, #args do -- This is used instead of pairs() so that the code can skip ahead when it finds arguments instead of flags by just incrementing i + local flagList = {} + if args[i]:sub(1, 2) == "--" then -- Long flag + flagList = { args[i]:sub(3) } + elseif args[i]:sub(1, 1) == "-" then -- Short flag(s) + for i2 = 2, #args[i] do -- i is 2 to account for the - character at the start + table.insert(flagList, args[i]:sub(i2, i2)) + end + end + for flagIndex, flag in ipairs(flagList) do -- Yes, this has to be in the argument loop for the skipahead to work. + local flagConfig = cliParseConfig[flag] + if flagConfig then -- This is a real flag + returnTable.flags[flag] = {} + if type(flagConfig) == "table" then + if flagIndex ~= #flagList then -- This flag is in a chain and it's not the last one + if flagConfig[1] ~= 0 then + return false, "Flag " .. flag .. " expects at least " .. flagConfig[1] .. " arguments, got 0" + end + else + for i2 = 1, flagConfig[1] do -- Iterate through the items AFTER the flags to find the minimum arguments + if args[i + i2]:sub(1, 1) ~= "-" then -- This checks for both long and short flags + table.insert(returnTable.flags[flag], args[i + i2]) -- Insert the argument found into the return table + else + return false, "Flag " .. flag .. " expects at least " .. flagConfig[1] .. " arguments, got " .. i2 + end + end + i = i + flagConfig[1] + local i2IncrementAmount -- See line 71 and 76 + for i2 = 1, flagConfig[2] - flagConfig[1] do -- Now search for the max args + if args[i + i2]:sub(1, 1) ~= "-" then -- This checks for both long and short flags + table.insert(returnTable.flags[flag], args[i + i2]) -- Insert the argument found into the return table + else + i2IncrementAmount = i2 - 1 + -- Since the current argument is NOT a valid one, decrement by 1 to return to the last valid one (this is important when adding i2 to i) + break + end + end + i = i + (i2IncrementAmount or flagConfig[2]) + end + else -- Flag required args are a single number + if flagIndex ~= #flagList then -- This flag is in a chain and it's not the last one + if flagConfig ~= 0 then + return false, "Flag " .. flag .. " expects " .. flagConfig .. " arguments, got 0" + end + else + for i2 = 1, flagConfig do -- Now search for the max args + if args[i + i2]:sub(1, 1) ~= "-" then -- This checks for both long and short flags + table.insert(returnTable.flags[flag], args[i + i2]) -- Insert the argument found into the return table + else + return false, "Flag " .. flag .. " expects " .. flagConfig[1] .. " arguments, got " .. i2 + end + end + i = i + flagConfig + end + end + else + return false, "Unexpected flag: " .. flag + end + end + end + + for _, arg in pairs(args) do + local foundArg = false + for _, flag in pairs(returnTable.flags) do -- A loop in a loop... Peak efficiency + if table.find(flag, arg) or arg:sub(1, 1) == "-" then -- AAAND ANOTHER LOOP?! + foundArg = true + break + end + end + if not foundArg then + table.insert(returnTable.args, arg) + end + end + + return returnTable +end + +return cliParse diff --git a/src/lib/component.lua b/src/lib/component.lua new file mode 100644 index 0000000..2a24a94 --- /dev/null +++ b/src/lib/component.lua @@ -0,0 +1,140 @@ +local computer +if require then + -- libcomponent can get loaded early enough in the boot process where require is not present + computer = require("computer") +else + computer = _G.computer +end + +local compLib +local LLcomponent +if table.copy then + compLib = table.copy(component) + LLcomponent = table.copy(component) +else + compLib = {} + LLcomponent = component +end + +-- local ocelot = LLcomponent.proxy(LLcomponent.list("ocelot")()) +-- ocelot.log("loaded") + +_G.componentlib = { ["additions"] = {}, ["removals"] = {} } +compLib.virtual = {} + +function compLib.virtual.add(address, componentType, proxy) + checkArg(1, address, "string") + checkArg(2, componentType, "string") + checkArg(3, proxy, "table") + proxy["address"] = address + local proc + if _PUBLIC.tsched then + proc = _PUBLIC.tsched.getCurrentTask() + end + componentlib.additions[address] = { ["componentType"] = componentType, ["proxy"] = proxy, ["proc"] = proc } + if componentlib.removals[address] then + componentlib.removals[address] = nil + end + computer.pushSignal("component_added", address, componentType) +end + +function compLib.virtual.remove(address) + checkArg(1, address, "string") + if componentlib.additions[address] then + computer.pushSignal("component_removed", address, componentlib.additions[address].componentType) + componentlib.additions[address] = nil + else + local componentTypeOutput = compLib.type(address) + computer.pushSignal("component_removed", address, componentTypeOutput or "unknown") + table.insert(componentlib.removals, address) + end +end + +function compLib.virtual.check(address) + checkArg(1, address, "string") + if _G.componentlib.additions[address] then + return true, _G.componentlib.additions[address].proc + else + return false + end +end + +function compLib.list(componentType) + checkArg(1, componentType, "string", "nil") + local componentList = LLcomponent.list(componentType) + for address, dataTable in pairs(componentlib.additions) do + if dataTable.componentType == componentType or not componentType then + componentList[address] = dataTable.componentType + end + end + for _, address in pairs(componentlib.removals) do + componentList[address] = nil + end + local i, value + setmetatable(componentList, { + __call = function(self) + i, value = next(self, i) + return i, value + end, + }) + return componentList +end + +function compLib.proxy(address) + if componentlib.additions[address] then + --ocelot.log("vcomponent") + return componentlib.additions[address].proxy + else + return LLcomponent.proxy(address) + end +end + +function compLib.invoke(address, funcName, ...) + --ocelot.log("Invoking " .. funcName .. " from " .. address) + if componentlib.additions[address] then + --ocelot.log("vcomponent") + if not componentlib.additions[address].proxy[funcName] then + error("no such method") + end + return componentlib.additions[address].proxy[funcName](...) + else + return LLcomponent.invoke(address, funcName, ...) + end +end + +function compLib.get(address) + checkArg(1, address, "string") + if #address < 3 then + return nil, "abbreviated address must be at least 3 characters long" + end + for currentAddress, name in compLib.list() do + if currentAddress:find("^" .. address) then + return currentAddress + end + end + return nil, "full address not found" +end + +function compLib.isAvailable(componentType) + checkArg(1, componentType, "string") + if LLcomponent.list(componentType)() then + return true + else + return false + end +end + +-- Add main component proxies to the library +setmetatable(compLib, { + ["__index"] = function(_, item) + if LLcomponent.list(item)() then + return compLib.proxy(compLib.list(item)()) + else + -- Why did I ever fucking write this?? + -- return compLib[item] + return nil + end + end, +}) + +return compLib diff --git a/src/lib/computer.lua b/src/lib/computer.lua new file mode 100644 index 0000000..14a223f --- /dev/null +++ b/src/lib/computer.lua @@ -0,0 +1,14 @@ +local computerlib = table.copy(computer) +local LLcomputer = table.copy(computer) + +function computerlib.pullSignal(timeout) + local startTime = LLcomputer.uptime() + local result + repeat + result = {LLcomputer.pullSignal(0)} + coroutine.yield() + until result or timeout and LLcomputer.uptime() >= startTime + timeout + return table.unpack(result) +end + +return computerlib diff --git a/src/lib/event.lua b/src/lib/event.lua new file mode 100644 index 0000000..81b3af6 --- /dev/null +++ b/src/lib/event.lua @@ -0,0 +1,62 @@ +local computer = require("computer") +local event = {} + +local bufferTime = 0.1 -- A little bit of buffer time so events won't be skipped by accident. + +--local ocelot = component.proxy(component.list("ocelot")()) +function event.pull(...) + local pid = _PUBLIC.tsched and _PUBLIC.tsched.getCurrentTask() and _PUBLIC.tsched.getCurrentTask().id or "kernel" + if not evmgr.eventQueue[pid] then + evmgr.eventQueue[pid] = {} + end + local eventQueue = evmgr.eventQueue[pid] + local args = { ... } + local evtypes, timeout = {}, nil + + for _, arg in pairs(args) do + if type(arg) == "number" and not timeout then -- It's a timeout + timeout = arg + else -- It's an event type + table.insert(evtypes, tostring(arg)) + end + end + + local startTime = computer.uptime() + + while true do + -- Check event queue for matching event + for i = 1, #eventQueue do + local foundevent = false + if evtypes[1] then -- event type(s) specified + for _, evtype in pairs(evtypes) do + if eventQueue[i][2] == evtype and eventQueue[i][1] >= startTime - bufferTime then + foundevent = true + end + end + else + if eventQueue[i][1] >= startTime - bufferTime then + foundevent = true + end + end + if foundevent then + -- Found matching event (or any event if no type specified) + local result = table.copy(eventQueue[i]) + table.remove(eventQueue, i) + table.remove(result, 1) -- remove the time of event argument + return table.unpack(result) + end + end + + if timeout ~= nil and computer.uptime() >= startTime + timeout then + return nil + end + + if timeout == nil then + coroutine.yield() + elseif timeout > 0 then + coroutine.yield() + end + end +end + +return event diff --git a/src/lib/filesystem.lua b/src/lib/filesystem.lua new file mode 100644 index 0000000..b5fcdcf --- /dev/null +++ b/src/lib/filesystem.lua @@ -0,0 +1,630 @@ +local loadfile = ... -- raw loadfile from boot.lua +local unicode, component, computer + +local bufferSize = math.huge or math.maxinteger + +if loadfile then + unicode = loadfile("/lib/unicode.lua")(loadfile) + component = loadfile("/lib/component.lua")(loadfile) + computer = _G.computer +elseif require then + unicode = require("unicode") + component = require("component") + computer = require("computer") +end + +local filesystem = {} + +function filesystem.canonical(path) + checkArg(1, path, "string") + local segList = {} + if path:sub(1, 1) ~= "/" then + path = "/" .. path + end + path = path:gsub("/+", "/") + for segment in path:gmatch("[^/]+") do + if segment == ".." and segList[1] then + table.remove(segList, #segList) + elseif segment ~= "." then + table.insert(segList, segment) + end + end + return "/" .. table.concat(segList, "/") +end + +function filesystem.basename(path) + checkArg(1, path, "string") + return path:match("/([^/]+)/?$") or "" +end + +function filesystem.concat(path1, path2) + checkArg(1, path1, "string") + checkArg(2, path2, "string") + if path1:sub(-1, -1) == "/" then + path1 = path1:sub(1, -2) + end + if path2:sub(1, 1) ~= "/" then + path2 = "/" .. path2 + end + return path1 .. path2 +end + +function filesystem.absolutePath(path) -- returns the address and absolute path of an object + checkArg(1, path, "string") + path = filesystem.canonical(path) + local address = nil + if path:find("^/tmp") then + address = computer.tmpAddress() + path = path:sub(5) + elseif path:find("^/mnt/...") then + address = component.get(path:sub(6, 8)) + if not address then + address = computer.getBootAddress() + else + path = path:sub(9) + end + else + address = computer.getBootAddress() + end + if not address then + return nil, "no such component" + end + return address, path +end + +function filesystem.parent(path) + checkArg(1, path, "string") + local p = filesystem.canonical(path) + -- return "/" on "/" + return p == "/" and "/" or (p:match("^(.*)/[^/]+/?$") or "/") +end + +function filesystem.exists(path) -- check if path exists + checkArg(1, path, "string") + local address, absPath = filesystem.absolutePath(path) + if not address then + return false + end + if absPath:find("^/special/drive/...") then + return not not (computer.getBootAddress() and component.get(absPath:sub(16, 18))) + end + if absPath:find("^/special/eeprom/") then + return table.find({ "init.lua", "data.bin", "label.txt" }, absPath:sub(17)) + end + return component.invoke(address, "exists", absPath) +end + +local function readBytes(self, n) + n = n or 1 + if n == 1 then + local byte = self:read(1) + if byte == nil then + return nil + end + return string.byte(byte) + end + local bytes, res = { string.byte(self:read(n), 1, n) }, 0 + if self.littleEndian then + for i = #bytes, 1, -1 do + res = (res << 8) & 0xFFFFFFFF | bytes[i] + end + else + for i = 1, #bytes do + res = (res << 8) & 0xFFFFFFFF | bytes[i] + end + end + return res +end + +local function readUnicodeChar(self) + return unicode.readChar(function() + return self:readBytes(1) + end) +end + +local function iterateBytes(self) + return function() + local byte = readBytes(self, 1) + if byte == nil then + self:close() + end + return byte + end +end + +local function iterateUnicodeChars(self) + return unicode.iterate(iterateBytes(self)) +end + +function filesystem.makeReadStream(content) + local properHandle = {} + local readCursor = 1 + function properHandle.read(self, amount) + checkArg(2, amount, "number") + local limit = string.len(content) + 1 + local out = nil + if readCursor < limit then + if amount == math.huge then + out = string.sub(content, math.min(readCursor, limit)) + else + out = string.sub(content, math.min(readCursor, limit), math.min(readCursor + amount - 1, limit)) + end + end + readCursor = readCursor + amount + if out == "" then + return nil + end + return out + end + + properHandle.readBytes = readBytes + properHandle.readUnicodeChar = readUnicodeChar + properHandle.iterateBytes = iterateBytes + properHandle.iterateUnicodeChars = iterateUnicodeChars + function properHandle.write() + return nil + end + + function properHandle.close() + content = nil + end + + return properHandle +end + +function filesystem.open(path, mode, buffered) -- opens a file and returns its handle + checkArg(1, path, "string") + checkArg(2, mode, "string", "nil") + checkArg(3, buffered, "boolean", "nil") + if not mode then + mode = "r" + end + if buffered == nil then + buffered = true + end + if not (mode == "r" or mode == "w" or mode == "rb" or mode == "wb" or mode == "a" or mode == "ab") then + return nil, "invalid handle type" + end + if path:find("^/special") and not filesystem.exists(path) then + return nil, "/special does not allow creating files" + end + local address, absPath = filesystem.absolutePath(path) + local unmanagedDrive = address == computer.getBootAddress() and absPath:find("^/special/drive") + local unmanagedProxy, sectorSize, sectorCount, handle + if unmanagedDrive then + unmanagedProxy = component.proxy(component.get(absPath:sub(16, 18))) + sectorSize = unmanagedProxy.getSectorSize() + sectorCount = math.ceil(unmanagedProxy.getCapacity() / sectorSize) + elseif not (address == computer.getBootAddress() and absPath:find("^/special/")) then + local handleArgs = { component.invoke(address, "open", absPath, mode) } + handle = handleArgs[1] + if not handle then + return table.unpack(handleArgs) + end + handleArgs = nil + end + local properHandle = {} + properHandle.handle = handle + properHandle.address = address + local content = nil + local bufferOffset = 0 -- Position in file where buffer starts + local readCursor = 1 -- Position within buffer (1-based) + + if buffered and mode == "r" then + content = component.invoke(address, "read", handle, bufferSize) or "" + bufferOffset = 0 + readCursor = 1 + end + + function properHandle.read(self, amount) + checkArg(2, amount, "number") + if unmanagedDrive then + -- TODO: Test if this still works + local sectorIdx = ((readCursor - 1) // sectorSize) + 1 + if sectorIdx > sectorCount then + return nil + end + local sector = unmanagedProxy.readSector(sectorIdx) + local data = sector:sub( + ((readCursor - 1) % sectorSize) + 1, + ((readCursor + math.min(amount, sectorSize) - 2) % sectorSize) + 1 + ) + readCursor = readCursor + #data + if data == "" then + return nil + end + return data + else + if buffered then + if amount == math.huge or amount == math.maxinteger then + -- Read everything remaining + local result = "" + + -- First, get what's left in current buffer + if content and readCursor <= #content then + result = content:sub(readCursor) + readCursor = #content + 1 + end + + -- Then read all remaining data from file + while true do + local newData = component.invoke(address, "read", handle, bufferSize) + if not newData or newData == "" then + break + end + result = result .. newData + end + + -- Update buffer state + content = nil + bufferOffset = bufferOffset + #(content or "") + + return result ~= "" and result or nil + else + local result = "" + local remaining = amount + + while remaining > 0 do + -- If we need more data or buffer is empty + if not content or readCursor > #content then + content = component.invoke(address, "read", handle, bufferSize) + if not content or content == "" then + break + end + bufferOffset = bufferOffset + (readCursor - 1) + readCursor = 1 + end + + -- Extract data from current buffer + local available = #content - readCursor + 1 + local toRead = math.min(remaining, available) + local chunk = content:sub(readCursor, readCursor + toRead - 1) + + result = result .. chunk + readCursor = readCursor + toRead + remaining = remaining - toRead + end + + return result ~= "" and result or nil + end + else + return component.invoke(self.address, "read", self.handle, amount) + end + end + end + + properHandle.readBytes = readBytes + properHandle.readUnicodeChar = readUnicodeChar + properHandle.iterateBytes = iterateBytes + properHandle.iterateUnicodeChars = iterateUnicodeChars + function properHandle.write(self, data) + checkArg(2, data, "string") + if unmanagedDrive then + local startSector = ((readCursor - 1) // sectorSize) + 1 + if startSector > sectorCount then + return nil, "not enough space" + end + local startSByte = ((readCursor - 1) % sectorSize) + 1 + local sect = unmanagedProxy.readSector(startSector) + unmanagedProxy.writeSector(startSector, sect:sub(1, startSByte - 1) .. data:sub(1, sectorSize - startSByte + 1)) + for i = 2, (#data + startSByte) // sectorSize do + if startSector + i - 1 > sectorCount then + return nil, "not enough space" + end + unmanagedProxy.writeSector( + startSector + i - 1, + data:sub(startSByte + sectorSize * (i - 1), startSByte + sectorSize * i - 1) + ) + end + readCursor = readCursor + #data + return true + else + return component.invoke(self.address, "write", self.handle, data) + end + end + + function properHandle.close(self) + if buffered then + content = nil + end + return component.invoke(self.address, "close", self.handle) + end + + if address == computer.getBootAddress() then + local eeprom + pcall(function() + eeprom = component.eeprom + end) + if eeprom then + local getFunc, setFunc + if absPath == "/special/eeprom/init.lua" then + getFunc, setFunc = "get", "set" + elseif absPath == "/special/eeprom/data.bin" then + getFunc, setFunc = "getData", "setData" + elseif absPath == "/special/eeprom/label.txt" then + getFunc, setFunc = "getLabel", "setLabel" + end + if mode:sub(1, 1) == "r" and getFunc then + local stream = filesystem.makeReadStream(eeprom[getFunc]() or "") + properHandle.read = stream.read + properHandle.close = stream.close + elseif mode:sub(1, 1) == "w" and setFunc then + local content = "" + function properHandle.write(self, data) + checkArg(2, data, "string") + content = content .. data + end + + function properHandle.close(self) + return eeprom[setFunc](content) + end + end + end + end + function properHandle.seek(self, whence, offset) + checkArg(2, whence, "string", "number", "nil") + checkArg(3, offset, "number", "nil") + if not offset then + offset = 0 + end + if type(whence) == "number" then + offset = whence + end + if not whence or type(whence) == "number" then + whence = "cur" + end + + if buffered then + local currentAbsolutePos = bufferOffset + readCursor - 1 + -- Seek real handle position to buffer handle position + component.invoke(self.address, "seek", self.handle, "set", currentAbsolutePos) + local newPos = component.invoke(self.address, "seek", self.handle, whence, math.max(offset, -currentAbsolutePos)) + content = nil + bufferOffset = newPos or 0 + readCursor = 1 + return newPos + else + return component.invoke(self.address, "seek", self.handle, whence, offset) + end + end + + return properHandle +end + +function filesystem.list(path) + checkArg(1, path, "string") + path = filesystem.canonical(path) + if path == "/mnt" then + -- list drives + local returnTable = {} + local tmpAddress = computer.tmpAddress() + for address, _ in component.list("filesystem") do + if address ~= tmpAddress then + table.insert(returnTable, address:sub(1, 3) .. "/") + end + end + return returnTable + elseif path == "/special/drive" then + local returnTable = {} + local tmpAddress = computer.tmpAddress() + for address, type in component.list("drive") do + if address ~= tmpAddress and type == "drive" then + table.insert(returnTable, address:sub(1, 3)) + end + end + return returnTable + elseif path == "/special/eeprom" then + return { "init.lua", "data.bin", "label.txt" } + else + local address, absPath = filesystem.absolutePath(path) + if not address then + return false + end + return component.invoke(address, "list", absPath) + end +end + +function filesystem.size(path) + checkArg(1, path, "string") + local address, absPath = filesystem.absolutePath(path) + if not address then + return false + end + if address == computer.getBootAddress() then + if absPath:find("^/special/drive") then + local drive = component.get(absPath:sub(16, 18)) + if not drive then + return false + end + return component.invoke(drive, "getCapacity") + elseif absPath:find("^/special/eeprom") then + local eeprom + pcall(function() + eeprom = component.eeprom + end) + if eeprom then + local getFunc + if absPath == "/special/eeprom/init.lua" then + getFunc = "get" + elseif absPath == "/special/eeprom/data.bin" then + getFunc = "getData" + elseif absPath == "/special/eeprom/label.txt" then + getFunc = "getLabel" + end + return #(eeprom[getFunc]()) + end + end + end + return component.invoke(address, "size", absPath) +end + +local function getRecursiveList(address, absPath) + local list = component.invoke(address, "list", absPath) + local dirList = {} + local listChanged = true + while listChanged do + listChanged = false + for i = 1, #list do + if component.invoke(address, "isDirectory", absPath .. "/" .. list[i]) then + listChanged = true + local dir = list[i] + if dir:sub(-1) == "/" then + dir = dir:sub(1, -2) + end + table.insert(dirList, dir) + table.remove(list, i) + local subDir = component.invoke(address, "list", absPath .. "/" .. dir) + for j = 1, #subDir do + table.insert(list, dir .. "/" .. subDir[j]) + end + end + end + end + return list, dirList +end + +local function copyContent(fromHandle, toHandle) + if not (fromHandle and toHandle) then + return + end + while true do + local tmpdata = fromHandle.read(2048) + if not tmpdata then + break + end + local status, reason = toHandle.write(tmpdata) + if status ~= true then + break + end + end + fromHandle:close() + toHandle:close() +end + +local function copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath) + if fromAbsPath:sub(-1) == "/" then + fromAbsPath = fromAbsPath:sub(1, -2) + end + if toAbsPath:sub(-1) == "/" then + toAbsPath = toAbsPath:sub(1, -2) + end + component.invoke(toAddress, "makeDirectory", toAbsPath) + local fileList, dirList = getRecursiveList(fromAddress, fromAbsPath) + for i = 1, #dirList do + component.invoke(toAddress, "makeDirectory", toAbsPath .. "/" .. dirList[i]) + end + for i = 1, #fileList do + local fromFile, toFile = fromAbsPath .. "/" .. fileList[i], toAbsPath .. "/" .. fileList[i] + local fromHandle = component.invoke(fromAddress, "open", fromFile, "r") + local toHandle = component.invoke(toAddress, "open", toFile, "w") + copyContent({ + ["read"] = function(...) + return component.invoke(fromAddress, "read", fromHandle, ...) + end, + ["close"] = function(...) + return component.invoke(fromAddress, "close", fromHandle, ...) + end, + }, { + ["write"] = function(...) + return component.invoke(toAddress, "write", toHandle, ...) + end, + ["close"] = function(...) + return component.invoke(toAddress, "close", toHandle, ...) + end, + }) + end +end + +function filesystem.isDirectory(path) + checkArg(1, path, "string") + local address, absPath = filesystem.absolutePath(path) + if not address then + return false + end + return component.invoke(address, "isDirectory", absPath) +end + +function filesystem.isFile(path) + return not filesystem.isDirectory(path) and filesystem.exists(path) +end + +function filesystem.rename(fromPath, toPath) + checkArg(1, fromPath, "string") + checkArg(2, toPath, "string") + local fromAddress, fromAbsPath = filesystem.absolutePath(fromPath) + local toAddress, toAbsPath = filesystem.absolutePath(toPath) + if not fromAddress or not toAddress then + return false + end + if fromAddress == toAddress then + return component.invoke(fromAddress, "rename", fromAbsPath, toAbsPath) + elseif filesystem.isDirectory(fromPath) then -- component.invoke(fromAddress, "isDirectory", fromAbsPath) then + copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath) + filesystem.remove(fromPath) -- component.invoke(fromAddress,"remove", fromAbsPath) + else + local handle, data, tmpdata = filesystem.open(fromPath), "", nil -- component.invoke(fromAddress, "open", fromAbsPath, "r"), "", nil + repeat + tmpdata = handle:read(math.huge or math.maxinteger) -- component.invoke(fromAddress, "read", handle, math.huge or math.maxinteger) + data = data .. (tmpdata or "") + until not tmpdata + tmpdata = handle:close() -- component.invoke(fromAddress, "close", handle) + local handle = filesystem.open(toPath) -- component.invoke(toAddress, "open", toAbsPath, "w") + handle:write(data) -- component.invoke(toAddress, "write", handle, data) + handle:close() -- component.invoke(toAddress, "close", handle) + filesystem.remove(fromPath) -- component.invoke(fromAddress, "remove", fromAbsPath) + end +end + +function filesystem.copy(fromPath, toPath) + checkArg(1, fromPath, "string") + checkArg(2, toPath, "string") + local fromAddress, fromAbsPath = filesystem.absolutePath(fromPath) + local toAddress, toAbsPath = filesystem.absolutePath(toPath) + if not fromAddress or not toAddress then + return false + end + if filesystem.isDirectory(fromPath) then -- component.invoke(fromAddress, "isDirectory", fromAbsPath) + copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath) + else + --[[ local handle = filesystem.open(fromPath,"r") + local data, tmpdata = "", nil + repeat + tmpdata = handle:read(math.huge or math.maxinteger) + data = data .. (tmpdata or "") + until not tmpdata + tmpdata = handle:close() + local handle = filesystem.open(toPath,"w") + handle:write(data) + handle:close() ]] + copyContent(filesystem.open(fromPath, "r"), filesystem.open(toPath, "w")) + end +end + +function filesystem.remove(path) + checkArg(1, path, "string") + local address, absPath = filesystem.absolutePath(path) + if not address then + return false + end + if absPath:find("^/special") then + return false + end + if absPath:find("^/tmp") then + return false + end + if absPath:find("^/mnt") then + return false + end + return component.invoke(address, "remove", absPath) +end + +function filesystem.makeDirectory(path) + checkArg(1, path, "string") + local address, absPath = filesystem.absolutePath(path) + if not address then + return false + end + return component.invoke(address, "makeDirectory", absPath) +end + +return filesystem diff --git a/src/lib/json.lua b/src/lib/json.lua new file mode 100644 index 0000000..3aa0b76 --- /dev/null +++ b/src/lib/json.lua @@ -0,0 +1,4 @@ +-- json.lua by rxi +-- Minified with luamin +-- Original: https://github.com/rxi/json.lua +local a={_version="0.1.2"}local b;local c={["\\"]="\\",["\""]="\"",["\b"]="b",["\f"]="f",["\n"]="n",["\r"]="r",["\t"]="t"}local d={["/"]="/"}for e,f in pairs(c)do d[f]=e end;local function g(h)return"\\"..(c[h]or string.format("u%04x",h:byte()))end;local function i(j)return"null"end;local function k(j,l)local m={}l=l or{}if l[j]then error("circular reference")end;l[j]=true;if rawget(j,1)~=nil or next(j)==nil then local n=0;for e in pairs(j)do if type(e)~="number"then error("invalid table: mixed or invalid key types")end;n=n+1 end;if n~=#j then error("invalid table: sparse array")end;for o,f in ipairs(j)do table.insert(m,b(f,l))end;l[j]=nil;return"["..table.concat(m,",").."]"else for e,f in pairs(j)do if type(e)~="string"then error("invalid table: mixed or invalid key types")end;table.insert(m,b(e,l)..":"..b(f,l))end;l[j]=nil;return"{"..table.concat(m,",").."}"end end;local function p(j)return'"'..j:gsub('[%z\1-\31\\"]',g)..'"'end;local function q(j)if j~=j or j<=-math.huge or j>=math.huge then error("unexpected number value '"..tostring(j).."'")end;return string.format("%.14g",j)end;local r={["nil"]=i,["table"]=k,["string"]=p,["number"]=q,["boolean"]=tostring}b=function(j,l)local s=type(j)local t=r[s]if t then return t(j,l)end;error("unexpected type '"..s.."'")end;function a.encode(j)return b(j)end;local u;local function v(...)local m={}for o=1,select("#",...)do m[select(o,...)]=true end;return m end;local w=v(" ","\t","\r","\n")local x=v(" ","\t","\r","\n","]","}",",")local y=v("\\","/",'"',"b","f","n","r","t","u")local z=v("true","false","null")local A={["true"]=true,["false"]=false,["null"]=nil}local function B(C,D,E,F)for o=D,#C do if E[C:sub(o,o)]~=F then return o end end;return#C+1 end;local function G(C,D,H)local I=1;local J=1;for o=1,D-1 do J=J+1;if C:sub(o,o)=="\n"then I=I+1;J=1 end end;error(string.format("%s at line %d col %d",H,I,J))end;local function K(n)local t=math.floor;if n<=0x7f then return string.char(n)elseif n<=0x7ff then return string.char(t(n/64)+192,n%64+128)elseif n<=0xffff then return string.char(t(n/4096)+224,t(n%4096/64)+128,n%64+128)elseif n<=0x10ffff then return string.char(t(n/262144)+240,t(n%262144/4096)+128,t(n%4096/64)+128,n%64+128)end;error(string.format("invalid unicode codepoint '%x'",n))end;local function L(M)local N=tonumber(M:sub(1,4),16)local O=tonumber(M:sub(7,10),16)if O then return K((N-0xd800)*0x400+O-0xdc00+0x10000)else return K(N)end end;local function P(C,o)local m=""local Q=o+1;local e=Q;while Q<=#C do local R=C:byte(Q)if R<32 then G(C,Q,"control character in string")elseif R==92 then m=m..C:sub(e,Q-1)Q=Q+1;local h=C:sub(Q,Q)if h=="u"then local S=C:match("^[dD][89aAbB]%x%x\\u%x%x%x%x",Q+1)or C:match("^%x%x%x%x",Q+1)or G(C,Q-1,"invalid unicode escape in string")m=m..L(S)Q=Q+#S else if not y[h]then G(C,Q-1,"invalid escape char '"..h.."' in string")end;m=m..d[h]end;e=Q+1 elseif R==34 then m=m..C:sub(e,Q-1)return m,Q+1 end;Q=Q+1 end;G(C,o,"expected closing quote for string")end;local function T(C,o)local R=B(C,o,x)local M=C:sub(o,R-1)local n=tonumber(M)if not n then G(C,o,"invalid number '"..M.."'")end;return n,R end;local function U(C,o)local R=B(C,o,x)local V=C:sub(o,R-1)if not z[V]then G(C,o,"invalid literal '"..V.."'")end;return A[V],R end;local function W(C,o)local m={}local n=1;o=o+1;while 1 do local R;o=B(C,o,w,true)if C:sub(o,o)=="]"then o=o+1;break end;R,o=u(C,o)m[n]=R;n=n+1;o=B(C,o,w,true)local X=C:sub(o,o)o=o+1;if X=="]"then break end;if X~=","then G(C,o,"expected ']' or ','")end end;return m,o end;local function Y(C,o)local m={}o=o+1;while 1 do local Z,j;o=B(C,o,w,true)if C:sub(o,o)=="}"then o=o+1;break end;if C:sub(o,o)~='"'then G(C,o,"expected string for key")end;Z,o=u(C,o)o=B(C,o,w,true)if C:sub(o,o)~=":"then G(C,o,"expected ':' after key")end;o=B(C,o+1,w,true)j,o=u(C,o)m[Z]=j;o=B(C,o,w,true)local X=C:sub(o,o)o=o+1;if X=="}"then break end;if X~=","then G(C,o,"expected '}' or ','")end end;return m,o end;local _={['"']=P,["0"]=T,["1"]=T,["2"]=T,["3"]=T,["4"]=T,["5"]=T,["6"]=T,["7"]=T,["8"]=T,["9"]=T,["-"]=T,["t"]=U,["f"]=U,["n"]=U,["["]=W,["{"]=Y}u=function(C,D)local X=C:sub(D,D)local t=_[X]if t then return t(C,D)end;G(C,D,"unexpected character '"..X.."'")end;function a.decode(C)if type(C)~="string"then error("expected argument of type string, got "..type(C))end;local m,D=u(C,B(C,1,w,true))D=B(C,D,w,true)if D<=#C then G(C,D,"trailing garbage")end;return m end;return a diff --git a/src/lib/log.lua b/src/lib/log.lua new file mode 100644 index 0000000..1105178 --- /dev/null +++ b/src/lib/log.lua @@ -0,0 +1,143 @@ +local fs, computer, gpu +local chunkSize = 1024 +if require then + fs = require("filesystem") + computer = require("computer") + gpu = require("component").gpu +else + local loadfile = ... + fs = loadfile("/lib/filesystem.lua")(loadfile) + computer = _G.computer + gpu = loadfile("/lib/component.lua")(loadfile).gpu +end + +local resX, resY = gpu.getResolution() +local log = {} +if not _G.logSettings then + _G.logSettings = { -- We have to preload the library just for this :P + ["printLogs"] = true, -- FIXME: Or do we? + ["printerY"] = 1, + } +end + +function getlines(s) + if s:sub(-1) ~= "\n" then + s = s .. "\n" + end + return s:gmatch("(.-)\n") +end + +local function writeToScreen(text) + -- Print onscreen + if text:sub(1, 4) == "INFO" then -- Set color + gpu.setForeground(0xFFFFFF) + elseif text:sub(1, 4) == "WARN" then + gpu.setForeground(0xFFFF00) + elseif text:sub(1, 5) == "ERROR" then + gpu.setForeground(0xFF0000) + end + local i = 0 + for line in getlines(text) do + line = line:gsub("\t", " ") + repeat -- Line wrapping + if _G.logSettings.printerY > resY then + gpu.copy(1, 2, resX, resY - 1, 0, -1) + _G.logSettings.printerY = resY + end + gpu.set(1, _G.logSettings.printerY, line .. string.rep(" ", resX - #line)) + line = line:sub(resX + 1) + _G.logSettings.printerY = _G.logSettings.printerY + 1 + until line == "" + end +end + +local logFileSizeLimit = 16384 + +local function writeToLog(path, text) + fs.makeDirectory("halyde/logs") -- Git likes to not clone empty directories + local handle = fs.open(path, "a") + if handle then + handle:write(text .. "\n") + handle:close() + end + + -- Log trimming if it gets too long + if fs.size(path) > logFileSizeLimit then + local sizeCounter = 0 + local readHandle = fs.open(path, "r", false) -- Making sure buffering is disabled, otherwise this whole thing is pointless + local currentChunk = "" + readHandle:seek("end", -chunkSize) + repeat + currentChunk = readHandle:read(chunkSize) + readHandle:seek(-chunkSize * 2) + sizeCounter = sizeCounter + chunkSize + until sizeCounter >= logFileSizeLimit * 0.75 + while true do + local infoEntry = currentChunk:find("INFO [", 1, true) + local warnEntry = currentChunk:find("WARN [", 1, true) + local errorEntry = currentChunk:find("ERROR [", 1, true) + if not infoEntry and not warnEntry and not errorEntry then + readHandle:seek(-chunkSize) + else + readHandle:seek( + math.min( + infoEntry or math.huge or math.maxinteger, + warnEntry or math.huge or math.maxinteger, + errorEntry or math.huge or math.maxinteger + ) - 1 + ) + break + end + if readHandle:seek("cur") == 0 then -- Failsafe to prevent infinite loops + break + end + end + local writeHandle = fs.open(path, "w") + while true do + local tmpdata = readHandle:read(math.huge or math.maxinteger) + if not tmpdata then + break + end + writeHandle:write(tmpdata) + end + readHandle:close() + writeHandle:close() + end + + if _G.logSettings.printLogs then + writeToScreen(text) + end +end + +setmetatable(log, { + ["__index"] = function(_, index) + return { + ["logpath"] = fs.concat("/halyde/logs/", index .. ".log"), + ["info"] = function(text) + writeToLog( + fs.concat("/halyde/logs/", index .. ".log"), + "INFO [" .. string.format("%.2f", computer.uptime()) .. "] " .. text + ) + end, + ["warn"] = function(text) + writeToLog( + fs.concat("/halyde/logs/", index .. ".log"), + "WARN [" .. string.format("%.2f", computer.uptime()) .. "] " .. text + ) + end, + ["error"] = function(text) + writeToLog( + fs.concat("/halyde/logs/", index .. ".log"), + "ERROR [" .. string.format("%.2f", computer.uptime()) .. "] " .. text + ) + end, + } + end, +}) + +function log.setPrintLogs(setting) -- Yes, this works with the metatable. + checkArg(1, setting, "boolean") + _G.logSettings.printLogs = setting +end + +return log diff --git a/src/lib/md5.lua b/src/lib/md5.lua new file mode 100644 index 0000000..34937ab --- /dev/null +++ b/src/lib/md5.lua @@ -0,0 +1,27 @@ +-- md5.lua by kikito +-- Minified with luamin +-- Original: https://github.com/kikito/md5.lua +local a={_VERSION="md5.lua 1.1.0",_DESCRIPTION="MD5 computation in Lua (5.1-3, LuaJIT)",_URL="https://github.com/kikito/md5.lua",_LICENSE=[[ + MIT LICENSE + + Copyright (c) 2013 Enrique García Cota + Adam Baldwin + hanzao + Equi 4 Software + + 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. +]]}local b,c,d,e,f=string.char,string.byte,string.format,string.rep,string.sub;local g,h,i,j,k,l;local m,n=pcall(require,'bit')local o,p=pcall(require,'ffi')if m then g,h,i,j,k,l=n.bor,n.band,n.bnot,n.bxor,n.rshift,n.lshift else m,n=pcall(require,'bit32')if m then i=n.bnot;local q=function(r)return r<=0x7fffffff and r or-(i(r)+1)end;local s=function(t)return function(u,v)return q(t(q(u),q(v)))end end;g,h,j=s(n.bor),s(n.band),s(n.bxor)k,l=s(n.rshift),s(n.lshift)else local function w(x)local y=0;local z=1;for A=1,#x do y=y+x[A]*z;z=z*2 end;return y end;local function B(C,D)local E,F=C,D;if#E<#F then E,F=F,E end;for A=#F+1,#E do F[A]=0 end end;local G;i=function(r)local x=G(r)local H=math.max(#x,32)for A=1,H do if x[A]==1 then x[A]=0 else x[A]=1 end end;return w(x)end;G=function(r)if r<0 then return G(i(math.abs(r))+1)end;local x={}local I=1;local J;while r>0 do J=r%2;x[I]=J;r=(r-J)/2;I=I+1 end;return x end;g=function(K,r)local L=G(K)local M=G(r)B(L,M)local x={}for A=1,#L do if L[A]==0 and M[A]==0 then x[A]=0 else x[A]=1 end end;return w(x)end;h=function(K,r)local L=G(K)local M=G(r)B(L,M)local x={}for A=1,#L do if L[A]==0 or M[A]==0 then x[A]=0 else x[A]=1 end end;return w(x)end;j=function(K,r)local L=G(K)local M=G(r)B(L,M)local x={}for A=1,#L do if L[A]~=M[A]then x[A]=1 else x[A]=0 end end;return w(x)end;k=function(r,N)local O=0;if r<0 then r=i(math.abs(r))+1;O=0x80000000 end;local P=math.floor;for A=1,N do r=r/2;r=g(P(r),O)end;return P(r)end;l=function(r,N)if r<0 then r=i(math.abs(r))+1 end;for A=1,N do r=r*2 end;return h(r,0xFFFFFFFF)end end end;local Q;if o then local R=p.typeof("int[1]")Q=function(A)return p.string(R(A),4)end else Q=function(A)local t=function(S)return b(h(k(A,S),255))end;return t(0)..t(8)..t(16)..t(24)end end;local function T(S)local U=0;for A=1,#S do U=U*256+c(S,A)end;return U end;local V;if o then local W=p.typeof("const char*")local X=p.typeof("const int*")V=function(S)local Y=W(S)return p.cast(X,Y)[0]end else V=function(S)local U=0;for A=#S,1,-1 do U=U*256+c(S,A)end;return U end end;local function Z(S)return{V(f(S,1,4)),V(f(S,5,8)),V(f(S,9,12)),V(f(S,13,16)),V(f(S,17,20)),V(f(S,21,24)),V(f(S,25,28)),V(f(S,29,32)),V(f(S,33,36)),V(f(S,37,40)),V(f(S,41,44)),V(f(S,45,48)),V(f(S,49,52)),V(f(S,53,56)),V(f(S,57,60)),V(f(S,61,64))}end;local _={0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,0x698098d8,0x8b44f7af,0xffff5bb1,0x895cd7be,0x6b901122,0xfd987193,0xa679438e,0x49b40821,0xf61e2562,0xc040b340,0x265e5a51,0xe9b6c7aa,0xd62f105d,0x02441453,0xd8a1e681,0xe7d3fbc8,0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,0xa9e3e905,0xfcefa3f8,0x676f02d9,0x8d2a4c8a,0xfffa3942,0x8771f681,0x6d9d6122,0xfde5380c,0xa4beea44,0x4bdecfa9,0xf6bb4b60,0xbebfbc70,0x289b7ec6,0xeaa127fa,0xd4ef3085,0x04881d05,0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665,0xf4292244,0x432aff97,0xab9423a7,0xfc93a039,0x655b59c3,0x8f0ccc92,0xffeff47d,0x85845dd1,0x6fa87e4f,0xfe2ce6e0,0xa3014314,0x4e0811a1,0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391,0x67452301,0xefcdab89,0x98badcfe,0x10325476}local t=function(a0,a1,a2)return g(h(a0,a1),h(-a0-1,a2))end;local a3=function(a0,a1,a2)return g(h(a0,a2),h(a1,-a2-1))end;local a4=function(a0,a1,a2)return j(a0,j(a1,a2))end;local A=function(a0,a1,a2)return j(a1,g(a0,-a2-1))end;local a2=function(a5,u,v,a6,a7,a0,S,a8)u=h(u+a5(v,a6,a7)+a0+a8,0xFFFFFFFF)return g(l(h(u,k(0xFFFFFFFF,S)),S),k(u,32-S))+v end;local function a9(aa,ab,ac,ad,ae)local u,v,a6,a7=aa,ab,ac,ad;local af=_;u=a2(t,u,v,a6,a7,ae[0],7,af[1])a7=a2(t,a7,u,v,a6,ae[1],12,af[2])a6=a2(t,a6,a7,u,v,ae[2],17,af[3])v=a2(t,v,a6,a7,u,ae[3],22,af[4])u=a2(t,u,v,a6,a7,ae[4],7,af[5])a7=a2(t,a7,u,v,a6,ae[5],12,af[6])a6=a2(t,a6,a7,u,v,ae[6],17,af[7])v=a2(t,v,a6,a7,u,ae[7],22,af[8])u=a2(t,u,v,a6,a7,ae[8],7,af[9])a7=a2(t,a7,u,v,a6,ae[9],12,af[10])a6=a2(t,a6,a7,u,v,ae[10],17,af[11])v=a2(t,v,a6,a7,u,ae[11],22,af[12])u=a2(t,u,v,a6,a7,ae[12],7,af[13])a7=a2(t,a7,u,v,a6,ae[13],12,af[14])a6=a2(t,a6,a7,u,v,ae[14],17,af[15])v=a2(t,v,a6,a7,u,ae[15],22,af[16])u=a2(a3,u,v,a6,a7,ae[1],5,af[17])a7=a2(a3,a7,u,v,a6,ae[6],9,af[18])a6=a2(a3,a6,a7,u,v,ae[11],14,af[19])v=a2(a3,v,a6,a7,u,ae[0],20,af[20])u=a2(a3,u,v,a6,a7,ae[5],5,af[21])a7=a2(a3,a7,u,v,a6,ae[10],9,af[22])a6=a2(a3,a6,a7,u,v,ae[15],14,af[23])v=a2(a3,v,a6,a7,u,ae[4],20,af[24])u=a2(a3,u,v,a6,a7,ae[9],5,af[25])a7=a2(a3,a7,u,v,a6,ae[14],9,af[26])a6=a2(a3,a6,a7,u,v,ae[3],14,af[27])v=a2(a3,v,a6,a7,u,ae[8],20,af[28])u=a2(a3,u,v,a6,a7,ae[13],5,af[29])a7=a2(a3,a7,u,v,a6,ae[2],9,af[30])a6=a2(a3,a6,a7,u,v,ae[7],14,af[31])v=a2(a3,v,a6,a7,u,ae[12],20,af[32])u=a2(a4,u,v,a6,a7,ae[5],4,af[33])a7=a2(a4,a7,u,v,a6,ae[8],11,af[34])a6=a2(a4,a6,a7,u,v,ae[11],16,af[35])v=a2(a4,v,a6,a7,u,ae[14],23,af[36])u=a2(a4,u,v,a6,a7,ae[1],4,af[37])a7=a2(a4,a7,u,v,a6,ae[4],11,af[38])a6=a2(a4,a6,a7,u,v,ae[7],16,af[39])v=a2(a4,v,a6,a7,u,ae[10],23,af[40])u=a2(a4,u,v,a6,a7,ae[13],4,af[41])a7=a2(a4,a7,u,v,a6,ae[0],11,af[42])a6=a2(a4,a6,a7,u,v,ae[3],16,af[43])v=a2(a4,v,a6,a7,u,ae[6],23,af[44])u=a2(a4,u,v,a6,a7,ae[9],4,af[45])a7=a2(a4,a7,u,v,a6,ae[12],11,af[46])a6=a2(a4,a6,a7,u,v,ae[15],16,af[47])v=a2(a4,v,a6,a7,u,ae[2],23,af[48])u=a2(A,u,v,a6,a7,ae[0],6,af[49])a7=a2(A,a7,u,v,a6,ae[7],10,af[50])a6=a2(A,a6,a7,u,v,ae[14],15,af[51])v=a2(A,v,a6,a7,u,ae[5],21,af[52])u=a2(A,u,v,a6,a7,ae[12],6,af[53])a7=a2(A,a7,u,v,a6,ae[3],10,af[54])a6=a2(A,a6,a7,u,v,ae[10],15,af[55])v=a2(A,v,a6,a7,u,ae[1],21,af[56])u=a2(A,u,v,a6,a7,ae[8],6,af[57])a7=a2(A,a7,u,v,a6,ae[15],10,af[58])a6=a2(A,a6,a7,u,v,ae[6],15,af[59])v=a2(A,v,a6,a7,u,ae[13],21,af[60])u=a2(A,u,v,a6,a7,ae[4],6,af[61])a7=a2(A,a7,u,v,a6,ae[11],10,af[62])a6=a2(A,a6,a7,u,v,ae[2],15,af[63])v=a2(A,v,a6,a7,u,ae[9],21,af[64])return h(aa+u,0xFFFFFFFF),h(ab+v,0xFFFFFFFF),h(ac+a6,0xFFFFFFFF),h(ad+a7,0xFFFFFFFF)end;local function ag(self,S)self.pos=self.pos+#S;S=self.buf..S;for ah=1,#S-63,64 do local ae=Z(f(S,ah,ah+63))assert(#ae==16)ae[0]=table.remove(ae,1)self.a,self.b,self.c,self.d=a9(self.a,self.b,self.c,self.d,ae)end;self.buf=f(S,math.floor(#S/64)*64+1,#S)return self end;local function ai(self)local aj=self.pos;local ak=56-aj%64;if aj%64>56 then ak=ak+64 end;if ak==0 then ak=64 end;local S=b(128)..e(b(0),ak-1)..Q(h(8*aj,0xFFFFFFFF))..Q(math.floor(aj/0x20000000))ag(self,S)assert(self.pos%64==0)return Q(self.a)..Q(self.b)..Q(self.c)..Q(self.d)end;function a.new()return{a=_[65],b=_[66],c=_[67],d=_[68],pos=0,buf='',update=ag,finish=ai}end;function a.tohex(S)return d("%08x%08x%08x%08x",T(f(S,1,4)),T(f(S,5,8)),T(f(S,9,12)),T(f(S,13,16)))end;function a.sum(S)return a.new():update(S):finish()end;function a.sumhexa(S)return a.tohex(a.sum(S))end;return a diff --git a/src/lib/profiler.lua b/src/lib/profiler.lua new file mode 100644 index 0000000..94e6148 --- /dev/null +++ b/src/lib/profiler.lua @@ -0,0 +1,35 @@ +local thing = _G._PUBLIC or _G +thing.__PROFILER_INSTANCE = thing.__PROFILER_INSTANCE or { timers = {} } +local timers = thing.__PROFILER_INSTANCE.timers +local profiler = {} + +function profiler.start(label, overwrite) + thing.__PROFILER_INSTANCE.lastadded = label + timers[label] = timers[label] or {} + if not timers[label].start or overwrite then + timers[label].start = os.clock() + end + return function() timers[label].time = timers[label].time or os.clock() - timers[label].start end +end + +function profiler.results() + local _now = nil + local function now() + _now = _now or os.clock() + return _now + end + local out = {} + for label, t in pairs(timers) do + table.insert(out, { label = label, time = t.time or now() - t.start }) + end + table.sort(out, function(a, b) return a.time > b.time end) + return out +end + +function profiler.profile(label, func, ...) + local stop = profiler.start(label) + func(...) + stop() +end + +return profiler diff --git a/src/lib/raster.lua b/src/lib/raster.lua new file mode 100644 index 0000000..9d18975 --- /dev/null +++ b/src/lib/raster.lua @@ -0,0 +1,432 @@ +local raster = { + ["units"]={}, + ["defaultBackgroundColor"]=0x000000, + ["defaultForegroundColor"]=0xFFFFFF, + ["displayWidth"]=0, + ["displayHeight"]=0, + ["charWidth"]=0, + ["charHeight"]=0, + ["backgroundColor"]=0xFFFFFF +} + +local component = require("component") +-- local ocelot = component.proxy(component.list("ocelot")()) +local gpu = component.gpu + +local display = {} +local chunksAffected = {} + +local renderBuffer = nil + +-- braille rendering + +function raster.units.charToBraille(x,y) + return x*2,y*4 +end + +function raster.units.brailleToChar(x,y) + return math.ceil(x/2),math.ceil(y/4) +end + +function raster.init(width, height, bgcolor) + -- NOTE: Width and height are in characters, not pixels in braille. + -- If the width and height are nil, the entire screen will be used. + if width==nil and height==nil then + width, height = gpu.getResolution() + end + + raster.charWidth = width + raster.charHeight = height + + width, height = raster.units.charToBraille(width, height) + + bgcolor = bgcolor or raster.defaultBackgroundColor + if bgcolor~=0 then + for i=1,width*height do + display[i]=bgcolor + end + end + + raster.displayWidth = width + raster.displayHeight = height + raster.backgroundColor = bgcolor + + pcall(function() + renderBuffer = gpu.allocateBuffer() + end) + + raster.clear() +end + +function raster.set(x, y, color) + if x<1 or x>raster.displayWidth or y<1 or y>raster.displayHeight then + return false + end + + color = color or raster.defaultForegroundColor + local i = x+y*raster.displayWidth + display[i] = color + + local ci = math.floor((x-1)/2)+math.floor((y-1)/4)*raster.charWidth+1 + -- ocelot.log(x..","..y..":"..ci) + chunksAffected[ci] = true + + return true +end + +function raster.get(x, y) + local i = x+y*raster.displayWidth + return display[i] or raster.backgroundColor +end + +local function stats(arr) + local out = {} + for i=1,#arr do + local v = arr[i] + if out[v]==nil then + out[v]=1 + else + out[v] = out[v] + 1 + end + end + return out +end + +local function getKeys(t) + local keys = {} + for k,v in pairs(t) do + table.insert(keys,{k,v}) + end + table.sort(keys,function(a,b) + return a[2]>b[2] + end) + for i=1,#keys do + keys[i] = keys[i][1] + end + return keys +end + +local function colorDifference(a,b) + return ((a>>16)&255)-((b>>16)&255)+((a>>8)&255)-((b>>8)&255)+(a&255)-(b&255) +end + +local function limitTwoColors(arr) + local colors = getKeys(stats(arr)) + for i=1,#arr do + local v=arr[i] + if v==colors[1] then + arr[i]=0 + goto continue + elseif v==colors[2] then + arr[i]=1 + goto continue + else + --error("Pixel is not in the two colors (raster.lua:90)") + -- get closest color so atleast it kinda shows + if colorDifference(v,colors[1]) -dy then + err = err - dy + x1 = x1 + sx + end + + if e2 < dx then + err = err + dx + y1 = y1 + sy + end + end +end + +function raster.drawRect(x1,y1,x2,y2,col) + x1, y1, x2, y2 = math.floor(x1), math.floor(y1), math.floor(x2), math.floor(y2) + if x1 > x2 then x1, x2 = x2, x1 end + if y1 > y2 then y1, y2 = y2, y1 end + for x=x1,x2 do + raster.set(x,y1,col) + raster.set(x,y2,col) + end + for y=y1+1,y2-1 do + raster.set(x1,y,col) + raster.set(x2,y,col) + end +end + +function raster.fillRect(x1,y1,x2,y2,col) + x1, y1, x2, y2 = math.floor(x1), math.floor(y1), math.floor(x2), math.floor(y2) + if x1 > x2 then x1, x2 = x2, x1 end + if y1 > y2 then y1, y2 = y2, y1 end + for x=x1,x2 do + for y=y1,y2 do + raster.set(x,y,col) + end + end +end + +function raster.drawCircle(xc, yc, radius, color) + xc=math.floor(xc) + yc=math.floor(yc) + radius=math.floor(radius) + local x = 0 + local y = radius + local d = 3 - 2 * radius + + while y >= x do + -- Draw 8 symmetric points + raster.set(xc + x, yc + y, color) + raster.set(xc - x, yc + y, color) + raster.set(xc + x, yc - y, color) + raster.set(xc - x, yc - y, color) + raster.set(xc + y, yc + x, color) + raster.set(xc - y, yc + x, color) + raster.set(xc + y, yc - x, color) + raster.set(xc - y, yc - x, color) + + if d < 0 then + d = d + 4 * x + 6 + else + d = d + 4 * (x - y) + 10 + y = y - 1 + end + x = x + 1 + end +end + +function raster.drawEllipse(x1, y1, x2, y2, color) + if x1 > x2 then x1, x2 = x2, x1 end + if y1 > y2 then y1, y2 = y2, y1 end + + local xc = math.floor((x1 + x2) / 2) + local yc = math.floor((y1 + y2) / 2) + + local a = math.floor((x2 - x1) / 2) + local b = math.floor((y2 - y1) / 2) + + if a <= 0 or b <= 0 then + return + end + + if a == b then + raster.drawCircle(xc, yc, a, color) + return + end + + if a <= 1 and b <= 1 then + raster.set(xc, yc, color) + return + elseif a <= 1 then + for y = yc - b, yc + b do + raster.set(xc, y, color) + end + return + elseif b <= 1 then + for x = xc - a, xc + a do + raster.set(x, yc, color) + end + return + end + + local x = 0 + local y = b + local a2 = a * a + local b2 = b * b + + local d1 = b2 - (a2 * b) + (0.25 * a2) + local dx = 2 * b2 * x + local dy = 2 * a2 * y + + while dx < dy do + raster.set(xc + x, yc + y, color) + raster.set(xc - x, yc + y, color) + raster.set(xc + x, yc - y, color) + + if d1 < 0 then + x = x + 1 + dx = dx + (2 * b2) + d1 = d1 + dx + b2 + else + x = x + 1 + y = y - 1 + dx = dx + (2 * b2) + dy = dy - (2 * a2) + d1 = d1 + dx - dy + b2 + end + end + + local d2 = b2 * (x + 0.5) * (x + 0.5) + a2 * (y - 1) * (y - 1) - a2 * b2 + + while y >= 0 do + raster.set(xc + x, yc + y, color) + raster.set(xc - x, yc + y, color) + raster.set(xc + x, yc - y, color) + raster.set(xc - x, yc - y, color) + + if d2 > 0 then + y = y - 1 + dy = dy - (2 * a2) + d2 = d2 - dy + a2 + else + y = y - 1 + x = x + 1 + dx = dx + (2 * b2) + dy = dy - (2 * a2) + d2 = d2 + dx - dy + a2 + end + end +end + +function raster.fillCircle(x, y, r, color) + x, y = math.floor(x + 0.5), math.floor(y + 0.5) + r = math.floor(r + 0.5) + + if r <= 0 then return end + + local minX, maxX = x - r, x + r + local minY, maxY = y - r, y + r + + for py = minY, maxY do + for px = minX, maxX do + local dx, dy = px - x, py - y + local distSquared = dx*dx + dy*dy + + if distSquared <= r*r then + raster.set(px, py, color) + end + end + end +end + +function raster.fillEllipse(x1, y1, x2, y2, color) + local centerX = (x1 + x2) / 2 + local centerY = (y1 + y2) / 2 + + local a = math.abs(x2 - x1) / 2 + local b = math.abs(y2 - y1) / 2 + + centerX = math.floor(centerX + 0.5) + centerY = math.floor(centerY + 0.5) + a = math.floor(a + 0.5) + b = math.floor(b + 0.5) + + if a <= 0 or b <= 0 then return end + + if a == b then + raster.fillCircle(centerX, centerY, a, color) + return + end + + local minX = centerX - a + local maxX = centerX + a + local minY = centerY - b + local maxY = centerY + b + + for y = minY, maxY do + for x = minX, maxX do + local dx = x - centerX + local dy = y - centerY + local value = (dx*dx)/(a*a) + (dy*dy)/(b*b) + + if value <= 1 then + raster.set(x, y, color) + end + end + end +end + +return raster diff --git a/src/lib/serialize.lua b/src/lib/serialize.lua new file mode 100644 index 0000000..108dafd --- /dev/null +++ b/src/lib/serialize.lua @@ -0,0 +1,48 @@ +local function _serialize(value, indent, level, visited) + local currentIndent = indent and string.rep(indent, level) or "" + local nextIndent = indent and string.rep(indent, level + 1) or "" + local sep = indent and "\n" or " " + local t = type(value) + if t == "nil" then return "nil" end + if t == "string" then return string.format("%q", value) end + if t == "number" then + if value ~= value then return "0/0" end + if value == math.huge then return "math.huge" end + if value == -math.huge then return "-math.huge" end + return tostring(value) + end + if t == "boolean" then return tostring(value) end + if t == "table" then + if visited[value] then return "..." end + visited[value] = true + local items = {} + local arrayCount = 0 + for i = 1, #value do + if value[i] ~= nil then arrayCount = i else break end + end + for i = 1, arrayCount do + table.insert(items, nextIndent .. _serialize(value[i], indent, level + 1, visited)) + end + for k, v in pairs(value) do + if type(k) ~= "number" or k < 1 or k > arrayCount then + local keyStr + if type(k) == "string" and k:match("^[%a_][%w_]*$") then + keyStr = k + else + keyStr = "[" .. _serialize(k, indent, level + 1, visited) .. "]" + end + table.insert(items, nextIndent .. keyStr .. " = " .. _serialize(v, indent, level + 1, visited)) + end + end + visited[value] = nil + if #items == 0 then return "{}" end + return "{" .. sep .. table.concat(items, "," .. sep) .. sep .. currentIndent .. "}" + end + return tostring(value) +end + +function serialize(value, indent) + return _serialize(value, indent, 0, {}) +end + +return serialize diff --git a/src/lib/shell.lua b/src/lib/shell.lua new file mode 100644 index 0000000..0d4ebf6 --- /dev/null +++ b/src/lib/shell.lua @@ -0,0 +1,42 @@ +local filesystem = require("filesystem") + +-- get a list of installed shells +local shellDir = filesystem.list("/halyde/scripts/") -- HACK: /halyde/scripts features more than just shells! +local shells = {} +for i=1,#shellDir do + table.insert(shells,string.match(shellDir[i],"([^/]+)%.lua$")) +end + +-- locate the shell +local tasks = tsched.getTasks() +-- print(tasks) +local pid = tsched.getCurrentTask().id +local function taskFromPID(pid) + checkArg(1,pid,"number") + for i=1,#tasks do + if tasks[i] and tasks[i].id==pid then + return tasks[i] + end + end +end +local shellProcess +while true do + local task = taskFromPID(pid) + if not task then + error("parent shell task doesn't exist (ID="..pid..")") + end + if table.find(shells,task.name) then + shellProcess = task + break + end + pid = task.parent + if not pid then + error("could not find parent shell task") + end +end +if not shellProcess then error("could not locate shell task") end + +-- get the shell object from the process +-- print("Process ID: "..shellProcess.id) +-- print(ipc.shared[shellProcess.id].shell) +return ipc.shared[shellProcess.id].shell diff --git a/src/lib/solvit.lua b/src/lib/solvit.lua new file mode 100644 index 0000000..e07d988 --- /dev/null +++ b/src/lib/solvit.lua @@ -0,0 +1,501 @@ +local defaultDBPath = "/ag2/testdb.json" + +local computer = require("computer") +local fs = require("filesystem") + +-- local db = require("solvitdb") +local db = {} +local json = require("json") +function db.create(dbpath) + local handle = fs.open(dbpath,"w") + handle:write("{}") + handle:close() +end +function db.readJSON(dbpath) + local handle = fs.open(dbpath,"r") + local content = "" + while true do + local s = handle:read(math.huge or math.maxinteger) + if not s then break end + content=content..s + end + handle:close() + return content +end +function db.get(dbpath,pack) + local dbc = json.decode(db.readJSON(dbpath)) + return dbc[pack] +end +function db.set(dbpath,pack,info) + local dbc = json.decode(db.readJSON(dbpath)) + dbc[pack]=info + local handle = fs.open(dbpath,"w") + handle:write(json.encode(dbc)) + handle:close() +end +function db.remove(dbpath,pack) + local dbc = json.decode(db.readJSON(dbpath)) + dbc[pack]=nil + local handle = fs.open(dbpath,"w") + handle:write(json.encode(dbc)) + handle:close() +end +function db.list(dbpath,pack) + local dbc = json.decode(db.readJSON(dbpath)) + local keys = {} + for i,_ in pairs(dbc) do + table.insert(keys,i) + end + return ipairs(keys) +end + +local avs = {} +function avs.splitSingular(s) + local result = {} + for str in string.gmatch(s, "([^.]+)") do + table.insert(result,tonumber(str) or -1) + end + return result +end + +function avs.parse(pack) + if not string.find(pack,"=") then + return {pack} + end + local idx=pack:find("=") + local name=pack:sub(1,idx-1) + local verstr=pack:sub(idx+1) + if string.find(verstr,"-") then + idx=verstr:find("-") + verstr={verstr:sub(1,idx-1),verstr:sub(idx+1)} + else + verstr={verstr} + end + for i=1,#verstr do + verstr[i]=avs.splitSingular(verstr[i]) + end + if #verstr>1 then + for i=1,3 do + verstr[1][i]=math.max(verstr[1][i],0) + end + end + return {name,verstr} +end + +function avs.serializeSingle(ver) + local ver2 = table.copy(ver) + for i=1,3 do + if ver2[i]==-1 then + ver2[i]="*" + else + ver2[i]=tostring(ver2[i]) + end + end + return ver2[1].."."..ver2[2].."."..ver2[3] +end + +function avs.serializeVersion(ver) + local singles = {} + for i=1,#ver do + table.insert(singles,avs.serializeSingle(ver[i])) + end + if singles[1]==singles[2] then + singles={singles[1]} + end + local out="" + for i=1,#singles do + out=out..singles[i] + if i~=#singles then + out=out.."-" + end + end + return out +end + +function avs.serializePack(pack) + if #pack==1 then + return pack[1] + end + return pack[1].."="..avs.serializeVersion(pack[2]) +end + +function avs.singleGreater(ver1,ver2) + for i=1,3 do + if ver1[i]~=ver2[i] then + if ver1[i]==-1 or ver1[i]>ver2[i] then + return true + end + if ver2[i]==-1 or ver2[i]>ver1[i] then + return false + end + end + end + return false +end + +function avs.singleLesser(ver1,ver2) + return avs.singleGreater(ver2,ver1) +end + +function avs.singleMin(ver1,ver2) + return avs.singleLesser(ver1,ver2) and ver1 or ver2 +end + +function avs.singleMax(ver1,ver2) + return avs.singleGreater(ver1,ver2) and ver1 or ver2 +end + +function avs.compatibleRange(vers) + for i=1,#vers do + if type(vers[i])=="string" then vers[i]=avs.parse(vers[i])[2] end + if #vers[i]==1 then vers[i]={vers[i][1],vers[i][1]} end + end + local range = vers[1] + for i=2,#vers do + range[1]=avs.singleMax(range[1],vers[i][1]) + range[2]=avs.singleMin(range[2],vers[i][2]) + end + if avs.singleGreater(range[1],range[2]) then + return nil + end + return range +end + +function avs.matching(pack1,pack2) + if pack1[1]~=pack2[1] then return false end + local ver1 = pack1[2] or {{-1,-1,-1}} + local ver2 = pack2[2] or {{-1,-1,-1}} + if #ver1==1 then ver1={ver1[1],ver1[1]} end + if #ver2==1 then ver2={ver2[1],ver2[1]} end + return avs.compatibleRange({ver1,ver2})~=nil +end + +local function packageInArray(pack,arr) + for i=1,#arr do + if avs.matching(avs.parse(arr[i]),pack) then + return true,arr[i] + end + end + return false +end + +local function packageNameInArray(pack,arr) + for i=1,#arr do + if arr[i][1]==pack[1] then + return true,arr[i] + end + end + return false +end + +local function removeFromArray(el,arr) + for i=1,#arr do + if arr[i]==el then + table.remove(arr,i) + return i + end + end +end + +local function startTransaction(dbpath) + dbpath = dbpath or defaultDBPath + if not fs.exists(dbpath) then + db.create(dbpath) + end + + local yieldClock = computer.uptime() + local function yieldIfNecessary() + if computer.uptime()-yieldClock>=0.1 then + coroutine.yield() + yieldClock = computer.uptime() + end + end + + + local installIncomplete = false + local removeIncomplete = false + + local packInfo = {} + local ins = {} + local rem = {} + local transaction = {} + function transaction.install(name) + table.insert(ins,avs.parse(name)) + installIncomplete = true + end + function transaction.remove(name) + table.insert(rem,avs.parse(name)) + removeIncomplete = true + end + function transaction.autoRemove() + end + function transaction.update(name) + end + function transaction.updateAll(name) + end + function transaction.addInfo(name,info) + if not info.type then info.type="package" end + packInfo[name]=info + -- print(require("serialize")(packInfo)) + end + + local function getPackInfo(pack) + return packInfo[avs.serializePack(pack)] + end + local function findReverseDependencies(pack) + local out={} + for i,info in pairs(packInfo) do + if info.dependencies and packageInArray(pack,info.dependencies) then + table.insert(out,avs.parse(i)) + end + end + return out + end + local function finalizeInstall(settings) + -- find missing package information + local missing = {} + for i=1,#ins do + if getPackInfo(ins[i])==nil then + table.insert(missing,avs.serializePack(ins[i])) + end + end + if #missing>0 then + return false,missing + end + -- find dependencies + installIncomplete=false + local i=1 + while i<=#ins do + local deps = getPackInfo(ins[i]).dependencies + if deps and #deps>=1 then + for j=1,#deps do + local dep = avs.parse(deps[j]) + local inArr,arrPack = packageNameInArray(dep,ins) + if inArr then + if not avs.matching(dep,arrPack) then + local msg = "" + if settings.resolveConflict then + msg="Cannot resolve conflict: "..msg + end + local rev = findReverseDependencies(arrPack) + if #rev==0 then + msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but one or more packages depend on "..avs.serializePack(arrPack) + return false, msg + elseif #rev==1 then + msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but package "..avs.serializePack(rev[1]).." depends on "..avs.serializePack(arrPack) + return false, msg + else + msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but packages " + for i=1,#rev do + if i>1 and i~=#rev then + msg=msg..", " + elseif i==#rev then + msg=msg.." and " + end + msg=msg..avs.serializePack(rev[i]) + end + msg=msg.." depend on "..avs.serializePack(arrPack) + return false, msg + end + end + elseif type(db.get(dbpath,dep[1]))=="nil" then + installIncomplete=true + table.insert(ins,j,dep) + else + local dbinfo = db.get(dbpath,dep[1]) + local dbpack = {dep[1]} + if dbinfo.version then + dbpack = avs.parse(dep[1].."="..dbinfo.version) + end + if not avs.matching(dep,dbpack) then + if settings.resolveConflict then + removeIncomplete=true + table.insert(rem,1,dbpack) + + installIncomplete=true + table.insert(ins,j,dep) + else + local msg = "Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but another version ("..avs.serializePack(dbpack)..") is installed" + return false, msg + end + end + end + end + i=i+#deps + end + i=i+1 + end + end + local function finalizeRemove(settings) + removeIncomplete=false + -- filter to only have packages in the database + local i=1 + while i<=#rem do + local dat = db.get(dbpath,rem[i][1]) + if not dat then + table.remove(rem,i) + else + packInfo[avs.serializePack(rem[i])]=dat + i=i+1 + end + end + -- get package info from database + for i=1,#rem do + if not getPackInfo(rem[i]) then + packInfo[avs.serializePack(rem[i])]=db.get(rem[i][1]) + end + end + -- find if the main package is reverse dependant to another + i=1 + while i<=#rem do + local dat = getPackInfo(rem[i]) + if dat.reverseDependencies and #dat.reverseDependencies>0 then + for _,dep in ipairs(dat.reverseDependencies) do + if not packageNameInArray({dep},rem) then + if settings.cascade then + table.insert(rem,1,{dep}) + i=i+1 + else + return false, "Package "..rem[i][1].." is a dependency of "..dep + end + end + end + end + i=i+1 + end + -- look for dependencies if settings.autoremove is on + if settings.autoremove then + i=1 + while i<=#rem do + local deps = getPackInfo(rem[i]).dependencies + if deps and #deps>=1 then + for j=1,#deps do + local dep = avs.parse(deps[j]) + local depdat = packInfo[deps[j]] + if not depdat then + depdat = db.get(dbpath,dep[1]) + packInfo[deps[j]]=depdat + end + if (not packageNameInArray(dep,rem)) and type(db.get(dbpath,dep[1]))~="nil" and (#depdat.reverseDependencies==1 and depdat.reverseDependencies[1]==rem[i][1]) then + removeIncomplete=true + table.insert(rem,j,dep) + end + end + i=i+#deps + end + i=i+1 + end + end + end + function transaction.finalize(settings) + settings = settings or {} + + while installIncomplete or removeIncomplete do + while installIncomplete do + local out = {finalizeInstall(settings)} + if out[1]==false then return table.unpack(out) end + yieldIfNecessary() + end + while removeIncomplete do + local out = {finalizeRemove(settings)} + if out[1]==false then return table.unpack(out) end + yieldIfNecessary() + end + end + + local install = {} + local remove = {} + for i=1,#ins do + table.insert(install,avs.serializePack(ins[i])) + end + for i=1,#rem do + table.insert(remove,avs.serializePack(rem[i])) + end + return true, {install=install,remove=remove} + + -- return "true, {["install"] = {"dep1", "package1", "package2"}, ["remove"] = {"package3"}}" on success + -- return "false, {"dep1"}" when not enough data + -- return "false, "[verbose string]"" when conflict found + -- TODO: handle same range AVS + -- TODO: handle different intercompatible 1.*.* range AVS + -- TODO: handle different incompatible 1.*.* range AVS + -- TODO: handle different intercompatible 1.*.*-2.*.* range AVS + -- TODO: handle different incompatible 1.*.*-2.*.* range AVS + -- TODO: handle conflicts from package info + -- TODO: handle reverse conflicts from another package's info + -- TODO: handle automatic conflict resolving + -- TODO: handle update of a single package with no dependencies + -- TODO: handle update of a single package with dependencies that don't need updating + -- TODO: handle update of a single package with dependencies that need updating + -- TODO: handle update of a single package that has a set dependency version changed + -- TODO: handle updating all packages in the database + -- TODO: handle installing optional packages + -- TODO: handle installing virtual packages and store this vpack info to database + -- TODO: handle removing virtual packages from database info + -- TODO: handle installing groups and store this group info to database + -- TODO: handle removing groups and store this group info to database + end + local function storeInstall() + -- directly set + for _,pack in ipairs(ins) do + if getPackInfo(pack) then + local info = table.copy(getPackInfo(pack)) + if pack[2] then + info.version=avs.serializeVersion(pack[2]) + else + info.version=info.latestVersion or info.version + end + db.set(dbpath,pack[1],info) + end + end + -- set reverse dependencies + for _,pack in pairs(ins) do + local i = avs.serializePack(pack) + local v = packInfo[i] + if v and v.dependencies then + for _,dep in ipairs(v.dependencies) do + local depname = avs.parse(dep)[1] + local dat = db.get(dbpath,depname) + if not dat then goto continue end + if type(dat.reverseDependencies)~="table" then + dat.reverseDependencies={} + end + table.insert(dat.reverseDependencies,pack[1]) + db.set(dbpath,depname,dat) + ::continue:: + end + end + end + end + local function storeRemove() + -- directly remove + for _,pack in ipairs(rem) do + db.remove(dbpath,pack[1]) + end + -- remove reverse dependencies + for _,pack in ipairs(rem) do + local pdat = getPackInfo(pack) + if not pdat.dependencies then goto continue end + for _,dep in ipairs(pdat.dependencies) do + local depname = avs.parse(dep)[1] + local dat = db.get(dbpath,depname) + if dat.reverseDependencies then + removeFromArray(pack[1],dat.reverseDependencies) + end + db.set(dbpath,depname,dat) + end + ::continue:: + end + end + function transaction.store() + if #ins>0 then + storeInstall() + end + if #rem>0 then + storeRemove() + end + end + return transaction +end + +return { avs=avs,startTransaction=startTransaction } diff --git a/src/lib/solvitdb.lua b/src/lib/solvitdb.lua new file mode 100644 index 0000000..5b674f9 --- /dev/null +++ b/src/lib/solvitdb.lua @@ -0,0 +1,312 @@ +local solvitdb = {} + +local fs = require("filesystem") + +local function checkValidityAndOpen(path) + local handle = assert(fs.open(path)) + local data = assert(handle:read(8)) + if data:sub(5, 8) == "RTFM" then + local patLength = string.unpack(" location - i then + readAmount = location - i + end + if readAmount == 0 then + break + end + local data = readHandle:read(readAmount) + i = i + readAmount + assert(writeHandle:write(data)) + end + assert(writeHandle:write(bytes)) + while true do + local data = readHandle:read(chunkLength) + if not data then + break + end + assert(writeHandle:write(data)) + end + readHandle:close() + writeHandle:close() + fs.rename(tmpFilePath, filePath) +end + +local function remove(filePath, location, length) + local chunkLength = 512 + if length > 512 then + chunkLength = length + end + -- The file has to get shortened, so I have no choice but to do these shenanigans + local readHandle = assert(fs.open(filePath, "r")) + local tmpFilePath = filePath .. ".tmp" + local writeHandle = assert(fs.open(tmpFilePath, "w")) + local i = 0 + while true do + local readAmount = chunkLength + if readAmount > location - i then + readAmount = location - i + end + if readAmount == 0 then + break + end + local data = readHandle:read(readAmount) + i = i + readAmount + assert(writeHandle:write(data)) + end + readHandle:seek(length) + while true do + local data = readHandle:read(chunkLength) + if not data then + break + end + assert(writeHandle:write(data)) + end + readHandle:close() + writeHandle:close() + fs.rename(tmpFilePath, filePath) +end + +local function adjustPatLocationsAfterPackage(filePath, packageName, offset) + local readHandle, patLength = checkValidityAndOpen(filePath) + local pat = readPat(readHandle, patLength) + readHandle:close() + local modifiedLocation = pat[packageName] + local toAdjust = {} + for name, location in pairs(pat) do + if location > modifiedLocation then + table.insert(toAdjust, { name = name, location = location }) + end + end + + if #toAdjust == 0 then + return + end + + local readHandle = assert(fs.open(filePath, "r")) + readHandle:seek(8) -- Skip header + local patBytes = assert(readHandle:read(patLength)) + readHandle:close() + + local patFieldPositions = {} + for _, entry in ipairs(toAdjust) do + local needle = entry.name .. "." + local startPos = patBytes:find(needle, 1, true) + patFieldPositions[entry.name] = 8 + startPos + #needle - 1 + end + + local writeHandle = assert(fs.open(filePath, "a")) + for _, entry in ipairs(toAdjust) do + local newLocation = entry.location + offset + writeHandle:seek("set", patFieldPositions[entry.name]) + writeHandle:write(string.pack(" 0 then + writeHandle:write(encodedString:sub(1, #encodedString - difference)) + local currentSeek = writeHandle:seek() + writeHandle:close() + insert(path, currentSeek + 1, encodedString:sub(#data - difference, -1)) + adjustPatLocationsAfterPackage(path, name, difference) + end + else + readHandle:close() + writeHandle:seek("end") + local newPackageLocation = writeHandle:seek() - patLength - 8 + writeHandle:write(encodedString .. "\n") + writeHandle:close() + local patData = ("%s.%s;"):format(name, string.pack("=min and v1 then + if not chooseDrive() then + reset() + return + end +end +if not installLocation then + reset() + io.stderr.write("All drives are read-only.\nHalyde cannot be installed.") + return +end + +if width<80 then + gpu.set(1,1,"Are you sure you would like to install Halyde?") +else + gpu.set(1,1,"Are you sure you would like to install Halyde to "..installLocation.."?") +end +gpu.set(1,2,"This will erase all data on this disk.") +gpu.set(1,height-1,"Press Y to accept, or N to cancel.") +gpu.set(3,4,"Capacity: ") +gpu.set(3,5,"Used: ") +gpu.set(3,6,"ID: ") +gpu.set(3,7,"Label: ") +gpu.setForeground(0x00FF00) +if width>=80 then + gpu.set(50,1,installLocation) +end +gpu.set(13,4,math.floor(component.invoke(installAddress,"spaceTotal")/1024).." KiB") +gpu.set(9,5,math.floor(component.invoke(installAddress,"spaceUsed")/1024).." KiB") +gpu.set(7,6,installAddress) +gpu.set(10,7,component.invoke(installAddress,"getLabel") or "No label") + +if keyboard.keys[({event.pull("key_down")})[4]]=="n" then + return reset() +end + +-- installation + +local computer = require("computer") + +local function getFile(url) + local request, data, tmpdata = nil, "", nil + local status, errorMessage = pcall(function() + request = internet.request(url) + request:finishConnect() + end) + if not status then + return false, errorMessage + end + local responseCode = request:response() + if responseCode and responseCode ~= 200 then + return false, responseCode + end + repeat + tmpdata = request.read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + return data +end + +-- installation graphics +local webInstallConfig +local installationOrder = {"halyde", "edit", "argentum", "webinstall-extras"} + +gpu.setBackground(0x000000) +gpu.setForeground(0xFFFFFF) +gpu.fill(1,1,width,height," ") + +local function lpad(str, len, char) + str=tostring(str) + if char == nil then char = ' ' end + return string.rep(char, len - #str) .. str +end + +local function progress(package,progress) + local total = 0 + if webInstallConfig and type(webInstallConfig)=="table" then + for i,pck in ipairs(installationOrder) do + local packConfig = webInstallConfig[pck] + -- print(pck,packConfig) + total=total+#(packConfig.directories or {})+#(packConfig.files or {}) + end + else + total=1 + end + + local info = "" + local realProgress = 1 + if type(package)=="string" then + realProgress = progress + info = string.format("%s %s%%",package,lpad(math.floor(progress*100),2)) + else + realProgress = 0 + for i=1,package do + local packConfig = webInstallConfig[installationOrder[i]] + if i==package then + realProgress=realProgress+progress + else + local value = #(packConfig.directories or {})+#packConfig.files + realProgress=realProgress+value + end + end + realProgress=realProgress/total + local packConfig = webInstallConfig[installationOrder[package]] + progress=progress/(#(packConfig.directories or {})+#packConfig.files) + -- realProgress = (progress+package-1)/#installationOrder + local packInfo = installationOrder[package].." "..lpad(math.floor(progress*100),2).."%" + info = string.format("%s%% [%s]",lpad(math.floor(realProgress*100),2),packInfo) + end + + info=info..string.rep(" ",width-#info) + local progX = math.floor(realProgress*width) + gpu.setBackground(0x00FF00) + gpu.setForeground(0x000000) + gpu.set(1,height,info:sub(1,progX)) + gpu.setBackground(0x000000) + gpu.setForeground(0xFFFFFF) + gpu.set(progX+1,height,info:sub(progX+1)) +end +local logY = 1 +local function log(txt) + if logY>=height then + gpu.copy(1,2,width,height-2,0,-1) + gpu.fill(1,height-1,width,1," ") + logY=logY-1 + end + gpu.set(1,logY,txt) + logY=logY+1 +end +---------------------------- + +log("Fetching Argentum configuration for Halyde") +progress("Preparing",0) +webInstallConfig = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/argentum.cfg") +log("Loading Argentum configuration") +progress("Preparing",0.5) +webInstallConfig = load(webInstallConfig) +webInstallConfig = webInstallConfig() +log("Looking for outdated files in the drive") +progress("Preparing",1) +local oldFiles = {} +for oldFile in fs.list(installLocation) do + local usedFlag = false + for i = 1, 3 do + for _, file in pairs(webInstallConfig[installationOrder[i]].files) do + if oldFile == file then + usedFlag = true + end + end + if webInstallConfig[installationOrder[i]].directories then + for _, dir in pairs(webInstallConfig[installationOrder[i]].directories) do + if oldFile == dir .. "/" then + usedFlag = true + end + end + end + end + if oldFile=="halyde/" then usedFlag = true end + if not usedFlag then + table.insert(oldFiles, oldFile) + end +end +log("Found "..#oldFiles) +progress(1,0) + +for i = 1, 4 do + local webInstallConfig = webInstallConfig[installationOrder[i]] + local dirCount = 0 + if webInstallConfig.directories then + dirCount=#webInstallConfig.directories + for dirIdx, directory in ipairs(webInstallConfig.directories) do + log("Creating " .. directory .. "...") + progress(i,dirIdx-1) + fs.makeDirectory(installLocation .. directory) + end + end + for fileIdx, file in ipairs(webInstallConfig.files) do + log("Downloading " .. file .. "...") + progress(i,fileIdx-1+dirCount) + local handle = fs.open(installLocation .. file, "w") + handle:write(getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/" .. file)) + handle:close() + end +end +for i, oldFile in ipairs(oldFiles) do + log("Removing "..oldFile) + progress("Finishing up",(i-1)/#oldFiles*1) + fs.remove(installLocation .. oldFile) +end + +log("Setting boot address") +progress("Finishing up",1) +computer.setBootAddress(component.get(installLocation:sub(6, -2))) + +log("Setting label to Halyde") +component.invoke(component.get(installLocation:sub(6, -2)), "setLabel", "Halyde") + +gpu.fill(1,1,width,height," ") +computer.shutdown(true)