Compare commits

...

90 Commits

Author SHA1 Message Date
20kdc 9d152bb994
Update README.md 2022-08-24 16:59:46 +01:00
20kdc d2ad9ce4eb
Add to phrasebook 2022-08-05 15:27:58 +01:00
20kdc d623375c04
Remove outdated message.
Congratulations, the ride just keeps getting worse.
2022-07-11 11:30:00 +01:00
20kdc 0f333b9c2f
Fuck you, Microsoft. (Minecraft MP is no longer playable as of this date without migration) 2022-07-11 10:46:55 +01:00
20kdc 85869aa80d sys-init fix (hopefully) (presync)
This wasn't properly committed & pushed, fixed that now. Hopefully it doesn't destroy everything.
2021-04-26 18:08:00 +01:00
20kdc 6fc56b4d64 Get OCVM to kinda work for development 2021-04-12 17:22:02 +01:00
20kdc f083efd9a3 post-packaging minor fix to stop a separate "inst-gold.lua" file being created 2021-01-12 13:22:41 +00:00
20kdc 2ea52fc362 script to use the right algorithm 2021-01-12 13:14:12 +00:00
20kdc 2dbc3cdb0e Finish license changes to 20kdc-owned CLAW packages 2021-01-12 12:39:11 +00:00
20kdc 2fe4884558 BSD0 license for repository 2021-01-12 12:21:19 +00:00
20kdc dcd7154ec2 License 'change' (Please see #5)
This license change should only increase the amount of stuff people can do in countries where the public domain is not a valid concept.
If this license somehow restricts you as opposed to the previous one, please file an issue.
2021-01-12 12:16:50 +00:00
20kdc 881193aa9a Bugfix: Claiming a monitor is documented as returning the monitor proxy, now make that actually work 2020-04-22 18:16:57 +01:00
20kdc e308f54ad7 Try to fix app-slaunch 2020-04-21 23:01:15 +01:00
20kdc cbe40da3bb AmandaC found a bug, get things prepared for r10
Going to do the same thing as previously where I delay until I'm sure there isn't anything else lurking
2020-04-21 21:39:09 +01:00
20kdc 8bd316338b Merge branch 'master' into repository 2020-04-03 14:51:06 +01:00
20kdc 3dbea9d3f7 app-telnet: Add missing dependency on svc-t 2020-04-02 22:08:43 +01:00
20kdc 2ebc0840ac Rename app-kmt to app-telnet 2020-04-02 22:07:14 +01:00
20kdc da58c88d0d quick-fix DEFLATE decompressor so stuff runs on T1 again 2020-04-02 22:06:49 +01:00
20kdc fe17b0fb93 Finally settle on an exact level of support 2020-04-02 21:39:30 +01:00
20kdc c9157a1b7c More terminal improvements. Yes. Again. 2020-04-02 14:33:35 +01:00
20kdc ca4b6e5df2 Terminal polish: I might be going a tad too far with this 2020-04-02 12:15:09 +01:00
20kdc 7d1f6d2cae A bit of terminal polish 2020-04-02 01:52:25 +01:00
20kdc a585ce4a75 svc-t: Just realized will/won't need responses too 2020-04-02 00:15:36 +01:00
20kdc 3ed1cebe25 svc-t: Properly ignore TELNET subnegotiations 2020-04-02 00:08:28 +01:00
20kdc 7c70a1128c Bugfixes to Everest, finalize the terminal API 2020-04-01 23:24:20 +01:00
20kdc 5ac8f9ff11 Limited terminal line history 2020-03-31 18:28:59 +01:00
20kdc f761ad5824 Remove overwrite confirmations because life is short and UX nitpick bugs are long.
Have fun with this. If anyone complains I'm calling it the April 1st release.
2020-03-31 18:10:55 +01:00
20kdc aef0043d4a Finish up (maybe) the new compression scheme. 2020-03-31 17:52:24 +01:00
20kdc 151097cdce Finish purging deprecated lexcrunch stuff from the DEFLATE decompressor 2020-03-31 15:01:46 +01:00
20kdc bfcb4f9028 Smooth off the DEFLATE decompressor's sharp edges 2020-03-31 14:05:58 +01:00
20kdc cc55e8a66f Improve lexcrunch just a bit 2020-03-31 13:23:52 +01:00
20kdc 3c4a3147c4 The DEFLATE Decompressor (Of Pain And Suffering) 2020-03-31 12:00:06 +01:00
20kdc bc4d626b4e All the architectural changes pre-DEFLATE 2020-03-31 11:59:58 +01:00
20kdc dd21abe8fa More elseifs to feed DEFLATE, despite the negative effects on BDIVIDE 2020-03-30 20:21:41 +01:00
20kdc 22c1c211ef Bugfix: fix 'delete' key, fix Everest KB/M matching 2020-03-30 19:11:50 +01:00
20kdc 7840f0a231 Improve BDIVIDE (88 bytes free) 2020-03-30 17:55:20 +01:00
20kdc 339571ee9b Even more line editing improvements 2020-03-30 14:36:33 +01:00
20kdc 479412a5bb Better line editing 2020-03-30 12:08:12 +01:00
20kdc d2ee505316 Bigram compression for fun and size reduction 2020-03-29 22:43:38 +01:00
20kdc f5ba0489b2 Start work on r9 with the basic terminal system design 2020-03-28 19:13:03 +00:00
20kdc 20c016f068 Document svc-virtudev 2020-03-28 19:12:31 +00:00
20kdc fab88f137c svc-virtudev: virtual devices for KittenOS NEO 2020-03-28 00:32:16 +00:00
20kdc 7680aa7579 Apparently I missed a few details of the packaging stuff for setup 2020-03-27 22:35:00 +00:00
20kdc 429de87a61 Add missing secpolicy version bump (oops) 2019-01-02 15:28:59 +00:00
20kdc 7307eb30a4 Consider 'tablet' component & tablet_use safe in secpolicy, fix incorrect documentation on k.computer 2019-01-02 15:28:01 +00:00
20kdc 0d9583fcff r8: Yet another release for a single feature request
This isn't getting pushed until AmandaC's tested it.
I'm not sure this is such a good idea, anyway ; if it's for load/save
 workflow improvements, wouldn't it be better to have a file access method
 that allows for file re-opening?
That said, there's a limit to *that* before you just have to say,
 "Just use /data/".
2018-12-24 21:11:59 +00:00
20kdc 375995c2d3 r7: Fix the read-only-FS filemanager bug AmandaC found
This should fix everything
2018-12-11 16:40:30 +00:00
20kdc efae7716da Merge branch 'repository' (for rwhateverwe'reon) 2018-12-11 15:26:23 +00:00
20kdc 27bd71f9e4 (Probably) fix sys-init self-destructing if no monitors around 2018-11-09 23:19:27 +00:00
20kdc 3d3517bc53 app-tapedeck: Copy/Erase buttons, operation cancelling 2018-10-13 10:37:07 +01:00
20kdc 916d127337 Add stubs for app-metamachine getDeviceInfo/setArchitecture/getArchitecture, add some debug for missing methods (plan9k vm fix) 2018-10-11 17:40:54 +01:00
20kdc 630c5f57f5 app-metamachine: Native clipboard support 2018-09-29 10:43:46 +01:00
20kdc 3502cdedc2 Merge branch 'repository' 2018-09-27 23:03:25 +01:00
20kdc 21294872f3 NEO r6: sys-init improvements
...Yup, that's it for this one.
2018-09-27 23:01:05 +01:00
20kdc 71a0aa0b08 Update the tape interface application to do everything it should need to be able to do 2018-09-26 22:46:03 +01:00
20kdc 99cb58d9fc Add a tape interface application, 'app-tapedeck' 2018-09-26 01:46:16 +01:00
20kdc 324ed86335 Update KMT to v1: be faster at reading 2018-07-28 17:20:43 +01:00
20kdc 13d3abfd26 quickly fix this (this is r5 now) 2018-07-28 16:50:35 +01:00
20kdc 27f7fe35da Finally finish the packaging updates for r5? 2018-07-28 15:00:30 +01:00
20kdc 6255587090 Here goes nothing.
Releasing a version with this commit NOT enabled to dev
2018-07-28 14:53:04 +01:00
20kdc f903a16166 Hopefully correctly add 'KMT'
("LimboCon have something to look at backup plan" app)
2018-07-28 14:50:24 +01:00
20kdc 347bebdb03 Fix a mistake with the logo.bmp thing 2018-07-28 14:50:07 +01:00
20kdc 8f86dfd730 Merge branch 'repository' 2018-07-28 14:41:48 +01:00
20kdc 76448b8187 Replace app-klogo with a more general app-bmpview 2018-07-25 23:45:01 +01:00
20kdc b320dfe083 Enabling DEV in CLAW for next dev version upload 2018-07-09 18:19:21 +01:00
20kdc 3bc323e268 Fix bitmap library issues, bugfixes to everest and glacier 2018-07-09 18:17:49 +01:00
20kdc 1e3bf096d5 Fix app-batmon losing track of time 2018-06-12 21:45:54 +01:00
20kdc 6237871d36 Fix some CLAW stuff and document CLAW formats in neo-docs 2018-06-12 21:45:40 +01:00
20kdc d371044146 Update packaging script so inst.lua version number can be easily checked 2018-06-12 01:51:19 +01:00
20kdc 050941a513 Make sys-everest a bit smaller by eliminating abstraction. 2018-06-12 01:35:15 +01:00
20kdc 3ba0792dfe Fix app-flash bugs
EEPROM names/addresses are now always at least partially visible,
 and EEPROM regulations on labels no longer block editing.
2018-06-12 01:34:02 +01:00
20kdc 7ca2c26979 Ported batmon from legacy, and made app-textedit less GPU-hungry
Both of these should be useful for multimonitor setups,
 in their own individual ways.
2018-06-12 01:29:44 +01:00
20kdc ccb9c3b279 CLAWv3, aka 'C2' - lower memory CLAW
Hopefully this'll finally end the "CLAW runs out of memory an awful lot" problem.
2018-06-12 01:27:26 +01:00
20kdc 8ab47c96b3 Improve the compression, and get rid of imitclaw (will be outdated) 2018-06-10 00:06:12 +01:00
20kdc e1530057a6 Fix nbcompose and knbs bugs 2018-06-09 22:55:35 +01:00
20kdc 9254745a33 sys-everest: Get rid of some typechecks that were just nomming space 2018-06-08 19:55:30 +01:00
20kdc 4c12bb548a Add app-nbcompose and the library for it "knbs". 2018-06-08 19:43:19 +01:00
20kdc 46d60df1ec app-pclogix-upload and app-rsctrl
Yes, I made sure to ask Mimiru about permission to target the uploader there.
2018-06-08 16:26:29 +01:00
20kdc e75cd20aa4 v5 beginnings: logo update and a new Everest key 2018-06-06 23:10:32 +01:00
20kdc ae89024112 Merge branch 'repository' (metamachine stuff) and thus release R4 2018-06-03 20:51:24 +01:00
20kdc c3b6cdc898 Fix #2 (screens being double-claimed) 2018-06-03 20:50:32 +01:00
20kdc d1e83616e7 Make sure I actually update logo bmp for r4
r3 is kind of an interim release anyway, so meh on that, but...
2018-05-30 16:57:29 +01:00
20kdc 154d9676ab Fix more metamachine bugs... 2018-05-30 16:45:24 +01:00
20kdc e6afe02457 Update kn-refer in regards to the filename characters. 2018-05-30 16:29:06 +01:00
20kdc 3df5b6d3c9 Fix metamachine being broken due to undocumented OC component.list quirk 2018-05-30 16:23:29 +01:00
20kdc f011da9810 Get rid of the 'future' idea. Final commit for r3. 2018-05-30 14:11:45 +01:00
20kdc 28d639e1d1 Fix some small metamachine issues, and make the virtual EEPROM for metamachine better. 2018-05-30 14:07:52 +01:00
20kdc 7fa7441794 Add early version of app-metamachine, update kn-refer for r3 2018-05-29 23:50:11 +01:00
20kdc d5405685dd NEO r2p1 system changes 2018-05-29 23:47:20 +01:00
20kdc 7092b41f78 Work out how future development is going to work 2018-05-23 23:35:05 +01:00
123 changed files with 7465 additions and 2644 deletions

21
.gitignore vendored
View File

@ -1,9 +1,8 @@
# This is released into the public domain.
# No warranty is provided, implied or otherwise.
# leaving in preSH.tar.gz for anyone who's interested
# in how NOT to do compression
code.tar
code/data/app-claw/*
work.tar
work
work/
@ -20,8 +19,24 @@ repobuild/*/*
repobuild/*/*/
repobuild/*/*/*
laboratory/ocemu.cfg
laboratory/bios.lua
laboratory/data
laboratory/log
laboratory/*/
laboratory/*/*
laboratory/*/*/
laboratory/*/*/*
laboratory/*/*/*/
laboratory/*/*/*/*
inst.lua
com2/code.tar.bd
# Available as the respective release
inst-gold.lua
# Compression stuff
inst/iSymTab
# internal
upldr.sh
upldr-dev.sh
upldr-gold.sh
repository/inst.lua

View File

@ -1,75 +0,0 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
- -- To Microsoft.
- -- You deserve to get yourselves investigated by Trading Standards.
- -- I don't mean that in a "and then everything comes out okay" way,
- -- I mean that as in a "you deserve to be legitimately threatened with being shutdown" way.
- -- See that MultiMC issue, #2223, about the Windows 10 thing?
- -- That's the beginnings of what you've caused, and don't you dare deny the intent.
- -- In what world is swapping around the names of two products, both in
- -- active sales no less, to get one to be chosen over the other by an
- -- uninformed buyer *not illegal*?
- -- To anyone else, I say this.
- -- If you're affected by this, just pirate the Java edition.
- -- You have been affected by what I can say without contradiction is a campaign of lies.
- -- Deliberate or otherwise, but history would point in favour of a calculated attack.
- -- Take what is frankly rightfully yours.
- -- Oh, Microsoft, did you not like that? I don't care, and it is plainly obvious why I shouldn't care.
- -- To the Mojang team - the *real* Mojang team (Anyone working on Bedrock is exempt) - I pity you,
- -- but I'm afraid I have to recommend this course of action.
- -- There is no other way. I'm sure you already know what Microsoft is like first-hand.
- -- I'm sure you've read the reports and you know Microsoft will lobby their way
- -- past anything I could even suggest to any politician, if it even got that far.
- -- I'm sure you can already see your presumed redundancies coming, and I doubt I hasten it.
- -- And frankly, preventing Microsoft from abusing Minecraft as a lock-in tactic in whatever way I can
- -- takes priority.
- -- To those who doubted what I said to them when I said it to them,
- -- to those who doubt me now, know this. It's happening, it's real,
- -- there is proof with basic searches.
- -- To those who are ignorant, and yet scared to let anyone leave them,
- -- THOSE WITHOUT WINDOWS 10 ON THEIR DESKTOPS WON'T BE ABLE TO JOIN YOU
- -- ON MC IF THIS GOES AS THEY LIKELY INTEND.
- -- You may doubt my "likely", but remember this:
- -- We thought they would do this long ago, when Microsoft bought Mojang.
- -- It took time, yes. But it started to happen and when it did, everything started moving into place.
- -- The "Better Together" (unless on a non-Windows 10 desktop) update was simply the end of a journey.
- -- When I look back, the guesses - mine and others - as to what they will do next have been delayed,
- -- but they are occurring, along with new horrors that I did not expect,
- -- like the commercialization of that offered by mods for free here on the "Java Edition".
- -- You can argue all you like about how they put work into that stuff,
- -- fact is, Bedrock is a downgrade.
- -- You could argue about performance, but - Mods fix the performance issues,
- -- and they aren't exactly hard to install. Download Forge installer. Run Forge installer.
- -- Copy one file. Done.
- -- You cannot argue about playerbase - people are left behind, unable to go to Bedrock.
- -- Even if you claim it's their fault for using Windows 10, how much have you somehow missed
- -- about Windows 10's various nastiness? There's a laptop basically unusable for anything real-time here
- -- on Windows 10 because of an update that screwed it over, and no, a reset did not fix it. Nothing did.
- -- Except moving it to another OS, but of course, that wouldn't work so well if Bedrock was a concern.
- -- Moddability has been, and probably always will be, destroyed on Bedrock.
- -- Sure, you'll eventually get some more and more access,
- -- but there's a reason the way Minecraft modding went
- -- lead to the inventive and amazing mods out there,
- -- and it certainly wasn't a bunch of "behavior packs"!
- -- To those who wonder what the hell this is doing here,
- -- I don't care. Feel free to remove the file from your local machines,
- -- but if you send a PR asking for its removal, I *will* reject it instantly.
- -- -20kdc
-----BEGIN PGP SIGNATURE-----
iQEzBAEBCAAdFiEE4HrK8U+8321iGlEUALn5S4V/2fkFAlrafToACgkQALn5S4V/
2fkW/wf/afn8PjeCYyNOWNhGmELNO5dL3eWaBb86KAiDZXMYPmCifDiXOOmYeq2t
3cqcDXujOQJN7PE8RBzrVSIm2kjLtQXoFuhww6A9p3LuyXdPT0WykMroPM5xYrrH
7c6VtaWkXngHn/Z/aLgIDsveW/LiHqG5a8N98XdPivlRgfsBPo5+/wv84gcnAGzL
vJURI/GMEuFj+Bg6Vw0fiiYF4DAVKSdSb830bW4flFTwXBHOL8SiUbEDBX1W5nj1
KbnIhnXdy/rGbjsVUlpnDIyL+IdTyRwLyxYpjvHqpdb0RjALCw+b6uzQ6feOUb2/
RsDIy5cRGY4k2sSP+yFByvNR34HphA==
=uPTk
-----END PGP SIGNATURE-----

View File

@ -1,3 +1,51 @@
# ATTENTION! WARNING! ETCETERA!!!
AS OF 11TH JULY 2022, ALL SUPPORT AND MAINTENANCE ON THIS REPOSITORY HAS BEEN DROPPED.
Microsoft finally turned off whatever mechanism PolyMC was using to let people play multiplayer on Mojang accounts.
Use UltimMC. Use something like https://modrinth.com/mod/easyauth . Host your servers this way.
Microsoft have proven they are willing to remove a bought product off your hands if you don't agree to their new rules about it.
They have proven that "buying" something off of them means nothing and anything you've "bought" from Mojang means nothing.
*They have stolen what they have been paid money for. Therefore they don't deserve to have anything bought off them at all.*
And before any of you go "just migrate", look at the warning signs as to the ban systems.
Maybe watch FitMC's video: https://www.youtube.com/watch?v=rdoFUhd0EkI
Or maybe read the migration horror stories:
+ https://www.reddit.com/r/xbox/comments/sscoa7/does_anyone_have_an_idea_why_was_i_locked_out_of/
+ https://news.ycombinator.com/item?id=31551846
+ https://answers.microsoft.com/en-us/xbox/forum/all/im-locked-out-of-minecraft-after-migrating-my/9f1f64cb-6a2d-4cd2-abb8-0010a6bc6f31
+ https://www.reddit.com/r/Minecraft/comments/we8asn/why_microsoft_ever_since_i_was_forced_to_migrate/
+ even rekrap1 got bitten by this: https://www.youtube.com/watch?v=rzPYH98TTMM
.
.
.
.
.
.
.
.
.
.
. OLD README BELOW
# KittenOS NEO
As per usual, no warranty, not my responsibility if this breaks, or if you somehow try to run it on an actual (non-OpenComputers) computer.
@ -36,12 +84,16 @@ It would be really nice if, if I have screwed up, that you tell me how.
Preferably with a solution that fits the technological constraints.
Licensing in this project is rather fluid,
but everything in code/ is unconditionally under the following license:
but everything that is not in `repository/` is unconditionally under the following license:
This is released into the public domain.
No warranty is provided, implied or otherwise.
Copyright (C) 2018-2021 by KittenOS NEO contributors
This will be referred to as "Public Domain".
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
This will be referred to as "BSD0".
It should be considered equivalent to CC0, and this is the intent,
but it is smaller, which is somewhat important when optimizing for size.
@ -135,4 +187,3 @@ Firstly, for an uncompressed installer (just to test installer basecode), you us
Secondly, for a compressed installer, you use `package.sh`.
That rebuilds `code.tar` and `inst.lua`, and also prepares the final structure of the repository to upload.

View File

@ -1,87 +0,0 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- This program tries to crunch down the installer a bit further.
-- Specific target in mind, it has no support for string escapes.
-- It also does this:
for i = 1, 3 do
print(io.read())
end
local sequences = {
{"\n", " "},
{" ", " "},
{" #", "#"},
{"# ", "#"},
{" ,", ","},
{", ", ","},
{" (", "("},
{"( ", "("},
{" )", ")"},
{") ", ")"},
{" <", "<"},
{"< ", "<"},
{" >", ">"},
{"> ", ">"},
{" *", "*"},
{"* ", "*"},
{" ~", "~"},
{"~ ", "~"},
{" /", "/"},
{"/ ", "/"},
{" %", "%"},
{"% ", "%"},
{" =", "="},
{"= ", "="},
{" -", "-"},
{"- ", "-"},
{" +", "+"},
{"+ ", "+"},
{".. ", ".."},
{" ..", ".."},
{"\"\" ", "\"\""},
{"=0 t", "=0t"},
{">0 t", ">0t"},
{">1 t", ">1t"},
{"=1 w", "=1w"},
{"=380 l", "=380l"},
{"=127 t", "=127t"},
{"=128 t", "=128t"},
{">255 t", ">255t"},
{"=512 t", "=512t"}
}
local function pass(buffer)
local ob = ""
local smode = false
while #buffer > 0 do
if not smode then
local ds = true
while ds do
ds = false
for _, v in ipairs(sequences) do
if buffer:sub(1, #(v[1])) == v[1] then
buffer = v[2] .. buffer:sub(#(v[1]) + 1)
ds = true
end
end
end
end
local ch = buffer:sub(1, 1)
buffer = buffer:sub(2)
ob = ob .. ch
if ch == "\"" then
smode = not smode
end
end
return ob
end
local op = io.read("*a")
while true do
local np = pass(op)
if np == op then
io.write(np)
return
end
op = np
end

75
claw/C2-Format.md Normal file
View File

@ -0,0 +1,75 @@
# Claw2 Formats
## .c2l format
The .c2l format is the server package list for Claw2.
In an exception to the rule, this file only exists on the server.
It is used solely in the main package list panel.
It is a file made up of lines.
Each line contains a package name, followed by a dot, followed by the package version.
## .V.c2p format
The .V.c2p (where V is the version) format is the entire contents of the package view panel,
as text, with newlines, in UTF-8.
This is used when a package is selected in Claw2.
## .c2x format
The .c2x format is the actual installation script for the package.
It is executed by svc-claw-worker.
It's loaded in all-at-once, then it's gmatched
with the pattern [^\n]+.
A line starting with "?" represents a dependency.
A line starting & ending with "/" represents a directory creation.
And a line starting with "+" represents a file.
Package metadata is not implied.
Thus, a valid .c2x is:
```
?neo
/apps/
+apps/app-carrot.0.c2p
+apps/app-carrot.c2x
```
## Claw2 Architecture
app-claw is a very dumb client, but the only thing that'll bother
to parse a .c2l (because it has package list/search),
and the only thing that cares about version numbers.
The purpose of it is to provide an older-CLAW-style GUI.
It *may* take an argument, in which case a package panel is opened,
otherwise the main search panel is opened.
When it wants to do anything, it shuts itself down, running svc-claw-worker.
svc-app-claw-worker does all package consistency & such work.
It can only be run from app-claw, and runs app-claw after it's done.
It takes 5 arguments:
1. The target filesystem proxy.
2. The target package name. This package is viewed in app-claw after completion.
3. The source to download files from.
If nil, the package is being deleted.
Otherwise, can either be a proxy or a string.
Proxy means it's a filesystem,
string means it's an internet base.
4. Checked flag
5. primary inet card proxy, if any

38
claw/clawconv.lua Normal file
View File

@ -0,0 +1,38 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- CLAW local.lua converter. Expects to be run from outermost folder.
local target = ...
local serial = loadfile("code/libs/serial.lua")()
for k, v in pairs(serial.deserialize(io.read("*a"))) do
print(k .. "." .. v.v .. ".c2p")
print(k .. ".c2x")
local f2 = io.open(target .. k .. "." .. v.v .. ".c2p", "wb")
f2:write(k .. "\n")
f2:write(v.desc .. "\n")
f2:write("v" .. v.v .. " deps " .. table.concat(v.deps, ", "))
f2:close()
f2 = io.open(target .. k .. ".c2x", "wb")
for _, vx in ipairs(v.deps) do
f2:write("?" .. vx .. "\n")
end
for _, vx in ipairs(v.dirs) do
f2:write("/" .. vx .. "\n")
end
for _, vx in ipairs(v.files) do
f2:write("+" .. vx .. "\n")
end
f2:write("/data\n")
f2:write("/data/app-claw\n")
f2:write("+data/app-claw/" .. k .. ".c2x\n")
f2:write("+data/app-claw/" .. k .. "." .. v.v .. ".c2p\n")
f2:close()
end

View File

@ -1,9 +1,14 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
return {
["neo"] = {
desc = "KittenOS NEO Kernel & Base Libs",
v = 2,
v = 10,
deps = {
},
dirs = {
@ -18,6 +23,7 @@ return {
"libs/serial.lua",
"libs/fmttext.lua",
"libs/neoux.lua",
"libs/lineedit.lua",
"libs/braille.lua",
"libs/bmp.lua",
"libs/sys-filewrap.lua",
@ -26,7 +32,7 @@ return {
},
["neo-init"] = {
desc = "KittenOS NEO / sys-init (startup)",
v = 2,
v = 11,
deps = {
"neo",
"neo-icecap",
@ -41,7 +47,7 @@ return {
},
["neo-launcher"] = {
desc = "KittenOS NEO / Default app-launcher",
v = 2,
v = 10,
deps = {
"neo"
},
@ -54,7 +60,7 @@ return {
},
["neo-everest"] = {
desc = "KittenOS NEO / Everest (windowing)",
v = 2,
v = 10,
deps = {
"neo"
},
@ -67,7 +73,7 @@ return {
},
["neo-icecap"] = {
desc = "KittenOS NEO / Icecap",
v = 2,
v = 10,
deps = {
"neo"
},
@ -84,7 +90,7 @@ return {
},
["neo-secpolicy"] = {
desc = "KittenOS NEO / Secpolicy",
v = 2,
v = 10,
deps = {
},
dirs = {
@ -96,7 +102,7 @@ return {
},
["neo-coreapps"] = {
desc = "KittenOS NEO Core Apps",
v = 2,
v = 10,
deps = {
"neo"
},
@ -105,40 +111,39 @@ return {
},
files = {
"apps/app-textedit.lua",
"apps/app-batmon.lua",
"apps/app-control.lua",
"apps/app-taskmgr.lua"
}
},
["app-klogo"] = {
desc = "KittenOS NEO Logo shower",
v = 2,
["app-bmpview"] = {
desc = "KittenOS NEO .bmp viewer",
v = 10,
deps = {
"neo",
"app-klogo-logo"
},
dirs = {
"apps"
},
files = {
"apps/app-klogo.lua",
"apps/app-bmpview.lua",
},
},
["app-klogo-logo"] = {
["neo-logo"] = {
desc = "KittenOS NEO Logo (data)",
v = 2,
v = 10,
deps = {
},
dirs = {
"data",
"data/app-klogo"
"docs"
},
files = {
"data/app-klogo/logo.bmp"
"docs/logo.bmp"
},
},
["app-flash"] = {
desc = "KittenOS NEO EEPROM Flasher",
v = 2,
v = 10,
deps = {
"neo"
},
@ -151,7 +156,7 @@ return {
},
["app-wget"] = {
desc = "KittenOS Web Retriever",
v = 2,
v = 10,
deps = {
"neo"
},
@ -164,23 +169,35 @@ return {
},
["app-claw"] = {
desc = "KittenOS NEO Package Manager",
v = 2,
v = 10,
deps = {
"neo"
},
dirs = {
"apps",
"libs"
"apps"
},
files = {
"apps/app-claw.lua",
"libs/app-claw-core.lua",
"libs/app-claw-csi.lua"
"apps/svc-app-claw-worker.lua"
},
},
["svc-t"] = {
desc = "KittenOS NEO Terminal System",
v = 10,
deps = {
"neo"
},
dirs = {
"apps"
},
files = {
"apps/svc-t.lua",
"apps/app-luashell.lua"
},
},
["neo-meta"] = {
desc = "KittenOS NEO: Use 'All' to install to other disks",
v = 2,
v = 10,
deps = {
"neo",
"neo-init",
@ -189,9 +206,11 @@ return {
"neo-icecap",
"neo-secpolicy",
"neo-coreapps",
"app-klogo",
"neo-logo",
"app-bmpview",
"app-flash",
"app-claw",
"svc-t",
"app-wget"
},
dirs = {

328
claw/repo-claw.lua Normal file
View File

@ -0,0 +1,328 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- local.lua : CLAW Repository Metadata
-- Authors: 20kdc
return {
["app-eeprog"] = {
desc = "Example program: EEPROM programmer / copier",
v = 1,
deps = {
"neo",
"zzz-license-kosneo-bsd0"
},
dirs = {
"apps",
"docs",
"docs/repoauthors"
},
files = {
"apps/app-eeprog.lua",
"docs/repoauthors/app-eeprog"
},
},
["neo-docs"] = {
desc = "KittenOS NEO system documentation",
v = 10,
deps = {
"zzz-license-kosneo-bsd0"
},
dirs = {
"docs",
"docs/repoauthors"
},
files = {
"docs/an-intro",
"docs/kn-intro",
"docs/kn-refer",
"docs/kn-sched",
"docs/kn-perms",
"docs/us-perms",
"docs/us-nxapp",
"docs/us-setti",
"docs/us-evrst",
"docs/us-clawf",
"docs/us-termi",
"docs/ul-seria",
"docs/ul-fwrap",
"docs/ul-event",
"docs/ul-fmttx",
"docs/ul-neoux",
"docs/ul-brail",
"docs/ul-bmp__",
"docs/ul-linee",
"docs/gp-pedan",
"docs/repoauthors/neo-docs"
},
},
["app-nbox2018"] = {
desc = "NBOX2018 and NPRT2018, a 3D-printing toolbox",
v = 1,
deps = {
"neo",
"zzz-license-kosneo-bsd0"
},
dirs = {
"apps",
"docs",
"docs/repoauthors"
},
files = {
"apps/app-nbox2018.lua",
"apps/app-nprt2018.lua",
"docs/repoauthors/app-nbox2018"
},
},
["app-allmem"] = {
desc = "Near-reproducible memory usage figures",
v = 1,
deps = {
"neo",
"zzz-license-kosneo-bsd0"
},
dirs = {
"apps",
"docs",
"docs/repoauthors"
},
files = {
"apps/app-allmem.lua",
"docs/repoauthors/app-allmem"
},
},
["app-telnet"] = {
desc = "TELNET client",
v = 1,
deps = {
"neo",
"svc-t",
"zzz-license-kosneo-bsd0"
},
dirs = {
"apps",
"docs",
"docs/repoauthors"
},
files = {
"apps/app-telnet.lua",
"docs/repoauthors/app-telnet"
},
},
["svc-ghostie"] = {
desc = "Application that schedules a scare after a random time to test svc autostart",
v = 1,
deps = {
"neo",
"zzz-license-kosneo-bsd0"
},
dirs = {
"apps",
"docs",
"docs/repoauthors"
},
files = {
"apps/svc-ghostie.lua",
"apps/app-ghostcall.lua",
"docs/repoauthors/svc-ghostie"
},
},
["app-metamachine"] = {
desc = "Virtual machine",
v = 5,
deps = {
"neo",
"zzz-license-kosneo-bsd0"
},
dirs = {
"apps",
"libs",
"docs",
"docs/repoauthors",
"data",
"data/app-metamachine"
},
files = {
"apps/app-metamachine.lua",
"libs/metamachine-vgpu.lua",
"libs/metamachine-vfs.lua",
"docs/repoauthors/app-metamachine",
"data/app-metamachine/confboot.lua",
"data/app-metamachine/lucaboot.lua"
},
},
["app-pclogix-upload"] = {
desc = "paste.pc-logix.com text uploader",
v = 1,
deps = {
"neo",
"zzz-license-kosneo-bsd0"
},
dirs = {
"apps",
"docs",
"docs/repoauthors"
},
files = {
"apps/app-pclogix-upload.lua",
"docs/repoauthors/app-pclogix-upload"
},
},
["app-rsctrl"] = {
desc = "Redstone control",
v = 1,
deps = {
"neo",
"zzz-license-kosneo-bsd0"
},
dirs = {
"apps",
"docs",
"docs/repoauthors"
},
files = {
"apps/app-rsctrl.lua",
"docs/repoauthors/app-rsctrl"
},
},
["app-nbcompose"] = {
desc = "Music player/composer using the NBS format",
v = 2,
deps = {
"neo",
"lib-knbs",
"zzz-license-kosneo-bsd0"
},
dirs = {
"apps",
"docs",
"docs/repoauthors"
},
files = {
"apps/app-nbcompose.lua",
"docs/repoauthors/app-nbcompose"
},
},
["app-tapedeck"] = {
desc = "Computronics Tape Drive interface",
v = 3,
deps = {
"neo",
"zzz-license-kosneo-bsd0"
},
dirs = {
"apps",
"docs",
"docs/repoauthors"
},
files = {
"apps/app-tapedeck.lua",
"docs/repoauthors/app-tapedeck"
},
},
["app-launchbar"] = {
desc = "Application launcher bar",
v = 0,
deps = {
"neo",
"zzz-license-pd"
},
dirs = {
"apps",
"docs",
"docs/repoauthors"
},
files = {
"apps/app-launchbar.lua",
"docs/repoauthors/app-launchbar"
},
},
["app-slaunch"] = {
desc = "Searching launcher",
v = 0,
deps = {
"neo",
"zzz-license-pd"
},
dirs = {
"apps",
"docs",
"docs/repoauthors"
},
files = {
"apps/app-slaunch.lua",
"docs/repoauthors/app-slaunch"
},
},
-- libraries
["lib-knbs"] = {
desc = "NBS reader/writer library",
v = 2,
deps = {
"zzz-license-kosneo-bsd0"
},
dirs = {
"libs",
"docs",
"docs/repoauthors"
},
files = {
"libs/knbs.lua",
"docs/repoauthors/lib-knbs"
},
},
["svc-virtudev"] = {
desc = "a clone of vcomponent",
v = 2,
deps = {
"neo",
"zzz-license-kosneo-bsd0"
},
dirs = {
"apps",
"docs",
"docs/repoauthors"
},
files = {
"apps/svc-virtudev.lua",
"apps/app-vdrslamp.lua",
"docs/us-virtu",
"docs/repoauthors/svc-virtudev"
},
},
-- licenses (MUST BE IMMUTABLE)
["zzz-license-pd"] = {
desc = "license file 'Public Domain'",
v = 0,
deps = {
},
dirs = {
"docs",
"docs/licensing",
"docs/repoauthors"
},
files = {
"docs/licensing/Public Domain",
"docs/repoauthors/zzz-license-pd"
},
},
["zzz-license-kosneo-bsd0"] = {
desc = "license file 'KittenOS NEO BSD0'",
v = 0,
deps = {
},
dirs = {
"docs",
"docs/licensing",
"docs/repoauthors"
},
files = {
"docs/licensing/KittenOS NEO BSD0",
"docs/repoauthors/zzz-license-kosneo-bsd0"
},
}
}

View File

@ -1,17 +0,0 @@
local merges = {...}
neo = {
wrapMeta = function (x)
return x
end
}
local serial = loadfile("code/libs/serial.lua")()
local repo = {}
for _, v in ipairs(merges) do
local f = io.open(v, "rb")
local fd = f:read("*a")
f:close()
for k, v in pairs(serial.deserialize(fd)) do
repo[k] = v
end
end
io.write(serial.serialize(repo))

78
code/apps/app-batmon.lua Normal file
View File

@ -0,0 +1,78 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- app-batmon: Still not batman.
-- Port of the original 'batmon.lua' from KittenOS Legacy.
local window = neo.requireAccess("x.neo.pub.window", "window")(10, 2)
-- OCE/s, OCE at last check, uptime of last timer set, uptime of last check
local lastChange, lastValue, lastTimer, lpTimer = 0
local usage = {
"[####]:",
"[###:]:",
"[### ]:",
"[##: ]:",
"[## ]:",
"[#: ]:",
"[# ]:",
"[: ]:",
"[ ]:",
"WARNING"
}
local function getText(y)
if y == 2 then
if not lastChange then
return "Wait..."
end
local ind = "Dc. "
local wc = lastChange
local wv = os.energy()
if wc > 0 then
wc = -wc
wv = os.maxEnergy() - wv
ind = "Ch. "
end
local m = math.floor((wv / -wc) / 60)
return ind .. m .. "m"
end
local dec = os.energy() / os.maxEnergy()
-- dec is from 0 to 1.
local potential = math.floor(dec * #usage)
if potential < 0 then potential = 1 end
if potential >= #usage then potential = #usage - 1 end
return usage[#usage - potential]
end
local function update()
local nv = os.energy()
if lastValue then
lastChange = (nv - lastValue) / (os.uptime() - lpTimer)
end
lpTimer = os.uptime()
lastValue = nv
lastTimer = os.uptime() + 10
if lastChange then
if lastChange > 10 then
lastTimer = lastTimer - 9
end
end
neo.scheduleTimer(lastTimer)
window.setSize(10, 2)
end
update()
while true do
local ev, a, b, c = coroutine.yield()
if ev == "x.neo.pub.window" then
if b == "close" then
return
elseif b == "line" then
local tx = getText(c):sub(1, 10)
window.span(1, c, tx .. (" "):rep(10 - #tx), 0xFFFFFF, 0)
end
elseif ev == "k.timer" then
update()
end
end

View File

@ -1,12 +1,15 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
local event = require("event")(neo)
local neoux = require("neoux")(event, neo)
local braille = require("braille")
local bmp = require("bmp")
local icecap = neo.requireAccess("x.neo.pub.base", "loadimg")
local file = icecap.open("/logo.bmp", false)
local file = neoux.fileDialog(false)
local header = file.read(bmp.headerMinSzBMP)
local palette = ""
@ -61,9 +64,6 @@ lcWidth = bitmap.dsSpan
local running = true
local function decodeRGB(rgb, igp, col)
if igp and bitmap.bpp > 24 then
rgb = math.floor(rgb / 256)
end
local r, g, b = math.floor(rgb / 65536) % 256, math.floor(rgb / 256) % 256, rgb % 256
-- the new KittenOS NEO logo is 'sensitive' to dithering, so disable it
if not col then
@ -82,6 +82,8 @@ local fp = neoux.tcwindow(bW, bH, {
braille.new(1, 1, bW, bH, {
selectable = true,
get = function (window, x, y, bg, fg, selected, colour)
if x > bitmap.width then return 0, 0, 0 end
if y > bitmap.height then return 0, 0, 0 end
if bitmap.ignoresPalette then
return decodeRGB(bitmap.getPixel(x - 1, y - 1, 0), true, colour)
end
@ -95,9 +97,9 @@ end, 0xFFFFFF, 0)
neoux.create(bW, bH, nil, function (w, t, r, ...)
if t == "focus" then
if r then
if r and not bitmap.ignoresPalette then
local pal = {}
for i = 0, 15 do
for i = 0, math.min(15, bitmap.paletteCol - 1) do
pal[i + 1] = bitmap.getPalette(i)
end
w.recommendPalette(pal)

View File

@ -1,148 +1,84 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- app-claw: Package manager.
local ldrPkg, _, tgtPkg = ...
-- libs & such
local event = require("event")(neo)
local neoux, err = require("neoux")
if not neoux then error(err) end
neoux = neoux(event, neo)
local claw = require("app-claw-core")()
local clawcsi = require("app-claw-csi")
local neoux = require("neoux")(event, neo)
local source = "http://20kdc.duckdns.org/neo/"
local disks = neo.requireAccess("c.filesystem", "searching disks for packages")
local primaryDisk = disks.primary
local primaryINet = neo.requestAccess("c.internet")
if primaryINet then primaryINet = primaryINet.list()() end
--
local function yielder()
-- slightly dangerous, but what can we do?
pcall(event.sleepTo, os.uptime() + 0.05)
end
local function download(url, cb)
if not primaryINet then return nil, "no internet" end
local req, err = primaryINet.request(source .. url)
if not req then
cb(nil)
return nil, "dlR/" .. tostring(err)
end
-- OpenComputers#535
req.finishConnect()
while true do
local n, n2 = req.read(neo.readBufSize)
local o, r = cb(n)
if not o then
req.close()
return nil, r
end
if not n then
req.close()
if n2 then
return nil, n2
else
break
end
local function readFile(src, url, ocb)
local buf = ""
local function cb(data)
if not data then
ocb(buf)
else
if n == "" then
yielder()
end
buf = buf .. data
buf = buf:gsub("[^\n]*\n", function (t)
ocb(t:sub(1, -2))
return ""
end)
end
end
return true
end
local function fsSrc(disk)
return function (url, cb)
local h, e = disk.open(url, "rb")
if not h then cb(nil) return nil, tostring(e) end
local c = ""
while c do
c = disk.read(h, neo.readBufSize)
local o, r = cb(c)
if not o then return nil, r end
end
disk.close(h)
return true
end
end
local function fsDst(disk)
return {function (url)
local h, e = disk.open(url, "wb")
if not h then return nil, tostring(e) end
return function (d)
local ok, r = true
if d then
ok, r = disk.write(h, d)
if type(src) == "string" then
assert(primaryINet, "no internet")
local req, err = primaryINet.request(src .. url)
assert(req, err)
-- OpenComputers#535
req.finishConnect()
while true do
local n, n2 = req.read(neo.readBufSize)
cb(n)
if not n then
req.close()
if n2 then
error(n2)
else
cb(nil)
break
end
else
disk.close(h)
if n == "" then
-- slightly dangerous, but what can we do?
pcall(event.sleepTo, os.uptime() + 0.05)
end
end
if not ok then return nil, tostring(r) end
return true
end
end, disk.makeDirectory, disk.exists, disk.isDirectory, disk.remove, disk.rename}
end
local function checked(...)
local res, res2, err = pcall(...)
if not res then
neoux.startDialog(tostring(res2), "error!", true)
elseif not res2 then
neoux.startDialog(tostring(err), "failed!", true)
else
return res2
if url == "data/app-claw/local.c2l" then
for _, v in ipairs(src.list("data/app-claw/")) do
ocb(v)
end
return
end
local h, e = src.open(url, "rb")
assert(h, e)
repeat
local c = src.read(h, neo.readBufSize)
cb(c)
until not c
src.close(h)
end
end
-- Beginning Of The App (well, the actual one)
local genCurrent, genPrimary, genPackage, primaryWindow
local running = true
-- primary
local primarySearchTx = ""
local primaryPage = 1
local primaryList = {}
local primaryNextMinus = false
-- package
local packageLock = nil
local packageId = "FIXME"
local function describe(pkg)
local weHave = claw.getInfo(pkg, "local")
local theyHave = claw.getInfo(pkg, "local")
local someoneHas = claw.getInfo(pkg, nil, true)
if weHave then
if theyHave.v > weHave.v then
return pkg .. " [v" .. weHave.v .. "!]"
end
if someoneHas.v < weHave.v then
return pkg .. " (v" .. weHave.v .. ") R<"
end
return pkg .. " (v" .. weHave.v .. ")"
end
return pkg
end
local function primaryWindowRegenCore()
local gen, gens = genCurrent()
return 25, 12, "claw", neoux.tcwindow(25, 12, gen, function (w)
w.close()
running = false
end, 0xFF8F00, 0, gens)
end
local function primaryWindowRegen()
primaryWindow.reset(primaryWindowRegenCore())
end
-- Sources
local sources = {}
local sourceList = {}
-- Use all non-primary filesystems
local disks = neo.requireAccess("c.filesystem", "searching disks for packages")
local primaryDisk = disks.primary
for pass = 1, 3 do
for v in disks.list() do
local nam = nil
@ -154,11 +90,8 @@ for pass = 1, 3 do
nam = v.address
end
if nam then
local ok, r = clawcsi(claw, nam, fsSrc(v), (not v.isReadOnly()) and fsDst(v))
if not ok and nam == "local" then
claw.unlock()
error(r)
end
sources[nam] = v
table.insert(sourceList, nam)
end
end
end
@ -167,19 +100,105 @@ end
disks = nil
if primaryINet then
checked(clawcsi, claw, "inet", download)
sources["inet"] = "http://20kdc.duckdns.org/neo/"
table.insert(sourceList, "inet")
end
clawcsi = nil
-- List scanning for package window
primaryList = claw.getList()
local function scanList(content)
local lst = {}
local lst2 = {}
for k, v in pairs(sources) do
local ok, err = pcall(readFile, v, "data/app-claw/local.c2l", function (l)
if l:sub(-4) == ".c2p" then
local lt, ltv = l:sub(1, -5)
ltv = lt:match("%.[0-9]+$")
if ltv and l:find(content, 1, true) then
lt = lt:sub(1, -(#ltv + 1))
lst2[lt] = true
end
end
end)
if (not ok) and ((k == "inet") or (k == "local")) then
neoux.startDialog(tostring(err), k)
end
end
for k, v in pairs(lst2) do
table.insert(lst, k)
end
table.sort(lst)
return lst
end
-- Beginning Of The App (well, the actual one)
local genCurrent, genPrimary, genPackage, primaryWindow
local running = true
-- primary
local primarySearchTx = ""
local primaryPage = 1
local primaryList = scanList("")
local primaryNextMinus = false
-- package
local packageLock = nil
local packageId = "FIXME"
local function describe(pkgs)
local lowestV, highestV, myV = {}, {}, {}
for pk, pv in ipairs(pkgs) do
lowestV[pk] = math.huge
highestV[pk] = -math.huge
end
for k, v in pairs(sources) do
pcall(readFile, v, "data/app-claw/local.c2l", function (l)
local lp = l:match("%.[0-9]+%.c2p$")
for pk, pkg in ipairs(pkgs) do
if lp and l:sub(1, -(#lp + 1)) == pkg then
local v = tonumber(lp:sub(2, -5))
if k == "local" then
myV[pk] = v
end
lowestV[pk] = math.min(lowestV[pk], v)
highestV[pk] = math.max(highestV[pk], v)
end
end
end)
end
for pk, pkg in ipairs(pkgs) do
if lowestV[pk] == math.huge then
pkgs[pk] = pkg .. " (ERR)"
elseif myV[pk] then
if highestV[pk] > myV[pk] then
pkgs[pk] = pkg .. " [v" .. myV[pk] .. "!]"
elseif lowestV[pk] < myV[pk] then
pkgs[pk] = pkg .. " (v" .. myV[pk] .. ") R<"
else
pkgs[pk] = pkg .. " (v" .. myV[pk] .. ")"
end
end
end
end
local function primaryWindowRegenCore()
local gen, gens = genCurrent()
return 25, 14, "claw", neoux.tcwindow(25, 14, gen, function (w)
w.close()
running = false
end, 0xFF8F00, 0, gens)
end
local function primaryWindowRegen()
primaryWindow.reset(primaryWindowRegenCore())
end
-- Sections
function genPrimary()
local minus = (primaryNextMinus and 3) or nil
primaryNextMinus = false
local pgs = 10
local pgs = 12
local pages = math.ceil(#primaryList / pgs)
local elems = {
neoux.tcbutton(23, 1, "+", function (w)
@ -198,14 +217,22 @@ function genPrimary()
end)
}
local base = (primaryPage - 1) * pgs
local pkgs = {}
for i = 1, pgs do
local ent = primaryList[base + i]
if ent then
local enttx = describe(ent)
pkgs[i] = ent
end
end
describe(pkgs)
for i = 1, pgs do
local ent = primaryList[base + i]
if ent then
local enttx = pkgs[i]
table.insert(elems, neoux.tcbutton(1, i + 1, unicode.safeTextFormat(enttx), function (w)
-- FREE UP MEMORY NOW
elems = {}
w.reset(25, 12, "claw", function (ev)
w.reset(25, 14, "claw", function (ev)
if ev == "close" then
w.close()
running = false
@ -217,22 +244,13 @@ function genPrimary()
end))
end
end
table.insert(elems, neoux.tcfield(1, 12, 16, function (s)
table.insert(elems, neoux.tcfield(1, 14, 16, function (s)
if s then primarySearchTx = s end
return primarySearchTx
end))
table.insert(elems, neoux.tcbutton(17, 12, "Search!", function (w)
local n = {}
for _, v in ipairs(claw.getList()) do
for i = 1, #v do
if v:sub(i, i + #primarySearchTx - 1) == primarySearchTx then
table.insert(n, v)
break
end
end
end
table.insert(elems, neoux.tcbutton(17, 14, "Search!", function (w)
primaryPage = 1
primaryList = n
primaryList = scanList(primarySearchTx)
primaryWindowRegen()
end))
return elems, minus
@ -245,49 +263,38 @@ local function packageGetBB(src, lclI, srcI, srcW)
if srcI and srcW then
table.insert(buttons, {
"Del",
function ()
if packageLock then return end
packageLock = ""
checked(claw.remove, src, packageId, true)
packageLock = nil
primaryWindowRegen()
function (w)
w.close()
running = false
neo.executeAsync("svc-app-claw-worker", sources[src], packageId, nil, src == "local", primaryINet)
end
})
end
if srcI and ((not lclI) or (lclI.v < srcI.v)) then
if srcI and ((not lclI) or (lclI < srcI)) then
table.insert(buttons, {
"Get",
function ()
if packageLock then return end
packageLock = "installing from " .. src
primaryWindowRegen()
checked(claw.installTo, "local", packageId, src, true, yielder)
packageLock = nil
primaryWindowRegen()
function (w)
w.close()
running = false
neo.executeAsync("svc-app-claw-worker", sources["local"], packageId, sources[src], true, primaryINet)
end
})
end
if srcW and lclI and not srcI then
table.insert(buttons, {
"All",
function ()
if packageLock then return end
packageLock = "storing w/ dependencies at " .. src
primaryWindowRegen()
checked(claw.installTo, src, packageId, "local", true, yielder)
packageLock = nil
primaryWindowRegen()
function (w)
w.close()
running = false
neo.executeAsync("svc-app-claw-worker", sources[src], packageId, sources["local"], true, primaryINet)
end
})
table.insert(buttons, {
"Put",
function ()
if packageLock then return end
packageLock = "storing at " .. src
primaryWindowRegen()
checked(claw.installTo, src, packageId, "local", false, yielder)
packageLock = nil
primaryWindowRegen()
function (w)
w.close()
running = false
neo.executeAsync("svc-app-claw-worker", sources[src], packageId, sources["local"], false, primaryINet)
end
})
end
@ -306,26 +313,50 @@ function genPackage()
-- inet v21 <pull>
-- dir v22 <pull> <push>
-- crockett <push>
local info = claw.getInfo(packageId)
local infoL = claw.getInfo(packageId, "local")
local sourceVers = {}
local c2pSrc = "local"
local c2pVer = -1
for k, v in pairs(sources) do
local ok, err = pcall(readFile, v, "data/app-claw/local.c2l", function (l)
local lp = l:match("%.[0-9]+%.c2p$")
if lp and l:sub(1, -(#lp + 1)) == packageId then
sourceVers[k] = tonumber(lp:sub(2, -5))
if c2pVer < sourceVers[k] then
c2pSrc = k
c2pVer = sourceVers[k]
end
end
end)
end
if sourceVers["local"] then
c2pSrc = "local"
c2pVer = sourceVers["local"]
end
local text = ""
local ok = pcall(readFile, sources[c2pSrc], "data/app-claw/" .. packageId .. "." .. c2pVer .. ".c2p", function (l)
text = text .. l .. "\n"
end)
if not ok then
text = packageId .. "\nUnable to read v" .. c2pVer .. " c2p from: " .. c2pSrc
end
local elems = {
neoux.tcrawview(1, 1, neoux.fmtText(unicode.safeTextFormat(packageId .. "\n" .. info.desc .. "\nv" .. info.v .. " deps " .. table.concat(info.deps, ", ")), 25)),
neoux.tcrawview(1, 1, neoux.fmtText(unicode.safeTextFormat(text), 25)),
neoux.tcbutton(20, 1, "Back", function ()
if packageLock then return end
genCurrent = genPrimary
primaryWindowRegen()
end)
}
for k, v in ipairs(claw.sourceList) do
local lI = claw.getInfo(packageId, v[1])
local row = 12 + k - #(claw.sourceList)
for k, v in ipairs(sourceList) do
local row = 14 + k - #sourceList
local pfx = " "
if lI then
pfx = "v" .. string.format("%04i", lI.v) .. " "
if sourceVers[v] then
pfx = "v" .. string.format("%04i", sourceVers[v]) .. " "
end
table.insert(elems, neoux.tcrawview(1, row, {neoux.pad(pfx .. v[1], 14, false, true)}))
table.insert(elems, neoux.tcrawview(1, row, {neoux.pad(pfx .. v, 14, false, true)}))
local col = 26
for _, bv in ipairs(packageGetBB(v[1], infoL, lI, v[2])) do
local srcW = type(sources[v]) ~= "string"
for _, bv in ipairs(packageGetBB(v, sourceVers["local"], sourceVers[v], srcW)) do
local b = neoux.tcbutton(col, row, bv[1], bv[2])
col = col - b.w
b.x = col
@ -337,10 +368,14 @@ end
--
genCurrent = genPrimary
if ldrPkg == "svc-app-claw-worker" and tgtPkg then
packageId = tgtPkg
genCurrent = genPackage
else
genCurrent = genPrimary
end
primaryWindow = neoux.create(primaryWindowRegenCore())
while running do
event.pull()
end
claw.unlock()

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- app-control: Settings changer
local settings = neo.requireAccess("x.neo.sys.manage", "management")
@ -226,7 +230,7 @@ window = neoux.create(currentGen())
while running do
local src, id, k, v = event.pull()
if src == "x.neo.sys.manage" then
if id == "set_setting" then
if id == "set_setting" and currentGen ~= logGen then
window.reset(currentGen())
end
end

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
local event = require("event")(neo)
local neoux = require("neoux")(event, neo)
@ -20,12 +24,14 @@ local busy = false
local regenCore
local function regenLabeller(set, get, wd)
local tx = get()
return wd, 2, nil, neoux.tcwindow(wd, 1, {
neoux.tcfield(1, 1, wd, function (nt)
if nt then
tx = nt
set(nt)
end
return get()
return tx
end)
}, function (w)
busy = false
@ -39,7 +45,7 @@ function regenCore()
for v in eeprom.list() do
local lbl = unicode.safeTextFormat(v.getLabel())
table.insert(elems, neoux.tcrawview(1, l, {
v.address:sub(1, 8) .. " " .. lbl
require("fmttext").pad(v.address:sub(1, 8) .. " " .. lbl, 25, false, true)
}))
table.insert(elems, neoux.tcbutton(1, l + 1, "get", function (window)
if busy then return end

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- app-fm: dummy app to start FM
neo.requestAccess("x.neo.pub.base").showFileDialogAsync(nil)

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- app-launcher: The launcher
local event = require("event")(neo)

168
code/apps/app-luashell.lua Normal file
View File

@ -0,0 +1,168 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
local _, _, termId = ...
local ok = pcall(function ()
assert(string.sub(termId, 1, 12) == "x.neo.pub.t/")
end)
local termClose
if not ok then
termId = nil
assert(neo.executeAsync("svc-t", function (res)
termId = res.access
termClose = res.close
neo.scheduleTimer(0)
end, "luashell"))
while not termId do
coroutine.yield()
end
end
TERM = neo.requireAccess(termId, "terminal")
-- Using event makes it easier for stuff
-- within the shell to not spectacularly explode.
event = require("event")(neo)
local alive = true
event.listen("k.procdie", function (_, _, pid)
if pid == TERM.pid then
alive = false
end
end)
TERM.write([[
KittenOS NEO Shell Usage Notes
Prefixing = is an alias for 'return '.
io.read(): Reads a line.
print: 'print with table dumping' impl.
TERM: Your terminal. (see us-termi doc.)
os.execute(): launch terminal apps!
tries '*', 'sys-t-*', 'svc-t-*', 'app-*'
example: os.execute("luashell")
os.exit(): quit the shell
=listCmdApps(): -t- (terminal) apps
event: useful for setting up listeners
without breaking shell functionality
]])
function listCmdApps()
local apps = {}
for _, v in ipairs(neo.listApps()) do
if v:sub(4, 6) == "-t-" then
table.insert(apps, v)
end
end
return apps
end
local function vPrint(slike, ...)
local s = {...}
if #s > 1 then
for i = 1, #s do
if i ~= 1 then TERM.write("\t") end
vPrint(slike, s[i])
end
elseif slike and type(s[1]) == "string" then
TERM.write("\"" .. s[1] .. "\"")
elseif type(s[1]) ~= "table" then
TERM.write(tostring(s[1]))
else
TERM.write("{")
for k, v in pairs(s[1]) do
TERM.write("[")
vPrint(true, k)
TERM.write("] = ")
vPrint(true, v)
TERM.write(", ")
end
TERM.write("}")
end
end
print = function (...)
vPrint(false, ...)
TERM.write("\r\n")
end
local ioBuffer = ""
io = {
read = function ()
while alive do
local pos = ioBuffer:find("\n")
if pos then
local line = ioBuffer:sub(1, pos):gsub("\r", "")
ioBuffer = ioBuffer:sub(pos + 1)
return line
end
local e = {event.pull()}
if e[1] == TERM.id then
if e[2] == "data" then
ioBuffer = ioBuffer .. e[3]
end
end
end
end,
write = function (s) TERM.write(s) end
}
local originalOS = os
os = setmetatable({}, {
__index = originalOS
})
function os.exit()
alive = false
end
function os.execute(x, ...)
local subPid = neo.executeAsync(x, TERM.id, ...)
if not subPid then
subPid = neo.executeAsync("sys-t-" .. x, TERM.id, ...)
end
if not subPid then
subPid = neo.executeAsync("svc-t-" .. x, TERM.id, ...)
end
if not subPid then
subPid = neo.executeAsync("app-" .. x, TERM.id, ...)
end
if not subPid then
error("cannot find " .. x)
end
while true do
local e = {event.pull()}
if e[1] == "k.procdie" then
if e[3] == subPid then
return
end
end
end
end
while alive do
TERM.write("> ")
local code = io.read()
if code then
local ok, err = pcall(function ()
if code:sub(1, 1) == "=" then
code = "return " .. code:sub(2)
end
print(assert(load(code))())
end)
if not ok then
TERM.write(tostring(err) .. "\r\n")
end
end
end
if termClose then
termClose()
end

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- app-taskmgr: Task manager
-- a-hello : simple test program for Everest.

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- 'neolithic': Text Editor
-- This was textedit (femto) from KittenOS 'ported' to NEO.
@ -11,7 +15,7 @@ local lines = {
"F5, F6, ^←: Copy, Paste, Delete Line",
-- These two are meant to replace similar functionality in GNU Nano
-- (which I consider the best console text editor out there - Neolithic is an *imitation* and a poor one at that),
-- except fixing a UI flaw by instead making J responsible for resetting the append flag,
-- except fixing a UI flaw by instead adding a visible way to reset the append flag,
-- so the user can more or less arbitrarily mash together lines
"F7: Reset 'append' flag for Cut Lines",
"F8: Cut Line(s)",
@ -30,33 +34,17 @@ local clipsrc = neo.requireAccess("x.neo.pub.globals", "clipboard")
local windows = neo.requireAccess("x.neo.pub.window", "windows")
local files = neo.requireAccess("x.neo.pub.base", "files").showFileDialogAsync
local lineEdit = require("lineedit")
local cursorX = 1
local cursorY = math.ceil(#lines / 2)
local cFlash = true
local ctrlFlag = false
local ctrlFlag, appendFlag
local dialogLock = false
local appendFlag = false
local sW, sH = 37, #lines + 2
local window = windows(sW, sH)
local filedialog = nil
local flush
local function splitCur()
local s = lines[cursorY]
local st = unicode.sub(s, 1, cursorX - 1)
local en = unicode.sub(s, cursorX)
return st, en
end
local function clampCursorX()
local s = lines[cursorY]
if unicode.len(s) < (cursorX - 1) then
cursorX = unicode.len(s) + 1
return true
end
return false
end
local cbs = {}
local function fileDialog(writing, callback)
@ -140,31 +128,14 @@ local function getline(y)
-- rX is difficult!
local rX = 1
local Xthold = math.max(1, math.floor(sW / 2) - 1)
local _, cursorXP = unicode.safeTextFormat(lines[cursorY], cursorX)
local cLine, cursorXP = unicode.safeTextFormat(lines[cursorY], cursorX)
rX = (math.max(0, math.floor(cursorXP / Xthold) - 1) * Xthold) + 1
local line = lines[rY]
if not line then
return ("¬"):rep(sW)
end
line = unicode.safeTextFormat(line)
-- <alter RX here by 1 if needed>
local tl = unicode.sub(line, rX, rX + sW - 1)
cursorXP = (cursorXP - rX) + 1
if cFlash then
if rY == cursorY then
if cursorXP >= 1 then
if cursorXP <= sW then
local start = unicode.sub(tl, 1, cursorXP - 1)
local endx = unicode.sub(tl, cursorXP + 1)
tl = start .. "_" .. endx
end
end
end
end
while unicode.len(tl) < sW do
tl = tl .. " "
end
return tl
return lineEdit.draw(sW, line, rY == cursorY and cursorXP, rX)
end
local function delLine()
local contents = lines[cursorY]
@ -180,22 +151,7 @@ local function delLine()
end
return contents
end
-- add a single character
local function putLetter(ch)
if ch == "\r" then
local a, b = splitCur()
lines[cursorY] = a
table.insert(lines, cursorY + 1, b)
cursorY = cursorY + 1
cursorX = 1
return
end
local a, b = splitCur()
a = a .. ch
lines[cursorY] = a .. b
cursorX = unicode.len(a) + 1
end
local function key(ka, kc, down)
local function key(ks, kc, down)
if dialogLock then
return false
end
@ -213,23 +169,23 @@ local function key(ka, kc, down)
sH = 1
end
sW, sH = window.setSize(sW, sH)
end
if kc == 208 then -- Down
return false
elseif kc == 208 then -- Down
sH = sH + 1
sW, sH = window.setSize(sW, sH)
end
if kc == 203 then -- Left
return false
elseif kc == 203 then -- Left
sW = sW - 1
if sW == 0 then
sW = 1
end
sW, sH = window.setSize(sW, sH)
end
if kc == 205 then -- Right
return false
elseif kc == 205 then -- Right
sW = sW + 1
sW, sH = window.setSize(sW, sH)
end
if kc == 14 then -- ^Backspace
return false
elseif kc == 14 then -- ^Backspace
delLine()
return true
end
@ -245,10 +201,9 @@ local function key(ka, kc, down)
if cursorY < 1 then
cursorY = 1
end
clampCursorX()
cursorX = lineEdit.clamp(lines[cursorY], cursorX)
return true
end
if kc == 208 or kc == 209 then -- Go down one - go down page
elseif kc == 208 or kc == 209 then -- Go down one - go down page
local moveAmount = 1
if kc == 209 then
moveAmount = math.floor(sH / 2)
@ -257,39 +212,7 @@ local function key(ka, kc, down)
if cursorY > #lines then
cursorY = #lines
end
clampCursorX()
return true
end
if kc == 203 then
if cursorX > 1 then
cursorX = cursorX - 1
else
if cursorY > 1 then
cursorY = cursorY - 1
cursorX = unicode.len(lines[cursorY]) + 1
else
return false
end
end
return true
end
if kc == 205 then
cursorX = cursorX + 1
if clampCursorX() then
if cursorY < #lines then
cursorY = cursorY + 1
cursorX = 1
end
end
return true
end
-- Extra Non-Control Keys
if kc == 199 then
cursorX = 1
return true
end
if kc == 207 then
cursorX = unicode.len(lines[cursorY]) + 1
cursorX = lineEdit.clamp(lines[cursorY], cursorX)
return true
end
-- Major Actions
@ -298,17 +221,16 @@ local function key(ka, kc, down)
cursorX = 1
cursorY = 1
return true
end
if kc == 61 then -- F3
elseif kc == 61 then -- F3
startLoad()
end
if kc == 62 then -- F4
return false
elseif kc == 62 then -- F4
startSave()
end
if kc == 63 then -- F5
return false
elseif kc == 63 then -- F5
clipsrc.setSetting("clipboard", lines[cursorY])
end
if kc == 64 then -- F6
return false
elseif kc == 64 then -- F6
local tx = clipsrc.getSetting("clipboard") or ""
local txi = tx:find("\n")
local nt = {}
@ -322,11 +244,10 @@ local function key(ka, kc, down)
table.insert(lines, cursorY, v)
end
return true
end
if kc == 65 then -- F7
elseif kc == 65 then -- F7
appendFlag = false
end
if kc == 66 then -- F8
return false
elseif kc == 66 then -- F8
if appendFlag then
local base = clipsrc.getSetting("clipboard")
clipsrc.setSetting("clipboard", base .. "\n" .. delLine())
@ -337,29 +258,37 @@ local function key(ka, kc, down)
return true
end
end
-- Letters
if ka == 8 or kc == 211 then
if cursorX == 1 then
if cursorY == 1 then
return false
end
local l = table.remove(lines, cursorY)
cursorY = cursorY - 1
cursorX = unicode.len(lines[cursorY]) + 1
lines[cursorY] = lines[cursorY] .. l
else
local a, b = splitCur()
a = unicode.sub(a, 1, unicode.len(a) - 1)
lines[cursorY] = a.. b
cursorX = cursorX - 1
end
return true
-- LEL Keys
local lT, lC, lX = lineEdit.key(ks, kc, lines[cursorY], cursorX)
if lT then
lines[cursorY] = lT
end
if ka ~= 0 then
putLetter(unicode.char(ka))
return true
if lC then
cursorX = lC
end
return false
if lX == "l<" and cursorY > 1 then
cursorY = cursorY - 1
cursorX = unicode.len(lines[cursorY]) + 1
elseif lX == "l>" and cursorY < #lines then
cursorY = cursorY + 1
cursorX = 1
elseif lX == "w<" and cursorY ~= 1 then
local l = table.remove(lines, cursorY)
cursorY = cursorY - 1
cursorX = unicode.len(lines[cursorY]) + 1
lines[cursorY] = lines[cursorY] .. l
elseif lX == "w>" and cursorY ~= #lines then
local l = table.remove(lines, cursorY)
cursorX = unicode.len(l) + 1
lines[cursorY] = l .. lines[cursorY]
elseif lX == "nl" then
local line = lines[cursorY]
lines[cursorY] = unicode.sub(line, 1, cursorX - 1)
table.insert(lines, cursorY + 1, unicode.sub(line, cursorX))
cursorX = 1
cursorY = cursorY + 1
end
return true
end
flush = function ()
@ -368,16 +297,9 @@ flush = function ()
end
end
neo.scheduleTimer(os.uptime() + 0.5)
while true do
local e = {coroutine.yield()}
if e[1] == "k.timer" then
cFlash = not cFlash
local csY = math.ceil(sH / 2)
window.span(1, csY, getline(csY), 0xFFFFFF, 0)
neo.scheduleTimer(os.uptime() + 0.5)
elseif e[1] == "x.neo.pub.window" then
if e[1] == "x.neo.pub.window" then
if e[2] == window.id then
if e[3] == "line" then
window.span(1, e[4], getline(e[4]), 0xFFFFFF, 0)
@ -388,10 +310,10 @@ while true do
local csY = math.ceil(sH / 2)
local nY = math.max(1, math.min(#lines, (math.floor(e[5]) - csY) + cursorY))
cursorY = nY
clampCursorX()
cursorX = lineEdit.clamp(lines[cursorY], cursorX)
flush()
elseif e[3] == "key" then
if key(e[4], e[5], e[6]) then
if key(e[4] ~= 0 and unicode.char(e[4]), e[5], e[6]) then
flush()
end
elseif e[3] == "focus" then
@ -406,7 +328,7 @@ while true do
if c == "\n" then
c = "\r"
end
putLetter(c)
key(c, 0, true)
end
end
flush()

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
local event = require("event")(neo)
local neoux = require("neoux")(event, neo)

View File

@ -0,0 +1,159 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- svc-app-claw-worker: Who stays stays. Who goes goes.
local callerPkg, _, destProx, packageId, downloadSrc, checked, primaryINet = ...
if callerPkg ~= "app-claw" then error("Internal process for app-claw's bidding.") end
-- This 'mutex' remains active as long as the process does.
neo.requireAccess("r.svc.app-claw-worker", "CLAW mutex")
local function wrapLines(ocb)
local buf = ""
return function (data)
if not data then
ocb(buf)
else
buf = buf .. data
buf = buf:gsub("[^\n]*\n", function (t)
ocb(t:sub(1, -2))
return ""
end)
end
end
end
local function download(url, cb, src)
if type(src) == "string" then
assert(primaryINet, "no internet")
local req, err = primaryINet.request(src .. url)
assert(req, err)
-- OpenComputers#535
req.finishConnect()
while true do
local n, n2 = req.read(neo.readBufSize)
cb(n)
if not n then
req.close()
if n2 then
error(n2)
else
cb(nil)
break
end
else
if n == "" then
neo.scheduleTimer(os.uptime() + 0.05)
while true do
local res = coroutine.yield()
if res == "k.timer" then break end
end
end
end
end
else
local h, e = src.open(url, "rb")
assert(h, e)
repeat
local c = src.read(h, neo.readBufSize)
cb(c)
until not c
src.close(h)
end
end
local opInstall, opRemove
function opInstall(packageId, checked)
local gback = {} -- the ultimate strategy
local gdir = {}
local preinstall = {}
download("data/app-claw/" .. packageId .. ".c2x", wrapLines(function (l)
if l:sub(1, 1) == "?" and checked then
preinstall[l:sub(2)] = true
elseif l:sub(1, 1) == "+" then
table.insert(gback, l:sub(2))
elseif l:sub(1, 1) == "/" then
table.insert(gdir, l:sub(2))
end
end), downloadSrc)
for _, v in ipairs(gdir) do
destProx.makeDirectory(v)
assert(destProx.isDirectory(v), "unable to create dir " .. v)
end
gdir = nil
for k, _ in pairs(preinstall) do
if not destProx.exists("data/app-claw/" .. k .. ".c2x") then
opInstall(k, true)
end
end
preinstall = nil
for _, v in ipairs(gback) do
local f = destProx.open(v .. ".C2T", "wb")
assert(f, "unable to create download file")
local ok, e = pcall(download, v, function (b)
assert(destProx.write(f, b or ""), "unable to save data")
end, downloadSrc)
destProx.close(f)
if not ok then error(e) end
end
-- CRITICAL SECTION --
if destProx.exists("data/app-claw/" .. packageId .. ".c2x") then
opRemove(packageId, false)
end
for _, v in ipairs(gback) do
if destProx.exists(v) then
for _, v in ipairs(gback) do
destProx.remove(v .. ".C2T")
end
error("file conflict: " .. v)
end
end
for _, v in ipairs(gback) do
destProx.rename(v .. ".C2T", v)
end
end
function opRemove(packageId, checked)
if checked then
local dependents = {}
for _, pidf in ipairs(destProx.list("data/app-claw/")) do
if pidf:sub(-4) == ".c2x" then
local pid = pidf:sub(1, -5)
download("data/app-claw/" .. pidf, wrapLines(function (l)
if l == "?" .. packageId then
table.insert(dependents, pid)
end
end), destProx)
end
end
assert(not dependents[1], "Cannot remove " .. packageId .. ", required by:\n" .. table.concat(dependents, ", "))
end
local rmSchedule = {}
download("data/app-claw/" .. packageId .. ".c2x", wrapLines(function (l)
if l:sub(1, 1) == "+" then
table.insert(rmSchedule, l:sub(2))
end
end), destProx)
for _, v in ipairs(rmSchedule) do
destProx.remove(v)
end
end
local ok, e
if downloadSrc then
ok, e = pcall(opInstall, packageId, checked)
else
ok, e = pcall(opRemove, packageId, checked)
end
destProx = nil
downloadSrc = nil
primaryINet = nil
neo.executeAsync("app-claw", packageId)
if not ok then
error(e)
end

495
code/apps/svc-t.lua Normal file
View File

@ -0,0 +1,495 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- svc-t.lua : terminal
local _, _, retTbl, title = ...
assert(retTbl, "need to alert creator")
if title ~= nil then
assert(type(title) == "string", "title must be string")
end
local function rW()
return string.format("%04x", math.random(0, 65535))
end
local id = "neo.pub.t/" .. rW() .. rW() .. rW() .. rW()
local closeNow = false
-- Terminus Registration State --
local tReg = neo.requireAccess("r." .. id, "registration")
local sendSigs = {}
-- Display State --
-- unicode.safeTextFormat'd lines.
-- The size of this must not go below 1.
local console = {}
-- This must not go below 3.
local conW = 40
local conCX, conCY = 1, 1
local conSCX, conSCY = 1, 1
-- Performance
local consoleShown = {}
local conCYShown
for i = 1, 14 do
console[i] = (" "):rep(conW)
end
-- Line Editing State --
-- Nil if line editing is off.
-- In this case, the console height
-- must be adjusted accordingly.
local leText = ""
-- These are NOT nil'd out,
-- particularly not the history buffer.
local leCX = 1
local leHistory = {
-- Size = history buffer size
"", "", "", ""
}
local function cycleHistoryUp()
local backupFirst = leHistory[1]
for i = 1, #leHistory - 1 do
leHistory[i] = leHistory[i + 1]
end
leHistory[#leHistory] = backupFirst
end
local function cycleHistoryDown()
local backup = leHistory[1]
for i = 2, #leHistory do
backup, leHistory[i] = leHistory[i], backup
end
leHistory[1] = backup
end
-- Window --
local window = neo.requireAccess("x.neo.pub.window", "window")(conW, #console + 1, title)
-- Core Terminal Functions --
local function setSize(w, h)
conW = w
for i = 1, #console do
consoleShown[i] = nil
end
while #console < h do
table.insert(console, "")
end
while #console > h do
table.remove(console, 1)
end
for i = 1, #console do
console[i] = unicode.sub(console[i], 1, w) .. (" "):rep(w - unicode.len(console[i]))
end
if leText then
window.setSize(w, h + 1)
else
window.setSize(w, h)
end
conCX, conCY = 1, h
end
local function setLineEditing(state)
if state and not leText then
leText = ""
leCX = 1
setSize(conW, #console)
elseif leText and not state then
leText = nil
setSize(conW, #console)
end
end
local function draw(i)
if console[i] then
window.span(1, i, console[i], 0, 0xFFFFFF)
if i == conCY and not leText then
window.span(conCX, i, unicode.sub(console[i], conCX, conCX), 0xFFFFFF, 0)
end
elseif leText then
window.span(1, i, require("lineedit").draw(conW, unicode.safeTextFormat(leText, leCX)), 0xFFFFFF, 0)
end
end
local function drawDisplay()
for i = 1, #console do
if consoleShown[i] ~= console[i] or i == conCY or i == conCYShown then
draw(i)
consoleShown[i] = console[i]
end
end
conCYShown = conCY
end
-- Terminal Visual --
local function consoleSD()
for i = 1, #console - 1 do
console[i] = console[i + 1]
end
console[#console] = (" "):rep(conW)
end
local function consoleSU()
local backup = (" "):rep(conW)
for i = 1, #console do
backup, console[i] = console[i], backup
end
end
local function consoleCLS()
for i = 1, #console do
console[i] = (" "):rep(conW)
end
conCX, conCY = 1, 1
end
local function writeFF()
if conCY ~= #console then
conCY = conCY + 1
else
consoleSD()
end
end
local function writeData(data)
-- handle data until completion
while #data > 0 do
local char = unicode.sub(data, 1, 1)
--neo.emergency("svc-t.data: " .. char:byte())
data = unicode.sub(data, 2)
-- handle character
if char == "\t" then
-- not ideal, but allowed
char = " "
end
if char == "\r" then
conCX = 1
elseif char == "\x00" then
-- caused by TELNET \r rules
elseif char == "\n" then
conCX = 1
writeFF()
elseif char == "\a" then
-- Bell (er...)
elseif char == "\b" then
conCX = math.max(1, conCX - 1)
elseif char == "\v" or char == "\f" then
writeFF()
else
local charL = unicode.wlen(char)
if (conCX + charL - 1) > conW then
conCX = 1
writeFF()
end
local spaces = (" "):rep(charL - 1)
console[conCY] = unicode.sub(console[conCY], 1, conCX - 1) .. char .. spaces .. unicode.sub(console[conCY], conCX + charL)
conCX = conCX + charL
-- Cursor can be (intentionally!) off-screen here
end
end
end
local function writeANSI(s)
--neo.emergency("svc-t.ansi: " .. s)
-- This supports just about enough to get by.
if s == "c" then
consoleCLS()
return
end
local pfx = s:sub(1, 1)
local cmd = s:sub(#s)
if pfx == "[" then
local np = tonumber(s:sub(2, -2)) or 1
if cmd == "A" then
conCY = conCY - np
elseif cmd == "B" then
conCY = conCY + np
elseif cmd == "C" then
conCX = conCX + np
elseif cmd == "D" then
conCX = conCX - np
elseif cmd == "f" or cmd == "H" then
local p = s:find(";")
if not p then
conCY = np
conCX = 1
else
conCY = tonumber(s:sub(2, p - 1)) or 1
conCX = tonumber(s:sub(p + 1, -2)) or 1
end
elseif cmd == "J" then
consoleCLS()
elseif cmd == "K" then
if s == "[K" or s == "[0K" then
-- bash needs this
console[conCY] = unicode.sub(console[conCY], 1, conCX - 1) .. (" "):rep(1 + conW - conCX)
else
console[conCY] = (" "):rep(conW)
end
elseif cmd == "n" then
if s == "[6n" then
for _, v in pairs(sendSigs) do
v("data", "\x1b[" .. conY .. ";" .. conX .. "R")
end
end
elseif cmd == "s" then
conSCX, conSCY = conCX, conCY
elseif cmd == "u" then
conCX, conCY = conSCX, conSCY
end
end
conCX = math.min(math.max(math.floor(conCX), 1), conW)
conCY = math.min(math.max(math.floor(conCY), 1), #console)
end
-- The Terminus --
local tvBuildingCmd = ""
local tvBuildingUTF = ""
local tvSubnegotiation = false
local function incoming(s)
tvBuildingCmd = tvBuildingCmd .. s
-- Flush Cmd
while #tvBuildingCmd > 0 do
if tvBuildingCmd:byte() == 255 then
-- It's a command. Uhoh.
if #tvBuildingCmd < 2 then break end
local cmd = tvBuildingCmd:byte(2)
local param = tvBuildingCmd:byte(3)
local cmdLen = 2
-- Command Lengths
if cmd >= 251 and cmd <= 254 then cmdLen = 3 end
if #tvBuildingCmd < cmdLen then break end
if cmd == 240 then
-- SE
tvSubnegotiation = false
elseif cmd == 250 then
-- SB
tvSubnegotiation = true
elseif cmd == 251 and param == 1 then
-- WILL ECHO (respond with DO ECHO, disable line editing)
-- test using io.write("\xFF\xFB\x01")
for _, v in pairs(sendSigs) do
v("telnet", "\xFF\xFD\x01")
end
setLineEditing(false)
elseif cmd == 252 and param == 1 then
-- WON'T ECHO (respond with DON'T ECHO, enable line editing)
for _, v in pairs(sendSigs) do
v("telnet", "\xFF\xFE\x01")
end
setLineEditing(true)
elseif cmd == 251 or cmd == 252 then
-- WILL/WON'T (x) (respond with DON'T (X))
local res = "\xFF\xFE" .. string.char(param)
for _, v in pairs(sendSigs) do
v("telnet", res)
end
elseif cmd == 253 or cmd == 254 then
-- DO/DON'T (x) (respond with WON'T (X))
local res = "\xFF\xFC" .. string.char(param)
for _, v in pairs(sendSigs) do
v("telnet", res)
end
elseif cmd == 255 then
if not tvSubnegotiation then
tvBuildingUTF = tvBuildingUTF .. "\xFF"
end
end
tvBuildingCmd = tvBuildingCmd:sub(cmdLen + 1)
else
if not tvSubnegotiation then
tvBuildingUTF = tvBuildingUTF .. tvBuildingCmd:sub(1, 1)
end
tvBuildingCmd = tvBuildingCmd:sub(2)
end
end
-- Flush UTF/Display
while #tvBuildingUTF > 0 do
local head = tvBuildingUTF:byte()
local len = 1
local handled = false
if head == 27 then
local h2 = tvBuildingUTF:byte(2)
if h2 == 91 then
for i = 3, #tvBuildingUTF do
local cmd = tvBuildingUTF:byte(i)
if cmd >= 0x40 and cmd <= 0x7E then
writeANSI(tvBuildingUTF:sub(2, i))
len = i
handled = true
break
end
end
elseif h2 then
len = 2
writeANSI(tvBuildingUTF:sub(2, 2))
handled = true
end
if not handled then break end
end
if not handled then
if head < 192 then
len = 1
elseif head < 224 then
len = 2
elseif head < 240 then
len = 3
elseif head < 248 then
len = 4
elseif head < 252 then
len = 5
elseif head < 254 then
len = 6
end
if #tvBuildingUTF < len then
break
end
-- verified one full character...
writeData(tvBuildingUTF:sub(1, len))
end
tvBuildingUTF = tvBuildingUTF:sub(len + 1)
end
end
do
tReg(function (_, pid, sendSig)
sendSigs[pid] = sendSig
return {
id = "x." .. id,
pid = neo.pid,
write = function (text)
incoming(tostring(text))
drawDisplay()
end
}
end, true)
if retTbl then
coroutine.resume(coroutine.create(retTbl), {
access = "x." .. id,
close = function ()
closeNow = true
neo.scheduleTimer(0)
end
})
end
end
local control = false
local function key(a, c)
if control then
if c == 203 and conW > 8 then
setSize(conW - 1, #console)
return
elseif c == 200 and #console > 1 then
setSize(conW, #console - 1)
return
elseif c == 205 then
setSize(conW + 1, #console)
return
elseif c == 208 then
setSize(conW, #console + 1)
return
end
end
-- so with the reserved ones dealt with...
if not leText then
-- Line Editing not active.
-- For now support a bare minimum.
for _, v in pairs(sendSigs) do
if a == "\x03" then
v("telnet", "\xFF\xF4")
elseif c == 199 then
v("data", "\x1b[H")
elseif c == 201 then
v("data", "\x1b[5~")
elseif c == 207 then
v("data", "\x1b[F")
elseif c == 209 then
v("data", "\x1b[6~")
elseif c == 203 then
v("data", "\x1b[D")
elseif c == 205 then
v("data", "\x1b[C")
elseif c == 200 then
v("data", "\x1b[A")
elseif c == 208 then
v("data", "\x1b[B")
elseif a == "\r" then
v("data", "\r\n")
elseif a then
v("data", a)
end
end
elseif not control then
-- Line Editing active and control isn't involved
if c == 200 or c == 208 then
-- History cursor up (history down)
leText = leHistory[#leHistory]
leCX = unicode.len(leText) + 1
if c == 208 then
cycleHistoryUp()
else
cycleHistoryDown()
end
return
end
local lT, lC, lX = require("lineedit").key(a, c, leText, leCX)
leText = lT or leText
leCX = lC or leCX
if lX == "nl" then
cycleHistoryUp()
leHistory[#leHistory] = leText
-- the whole thing {
local fullText = leText .. "\r\n"
writeData(fullText)
drawDisplay()
for _, v in pairs(sendSigs) do
v("data", fullText)
end
-- }
leText = ""
leCX = 1
end
end
end
while not closeNow do
local e = {coroutine.yield()}
if e[1] == "k.procdie" then
sendSigs[e[3]] = nil
elseif e[1] == "x.neo.pub.window" then
if e[3] == "close" then
break
elseif e[3] == "clipboard" then
for i = 1, unicode.len(e[4]) do
local c = unicode.sub(e[4], i, i)
if c ~= "\r" then
if c == "\n" then
c = "\r"
end
key(c, 0)
end
end
draw(#console + 1)
elseif e[3] == "key" then
if e[5] == 29 or e[5] == 157 then
control = e[6]
elseif e[6] then
key(e[4] ~= 0 and unicode.char(e[4]), e[5])
draw(#console + 1)
end
elseif e[3] == "line" then
draw(e[4])
end
end
end

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- s-everest
@ -46,7 +50,7 @@ local monitors = {}
-- This is where we stuff processes until monitors show up
monitors[0] = {nil, nil, 160, 50}
-- {monitor, x, y, w, h, callback}
-- {monitor, x, y, w, h, callback, title}
-- callback events are:
-- key ka kc down
-- line y
@ -61,10 +65,12 @@ local shuttingDown = false
-- Also used for settings.
local savingThrow = neo.requestAccess("x.neo.sys.manage")
local draggingWindowX, draggingWindowY
local function suggestAppsStop()
for k, v in ipairs(surfaces) do
for i = 1, 4 do
v[6]("close")
v[6](v[8], "close")
end
end
end
@ -81,8 +87,8 @@ local function dying()
-- If a process evades the deathtrap then it clearly has reason to stay alive regardless of Everest status.
-- Also note, should savingThrow fail, neo.dead is now a thing.
for _, v in ipairs(surfaces) do
pcall(v[6], "line", 1)
pcall(v[6], "line", 2)
pcall(v[6], v[8], "line", 1)
pcall(v[6], v[8], "line", 2)
end
surfaces = {}
end
@ -140,162 +146,13 @@ local function doBackgroundLine(m, mg, bdx, bdy, bdl)
local str = unicode.sub(statusLine, bdx, bdx + bdl - 1)
local strl = unicode.len(str)
pcall(mg.set, bdx, bdy, unicode.undoSafeTextFormat(str))
pcall(mg.fill, bdx + strl, bdy, bdl - strl, 1, " ")
pcall(mg.set, bdx + strl, bdy, (" "):rep(bdl - strl))
else
monitorGPUColours(m, mg, 0x000040, 0)
pcall(mg.fill, bdx, bdy, bdl, 1, " ")
pcall(mg.set, bdx, bdy, (" "):rep(bdl))
end
end
local function updateRegion(monitorId, x, y, w, h, surfaceSpanCache)
if not renderingAllowed() then return end
local m = monitors[monitorId]
local mg, rb = m[1]()
if not mg then return end
if rb then
monitorResetBF(m)
end
-- The input region is the one that makes SENSE.
-- Considering WC handling, that's not an option.
-- WCHAX: start
if x > 1 then
x = x - 1
w = w + 1
end
-- this, in combination with 'forcefully blank out last column of window during render',
-- cleans up littering
w = w + 1
-- WCHAX: end
for span = 1, h do
local backgroundMarkStart = nil
for sx = 1, w do
local t, tx, ty = surfaceAt(monitorId, sx + x - 1, span + y - 1)
if t then
-- Background must occur first due to wide char weirdness
if backgroundMarkStart then
local bdx, bdy, bdl = backgroundMarkStart + x - 1, span + y - 1, sx - backgroundMarkStart
doBackgroundLine(m, mg, bdx, bdy, bdl)
backgroundMarkStart = nil
end
if not surfaceSpanCache[monitorId .. "_" .. t .. "_" .. ty] then
surfaceSpanCache[monitorId .. "_" .. t .. "_" .. ty] = true
surfaces[t][6]("line", ty)
end
elseif not backgroundMarkStart then
backgroundMarkStart = sx
end
end
if backgroundMarkStart then
doBackgroundLine(monitors[monitorId], mg, backgroundMarkStart + x - 1, span + y - 1, (w - backgroundMarkStart) + 1)
end
end
end
local function updateStatus()
statusLine = "Λ-¶: menu (launch 'control' to logout)"
if surfaces[1] then
if #monitors > 1 then
-- 123456789X123456789X123456789X123456789X123456789X
statusLine = "Λ-+: move, Λ-Z: switch, Λ-X: swMonitor, Λ-C: close"
else
statusLine = "Λ-+: move, Λ-Z: switch, Λ-C: close"
end
end
statusLine = unicode.safeTextFormat(statusLine)
for k, v in ipairs(monitors) do
updateRegion(k, 1, 1, v[3], 1, {})
end
end
local function ensureOnscreen(monitor, x, y, w, h)
if monitor <= 0 then monitor = #monitors end
if monitor >= (#monitors + 1) then monitor = 1 end
-- Failing anything else, revert to monitor 0
if #monitors == 0 then monitor = 0 end
x = math.min(math.max(1, x), monitors[monitor][3] - (w - 1))
y = math.max(1, math.min(monitors[monitor][4] - (h - 1), y))
return monitor, x, y
end
-- This is the "a state change occurred" function, only for use when needed
local function reconcileAll()
for k, v in ipairs(surfaces) do
-- About to update whole screen anyway so avoid the wait.
v[1], v[2], v[3] = ensureOnscreen(v[1], v[2], v[3], v[4], v[5])
end
local k = 1
while k <= #monitors do
local v = monitors[k]
local mon, rb = v[1]()
if rb then
monitorResetBF(v)
end
if mon then
-- This *can* return null if something went wonky. Let's detect that
v[3], v[4] = mon.getResolution()
if not v[3] then
neo.emergency("everest: monitor went AWOL and nobody told me u.u")
table.remove(monitors, k)
v = nil
end
end
if v then
updateRegion(k, 1, 1, v[3], v[4], {})
k = k + 1
end
end
updateStatus()
end
-- NOTE: If the M, X, Y, W and H are the same, this function ignores you, unless you put , true on the end.
local function moveSurface(surface, m, x, y, w, h, force)
local om, ox, oy, ow, oh = table.unpack(surface, 1, 5)
m = m or om
x = x or ox
y = y or oy
w = w or ow
h = h or oh
surface[1], surface[2], surface[3], surface[4], surface[5] = m, x, y, w, h
local cache = {}
if om == m and ow == w and oh == h then
if ox == x and oy == y and not force then
return
end
-- note: this doesn't always work due to WC support, and due to resize-to-repaint
if renderingAllowed() and not force then
local cb, b = monitors[m][1]()
if b then
monitorResetBF(monitors[m])
end
if cb then
cb.copy(ox, oy, w, h, x - ox, y - oy)
if surface == surfaces[1] then
local cacheControl = {}
for i = 1, h do
cacheControl[om .. "_1_" .. i] = true
end
updateRegion(om, ox, oy, ow, oh, cacheControl)
return
end
end
end
end
updateRegion(om, ox, oy, ow, oh, cache)
updateRegion(m, x, y, w, h, cache)
end
-- Returns offset from where we expected to be to where we are.
local function ofsSurface(focus, dx, dy)
local exX, exY = focus[2] + dx, focus[3] + dy
local m, x, y = ensureOnscreen(focus[1], exX, exY, focus[4], focus[5])
moveSurface(focus, nil, x, y)
return focus[2] - exX, focus[3] - exY
end
local function ofsMSurface(focus, dm)
local m, x, y = ensureOnscreen(focus[1] + dm, focus[2], focus[3], focus[4], focus[5])
moveSurface(focus, m, x, y)
end
local function handleSpan(target, x, y, text, bg, fg)
if not renderingAllowed() then return end
local m = monitors[target[1]]
@ -353,6 +210,178 @@ local function handleSpan(target, x, y, text, bg, fg)
submitSegment()
end
local function updateRegion(monitorId, x, y, w, h, surfaceSpanCache)
if not renderingAllowed() then return end
local m = monitors[monitorId]
local mg, rb = m[1]()
if not mg then return end
if rb then
monitorResetBF(m)
end
-- The input region is the one that makes SENSE.
-- Considering WC handling, that's not an option.
-- WCHAX: start
if x > 1 then
x = x - 1
w = w + 1
end
-- this, in combination with 'forcefully blank out last column of window during render',
-- cleans up littering
w = w + 1
-- WCHAX: end
for span = 1, h do
local backgroundMarkStart = nil
for sx = 1, w do
local t, tx, ty = surfaceAt(monitorId, sx + x - 1, span + y - 1)
if t then
-- Background must occur first due to wide char weirdness
if backgroundMarkStart then
local bdx, bdy, bdl = backgroundMarkStart + x - 1, span + y - 1, sx - backgroundMarkStart
doBackgroundLine(m, mg, bdx, bdy, bdl)
backgroundMarkStart = nil
end
if not surfaceSpanCache[monitorId .. "_" .. t .. "_" .. ty] then
surfaceSpanCache[monitorId .. "_" .. t .. "_" .. ty] = true
if ty == 1 then
local lw = surfaces[t][4]
local bg = 0x0080FF
local fg = 0x000000
local tx = "-"
if t == 1 then
bg = 0x000000
fg = 0x0080FF
tx = "+"
end
local vtitle = surfaces[t][7]
local vto = unicode.len(vtitle)
if vto < lw then
vtitle = vtitle .. (tx):rep(lw - vto)
else
vtitle = unicode.sub(vtitle, 1, lw)
end
handleSpan(surfaces[t], 1, 1, vtitle, bg, fg)
else
surfaces[t][6](surfaces[t][8], "line", ty - 1)
end
end
elseif not backgroundMarkStart then
backgroundMarkStart = sx
end
end
if backgroundMarkStart then
doBackgroundLine(monitors[monitorId], mg, backgroundMarkStart + x - 1, span + y - 1, (w - backgroundMarkStart) + 1)
end
end
end
local function updateStatus()
statusLine = "Λ-¶: menu (launch 'control' to logout)"
if surfaces[1] then
if #monitors > 1 then
-- 123456789X123456789X123456789X123456789X123456789X
statusLine = "Λ-+: move, Λ-Z: switch, Λ-X: swMonitor, Λ-C: close"
else
statusLine = "Λ-+: move, Λ-Z: switch, Λ-C: close"
end
end
statusLine = unicode.safeTextFormat(statusLine)
for k, v in ipairs(monitors) do
updateRegion(k, 1, 1, v[3], 1, {})
end
end
local function ensureOnscreen(monitor, x, y, w, h)
if monitor <= 0 then monitor = #monitors end
if monitor >= (#monitors + 1) then monitor = 1 end
-- Failing anything else, revert to monitor 0
if #monitors == 0 then monitor = 0 end
x = math.min(math.max(x, 1), monitors[monitor][3] - (w - 1))
y = math.max(2 - h, math.min(monitors[monitor][4], y))
return monitor, x, y
end
-- This is the "a state change occurred" function, only for use when needed
local function reconcileAll()
for k, v in ipairs(surfaces) do
-- About to update whole screen anyway so avoid the wait.
v[1], v[2], v[3] = ensureOnscreen(v[1], v[2], v[3], v[4], v[5])
end
local k = 1
while k <= #monitors do
local v = monitors[k]
local mon, rb = v[1]()
if rb then
monitorResetBF(v)
end
if mon then
-- This *can* return null if something went wonky. Let's detect that
v[3], v[4] = mon.getResolution()
if not v[3] then
neo.emergency("everest: monitor went AWOL and nobody told me u.u")
table.remove(monitors, k)
v = nil
end
end
if v then
updateRegion(k, 1, 1, v[3], v[4], {})
k = k + 1
end
end
updateStatus()
end
-- NOTE: If the M, X, Y, W and H are the same, this function ignores you, unless you put , true on the end.
local function moveSurface(surface, m, x, y, w, h, force)
local om, ox, oy, ow, oh = table.unpack(surface, 1, 5)
m = m or om
x = x or ox
y = y or oy
w = w or ow
h = h or oh
surface[1], surface[2], surface[3], surface[4], surface[5] = m, x, y, w, h
local cache = {}
if om == m and ow == w and oh == h then
if ox == x and oy == y and not force then
return
end
-- note: this doesn't always work due to WC support, and due to resize-to-repaint
if renderingAllowed() and not force then
local cb, b = monitors[m][1]()
if b then
monitorResetBF(monitors[m])
end
if cb then
local monH = monitors[m][4]
local cutTop = math.max(0, 1 - math.min(oy, y))
local cutBottom = math.max(0, (math.max(oy + oh, y + h) - 1) - monH)
pcall(cb.copy, ox, oy + cutTop, w, h - (cutTop + cutBottom), x - ox, y - oy)
if surface == surfaces[1] then
local cacheControl = {}
for i = 1 + cutTop, h - cutBottom do
cacheControl[om .. "_1_" .. i] = true
end
updateRegion(om, ox, oy, ow, oh, cacheControl)
return
end
end
end
end
updateRegion(om, ox, oy, ow, oh, cache)
updateRegion(m, x, y, w, h, cache)
end
-- Returns offset from where we expected to be to where we are.
local function ofsSurface(focus, dx, dy)
local exX, exY = focus[2] + dx, focus[3] + dy
local m, x, y = ensureOnscreen(focus[1], exX, exY, focus[4], focus[5])
moveSurface(focus, nil, x, y)
return focus[2] - exX, focus[3] - exY
end
local function ofsMSurface(focus, dm)
local m, x, y = ensureOnscreen(focus[1] + dm, focus[2], focus[3], focus[4], focus[5])
moveSurface(focus, m, x, y)
end
local basePalT2 = {
-- on T2 we provide 'system colours' by default
0x000000, 0x0080FF, 0x000040, 0xFFFFFF,
@ -416,11 +445,11 @@ local function changeFocus(oldSurface, optcache)
if ns1 ~= oldSurface then
if oldSurface then
setSurfacePalette(oldSurface, nil)
oldSurface[6]("focus", false)
oldSurface[6](oldSurface[8], "focus", false)
end
if ns1 then
setSurfacePalette(ns1, nil)
ns1[6]("focus", true)
ns1[6](ns1[8], "focus", true)
end
updateStatus()
if oldSurface then
@ -459,70 +488,11 @@ everestProvider(function (pkg, pid, sendSig)
else
title = base .. ":" .. title
end
local surf = {math.min(#monitors, math.max(1, lIM)), 1, 2, w, h}
if h >= monitors[surf[1]][4] then
surf[3] = 1
end
local focusState = false
local llid = lid
lid = lid + 1
local specialDragHandler
surf[6] = function (ev, a, b, c, d, e)
-- Must forward surface events
if ev == "focus" then
focusState = a
end
if ev == "touch" then
specialDragHandler = nil
if math.floor(b) == 1 then
if e == 1 then
sendSig(llid, "close")
return
end
specialDragHandler = function (x, y)
local ofsX, ofsY = math.floor(x) - math.floor(a), math.floor(y) - math.floor(b)
if (ofsX == 0) and (ofsY == 0) then return end
ofsSurface(surf, ofsX, ofsY)
end
return
end
b = b - 1
end
if ev == "drag" then
if specialDragHandler then
specialDragHandler(a, b)
return
end
b = b - 1
end
if ev == "scroll" or ev == "drop" then
specialDragHandler = nil
b = b - 1
end
if ev == "line" then
if a == 1 then
local lw = surf[4]
local bg = 0x0080FF
local fg = 0x000000
local tx = "-"
if focusState then
bg = 0x000000
fg = 0x0080FF
tx = "+"
end
local vtitle = title
local vto = unicode.len(vtitle)
if vto < lw then
vtitle = vtitle .. (tx):rep(lw - vto)
else
vtitle = unicode.sub(vtitle, 1, lw)
end
handleSpan(surf, 1, 1, vtitle, bg, fg)
return
end
a = a - 1
end
sendSig(llid, ev, a, b, c, d, e)
local surf = {math.min(#monitors, math.max(1, lIM)), 1, 2, w, h, sendSig, title, llid}
if h >= monitors[surf[1]][4] then
surf[3] = 1
end
local osrf = surfaces[1]
table.insert(surfaces, 1, surf)
@ -551,17 +521,13 @@ everestProvider(function (pkg, pid, sendSig)
end,
span = function (x, y, text, bg, fg)
if neo.dead then error("everest died") end
if type(x) ~= "number" then error("X must be number.") end
if type(y) ~= "number" then error("Y must be number.") end
if type(bg) ~= "number" then error("Background must be number.") end
if type(fg) ~= "number" then error("Foreground must be number.") end
if type(text) ~= "string" then error("Text must be string.") end
checkArg(3, text, "string")
x, y, bg, fg = math.floor(x), math.floor(y), math.floor(bg), math.floor(fg)
if y == 0 then return end
handleSpan(surf, x, y + 1, text, bg, fg)
end,
recommendPalette = function (pal)
if not focusState then return 0 end
if surfaces[1] ~= surf then return 0 end
return setSurfacePalette(surf, pal)
end,
close = function ()
@ -586,7 +552,7 @@ end)
-- THE EVEREST USER API ENDS (now for the session API, which just does boring stuff)
-- used later on for lost monitor, too
local function disclaimMonitor(mon)
neo.ensureType(mon, "string")
checkArg(1, mon, "string")
screens.disclaim(mon)
for k, v in ipairs(monitors) do
if v[2] == mon then
@ -600,7 +566,7 @@ end
everestSessionProvider(function (pkg, pid, sendSig)
return {
endSession = function (gotoBristol)
neo.ensureType(gotoBristol, "boolean")
checkArg(1, gotoBristol, "boolean")
shuttingDown = true
if gotoBristol then
suggestAppsStop()
@ -638,69 +604,59 @@ end
local isAltDown = false
local isCtrDown = false
local function key(ku, ka, kc, down)
local ku = screens.getMonitorByKeyboard(ku)
if not ku then return end
local ku, lin = screens.getMonitorByKeyboard(ku)
for k, v in ipairs(monitors) do
if v[2] == mu then
lIM = k
if ku and v[2] == ku then
lin = k
break
end
end
if not lin then return end
lIM = lin
local focus = surfaces[1]
if kc == 29 then isCtrDown = down end
if kc == 56 then isAltDown = down end
if isAltDown then
if ka == 120 then
if focus and down then ofsMSurface(focus, 1) end return
if kc == 29 then
isCtrDown = down
elseif kc == 56 then
isAltDown = down
end
if isAltDown and ka == 122 then
if focus and down then
local n = table.remove(surfaces, 1)
table.insert(surfaces, n)
changeFocus(n)
end
if kc == 200 then
if focus and down then ofsSurface(focus, 0, -1) end return
elseif isAltDown and kc == 200 then
if focus and down then ofsSurface(focus, 0, -1) end
elseif isAltDown and kc == 208 then
if focus and down then ofsSurface(focus, 0, 1) end
elseif isAltDown and kc == 203 then
if focus and down then ofsSurface(focus, -1, 0) end
elseif isAltDown and kc == 205 then
if focus and down then ofsSurface(focus, 1, 0) end
elseif isAltDown and ka == 120 then
if focus and down then ofsMSurface(focus, 1) end
elseif isAltDown and ka == 97 then
if not down then
isAltDown = false
end
if kc == 208 then
if focus and down then ofsSurface(focus, 0, 1) end return
end
if kc == 203 then
if focus and down then ofsSurface(focus, -1, 0) end return
end
if kc == 205 then
if focus and down then ofsSurface(focus, 1, 0) end return
end
if ka == 122 then
if focus and down then
local n = table.remove(surfaces, 1)
table.insert(surfaces, n)
changeFocus(n)
end return
end
if ka == 3 then
-- Ctrl-Alt-C (!?!?!!)
elseif isAltDown and (ka == 3 or ka == 99) then
if down then
if isCtrDown then
error("User-authorized Everest crash.")
elseif focus then
focus[6](focus[8], "close")
end
end
if ka == 99 then
if down then
if isCtrDown then
error("User-authorized Everest crash.")
else
if focus then
focus[6]("close")
end
end
end
return
elseif isAltDown and ka == 13 then
if down then
startLauncher()
end
if ka == 13 then
if down then
startLauncher()
end
return
end
end
if focus then
elseif focus then
if kc ~= 56 then
lIM = focus[1]
end
focus[6]("key", ka, kc, down)
focus[6](focus[8], "key", ka, kc, down)
end
end
@ -733,7 +689,7 @@ while not shuttingDown do
end
if s[1] == "h.clipboard" then
if surfaces[1] then
surfaces[1][6]("clipboard", s[3])
surfaces[1][6](surfaces[1][8], "clipboard", s[3])
end
end
-- next on my list: high-res coordinates
@ -749,7 +705,16 @@ while not shuttingDown do
local ns = table.remove(surfaces, sid)
table.insert(surfaces, 1, ns)
changeFocus(os)
ns[6]("touch", lx, ly, ix, iy, s[5])
draggingWindowX, draggingWindowY = nil
if ly == 1 then
if s[5] == 1 then
ns[6](ns[8], "close")
else
draggingWindowX, draggingWindowY = lx, ly
end
else
ns[6](ns[8], "touch", lx, ly - 1, ix, iy, s[5])
end
else
if s[5] == 1 then startLauncher() end
end
@ -766,8 +731,16 @@ while not shuttingDown do
if k == focus[1] then
local x, y = (math.ceil(s[3]) - focus[2]) + 1, (math.ceil(s[4]) - focus[3]) + 1
local ix, iy = s[3] - math.floor(s[3]), s[4] - math.floor(s[4])
-- Ok, so let's see...
focus[6](s[1]:sub(3), x, y, ix, iy, s[5])
if s[1] == "h.drag" and draggingWindowX then
local ofsX, ofsY = x - draggingWindowX, y - draggingWindowY
if ofsX ~= 0 or ofsY ~= 0 then
ofsSurface(focus, ofsX, ofsY)
end
else
draggingWindowX, draggingWindowY = nil
-- Ok, so let's see...
focus[6](focus[8], s[1]:sub(3), x, y - 1, ix, iy, s[5])
end
end
break
end
@ -799,7 +772,7 @@ while not shuttingDown do
if os1 then
changeFocus(os1)
else
changeFocus(surfaces[1])
changeFocus()
end
end
if s[1] == "x.neo.sys.screens" then

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- s-donkonit : config, shutdown, screens
@ -125,7 +129,7 @@ local mBase = {
end
return s
end,
delSetting = function ()
delSetting = function (name)
neo.ensureType(name, "string")
local val = nil
if name == "password" or name == "pub.clipboard" then val = "" end

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- s-icecap : Responsible for x.neo.pub API, crash dialogs, and security policy that isn't "sys- has ALL access, anything else has none"
-- In general, this is what userspace will be interacting with in some way or another to get stuff done
@ -34,18 +38,20 @@ nexus = {
neo.emergency("icecap nexus prereg issue")
theEventHandler("k.registration", "x.neo.pub.window")
end
local dw = e(w, h, t)
local dwo, dw = pcall(e, w, h, t)
if not dwo then
addOnReg("x.neo.pub.window", cb)
return
end
c(dw)
everestWindows[dw.id] = function (...)
return c(dw, ...)
end
return true
else
addOnReg("x.neo.pub.window", cb)
end
end
if not cb() then
addOnReg("x.neo.pub.window", cb)
end
return dw
cb()
end,
windows = everestWindows,
startDialog = function (tx, ti)
@ -79,20 +85,12 @@ local function getPfx(xd, pkg)
end
end
local endAcPattern = "/[a-z0-9/%.]*$"
local function matchesSvc(xd, pkg, perm)
local pfx = getPfx(xd, pkg)
if pfx then
local permAct = perm
local paP = permAct:match(endAcPattern)
if paP then
permAct = permAct:sub(1, #permAct - #paP)
end
if permAct == pfx then
return "allow"
end
local function splitAC(ac)
local sb = ac:match("/[a-z0-9/%.]*$")
if sb then
return ac:sub(1, #ac - #sb), sb
end
return ac
end
donkonitDFProvider(function (pkg, pid, sendSig)
@ -107,17 +105,20 @@ donkonitDFProvider(function (pkg, pid, sendSig)
end
end
return {
showFileDialogAsync = function (forWrite)
showFileDialogAsync = function (forWrite, defName)
if not rawequal(forWrite, nil) then
require("sys-filewrap").ensureMode(forWrite)
end
if not rawequal(defName, nil) then
defName = tostring(defName)
end
-- Not hooked into the event API, so can't safely interfere
-- Thus, this is async and uses a return event.
local tag = {}
neo.scheduleTimer(0)
table.insert(todo, function ()
-- sys-filedialog is yet another "library to control memory usage".
local closer = require("sys-filedialog")(event, nexus, function (res) openHandles[tag] = nil sendSig("filedialog", tag, res) end, neo.requireAccess("c.filesystem", "file managers"), pkg, forWrite)
local closer = require("sys-filedialog")(event, nexus, function (res) openHandles[tag] = nil sendSig("filedialog", tag, res) end, neo.requireAccess("c.filesystem", "file managers"), pkg, forWrite, defName)
openHandles[tag] = closer
end)
return tag
@ -125,7 +126,8 @@ donkonitDFProvider(function (pkg, pid, sendSig)
myApi = getPfx("", pkg),
lockPerm = function (perm)
-- Are we allowed to?
if not matchesSvc("x.", pkg, perm) then
local permPfx, detail = splitAC(perm)
if getPfx("x.", pkg) ~= permPfx then
return false, "You don't own this permission."
end
local set = "perm|*|" .. perm
@ -218,7 +220,11 @@ local function secPolicyStage2(pid, proc, perm, req)
-- Push to ICECAP thread to avoid deadlock b/c wrong event-pull context
neo.scheduleTimer(0)
table.insert(todo, function ()
local ok, err = pcall(secpol, nexus, settings, proc.pkg, pid, perm, req, matchesSvc)
local fPerm = perm
if fPerm:sub(1, 2) == "r." then
fPerm = splitAC(fPerm)
end
local ok, err = pcall(secpol, nexus, settings, proc.pkg, pid, fPerm, req, getPfx("", proc.pkg))
if not ok then
neo.emergency("Used fallback policy because of run-err: " .. err)
req(def)
@ -238,11 +244,7 @@ rootAccess.securityPolicy = function (pid, proc, perm, req)
end
-- Do we need to start it?
if perm:sub(1, 6) == "x.svc." and not neo.usAccessExists(perm) then
local appAct = perm:sub(7)
local paP = appAct:match(endAcPattern)
if paP then
appAct = appAct:sub(1, #appAct - #paP)
end
local appAct = splitAC(perm:sub(7))
-- Prepare for success
onReg[perm] = onReg[perm] or {}
local orp = onReg[perm]

View File

@ -1,21 +1,27 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- s-bristol splash screen, login agent
-- Named to allude to Plymouth (splash screen used in some linux distros)
local callerPkg, callerPid, callerScr = ...
local gpuG, screen = nil, nil
local shutdownEmergency = neo.requestAccess("k.computer").shutdown
neo.requestAccess("s.h.key_down")
neo.requestAccess("s.h._kosneo_syslog")
local scrW, scrH
local warnings = {
"",
"",
""
}
-- gpuG/performDisclaim are GPU management, while screen is used for prioritization
local gpuG, performDisclaim, screen = nil
local scrW, scrH = 1, 1
local nssInst
local console = {}
local helpActive = false
local buttonsActive = false
-- Attempts to call upon nsm for a safe shutdown
local function shutdown(reboot)
@ -30,32 +36,53 @@ local function shutdown(reboot)
end
end
local function rstfbDraw(gpu)
pcall(gpu.setBackground, 0xFFFFFF)
local function basicDraw(bg)
local gpu = gpuG()
pcall(gpu.setBackground, bg or 0xFFFFFF)
pcall(gpu.setForeground, 0x000000)
end
local function basicDraw(gpu)
local ok, sw, sh = pcall(gpu.getResolution)
if not ok then return end
scrW, scrH = sw, sh
pcall(gpu.fill, 1, 1, scrW, scrH, " ")
pcall(gpu.set, 2, 2, "KittenOS NEO")
end
local function advDraw(gpu)
basicDraw(gpu)
local usage = math.floor((os.totalMemory() - os.freeMemory()) / 1024)
pcall(gpu.set, 2, 3, "RAM Usage: " .. usage .. "K / " .. math.floor(os.totalMemory() / 1024) .. "K")
for i = 1, #warnings do
pcall(gpu.set, 2, 6 + i, warnings[i])
local cut = 7
if buttonsActive then cut = 9 end
local areaSize = scrH - cut
local n2 = 0
if helpActive then
if _VERSION == "Lua 5.2" then
table.insert(console, "WARNING: Lua 5.2 memory usage issue!")
table.insert(console, "Shift-right-click while holding the CPU/APU.")
n2 = 2
end
table.insert(console, "TAB to change option, ENTER to select.")
n2 = n2 + 1
end
for i = 1, areaSize do
pcall(gpu.set, 2, 6 + i, console[#console + i - areaSize] or "")
end
for i = 1, n2 do
table.remove(console, #console)
end
return gpu
end
local function consoleEventHandler(ev)
if ev[1] == "h._kosneo_syslog" then
local text = ""
for i = 3, #ev do
if i ~= 3 then text = text .. " " end
text = text .. tostring(ev[i])
end
table.insert(console, text)
end
end
-- Callback setup by finalPrompt to disclaim the main monitor first
local performDisclaim = nil
local function retrieveNssMonitor(nss)
-- Attempts to get an NSS monitor with a priority list of screens
local function retrieveNssMonitor(...)
local spc = {...}
gpuG = nil
local subpool = {}
while not gpuG do
@ -67,19 +94,23 @@ local function retrieveNssMonitor(nss)
-- If no monitors are available, shut down now.
-- NSS monitor pool output is smaller than, but similar to, Everest monitor data:
-- {gpu, screenAddr}
local pool = nss.getClaimable()
local pool = nssInst.getClaimable()
while not pool[1] do
coroutine.yield() -- wait for presumably a NSS notification
pool = nss.getClaimable()
-- wait for presumably a NSS notification
consoleEventHandler({coroutine.yield()})
pool = nssInst.getClaimable()
end
subpool = {}
-- Specifies which element to elevate to top priority
local optimalSwap = nil
local optimal = #spc + 1
if screen then
for k, v in ipairs(pool) do
if v == screen then
optimalSwap = k
break
for k2, v2 in ipairs(spc) do
if v == v2 and optimal > k2 then
optimalSwap, optimal = k, k2
break
end
end
end
end
@ -89,7 +120,7 @@ local function retrieveNssMonitor(nss)
pool[1] = swapA
end
for _, v in ipairs(pool) do
local gpu = nss.claim(v)
local gpu = nssInst.claim(v)
if gpu then
local gcb = gpu()
if gcb then
@ -102,52 +133,53 @@ local function retrieveNssMonitor(nss)
end
end
end
if not subpool[1] then
error("None of the GPUs we got were actually usable")
if subpool[1] then
gpuG, screen = table.unpack(subpool[1])
end
gpuG = subpool[1][1]
screen = subpool[1][2]
end
-- done with search
local gpu = gpuG()
rstfbDraw(gpu)
performDisclaim = function (full)
nss.disclaim(subpool[1][2])
nssInst.disclaim(subpool[1][2])
if full then
for _, v in ipairs(subpool) do
nss.disclaim(v[2])
nssInst.disclaim(v[2])
end
end
end
return gpu
end
local function sleep(t)
neo.scheduleTimer(os.uptime() + t)
while true do
local ev = {coroutine.yield()}
consoleEventHandler(ev)
if ev[1] == "k.timer" then
break
end
if ev[1] == "x.neo.sys.screens" then
-- This implies we have and can use nss, but check anyway
local nss = neo.requestAccess("x.neo.sys.screens")
if nss then
local gpu = retrieveNssMonitor(nss)
basicDraw(gpu)
end
retrieveNssMonitor(screen)
basicDraw()
end
end
end
local function alert(s)
console = {s}
helpActive, buttonsActive = false, false
basicDraw()
sleep(1)
buttonsActive = true
end
local function finalPrompt()
local nss = neo.requestAccess("x.neo.sys.screens")
if nss then
retrieveNssMonitor(nss)
else
error("no glacier to provide GPU for the prompt")
nssInst = neo.requestAccess("x.neo.sys.screens")
if not nssInst then
console = {"sys-glacier not available"}
basicDraw()
error("no nssInst")
end
retrieveNssMonitor()
helpActive, buttonsActive = true, true
-- This is nsm's final chance to make itself available and thus allow the password to be set
local nsm = neo.requestAccess("x.neo.sys.manage")
local waiting = true
@ -159,24 +191,11 @@ local function finalPrompt()
return false
end
end
if _VERSION == "Lua 5.2" then
warnings[1] = "NOTE: It's recommended you use Lua 5.3."
warnings[2] = "Shift-right-click while holding the CPU item."
else
warnings[1] = "TAB to change option,"
warnings[2] = "ENTER to select..."
end
-- The actual main prompt loop
while waiting do
local gpu = gpuG()
rstfbDraw(gpu)
advDraw(gpu)
local entry = ""
local entry2 = ""
local active = true
local shButton = "<Shutdown>"
local rbButton = "<Reboot>"
local smButton = "<Safe Mode>"
local pw = {function ()
return "Password: " .. entry2
end, function (key)
@ -188,10 +207,7 @@ local function finalPrompt()
if entry == password then
waiting = false
else
local gpu = gpuG()
rstfbDraw(gpu)
advDraw(gpu)
sleep(1)
alert("Incorrect password")
end
active = false
end
@ -208,68 +224,69 @@ local function finalPrompt()
end
local controls = {
{function ()
return shButton
return "<Shutdown>"
end, function (key)
if key == 13 then
local gpu = gpuG()
rstfbDraw(gpu)
basicDraw(gpu)
pcall(gpu.set, 2, 4, "Shutting down...")
alert("Shutting down...")
shutdown(false)
end
end, 2, scrH - 1, unicode.len(shButton)},
end, 2, scrH - 1, 10},
{function ()
return rbButton
return "<Reboot>"
end, function (key)
if key == 13 then
local gpu = gpuG()
rstfbDraw(gpu)
basicDraw(gpu)
pcall(gpu.set, 2, 4, "Rebooting...")
alert("Rebooting...")
shutdown(true)
end
end, 3 + unicode.len(shButton), scrH - 1, unicode.len(rbButton)},
end, 13, scrH - 1, 8},
{function ()
return smButton
return "<Safe Mode>"
end, function (key)
if key == 13 then
local gpu = gpuG()
rstfbDraw(gpu)
basicDraw(gpu)
pcall(gpu.set, 2, 4, "Login to activate Safe Mode.")
sleep(1)
gpu = gpuG()
safeModeActive = true
rstfbDraw(gpu)
advDraw(gpu)
alert("Login to activate Safe Mode.")
end
end, 4 + unicode.len(shButton) + unicode.len(rbButton), scrH - 1, unicode.len(smButton)},
end, 22, scrH - 1, 11},
pw,
}
local control = #controls
local lastKeyboard
while active do
local gpu = gpuG()
for k, v in ipairs(controls) do
if k == control then
pcall(gpu.setBackground, 0x000000)
pcall(gpu.setForeground, 0xFFFFFF)
else
pcall(gpu.setBackground, 0xFFFFFF)
pcall(gpu.setForeground, 0x000000)
local gpu = basicDraw()
if gpu then
for k, v in ipairs(controls) do
if k == control then
pcall(gpu.setBackground, 0x000000)
pcall(gpu.setForeground, 0xFFFFFF)
else
pcall(gpu.setBackground, 0xFFFFFF)
pcall(gpu.setForeground, 0x000000)
end
pcall(gpu.fill, v[3], v[4], v[5], 1, " ")
pcall(gpu.set, v[3], v[4], v[1]())
end
pcall(gpu.fill, v[3], v[4], v[5], 1, " ")
pcall(gpu.set, v[3], v[4], v[1]())
end
-- event handling...
local sig = {coroutine.yield()}
consoleEventHandler(sig)
if sig[1] == "x.neo.sys.screens" then
-- We need to reinit screens no matter what.
retrieveNssMonitor(nss)
retrieveNssMonitor(screen)
active = false
end
if sig[1] == "h.key_down" then
if sig[2] ~= lastKeyboard then
lastKeyboard = sig[2]
local nScreen = nssInst.getMonitorByKeyboard(lastKeyboard)
if nScreen and nScreen ~= screen then
neo.emergency("new primary:", nScreen)
retrieveNssMonitor(nScreen, screen)
active = false
end
end
if sig[4] == 15 then
-- this makes sense in context
control = control % (#controls)
control = control % #controls
control = control + 1
else
controls[control][2](sig[3])
@ -277,24 +294,21 @@ local function finalPrompt()
end
end
end
local gpu = gpuG()
rstfbDraw(gpu)
advDraw(gpu)
helpActive, buttonsActive = false, false
return safeModeActive
end
local function postPrompt()
local gpu = gpuG()
local nsm = neo.requestAccess("x.neo.sys.manage")
local sh = "sys-everest"
warnings = {"Unable to get sys-init.shell due to no NSM, using sys-everest"}
console = {"Unable to get shell (no sys-glacier)"}
if nsm then
sh = nsm.getSetting("sys-init.shell") or sh
warnings = {"Starting " .. sh}
console = {"Starting " .. sh}
end
rstfbDraw(gpu)
advDraw(gpu)
basicDraw()
performDisclaim()
neo.executeAsync(sh)
-- There's a delay here to allow taking the monitor.
sleep(0.5)
for i = 1, 9 do
local v = neo.requestAccess("x.neo.sys.session")
@ -304,16 +318,16 @@ local function postPrompt()
end
end
-- ...oh. hope this works then?
warnings = {"That wasn't a shell. Try Safe Mode."}
rstfbDraw(gpu)
advDraw(gpu)
console = {"x.neo.sys.session not found, try Safe Mode."}
retrieveNssMonitor(screen)
basicDraw()
sleep(1)
shutdown(true)
end
local function initializeSystem()
-- System has just booted, bristol is in charge
-- Firstly, since we don't know scrcfg, let's work out something sensible.
-- No screen configuration, so just guess.
-- Note that we should try to keep going with this if there's no reason to do otherwise.
local gpuAc = neo.requestAccess("c.gpu")
local screenAc = neo.requestAccess("c.screen")
@ -340,55 +354,36 @@ local function initializeSystem()
gW, gH = math.min(80, gW), math.min(25, gH)
pcall(gpu.setResolution, gW, gH)
pcall(gpu.setDepth, gpu.maxDepth()) -- can crash on OCEmu if done at the "wrong time"
pcall(gpu.setForeground, 0x000000)
end
-- Setup the new GPU provider
gpuG = function () return gpu end
--
local w = 1
local steps = {
"sys-glacier", -- (Glacier : Config, Screen, Power)
-- Let that start, and system GC
"WAIT",
"WAIT",
"WAIT",
"WAIT",
-- Start services
"INJECT",
-- extra GC time
"WAIT",
"WAIT",
"WAIT",
"WAIT",
"WAIT",
"WAIT",
"WAIT"
}
local steps = {"sys-glacier"}
for i = 1, 4 do table.insert(steps, "WAIT") end
table.insert(steps, "INJECT")
for i = 1, 8 do table.insert(steps, "WAIT") end
local stepCount = #steps
neo.scheduleTimer(os.uptime())
while true do
local ev = {coroutine.yield()}
if ev[1] == "k.procnew" then
table.insert(warnings, ev[2] .. "/" .. ev[3] .. " UP")
end
if ev[1] == "k.procdie" then
table.insert(warnings, ev[2] .. "/" .. ev[3] .. " DOWN")
table.insert(warnings, tostring(ev[4]))
end
consoleEventHandler(ev)
if ev[1] == "k.timer" then
if gpu then
pcall(gpu.setForeground, 0x000000)
local bg = 0xFFFFFF
if w < stepCount then
local n = math.floor((w / stepCount) * 255)
pcall(gpu.setBackground, (n * 0x10000) + (n * 0x100) + n)
else
pcall(gpu.setBackground, 0xFFFFFF)
bg = (n * 0x10000) + (n * 0x100) + n
end
basicDraw(gpu)
basicDraw(bg)
end
if steps[w] then
if steps[w] == "INJECT" then
local nsm = neo.requestAccess("x.neo.sys.manage")
if not nsm then
table.insert(warnings, "Settings not available for INJECT.")
table.insert(console, "Settings not available for INJECT.")
else
local nextstepsA = {}
local nextstepsB = {}
@ -412,10 +407,8 @@ local function initializeSystem()
else
local v, err = neo.executeAsync(steps[w])
if not v then
neo.emergency(steps[w] .. " STF")
neo.emergency("failed start:", steps[w])
neo.emergency(err)
table.insert(warnings, steps[w] .. " STF")
table.insert(warnings, err)
end
end
else
@ -437,12 +430,10 @@ end
-- System initialized
if finalPrompt() then
-- Safe Mode
local gpu = gpuG()
rstfbDraw(gpu)
basicDraw(gpu)
local nsm = neo.requestAccess("x.neo.sys.manage")
if nsm then
pcall(gpu.set, 2, 4, "Rebooting for Safe Mode...")
console = {"Rebooting for Safe Mode..."}
basicDraw()
for _, v in ipairs(nsm.listSettings()) do
if v ~= "password" then
nsm.delSetting(v)
@ -450,12 +441,14 @@ if finalPrompt() then
end
else
-- assume sysconf.lua did something very bad
pcall(gpu.set, 2, 4, "No NSM. Wiping configuration completely.")
console = {"No NSM. Wiping configuration completely."}
local fs = neo.requestAccess("c.filesystem")
if not fs then
pcall(gpu.set, 2, 4, "Failed to get permission, you're doomed.")
table.insert(console, "Failed to get permission, you're doomed.")
else
fs.primary.remove("/data/sys-glacier/sysconf.lua")
end
fs.primary.remove("/data/sys-glacier/sysconf.lua")
basicDraw()
end
-- Do not give anything a chance to alter the new configuration
shutdownEmergency(true)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,6 +1,10 @@
-- KittenOS N.E.O Kernel: "Tell Mettaton I said hi."
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- NOTE: local is considered unnecessary in kernel since 21 March
@ -134,7 +138,7 @@ function wrapMeta(t)
end,
__pairs = function ()
return function (x, key)
local k, v = next(t, k)
local k, v = next(t, key)
if k then return k, wrapMeta(v) end
end, 9, nil
end,
@ -166,7 +170,7 @@ function ensureType(a, t)
end
function ensurePathComponent(s)
if not string.match(s, "^[a-zA-Z0-9_%-%+%,%.%#%~%@%'%;%[%]%(%)%&%%%$%! %=%{%}%^]+$") then error("chars disallowed: " .. s) end
if not string.match(s, "^[a-zA-Z0-9_%-%+%,%.%#%~%@%'%;%[%]%(%)%&%%%$%! %=%{%}%^\x80-\xFF]+$") then error("chars disallowed: " .. s) end
if s == "." then error("single dot disallowed") end
if s == ".." then error("double dot disallowed") end
end
@ -302,7 +306,6 @@ baseProcEnvCore = {
utf8 = wrapUtf8,
require = loadLibraryInner,
assert = assert, ipairs = ipairs,
load = load,
next = function (t, k)
local mt = getmetatable(t)
if mt == uniqueNEOProtectionObject then error("NEO-Protected Object") end
@ -314,14 +317,15 @@ baseProcEnvCore = {
tonumber = tonumber, tostring = tostring,
setmetatable = setmetatable, getmetatable = function (n)
local mt = getmetatable(n)
if mt == uniqueNEOProtectionObject then return "NEO-Protected Object" end
if rawequal(mt, uniqueNEOProtectionObject) then return "NEO-Protected Object" end
return mt
end,
rawset = function (t, i, v)
local mt = getmetatable(t)
if mt == uniqueNEOProtectionObject then error("NEO-Protected Object") end
if rawequal(mt, uniqueNEOProtectionObject) then error("NEO-Protected Object") end
return rawset(t, i, v)
end, rawget = rawget, rawlen = rawlen, rawequal = rawequal,
checkArg = checkArg
}
baseProcNeo = {
emergency = emergencyFunction,
@ -357,6 +361,12 @@ baseProcNeoMT = {
function baseProcEnv()
local pe = setmetatable({}, baseProcEnvMT)
pe.load = function (a, b, c, d, ...)
if rawequal(d, nil) then
d = pe
end
return load(a, b, c, d, ...)
end
pe.neo = setmetatable({}, baseProcNeoMT)
pe._G = pe
return pe
@ -453,7 +463,7 @@ function retrieveAccess(perm, pkg, pid)
accesses[uid] = function (pkg, pid)
return nil
end
return function (f)
return function (f, secret)
-- Registration function
ensureType(f, "function")
local accessObjectCache = {}
@ -475,8 +485,10 @@ function retrieveAccess(perm, pkg, pid)
end
-- returns nil and fails
end
-- Announce registration
distEvent(nil, "k.registration", uid)
if not secret then
-- Announce registration
distEvent(nil, "k.registration", uid)
end
end, function ()
-- Registration becomes null (access is held but other processes cannot retrieve object)
if accesses[uid] then
@ -490,6 +502,7 @@ end
function start(pkg, ppkg, ppid, ...)
local proc = {}
local pid = lastPID
emergencyFunction("starting:", pkg)
lastPID = lastPID + 1
local function startFromUser(ipkg, ...)

View File

@ -1,254 +0,0 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- app-claw-core: assistant to app-claw
-- should only ever be one app-claw at a time
-- USING THIS LIBRARY OUTSIDE OF APP-CLAW IS A BAD IDEA.
-- SO DON'T DO IT.
-- Also serves to provide a mutex.
local lock = false
return function ()
if lock then
error("libclaw safety lock in use")
end
lock = true
local sourceList = {}
local sources = {}
-- 1 2 3 4 5 6
-- source ents: src dst index
-- dst entries: writeFile(fn), mkdir(fn), exists(fn), isDirectory(fn), remove(fn), rename(fna, fnb)
-- writeFile(fn) -> function (data/nil to close)
local function saveInfo(dn)
sources[dn][2][2]("data")
sources[dn][2][2]("data/app-claw")
local cb, _, r = sources[dn][2][1]("data/app-claw/local.lua")
if not cb then return false, r end
_, r = cb(require("serial").serialize(sources[dn][3]))
if not _ then return false, r end
_, r = cb(nil)
if not _ then return false, r end
return true
end
local remove, installTo, expandCSI, compressCSI
local function expandS(v)
return v:sub(2, v:byte(1) + 1), v:sub(v:byte(1) + 2)
end
local function expandT(v)
local t = {}
local n = v:byte(1)
v = v:sub(2)
for i = 1, n do
t[i], v = expandS(v)
end
return t, v
end
local function compressT(x)
local b = string.char(#x)
for _, v in ipairs(x) do
b = b .. string.char(#v) .. v
end
return b
end
local function expandCSI(v)
local t = {}
local k
k, v = expandS(v)
t.desc, v = expandS(v)
t.v = (v:byte(1) * 256) + v:byte(2)
v = v:sub(3)
t.dirs, v = expandT(v)
t.files, v = expandT(v)
t.deps, v = expandT(v)
return k, t, v
end
local function compressCSI(k, v)
local nifo = string.char(#k) .. k
nifo = nifo .. string.char(math.min(255, #v.desc)) .. v.desc:sub(1, 255)
nifo = nifo .. string.char(math.floor(v.v / 256), v.v % 256)
nifo = nifo .. compressT(v.dirs)
nifo = nifo .. compressT(v.files)
nifo = nifo .. compressT(v.deps)
return nifo
end
local function findPkg(idx, pkg, del)
del = del and ""
idx = sources[idx][3]
while #idx > 0 do
local k, d
k, d, idx = expandCSI(idx)
if del then
if k == pkg then
return d, del .. idx
end
del = del .. compressCSI(k, d)
else
if k == pkg then return d end
end
end
end
-- NOTE: Functions in this must return something due to the checked-call wrapper,
-- but should all use error() for consistency.
-- Operations
installTo = function (dstName, pkg, srcName, checked, yielder)
local errs = {}
if srcName == dstName then
error("Invalid API use")
end
-- preliminary checks
local srcPkg = findPkg(srcName, pkg, false)
assert(srcPkg)
if checked then
for _, v in ipairs(srcPkg.deps) do
if not findPkg(dstName, v) then
if not findPkg(srcName, v) then
table.insert(errs, pkg .. " depends on " .. v .. "\n")
elseif #errs == 0 then
installTo(dstName, v, srcName, checked, yielder)
else
table.insert(errs, pkg .. " depends on " .. v .. " (can autoinstall)\n")
end
end
end
end
-- Files from previous versions to get rid of
local ignFiles = {}
local oldDst = findPkg(dstName, pkg)
if oldDst then
for _, v in ipairs(oldDst.files) do
ignFiles[v] = true
end
end
oldDst = nil
for _, v in ipairs(srcPkg.files) do
if not ignFiles[v] then
if sources[dstName][2][3](v) then
table.insert(errs, v .. " already exists (corrupt system?)")
end
end
end
if #errs > 0 then
error(table.concat(errs))
end
for _, v in ipairs(srcPkg.dirs) do
sources[dstName][2][2](v)
if not sources[dstName][2][4](v) then
error("Unable to create directory " .. v)
end
end
for _, v in ipairs(srcPkg.files) do
local tmpOut, r, ok = sources[dstName][2][1](v .. ".claw-tmp")
ok = tmpOut
if ok then
ok, r = sources[srcName][1](v, tmpOut)
end
if ok then
yielder()
else
-- Cleanup...
for _, v in ipairs(srcPkg.files) do
sources[dstName][2][5](v .. ".claw-tmp")
end
error(r)
end
end
-- PAST THIS POINT, ERRORS CORRUPT!
-- Remove package from DB
local oldDst2, oldDst3 = findPkg(dstName, pkg, true)
sources[dstName][3] = oldDst3 or sources[dstName][3]
oldDst2, oldDst3 = nil
saveInfo(dstName)
-- Delete old files
for k, _ in pairs(ignFiles) do
yielder()
sources[dstName][2][5](k)
end
-- Create new files
for _, v in ipairs(srcPkg.files) do
yielder()
sources[dstName][2][6](v .. ".claw-tmp", v)
end
-- Insert into DB
sources[dstName][3] = sources[dstName][3] .. compressCSI(pkg, srcPkg)
saveInfo(dstName)
return true
end
remove = function (dstName, pkg, checked)
if checked then
local errs = {}
local buf = sources[dstName][3]
while #buf > 0 do
local dpsName, dpsV
dpsName, dpsV, buf = expandCSI(buf)
for _, v in ipairs(dpsV.deps) do
if v == pkg then
table.insert(errs, dpsName .. " depends on " .. pkg .. "\n")
end
end
end
if #errs > 0 then
return nil, table.concat(errs)
end
end
local dstPkg, nbuf = findPkg(dstName, pkg, true)
assert(dstPkg, "Package wasn't installed")
for _, v in ipairs(dstPkg.files) do
sources[dstName][2][5](v)
end
sources[dstName][3] = nbuf
saveInfo(dstName)
return true
end
return {
-- Gets the latest info, or if given a source just gives that source's info.
-- Do not modify output.
getInfo = function (pkg, source, oldest)
if source then return findPkg(source, pkg) end
local bestI = {
v = -1,
desc = "An unknown package.",
deps = {}
}
if oldest then bestI.v = 10000 end
for k, v in pairs(sources) do
local pkgv = findPkg(k, pkg)
if pkgv then
if ((not oldest) and (pkgv.v > bestI.v)) or (oldest and (pkgv.v > bestI.v)) then
bestI = pkgv
end
end
end
return bestI
end,
sources = sources,
sourceList = sourceList,
remove = remove,
installTo = installTo,
expandCSI = expandCSI,
compressCSI = compressCSI,
-- Gets a total list of packages, as a table of strings. You can modify output.
getList = function ()
local n = {}
local seen = {}
for k, v in pairs(sources) do
local p3 = v[3]
while #p3 > 0 do
local kb, _, nx = expandCSI(p3)
p3 = nx
if not seen[kb] then
seen[kb] = true
table.insert(n, kb)
end
end
end
table.sort(n)
return n
end,
unlock = function ()
lock = false
end
}
end

View File

@ -1,28 +0,0 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- app-claw-csi: addSource function
-- USING THIS LIBRARY OUTSIDE OF APP-CLAW IS A BAD IDEA.
-- SO DON'T DO IT.
-- NOTE: If a source is writable, it's added anyway despite any problems.
return function (claw, name, src, dst)
local ifo = ""
local ifok, e = src("data/app-claw/local.lua", function (t)
ifo = ifo .. (t or "")
return true
end)
e = e or "local.lua parse error"
ifo = ifok and require("serial").deserialize(ifo)
if not (dst or ifo) then return false, e end
table.insert(claw.sourceList, {name, not not dst})
local nifo = ifo or ""
if type(nifo) == "table" then
nifo = ""
for k, v in pairs(ifo) do
nifo = nifo .. claw.compressCSI(k, v)
end
end
claw.sources[name] = {src, dst, nifo}
return not not ifo, e
end

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- bmp: Portable OC BMP/DIB library
-- Flexible: Reading can be set to
@ -153,20 +157,8 @@ return {
local planes = get16(0x1A)
local bpp = get16(0x1C)
local compression = get32(0x1E)
local paletteCol = 0
local other = get32(0x2E)
paletteCol = other
-- postprocess
local paletteCol = get32(0x2E)
-- The actual values used for addressing, for cMode to mess with
local basePtr = 14 + hdrSize + (paletteCol * 4)
local scanWB = math.ceil((bpp * width) / 32) * 4
local monoWB = (math.ceil((bpp * width) / 32) * 4)
local planeWB = scanWB * height
if not packed then
basePtr = get32(0x0A) -- 'BM' header
end
-- negative height means sane coords
local upDown = true
if height >= 0x80000000 then
@ -175,6 +167,18 @@ return {
upDown = false
end
-- postprocess
-- The actual values used for addressing, for cMode to mess with
local basePtr = 14 + hdrSize + (paletteCol * 4)
local scanWB = math.ceil((bpp * width) / 32) * 4
local monoWB = math.ceil(width / 32) * 4
local planeWB = scanWB * height
if not packed then
basePtr = get32(0x0A) -- 'BM' header
end
-- Cursor/Icon
if cMode then
height = math.floor(height / 2)

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- Braille Converter & NeoUX component
-- While the Neoux part isn't OS-independent,

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- event: Implements pull/listen semantics in a consistent way for a given process.
-- This is similar in API to OpenOS's event framework, but is per-process.

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
local fmt
fmt = {

77
code/libs/lineedit.lua Normal file
View File

@ -0,0 +1,77 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
return {
-- note: everything must already be unicode.safeTextFormat'd
draw = function (sW, line, cursorX, rX)
-- if no camera, provide a default
rX = rX or math.max(1, (cursorX or 1) - math.floor(sW * 2 / 3))
-- transform into area-relative
local tl = unicode.sub(line, rX, rX + sW - 1)
-- inject cursor
if cursorX then
cursorX = (cursorX - rX) + 1
if cursorX >= 1 then
if cursorX <= sW then
tl = unicode.sub(tl, 1, cursorX - 1) .. "_" .. unicode.sub(tl, cursorX + 1)
end
end
end
return tl .. (" "):rep(sW - unicode.len(tl))
end,
clamp = function (tl, cursorX)
tl = unicode.len(tl)
if tl < cursorX - 1 then
return tl + 1
end
return cursorX
end,
-- returns line, cursorX, special
-- return values may be nil if irrelevant
key = function (ks, kc, line, cursorX)
local cS = unicode.sub(line, 1, cursorX - 1)
local cE = unicode.sub(line, cursorX)
local ll = unicode.len(line)
if kc == 203 then -- navi <
if cursorX > 1 then
return nil, cursorX - 1
else
-- cline underflow
return nil, nil, "l<"
end
elseif kc == 205 then -- navi >
if cursorX > ll then
-- cline overflow
return nil, nil, "l>"
end
return nil, cursorX + 1
elseif kc == 199 then -- home
return nil, 1
elseif kc == 207 then -- end
return nil, unicode.len(line) + 1
elseif ks == "\8" then -- del
if cursorX == 1 then
-- weld prev
return nil, nil, "w<"
else
return unicode.sub(cS, 1, unicode.len(cS) - 1) .. cE, cursorX - 1
end
elseif kc == 211 then -- del
if cursorX > ll then
return nil, nil, "w>"
end
return cS .. unicode.sub(cE, 2)
elseif ks then -- standard letters
if ks == "\r" then
-- new line
return nil, nil, "nl"
end
return cS .. ks .. cE, cursorX + unicode.len(ks)
end
-- :(
end
}

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- neoux: Implements utilities on top of Everest & event:
-- Everest crash protection
@ -25,7 +29,7 @@ newNeoux = function (event, neo)
end
end)
local neoux = {}
neoux.fileDialog = function (forWrite, callback)
neoux.fileDialog = function (forWrite, callback, dfn)
local sync = false
local rtt = nil
if not callback then
@ -35,7 +39,7 @@ newNeoux = function (event, neo)
rtt = rt
end
end
local tag = neo.requireAccess("x.neo.pub.base", "filedialog").showFileDialogAsync(forWrite)
local tag = neo.requireAccess("x.neo.pub.base", "filedialog").showFileDialogAsync(forWrite, dfn)
local f
f = function (_, fd, tg, re)
if fd == "filedialog" then
@ -339,6 +343,8 @@ newNeoux = function (event, neo)
end
-- Note: w should be at least 2 - this is similar to buttons.
neoux.tcfield = function (x, y, w, textprop)
-- compat. workaround for apps which nuke tcfields
local p = unicode.len(textprop()) + 1
return {
x = x,
y = y,
@ -347,22 +353,24 @@ newNeoux = function (event, neo)
selectable = true,
key = function (window, update, a, c, d, f)
if d then
local ot = textprop()
local le = require("lineedit")
p = le.clamp(ot, p)
if c == 63 then
neo.requireAccess("x.neo.pub.globals", "clipboard").setSetting("clipboard", textprop())
neo.requireAccess("x.neo.pub.globals", "clipboard").setSetting("clipboard", ot)
elseif c == 64 then
local contents = neo.requireAccess("x.neo.pub.globals", "clipboard").getSetting("clipboard")
contents = contents:match("^[^\r\n]*")
textprop(contents)
update()
elseif a == 8 then
local str = textprop()
textprop(unicode.sub(str, 1, unicode.len(str) - 1))
update()
return true
elseif a >= 32 then
textprop(textprop() .. unicode.char(a))
update()
return true
elseif a ~= 9 then
local lT, lC, lX = le.key(a ~= 0 and unicode.char(a), c, ot, p)
if lT or lC then
if lT then textprop(lT) end
p = lC or p
update()
return true
end
end
end
end,
@ -377,9 +385,10 @@ newNeoux = function (event, neo)
fg = bg
bg = fg1
end
local text = unicode.safeTextFormat(textprop())
text = "[" .. neoux.pad(text, w - 2, false, true, true) .. "]"
window.span(x, y, text, bg, fg)
local t, e, r = textprop(), require("lineedit")
p = e.clamp(t, p)
t, r = unicode.safeTextFormat(t, p)
window.span(x, y, "[" .. e.draw(w - 2, t, selected and r) .. "]", bg, fg)
end
}
end

View File

@ -1,5 +1,10 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
local doSerialize = nil
function doSerialize(s)
if type(s) == "table" then

View File

@ -1,8 +1,12 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- just don't bother with proper indent here
return function (event, nexus, retFunc, fs, pkg, mode)
return function (event, nexus, retFunc, fs, pkg, mode, defName)
local fmt = require("fmttext")
local class = "manage"
@ -141,7 +145,7 @@ end
nexus.create(w, h, class .. " " .. pkg, function (w, ev, a, b, c)
if not wnd then
wnd = w
prepareNode(require("sys-filevfs")(fs, mode))
prepareNode(require("sys-filevfs")(fs, mode, defName))
end
if ev == "key" then
key2(a, b, c)

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- Used by filedialog to provide a sane relative environment.
-- Essentially, the filedialog is just a 'thin' UI wrapper over this.
@ -16,12 +20,12 @@ end
local getFsNode, getRoot
local setupCopyNode
function setupCopyNode(parent, myRoot, op, complete, impliedName)
function setupCopyNode(parent, myRoot, op, complete)
local function handleResult(aRes, res)
if aRes then
return complete(res, true)
else
return nil, setupCopyNode(parent, res, op, complete, impliedName)
return nil, setupCopyNode(parent, res, op, complete)
end
end
return {
@ -32,11 +36,6 @@ function setupCopyNode(parent, myRoot, op, complete, impliedName)
complete(nil, false)
return false, parent
end})
if impliedName and myRoot.unknownAvailable then
table.insert(l, {"Implied: " .. impliedName, function ()
return handleResult(myRoot.selectUnknown(impliedName))
end})
end
for _, v in ipairs(myRoot.list()) do
table.insert(l, {v[1], function ()
return handleResult(v[2]())
@ -54,7 +53,7 @@ local function setupCopyVirtualEnvironment(fs, parent, fwrap, impliedName)
if not fwrap then
return false, dialog("Could not open source", parent)
end
local myRoot = getRoot(fs, true)
local myRoot = getRoot(fs, true, impliedName)
-- Setup wrapping node
return setupCopyNode(parent, myRoot, "Copy", function (fwrap2, intent)
if not fwrap2 then
@ -72,14 +71,22 @@ local function setupCopyVirtualEnvironment(fs, parent, fwrap, impliedName)
fwrap.close()
fwrap2.close()
return false, dialog("Completed copy.", parent)
end, impliedName)
end)
end
function getFsNode(fs, parent, fsc, path, mode)
function getFsNode(fs, parent, fsc, path, mode, impliedName)
local va = fsc.address:sub(1, 4)
local fscrw = not fsc.isReadOnly()
local dir = path:sub(#path, #path) == "/"
local confirmedDel = false
local t
local function selectUnknown(text)
-- Relies on text being nil if used in leaf node
local rt, re = require("sys-filewrap").create(fsc, path .. (text or ""), mode)
if not rt then
return false, dialog("Open Error: " .. tostring(re), parent)
end
return true, rt
end
t = {
name = ((dir and "DIR: ") or "FILE: ") .. va .. path,
list = function ()
@ -91,11 +98,25 @@ function getFsNode(fs, parent, fsc, path, mode)
for k, v in ipairs(fsc.list(path)) do
local nm = "F: " .. v
local fp = path .. v
if fsc.isDirectory(fp) then
local cDir = fsc.isDirectory(fp)
if cDir then
nm = "D: " .. v
end
n[k + 1] = {nm, function () return nil, getFsNode(fs, t, fsc, fp, mode) end}
if (not cDir) and (fscrw or mode == false) and (mode ~= nil) then
local vn = v
n[k + 1] = {nm, function () return selectUnknown(vn) end}
else
n[k + 1] = {nm, function () return nil, getFsNode(fs, t, fsc, fp, mode, impliedName) end}
end
end
else
table.insert(n, {"Copy", function ()
local rt, re = require("sys-filewrap").create(fsc, path, false)
if not rt then
return false, dialog("Open Error: " .. tostring(re), parent)
end
return nil, setupCopyVirtualEnvironment(fs, parent, rt, path:match("[^/]*$") or "")
end})
end
if fscrw then
if dir then
@ -114,31 +135,6 @@ function getFsNode(fs, parent, fsc, path, mode)
end
}
end})
else
if mode ~= nil then
local tx = "Open"
if mode == true then
tx = "Save"
elseif mode == "append" then
tx = "Append"
end
if fscrw or mode == false then
table.insert(n, {tx, function ()
local rt, re = require("sys-filewrap").create(fsc, path, mode)
if not rt then
return false, dialog("Open Error: " .. tostring(re), parent)
end
return true, rt
end})
end
end
table.insert(n, {"Copy", function ()
local rt, re = require("sys-filewrap").create(fsc, path, false)
if not rt then
return false, dialog("Open Error: " .. tostring(re), parent)
end
return nil, setupCopyVirtualEnvironment(fs, parent, rt, path:match("[^/]*$") or "")
end})
end
if path ~= "/" then
local delText = "Delete"
@ -172,20 +168,20 @@ function getFsNode(fs, parent, fsc, path, mode)
end})
end
end
if not dir then
elseif impliedName then
table.insert(n, {"Implied: " .. impliedName, function ()
return selectUnknown(impliedName)
end})
end
return n
end,
unknownAvailable = dir and (mode ~= nil) and ((mode == false) or fscrw),
selectUnknown = function (text)
local rt, re = require("sys-filewrap").create(fsc, path .. text, mode)
if not rt then
return false, dialog("Open Error: " .. tostring(re), parent)
end
return true, rt
end
selectUnknown = selectUnknown
}
return t
end
function getRoot(fs, mode)
function getRoot(fs, mode, defName)
local t
t = {
name = "DRVS:",
@ -209,7 +205,7 @@ function getRoot(fs, mode)
id = "RW " .. amount .. "% " .. mb .. "M " .. id
end
table.insert(l, {fsi.address:sub(1, 4) .. " " .. id, function ()
return nil, getFsNode(fs, t, fsi, "/", mode)
return nil, getFsNode(fs, t, fsi, "/", mode, defName)
end})
end
return l

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
local function ensureMode(mode)
local n = "rb"

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
return function (
gpus, screens,
@ -9,13 +13,12 @@ return function (
address
)
neo.ensureType(address, "string")
for _, monitor in ipairs(monitorPool) do
for k, monitor in ipairs(monitorPool) do
if monitor.address == address then
-- find GPU
local gpu, bestStats = nil, {-math.huge, -math.huge, -math.huge}
currentGPUBinding = {}
for k, _ in pairs(currentGPUBinding) do
currentGPUBinding[k] = nil
for a, _ in pairs(currentGPUBinding) do
currentGPUBinding[a] = nil
end
for v in gpus() do
v.bind(monitor.address, false)
@ -96,7 +99,7 @@ return function (
return v, didBind
end
end
end, v
end, monitor
end
end
end

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- CRITICAL FILE!
-- This file defines how your KittenOS NEO system responds to access requests.
@ -11,21 +15,28 @@
-- IRC is usually pretty safe, but no guarantees.
-- Returns "allow", "deny", or "ask".
local function actualPolicy(pkg, pid, perm, matchesSvc)
local function actualPolicy(pkg, pid, perm, pkgSvcPfx)
-- System stuff is allowed.
if pkg:sub(1, 4) == "sys-" then
return "allow"
end
-- svc-t's job is solely to emulate terminals
-- TO INSTALL YOUR OWN TERMINAL EMULATOR:
-- perm|app-yourterm|r.neo.t
if pkg == "svc-t" and perm == "r.neo.pub.t" then
return "allow"
end
-- <The following is for apps & services>
-- x.neo.pub (aka Icecap) is open to all
-- x.neo.pub.* is open to all
if perm:sub(1, 10) == "x.neo.pub." then
return "allow"
end
-- These signals are harmless, though they identify HW (as does everything in OC...)
if perm == "s.h.component_added" or perm == "s.h.component_removed" then
if perm == "s.h.component_added" or perm == "s.h.component_removed" or perm == "s.h.tablet_use" or perm == "c.tablet" then
return "allow"
end
if matchesSvc("r.", pkg, perm) then
-- Userlevel can register for itself
if perm == "r." .. pkgSvcPfx then
return "allow"
end
-- Userlevel has no other registration rights
@ -44,8 +55,8 @@ local function actualPolicy(pkg, pid, perm, matchesSvc)
return "ask"
end
return function (nexus, settings, pkg, pid, perm, rsp, matchesSvc)
local res = actualPolicy(pkg, pid, perm, matchesSvc)
return function (nexus, settings, pkg, pid, perm, rsp, pkgSvcPfx)
local res = actualPolicy(pkg, pid, perm, pkgSvcPfx)
if settings then
res = settings.getSetting("perm|" .. pkg .. "|" .. perm) or
settings.getSetting("perm|*|" .. perm) or res

View File

@ -1,61 +0,0 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- BDIVIDE
-- format:
-- 0-127 for constants
-- <block + 128>, <(len - 3) * 2, + lowest bit is upper bit of position>, <position - 1>
io.write("\x00") -- initiation character
local blockCache = {}
local window = 0
local blockUse = 128
for i = 128, 128 + blockUse - 1 do
blockCache[i] = ("\x00"):rep(512)
end
local function runBlock(blk)
-- firstly, get current block index
local blockIndex = window + 128
window = (window + 1) % blockUse
blockCache[blockIndex] = ""
-- ok, now work on the problem
local i = 1
while i <= #blk do
local bestData = blk:sub(i, i)
local bestRes = bestData
local bestScore = 1
for bid = 128, 128 + blockUse - 1 do
for lm = 0, 127 do
local al = lm + 3
local pfx = blk:sub(i, i + al - 1)
if #pfx ~= al then
break
end
local p = blockCache[bid]:find(pfx, 1, true)
if not p then
break
end
local score = al / 3
if score > bestScore then
bestData = string.char(bid) .. string.char((lm * 2) + math.floor((p - 1) / 256)) .. string.char((p - 1) % 256)
bestRes = pfx
bestScore = score
end
end
end
-- ok, encode!
io.write(bestData)
blockCache[blockIndex] = blockCache[blockIndex] .. bestRes
i = i + #bestRes
end
end
while 1 do
local blkd = io.read(512)
runBlock(blkd)
if #blkd < 512 then
return
end
end

View File

@ -1,73 +0,0 @@
-- This is released into the public domain. XX
-- No warranty is provided, implied or otherwise. XX
local sector = io.write -- XX
-- BUNDIVIDE reference implementation for integration XX
local Cs,Cbu,Cb,Cw,Cp,Ct,Ci,CP,CB,CD={},128,"",128,"",""
CP = function(d,b)
Ct = Ct .. d
while#Ct>2 do
b = Ct:byte()
Ct = Ct:sub(2)
if b == 127 then
b = Ct:byte()
Ct = Ct:sub(2)
if b == 127 then
b = Ct:byte()+254
if b > 255then
b = b - 256
end
Ct = Ct:sub(2)
else
b = b + 127
end
end
Cp = Cp..string.char(b)
if #Cp == 512 then
sector(Cp)
Cp = ""
end
end
end
for i = 128, 127 + Cbu do
Cs[i] = ("\x00"):rep(512)
end
Cs[Cw] = ""
CB = function (d, i, d2, x, y)
i=1
while i <= #d - 2 do
b=d:byte(i)
d2=d:sub(i,i)
i=i+1
if not Ci then
if b==0then
Ci=1
end
else
if b >= 128 then
x = d:byte(i)
i = i + 1
y=d:byte(i) + ((x % 2) * 256)
i = i + 1
d2=Cs[b]:sub(y + 1,y + 3 + math.floor(x / 2))
end
Cs[Cw]=Cs[Cw]..d2
if #Cs[Cw]>=512 then
CP(Cs[Cw])
Cw=((Cw-127)%Cbu)+128
Cs[Cw]=""
end
end
end
return i
end
CD = function(d)
Cb = Cb .. d
Cb = Cb:sub(CB(Cb))
end
CD(io.read("*a")) -- XX
--D.remove("init-bdivide.lua")--
--D.rename("init.lua","init-bdivide.lua")--
--local Ch=D.open("init-bdivide.lua","rb")--
--dieCB=function()D.close(Ch)D.remove("init-bdivide.lua")end--
--while true do local t=D.read(Ch, 64)if not t then break end CD(t)end--
CD("\x00\x00")CP(Cs[Cw] .. "\x00\x00")

View File

@ -1,20 +0,0 @@
-- PREPROC: preprocess input to be 7-bit
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
while true do
local c = io.read(1)
if not c then return end
local bc = c:byte()
if bc < 127 then
io.write(c)
else
if bc <= 253 then
-- 127(0) through 253(126)
io.write("\x7F" .. string.char(bc - 127))
else
-- 254(0) through 255 (1)
io.write("\x7F\x7F" .. string.char(bc - 254))
end
end
end

View File

@ -1,4 +1,11 @@
-- KittenOS NEO Repository Compliance Check Tool
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- I'm still not a lawyer
local filesAccountedFor = {
["repository/data/app-claw/local.lua"] = 0,

View File

@ -1,27 +0,0 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- arg is the size of the code.tar file
local arg = ...
os.execute("lua com2/preproc.lua < code.tar | lua com2/bdivide.lua > com2/code.tar.bd")
os.execute("cat insthead.lua")
print("sC=" .. math.ceil(tonumber(arg) / 512))
local src = io.open("com2/bundiv.lua", "r")
while true do
local line = src:read()
if not line then
io.flush()
src:close()
return
end
local endix = line:sub(#line-1,#line)
if endix ~= "XX" then
if endix == "--" then
-- included
print(line:sub(3,#line-2))
else
print(line)
end
end
-- XX means ignored.
end

View File

@ -1,34 +0,0 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Imitation CLAW
local done = {}
local f, e = loadfile("code/data/app-claw/local.lua")
if not f then error(e) end
f = f()
if not os.execute("mkdir work") then
error("Delete 'work'")
end
for k, v in pairs(f) do
for _, vd in ipairs(v.dirs) do
os.execute("mkdir work/" .. vd .. " 2> /dev/null")
end
for _, vf in ipairs(v.files) do
-- not totally proofed but will do
if not os.execute("cp code/" .. vf .. " work/" .. vf) then
error("Could not copy " .. vf .. " in " .. k)
end
if done[vf] then
error("duplicate " .. vf .. " in " .. k)
end
print(vf .. "\t\t" .. k)
done[vf] = true
end
end
os.execute("mkdir -p work/data/app-claw")
os.execute("cp code/data/app-claw/local.lua work/data/app-claw/local.lua")
os.execute("cd code ; find . > ../imitclaw.treecode")
os.execute("cd work ; find . > ../imitclaw.treework")
os.execute("diff -u imitclaw.treecode imitclaw.treework")
os.execute("rm imitclaw.treecode imitclaw.treework")

40
inst/bdivide/bga.lua Normal file
View File

@ -0,0 +1,40 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
local bga = {}
local str = io.read("*a")
for i = 1, #str - 1 do
local bg = str:sub(i, i + 1)
bga[bg] = (bga[bg] or 0) + 1
end
local first = {}
local second = {}
local mode = ...
for k, v in pairs(bga) do
if mode == "combined" then
print(string.format("%08i: %02x%02x : %s", v, k:byte(1), k:byte(2), k))
end
first[k:sub(1, 1)] = (first[k:sub(1, 1)] or 0) + v
second[k:sub(1, 1)] = (second[k:sub(1, 1)] or 0) + v
end
for k, v in pairs(first) do
if mode == "first" then
print(string.format("%08i: %s", v, k))
end
end
for k, v in pairs(second) do
if mode == "second" then
print(string.format("%08i: %s", v, k))
end
end

89
inst/bdivide/compress.lua Normal file
View File

@ -0,0 +1,89 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- PREPROC (r9 edition): preprocess input to be 7-bit
local frw = require("libs.frw")
local
-- SHARED WITH DECOMPRESSION ENGINE
function p(x, y)
if x == 126 then
return string.char(y), 3
elseif x == 127 then
return string.char(128 + y), 3
elseif x >= 32 then
return string.char(x), 2
elseif x == 31 then
return "\n", 2
elseif x == 30 then
return "\x00", 2
end
return string.char(("enart"):byte(x % 5 + 1), ("ndtelh"):byte((x - x % 5) / 5 + 1)), 2
end
local preprocParts = {}
local preprocMaxLen = 0
for i = 0, 127 do
for j = 0, 127 do
local d, l = p(i, j)
if d then
preprocMaxLen = math.max(preprocMaxLen, #d)
l = l - 1
if (not preprocParts[d]) or (#preprocParts[d] > l) then
if l == 2 then
preprocParts[d] = string.char(i, j)
else
preprocParts[d] = string.char(i)
end
end
end
end
end
local function preprocWithPadding(blk, p)
local out = ""
local needsPadding = false
while blk ~= "" do
p(blk)
local len = math.min(preprocMaxLen, #blk)
while len > 0 do
local seg = blk:sub(1, len)
if preprocParts[seg] then
out = out .. preprocParts[seg]
needsPadding = #preprocParts[seg] < 2
blk = blk:sub(#seg + 1)
break
end
len = len - 1
end
assert(len ~= 0)
end
-- This needsPadding bit is just sort of quickly added in
-- to keep this part properly maintained
-- even though it might never get used
if needsPadding then
return out .. "\x00"
end
return out
end
local bdCore = require("bdivide.core")
return function (data, lexCrunch)
io.stderr:write("preproc: ")
local pi = frw.progress()
local function p(b)
pi(1 - (#b / #data))
end
data = preprocWithPadding(data, p)
io.stderr:write("\nbdivide: ")
pi = frw.progress()
data = bdCore.bdividePad(bdCore.bdivide(data, p))
io.stderr:write("\n")
return lexCrunch.process(frw.read("bdivide/instdeco.lua"), {}), data
end

76
inst/bdivide/core.lua Normal file
View File

@ -0,0 +1,76 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- BDIVIDE r5 edition
-- Algorithm simplified for smaller implementation and potentially better compression
-- format:
-- 0-127 for constants
-- <128 + (length - 4)>, <position high>, <position low>
-- Position is where in the window it was found, minus 1.
-- windowSize must be the same between the encoder and decoder,
-- and is the amount of data preserved after cropping.
local bdivCore = {}
function bdivCore.bdivide(blk, p)
local out = ""
local windowSize = 0x10000
local windowData = ("\x00"):rep(windowSize)
while blk ~= "" do
p(blk)
local bestData = blk:sub(1, 1)
assert(blk:byte() < 128, "BDIVIDE does not handle 8-bit data")
local bestRes = bestData
for lm = 0, 127 do
local al = lm + 4
local pfx = blk:sub(1, al)
if #pfx ~= al then
break
end
local p = windowData:find(pfx, 1, true)
if not p then
break
end
local pm = p - 1
local thirdByte = pm % 256
bestData = string.char(128 + lm, math.floor(pm / 256), thirdByte)
bestRes = pfx
end
-- ok, encode!
out = out .. bestData
-- crop window
windowData = (windowData .. bestRes):sub(-windowSize)
blk = blk:sub(#bestRes + 1)
end
return out
end
-- Adds padding if required
function bdivCore.bdividePad(data)
local i = 1
-- Basically, if it ends on a literal,
-- then the literal won't get read without two padding bytes.
-- Otherwise (including if no data) it's fine.
local needsPadding = false
while i <= #data do
if data:byte(i) > 127 then
i = i + 3
needsPadding = false
else
i = i + 1
needsPadding = true
end
end
if needsPadding then
return data .. "\x00\x00"
end
return data
end
return bdivCore

68
inst/bdivide/instdeco.lua Normal file
View File

@ -0,0 +1,68 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- BDIVIDE (r5 edition) and PREPROC (r9 edition)
-- decompression engine for installer
$bdPPBuffer = ""
$bdBDBuffer = ""
$bdBDWindow = ("\x00"):rep(2^16)
-- High-level breakdown:
-- q is unescaper.
-- It'll only begin to input if at least 3 bytes are available,
-- so you'll want to throw in 2 extra zeroes at the end of stream as done here.
-- It uses t (input buffer) and p (output buffer).
-- Ignore its second argument, as that's a lie, it's just there for a local.
-- L is the actual decompressor. It has the same quirk as q, wanting two more bytes.
-- It stores to c (compressed), and w (window).
-- It outputs that which goes to the window to q also.
-- And it also uses a fake local.
-- SEE compress.lua FOR THIS FUNCTION
function $bdPP(x, y)
if x == 126 then
return string.char(y), 3
elseif x == 127 then
return string.char(128 + y), 3
elseif x >= 32 then
return string.char(x), 2
elseif x == 31 then
return "\n", 2
elseif x == 30 then
return "\x00", 2
end
return string.char(("enart"):byte(x % 5 + 1), ("ndtelh"):byte((x - x % 5) / 5 + 1)), 2
end
${
function $engineInput($L|lData)
$bdBDBuffer = $bdBDBuffer .. $lData
while #$bdBDBuffer > 2 do
$lData = $bdBDBuffer:byte()
if $lData < 128 then
$lData = $bdBDBuffer:sub(1, 1)
$bdBDBuffer = $bdBDBuffer:sub(2)
else
${
$L|bdBDPtr = $bdBDBuffer:byte(2) * 256 + $bdBDBuffer:byte(3) + 1
$lData = $bdBDWindow:sub($bdBDPtr, $bdBDPtr + $lData - 125)
$bdBDBuffer = $bdBDBuffer:sub(4)
$}
end
$bdPPBuffer = $bdPPBuffer .. $lData
$bdBDWindow = ($bdBDWindow .. $lData):sub(-2^16)
while #$bdPPBuffer > 1 do
${
$lData, $L|bdPPAdv = $bdPP($bdPPBuffer:byte(), $bdPPBuffer:byte(2))
$bdPPBuffer = $bdPPBuffer:sub($bdPPAdv)
$}
$engineOutput($lData)
end
end
end
$}

22
inst/bdvlite/compress.lua Normal file
View File

@ -0,0 +1,22 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
local frw = require("libs.frw")
local bdCore = require("bdivide.core")
return function (data, lexCrunch)
io.stderr:write("\nbdivide: ")
local pi = frw.progress()
local function p(b)
pi(1 - (#b / #data))
end
data = bdCore.bdividePad(bdCore.bdivide(data, p))
io.stderr:write("\n")
return lexCrunch.process(frw.read("bdvlite/instdeco.lua"), {}), data
end

76
inst/bdvlite/core.lua Normal file
View File

@ -0,0 +1,76 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- BDIVIDE r5 edition
-- Algorithm simplified for smaller implementation and potentially better compression
-- format:
-- 0-127 for constants
-- <128 + (length - 4)>, <position high>, <position low>
-- Position is where in the window it was found, minus 1.
-- windowSize must be the same between the encoder and decoder,
-- and is the amount of data preserved after cropping.
local bdivCore = {}
function bdivCore.bdivide(blk, p)
local out = ""
local windowSize = 0x10000
local windowData = ("\x00"):rep(windowSize)
while blk ~= "" do
p(blk)
local bestData = blk:sub(1, 1)
assert(blk:byte() < 128, "BDIVIDE does not handle 8-bit data")
local bestRes = bestData
for lm = 0, 127 do
local al = lm + 4
local pfx = blk:sub(1, al)
if #pfx ~= al then
break
end
local p = windowData:find(pfx, 1, true)
if not p then
break
end
local pm = p - 1
local thirdByte = pm % 256
bestData = string.char(128 + lm, math.floor(pm / 256), thirdByte)
bestRes = pfx
end
-- ok, encode!
out = out .. bestData
-- crop window
windowData = (windowData .. bestRes):sub(-windowSize)
blk = blk:sub(#bestRes + 1)
end
return out
end
-- Adds padding if required
function bdivCore.bdividePad(data)
local i = 1
-- Basically, if it ends on a literal,
-- then the literal won't get read without two padding bytes.
-- Otherwise (including if no data) it's fine.
local needsPadding = false
while i <= #data do
if data:byte(i) > 127 then
i = i + 3
needsPadding = false
else
i = i + 1
needsPadding = true
end
end
if needsPadding then
return data .. "\x00\x00"
end
return data
end
return bdivCore

34
inst/bdvlite/instdeco.lua Normal file
View File

@ -0,0 +1,34 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- BDIVIDE (r5 edition)
-- decompression engine used to decompress DEFLATE decompression engine
$bdBDBuffer = ""
$bdBDWindow = ("\x00"):rep(2^16)
${
function $engineInput($L|lData)
$bdBDBuffer = $bdBDBuffer .. $lData
while #$bdBDBuffer > 2 do
$lData = $bdBDBuffer:byte()
if $lData < 128 then
$lData = $bdBDBuffer:sub(1, 1)
$bdBDBuffer = $bdBDBuffer:sub(2)
else
${
$L|bdBDPtr = $bdBDBuffer:byte(2) * 256 + $bdBDBuffer:byte(3) + 1
$lData = $bdBDWindow:sub($bdBDPtr, $bdBDPtr + $lData - 125)
$bdBDBuffer = $bdBDBuffer:sub(4)
$}
end
$bdBDWindow = ($bdBDWindow .. $lData):sub(-2^16)
$engineOutput($lData)
end
end
$}

82
inst/build.lua Normal file
View File

@ -0,0 +1,82 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- KittenOS NEO Installer Generator --
local args = {...}
local cid = args[1]
local tarName = args[2]
local algorithmsInReverseOrder = {}
for i = 3, #args do
table.insert(algorithmsInReverseOrder, 1, args[i])
end
local u = require("libs.frw")
local instSize = 0
local function put(data)
io.write(data)
instSize = instSize + #data
end
-- Installer Lexcrunch Context --
local lexCrunch = require("libs.lexcrunch")()
-- Installer Core --
-- installerFinalized:
-- Stuff that's already finished and put at the end of RISM. Prepend to this.
-- installerPayload / installerProgramLength:
-- The next-outer chunk that hasn't been written to the end of RISM
-- as the compression scheme (if one) has not been applied yet.
-- Really, installerProgramLength is only necessary because of the innermost chunk,
-- as that chunk has the TAR; additional data that's part of the same effective compression block,
-- but requires the program length to avoid it.
local installerPayload
local installerProgramLength
local installerFinalized = ""
do
local tarData = u.read(tarName)
local tarSectors = math.floor(#tarData / 512)
local installerCore = lexCrunch.process(u.read("instcore.lua"), {["$$SECTORS"] = tostring(tarSectors)})
installerPayload = installerCore .. tarData
installerProgramLength = #installerCore
end
-- Installer Compression --
for _, v in ipairs(algorithmsInReverseOrder) do
io.stderr:write("compressing (" .. v .. ")\n")
local algImpl = require(v .. ".compress")
local algEngine, algData = algImpl(installerPayload, lexCrunch)
io.stderr:write("result: " .. #installerPayload .. " -> " .. #algData .. "\n")
-- prepend the program length of the last section
algEngine = lexCrunch.process("$iBlockingLen = " .. installerProgramLength .. " " .. algEngine, {})
-- commit
installerPayload = algEngine
installerProgramLength = #installerPayload
installerFinalized = algData .. installerFinalized
end
-- Installer Final --
-- This is a special case, so the program length/payload/etc. business has to be repeated.
put("--" .. cid .. "\n")
put("--This is released into the public domain. No warranty is provided, implied or otherwise.\n")
put(lexCrunch.process(u.read("insthead.lua"), {["$$CORESIZE"] = tostring(installerProgramLength)}))
local RISM = installerPayload .. installerFinalized
RISM = RISM:gsub("\xFE", "\xFE\xFE")
RISM = RISM:gsub("]]", "]\xFE]")
RISM = "\x00" .. RISM
put("--[[" .. RISM .. "]]")
-- Dumping debug info --
local dbg = io.open("iSymTab", "wb")
lexCrunch.dump(dbg)
dbg:close()

18
inst/deflate/compress.lua Normal file
View File

@ -0,0 +1,18 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- This is a wrapper around (i.e. does not contain) Zopfli.
local frw = require("libs.frw")
return function (data, lexCrunch)
frw.write("tempData.bin", data)
os.execute("zopfli --i1 --deflate -c tempData.bin > tempZopf.bin")
local res = frw.read("tempZopf.bin")
os.execute("rm tempData.bin tempZopf.bin")
return lexCrunch.process(frw.read("deflate/instdeco.lua"), {}), res
end

291
inst/deflate/instdeco.lua Normal file
View File

@ -0,0 +1,291 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- THIS NEXT LINE IS CLEARLY AWFUL
$bdBDWindow = nil
-- THE DEFLATE DECOMPRESSOR OF MADNESS --
-- Core I/O --
-- $dfAlignToByteRemaining is
-- set to 8 in the outer engine
${
function $dfGetIntField($L|lLen, $L|lVal)
$lVal = 0
for $L|lI = 0, $lLen - 1 do
if coroutine.yield() then
$lVal = $lVal + 2^$lI
end
end
return $lVal
end
$}
-- Huffman Core --
-- The approach here is based around 1-prefixed integers.
-- These are stored in a flat table.
-- So 0b1000 is the pattern 000.
${
function $dfReadHuffmanSymbol($L|lTree, $L|lVal)
$lVal = 1
while not $lTree[$lVal] do
$lVal = ($lVal * 2) + $dfGetIntField(1)
end
return $lTree[$lVal]
end
$}
${
function $dfGenHuffmanTree($L|lCodeLens)
-- $L|lI (used everywhere; defining inside creates a bug because it gets globalized)
$L|lNextCode = {}
${
-- To explain:
-- lNextCode is needed all the way to the end.
-- But lBLCount isn't needed after it's used to
-- generate lNextCode.
-- And lCode is very, very temporary.
-- Hence this massive block.
$L|lBLCount = {}
-- Note the 0
for $lI = 0, 15 do
$lBLCount[$lI] = 0
end
for $lI = 0, #$lCodeLens do
${
$L|lCodeLen = $lCodeLens[$lI]
if $lCodeLen ~= 0 then
$lBLCount[$lCodeLen] = $lBLCount[$lCodeLen] + 1
end
$}
end
$L|lCode = 0
for $lI = 1, 15 do
$lCode = ($lCode + $lBLCount[$lI - 1]) * 2
$lNextCode[$lI] = $lCode
end
$}
$L|lTree = {}
for $lI = 0, #$lCodeLens do
${
$L|lCodeLen = $lCodeLens[$lI]
if $lCodeLen ~= 0 then
$L|lPow = math.floor(2 ^ $lCodeLen)
assert($lNextCode[$lCodeLen] < $lPow, "Tl" .. $lCodeLen)
$L|lK = $lNextCode[$lCodeLen] + $lPow
assert(not $lTree[$lK], "conflict @ " .. $lK)
$lTree[$lK] = $lI
$lNextCode[$lCodeLen] = $lNextCode[$lCodeLen] + 1
end
$}
end
return $lTree
end
$}
-- DEFLATE fixed trees --
${
$L|dfFixedTL = {}
for $L|lI = 0, 143 do $dfFixedTL[$lI] = 8 end
for $lI = 144, 255 do $dfFixedTL[$lI] = 9 end
for $lI = 256, 279 do $dfFixedTL[$lI] = 7 end
for $lI = 280, 287 do $dfFixedTL[$lI] = 8 end
$dfFixedLit = $dfGenHuffmanTree($dfFixedTL)
-- last line possibly destroyed dfFixedTL, but that's alright
$dfFixedTL = {}
for $lI = 0, 31 do $dfFixedTL[$lI] = 5 end
$dfFixedDst = $dfGenHuffmanTree($dfFixedTL)
$}
-- DEFLATE LZ Core --
$dfWindow = ("\x00"):rep(2^15)
$dfPushBuf = ""
${
function $dfOutput($L|lData)
$dfWindow = ($dfWindow .. $lData):sub(-2^15)
$dfPushBuf = $dfPushBuf .. $lData
end
$}
$dfBittblLength = {
0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 2, 2, 2, 2,
3, 3, 3, 3, 4, 4, 4, 4,
5, 5, 5, 5, 0
}
$dfBasetblLength = {
3, 4, 5, 6, 7, 8, 9, 10,
11, 13, 15, 17, 19, 23, 27, 31,
35, 43, 51, 59, 67, 83, 99, 115,
131, 163, 195, 227, 258
}
$dfBittblDist = {
0, 0, 0, 0, 1, 1, 2, 2,
3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10,
11, 11, 12, 12, 13, 13
}
$dfBasetblDist = {
1, 2, 3, 4, 5, 7, 9, 13,
17, 25, 33, 49, 65, 97, 129, 193,
257, 385, 513, 769, 1025, 1537, 2049, 3073,
4097, 6145, 8193, 12289, 16385, 24577
}
${
function $dfReadBlockBody($L|lLit, $L|lDst, $L|lLitSym, $L|lLen, $L|lDCode, $L|lPtr)
while true do
$lLitSym = $dfReadHuffmanSymbol($lLit)
if $lLitSym <= 255 then
$dfOutput(string.char($lLitSym))
elseif $lLitSym == 256 then
return
elseif $lLitSym <= 285 then
$lLen = $dfBasetblLength[$lLitSym - 256] + $dfGetIntField($dfBittblLength[$lLitSym - 256])
$lDCode = $dfReadHuffmanSymbol($lDst)
$lPtr = 32769 - ($dfBasetblDist[$lDCode + 1] + $dfGetIntField($dfBittblDist[$lDCode + 1]))
for $L|lI = 1, $lLen do
$dfOutput($dfWindow:sub($lPtr, $lPtr))
end
else
error("nt" .. v)
end
end
end
$}
-- Huffman Dynamics --
${
function $dfReadHuffmanDynamicSubcodes($L|lDistLens, $L|lCount, $L|lMetatree, $L|lCode)
-- used a lot: $L|lI
$lCode = 0
$lDistLens[-1] = 0
while $lCode < $lCount do
-- use a tmpglb since it's just for the chain
$L|lInstr = $dfReadHuffmanSymbol($lMetatree)
if $lInstr < 16 then
$lDistLens[$lCode] = $lInstr
$lCode = $lCode + 1
elseif $lInstr == 16 then
for $lI = 1, 3 + $dfGetIntField(2) do
$lDistLens[$lCode] = $lDistLens[$lCode - 1]
$lCode = $lCode + 1
assert($lCode <= $lCount, "sc.over")
end
elseif $lInstr == 17 then
for $lI = 1, 3 + $dfGetIntField(3) do
$lDistLens[$lCode] = 0
$lCode = $lCode + 1
assert($lCode <= $lCount, "sc.over")
end
elseif $lInstr == 18 then
for $lI = 1, 11 + $dfGetIntField(7) do
$lDistLens[$lCode] = 0
$lCode = $lCode + 1
assert($lCode <= $lCount, "sc.over")
end
else
-- is this even possible?
error("sc.unki")
end
end
$lDistLens[-1] = nil
return $lDistLens
end
$}
$dfDynamicMetalensScramble = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}
${
function $dfReadHuffmanDynamic($L|lState, $L|lLiteralSize, $L|lDistanceSize, $L|lDistanceLens)
-- $L|lI loop variable, used all over
-- to save on locals, this is reused
-- as metalens
$lState = {}
for $lI = 0, 18 do $lState[$lI] = 0 end
$lLiteralSize = $dfGetIntField(5) + 257
$lDistanceSize = $dfGetIntField(5) + 1
for $lI = 1, $dfGetIntField(4) + 4 do
$lState[$dfDynamicMetalensScramble[$lI]] = $dfGetIntField(3)
end
-- as metatree
$lState = $dfGenHuffmanTree($lState)
-- as concatenated subcodes
$lState = $dfReadHuffmanDynamicSubcodes({}, $lLiteralSize + $lDistanceSize, $lState)
-- The distance lengths are removed from lState,
-- while being added to lDistanceLens
-- The result is completion
$lDistanceLens = {}
for $lI = 0, $lDistanceSize - 1 do
$lDistanceLens[$lI] = $lState[$lLiteralSize + $lI]
$lState[$lLiteralSize + $lI] = nil
end
return $dfGenHuffmanTree($lState), $dfGenHuffmanTree($lDistanceLens)
end
$}
-- Main Thread --
${
$dfThread = coroutine.create(function ($L|lFinal, $L|lLitLen)
while true do
$lFinal = coroutine.yield()
$L|lBlockType = $dfGetIntField(2)
if $lBlockType == 0 then
-- literal
$dfGetIntField($dfAlignToByteRemaining)
$lLitLen = $dfGetIntField(16)
-- this is weird, ignore it
$dfGetIntField(16)
for $L|lI = 1, $lLitLen do
$dfOutput(string.char($dfGetIntField(8)))
end
elseif $lBlockType == 1 then
-- fixed Huffman
$dfReadBlockBody($dfFixedLit, $dfFixedDst)
elseif $lBlockType == 2 then
-- dynamic Huffman
$dfReadBlockBody($dfReadHuffmanDynamic())
else
error("b3")
end
while $lFinal do
coroutine.yield()
end
end
end)
$}
-- The Outer Engine --
coroutine.resume($dfThread)
${
function $engineInput($L|lData, $L|lByte)
for $L|lI = 1, #$lData do
$lByte = $lData:byte($lI)
$dfAlignToByteRemaining = 8
while $dfAlignToByteRemaining > 0 do
-- If we're providing the first bit (v = 8), then there are 7 bits remaining.
-- So this hits 0 when the *next* 8 yields will provide an as-is byte.
$dfAlignToByteRemaining = $dfAlignToByteRemaining - 1
assert(coroutine.resume($dfThread, $lByte % 2 == 1))
$lByte = math.floor($lByte / 2)
end
end
-- flush prepared buffer
$engineOutput($dfPushBuf)
$dfPushBuf = ""
end
$}

80
inst/instcore.lua Normal file
View File

@ -0,0 +1,80 @@
-- KOSNEO installer base
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
$icScreen = $component.list("screen", true)()
$icGPU = $component.list("gpu", true)()
$icFilename = "Starting..."
$icBytesRemaining = 0
if $icScreen and $icGPU then
$icGPU = $component.proxy($icGPU)
$icGPU.bind($icScreen)
$icGPU.setResolution(50, 5)
$icGPU.setBackground(2^24-1)
$icGPU.setForeground(0)
$icGPU.fill(1, 1, 50, 5, "")
$icGPU.fill(1, 2, 50, 1, " ")
$icGPU.set(2, 2, "KittenOS NEO Installer")
end
function $icOctalToNumber($a0)
if $a0 == "" then return 0 end
return $icOctalToNumber($a0:sub(1, -2)) * 8 + ($a0:byte(#$a0) - 48)
end
$icSectorsRead = 0
$iBlockingLen = 512
function $iBlockingHook($a0)
if $icBytesRemaining > 0 then
${
$L|icByteAdv = math.min(512, $icBytesRemaining)
$icBytesRemaining = $icBytesRemaining - $icByteAdv
if $icFile then
$filesystem.write($icFile, $a0:sub(1, $icByteAdv))
if $icBytesRemaining <= 0 then
$filesystem.close($icFile)
$icFile = nil
end
end
$}
else
$icFilename = $a0:sub(1, 100):gsub("\x00", "")
-- this sets up the reading/skipping of data
$icBytesRemaining = $icOctalToNumber($a0:sub(125, 135))
if $icFilename:sub(1, 2) == "./" and $icFilename ~= "./" then
$icFilename = $icFilename:sub(3)
if $icFilename:sub(#$icFilename) == "/" then
$filesystem.makeDirectory($icFilename)
else
$icFile = $filesystem.open($icFilename, "wb")
if $icBytesRemaining == 0 then
$filesystem.close($icFile)
$icFile = nil
end
end
end
end
-- UPDATE DISPLAY --
$icSectorsRead = $icSectorsRead + 1
if $icScreen and $icGPU then
$icGPU.fill(1, 2, 50, 1, " ")
$icGPU.set(2, 2, "KittenOS NEO Installer : " .. $icFilename)
$icGPU.fill(2, 4, 48, 1, "")
$icGPU.fill(2, 4, math.ceil(48 * $icSectorsRead / $$SECTORS), 1, " ")
end
if $icSectorsRead % 16 == 0 then
$computer.pullSignal(0.01)
end
if $icSectorsRead == $$SECTORS then
$filesystem.close($readInFile)
$filesystem.remove("init.neoi.lua")
$computer.shutdown(true)
end
end

71
inst/insthead.lua Normal file
View File

@ -0,0 +1,71 @@
-- KOSNEO installer base
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
$computer = computer
$component = component
assert($component, "KittenOS NEO installer: Copy as init.lua to the target disk, then remove other disks & reboot.")
$filesystem = $component.proxy($computer.getBootAddress())
$filesystem.remove("init.neoi.lua")
$filesystem.rename("init.lua", "init.neoi.lua")
$readInFile = $filesystem.open("init.neoi.lua", "rb")
$iBlockingBuffer = ""
$iBlockingLen = $$CORESIZE
${
function $iBlockingHook($L|lBlock)
-- Run the next script (replacement compression engine,)
assert(load($lBlock))()
end
$}
${
function $engineOutput($L|lBlock)
$iBlockingBuffer = $iBlockingBuffer .. $lBlock
while #$iBlockingBuffer >= $iBlockingLen do
$lBlock = $iBlockingBuffer:sub(1, $iBlockingLen)
$iBlockingBuffer = $iBlockingBuffer:sub($iBlockingLen + 1)
$iBlockingHook($lBlock)
end
end
$}
$engineInput = $engineOutput
while true do
$readInBlock = $filesystem.read($readInFile, 1024)
${
for i = 1, #$readInBlock do
-- Read-in state machine
-- IT IS VERY IMPORTANT that read-in be performed char-by-char.
-- This is because of compression chain-loading; if the switch between engines isn't "clean",
-- bad stuff happens.
-- This character becomes invalid once
-- it gets passed to engineInput,
-- but that's the last step, so it's ok!
$L|readInChar = $readInBlock:sub(i, i)
if not $readInState then
if $readInChar == "\x00" then
$readInState = 0
end
elseif $readInState == 0 then
if $readInChar == "\xFE" then
$readInState = 1
else
$engineInput($readInChar)
end
else
$engineInput($readInChar)
$readInState = 0
end
end
$}
end

34
inst/libs/frw.lua Normal file
View File

@ -0,0 +1,34 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
return {
read = function (fn)
local f = io.open(fn, "rb")
local d = f:read("*a")
f:close()
return d
end,
write = function (fn, data)
local f = io.open(fn, "wb")
f:write(data)
f:close()
end,
progress = function ()
io.stderr:write("00% \\")
local lastPercent = 0
local chr = 0
return function (fraction)
local percent = math.ceil(fraction * 100)
if percent ~= lastPercent then
lastPercent = percent
chr = (chr + 1) % 4
io.stderr:write(string.format("\8\8\8\8\8%02i%% %s", percent, ("\\|/-"):sub(chr + 1, chr + 1)))
end
end
end
}

195
inst/libs/lexcrunch.lua Normal file
View File

@ -0,0 +1,195 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- This library helps in crunching down the installer a bit further.
local sequences = {
{"\n", " "},
{" ", " "},
{" #", "#"},
{"# ", "#"},
{" ,", ","},
{", ", ","},
{" (", "("},
{"( ", "("},
{" )", ")"},
{") ", ")"},
{" {", "{"},
{"{ ", "{"},
{" }", "}"},
{"} ", "}"},
{" <", "<"},
{"< ", "<"},
{" >", ">"},
{"> ", ">"},
{" *", "*"},
{"* ", "*"},
{" ~", "~"},
{"~ ", "~"},
{" /", "/"},
{"/ ", "/"},
{" %", "%"},
{"% ", "%"},
{" =", "="},
{"= ", "="},
{" -", "-"},
{"- ", "-"},
{" +", "+"},
{"+ ", "+"},
{".. ", ".."},
{" ..", ".."},
{"\"\" ", "\"\""},
{"=0 t", "=0t"},
{">0 t", ">0t"},
{">1 t", ">1t"},
{"=1 w", "=1w"},
{"=380 l", "=380l"},
{"=127 t", "=127t"},
{"<128 t", "<128t"},
{"=128 t", "=128t"},
{">255 t", ">255t"},
{"=512 t", "=512t"}
}
local function pass(buffer)
local ob = ""
local smode = false
while #buffer > 0 do
if not smode then
local ds = true
while ds do
ds = false
for _, v in ipairs(sequences) do
if buffer:sub(1, #(v[1])) == v[1] then
buffer = v[2] .. buffer:sub(#(v[1]) + 1)
ds = true
end
end
end
end
local ch = buffer:sub(1, 1)
buffer = buffer:sub(2)
ob = ob .. ch
if ch == "\"" then
smode = not smode
end
end
return ob
end
-- Context creation --
return function ()
local forwardSymTab = {}
local reverseSymTab = {}
local temporaryPool = {}
local stackFrames = {}
local log = {}
local possible = {}
for i = 1, 52 do
possible[i] = ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"):sub(i, i)
end
local function allocate(reason)
for _, v in pairs(possible) do
if not reverseSymTab[v] then
reverseSymTab[v] = reason
return v
end
end
end
local function allocTmp(id)
assert(not forwardSymTab[id], "var already exists: " .. id)
local val = table.remove(temporaryPool, 1)
if not val then val = allocate("temporary") end
forwardSymTab[id] = val
table.insert(log, "allocTmp " .. id .. " -> " .. val)
return val
end
local lexCrunch = {}
function lexCrunch.dump(file)
file:write("forward table:\n")
for k, v in pairs(forwardSymTab) do
file:write(k .. " -> " .. v .. "\n")
end
file:write("reverse table (where differing):\n")
for k, v in pairs(reverseSymTab) do
if forwardSymTab[v] ~= k then
file:write(v .. " -> " .. k .. "\n")
end
end
file:write("log:\n")
for k, v in ipairs(log) do
file:write(v .. "\n")
end
end
function lexCrunch.process(op, defines)
-- symbol replacement
op = op:gsub("%$[%$a-z%{%}%|A-Z0-9]*", function (str)
if str:sub(2, 2) == "$" then
-- defines
assert(defines[str], "no define " .. str)
return defines[str]
end
local com = {}
for v in str:sub(2):gmatch("[^%|]*") do
table.insert(com, v)
end
if com[1] == "L" then
assert(#com == 2)
local id = "$" .. com[2]
assert(stackFrames[1], "allocation of " .. id .. " outside of stack frame")
table.insert(stackFrames[1], id)
return allocTmp(id)
elseif com[1] == "{" then
assert(#com == 1)
table.insert(stackFrames, 1, {})
return ""
elseif com[1] == "}" then
assert(#com == 1)
for _, id in ipairs(table.remove(stackFrames, 1)) do
table.insert(temporaryPool, forwardSymTab[id])
forwardSymTab[id] = nil
end
return ""
else
assert(#com == 1)
local id = "$" .. com[1]
-- normal handling
if forwardSymTab[id] then
return forwardSymTab[id]
end
local v = allocate(id)
forwardSymTab[id] = v
return v
end
end)
-- comment removal
while true do
local np = op:gsub("%-%-[^\n]*\n", " ")
np = np:gsub("%-%-[^\n]*$", "")
if np == op then
break
end
op = np
end
-- stripping
while true do
local np = pass(op)
if np == op then
return np
end
op = np
end
return op
end
return lexCrunch
end

40
inst/status.lua Normal file
View File

@ -0,0 +1,40 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- Status Screen --
local target = ...
local u = require("libs.frw")
local instSize = #u.read(target)
local status = ""
local statusDetail = ""
local blinkI = ""
if instSize > 65536 then
blinkI = "5;31;"
status = " DO NOT SHIP "
statusDetail = "The installer is too big to ship safely.\nIt's possible it may crash on Tier 1 systems.\nUpgrade the compression system or remove existing code."
elseif instSize > 64000 then
blinkI = "33;"
status = " Shippable * "
statusDetail = "The installer is getting dangerously large.\nReserve further room for bugfixes."
else
blinkI = "32;"
status = " All Green "
statusDetail = "The installer is well within budget with room for features.\nDevelop as normal."
end
io.stderr:write("\n")
local ctS, ctM, ctE = " \x1b[1;" .. blinkI .. "7m", "\x1b[0;7m", "\x1b[0m\n"
io.stderr:write(ctS .. " " .. ctM .. " " .. ctE)
io.stderr:write(ctS .. status .. ctM .. string.format(" %07i ", 65536 - instSize) .. ctE)
io.stderr:write(ctS .. " " .. ctM .. " " .. ctE)
io.stderr:write("\n")
io.stderr:write(statusDetail .. "\n")
io.stderr:write("\n")
io.stderr:write("Size: " .. instSize .. "\n")
io.stderr:write(" max. 65536\n")
io.stderr:write("\n")

32
inst/symbolGuide.md Normal file
View File

@ -0,0 +1,32 @@
# The Symbol Guide
## lexCrunch commands
The following prefixes are really special,
and are lexcrunch's responsibility:
"$$THING" : These are defines.
"$Thing" : Writes a global into stream. If not already allocated, is allocated a global.
"${" : Opens a frame.
"$}" : Closes a frame. (Attached temps are released.)
"$L|THING" : Allocates THING from temp pool, attaches to stack frame, writes to stream.
Use inside a comment to erase the written symbol
## Conventions
The rest are convention:
"$iThing" symbols are Installer Wrapper.
"$icThing" symbols are Installer Core.
"$dfThing" symbols are DEFLATE Engine.
"$bdThing" symbols are BDIVIDE Engine.
"$a0", "$a1", etc. are Local Symbols.
DEPRECATED, THESE ARE AN OLD MECHANISM, USE FRAMED TEMPS INSTEAD.
These are reserved only for use in locals.
(For loops count.)
"$lThing" symbols are used to name Local Symbols using aliases.
NO THEY ARE NOW USED FOR ALL TEMPS, INCLUDING LOCAL SYMBOLS

View File

@ -0,0 +1,14 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- Example compression engine.
-- Given: data, lexCrunch
-- returns compressionEngine, compressedData
return function (data, lexCrunch)
return lexCrunch.process(" $engineInput = $engineOutput ", {}), data
end

View File

@ -1,97 +0,0 @@
-- KOSNEO inst.
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
C, O, G, D = component, computer
assert(C, "To install, please copy as init.lua to a blank disk or a system to update, then remove all other disks and reboot.")
sa = C.list("screen", true)()
if sa then
G = C.list("gpu", true)()
if G then
G = C.proxy(G)
G.bind(sa)
G.setForeground(0xFFFFFF)
G.setBackground(0x000000)
G.setResolution(50, 5)
G.setDepth(1)
G.fill(1, 1, 50, 5, " ")
G.setBackground(0xFFFFFF)
G.setForeground(0x000000)
G.fill(1, 2, 50, 1, " ")
G.set(2, 2, "KittenOS NEO Installer")
end
end
D = C.proxy(O.getBootAddress())
tFN,tFSR,tW,tF="Starting...",0,0
function tO(oct)
local v = oct:byte(#oct) - 0x30
if #oct > 1 then
return (tO(oct:sub(1, #oct - 1)) * 8) + v
end
return v
end
function tA(s)
if tW > 0 then
tW = tW - 1
return
end
if tF then
local ta = math.min(512, tFSR)
D.write(tF, s:sub(1, ta))
tFSR = tFSR - ta
if tFSR == 0 then
D.close(tF)
tF = nil
end
else
tFN = s:sub(1, 100):gsub("\x00", "")
local sz = tO(s:sub(125, 135))
if tFN:sub(1, 5) ~= "code/" then
tW = math.ceil(sz / 512)
else
tFN = tFN:sub(6)
if tFN == "" then
return
end
if tFN:sub(#tFN) == "/" then
tW = math.ceil(sz / 512)
D.makeDirectory(tFN)
else
tF = D.open(tFN, "wb")
tFSR = sz
if tFSR == 0 then
D.close(tF)
tF = nil
end
end
end
end
end
sN, sC = 0, 0
function sector(n)
tA(n)
sN = sN + 1
if G then
local a = sN / sC
G.fill(1, 2, 50, 1, " ")
G.set(2, 2, "KittenOS NEO Installer : " .. tFN)
G.setForeground(0xFFFFFF)
G.setBackground(0x000000)
G.fill(2, 4, 48, 1, " ")
G.setBackground(0xFFFFFF)
G.setForeground(0x000000)
G.fill(2, 4, math.ceil(48 * a), 1, " ")
end
if sN % 8 == 0 then
O.pullSignal(0.05)
end
if sN == sC then
dieCB()
O.shutdown(true)
end
end

17
laboratory/client.cfg Normal file
View File

@ -0,0 +1,17 @@
{
["components"]={
{"screen","67c66973-51ff-4aec-29cd-baabf2fbe346",},
{"gpu","9b7fe47d-77d0-1623-0dc5-126752e3f2c7",44800,},
{"eeprom","94a9cc26-347e-cc46-7faf-0c131bb7d8b7",4096,256,"EEPROM",},
{"computer","36bc34ad-8c4a-d19a-10e3-0162c6f3295a",1.04858e+06,},
{"filesystem","tmpfs",true,"tmpfs",},
{"filesystem","c1-sda",},
{"filesystem","c1-sdb",},
{"keyboard","dd142450-613c-435d-6049-743b27434cc1","67c66973-51ff-4aec-29cd-baabf2fbe346",},
{"modem","460ae893-9aa9-f10d-6bb0-462199da7376",56000,8192,8,},
{"internet","ef97c750-d30a-ad33-5321-6e7a64ba3baa",true,true,},
{"data","c5243e5f-cd2f-6c39-dfb2-5a788dcdef7c",["tier"]=1,},
{"sandbox","ece7b9ba-1625-f3f6-d74d-6e641a5de07f",},
},
["system"]={["allowBytecode"]=false,["allowGC"]=false,["maxTcpConnections"]=4,["timeout"]=math.huge,},
}

10
laboratory/launch.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/sh
# This is released into the public domain.
# No warranty is provided, implied or otherwise.
XPWD=`pwd`
export XPWD
cd "$OCEMU/src"
./boot.lua "$XPWD"

10
laboratory/locvm.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/sh
# Copyright (C) 2018-2021 by KittenOS NEO contributors
#
# Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
# THIS SOFTWARE.
ocvm .

View File

@ -0,0 +1,28 @@
ocemu {
emulator {
components {
{"gpu", "c1-gpu-tier3", 0, 160, 50, 3},
{"gpu", "c1-gpu-tier1", 0, 50, 16, 1},
{"screen_sdl2", "c1-screen-tier3", -1, 160, 50, 3},
-- {"screen_sdl2", "c1-screen-tier1", -1, 50, 16, 1},
{"modem", "c1-modem", 1, false},
{"eeprom", "c1-eeprom", 9, "lua/bios.lua"},
{"filesystem", "c1-tmpfs", -1, "tmpfs", "tmpfs", false, 5},
{"filesystem", "c1-sda", 5, nil, "Workbench", false, 4},
{"filesystem", "c1-sdb", 5, nil, "Repository", false, 4},
{"filesystem", "openos", 0,"loot/openos","openos",true,1},
{"internet", "c1-internet", 2},
{"computer", "c1-computer", -1},
{"ocemu", "c1-ocemu", -1},
{"keyboard_sdl2", "c1-keyboard", -1}
}
debug=false
fast=false
vague=false
}
internet {
enableHttp=true
enableTcp=true
}
version=3
}

11
laboratory/reset-i.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/sh
# This is released into the public domain.
# No warranty is provided, implied or otherwise.
cp ocemu.cfg.default ocemu.cfg && rm -rf c1-sda c1-sdb tmpfs
mkdir c1-sda c1-sdb
echo -n c1-sda > c1-eeprom/data.bin
cd ..
./package.sh $*
cp inst.lua laboratory/c1-sda/init.lua

10
laboratory/reset.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/sh
# This is released into the public domain.
# No warranty is provided, implied or otherwise.
cp ocemu.cfg.default ocemu.cfg && rm -rf c1-sda c1-sdb
mkdir c1-sda c1-sdb
echo -n c1-sda > c1-eeprom/data.bin
./update.sh

11
laboratory/update.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/sh
# This is released into the public domain.
# No warranty is provided, implied or otherwise.
cd ..
cp -r code/* laboratory/c1-sdb/
cp -r repository/* laboratory/c1-sdb/
lua claw/clawconv.lua laboratory/c1-sdb/data/app-claw/ < claw/code-claw.lua > /dev/null
lua claw/clawconv.lua laboratory/c1-sdb/data/app-claw/ < claw/repo-claw.lua >> /dev/null
cp -r laboratory/c1-sdb/* laboratory/c1-sda/

View File

@ -1,28 +0,0 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
os.execute("tar -cf code.tar code")
os.execute("cat insthead.lua > inst.lua")
local f = io.open("inst.lua", "ab")
local df = io.open("code.tar", "rb")
local sc = 0
while true do
local d = df:read(512)
if not d then break end
sc = sc + 1
end
df:close()
local df = io.open("code.tar", "rb")
f:write("dieCB = function () end")
f:write("sC = " .. sc .. "\n")
while true do
local d = df:read(512)
if d then
f:write(string.format("sector(%q)", d))
else
break
end
end
df:close()
f:close()

11
package-rel.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/sh
# Copyright (C) 2018-2021 by KittenOS NEO contributors
#
# Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
# THIS SOFTWARE.
./package.sh bdvlite deflate

21
package-repo.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/sh
# Copyright (C) 2018-2021 by KittenOS NEO contributors
#
# Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
# THIS SOFTWARE.
# Package repository using supplied inst.lua (use inst-gold.lua for repository branch)
# this is a guard check to avoid removing repobuild if it's blatantly
# not the actual repobuild directory (this is an rm -rf after all)
stat repobuild/data/app-claw 1>/dev/null 2>/dev/null && rm -rf repobuild
mkdir -p repobuild
cp -r code/* repobuild/
cp -r repository/* repobuild/
cp $1 repobuild/inst.lua
lua claw/clawconv.lua repobuild/data/app-claw/ < claw/code-claw.lua > repobuild/data/app-claw/local.c2l
lua claw/clawconv.lua repobuild/data/app-claw/ < claw/repo-claw.lua >> repobuild/data/app-claw/local.c2l

View File

@ -1,20 +1,27 @@
#!/bin/sh
# This is released into the public domain.
# No warranty is provided, implied or otherwise.
# Copyright (C) 2018-2021 by KittenOS NEO contributors
#
# Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
# THIS SOFTWARE.
rm code/data/app-claw/*
mkdir -p code/data/app-claw
lua claw/clawconv.lua code/data/app-claw/ < claw/code-claw.lua > /dev/null
rm code.tar
# Hey, look behind you, there's nothing to see here.
# ... ok, are they seriously all named "Mann"?
tar --owner=gray:0 --group=mann:0 -cf code.tar code
lua heroes.lua `wc -c code.tar` | lua bonecrunch.lua > inst.lua
echo -n "--[[" >> inst.lua
cat com2/code.tar.bd >> inst.lua
echo -n "]]" >> inst.lua
cd code
tar --mtime=0 --owner=gray:0 --group=mann:0 -cf ../code.tar .
cd ..
stat repobuild/data/app-claw/local.lua && rm -rf repobuild
mkdir repobuild
cp -r code/* repobuild/
cp -r repository/* repobuild/
cp inst.lua repobuild/
lua clawmerge.lua repository/data/app-claw/local.lua code/data/app-claw/local.lua > repobuild/data/app-claw/local.lua
# The Installer Creator
cd inst
lua build.lua `git status --porcelain=2 --branch | grep branch.oid | grep -E -o "[0-9a-f]*$" -` ../code.tar $* > ../inst.lua
lua status.lua ../inst.lua
cd ..
# Common Repository Setup Code
./package-repo.sh inst.lua

View File

@ -1,609 +0,0 @@
-- KOSNEO inst.
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- PADPADPADPADPADPADPADPAD
local C, O, G, D = component, computer
local sAddr = C.list("screen", true)()
if sAddr then
G = C.list("gpu", true)()
if G then
G = C.proxy(G)
G.bind(sAddr)
G.setForeground(0xFFFFFF)
G.setBackground(0x000000)
G.setResolution(50, 5)
G.setDepth(1)
G.fill(1, 1, 50, 5, " ")
G.setBackground(0xFFFFFF)
G.setForeground(0x000000)
G.fill(1, 2, 50, 1, " ")
G.set(2, 2, "KittenOS NEO Installer")
end
end
D = C.proxy(O.getBootAddress())
local file = nil
local fileName = "Starting..."
local fileSizeRm = 0
local ws = 0
local convoct
convoct = function (oct)
local v = oct:byte(#oct) - 0x30
if #oct > 1 then
return (convoct(oct:sub(1, #oct - 1)) * 8) + v
end
return v
end
local function sectorCore(sector)
if ws > 0 then
ws = ws - 1
return
end
if file then
local takeaway = math.min(512, fileSizeRm)
D.write(file, sector:sub(1, takeaway))
fileSizeRm = fileSizeRm - takeaway
if fileSizeRm == 0 then
D.close(file)
file = nil
end
else
local name = sector:sub(1, 100):gsub("\x00", "")
local sz = convoct(sector:sub(125, 135))
if name:sub(1, 5) ~= "code/" then
ws = math.ceil(sz / 512)
else
if name:sub(#name, #name) == "/" then
ws = math.ceil(sz / 512)
D.makeDirectory(name:sub(6))
else
fileName = name:sub(6)
file = D.open(fileName, "wb")
fileSizeRm = sz
if file then
if fileSizeRm == 0 then
D.close(file)
file = nil
end
end
end
end
end
end
local dieCB = function () end
local sectorNum = 0
local sectorCount = 0
local function sector(n)
sectorCore(n)
sectorNum = sectorNum + 1
if G then
local a = sectorNum / sectorCount
G.fill(1, 2, 50, 1, " ")
G.set(2, 2, "KittenOS NEO Installer : " .. fileName)
G.setForeground(0xFFFFFF)
G.setBackground(0x000000)
G.fill(2, 4, 48, 1, " ")
G.setBackground(0xFFFFFF)
G.setForeground(0x000000)
G.fill(2, 4, math.ceil(48 * a), 1, " ")
end
if sectorNum % 8 == 0 then
O.pullSignal(0.05)
end
if sectorNum == sectorCount then
dieCB()
O.shutdown(true)
end
end
sectorCount = 260
D.remove("init-symsear.lua")
D.rename("init.lua", "init-symsear.lua")
local instHandle = D.open("init-symsear.lua", "rb")
dieCB = function ()
D.close(instHandle)
D.remove("init-symsear.lua")
end
local syms = {" ","s","e","t","a","i","\24","(","r",".","\
","p","\"","o","c","m",", ","\1","l","n",")","d","u","\17","\9","x","-","\7","\6","\8","\4","\13","\2","\3","\5","g","\14","\21","\11"," then\
","1","w"," ","\12","\18","\22","f","F","y",",","\20","re","b","k"," = ","\23","return "," local ","\16","v"," if ","\
","\15","\19","\
"," ","[","en","/","0","]","path","nction (","\
","se","h"," =","or","S","T","le","\
local ",")\
","= fu","on"," == ","ne",")\
","functio","\
end\
","D","{"," t","n ","oc","lo"," end\
","\0\0\0\0\0\0\0\0\0","un"," i","W","\0\0\0\0\0\0","fu","et"," end\
","then ","nd","ni","A","ing"," tab","loca","etting",")\
","ct","C","P","}","\
end\
",")\
","onit","= ","end\
","\0","I","Y"," do\
","return\
","le.inser"," = func","= \"","al"," r"," e","in","he","nc"," e","j","donkonit","tion ()",") do\
"," the","\
",")\
","ur","#","+","N","cursor",".inse",", v ","nction (w"," neo.req"," for "," then\
","\0\0\0\0\0\0\0","nd\
","()\
","neo.","ath","table","l ","do","2","3",":","H"," = nil\
","nd\
e","hen\
","\7local ","indowCore",")\
end\
","d\") end\
"," = false","if ","pairs(","dow","string\"","ti","O","uestAcces","nd\
if ","icode."," if","v in ","pkg, pid","000","750\0000001","end\
e","750\0000000"," else\
","window","end","00",".neo.sys.","neoux.tc","= {}\
","(window, ","5","=","E","R",") end\
"," cursor","request","ode/app"," return e","\" the","equire("," "," then",".neo.pub.","hile true"," end"," en","\0\0","B","M"," ret","for ","in ipai"," true\
","close","code.sub","error(\"","return t","oroutine"," end\
if","end\
en","tion ","\", funct",":sub(","vailable"," end\
"," l"," == \"","prima"," if type(","ryWindow","window.s"," end\
","L","X","~","on ("," in ipair"," for _, v"," for k","\0\0\0\0\0cod","lose()\
"," = funct","rror(\"Exp","nsurePath","s.primary","primary","buildingS","unic"," "," re","surfaces","disallowe","ackground","neoux.","ccess","selectUnk"," end\
"," = uni","\
end\
"," f","7","Z","z","for k","rue\
end\
","ode.len("," end\
","lse\
",") end","end\
"," fu",":sub(1, ","sub(1, 4)"," ","neo","the","if not "," th","tion","unknownA","end\
","[1] == \"","= unico","Acces","\
end\
\
l","n\
","\") end\
","de.len("," end","\" then\
","0\0000001","urface","nd\
","()\
","(surfaces"," if ","\0\0\0\0\0\0\0\0","unc","urn","\
"," end\
"," neoux","ction","\
end\
","\
","neoux","\24\0\0\0\0\0\0\0\0","\
end"," end\
end\
","\
end\
"," if "," err","s[1] == \"","unicode."," window"," if ","ocal fun","= false","\0\0\0\0\0\0\0000","\0\0\0\0\0\0\00000","code","_, v in i","()\
","d\
","f ev == \""," return",", bg, fg"," end"," neoux.","\0\0\0","w.close(","\
if ev"," i"," if not ","if kc == ",")\
end\
","indow","onkonit","00175","table.",", bg, fg)"," l"," end\
",") == \"","d\
"," return ","rn","ca","q"," tabl"," error(\"","end\
re"," in ","hen err","nd\
en","nd\
","\
end","turn ","x.neo.","ion ("," table","false","string","e.len(","d\
"," re","1, unicod","cursorX ","local ","end\
end\
","cal","\" then e","nd\
if k","ion","00644\00000"," end\
","\
end"," window."," e","if not","== \"",", func"," neo.",", functi","\
end\
end"," r","end\
","ion (w)\
","\
end"," = false\
"," ret","tu"," end\
","f ","unction","= nil\
","th","n\
","6","U","_","requir","eturn\
"," funct","eturn tr","\
\
local"," table.","eques","rn ",")\
r",".insert(","ode.",")\
end","== ","\0\0\0\0\0\0\0c","n\
","\0\0\0\0\0","()\
"," = nil","able.","n\
"," = n","return","000644\0000","Access","able","etu","d\
","\
if ","end\
end","nction (p","ub(1, 4) "," return "," return","lse\
","abl","= uni","for k, ","\0\0\0\0\0\0\0\0c","cursorY","ble","bg, fg)\
","\
e","ind","ret","tring\"","000000"," neo","pairs","then\
",")\
","le.inse","loc","\
end","n error","\
end\
\
","if s[1] =","lse\
","turn","ursor","function","neou","\0\0\0\0\0\0c","function ","\0000001","end\
e","eo.","Access(\""," re"," lo","\
e",")\
end","urn ","\
re"," end\
e"," = f","\0\0\0\0","cal ","\0000000644\0","in ip","\
en"," return",")\
i","ction ","\
end\
","ode","equire(\"","r(\"Expect","ctio"," cursorY","if","%","4","<","G","\
local f","\
ret"," for ","d\
if"," erro","true\
end","ed\") end","hen ","nd\
en","al ","on ","] then\
","string\")","for _, ","unicode.l","[1] == ","true\
","uestAc","nd\
end\
"," for k, "," if ev ","then\
","\
en",")\
en","do\
","e.ins",".primary"," true","coroutine","return\
","airs(",") end\
","nd\
end\
\
","\
return","window, ","end\
e","end\
end","750\0000","eturn t",", functio"," e","d\") end","en\
","ocal","icode.len","e\
","n\
","w.close()"," uni","sub(","n ipairs("," for ","end\
","ion ()","end\
en","quest","cursorY ","eturn","unicode.s","x.neo.pub"," = neo","hen\
","then\
","string\") ","in ipa"," retur","indowCor"," re","750\00000"," do\
",")\
if "," then ret","\") end"," en","n\
","ipairs"," then err","d\
en","= f"," local",".neo.","\24\0\0\0\0\0\0\0","func","()\
",")\
en","curs","\
loca","ode/apps","nd\
if "," i"," = neo.r","kg, pid, ","\
i","win","code.sub(","require","wind"," else\
","close()"," end\
e","\
\
local "," then re","\
e",".request","wnAvailab","tion (","\
local ","error(","\0000001750\0","n\
","d\
"," ","uestA","reques","end\
end\
\
","ion ()\
"," curso","then\
","0001750","surfac",".close("," function","= neo.","if type("," loc","d\
e","oroutine."," do\
"," else\
","tion ()\
",", v in ","window.","neo.re","0000","\000000","\
end\
","ipairs("," ret","\"x.neo.pu","error","\
i","\
end","()\
","= nil\
","nsert(","n erro","rror(\"","nil\
","cursorX ="," do\
"," for","turn f","en\
"," table.","\
","\") end\
","\") end\
",".close()\
","\
re","d\
e","\
end\
","ownAvaila","000644\00000"," false\
","\
re","false\
","ion ()\
",")\
e","urn end\
",")\
if","x.neo.sys","indow, ","then\
",")\
if t","cti","ion ","ion (w","\" then"," = nil\
","then","e\" then\
"," if","on (w)\
",") do\
"," en","tAccess(\"","surePath"," end\
end","\
end\
","e.inse"," table.i"," local","oca","n (","0 then\
",") end\
"," loc","se\
","do\
","nd\
end\
"," then e","0000644\0","ion ()\
","ocal ","end\
if ","e\
"," then\
"," local","icode.le","\" then "," ==","] == \"","eoux","ndow","\0\0\0\0\0c","nownAvail"," functi","0\000000000","eoux.tc","hen error"," = neo.","table.ins"," window","(window","d\
if ","\
if ","\
local","end\
e","nd\
end","insert("," local ","k, v i","surface","eturn ","\
loc","sub(1, ","end\
if ","io","!","$","&","'","*","8","9",";",">","?","@","J","K","Q","V","\\","^","|","<22>","…","ˆ","‰","Œ","<22>","Ž","<22>","<22>","","“","”","˜","¦","¬","¼","½","Â","ï",}
local bytBuf = ""
local bitBuf = ""
local function getByte()
if bytBuf == "" then
bytBuf = D.read(instHandle, 64)
end
local r = bytBuf:byte()
bytBuf = bytBuf:sub(2)
return r
end
while true do
if getByte() == 0 then break end
end
local function pb(c, p) if c % (p * 2) ~= c % p then bitBuf = bitBuf .. "1" else bitBuf = bitBuf .. "0" end end
local stn = 0
local function getBit()
if bitBuf == "" then
local c = getByte()
c = (c - stn) % 256 stn = stn + 3
pb(c, 1)
pb(c, 2)
pb(c, 4)
pb(c, 8)
pb(c, 16)
pb(c, 32)
pb(c, 64)
pb(c, 128)
end
local bit = bitBuf:sub(1, 1) == "1"
bitBuf = bitBuf:sub(2)
return bit
end
local buf = ""
local mode = false
local bc2 = 10
while true do
local bc = getBit()
local v = 0
if bc then bc = bc2 v = 64 else bc = 6 end
for bit = 0, bc - 1 do
if getBit() then v = v + (2 ^ bit) end
end
buf = buf .. syms[v]
if mode then
while #buf >= 512 do
sector(buf:sub(1, 512))
buf = buf:sub(513)
end
else
if #buf == 27939 then
stn = 0
bc2 = 11
bitBuf = ""
syms = {}
while #buf > 0 do
local len = buf:byte()
if len > 127 then error("symlen") end
buf = buf:sub(2)
local ch = buf:sub(1, len)
buf = buf:sub(len + 1)
table.insert(syms, ch)
end
mode = true
end
end
end
--[[°%ª<>°œ]-¤åEØr=´ÚZE‡N×ϯp¢œŸìᄉ±ä<C2B1>ôžüƈ¤ØÓ¦°4Þ.6ÆÊy3ƒ·ÿ…ÍbHÊM¼ÑväàäàÂìk
 £h 3/U<EFBFBD>š-<EFBFBD>3PMn4´©;<EFBFBD>FÅag·Ž¸µæ­žãâÙ<EFBFBD>¹áÀxÅ-Õ {.wÄëÒ1íÃñ`&RèßöO÷?!øè1-y ¾Oµl!´m§<ƒ;©.­ýç@¾hPÈÝb!dÙd´®<EFBFBD>{¹Öÿ¨8<EFBFBD>©@óªëÀ1F
ØBÃS·y­Þ]ÎãS
nn9ÂZKá_²6"‡ ¤qd2YZúkÌ(ðÈûb f_“±<E2809C>èˆuî¾â}šžÍ„°¤1õ¹Ø Sôl¹¶ùÌüdÎx¬3ø§àOë¾
{=»íæ|"£‰Ÿ9s'`<60>%¯@*±QR?Š\>eÎ yv>ÝÃ2µ“î9·¡¯ˆ…(
<EFBFBD>/¨²5ÀösÐŒ<EFBFBD>Çáænîs'¯“êcÚûFnňðÝr‰O/DIj,yéBÕ¿ë“— AUÐ<55>^\\ÇŽ¢è7“0xý1”"yŒ¶ ¾ªÐg¶×àÃ+/€I`Þ™.:ØGóÒÚm!óu¾ÕéöeG4 ¦¬Tž“.£ÿ½µŠp‡X*¿Y(1Úk˜¬°Þyñ ‰Àaox
rÏéÔ¤-*λ*©¸×B?£ÒAÏiRõi6á;[«à,òH#åU(;]ôFS¥óÇv])é]§Ö<EFBFBD>pgîårê/ˆi4(¢W+³!O+Të.R9ýºbåÞ`úúö?éäÆ<EFBFBD>Ê<EFBFBD>×⊠0E"e/\•&žP5¥<35>0ŒS¹ºUÊká6òIm0Œ¨æÕq„-kEÜ·7~Äò§ò¾‹ÓÙ£é[<5B>€¼Ò¬7K¶Å~îqÈäÚ=à-fÚ€?‡ñ8 üûÜmf*<2A>òZ<5A>Êq3¹MIS»MSÍn£S˜”¬¢<C2AC>jw¨pñr˜(Àº"ª©ó¦+Ü÷n­¼iU¢æ¸]UMìµNÖìÛ<EFBFBD>öâ¾[_n#ÉÜ-<¨ãoƨ:)æ}3lhUYúìèá¤.^챟è×~Õ ?hÔî·ò®"bYÌ+Ò•Þš2¹<32> ] Îãéó§Ü|ê|ê¼` ]©]#ÌMœ«[nJ“bâ6º@éMyHš<48>¬¸ÃÎi<>jkZh:o`ÇIñ“Y=“¥²0J
XdP±ô±
3óÍF, ~É óÿß;BTI((¤ªh9*·j4ÉeYè¯ÐØ\ã¯íÅ¥&Tðo=÷Ž;Öðÿþh²(+-ò<ƒÚÚ7¸ºy¿ÅV´öó&Xl6ÿäüâ³:úˆ )+L¿07PRCõORYÂ.EíWx0?_tKË$ý>š7ïÿ8 |<EFBFBD>bB¿íÐÖ,£.Ýu{±AÍå¾ILNääÂáx¸w²u1´óø=B Z!Ïaò+}/c´ˆ,Emqïs¿o]cBÔ Tdwñ¡°'Îâ­4ðë¶[ÆOæÀI¬%œÿúæåüµmL<12>ƒ…fÄ]°Õ>«t¡ÛKCJ8†`~rUv†QÚÔ]îYÙ¬‡‰7˜–šó“»Ê6¢¤ ò5*´žëœ“{ò´ŠÏ<Ò¢0ö ëâùüÓõ
Áeí Ý¡Â!å À$sq;<EFBFBD>tÍÛkÈŒXôÎk%aæéË<EFBFBD>ŠQv·ÙAÃIÿ/ Ò&Ú?´'?qט>~%ÕÕC®T_W*%Œ\&÷m„[ñè/lÔ†sÂo"-¨ý#YÑ>F„F] <>@)<29>xA ¨êÑÅØ<C385>š5„ŽTÚÕz<C395>Ò/hlÏ¿9»îËÓ"K°±™ÌLúüª ß;ïþÙ$hoÍjù?î#v\±=‡0ß<“<äŒIbQlØešéG:‰ñ-úç­¢†GËzR;2i ¡ÃOÂo²s:-@F‡;gÔ¡zðxXï&?ÚìV]>0ec+?nVÞƒ¶I¯l7Þn©Äøq<C3B8>hé™m…ë6•g÷?×»Ÿ*Ÿ1•»|†ùÐÛ+mkFäM
Iéô^!z¡ê:öñj{3×åûæѱpƺ¼:/\6zT?ÂÓœ//eÕèLC¥gá¡ Ô¥'Åú¡ÐÓ™2 æíÞW×ïÌõ#{ü5Æ—æew\(ãêmë_%×ôI!1bù0Ñû<C391>‡4y9"Íæ2®±¹6°<ã"<22>V­Þŵb}°pñV"˜qžÉê-±S ¥·˜.<2E>ß®:Þø†ÙcÛˆÁL¢<ÓÉå=~ü'À8R<EFBFBD>«;˜TÐZ£ÇJNßo ù%ÿUGÅÇ ÚèG[ë#¥rKyˆ¹Èõ<EFBFBD>fÉîë¦×¬ °ºìª àË¢Ä`îÚƒ`³ØÕà)ßÐRÐÅþî<EFBFBD>FXCÍMû¨È¢6<EFBFBD>í@CWô<EFBFBD>ŽqC,å#ÁhiÑŽÎLš÷K¦YÓE¤@K×nZ˜¾h£)9°1Òµõú(dz·Ê¸<EFBFBD><EFBFBD>:füUƒ&9Ÿ£ <EFBFBD>r*ÿ2b°¥g>Í~<EFBFBD>äËMtaéžôºÉ è)\K²y@é9`ŽE*­ý0©5³&<EFBFBD>­þ´Jäÿjî2ÖÞÝp
#èi<EFBFBD>M-i8
èÀ2Õ)%ÓÄœ2íï¡m·BM âJò^šîàÍm~Ü@~Œ ó$šˆŸ¤4°ÓÖ¥=$ Éb»ç<EFBFBD>ð2¨ò ¨ §!n Îáxê!Ð>2øàÁˆÁ©1aÑ_=[â易GÜõÝ<0ˆŽ<EFBFBD>Z²?Íï»~V<EFBFBD>>^ ŸOFòLæ |ç<EFBFBD>¥>ÌÉý<EFBFBD>ˆÊBò¿[5T^Š=G\ÅåMUMzš«m¹¦||ŸÃÖÃ΃¹Ê©˜íügÊx³`ÕµlÔëN°c,{80øÑí':9üõú ˜<C399>N‰kà Þ¸³£é€Ñ8ønYP]í\é`E¢jCu<43>s|“…°«ýº£ 1<>G)x*¨wñˆ¼àýA=pÐøÌÛX7`>ß°ÿ )¬>p°}gUËDE2¸$âk߬ï!âMD
ú¬pñV·¼ì!~©%´á÷6¹¿ë(­˜´Þ<EFBFBD>ç(ŽÆðºÍØ>£³M¼ãsá
í*i ú%x?c_¼C Q¼2ðXèÂw¬ôK·´Ú=É}k/1mƒÝaRçoÉD·á³3
»ŽÉ˜4ÜÙ¢$ñ¥E#IJ2¡Ÿ«àú5éê»rÿW~4,;·&2ÍZ(]VpGÃZî
Ûç<EFBFBD>s<EFBFBD>ê¿r¡ùË'·„y€E ö ¦˜vLt¢Âl§-=´5öôÑ…úòfÔqïÜj؈¬õsu0ÿ(Z(;-a‰¯rYC¬ér÷ײÀDN±dcâ×^|Lä<š<06>…u_yðxãn[KAxð€§G½°·ú9Õ Ç¡gŸÝ\Ö¸vëfšk¨Ì¼r[ .ÃchJ`|ò( ÄÒmÅg¸*œ“tI?}CM:Æ$t¬ïzIÏË]{vZ\¶©žˆ¯<>`7œ°¤-*λÌaµÝ¯â?Ù¥ISKåì9!éðî¥ói*Z CE°•4†îaÐÂUyñ¢»lczÒ|GÐîÔQ){¶oò”7,n=‰8a<38>…4½ÂÒùæ<6A>vÐU¶€%þÒÉL שÿX·ùÄóDÆzÛK)FŒºBˆ¶i¼¿[<5B>=eÅPŽˆ=F%µŽk…BšâžÒÅ=kŸ°Ç¨:¼ƒÂüeî2ÏùÊ4X³Æ… ´T¸?9*‰Ð7ÁáΉ 9KG 0ÿ.uW¿¦Õ'`H&<Iq½µÇ(³cqWŸÛ¤°ði ëSØD4íŒí hT zœLŠh«£Š× ¹c½ÙÐ<EFBFBD>ñÁ2§£Gþf=wC ÒÆ!¯qE-A$<EFBFBD>w£§©<EFBFBD>Üç9x8aôPîc,|7qse°0¼v©8M
Žçɯ騢+³­÷Ô<EFBFBD>ÿ ÈÕÑ#šÈôçívNtÑ·ö ãä:S Te|­9Nw»NpIUl]â}è,®½Åk4vs~.«éÆ¡¹µ£çÕ{DöLÐÝ<EFBFBD>0\ÞàÕßå^ê¥![ n*×\",1ðGqpØùT™Oh$g)†±-sÂ<Áá)“Ú¸ù£I°î&ÀÝ<C380>¹U
ÐEÒùÔÑóÜîG#÷qJ>ÕµÈù¤?9 Ïž¤¾]}Å/äüq<EFBFBD>æ¡k#Ó¨rîya(Êñò|^ÂÇÉŸûð[ C°¦êŠ3-Ü¿ÉÖý×W˜:<EFBFBD>ˆä<EFBFBD>üÜEð;EV-<EFBFBD>ži&#»+?ÒH=qƒœÍ
(ϵ«»ñR|rpÔŒƒ²ã 4ž±*£×Æ6~\8¹¥¸¤âó/Z®<EFBFBD>»Ônãr]+:ãq<EFBFBD>¼³DHAy¡0çžÏfˆ(õÅÈ
kQÌš&ל <EFBFBD>¨@¿¼fàWÙ»2Úk¸e÷°-(s|ÙÝäÅc *¸>±<EFBFBD>Â`K<i<BžÎ<EFBFBD>;<EFBFBD>4óJZLi<g·ÙŠÿ¤0t[œ<EFBFBD>í )Ùÿ § Ã¥/ßõ.¿½0ÂËSÁÎX\ýìL@ ê¦ wÿ˜þ š<EFBFBD>·'"<22>4Ì5 1¹39R*Gz<47>ƒ¯ÀqFŠ¸g C@§º̵1©å_âI3£©•7'¾<EFBFBD>Á5Cnº"îQE ‰—Q°Í@! ä' œÔò<C394>÷=Ä]Ïž G8†ñèj}|a{JÆÿé ´ ÷a{°³6¸ïŸŽ ªªÃ0„_ÍÐãº6<C2BA>/Ù`º ž@óôM C6®Ü!8õœRÿø› šâ@XS[I"]N%hÉb7PÜ3ÖbçY*ei_Üf©;C{äZ蟳Ì)©[úpa¥=°7ù)F«ðJ±ç d7±>Ñžõ6PñÍ{ 'Ç›§ˆ¥ªÖhtÓxI<78>FӇˀ”aÆpí¯lg#£#‹û<05>„Ę˜8¿Ÿ=uAîXÀr/¶Ú¸ÜÞ”S³`Ù&¹éþúèÉïü{:7@vî QÐt¶!Ÿ%É(u-¿qs~M0IgsÙŸ<¿„s·òôz58ϼքå܉~TrÝÿõÁß…º$L]XGË1Ðã\Õ›!è øhpOb*]oÀ7£óÝb[%ŽMé:¯ßÒIX¾ˆÚŠZé¢û+•úãŠð+|©v,»=[F2 1lzPïÇÄ j2èº@bÝšYÓC_‡í×Åã• £Í`ß³AbÒÚÏ'ßÏL9'ƒiM\<15>n[eŠlïžQÉ—Ùüŵ÷N¡ º¯TÒº­}d!ÆÈb¡öŹ1-d°º v“öWå(».(.S:J ¡](™sbtÕ…¦ÄÉÒS^<r ]®©¹õ¿¦´‡Dum%ÍM­Ò™‰ª­-¯´:ÙÂÈ&|uã[;嵐ù³7 M¼>s<>òFŠQ#VŸÜ$æ#÷AŸ³/8d¦A˜'ŽÚË«ç³×Ë¿õ«·¨§šø°¥².»Ó³ x$Öô/'ewâ<EFBFBD>UsÌ«ø„¡>Ï;]‡LœŸ.uÅ¢éE*f.C²UDM<44>?ÆfZù^ò\\<EFBFBD>¿j†©žIÕü8€”  <>Â",S<> ­dº¹BÏÔù{Té@Ç¢æ
ŠééLí5oE:<EFBFBD>ÐÞ¾)Ñ2:+³+²§Ô½xG^½tHŒUˆœÚŽ¨£ê¯Ñ<EFBFBD>ü¨¼<EFBFBD>¥5UΞ©©lîJ <EFBFBD>Ð꿬ֽJI¢§/5ù²Ú:ÌÎ$E<EFBFBD>­\.S/L?p4u¡á>ˆéôÑƱ'ƒ+´éÅcÇ ø©‰ZO<ì±ýâ¤<C3A2>ГöV<C3B6>XçùÄ
ÒÖjlxR¦°H}ù%V-ôZt¡^æ^2o7õÇó/!Ë?à®㤩rsZnz?9 }æŽá:­ùÿçªc{$/B%?Á{Qøè;å¹ÚŠr æ ^ÿáæ:/ka­H8¦r7¬9`;¸9 KX áŒUX½bå|ö/ŽõÞÙ<EFBFBD>¾qû,I!Ü&åëa¡ö<œÇ OÝÑÿ]\ÙF®$c0:Bêó"HA&¶Ö)ø ¥æ£u)nÒ´)~<7E><>wÎvdS¬å³§ru{Ns=ñûàŸ“LßÈΣ-íN"Î<EFBFBD>ȮǢ³ê<EFBFBD>àˆÚ[ÇRÞµŽ®JmMQ°ŠÒ¬ÞÁ¨æ EC¹TDzÈ"“ÁÙÎXrAÔ_ €Ÿ¾}ø?6gŸNƒ$!•<>Þôªââ¤/H°U¿:ºî<Òïæ ]ÜÔ!{|…gÖêó'B¸
J˜¦~g#?|ÒRfäÞ-è`pðEúúÐiKd;ŸUj<EFBFBD>¸¹Zïo£Uvz(åˆ<EFBFBD><EFBFBD>W°½Ûç­(({Cÿëñ-ÙÂ+Þ× Šä{S,bë¦È
l§½ïŠSR´5Š¸ëûCEkoRc\ÚWÊàväø¡{ñÌŸÀt^ÃòI¢rÂ3ÉÍL ñáqíi£;ÿ,<EFBFBD>õÙû1ñèêžLýxk ÆÆ3Ï7nIÊ
y<EFBFBD>W#eíw/̯ó¹i f¯žÊÿ·Ê%O¸X]Ø¥ë8VPŠ`Ù!ûß×>_ûîÀþÿÿ"Ž>T<>¢+<1C>!¼=?þDJL{¦`4üîxýkß ®wò<77>ûq°é/
Ò¤Úô<¤Ãü;ÜêÝ0t«È+a/#îñ÷ñø}q{Ww-±ø×à ¢+<CE¸¸ƒ>²Óã<EFBFBD> Öpkæ]ÚÑÔœ¾<EFBFBD>³ÔíÄÎO_ B¦ {ƒvì×ÎÛý,é×é¤( þÒýdåšÈûr¿\Ÿœ¾ ìZ±30W2XÆF²1ÆzVNdq±áØèÆžÂ#*bõª®o­»:xƒhG·j ÑÎ<EFBFBD>Tu "òøTI´ùœ ÿÃaÃÁ|ìhZäK¶y4|Eá \ήzÖYl7ÓBŠ™ÍqãJC81ÓÅÔû£ùˆ }Y;ÏÍÛ$ âżøž]ì¶a;·ïW_¯÷;Zbð3ôÑ‚2ÕoóŽé{*-2h—®áI`Ã¥šçšéEkÁŸ­H5ió¿S+ŠÞÄY”½&®Á¾<C381>ìíºQ¶B-ʈ  Ò‘Üó×Ð:za÷š
ì^<|«OES <¨ 9^'^ 6ÄЊ‹Ä ®g†%X»ºË®¨ <10>7œ£|Âî+°›Ý$ øâ"…î÷þÕeûïÜK[øÌOhSY iiI« _¤šÉ¡ß<16>*šŸÈ^;,.êÝÁBW¢tÆQ$Ó‘7^ã@_Ž*}z@“…koË¢È]uáD+­õÔÁIþt4†ô´Ç_³ '<ÄÞv,zKq_Da q-äÕÈí<EFBFBD>žkèý-Õo2Ò3ƒ¸¢÷!çOƒ§¹x±Š²rMŒ+¾ÛH<EFBFBD><EFBFBD> ²®<EFBFBD>èçv²°ZàÑDí>åËù
$@"a)<29>Ö™]x"ž ´ä4²;>nSNYG»¥Þ)®î§ŽÖü¿IÕwcZt79 <EFBFBD>ßVâqë¹zdKËòêÔ R)]~äËûÝ%îrÝEÜÄÂl ¯ñæ¦æj·?xvxJLý¾´¹ª§ßÎ&Ý^±µ°@kŒs u|#⌢h¦ãC -öæâÿ|ÕñÔ w©Ø½jÞBÞ·<EFBFBD>ð_Þo
WÝãòi}c­>õ6Uc/² й;ïeM£}|H3_k|¯/grb°]Ã<EFBFBD>«)?>g<EFBFBD>ÀS"IKÀLºã€~ÆϲÐ> C8 çVÝÌ+ïÕãÛá­ìâvÕscA(Ì'….FÔ=Ïçp®|p;>ô<w> £½s—«ë€×Þ²~ja˜Ó<þzÿá…·páå%Ê5¶t­àðÛúúÿµ^£1Ó€NäLaì÷Jíp|+ø;(XAC6j“I¼ _a\í«Üú\&;×ß[ï^³vwk§x©ÛÄr±¼qÀHð‰7†‰ Ø“ø¦.¾F*éë®Üä»ÒÌ@÷e<C3B7><65>äuÝ%˜kG(%¦üŠ?o¬n1gÛ%Þ5#G(SmŠ@ÆF<êSÅ9þíic…>ƒ|gØ<67>Œ%vØúµ~Æ„žo¼þðt©Ì;Îi»DHNÎvF g¤ï´äíòqoè…㌆8“ájQÃd-¼~J<>Ce¬-É¢Ô¬ª¦TM7ééÅr>èÄ„û¬ðš.Z<>$­H*F<>
Ý cÖÅߘÏTÌý<EFBFBD>1ÒÀ+Fèn$& woô8#@<EFBFBD>NrÍ°$$9?ñ _½F
e!Ù içz}é%n>ÕV7 âÙ'åL÷Ú© Á¦â'º+.Θüî?(ò4ïßCuEüÏûi|wJoÔ=²îÚŠ©9¦}CjƒÎÇ<EFBFBD>Ï úZÈMâbHi˜>áɼƒ¸×ì<EFBFBD>Æò½z£åã³µ^Ä;¼â£¥á÷Ñ\pÛ꣤í ÿb1£þÎQ*aMü)© œÄšJæÄŠ~}´Í¦ï«Õž»êð£´ý<EFBFBD>žÁ³ÍÈB¥³·ÂªCÕqœ ^BPßhƤõÄ:qàüx`í¸Œ6Ó¥P­°;aM]V6àÅiLÓÑ¢nZÞ dtƒziäYýÉå¬'"déè÷å×°Nû.<2E>ü±…X¶N ïò)Ý h™ íjÇsBù]Ÿ#i°ÈTœSrf÷i/OR•DY„c²gmóôdƒc5ørÊ6<C38A>p<EFBFBD><EFBFBD>ÿ‰&´þö§uÑæÄAvÎá»>û(Á(fßQ÷YW<00>é‡ë2ÀuI¿œ„.AÍiàŠÊ+¤…o%ñôZê@΃³Yðrˆ1Bo~cÒsD8x³x£MEž”·îפ3ü¾ÛI¶0Bç5<ÊÚSåVÚ¤œl¼ ëu(”òaSôÅT…\\ŒÐüÿD4üY=XGšëb™×<E284A2>ø­{R D¸V0¾‡ç!“N•'½ µ>Ë!V­"YÃî@‡Hö5MïpÙî¤+÷øßð!YŸ)ƒâF/‹®êÓ#Ùs&#=¿¶}ñËFds[-~õ­æäf½†˜µ*{˜z-³ŽÎÓ—ÛùîÌ<C3AE>ͬ­Î~U,ì@âÑüFñ{E¡‡3{<0/f£a„o3JCdŽvp<76>ñ²m´ZêŽù§ <0C>5‰™E<E284A2>~™ZQöµËô~1­ç±>•mòtœ:Üi¶¯jï<6A>4n|@¾:Å·ö(S.DšL*'xsÉ-…«W…ë<E280A6>„W—$ÅVÊ­¯¥ë)­»ž?‰OÙqŠøB§¼”“" k,<EFBFBD>V¾µ
®|Ÿä¼»¥0à4ð¤QpÃÙ 'Ò$Wúª‡Ëífì;ª³P.}{¨Urß$$7°I.D:»{{»
c1*ZÖÖ<EFBFBD> }Û¨¨¡GŠAÌD<EFBFBD><EFBFBD> :ò Ÿ%ú²3Ž´u»ÁfþòîL×>y4<EFBFBD>âK 3êê¥ôtm
±e¼µ:Ï£!b<EFBFBD>.îr6¿<EFBFBD>+ev[RãÖ{ˆþ ÇñÖò8¥î<EFBFBD>ôêÏ:<EFBFBD>Òôý¦ßü:)%#«CäçןéŒìOBq$Y]|ã)ïˆý`\Å!/÷qJÃ='<#{íH×­çÇc^^|WŽÃ¢T¡zÐÿöªÃpº… UJ³ãgÃWa-GO.Aû \ì
øv£zþ!@/ø »V£Hb]×|l.®c_FIîsp³§3ìÒ<<EFBFBD>©<EFBFBD>$ãœz4pç8Î09¾ùñ«¤<EFBFBD>(2\˜$ü<nkã~.vüúJa,'è€(;‰¼
Sy}L5[fc`zoYàoOä~M6PmË)]ÎàaÕëÜù7&Âm¡FS×-}ݘ|Ròh3»œÁ# = O<EFBFBD>opÿEBö*×'mE²‰¶z·XöjÄkTËà­Šá´”³—±€üõÃÁ¦m¯y@“Ÿ_ܪ¨ò”ùk<16>èàÖÅ÷|ý ˆ&^iÔ)>Y2=Í2~.Î"ˆ5ÓL&IMMBE‰ È1‰œ­ÿvʨiòzä~ð òãªÈ Î6ÃIS¸¸C§.óµœCcÀòuhÒÒ1R»Ûõ&84úíÀ%ÿŠP<C5A0>Ð)mí^[À­]ùÌ3sRÛ>n“Q§˜:®7š»PHv~쳤r~Ž€´<E282AC>°&Ò£'ë}ú%:¿ÒϺ@ÙDhk<EFBFBD>eáöðJlj)f×SEÏ6¡dg06ĺ+TT%QïÂJìWÌehÁØEeà £ »ÒDê) rÜEݾT-ëÎ<EFBFBD>R<EFBFBD>ÛïpÍ9pŠÄz1é{ÙòßÂ
6¾ˆÜ$f·.e£é¬y4ýšˆ^m\éoUF¿ÆÀ?÷áò=t̃ä<EFBFBD>ijíÍ<ˆÊï¹{Á´UGÔCçÇò )ßóhñ⣡9|2ɲ¨aÃP+QPFšù¤¡ÞV[^Hn_W>ÊûcKT%0³Õ|ºSÝg>+p\J<EFBFBD>³æó¬Å³VâUÆFÏHGu-Ø/8
¯´[¯KºB11yQ 5 ?TbR¥užÖb
ƒrbj%)Ý?¸Œ7ÔgA¼ý$w»½p³ÈÏF
é-ÆÖØÕU8,á5ÀXJùÃc<EFBFBD><EFBFBD>iƒËIIÞ*ˆ,êžWPk1ÄEe˜m±²\á¬è帳~¨ÔŸüýˆí¯Ÿ(¨ù¦ êM%»E«ç7ŽüÄM´-X y¡£ø$q÷ý ƒ%â «Å$©]ÁëªÉÅ<EFBFBD>ÀÿÒBJ¦Î$RéQk¿mv÷M¡!kÿý| G3k_â"¸‡ÉãŽ"H ¬E¯ˆ!·ðÀúŒ6*õúü¿Û ·Ew¹Aíçº+²<B<EFBFBD>è_>¬½ñu3EóÎôžg´]¨[$¢Ô³öêJ:ððó:<EFBFBD>¡HªbYžêÐ"<13>Téñ¯°:Yƒß(³-ÝF…8ZÍ9ΟÅýxŸ×3ËêN™Y²áÏÿ"ôè C<EFBFBD>ûÁ£¸,žx¤<EFBFBD>¹˜³&«3 Ãø=<EFBFBD>ÿ+ŽøÔˆ["t0¼sD<73>úƒÃQÞ쓼,Á¸ðkHq'TB˜Ã;ž»‡cŸ8g)¢ÑÏìE‡€ù Þ{ÒdÖ“äç!£¾,°#M`^¢ùGÐÓS”6‰rÂaÈ&þà¯!˜¸2<C2B8>%a0äomoˆ3w…ì |[7Qçs[2åÓVê 'Àï#
÷¹çÎýÖ Ç°ç¨ÿ»Y¸§iIåæÌ-Î 6¼ÞŽÌªíArGþ<EFBFBD>ÿ]k+^eÚ5@=>CððMÿ[íŽÇ6R0¿:ŒÎ2÷eÏÐ&Š<EFBFBD>5Žá 鳦}Û-À [¿ÄÜGËû÷.ÒW$<EFBFBD>\+C-ã_
ùØÅôœbO˜{Íø&Ð3Ö?üH ~<EFBFBD>»Sj|b5fÛril=*¬u7ráÃ>x:£røѵ<EFBFBD>Ë?ÞÄÐ¥_Û.e1¯h¾ºP à{ƒÜFòBC,{7j©|×WiH,j¦ÓÝ1táë #9ì»ÕúžŸ&6˜Òa¦ùó~SúÍ=!Â^ÛÕ¥õæ¿hj&m».3¸OE@U7:ùàݸ5ú9¬?ËÿrÑx/zìe2<EFBFBD>ùY\ª(H 1é¸m¨iѪ:è\§Ý2Þà:M<EFBFBD>c4Øôäf$d(q/U:ig Õ3éö®5
³QÂÙH3Hqœw Ïd»ÚRm4Ôš²Ò<EFBFBD>JËÑ>«Æ©%ºõ¬<EFBFBD>Å;s -ªÔ\Ð×î²ëóçt`^BwPží0)­xPg¤¢¤ºBŽoCÉÉÑ~·úËCc8p,ó=`:E ÊЮü¢÷­Ü¬v?q<
ä¾*-¯g[ʤÝÓÝJ¼q ÕùF(+6¢·%S L¨H<EFBFBD>D{^½âHÔeQ'u ?ðéxIÜŠÏ<C5A0>u—ÞýȹÏü· c˱h:zËÛÐa¼…åÜÖîYÝ<59>&ÍâB¡x‰pÌ“£<1C>„=€­<9löË<C3B6>YZæ¡x…í•Q§ ª#/2ãß?SL§ ¦e(ÿ°t/]Q{PCé}Óu…¬ù½ÿîÚQrèú†3.Ç?YìVRå)%r*„©¹ë6(Ê=L™¹:Àúz*óÀi.d´ù<Þ±œ³ôM™¯q$Ô¤´™2<E284A2>ºÿµÎP@šSûT<04>
ïëµ)=º  FÚËGV-l-6O9^<EFBFBD>g«4Ë(b8Ô¾]§sf ûŽß4Ç~~TŒË¼Å½Ï«­Äï<EFBFBD>ÜÌ[Íä ©Xˆö{v(æëTüKYHr¥Ë?Íÿø\즹'îÑ@ËáBŠÌæS¾(˜ÎXŽÙ@<40>ÙvÈ”…>†ž>Õ”õ+÷\&¦z\ØlˆñÍÔ϶Çø°àû¬êÔñO3q³€ý]¯ ¼=,ùlÙ1¬<j¥{¦þ`¡sY‰<E280B0>…½7òt¨@,j~mѪh g”<67>Z.ÚÇÍXAÂõW d<64>ëÞÊnÀ"dìî¥èkëA,îw*èx0³[dœ½á?Z{î <C3AE>6
;ØøwÂôNdeÛKä¼ïnE û'¥ˆW;W¿ÃZ'´¿µ ºöÃÀÃÿa}ïÖ+þ6ÁRU"páo¶÷-_x¼ÿæ1{dð©-<2D>À{Y¦O>ª¡â6ù±”À£á"Áäsp˜Ë¢­ýoqéá8Ó`­¦°©Ï?èCíVân\VqçapÍJõIîûü&ß<EFBFBD>,ª¸%e¢c E§òf8¥ÉbG<EFBFBD>ç^ÿcjnO¡ ;ëÛA¢zpG¿J/Ôr«¦<EFBFBD>ó4¯¾ðüË»øg6síûý÷Èêɬ¤´G=Û(ì. ¡2©YD@'8W_ DÊÚQµœù_­UpoÕ©BÕJJ#dB©®-§µ¼·±mþ½6¯µD@™>âŽHh»è4wû<77>ýãù&¸ü}5Z!<)°YŒS®0ÆIžù<E¢ÅdPá#Z<>¼¡œ§•g`sœ¸ÁƒÇ
C<EFBFBD>Ô¥¾aÙ3:q˜R#»<EFBFBD>ÈtÔIÓmMŠ'°<EFBFBD>>ŠõázSrFFr-pÚÎàÅE,4e9ŠÃcnÔh认¶Ld=dG©õÿñ¾é8£?WóYž÷ÿº£ª4.jØÛéD‡¡„ÔÝÞ9Üèíª­'¾ ÝéoEÅ~IOJAÞ¾} /Ò,T^i¹+>ÈG¼^`U šìíï_Àïde@2ªÓ®WòI¹Â¤kk×A¸ÜôBCÚÏkùÜ<EFBFBD>bîr©ÿ|<EFBFBD>+(æ!ñ³tãCëd¹©+>;åÀDmÛ³] N^Š¹\žw]¾Ñï]S<EFBFBD>´åHÎÉÃ05|+¼2ö½´,×Ñ<P»Át·#à®;$z­˜mO·Â,®9èCy½^CójmsìȪ©D| ;¤<EFBFBD>­b<EFBFBD>8d ¡$+ð¿íw÷« Ý:v°ïØï>ë漪o˜ ?-Ï<EFBFBD>q¡[h^@=A¹L4^\×^³ÏdcEVõÓcµU­ŽÇÍÂÁ¹SÀœ#&}δ¼¥¬60ô¼PuâIÕÿVñ^\íòB!íÁDJšeõÃ@FíP©+Ði$smcOz<EFBFBD>Ü ¤|¤^ΰU3V}NZ#$/´ ^ê%t¨opïVVÌžï@oþÑÚvÐÚŸ'õè˜Â^÷r•Õ#j±&)Z/Þ=Xyìž<C3AC>48P<38>€àJÜÎòäúÊr8mýÕEyõõ <> ò¾<C3B2>ÿÓ©©ÓÿºýÅ}´SŨ:óÎÍ<C38E>à‹·àJ%
c$<EFBFBD>þB !o>¾<¥z"(HOF…êCKfQw@<40>N×HÍž?š)jiBˆ7<CB86>œ˜ãŠ3þš%³R8ål´“οàÁäÑ¢nþµÚ7 ñ7eYh/2Nd+ŠíRÂ/¢‚î%ÝC&Ð.™rÀÁhöð¬QcÕa­Žâl_os¼Z7ºCüš±½@k`±©ÀKðŒb»ØËÃÙÆ:ôl_ Czoòûˆ«N]Âãb•ç³Ö]œßJ¹$êÖÇMª@×x”¡hÐTóT=ÙÀovv²°Å³;P©1y¶%Ô_ O¢ª®™€ÃÅíñ—JÎÜë×:7ò#ø§!BµgVýÑ_ 'G@œ<ƒ8oR±Ö<#‡NSSHËxò&épÝt s“)E«øÝ~ͫȨ”J¡"Þ㎩LÅxñ¿ÍÈ Þý̲áÓ[ØyÝd)ëÒyöðY1ëLv9º_¯ØL'&)Mõ¯7ʹC<01>9Ʋ|TæäOc&hÚ¨bª»9r3àغšüä9À'ÂV½î¦av'öBžjP÷°/ZZÙñM$1Άz!D­ÎZé1aJ~¨}4ìmsÛ®«k¶^ã]krmg8Zt<5A>PGsrIu<49>]K+ºÊ†¾Yƒê¡4µQUux˜ ÏÄ<EFBFBD>ê$OVp¤Ó+Sn}dÿv™k'¬&4;60#=Y<;>ÄdÏä¿uNŽ,Ñ˸˜ì¿¡©+ÿý«[,¸à¦Æªn1sÌó°9½ŸOeªQi­]2~éq1{øëÞ¥(J­þ60¨,²:z~ÍIYë$Ø!Œå[yÔdPmeV ûý,è¾Ü`ü&eóªªU0¼=/7»^ä­*ò/, ðÅ<EFBFBD>bvα4=
ux\kÍÛ7(ÇÁŸ<EFBFBD>˜oÝa¤¯|x~œŠ;¢<EFBFBD>¿ªÁ%Ü¡·æË9«¾è?ˆ<J°½¦qÃsö-/D.äz3Ÿà¾%*)+8(Ý #`³¨2L;f¹;zL!u´M]_\Ü¿³h2 #ïÕªƒ˜Ëɳ<EFBFBD>Ø@ªÕ'n/Ý4ÖWÀ…Úìçtµ“úÿþ@ùvî{Öü†…Òã! “VT÷}Íâx
ÿ´u%óÄ»!§ü=¥Û<EFBFBD>ûttCrÜ©<EFBFBD>ïfBÙ³£°´ÅUA¼@Ø+ÅÛ÷Øûè¹­ìÙ<ÖÕ:7& r%;h!z.ATú¥IVjP³Oï¡<EFBFBD>žh&<EFBFBD>ð"<EFBFBD>åÝ„3ŸéBµ ú uiŠÒÀ³xCý9vUP{Ö@xôhHõñáÖœÁÐ<C381>iCÔŽ÷ÜÍ4+)ùž:¸<>˜¸e£ß8´“¾<E2809C>œÀ$͉xyÜß<¦ÛJ7½Å®Äå ûjן3ä¯Zœ" 1ZÿòvW>;N8}-òYVkS 5x,ãñ <EFBFBD>/S˜Vð4D;Ÿ9\ÂÍ-0Ðè;kÖíˆvqpt¨Í  Žˆ Í°£ž¿²­Öšs#C,>G¨[Éï:äîT¥²­g'G w¾;_7*%$%§iL?V^Là<4C>5ʇj5øÍ=Ò§¿i×éòStí}ó@>‡ÄÜKu}0¸<>4À.ðÓÆM‡(nKëÞÙØÙ[sÁ„|ÆMŽ-N;03\Vì“ÚšÍRbÊÈc98gd©™ ¶n„Îf¶u=<3D>=œ9T¸¦ÈµsüÑvWr¶@nµ¿á´áËÀ½ëw^ŽÔWa|/:ôã&¶·Íÿq×fgô1M '8¹Í4¸DM`æn_j?¡e2±kiÃømŠõë¨"æŒÙ¡/)¢<>Éú<ñ…릈ÕCÓX`1g<18>ò N'mHù ½\¾rñnÖ/8x<38>fM^Í•NUZ€_¯PèÁ.{ ¯¤…!…ÜI¼—ñÖ@³ÉÖ´mR05×< ú¸õÄÙÚ]œ}Aú9\><3E>µ/ÓÓD‡Æ­+—'<Ñ-F=Éþ€Í¦KN•Jk£ØM^;…Wï2½T‚·ªè³ òA\Í¢iÌÑá«ünÐ~!l81õ?ä 
ý7£ä6Ê4<EFBFBD>³/š) ³\ëp[~æ?Hˆ<EFBFBD>îý®<EFBFBD>90tÑqòþý6ÉGê5Ì<EFBFBD>žê¹0G~|½íÔT9#<EFBFBD>|ìDz î4z&|° B¤±ì)·ids¢Äí°ãhxKDtféƒjðÝ¿>&x»zÔãü^ñ° Mw˜e<EFBFBD>ïž$E)½6><¼<EFBFBD>h*ØÕYùåI<EFBFBD>âÑ{œ0Q"‡«-E¢d?>GªÈʾ·9>™Ë’ÅÇú<C3BA>b[‹ª}«š<>D²‡N±† <20>¸­ãÉÈÑèõ4¸Š!¢äÚØ÷æçí n.©}+
UC2Åß¡¦øÒrŽO² ­nPƒåáÇ}÷º$tz<EFBFBD>{4¢w>¡f%Ê]·;¤áƒü³q¹ÌRz]+Ò®,?W{æše·bsRÕShĵ{áÐ(@²]ò;+ã®xo×~^O<EFBFBD>¢^´yBqþk³ÉöéânlœLfS$Xù¦;èu\%ÒºÒv«åBÙažgatæb5F¸ý¨ŸÅªË¯ß1¿p²þ©ö÷ÆnÞÅàMx$9Ÿ·ßar9ÍyÃWÜÿ`ä?<EFBFBD>µñDüįF´ô±öÆ<½HôoÖTv,ô;ƒuãB=õ´Ä2ŠŽ8E·;gHœÂReÅBbüYˆhãs¡üq5uSlÝ_ÂJ<EFBFBD>鈌˜ÿžä¹¡×Š`_+<EFBFBD>p7ÈàRý[æ8Ü¢ãÃ<EFBFBD>ªÞçI<EFBFBD>¤¾FÕ<^C¤¯~-µÉ0´j^2Sm­¼Õ3 ñ%Bo/49Uÿ#BŠZòÃø$B>Ç ýãKðtŽÁŽÿ:HSûÙË[d²í·â×PÓff]æ½_ˆâ¼bŠaA«ÁÉ<EFBFBD><EFBFBD>Óc+ùP¯ñ?`Õré̽<EFBFBD>4þÑl, $z$Gëë¿ò_<EFBFBD>0Š˜%·¡ (öHÝæo;⾌ãB²Ëø¹J&[EN^<EFBFBD>¤Z/D?]¤ú êÏñ´5ìãr|Q&Ë.G¢ß3]˜-àïçÀ«æ8ÍÖ_+Ò®,?WxHÆèù­@ƒ¤æW·¦ ¨32n<EFBFBD>û_@3¡
a¾)[}¾}æ+¸ô6{ï œ,'·åˆP‡±¾6{rðüÖžl_…ùþGÚߟcª,{r¦Ù€.xà¹iO²ˆ¡>ɘp+þ6ˆ7½ÞÂ+VO£wVØÂoBh%+³yø2~^ˆš<4·ƒ¦½¥9Ìæ3yBd?<3F>úÍSíàëåÁÞEb¥z[W'CqˆÎ(|:¢ô'Tfì,§*PÏpñ{…½úÓî]¦sØìƒ<C3AC>Y)×Öaxž¦ÿaæñI2ú<32>çÆ}Á»YÕMÀò8ã¨!Í<>îpþtçB_
Õî-2Øß¡î3{}µB}×DsX<EFBFBD>~f5\·¢¯<EFBFBD>~:v=6y¢ðàÆño­d¨¢0<EFBFBD>Ê.žÅ¯Š<EFBFBD>Áã ×8ýç¬üò0 ¿Ç%\L<EFBFBD>I0ÿ&:iÄ1|9[üVlÄÑþPã<EFBFBD>ÛEjÌ°õjq %T 3þw¦3`h«vÊNvÊwÁÓñm3-÷&Qk!IöÃt|œz
U<EFBFBD>:h1*¼k´ðK^z|©¸Ó×_×(V¿d$ÃËÈþ¯$¢å<EFBFBD>ž ⟳²³5÷ÚÍÈÇÈJ ïâÝê
æ½+ú5 qhÆéòu<EFBFBD>5­+_R˜3ß
RøµÇ 7<ÃázÝŸ½ü±§þI<EFBFBD>Æ©œL©Q2¾b%켃ë<Íu#ÖYC¢¸ñ
mp7=MF!Œò±TüÙ»âShÿVÅB§T<EFBFBD>PTH_YÃPÿïý°ãÉ<EFBFBD>ãDÍÎÁA¯ÕÁÇ}³Úä©<EFBFBD>õ.Û$×Û(µ÷. lž¬¿©Ž_ø59C$åóC\P¹`^µƒ,`ÈÚ¨.G£¹øƒÍ¨ ­p ¦¬ƒï׹쟷Ë÷:YëîäJôÎ{ ØôE (=·.~ ¬ª5G½À{d´g1­û×®R_áe瘻h¸<EFBFBD>9R¦ ëÄ<EFBFBD>ÎQªùÌèúþÀÒ®×âÕÐÏÐR÷êåäåg) æGšvYjcÕž¸eVÞíQ÷œ´@žHdüR 5=ð^2Ÿª°#žî-¦˜׺­¨ÉßÒçOnßê²;5F^¥LsF«g¿G'ÆmÔg¦jº„;q„1Êi<xWy°vbv ÍÏò ã<>Ù¢¶óÚÑ<C391>Fvèç²»óíTÚ<54>§¾máƒLÕ¹1 “ýf4>ç͵â`œz{úp®óì~¤Ž ϺN~g<>†Í1kòc;bHc<48>äEíJ³wŽy<C5BD> eÏÐDäÊ<C3A4><C38A>.*¹8õÌ[²á´4Y+ùÉ,vymo®Ša~m ƒœ´m?X•;ae“žðÃÎed*­<>[䆇[v~<7E>Sä)À±wZ¨y<C2A8>"eÓŠÀvJ¯Ö÷Åä-u ª¿³—¢{½K?;m)néëcq
!ô{ΈŒ*õŒÂq|Bo<EFBFBD> èÛ
8!jA:«lHzy*Á¼îKDŒ-ÅbŸŒ&¼8<EFBFBD>=·êŒÃXáŸä±а£žÑt¥óŸ³Ã¾ì¤Í±Ø¹Zrâ 7ì±Xþýþ@»¡Îu˜Z=0+*«·¿È£ÚÎОàµÆJÛð13â·ÏosÙU¬WžC¯ý º¸WcÙÿÚ-ßاٳ` T)ˆÞ÷<EFBFBD>{PIL+1µop4GªÔ÷M<EFBFBD>`6pkòfœwZ¤cûw÷p'»°Ð‹Úã¾K .¹¥é¨@Ž‹Å¾ÃÙÆ"úUôÏ$ÇÚ§þ 'vAûýZ½^r24 $Ga³}yc<EFBFBD>ÅG0Å÷ÓX_âÈð<EFBFBD>ñm|@Ž Šå1<EFBFBD>#þšªÈ¤ât×é˜Ë]âô-¡ÖXó4æ·Lëé+y½ qI<§1É[1-J¦¨35ù9'x†“ŠaÐ\—Üsèhh„h1gŒ¤ÂcÑRéì3´™„ÞcÞo°¾²)¶Yù<59>Q†t³îN´°p§VñªvÈA¢À9¾˜<C2BE>m}­Å]2¡7f\ ,”¯ðeÎ<65>ÎÑúiÞ^^z^'=ɪßÏ(úÈеStÏÄ­ôþ½¹SQÃFÃôè2]Ã
à)ʨ¾¥ŸÏ¡<EFBFBD>)<EFBFBD>K<EFBFBD>Y[8í?¾ÖH)ORJ¨<EFBFBD>qíI]$§<EFBFBD>½ÈôÕá÷£p ˜ {f±QˆÄ-bâž2óÒÈ îJo ](aó8ŽF"~‰:ï†XBs-]:}*<Ñ’ä=?ùžêúg‡zùl jýê"sZe<EFBFBD>\?º»Ç/½Ë#ç²±ÜøŒ|IëÆðÊaÜ+Ø^îè;pñã9Rÿ½bœ«1r$u&Qm½Ó6·g?Ùç)`¢ÇŒXq«ä|ˆ˜=$ŸŽ+²ÿ3<EFBFBD>Ú1°ÈP8ú=µÃÃA{PÊÊæ<EFBFBD>f Mòï~û~Øú=Çá˜ÜÂŽŒÜ:3JùSI|Q«X c±áacícwŒwsP# ùmÀÿ´êcÆõ¦GÁtÉÔWÌB§ÞÚï¤Ï˜Žý_Áôj`ýD ;úŠ¢P"A4!Úá?Ä^3Qqƒ{µD$c#@X°Ì|qƒkùÑŒŸ™"à íÃH§q%o8Æ®½ÁØú³]ÿ^ìÎû´ÞüýR«÷¹ïù{[;+ˆÉ«Òû˜JC!¨Wæx¾½}| oŸ|Ùö±í)cp]S<EFBFBD>½Å|<EFBFBD>ÇÓÉG8¸×!X­¼UV¼ÿšÇS<EFBFBD>iæèmñMB~Ì@~ñ ¡~»ËΡöM¼[Àu lÀlƒÁhˆEÊßogÊX¦<EFBFBD>do¦¬9OzS§kÚòì-:0 m.¹Æ1ÂË+ Ñêg;úâÁ±9mypŠzžÿ!ÖÄU"ù5\Ao9Ãs68 BÝèS^R3ÆCœ¥"®2«¿qy^u ±ú [ææ+g_ñ²7¼!ÂZØþ'Weaè^»1¿ÙõÈy† ®HÎ #tsL¡* <>DQÓƵ9@<40>AÛ~åÒ ]<5D>¤w‰nç+N$h*\‡Ç¢•<EFBFBD><EFBFBD><EFBFBD>Ô·ª¥¤¥'Éø:Ã%ÒÍ{MžèÒTvñóMW/h2×öÅ(oGiÓ$5þ<EFBFBD>µx&ÿGµQ´vYÓˆ~Õ ^YXYÛ<EFBFBD>snm#<EFBFBD>( IsœºŽ«ú7­1²Ëç=£Ö"œ7ÜéNó³ K${µ'Û]Xÿ/r7Q<&ÕÂ/uÑ÷X4Ù=7r±}_ö2Üš2/ •Ø‰¦Úç%˜)Îù/wz‰±J
ËαßqHrÓÔ8ŒP(é¨ý¼£_+-'ñ¹ÔÃÏÍ-;<3B>;dWƒ8J¯+8aZ¶Ä¹8pµº.z€À©nÛÑÕ!Ʀ+¹.)ø‡<øKNÓðzñêYªËßJ3ïŠ
žˆ0:M(ß[Ù/a 4Ý<itý^Å°hÑtÍt+óQae,s\kNjø*®wˆm=('ÏÙìÇ~úxÎüJI<4A>I½Û,ÓÑðšáy[äRh,õYu·=i%ŸB­)%InCƒx^µ£YdáMóÕí—X˜€#O™Ü2B<8}µªêk ÿ¶"c]ÞúÑ<C3BA>Ú}XÐTÏT?Ì=HEþò Mè†+&.ÖðtCZ[—å:<3A>q"~×DüX­/°`Uö2>lÇÁÕH^b^l]`¨UKkN„¿l{Ü7·)Ñ6oÉœ—À¦É›é—xЧ=áWÿŒ]ó÷|âÙ<C3A2>ÿ»Ám“9ESCýP³…Vf†©t¿Æ´™~ý_"MÞTfÓ~§Ö<1D>“z<00>oƒ5Y¿¤Pÿ¤jŽ+֟Ϙ˜árÖÛurÃgRÍ8ùýª¡ð ,úÀÀEÊç%ÎÓ88?Kã+® ¸`ê=fÔI<C394>[ctX=”®3Ü+±<>Ÿ€Þ•¥f•
ïÕŪâç)¬ÇÀøÏýºJ SŒ 
êñj>÷JeO<*ÞFa2´P~·*ls¤È+ACS,ƒ`«^N²ëUQ.PgR|ð Âè#Øßö¹aúŸ:¤'{ˆñµ PÒ'ÌJ`Vl/š<EFBFBD><EFBFBD>F­ƒEXŸÍö20R"á6Å$)a-:¾?zœW<>ѵàõuiƒm´'¦¦<C2A6>#-—9ž+7&øá¯wØOÊ<4F>tÜߢîqd vâ÷HôÏ• Æ*<±Ò 0uJid/ÜY\¥!ZÊ5VWðG˜ÈÉ<C388>ŠžY<C5BE>¶»…Dîð<>ù_Ÿ,fð£OQÝ…,KµÐÕÄÐ&Æ\ñÕÐÕÓvPéÁéð ˆ<C3B0>ÿKG0)’Þ
ÜuJÁ}».wNÀŽc˜]>Z3f3i¸ÂÊ5h5þÁiàŠu¦¨Ïj/Œc žœøÑãXð@âäÿ T5òô 
Žz ¨«Z-žjB"¦F/:U}B
¿ªî1tuŠJqüÙ­<EFBFBD>>¯u} £¾æ ·ì­û<EFBFBD>D@c® #ùø®ø¨[¾ 9åöS H·CYÏuC xc¥z{;t¦HKöxÏÍÿŸ¨°·^UorYtø¼½`«!¿TÓÕx!䈎-®çÌ(¼£ŽQ×üJZåb¦mEw]x®Îé8y|á5ázB.Ǭ]"¡ì¸"ªÄ°öTóºõ<EFBFBD>cm¨CsB}z;¡+ê£÷Ѽ »©©ää²9l¼'ƧÕKqà ÞèÓæGÛù^TZv½[ë'q½"ŒI8|¯0·¦=•NäÍͦŽi9°I‰ Àp<EFBFBD>¹st.ú±N:hæꮪÿ"¯À5RC`ë<˽ÜÃmÏžÏÔ>Û­è]¤þBe`6áXñ1µ²&ìH+_!ܬRײzx¾õFf:Rfÿ÷<EFBFBD>[|Àót,·1ÙÞæí¥¨Loç[¬1?HËÞCÄ7Õ$Ø)M=0{úù-<EFBFBD>l)÷0˜óÒžJòÞ|ñÛBÞÀ I»Â cÿáp©ÛEo³¼R <EFBFBD>Œ<EFBFBD>Ñ´§¢¡¢$æɼ··9ûÞÉ1Î<EFBFBD>ÐÈ\üW¸YÝ8 oR[ c=iËMÝ ¸{)E}2dZIDCÍpòÿkVØš}pkjkíO¨×Û±ÐäݹML<EFBFBD>q¬dø.Yp¬ÇHªëĨgÜ$1m-.?)ü*i1sm :ÃP²% ZàACȪ¾0h7}Fr^y1CœøüÖç'<EFBFBD>·oøôÿ€‡Ù.v É´<EFBFBD>À»Ì&êýØÀ%ÖL<C396>E9ƒ¼VùŠø(°"ý~N=¬ÞÀ“ùõ±c¬Y¿½´èBAµH¾¬ršXydšÐD:²{“-¾G?Gçy¸¥á:¡Í.­Ï µ4öñ ÝfÍ:è…ºQÏ]‡‡ŠÇch³Þ¹[äÒ |v(¦•+¶r@=I9íÄ„u@}¯0†îy˦B¨~ÕPÀ‡úÀÀ¸ffŽ÷°&(u0Á̽ÿXm'Éø<²ÔÚæ­¹!?5 ~^z'ËçÔ ÞP@w駶Qõ1þÇ:LLò`²|€ŽŸg†äp/Cî¶anTØ%þïÓõ%’¼Ô¶2IçWP  Kd S 7gÔþÁrM¿Ô÷¶Žy#êÓÿr5øLåJFëJª0YŘá2îÑ<C3AE>4¨%k}˜Qø<¡<>¤&ž-ÜMâÅ’aH
QÓx´§ýà.ïïD ôp> HTt'.ËYÞþ]'* wõgE½ B;½ŽœµÔaœUjÈóÑ Ù¥àš·tÍÒ&œ´]¤wÂüèÛzz¼ïÕ¬Öþâ­ä²ÞLÿ£1Ö5?Œë=.3ß}¥3ý§Á@=ä>3PÉpZVåŽü½õàcóÊêlµ¡T«˜&<EFBFBD>ìÌÜ0 <EFBFBD>$ï*±ôò{ïBÒ¡âÙªËâ­õí Ÿÿ-¿M{THm ô½ÒÅ7TD,¸ÝÒËh èÛ,çbàqÆŒÏ6X&k
tF{Ÿ®¯{¬EXp^»ÝñÇC9+¦Ç¹L«åG¦Ö)ûËÐÜï*Ÿ(y%:qH·ÒʯÊÇ~;ÍLg½e¯þÍòâÈhA˜4˜ï\¾®ÌúYÖÛü@v³¼]ùàŠamš¦÷÷ûƒmMpÇœÙRæE..ôÿÇÿµÀ<EFBFBD>eE3/M!Ž<EFBFBD>,¢6ƒWæøàÆ<EFBFBD>SS˜iT¨Ú[-ò6l©@ϲÃK)±y[h@"³8'Ôkº ´<C3A2>I<EFBFBD>×& " %öá5¦C«HÅiíÐݵ({­Û§Ï]¸š>ïÀ ÇÄc°úñ4}ê\ Iôcbÿõ@¨îÕ ûG ÿAQ7V$ ¿1YWHõ±ÛìnxO_VL/ý\!sáem3ï(Úª J!²¬š«÷ŸwI×;ÝÁ<EFBFBD>ÅØNæÝì"ÝÞt_G´çÿ³# :$KɱåÇš·qëm±øo<C3B8>¹ÐCƵKÖ`]柢Hry ¯õ¡,¹{[ˆe¨ÕŸ²®©ëûΨڿÆÉw*Ðè’ÉÍïÞÙØÙ[óîíîp2êA.Ñ7 À Š¨zT·Â9m`æ>í[óI„œQg `<60>Ðgqì¤Ýu÷¹¬ÐÁúÜ<C3BA>Ñ´§¢¡¢$æ9âŠ$=4EÊ—Ç`Ö@üv«O|¿©ÈüTP„þP[l|+¨|a¶$‰¥
Â/A4w´V/¨ªŸfye\}d)¬¯<EFBFBD><íL>­«<EFBFBD>©ßƒÈ½aêõ:´Æ3Þ<EFBFBD>yGf<EFBFBD>ÁÝ"%kÖkï .9<EFBFBD>ÙÊ$.¡u?<ž+8jsw®6mRI[¦Ô`²ÓÆ£—fëÃvœ£j¬X¾Äî´ŸÀ“Y7,° œ±ËÒv!òàÿ]ùÝ•h@ÜÉ9aòöµ=ÖÜf9x¤^Bgˆ¯"z]J94[ÿˆØ,ôÓ<EFBFBD>ÿN°¥s<EFBFBD> ²Ö±]µ
âÍž,±¦·2ÑàO Y¼ÃœÄ¨r±iQ¬ã¢¼.½¹JÎþGƧ¬Ü,%<EFBFBD>%ü)J:B`À0ø¥X½ÜÄõ<EFBFBD>Æþ·a.µQ{çZ5É­ÖÎCL1ËÁDj g!ÿðW?Û8ÝZÑ"È(míî‡û¹“oá³àS±ÐAq—ü±,o~ðV³ÉJ<C389>XcHУâ…È}-9“<39>êǯ=¬!ÜpüÚÑ|þB{X§0*¦êt6£fúóWr6Ò¥ZL$O#ÿ5kP(ø`a*²su[VPçRû2`u:컓î<E2809C>Ø>3Ó_¥Úˆ—¾bëö;µÇ4ß ×Uˆ4
»èz~õôŽ·üŒÐ§|H<EFBFBD>ø~üa;2IFt¹¼¢ocÞž<EFBFBD>ØB·ÆÞ±¿Ä<EFBFBD>2ÞŒˆ°Íô½Ò£¯&5Þ»I¤ØNIßãéâ ¤ö"w2œ,&(Ay$êTJΠ*<2A>ž-:ºÒöíÿš<15>×7(“ï€Ìk”h5;|›Ü;nþg†¤
O±úët¥?¨h°¾¼²M i4Ó-ó/B)S&¡s<EFBFBD>§ËE¿yŠåK³Ë¨c¢?£ìQ±ÇÖ"<EFBFBD>ÿÇmv°L¸øEK*ª¸-^Ñï{<7B>!Ñ&BôV$òü\Ÿ;t‰—¡pbÇPf”ùœø¯ü®»Ol«“•h\<EFBFBD>¾^C‰.<0F>©ähUSÑáêU¡Ÿ·þg”hŸ¯KWr;ØÝw 7Ta~ÁÐB¨-—oéªL4î)×ù(<28>ö¬4¯'âôx(zÁ˜<C381>r!¢ xD X-5-=¹ÓQ+?9í|n…·Í W^*ár<C3A1>v6<36>õ‚@{<7B>b“âc˜yžç©á<E280B9>À5yÅÁÍÖÆÌãòƒßâÂìÃóI@/ YiþCt?6Np4Ø\Ã::)ÌÊ2ð8ÆGH'Ñ‚Ü»¼+Îf´BéÑÉÝœ²ûŽóô¸cs¯­nÓ'GÐaå }©$þj ŽÁä7'¼Q¼ã<C2BC>¶XáÌH™k ãj 4ˆ½Ï:ÄÜ[Χà_éPjÏU><3E>é'‘ò”#n7;@\“Æ-Ô¢º$<Ójk
º5ãÀ@,hPÊþWêH÷rŠ1o¿<EFBFBD>·dÛWWLd+°8/ë4#iJ§¦i¯¢Y˜ŸnÍØØw*ž
èÅ¯Ç ò{µ;O®²çÚ݈âþ¡5DbC£<EFBFBD>"^h5äÈI-,½B4×—Pþ¹<C3BE>Þº}}rŠ¦QÌçggqŸêm­žþ×}µƒ¦øŒ®>2Xâ]å#B <09>$2ù¤qPæ:Uiíl­T®ëS$&jŠG™ôᢳQ­ÜŠ¶ÓGŽ<47>@€ivhõÌ^<÷At㘫úÏ”<C38F>—Å`ÒX±6<C2B1> .òŠÓGU?‡4e<34>äÜAmôýóDè ]ñvqÙäU:Zd¡à(2Ÿx£é‰]>ü·õÁrMÍ9éiÜ<1C>»#ôëq3ŸKBàGØñðÊ1…ÕÚ½¨±±œÏ,;_Y‰Ï&ctÙ²L g-”o}½­LvëÒ9v<1D> f9 =Õþ<C395>oŽd¸UN<>`-òæôtËö||²mmÑÙЈE¾ªõ J>Ð5a­äbZôÙ_ú“½ñmb<6D><62>ƒÞú¹NKA¿/z°ÕŽ)÷o^ —êWí1³Ï®P]µVT^a‡¹“"¯Âz-oy7 ŽËËŠj<EFBFBD>§_¾ålàº7¬M&M&²Q.N Œ^Ó´¹®oâ_¸;@É /µá] R~7B÷Ò8Q<EFBFBD>£W´Ô}Ï/ß/&%´/7×ä³.Liš¦¯¢Åx¹äL·ä䦺M¿Ñ`ú&p`{ççž`5W˜ü<EFBFBD>4ÝÔ¢Ì!öÒÞ)áôžžÇÚ÷¥#M ððOÜ!õªaAt~6bAÂþŽï»#÷3!Èã%ë 2œÕâ&öm
bùuG÷ŠëÓAO[­,Àö4;+h9x»l*°6¾t_J <EFBFBD>è´»¢ñWlµA1<EFBFBD>åÕMú)©ÃG/°ëòR,¼]IDóØ܃~yFæ4ö 1T^<EFBFBD>š2npJ´2ŽÚÁÁ˜{œ?Bö5Yß¿Ç,8Ç g]Ýñe|ï_oÐÔXOYÿ¹ÌÐD®a1¥
.2­ZA"Ê6ÛäÇpv ÌéžÝ‡'Ÿô,°ð}ã<>´Ù*h”1è ˜ §ü`V»ÄÄ<C384>ÇmK&wœæ9ºg6_K&fƒjɺ7«üCÏY‡Ë3äSÇpgfÆ™Åc¡AA與çZ^²5ÊDmµk~~o R~œ‡Ú^+'ŽêHß{¾žBcó•o~c•5÷GT&,Ç芌Y[zR¡¤<C2A1>€±ÁŽµR¢jp¸j/· :ýt…F £¿V™Bþú¥àÑôªHfS Líf0eŠUÄA†]2÷ØÛ¾;÷e|K½UXemCïå«Á}ã=L™˜—¬Žþï{¡¼ŠånÍɶû¥¿Ì<C2BF>ÉWØYàei
%;üÙ¸ÍI
aU1мÛ" _ŽUôA~/»é—ø#vÖUÚémzÄdb$îB~%yrH­º”"Ò¡¬-t­X´ÌE<EFBFBD>ÅÁ¾×Ü£¤ñ妈&û@î<EFBFBD>Yãg6<EFBFBD>DÌX?¥êL & ZEÛѤ¸àÄ*ŒÜÇvæù\§×ºSmÃ#¢èh.©œ ´èÚ½ß ÕãÑÑ?èã<EFBFBD>Ëÿ{¡}®ÖºÓH5ȾQW}µX11´v]=>9¤OÖZR6ˆ±l°ʺ
lDÙ+îÇ·äÌ4i¹öç<EFBFBD>B<EFBFBD>ã^Œf0<EFBFBD>)+;/úû¼O!M©1ù @ÿ"Ï<EFBFBD>2úÙHaLüÚ•@ˆö<CB86><C3B6>€zk˜\7OØ—éªï6¦l_Éå¹´ªuÕôÚaµ´4D»Tl¾ËÌŸqüSxër¿hìãÿI@vP<76>º1³}!!Vçò«˜ÉHCep|rteÚoÒÔƒ*“£jæýƒÎS<53>v๩Öê¥TF—¡Æ¶777gP„m•ß”ài®Î©òx<03>ìú:3ÑÒ…ºÝ~„toì0ÛYÜ“£<E2809C>Î,©=[Š@h¾{{ÆÕ/Œ(~#W/\ž¹DõºË<C2BA>Üâ2T ²¸öRæ#ƒù`·ÞGñ_)íW©à<C2A9>¼Èå“ gÒš ù—vËü:‡Y€ºäwuLñß—<C39F>ž ®äuè2Õ<7ÿD0ØÒ6øÛÎÉÈÉK ðãÞÝÞ`"øóòóu7õyF)Ä·Ã ×&B½<EFBFBD>/KóaºNÿ4+3²
^íˆvqpt¨Í  Žˆ Í°£ž×¹¥à¾÷Îi\vÔàâ<EFBFBD>Ì<EFBFBD>+²6QŽâBÓøÙ04l½Ê-fLÚ¡T7*IjÆÂï767¹{^QLKÌ(Øà/¡éÄÛçÙÜõqºj~ŠàÄ<EFBFBD> }ºB®Ûu,0®¨¤è¤ãpÆÀË«ÓŒñžå¾øjUPu?ä4!f#x0S¹M1~~úsqƒ»o<EFBFBD>³ErIr<EFBFBD>Àœˆã·î[0É&Åœ¦¡Jh+@Ñà%È©)ÐñŒÌN9÷D-é?ô¯jªðD4Ê|!ž^ Ò«#r¥Ý5x<R<EFBFBD>HQ \^n»`žxz¢«Ù4Ì3ÿÀv<EFBFBD>³þéöºà¨?;µLÄÄáíÊWèYü«óU©^槧j=»i?¦å¯ç#Ðï&¬ _b>`ÄßIhy© 9Zhó<EFBFBD>î<ž¨ô\{Š'‰”× Î×gAý;<iTöê6b>¼Ý­ïíôJôiòزüïÄÜÀ®)iœ8Aj3<6A><fQ€€ó<E282AC>]OTèZ”®l~¦<19>ÐŽi³»œW»é¨½º*0<>À¶DaÜuZ<75>Š…ˆ·c<C2B7>gçªiðó÷7³ú’üj<>hRz¡^Bûˆ¦†pãÊú@ÓFÌhþþ™Øîm|ùr)Ñ0i{½oƒŒÓùU³âoد¶/9Ó·¿µËѾ<C391>ÆRBüøEuâ힆Š-ìÑ©±ð•4$Ž±ÙC-_-`<É ÓÐGô
W¬Ó;bdi'­¤¬·á|'õ<EFBFBD>ºÒ£eýèªËp×UºzÃDE­Pîú;àü¯#{W nú6<EFBFBD>T<EFBFBD>í¤Ÿ «Â%¼<EFBFBD>CÛZÑÔ±êWà£~e .ÞÔ· G P<EFBFBD> t¢ÃéÝب9¦µø*X"¾dLêXEs)×C5-^ª.ÇãüQ¬H…ÑIâ>lÇc¸ìdýo‡â~¯û>Š„ÿ®~0ŃQ@mÉ™=àžl;‰5ä\ë¨-hJsµ@8 žnE{àPhÆÜ©¢q‰ç"Jp~Z¡M$7±T@ñÔ:Z[QÆçM4ÐX×É<EFBFBD>ÌoÏʾ¯<EFBFBD>]Œ<EFBFBD>NÔß¾Ôh©œ[t9Tê²1#ÑÐ^Uµ<EFBFBD>¤Ö¿ò5ãå¯í ö=jt1uPa"7YÒ¯:˸Ûµÿi_\±TVìŽ;<3B>†h®Ç…Ô§ÿâì"¬·7B ð¼ä.rE2;þû
ûÐ<àÇœ·æZ1Ü¢E{M=_tÉ®»H)É×ÇNXhÎyÉbCDª\]±<EFBFBD>˜­ð¤š8Ó4ÕŒ¾¹øûÈÄbýÞù&*$óÇïÄúÆ2OBÛY`ö~ Êý§ñÁRQ)ÑÛõ9Ã?Á4$GLÅÏFU«Aö\¢bÇs->wWv´o.Âܤ¦Ür!¥²¾RNÉpþ ÅL<EFBFBD>{KT¢o½t3 Ubiž;Á%%N<EFBFBD>SÆf5Ë;CiŽÐ+ãq%cŽ'3 ·ò9“#`Çn$|—¸ý&:¡%níˆâñºB¤I«Ípw]õV<C3B5> e—g †ªMd.&bU0é<30>u€øé6BB
LÎÙó'nì Äw<C384>¤7éÏÌö±¤"ä<>aðk)ƒ¾-ELÃ">ÄÊç}äcê
üÒÞžÞ3<EFBFBD>=ÑŸ<EFBFBD>ç<EFBFBD> A¯Ã*®uùDýoyt½PµVyX~|m·óJ°ú«¥
6±¥4&Ì%÷˜o¦½^ÖÃQŒ`!Š~e¬¸û6ïM-þ
ƒ'*&%KJ/ Ý´ ±øÃëp€ÓðÑä<C391>Š[_!,a1'®Ù]G<EFBFBD>ÏcÃCÚ=Ì÷«ª³pÙðÛãqÅÌ|àåÑõ²ðF²[ì œòJiKÜÙO*Q%>È%/Ñ»ÆÎQ¥÷dØY;
óà<EFBFBD>±b˜rÅ<Ó6íÕ/UÉÄðÚ¡B7]½2Ý@òßœ2°¤#Óòç»m!$.Œ&E¤7J§¯q9ÎZÎvS?bZÎ29ðæòwg<EFBFBD>Ê:´æ>Ù<EFBFBD>Ú¦âb¸ $Š6ñ¦=<ÕyõäÙ·îÚsÀöbþf ú°ù]!5ï(÷ÑÄ7S¨åæveði¹"8¸Ç¾NJR£‰àueåìyÓ ~g©·ìðž<>#§É£qÍ<04>Ó¹ÐWÜÔ¡ÊAß„Ölá*hcóï÷»(6˜ß
ע߽>2Ò*ÚÕ;«?åGÊóÏ,ÛØ£wnaX&ÿ7N aU¸òðÊ6L+ÃøŸ,`y ÍO·ðázVfæ(ôŽî <EFBFBD>ü1B!ñÚ!a^uïSU þ`ûÈ @æ[ «$TPq 4Š<EFBFBD>®1öÏ\(þV#®Ð*á<EFBFBD>Ð÷Ñ!Añ5÷V[7jßhèÄúëCu Æ)ƒ¹="ÑÓÙÐz—ö2"sC zè?é>¦V:d¡Ì¿¸/Æw|ÝtT©·ü6i¹ô´WgÏR°mr(ÑÑô¥àøé.3¬@P¸ú{Å»U=5ak'1¸¤@9¾SF…{[!}÷ãfÃc¨ÿÙx£x§éìÆ+.¥Zó!"%©¼¸¸¨<C2B8>æ,ÔÔ÷'ßB ŽEþÉ'<EFBFBD>ç·1vÀ´« )cè;”—ùŒ{<53>«tYrF^r=D¹n•ƒá†‰~Ä°cº,‡›œ^ižndëŠk]£>œ­ÈUÖgò±äš}8²Â(ñþ0ÿαðKZé[h—Z³£Ç6;µ1xSüè(TÍùØŒÂ]‡ø9;6oA<E280A0>hÂðR²”¨!<21>ÆüFÒ/iÚÏÁ÷çvºÓ'Ü#ï¡r ?ô¿Ïý2Ϩg\ßA(ØY5(z.HCƒŽCÇ\Ejµ.ž2>㣠èÏHvÊ'à“Ô
\n"ž™¼ÂjRÄÁÐPÍ©|Õ¾QÌgŸ°_FåñïL÷LéÃòX°¤=:Ý|*OéÅ00Õü¤<C3BC>°„PÆP<E28098>v€å©(¸ûŽœ—ƒ‹æ™6®| “ŽÞñÌí̶è7¶¥„AºMȃV4õÿîëÔ’!z ¢JZf:ŸcârµHW°£=_±ž»ÈZ)O±§aüñyt­®òwÏð ÓÅ©ã]Á#”¶äee¹À¯ÒÅWæÂqÅ×ìŸä­)ôQZ'v
 >ñ=~© Ub´³¯÷·%_lÃtxpzdÒMô³_o/&Ÿ¡ù­(|îVm#õIgýÕŒf»y#åA¦þíK*CmªaçN7æß{ #®œÿû-µáDÅz=Ü^ÈÙ§¢Za©Ë`&<EFBFBD>²tüÆ°Ó˜?r(¸ÓéÕ°÷r)ç7\Ú@Õ êSùß¡ßûºsy<EFBFBD>®^<EFBFBD>&Z:XL ÛÏ<q¹´t¤`X<EFBFBD>бè}Ó:`¦e6ßæ±pz\¦ãuŠ_éÅEÜ3 Æ¦³'Õ¤Ë<EFBFBD>ëÂ8ýø>ñ|oÛSð71ù†ˆAZ"m OsÆr²2G6[š÷W<C3B7>å¢ÛÀëX¢uÉ"vƒ¸‡Ût—̯šÏxŠ¦;’½ãÑQÃRä%c¦ÙxlÍÐçn<O´¡<10>¾ÊC{gdaÆ÷“.Wñ2>‡]×
?:ÉïZ4߃Y˜ôŸó=ž
íZ«Ø6ÞŸ)J!2¾<EFBFBD>$ÖòêíAæÄÃðî|¦8È´Îtré¤aÿuæÍöy<EFBFBD>d#®uˆ ?T{ïNPÛ0YT<EFBFBD>9 D¼YN×hg1¨\çÅM=7@å} ¤ÝýÃä¢s.&Âj¾½bèd$¡ÆìÝFÝÔŸ<EFBFBD>@%¤5¦@­p+³"‡ç¦²öEDÌ­VäB_®,UœphÚ‡ ú4ü‰<C3BC>§$èdâ¡öÛh®Š»X—>çÀ<C3A7>Å/ÈÍ# ÷àcšò$ûñ4<C3B1>@Ir e¦Aø±>ý03Ú<33>\ÑÞÿ>cÀ<>K¬ãel¢uïËws¨ˆ><3E><>w”Ë™=ßýæÀ÷2÷§¹%¯E ’ïñ<dêð3Ä÷æìJ'K3[Îr d0<qKUà(nªoŒ¡^89SÒ<53>A S˜áçÛ ¥õÅl0¾;v°3A<33>ˆ®X;Sœg‡±ù¹Ä#Ã,ÁFÿÎà+^vRèwé)¶(… i†SI$/Ѷžc~\D#ªˆSd?ýb)t¿<74>
¢àØÑp3ó²`¥(Ü-⽺ÝÃR²¸ÅÒaÑÇÔápÕÖoU]5¨õÄï*ì°>»öœ¸JöJ<ž<EFBFBD>£ÖµFÞË3<EFBFBD>L[qSqˆ_{N<EFBFBD>ËÂ<ŠN#bä£x4°¥f·´µ7ùÜÏÊÉÊLñäßÞßa#ùôó»¢A$ÆÌZÈ3 Š_PØç&¿þJ ØwÄÈm¥h³ÊlklîÏÄܱƒÈ«ž˜[
diMòªšÅkÌç[RÞÎÚÿù¡Ñ]k«Å´ãþK#ï8©ÐSk³v?:hƒwªRÂq]n<EFBFBD>éÛy^YXYÛ<EFBFBD>s ó¦˜eÄóÖçàRŒ<EFBFBD>âÓ˜[jÎŒ·X4ÇžæÔÜSúöïîo5\ŠÌT ƒE(˜Z<EFBFBD>;ÔWDÕpQz»ÁìHv5h{aÜ#â<EFBFBD>ãæ+ßWŠÜ¥Kèš  õ©«6è7윹OÑ+¨ý ìYRwíûÃ!¤lQAÿu%L£HuWP+',j9;Òq¨aä†Í“¹diâ}~©KÃùG²õ¸J}Âw¿b—Õ¬:£c í„:ÄR»A¦£ó˜tíÖôžï uSñ‡=,ú3ç266õ1gÈ"l'ïMXPÔ3ŒŸ^K³YB3¼ùôÀ)$s=Ó»×Â<EFBFBD>4¾éŠ±¬-Ó00Þä@ÍÄÑݦ¨á
tNçVõ3f³ÄO7»<EFBFBD>×&ã¬{Ü6è賩R,³:«Þ×ürÈop2>jÉ šlN䥹!q¬ÈCµÑ@Ô¥Ý×µVn฾iTúùú<·<EFBFBD>ÊqV9,'³»
|ÄŸ~´CQ[|~ÉëÉbp²x¹'O„ÊÈÈÛUeËBà̺©3“ÕÑÎÕË ça"ŧóé+Em‡f·`–œ¡-0/ˆŸ.ÕØ3fGÿbŠ”˜\••vÒc¦Q“;G,7[2²ã·ä<ãf!¹4³«ûÞ%àJK¼Ÿ áócf¹ò@*û¾õw¬#úÁ¤E:*bƒ><3E>c™†ûlص°¯Ž9W chªŽ¨õ°¸a´(j<>ùVîû«9ªSp¡íâRa
?JÑÕñ ë ðNS# ¢[Ö»šZ²[äS[s«C=º!R[±%tv»Æþ`¯f{v+2Ï Ç?/<EFBFBD>nUõNd¨ž¨õ>w³.,ò~B ˆßí ü(;¼Åý?4<EFBFBD>ï¡ÛÒ5%d;UUÇ6=C ng}0΢Þ}à<EFBFBD>H @ÿ¦/}<EFBFBD>@$·ÑhÍrå)Ã}¢$ejžÀèÔňÀ÷&Ö`Ù2YêñèwŽî¾SÿEŠ_ŸÁØ3ÎüT7^qg¬AäirdÜÙÅ{1ê^{žšÍ¦Ÿ¼+M«¹¡¢g¯¡RµÑÖÌÿbøÙÞáwõÌÍÜ5àz('ìÚû ÿ䞎íï`:LA†Cœxµ§û¯„\ÃZïÄ)`h£¾Y§Óãr¦Î =íé¤H·‰;ØŠ$öæc¿´<78>ø,%ã OÃå\IîÜfÈ g/·)û>Ji˜<69>AG8ƒDštÓ¹ù#LÙqJ`¥@dtæ{yc¤iÖ ¥tÏ{F¢ÈäãÁ­¤Y
xìÆÝp/´©ÀÃ=Ôé |ìàæVöòíj&ô§³ö°yß5!Û,HE-U¾S^r[fZ¢E2<EFBFBD>rSçWjˆd«91Öx÷$šÎœO[žX!«ºÝɃÔR<EFBFBD>Âè[¾p.×
òò°lIBOÿ]«3¤Ù05*^Â-b±PåÒÁx spW3ÙMkøáø«£³ôóžˆÍymÙ>^¢«ZêQã©AÞ1´=#(ì¡ëÐï<EFBFBD> ¥ Æ1ùz»~1ó +¸¡¸+ô¢¦è
íH½¬<M˜·ÏÓ7Õ"<EFBFBD>¶™„Šäƒ°¤´È^ó'€Ã⯻[éíÎÅ» Bc`ÌpÿOiÝ?â%ñ=°Ø4ÏIˆVŒ”³qDO 0 Lr/@-:uR5ïRÅQ¹öZ<C3B6>y8^Tp¶4f_èýƒ<´¨úb´RmoÍ¿otæÑØ×ñ,"Ú=?(ä÷îäÉ
Ûc<µ²9þÁ?[w>kN*«ì¯"¸gØíR5'b$5Á=`{ %¶—‚‰ˆÂ~œ†j¦¥žìIHgªF&y4r5ˆCÌÂÌb÷HhùåÁü1þŠAtÎhT8Â!cGa.0?Ù§e¹ÀXqäÓ7v¦×ÛÊ P¥¦ƒˆÇ˜ýãGÒ¸ !Æ­(®ËX2 |{]#£>rT7ù¸<08>Ÿàd÷Ø6âÚ§u·Ë(a}.r »(ÖñÓ®’;bÇPdQ‰èåœ ñSP÷Ýýe2[¶¯N“Ô¿æ*^6A(RCXÜ¡#ÈÖ®#|‰~Æô<>*ì›°ª¢ƒó²SšTŠ<54>LV¶@÷‹öŠ^Yö=Íeî1_kã…qw¸ò2ßë ;)ª;¬S·é¾ùâ}À—êù‡´êµôBæãÈÇ"",@ª´ü
/µ&1ËKœg !­1áºgOnAyPÝ.íœ^6­ç·|ô·óµQÓ ÍiØ <½Äâ7¤½ÚŒºá'ºËAøVŠè·>ð€ó‰Ê"f9#¸Z´û¨9oÐ*t¯û=aˆÁÊšåTŒ<>ÀRSÛ'¯qvyл<EFBFBD>f"ï§Ù¼}æ×µõì_üõÊE”sÏêÝ>æ{ÿ•ë4+&qÓ@ÿ ¹)~;Z%¾Qü×­¶e?Zº}<17>ÛX<C39B>T ¼…ýA¤¯qÉ#š|~©ÓÅÒ¾³]×"«w46 ¿ÄêtNó¸ð@ãòí<EFBFBD>Å? 㘊ÊÇÞq*µ»!0K9"Ę:§S\f]"<EFBFBD>ÖñffÇpxwÿ~ S/ôeœáÊ5ܲ*á
!Â?ä0÷S(4Nz(u.RÖ ¬Ò¸18<0p#zT ÄH<EFBFBD>Á\ïOt\¼ëY"¨¡í¨ôÐ:A¹œk<12Ë #“­Ê|ª…<C2AA>« ñv}.7†3¾ ã×4mÿ*<2A>GÑUæ*k8Zæl¨$çc­LQ[©MJ/.‰‰“ÌA<C38C>_±Êx«ƒœä*z-<2D> 6øa°gÇ/î &ý½€µómV|bÛÄâæ¬Úݯ ¼Cä<¾÷<C2BE>¸;yÃ[«Tÿý“… &¶Kg>ÑŠ_¢¾€<C2BE>»lŽñ[£íd¬ömµ_» Çñˆü=¥Áè1ØY½…èûÞ]„7<*@“FK¹O¢EÝo)mÚj<C39A>PK—úÚ¦ ñ?¸oÀ%| .yóÛ;Œ^è•\qŸ*&¾+¦e´ðóé/™ <20>¿{
Ld>>V®<EFBFBD>ßµV¿dJâ6\M_ ë<EFBFBD>jA¬æþ$öV)ª¢G$²B,S(颵µzLVA¸;þvcÁ¿ƒVh]!ñeu}¨À°®{1A÷Ä<EFBFBD>èJžÜž.â¾ÃšÃôãÌe®ÏU<"É¥Àñvû46 .r¢2Je~,_·5\«MW¥IF+*…auul\µ`ú¨þ}pb<70>Š=•øƒº-% $£¿†®‰ÏÚÂ¥íRȇ JñûƒIêð…_îû WÇq•˜&¦;A¡¡•BNmç3¾ÛOr‡vÜZgXœ©ú¨ˆSì2®‰z—¥<E28094>ûs<C3BB>Ûnž'¬«Èäè5a¸P MÁÂR E«o±¿Wºy(êÂ×­Û  l-
c¸7ccÉç;0ôD=ðdÐnòŒì·ÙKˆÃ{ݵ?¦šŸ<EFBFBD>B®ê8^º$ðØÉ·9O=]ÏͲÑëügŸÕæWõ®Ó+E)¥(W(7Vˆ»êC¦SM=¼8ûÇÂêõùïÕf×)"8”qŠÍ‡ÏÁ<C381> E¼ÇÌÏëê°3l®ìÀã Ô𿉉kÑlí¬ßqèÿû3%æ²:h&°?¿T:½(]/SÊJ¼4|P:\~ðYÚFeCš@†_î·HûÃ<C3BB>'¶þÔuÁç²Yk4V1<ƒ9ÍbúYk6 ñ[X¼«0¡ulÚ}‡û'M@Ae®Wýo¢B D¸ 4`†[܆ƒ¿_ÈyÄê†kŠ<6B>õ< zº2ÆŠ ^£4±Ê®¤®ûDëË•úÉiÊÓjˆäíøNï@è<>ñÎwúhëÄ 6‰ð*ã»t¡åX54‡9? `^ˆ%wA×ßcŽJp-ó÷˜œ¸á»¯<10>+qE<71>˱•ªåºytÒÈ<C392>ÆD, XÕp\éYñÙ¤<>$0ÐRGÎä©46"£Ÿ)¹úß xmêUö×TYÄ)i¨œöy
òý(:{,H+âŸo°à1¿ëèA/åZàýŠd^ÿ-?¬þIÇÔO4"+0«J<C2AB>*u>í¶;?JÚÆ«uÒTõÜãbVÝ$cÌCó¦Á̯$õ³Ï<C2B3>ž©®©Ëÿ¨3qKºê¿R(VP®ã\Ú¤éjéíÆî0;¥®/™Î¸Va#C×l<°HEù4ÀßN.*ž”t‰¥mTc¾ZlûÅõ»ÛÙ Â:<3A>×´hŒ2²íª3vŸšgÇ?‡øð/-”¼C`§é¸G#CÑÄÚ"49!iÖA Íp°²þ^Ÿ@ˤ2\Ð!î䨡} Š2²C@JG#y¤úß7ÙâLEHE0ÕNËÂb\î6b­à<EFBFBD>$dFð5° øJ<EFBFBD>>=Gî8´ˆòaÒa£Í¬\É'ºU¸cíaê-™È‡g™PXûtŸÊœWˆY™'
2ë*â<EFBFBD>L6»±ËÁ`ÞƵa"lÚü'ùz@.O2<4F>p~>]‰F#niº(JuG<02>†XN@h“eTý¦ V«'<1A><>ºŒÏ$~!|t…$†,Òþ˜ûIßÑ3ÎýPñt˜Êš‰9ü$wB£ ‡B…Ø=¤„¶v¦Aýk/Àìûe™ßÓQO¥ÙÄSù[_ßßoÕ¾—ø ŠàšüÊ‹—âæ™{Þ7¸3™Q¾¬Ö<C2AC>ÅÇÏ9_Øòzkfíàš!Fg"·. ßá[´ë[Ryå3Iâq¦¹ˆBkç[svduÎv÷< Œœ
·®žE(L·Þ¯Â9L½C
&˜®
çS5fÌ&wý6ð
&ÐpsýŒ¡fK8øッCšÝLŸdñ½bzˆå¤žƒüJCYµ«î"#¨ðcæ¥Øôö:ŸßrÕû)çíÍu©ÆÅàŽál”ÿ.ö‘÷Jl"ïû9­íf#oQkV¬YNã¥JVáZãqèœ<¬µî÷ïcJ´òn]µÜòc5<EFBFBD>ûC KBÊüß&/<EFBFBD>*ºWûÜâ$C¾ãóòöy+G|ÚÉ<EFBFBD>áÙJ-õ#,´e8é:¢eœžÍTa]) _g®VÙmÑæ%MŠXyÄ7Õ½(,ŠþïsÑJB<EFBFBD>Ó-~P¬ ?ðßc5jË©eƒ=9<EFBFBD>9?ÙYlÓÅ4ï,ïDê|ŒI¸µVsý:{6ü¦Ô=ÄÇ-±ÔDºªyà˼·²à7^ÊÒG ìßÚÙÚ\ôïîïq3 Uì!7ò >¦V´±ò:J lÅT*â<EFBFBD>\Ò:»RW²+És§æ±#¢Û¾±¬«¬.ð#ÑjíÚkvôSÜ^Š8=_¬l7ÿÅú<EFBFBD>o¡8<EFBFBD>Û<EFBFBD>2Á¡AR$ô¯B }©ª0<EFBFBD>eÒÛþd9ŒPÔG{3Ð[ÔQ½/DþÅ­./[æ<EFBFBD>Æ!6çÍ9Ô:=p¬µ»íw+:¬ KN·²=¡ú?CÈ2Òx`ad?a¨C¤z^$fõΘ <EFBFBD>TÜ©ýÔS9鱪"gà].<2E>Ã¥oÅaUÖÚpÕéØýlbÅé“bû¡(ÅÊÁâ‘£.cb'Ž?,VqÂÿCX R%x 'Yr‡¯ä'v¡†™º0€­jÝ7ÄÃŽ(¦Ù˯*ÐYáÀÌÇÞ3ØéSGá{ö#½ôyÏv”!c@ ÑV€E*#¢ñs/Ýëi`NûB"Osö½ë/~ï³à·öþS±ÛŽöôÈ»¥À,M®åøœûÆ%ÇßY¸
ªØ¿;תÈÉnÒk`<EFBFBD>X­*z2G7š{Ÿšgy´X¯T¬¢{èÔð»2dY¬¯iª.bÆõlÍÑf×ùé îà×Þ¤1÷ ÿ<+ÁõÿõúloÐp>y-úLòF.µQ^ŽtËh9><EFBFBD>ÿòÄóÒäØØ %©!ĹᠨØ4´«p_ÆéêpiOùÙí!h2ÛF_vvz>ÝmN×Û½è a%a$[á5UpÈýÆ*cLÒèÚo ù{<EFBFBD>³çº©÷7Íï§eF(3ÅÓHxÕbâÀçTY'¨ KJ¿NOþÖº—…W2Â¥gž2{5\ßF<EFBFBD>-ý”gfh+Òq¯“þy+°8㜥Ta§#ÎÈÓ¿…¹.ܸ&¤O™ýsÛAÈt
÷ëV?®ÝäûÎðÜW¹/Pbk[(CJÊ­¨â«|å§pT°õ4ÄÆâ¹Ã˜g8ö ¨ÎK´¸[¡<ÏÅ!ôîÆ>ê¦Ðšò$:ò-+±-<EFBFBD>ƒ^·2¬.p+[š>?HO <EFBFBD>ä;n'±•e!¦®/vë4œ¥Ùª̼Kœ<4B>'®žíf¼V@ùJ3 ¨cßœ)ÉþƨöEìDnݱÀG&¢)·omVáñ?ÕfUÌziÔ ük£R¥<EFBFBD>ûÒ»ŽõÇ´¯åÙ´#ç°ì+A̵]»¥ÀæZi@¥~òä|¸;#Ôˆ¨$²«ø1]@âi<EFBFBD>ÊMŠ=YÐœtuãéKÙóv¸òåž
µW]¸×Ò°òéô2PSêûÇ[ )ý¯¸êöDéÀsÍâR*¤pm?ßJ4¢S+!:WKÇ EÒœP¡&Z4<EFBFBD>ìeJ½SØÈ[à 3©Ç!Ÿ«ß Ƹ·ž˜@Ô¬ËXnã \\Ìùj h*y þ}Ì@5Î@Ò5p+/)¹íDjñð°ÌjUP] ܸÓüŒÄuäýš·Õ¤O#}ÓÇ÷vsºÜܾ쳫© :q{v± ±²±Xµ°z¤.h7R~ƒ
Ç[j¦wei`3Ñ\T½<EFBFBD>ÐV/¹~¨¨ŸrÕDµÍ£Ë÷ Äú{=eèâßJý<EFBFBD>ñk<EFBFBD>˜)®3 ¯sMã)VõÈ°Ž«¬Þ}öèWMÄÅfjÏ!úpa`'w)ò/o«<C2AB><E28093>ÀÄùÈÝÞµ—À½¡åÁVïëÄÅœÒeËfáÔo9}sf»Güt&.`÷^Ù=OÄå C!|~л´ÈqhX¢x!„r »¯&é£Çq}ßx
±àDÜ-ÝÑÖÔ"­4ÎÙ†½û¶ÌŽÊUÍ0=ÿÛ &âKŽ-P—ù:íÞ.ˆ•G5¤úQ23qZ,l¢5ÜC®äì<C3A4>Ú%Q˜Žr¿Ï~¢Øk#òdó½‡<C2BD>Ÿ}z™cùJ¥²«¯Ø±T7kÅÓÅ4îß]Uw:6mQÃMÂE³Ž•Ð<E280A2>§»o_(Ù1yÄJ[Eˆâ¤Žl,ÚDÕ'Çëüý©¶ðxåÿh v…É<02>5&'*Œ ݦÞÄ?
·¿cïãð·Ð+2:\§dfØ; {_,á÷ÅG&!½¢8¦Õwx)qØˬáÏvß¼ŒŠv¿¢aûcûâ<EFBFBD>¡»"ž° ¥)ýÆÕÿ涿ÊIÛ<49>Îوؚw-€èÌ8ó1]7dGÆ #áÆ6Úy4\<øŠA3£²ÔÖ‡4mjD)qrÑ'Œ<>>Ž>Õ©HÛ=Ñ©SŒ1Aw¾—@ï%Iw×0œþ©#…ñ›Ìy‰¿/ˆÙAr„>KèŒJ|=Ó¾¦«N/T'pÕøäߎ<C39F>Íy„S¶9Bà„¡JþŲì8´¡‰®Ôï¾I" *ßÉÞ³òCî¨ö ºzX? 8KGAî©D¥0>ƒ$¬~ÈfL<EFBFBD>10`âKw¸ŸC2x}yŸžgÝkRú/£";ËãTãÐ55ÕÈ*,¯!Ö ÀtŸ)°I _c=$<24>“ÖåùF)³¿iiz0µ˜ª`>¤P-¬j>saåõ¿ßç sn~Èb®¦JŸÙRº]<4D>Ùêö9òÖÿÜÀd¥Ò3“gü®ì<èÖóPö¤#þ<>Í¡&%<25>½•¢Ü W.-‡  fä`}Žf~P ôG3<47>ûc<C3BB>—jƒ@xÒ<78>Bàã —!“ç×­L!§Éر´æÛDû¡«Ì挫úVìÀXl*«×:k)Ö±p€Ö#î@FnÒçŽ"*ƒu<EFBFBD> DQX¤fDx§²ñÆŸ§ç<EFBFBD>¹œùŽ)BIjÖVùGØ8ÓÎa×ýÖù_<EFBFBD>êøjt!Ã6ª1-Žp@=ä£)*G;¥@&ÞÜ $<EFBFBD>ššuø²åt<EFBFBD>¿i¨Ô'¥@Ö{©7‡±Ò<18>I¼DB}Ëð©Ù ëægM|Z4) ”_Œ ùTœÆ&"Ážëé]74 ½¾ÈH¤¥*œv™YÊóäטû¹’[F=X˜t¼^›¹Ù¼b²@,¾;—™AðþzŸéžê
Ê!§k ³È=Œ×äª!ó±ß"½6ü6X IŠµ •_5]Á}¦ÑÕ/õ<¹È
Eîª"ÚÄk!o¥¬¨I°KÊûWÅ<57>àÞæ`VØbö
̽>K\âÅ°Þ!_ò9ni& {5èJÔÀGvsÕÖ¢î-w2¨ñkººo}£Öò°ùœI¼ô×H}05Ç>ÂÓÎÍPN× üµåhkÝsø #2¡d_«îº$ý& Œþ;ƒÝlˆ¬3¢@¸©<EFBFBD>Ãس7¯ÜŠÞ´øÉOZ³ãI ÔbºÕKÛlFú&Žås61}àçÀèŒÏõäÀ°. ZX0w$À¥ºF˜ôö©UW¯&TWÚ<EFBFBD>` 0³<EFBFBD>:냼Ž¸-[¥Ÿ¥sÇš%æöÒá0¿RïWì\Ž"£sìú8¦:û5  î!='-0'ì6<?6;ßâe§…ýU<C3BD>Ù(œ‘*qâsÁƒ6AÛ³â<l©(Ò0 wjü¿^PX¼€¿ûÒÜ¡jAW?êƒò<C692>l<EFBFBD>zC
cj.5­'˜{ «oe14™˜<77>…lÂN2£<32>¨7|ƒu°Ç„€$™2Ë32íã$óh*­ ÃðÃÙº•À3oû~ë°â—ó$}¢Mé QôÍ;A<>\1wjÝîqæ(ÑŠ7ð¢xÑ([_ð¼sÙÓ<C399>‰Ös~Ow­œ &… Q<EFBFBD>»ÁÉ¿$•@õm°¿¸ƒßA±G‡k£|ÍFùîùnÎ##>ÿ•€hõ­ Ñx4¼ ô[%ßV;ɶnn‰Ê„z}¸Ù€) Ÿ~ò6N¤ƒ¬€MÓ#¤÷- ¨)ÙÎíT}+ÄœEÝøyÏ&¶*"+!ÅîdòËŠlDUIãÕ'»àc/.´O@Ib$ Šá\ônwœ$£'/ÈãW=à„ˆn—ž¦v×µÄañÙ¨²¸<A=A±GúÛ,ßjýañ½Å{ÐQѼ<>r,ï˜ù:~y…Ar©ÇãnOÂM3Kqÿ\ÿ´Çÿ4Ÿ#Þ+Äß*<2A>w{¡†ŠPš •™wŸC†Ç éÑ@ºÑÀå çT—¯e"ßI>î~&ò)¾ú„?eðò9°z&1cæ7l3ì5PŠf½KTU5ÒDÚr n|»<>¹îíŒÉm69”—»ºã¨KÊ­ÞåW^%Ü -S«'£2jW-2˜5.<EFBFBD>z5ûÙ @<EFBFBD>Ê¿ê¹$7žXT[½´ˆL[ƒ||Èmni÷;[TúϪoIVÆ_P%È©i9ÜØZ[ÂÄòØzn9°g+oXÞïOûR÷Ë«Ó é¢»8!RYóbò£!`VIk
ƒ(aÆp`ÓbÀfÕoáµ¼.nâÓWŸw½î¦{Áþ~ÂëÚœœÖpLho/ç, K.ù çe`ìíA<EFBFBD>Š± ;¦|aˆ«¡Ý˃Ï^Í]<EFBFBD>µpFî Ý»îÄC¯!6P³·`e$B<õ³õŠ·ë)ËzÈWþ Û-[<EFBFBD>-Q UÂØ!*K-ž3Y®ö?f8µT­ sî<EFBFBD>4w&6ÓÎáhÈäF.ÚúϯÎìœ"Ü䣻2ó9ÀI‰¯gË„YËñ÷:Ù…+:¹<>ó8 2ÒÖõoOª²³Ž+zqU³8jÉ`aÅó3O—“Õå´rÀ¯ÆS²{ ; Ð<ÓÊ<C393>HÛ+zï÷Ó*د;ÒkÂáçW¦9Ú<39> ]àÄàËŽµ wSõÜ&”Œ7™L:UÁ]ß&1SХѻoau5êysiZ š·x½uÄfQº¯ªµ‘¦8nçïå‰Z]øÄÆû Ÿõا[iZÊa+^/¤A8ÿqá(%4QØBŒg(7ëŸB€çˆ<E2809A>Ê7~Òyœ=h¦´Ôn0“¸<E2809C>«"šÁA<EFBFBD>à.^ù¯í6]µÇZ;öÆøxÝO0×n"üG4ø¹õg'b±ßØýà¡Œ™@bMcPÕ ƒ¦P\~V`\#pŽK ñ°¨><>¦ ö—DüÅ*ÔÁbû®XÉ©Êxw?)´CõfÝõx™<43>´ÔŸüŠ¢ ºØ6´³[Ø”¶¬^gIÈ|á½r%<25>s—VœŸl¯pÏgµ2îÏHÄ §0@¡Œ¢¬hN®Ÿ¶t™Ó;+…ÃKý™çèÂß@'ÇtYÒx PôEêWÓ7x½cë´ßWA]B FÕsƒÚ¿8yj~îÐSÌ9½£ž ÃîÔ´÷"Ø£·®íÁÐsQêWêebV<EFBFBD>ÝyåÐFtµ¸
kæ4°º_¹|UX2ÅlzËD ½¿êf"†5<EFBFBD> œº|ŽßÃÄŸ¦”ÃË¢¥!I¬ÙÙ»ºç¸&)AcƒqvåØa 6é<36><02>4µÿÙÛÁˆåaÍ/,3!PX©«ê<C2AB>KÍ¿µwÍP¦U˜aA~ŠësŒÖæ…ž/Š œYÑå¤r®Ã°5-ÆÌRFÅg<C385>¢ý`ŒÏ´ÝäækcüŽ¿²Õ0“¿Çž–/5Ì,2?@G5dl—u]tsZóv•à3n´ q9‰“臠°¿f¡Ð¯ðY¹¿Ì!ÀÙ*ÈHÍP%Øpñ”¤â%íË3CtÐœnT%?û*x|5á(È®îEI‰@ ~­Ð<C2AD>Þ~Öt«`â|®,ÿ<> (º‡„êt ŸÂ,8å³(0óæÝãëtsê,![”
zþc};ÊèY-ŸÃÚþCVeøø)3
¨Ë}±Êø×PÔžú~lše³B¥³«wóËÇÚÓ_pc5N¢; 鱺ÉŤ<(ú`ñg²ô·¡{ jóîr$0Å7l þ³3ml?­Õ<EFBFBD>À=!×¼?G+[œi2j2œc. ÑÚn-~ȱ_w>ɽwWüRË3¿¤ª¼ž¼>@@ͭ亼pQÂyðz³zÀ¢ÌFXbòª*<EFBFBD><EFBFBD>¸y«J#<EFBFBD>_¨DgI0pì^ZcrÕâi¸ ÐŒ5Áa i<EFBFBD>ä9Ï ˆÊo^ä+B8ðó:Q÷ì´ß· nzp&øÇB<EFBFBD>ÐD¢˜¸(0Lñõ\l¢ZËDPÍhb$e<EFBFBD>¥r¤¬óÞàkìïz¥/ ¢§¼ñ$ž<EFBFBD>îåóáâa«â ·<EFBFBD>ÈFZéGó¡¥XIÌ}a>kÜè_RFd¯tU`0af êllçíþçZ£Ê7\òv-m½±ÉïW7ÜÁ|"ípªMá‚å楩³)üf !AÄ®…<C2AE>ÿ©T'u_ÏÊ°ñ¸´F¦?T\Yx9e?«`ÀLëüÄy•ÏΡ.¤Þ]µ>Œ¥ÒÏÈÇËT½šÅçß¹oTÛfdã"jôW¨õå%«}K§4tšþ¢-Ô)=\$µAMiRNti¿ f´uâñú<EFBFBD>h}<EFBFBD>×ñÛä÷|˜óöu$ÿ._@
Öì¿ßåŠK6û×Y«ó<EFBFBD>éÓ˜ô~@9~' »©A7Yó£ªÎ nÊW—½!Ådu©go†edïÜtH<01>sLÑèíΡ"rû-@]ËÀÅ·ƒÍ 7Œî ÷XEí#ê=FWoó8'O?35$Ÿf˜FNz4H:ƲóÄ rêÕ­z ÓrþØ Öûûû%ñ!ò®<EFBFBD>W<øʽ}Yf8½S>VÕôÏÞã 9öeJÚ8ù<EFBFBD>z²T4Œ¡ÍËá(ç@u<ETPÑU/dz 5D³gàÑq6§<õǨž ŽF«šz±Ð;:î±»ÛÂ<EFBFBD>ÑÇÄKñkjÞÆ05n+-5nqIÅã7`¹x<EFBFBD>1\ö78<EFBFBD>ŸÎ3U¼´è« ˆT ¹vÝj¹É1-;?È<EFBFBD>˜§Ñ¦£ž '™ÔAöEå=ÆÂ<C386>RÅñ° Æ*ÆQßüÉtbC‰´ õã/îÕDp<y´<7š\íâO||¾UMGisóz…Ðø“z"¼D&ÉaŸ¬Và°kLY>{>ÈdÄÊײÅ|Ìç=:»æ_šAåÓ`zù<âJZç³…k<VA<>“Lø?)‰§°KÒÀeþuöm¬Â|Ôy†¯šÉÉ<ð­ÆþMZªœÐše@é`ãí§<> ;†w‰¾^‚¦Ó´[px„X»:áɉ5<E280B0>Prû¤ö‰<C3B6>E(
ªšn0IixqüšúŠæíè>Ñåæz÷s¬¬<EFBFBD>èÏ6¨óè*)`TVº` ñðw(7O¬} Z#?|yX,;á y6ÛÌ ò¹"Ef±V2—GU€°šã q<>¡<EFBFBD><C2A1>#/ê<02>ĦŒjÒ⤡o; ³>|÷ÜÑAH#Éß/sìYtW÷×ð*0F¡-"U1q$qk7mo0ŠöçLÛƒ˜Á˜¿M \f¼øc§è/îyOÒ«[ õ£·1Œûëý±ËÆ¿(kvÞeÞ;<EFBFBD>Ʋd|ïY
þ
ŽêDR<EFBFBD>ÃM<EFBFBD>Ühj5Dì®L¹\U>¤µÍ»Ð%!ZÌšŸsˆ<EFBFBD><EFBFBD>?ñ |ÐWI¨û]Kr$<¯ÅÌ ÑÇ0U×Üf¡Õp|Cx.»¡<EFBFBD>¨§û1Áüؤ/ŒÏ¦f¸>jÉH£5bex°¿zv(9mÆR"=»X<02>Ñ»™ôN Ì„­[kÑÃãŸä!ÉÓTÒüWè3"¿ºpˆkpõ<ˆYK
<EFBFBD>õx,¹-_27È=¨ŒX.A7^žBg§Kp®3<EFBFBD>É[nZÔUý<EFBFBD>˳<EFBFBD>4:˜ A®ñ½ì<EFBFBD>˜ÈCA<_;í<EFBFBD>Of¤©?kí!0Í
²Ã[{¹0"æ<-˜ývVúâTWY€^7èLZ®~cg•n÷H<C3B7>E[o¤­µÃ<C2B5>Þ±¤?™·ê#>)ô´ÅIiÍTÓ_Ú-NÔê±°¦WN±*…l„,±§†ì<E280A0>¾¿ŽnÞ}3£êÕèaN~orîïÓú~ê„ õ•¡W‰iØ׫ν÷8¨tÍÄÏñýì/íõ /#i…qÓ9óbÃu: Ô$ÖaZ%ÁG,“ºͦBôOˆ ž¾-doÅ Ì®%<>þ¦à+£~™®‡˜3ÒUoE¼šÑóÃKÑÉTÃîøµ
jgæä©ó#õóÐøÜm Í2+Ì@úO0ùdÄÅzÌgö|ñs:³0ÍìêЯ͌ºf1ÓßÅ¡^E´à¬é$¬§
Ì=üÈkÐÑïÍ-¢£þßj®¢¬ò'É¢‡éwnhk%¨š* „Z?E)t9TÞêZƒ l˜s[Z{ꕱjÔG“¡<E2809C>ÊšÙ"¨ +šÅ ä¹°¤! óSFY«å‡ÎäRÈ M4Ôýu½qBƒKOÑJW×5YØ5óØ”4#c§omÔ£r{^çÄU<C384>³µ¡@:¯ó±<C3B3>åð<E2809A>û|$¨?@Nöcïã¯Ýæ…|¯CèrÛÝž»þÇPDyröf†_á_Û3<C39B>£&9[ 5zÓ=Ñ==€¶KDÙ@¯ÕžãÐx®uˆ<75>´‰¬kü‰%à“Ê”§\ë,,ñ°ˆÔ=Èëã»4ëÐ{?<3F>¿í¨Eø%t×Ì}¢<>^u<³6G{9AX /™7·¼Lü>ÉkNk´ªÈËÀù<C380>x˜7üû®@&Å]3ØV!¯ášG¥Œá Wâ|ÎdóÇΰ£EàóõååªôXù—&bT?’Ç §7ŸÆ2-*Í\˜Š:<3A>HpDQOœå„ÿIBNàh³Ï£”×ÒÁé&¢ÆoœÏ"Ý#mö²ÀvÞ>þÿžÆý
ð<H -òjkŠò¸ Ì}ËÅõ<EFBFBD>t«vh9þ2¸7°9á£çS3µ˜°o¹nÝÂ8#Ú£{¹§T<êèöÆËÌPGŽw>§Êë6Ûw4Xó"{r$(©5ÏpGñåQ3h…,oê!Fæa<—q<E28094>ï¶Bc®So£è»©WÁçhgŒº·ÆMVž#„ˆe<CB86>ñzEüÎ?ÊÈ/þ«6‰äWØ“¦3LÈÒþ˸M\äï†øÝz<ÏSìà 3á×&~•5§iµÀCpóK ie^]Þ¤Ëù;Ã{ò´—Š…„…ÉüªCƳDoß«'jfðU¼i\ÈÊaíÎ:ÕdŒßåýóé…+Ÿ&5[Z lKÆÕã“Zóu>¬fÀÓg<C393>©ÁÒ娵Ø"<EFBFBD>Rmfˆý.w!<EFBFBD>tÙŽ#ûcäŠê¸¿ÎPÈÈ°\Ò*ã 9û¤íµ0³É ®@')3ÏZ{¿†­8|¦ðTV`ü@‡¨ì¶Q[ó]gwóúò¶<05>Ä€Þž­Š¦vѳ‰Ä”åKÛë±u[ÆÁH°Ëí<™I)YÆða ýz-•@Ö<> Xk*ÄfŸì£ê¡²kx2¼À[ÙBK¬XVö¹žÊdEUiŒÃQ.ûÒ-ýC³Éîrmœµùh*ѳÔJ)\Ý…pKî;´þì½#E!éh<C3A9>Ýÿ†!÷_¢ 1{¡ÒÌÛ"¬ŒÌ?ÍcwÁ~3²ÌU<C38C>RÊÍÛuyôºÝ 8F*®àP˜˜„ÅfÚÌxòÈ:»`ÈìÒ+±
ªBðãøƒ£ïDŽˆ0?F
 Ð+AaK$:añE¼ÔÓCÓìr.ë³}Þ¥u¬¦ÂSøø<EFBFBD>Žt¤yž?áÒÜË%4ù3}?1»,+iÅá¤^ë©©²l·<EFBFBD>¾žC ì§ôÓza )s¸U¬nWÀJ÷_;Ûwà MénVw˜ðƒ¥çÓÄOªJ´pÙP³3_¬ñ½GN¸_¯e¢Õ0Ø|ãÀéØbûç°' ý/[IÄw 5ƒBD¸Ì‰dSG>X|õŸWïâ <20>Ä£Kžˈ„eŽ?‹“´ž.¯¤R&+<2B>3ýõñ>ãÙDsJ=äêeó`%-;Ôqf “P®&3"6iDé»É|CV9ç8]¹nçšajWs³!=jfîé†mu L©º&˜€¶‘ñ¥˜*C­qÿ|ÜóĜҹñßíAÛ_ÿCgHÑYÄi/î‰è/²ÅØd}]Y+ß- £Ekð»$ÎT3ÄÙ_8 <20>ØÈð<C388>P&Cü©ÊÉð~[¨¿eÞ” /GŸ|ÉúÂÉèç6tàÇs±¸2TuØÓè_ýÎî<C38E>ñ*ã)ö£;E„9å…=Ä1[*s˜£jq€Jir~…WóT÷S>9ú6Ñ—0qÖ{œ ®é¸Â~ZÄÎÞZa±š“t¢Fôºk11Bwý‡ÆK×!õEráyÍ%4¤u-;1l7GdYeBNØ¥T-{Åâ~ë‡ï²•¢î˜áP¦š¡ô”VO»Ã浘»ò](OÚV]Uó<ßåõ‰6”ÿö3tÎ0Ù>W¼n[<5B>\l2öÜGBÉ1Ln½‰ªÚ¥¸hmjS<6A>-ObVô©”ì<E2809D>Æ3ƒ5³Ï§•½äô©BÌÏG¯Í.‰ áí÷<03>õÿ-¨û5ç¬2 íç–[iŠ!ÒöˆSF€\ó…u‡GSÒdøVãû:åßjX€§w…<77>b<(Ìqš·VbPwkØLYÓ½Ú}Ê0RñúöÓÙ-Z£gqGÂC =0,‡1$`ʬ;&8³QÙ³\VÑФÂeÈiP¶ÃîŸíµãª â/Â¥ë‰Ñ´3zO™[<5B>ÁÑâLÀÎ\ÔækÃOéøZ[WalkAv<41>Ù |t
Ÿè#ð5[tùLU%wUHCBCÅj]t±fipi¦uˆ³ö4ƒ»¨ýÉ©œãsŠKÃñ´`jp¢ýÌ{DåÚD±¹Äæ@Š3 ü÷ߟH!S6)$ñ<EFBFBD>+#Ø^ D;ÕaëHÎñP~j]èx¤ÇG;ªÿµðϴʦ«yæñõ(¿ ô£äŸ;c¨ÒãTïjo¦ö¸Ã<EFBFBD>î<EFBFBD>¥&qi7>è¨1<Û>B§qkhLŽÖ´ uŸÀÏÊ<EFBFBD>4ÁÞZg<EFBFBD>¼½µÅ¾ãiT¬EG׿@1ÍËêð7ë>iÞáAåD*üÄydþa_²ö\øAH06®Æy2½µøw@È-Tv¹<EFBFBD>> vZËY_ÁIÍÂÒÆ©ýÇ9¿«Â%Š¬pÍÎ)ôð"LE¤¤Öò* )NQÚo=ß¹è•!±ø¤ºüñ)4Ùx[·ë®»°ÜGG=ÈÂUzk Ýlû|~¥×–>¿CÅ`ï¨
Z6¡C÷ý³O½àº!ÞùŒKêEÕØäÍêéóËïW/Â<EFBFBD> »³)¬Ô#Z/d>ê~.ù<EFBFBD>^COöS°êQÒkh<EFBFBD>d¥þxm2­.Û%77¢ÒËšFŸyÖM<ôV ²LVeÁÔý5¾pÚíJõabOÈ<EFBFBD> þÈpªNŸ4ÿ>9òt¦nfšú;Õ¤ƒ\ª¹X[¨ß Ÿˆ½´µ<ПŠ8K#Êà­À×úÀ¤-§ˆ£çqN÷7ä»þR}ò,C$Æm²>{^/b7²ó+Ö-JaJ.·Œ1-q{h}|ðñù ·q|$Ìy÷¡<EFBFBD>vq°Õ¸¹Y£,¦¢æð]·Ùe#-ønüRD×:ƒœ«C{¤¹C¢dôaÊ54ïÜnê¾uUQ~èRà#Ö#¸·}Àz]-íV¿uÍ)ÙI¸Þ ˆ»375RDÍXK6TmŠÉ<EFBFBD>ÌÒAD8NO#ŸQ+÷Ï,<¦¼Ä`øˆˆÎœ°{pwÄ;È<EFBFBD><EFBFBD>¾¾Ãµö =¬ª,žÏD|\iÝ7M¿p´arðO¿ÔüÈ&+QcT¹<EFBFBD>9õÊV9X.ªÜ1Óc^5ÞJÃÑ¢¥Ãµf_¬ )¡î£.Ht7ñ+œD<ÏÞK&¦ê2¤Kº\XKËÕÓ à7þùÍUŽP=ɵñ饊<EFBFBD>ýœía"ñ:ÉËc<10>q>ák¤ey˜<10>ëÞìj]§2l_§Jïw]µŸ¦@y!î¸<C3AE>w©P­Ê§N>;É÷ÂD`8Úül>àÒ²T@s¹~wƒ—æÞæø÷Ü#Á
RüðrC`DÅq~RAfÅÛ ç|VèUÖz[ñ<EFBFBD>¤÷œÖêD˜Yµƒ
<EFBFBD>ð¯%~³«Òò7ó´ròõ+Wuqú¤Þ¾íeo¬vùJ î: "à¢F3pdú5ò{D´Dy‰¬˜Šø¯+aåŒ[@x <06><>Ûµˆâ?—ðq!aÚÆï ·|õ¡ƒK|SŠÝdT¨ŠègóýêÑñð@pTˆ Mã+$6DHb?ñùWœÇ†M“Vߘ˥㕲G÷¢R:$Û)#ó¥#](©±|¯ûA9“Pë!k ÷ê®98%îÿú@dgð…S—oZ„<5A>S{(Ô_ÆO=¾AŠ\
¸É*ŠŠËs3 ÃnþwšãÍ»ÂÒ}žåÄ+£½½BÜ5ÍÉ
cÝÒzaùôùbñÁûQa иD"#aŠj r%Åè<Ãô_UMhN>œ<QZ[LrØ#úÒµŠ[°)¼F…5®Ô:¾áÖKœÇ´U-ßÒl×þñXàŒÚžBšš”ÛÒ5úŸ9C
Ê(;Œ<EFBFBD>ÿ-qÌR\=0DµKpÖy5b]:¬<EFBFBD>}|}ÿÁ¤#Eʵ©ªì>/ÀÎÁvÇ8JS´gÞë)<EFBFBD>èýmû
$ <EFBFBD>qe1ÿQvfF9434x[NŠ³ºåߟïå kv<EFBFBD>x¾Jží<EFBFBD>+W8l£q¥Ö HãìèïN¸ËBh<EFBFBD>`çîO

l(ÿAW° WY>?·îÒòm£BÌZWJ,^SæD@ëgærRyú99{VÕ¢Ìm²§çž´<EFBFBD>´ÃÛ­ô"¶xÉWAæÊ\ë,ßn¬wálë¼kNtw"m F¹!ruÕßy¥%Ë%4>¡ÆøÉÖ{0%íÿguwƒ¦ ID Íý¬­Ôo_k¿7¸=ÐÊNŽQä8®Ý¯ãö<EFBFBD>žöÆû#Ïð¸ ("Zè=~ñ%éU²<͹[&EŸþ Íj¹à¤mÁ†Æyÿ<79>:dACÊL•o…õøɹ‰\Ìž;S‡ÉhSL` öá«á'û>7` ëðA-ÝÚÝÎS¶¥u$jeÁ<04>^ ¬a¶²XýB;#ZPb „ŸŠä0Lÿ¤=w•"ü¼¨ž/dÿ~?î7çÔ4Õ`¹Þ1ÿ";ÙAâjÐ9,3é~î¥OZS´zýH?Emê“šCÝŽ<C39D>Ÿ ç„?<3F>U}_•®h<C2AE>‡±´D÷¥ÂÐÈ¡ìq;ÿ9šþ¤W8Sjå«4Ö0òÏt; å¨{¿!I2KÁ*_ ¬¼g=>"Jži"<EFBFBD>¾š²åkÆÈR£Žð„1ëÓ´˜Š7ˆ»>³©AÛŽÐÎöú©Ýœ²¥æ㎡Ç9¹†ƒÕ<C692>|ÌŒ² Ô'~ò-ñsðƒR ÀÌFŠÉôkqÅ°;düpˆ@šÏ!<`t"gÖGÖB!à¾>±5·¿õ`VmŽ«Ý[Ò2sšåýWŒQS<EFBFBD>@Èî*À}Ê=D½¿3ò%wpYfŒ'Uºó~Ÿl„…‡zHKµºµ_˜J¨²fùÕÑw—aÑíôŒãhdÁèW<C3A8>1R…}ËœÂn •©&zŒ*p)ü4ÐxÀL™<4C>ehàødw}¦§hñ%ˆ¿X‰Ž¶mŸ—öXñÆ<C3B1>R̵w†þÃð Ó ötOâ´ ÿúÿý_ÅMš;ØÙJ-GÃ7Xa°¥?…ºÿŽÃe %ErV®œ¢“`h<1C>"4¹ªð<C2AA>¾ñ¿«IÜë)6Ì\OU±”Í8H`É:™´ç4Æd÷‡ª¯Ä<C2AF>}#\\n%è+n`!AÒ¿$‹šÞˆêcüð2úp̪ƒSŽFÂ7<E280BA>Ÿfä:K p±WдùÝÌšv`<60>L=C|t*4KüµC jÜEœÎ +ÒžHª#h`Ü1D1 2ÕD§©TÑñP>œbâÒªeŸ=º—¤J{ 8 ŠF øÇyÁ”BêIv̵Oÿ"¿¾ïµOAúã¼ißµ2Âó˜°„{ñ«|:gÅ^åÍ1«Î<Ôn»ÅD$rZ_ñ…†uËW`|‰&þp8E¿!š` ZÅÊd¸²@± ¸N,bÏñØ&ÿ+äSém_ø7§þ™%܇´œœ{=nËP·1÷?Ê‘´'SуcРivÄë°¸[´˜O¨IDSʤÄÕ<¹*@Ý® <EFBFBD>¼<EFBFBD>#<EFBFBD>Ý ¾ì%eëñOºó^.á§ÑúSEØë1]$&8d¤)îí¸±v$ _-'·]lRÄà-åyw-¼~|“™JÒªÅá£Ä맳­còÄ·R¬ÊK8ÎÓàs×ÿíæ/zïõ>z„ÐY&|˜8N977>Y<>sb¸]Užé>n—‰h¥»Hé ¤’^ß<>S°¤³ó<C2B3>×l¶½+ï"yq MÇßÞLC×aNîÿíù@Jé:hG}]£¦ûHÛ“'i
û
ð=ÆÉNVGã¢@WªqbGÅ®r·;GB<EFBFBD>MËæÔú<EFBFBD>)ŸªMYNT(ÛýA³ÆÕß3PÚT.Ýå¨ËŽt=<EFBFBD>^Žò·k<EFBFBD><EFBFBD>z¾b.ok\CoõÐBN$jl$ Óô˜´@˜óË[¢ Ç"v²)¿É¢'ŠÕIy2ÀÅ“×Òï»ÛŒGÜ1m j‰·ìúïŒÍðò"@¼Ë1ÊJîð7ÞW@^E^¾óÙC<EFBFBD>¤§~¥QÄ·oK ;á0ã3!¡KRx½Ó8S©ËË­MMÜ$<EFBFBD>ŸúO±%ÍP9ÿV¿0V>©&.ôÚ­×j©<EFBFBD>ÒW´aÆe ìØ#V|£d±Ü¢˜Æd¥úà#¨ÁÓä·Ùn ¦Ep9ûƒÜR¼>«B<EFBFBD>yqì ÍH,»9Îl¥ ˆ´Žvnѱ|<EFBFBD>ˆÒõ<EFBFBD>cmYÅé¸âŽñîê5a¡{À¾:÷*¹äü !ÜC8¦æó:ìwêÛGÓAÄs(ÜOêð°] ü{§<EFBFBD>÷iaÄ ¤9ÎFðd3eÖöÅè<EFBFBD>pý§¢ æî±y(*?làwƒÔÇø|'·ËJ<EFBFBD>Ó?Üðk áhžwAžH£4<C2A3><34>„*6¡8™ª,4ü2\i˜¢òæ­}“Ìá:­Ú‡0·ÀAuà<C3A0>ТW´O¸Š§ˆkzñË£üb¬¦ºAÒ°ÔõŠ¿ð‡,5¢f¿éBÅ"º_hºe0u;DqÐG²R¾·^Ç\1hwEwâò™¦¢<EFBFBD><EFBFBD>˜1·©àu³½£c߯mÂqƒ°âÂg¾ áÊæ>æí¼2~õ€õÎvg5o™óòâ7ÁèǺ±7i<]ªˆu<E28093>¿X ˆ]þv”W -à€üäübcP˜´¼L™ä¤ü<$­ìƒûÍWrÚÂÔ¼àk³“, êÏÑ¥
o<EFBFBD>÷Kl<EFBFBD>ú~m8è|)Çnd3<EFBFBD>6œMÓóÅYy{À§x
â-mÅñÒ{ Í@·äÄàÎužš<EFBFBD>µËcÏP'ϦzáOìÐ)¦Õb/ç)Q0Á$[I¡Y—WÊANéÇšv%D²!LÚÒ'=ýqòÉ`ƒ»ÂÃÉÂ/449ÒÐ|ˆj˜%òªìYÌ7ä²ðTód¹ÏÊu<EFBFBD>œvnÃNÝ{ÿÅóX_{èáWŠ­Ñ$V«67XíRç;íƒ! $¬ºaUSÚÞŽQGCydG<EFBFBD>Ð º¼?<EFBFBD>yàÅ?õãæ¡£´Í¹ªpfßòY+TfB|¤«
Üy9Fš-Ç90D7þgSôm¼Ìphk.έu'Ì©‰°ÅòfÖäHxSʤ\Í…çbˆˆÜ/â×þl¡ãÒÛtú¤Ÿnz˜¤Ò<C2A4>^~|SšU5þD%1j8”*'Æd3Kú8eµţ mwÀy+ŠªšÔhh å ÍÑU<EFBFBD>$áÄo^%Ý4Ãyªÿðµxà©Ô5ãqàt<EFBFBD>ê¥Ï׊ø ¹Õ)ˆR©ô2-,-¯qTGBA÷TaüÝi×@ÛÊä<EFBFBD>ân<EFBFBD>#¨˜òï÷@¦¼F«/>ûV½ùˆÝÉ<EFBFBD>mät<?+<EFBFBD>¨<EFBFBD>: ×
2šõ¿Óæ«"ÙâP§@7V„³DþÍP¾XöÔ?Úº8êCɯr2Ê<32>Õ= Ö$!i¹…¢MçæIWšùÇOY€*•Ø*ig:!ƒ¸=…<><E280A6>}1’³‹Þå@_Z¸²„ñøeâCØLÛá”ÎÁzl%
àìùyþ»®\ýg±˜õðêR+] <EFBFBD>ØÀHþÈDPtšWKñR}H0@M¸XO<EFBFBD>¿#¨"³,/K0v÷@0¡{l²<6C>—ËwZ|PAd«ŠqCoˆrŒ”åë?(¾¼­Â²´é°ñâ†çLÍÉÆñÕ0Ô:aËâöð<ï׋¿ÆÔœ|Üax}^‚µ).4._p[>áèko ÀStÜ#<23>n[¢<>ó¦<C2A6>Ö]¹‘¸Úˆ%‡³—gÆɸµÙƒ¶BÚ.øôôŽÎoö§þ¢T³1 <20>V%¶¿Ä‹ì¶˜,GP]S¯&+M™3@[NsùjZuÁTùáÚå…‰¥ŽšœÒºž-ƒ«·ì£c¤«Âå«0MºÇJæ\OEÚâÏQ¤_<16>ƒëj¯Âý Á/
¿   f2>É£2?˜Þ˜ÅÚßœÍöj»ežÎý·¦Æ7Çfc<EFBFBD>ŸÑæHढÅãï.²ô^ý£ÙTM êpÂAò5Ñ4ïü)¨Ú<EFBFBD>Ùj¸8ç½IÁ<EFBFBD>D Mÿ@²ƒõhDgkŽ2ú<EFBFBD>$¹Ÿ¦¿Ë´¯É_{ü¦Ã9Q²´æîMö³màè¼d ¡F+Çà<2#!Y.³ÑF?\";GÏAZ¤jÊq"y>Ú6/KWÈxñ5g)ëÑíÁúÍœ'rÒ§³<EFBFBD>°ÕôÈ ÃýÌWùë"×Ùéój,Mê{8œˆ„QA
dÿQ ;3CGb³FH)Rmcl©g¡2ÉNúm~Î~§<EFBFBD>|Ùí|ºøÒ¯&öÎbñå81õ'㣟õ­O¢rô|é^}Ï/W<>Ì\ú2®dÎÑë&6ùó°ÊÑ?ä/È@p·4/aK¨¼ËhUÃf~Š¢°PÈ—ÙŸ” b5©iñ±6Ëà Æ»œø×èZ§Qƒ5D`Ö'÷Ð¥:#"Ù8¢”|H>üX¶H ¼Ó‰ÞyáÝßwgþéÑÞ…ãÝåfŒ%øt#)°5ÀÜm¶&ü?wºÕbìÑM[×hnõZ\†~º“éZqW, •díÌ@ô89aE,ÑwÛa_™qô¢c dêÍW<EFBFBD>ûB¹uµÓŸ¯[[/egÔ•²b²û</ÙX`ŠPµ¶Ù ÿæ1i\Zr&
PE¿šG<EFBFBD>ðð)?¹U@­]É?Š^BÿÈ ˆ^2
³|qþöŒ½ª~¨4U6OÆ¿êq<EFBFBD>Š@ê;Äx¼×Êïu¦Z¡ÿëix÷ìyqÇïððèÿF¥¦LO9^lDyAèLt<Z°ãgÄoœË²<EFBFBD>,%6½<EFBFBD>ó)ÑšÅYÿ¯<4ÊŠ²G³È³«Â hiü!/<«I7ÿs¦*ZR(5Ù¡6§š8®ÅVÛT²Î<EFBFBD>s1âK L\Ðb¡§ßÒÐèœÆH*<EFBFBD>4×<EFBFBD>û®gN¢0 °[j?ÑÅÆN/ÏzrímóÚïõû½°àqC
K<àBÞÌ#é6p°ÕXjîWNšÅd§]ÚA]Á¾urÃíê Ž<EFBFBD>ã&<EFBFBD>¾¦h[>±ÎÑ®®X®¹ÚE~Ø ·dQê<EFBFBD>Vø@îíý¡</tzT:¥Þ­¯2=­^PZ<EFBFBD>§Y\{obaX5J­ÜÜ÷±ÐJ¿Z¥¹vÜÊ=:mTLÂÒFØ<ïÝ®d¹û-Ž%I Úä<EFBFBD>Ry¿Ñ<EFBFBD>-4oAß>J¼].ËGtDk'£]¨À'5OšËŸNštœÙ°¦æDB¿ô.ÞßÙfÇ8GaY °h~sê*l³ ¬0 !1¥7?<EFBFBD>\fÇiwCªn{ñ$4~jçã÷£úrÿøŒÚ´íw#D[®¢hßÀ2µÄçèÁÌ
Túf øÏì p6]<z¾º.S14íšgCÃÎI±X|{Hßnbµ®r¤ÁÞÑÝ+ k}B]â¨RÖµ5Ö'Ù^¿Þ½â¥èÊŽ^E<12>,o§ ãìl¿,l‰_#db,´'P))<EFBFBD> Mv(<EFBFBD>.rQ¸Xáp˜ÍûcÁE˜}f©Å
û0üŸN»Ÿ|œš§$ 4Œi·ÂÂÁ%ÄÙÌÛ__Ðथ2ºå#*ñ{wk`F·0q¬U¨B>>í6û6]@T¦aRË1l§¡í'¦Ç¹Ì‹¹åÊ$Mˆ­<CB86>1<E28098>—+þ·,È@…•·îyú€á#jkNèYܸëèS£Ð÷6<03>À "4)ø)õ…ÆM¼ Z5>¾¡aˆkÏó¤À¤ÝD3¸É!O»öåœÉbrâŶñèÇtpêÆ~ Ø€ÅhPÞj|
Dn´"‰(æ©´É<7F>r……<E280A6>MÍÈ<C38D><ƒ¶I«Ê<C2AB>LqïJÈm³Na#s#ûÎÜ{rh<72>â0ùض°„Ër ¤é¯xž‹ÙåŸÕœŠ´2¿n^üN&°ú)sü†5DT„Ó5M0Dw4%žâ®v;¾Îd¿tEÊ_tÕäáìKoÀúãv7[ÁÔÃYoQnËš%§ .<2E>ùw•¸òÁLÎÇ<EUÝûåÿ¸ÔKKðÿüÀÜù  ¼ d¹<>ýQõy€R‰¹je íRèWj®§ ¬°n.«P¸<50>QKÉÝ°#%±Á‚Ý[ÊÀ±×½ÀÀçžÙVwGï‡9cUòÍ]ÿ<>4.é67|\Ö `¤2j ™RDýâ¸ÄQÑuZtÊä fÃï²×:¶N»œ Ó|<7C><$¡à´£ÈeÅQ¼!ÄÈRÕ¾¢R×óØú<çú;€õöcŒ\;"41jêÝòb\œZòcÒ.b^Ÿ<EFBFBD>4mxšãÃŒ ê/ñ®ï4Ëœ¨¸,¾Æ=Û¤]åL×V}Øêâ§ño#[D <EFBFBD>Q4'"3<>'6±
?:9bøj¿ÑÚ;î\Õ(Æ$z3]ep¨Ø»®5oV3ðÓÆÁÀÁCèƒ[©ld®Eô& ¯tƒy!9¿0Dgvj:+ |={üUÖOöZŒa»h¨>cw\ÆüäÜK''üd1œÑˆš7<EFBFBD>¸šÆÈ"bþ»?Qmä^Xxzï?)Üvw'Òd*%óØbª9É~Ák†IC•çj©ˆð8«u“>j°âmC2¦}V†¡”¹?òãz<C3A3>&#J²°ò´™ÆúÓF ;ÕÿŽ«õ;Ú¾n /@ý^"*¨ªO.Ëö¬^©c­SÊâéLά,ë} ×kqů Ôg÷Ug¾<EFBFBD>Õ&`Šn¥h?ÝQ¸<EFBFBD>Â*{«WèÛÖÕÖXýðëêëm/ÿB×e*b9,'$Ÿ°Ö—4ä\êUù ;O­e]¬`¥ž<>)¬_„<5F>@3‰ŠLÒŽh裡¢$æɼ·¶·ùüÅ<C3BC> 7ƒ30cœ"Ù jÐ<10>·c}3CQA
3ž%¼§HM<EFBFBD>OLS«Æ/Š=|¥d#j˜¢{T¢îoˆ,¤<EFBFBD>¢8Ëëº@?ÄM¸Ò/
\Ûy ^¥±ã·ï z¹07E$ 묬<P>£ÎƒÊšE
ße2´[eŽp£Vn®yÿ<EFBFBD>Ò®#<EFBFBD> Ôâ«X<EFBFBD>Ú;¼¸AÏyl$Ì£ä8¿éî&exµFG¦µŸƒZÆ9MiÓf ¤À­ :S[:_ W\ «Ÿæ§(,¦¯ƒä<EFBFBD>$¯âë<EFBFBD>£4¡.³f»Ê+N¿Ð5 V<EFBFBD>æn°Ò¦ölšd¥š!±¡ß~ã~Ÿ<EFBFBD>n ¢>[²Po_á¢mCHOèW×` svyOÒ¬ôYäÿˆä?.Ú`©¢,øî=Vm¤µ&PkÃVQ¬AàU]!*>=7Q'Ï:À‹÷à€EJW?õ¥a™žV€µnÑdhðåÁkàãV˜¶Y¬úBÇÎå²/vêW^Ë»µ<C2BB>ÓôÜûžñ‡†UÌõ¶ SÛà7†ôÃ"QV“i ‰4[Ä™Jÿ;=4@i^ØK³a^Š¼QJ²0`•¬ˆ--»_øË¡¼MCç³ÅýŸ©ÇÓ°R<¹`À¿Ò&Û·W<C2B7>Ò]Û©TîETzUý{šM¤K) ŒqI6 ,5,1Fµ5À> ·$¦†«bÖ)œ3ßË“ZêsNtÿ}KöÓ$š®ç­ò¶°Vl~¸Ò3ÅåÖ©M<>¢…z¼â¯¥=ós.ãÖTüd'úˆ&Rd_+ýè/9{·Ã8(z~ˆŽ­ôö±\쩘oót f??ÊH<EFBFBD>Óv.¬)+¼Ò2#c÷kgФ&Í)ðà Û>Þ,cÿÑ)úŽy|cþL3oS àªcesk.¡<EFBFBD>mÃOyo[zÁ`!Ì0vb$úN<EFBFBD>ŠÏ´Ðò7»Æߨ©Í¡¾7A^(ÁÏRM:ÆV:.sQKD±?¿BXÇhJhK,hÎÁY29£rn_-dXä@k(·ëž¡¸ °¢·˜µªk¡ÿÔ<EFBFBD>;®tTÈ+î9÷Æ4LS] ûTE@, Ê«V˜t/ž]À¸KImtÆF{kU[3M4þfÎ篧¬úrGŒÜ­îîk¦×˜=?©m|¸ÑDÄXLËÆ
Ë×[H_ßf7` o6x¼rWMhÝ/õˆS.ç:Tkf½<EFBFBD>`kê ¥Úo^cq* 7pñ8ÊK@èå á]Í$£ßðÀÑô)Ä|úmÈ^ ã¬^ð:ï°ÄöC@XüCFµÌK"m$/S>دÆõ°‹ BÙƒ$ ÊL¦“£bšƒ€dz®ã~¶¬‰ <0C>åÙ²aºú9_Bd±§ºP»ÅÈÃýIÖmçXùÜaWçå‡åqsn®¦1ð#vX)Ac”4ù:Òább5iJ§S”wVª÷¢—éi€G<E282AC>y|]Z%  æ¡Ï2Q¡ôï1JÅ×þ&LžÌÔ“tgè I0OQ!w#U¦bTOjŽEG‡&|;C%ùûlQPÙvFD~Q—ïóRda´¯ñ
ÆWÑTí<EFBFBD>hpœn¡ßº¥cNC­?ú;Þ?F]ªç*ó&<EFBFBD>G+jî0$°ÙReGI vw]µÁë·=085ý#Àö/¿Féèpdsjj-£OïŠ<EFBFBD>¢_VZ><EFBFBD>ÅݨuœT®î-ÞÜÞÐÑ×59ÍS´aDÌg÷§Aw<7½%²ª^¢()¢ZEóÌâßEQ]W_ÀUËéfÒzJ;äÊ<EFBFBD>²Çz¦Ò@ûÖ&[ì×TuØÝmƒ9øÎgÏfôó= ÿM0øF=8ƒÇ#]küiZp%þ6œ%³gÿ~A^¢IMÑ øéúaBw*"Ö¼ûÍ„6Ž·*<2A>¸µ´ë⥈ÍPæ{ñÐ ;TÙoa|Weð+B!<21>{9;@
ÈÐMHÁ«(ó'¯¶¿¯pA<2M<32>ɧÿQ4ªÂè(«iWßnŽøA¸¿ïwÕV5!&·ß„;F´<:uÃÌ`m\ÚÛ­*†)îðnÝñÔþîœ#Õœ‚˜›²=%®2;Ëq¤?JYò+š²ìmx oÈîet™}¿Xl<58>9-{ªÜà#§óToêÐèX½ÉÕh¾WïÑ=ÝyÛ ˆ‡*’ól»@±?§¨É-C4ÂL&?U/Š^8•hAªpJ'yS\§e<EFBFBD>n¦AwÝŠ_õu´þ¾˜Çé<EFBFBD>·<âNëí9ô~<EFBFBD>/ÒŒ0vªéfe[ÎL_iÎ<>Lú3ðˆj1ë1ngÂ]ºùÞGPWVtáÝ]¨å<EFBFBD>iSï`{üž^Ž´³B¸ì'çuå™81µm¾þýÅ ò@êi¬`ï†)õÒ[ð¡7H°¯R<º@”ãhÙgÏÐñUk\êtNg}W²†`½<>˜rO¡{(³„ϳ<C38F>BΨiŸ²‡<1D>Ü&æÀ/ï8¸ßd
¼va¦¸­W¾6½a§Û9Œ÷¸­?(ÙµC?ÁeÄ`«]ãíYkn¦'~õmãüÒîðXù`4šƒcÃ̲ÏÒ?ÐÅ7§£rð»YÐF_î5QSþ;Ã{d?ù}<7D>­°¤'ųð[ :nçæs<EFBFBD>Ÿ˜Êàèi.iõx|<šùjpœ»<EFBFBD>¯§«ÓÁÂÈ&*¾Ä ÌM ÜàXdû.áù 9|CHÐOÇO1d/Co²}¿HŒc±k£|{=BgpûTûøtŒVÓZ³÷ž¢1»ÏÃÄÊBÌÌôÖ ¼ÔèWv<EFBFBD>ï¦ê\Í2×~º6ÍŸdiQ
±ºb¾é[šë(ÏØÜ&ù½ƒbŒìk6²#øÁrVŸÌ³
¿ ë¹-÷}UñçÚQE·2ÛÛ:FiûacÕ ³tW:g&ãzÛM$´¨³ôÚ¦`JÄn2Ò÷bB¢±Ÿ¹r9§UJ¥As¡¸4¯OYk*f¯¨ë@ÜÉ¢ÙÇÛ|8<EFBFBD>Q)!ü}ðüéß±Š×Z, áî'A1t¾B†býVÄž<C384>Ë ˜n}æÜìã(~<7E>X+ƒœÉ<C593>àò±ž¾Ÿ¦æûØOCÅÌœQüõ,ÔU¥ùië N¨0%lpíùFÄ»QhÙ=ƒ«£ùÍQ§o÷i; sb¯oÇþ¡ázäåÚöS¸;|æã _3·)SSîÆ?l‰\ÝPù`Ê*.!HbŸ¡H—ó«á
Ýûä/˜@h)s6A·t\±òã¦<EFBFBD>ŽûɦŒ'œ‘Üah ëî´¨’-;«(ą̂¬¬Âãi)îðÌ}ÆpÔ²ž[.óö€¯¥®G ÚÌS01pðÀU<C380>Ú +b½9b<39>xJhŠCÌÖ™æåƒ8ƒtöD± ½DHÂËžŸu¦=<3D><>«ÇÕÍöµ˜¾œÞL@^<5E> ÷*óðt;ìPBÉ ;GÿD(w<>Ecù;< F),Ô17Ô:zTDªTóÔÿ”ñgyæºA³Â.D¨¢­™ªÁ—Ú™üÆT1e¦0.¡À€üGì­â^62EŽÉˆ<04>}uNf-,mòT›»*X6_JÜg<C39C>ý\5Q¹VZ¥¯e‰ËÄìy3¿ÖüAÌÌ\÷ëfÊl·Ð*¢«HÑûZÈ0õŒéùcãViez“ÿ×íO¡Ço«®!¥H½7(Ê<d5<Û“>ÎGjë™ÞäÖÑ]âk¦h(H¼¾óŒŠ=«¦ÁÏ®
zåþYˆà³Á ýñ<EFBFBD>õ&HPè*¬ô òæhˆnkP öˆ }ði<EFBFBD>,VÕ"ëF®©^VwÝž<dÆOõ¦ð%^¢d¤n7ÄC|ÀÖGk¥¿À¤LÞd/½Ìçt9ÙÒ+ÄQGrÍÙÔ‡í-oäiTÊw;;™“j}0ò¯#(î‰Ñ0ô§˜FŠ_7ºX_¥hë <01>s&ŒÌ®<C38C>݈»Ÿ Â¦Óâ¡ÎzÂÚ#Éw¼%àûIZLGS(yìîö
K<EFBFBD>ÝëÂÖ ÿ°<G( âó-ÇŒ¿ÒÛJz9ûªÇ<w¦8ó³
w6¿³k}#£Â8 ŸÕ¼;Ú¤JŠé»s
zãŒÇHtWÒ[àôÞñdwÏ,>Bas;+œ2÷J ?ÔûRÿ¿9ÜýxONŒòK]ÀËnjn%<EFBFBD>e-/
êé =Ò¥aÐÀ¯#Ó}ìµ¼òÇß`´þâš<õy wÿÍÐp®´»jetãk<EFBFBD>\vš9¡aÄGaáióåãŞ<EFBFBD>¦K¤b n¤ȪÝ<EFBFBD>¢_·:D@ÂGò ÌãÓØTüâ:ë/-mÑc¢œ¢ãJe6¸T.}±ñp¾<EFBFBD>˜%Uu»)EÉ8O~6Â%ãÆô+­¬Ýz@ Ïœìê«®ëõÞº½úD ÈÑ0ÆÔVÚÒääjGiv·ÉƒÛQ.Gc ðÚc~£ci¦
<Šv¿MÂÓ/jKºÞdsÿRÊ<EFBFBD>C²Å¥š3ù¯š8ihX·rºJäfÁv)úå)â#qÓâëB:,  'LsŸ…ZÏ’'­$3X<žªPœùOñ5hE߯dWªQìqÊuh¤Æ<EFBFBD>ËêòwªÁIô:ð~*Ò) ¹V±ªå{éq'N-\À×#_¡JÐ"Q(T½ 9&¥6A¹ZI“×b}Ì[
`Õ¢Þn}»}ùñ<EFBFBD>ÃÊI »!Çä»2µâ[ƒÙÞæÎ)uTùäè>od¿MKؘP¬>1ð Ë­ÿš+»¹·Š¼¾jO\¦}]`÷éßíx<EFBFBD>þa<EFBFBD>³[˜ÿø˜Æ\¢²q<EFBFBD>â>ôdÔé˜SÈAð%ø+ÅɸrŽnÈuŒE¨izU@tv"p¡P׸šcæçì©—<06>|ÂÞ(|nñ¾žlYH$ÉýÌ9ä© y>8–éÓYgUÊ;öqeK¶PžBeVt J­âœRP#“&Å5ŸrP¤ò¤`—~“
ÏÉ"¸<EFBFBD>™K÷žwõŽm%õ”’|\¤ûöÃÕ¬;Ó8qáó.ÛEñjîïh~ ûûÿ ÿi”IPD$Èü…«éÌPΩl]<bS½iuÏÕ´€’-§Û €´¶r…¶Í©N7Ž]a©#æÙ¶ÑB÷>¹SYæÖZNx”ºðÛ㦉¦ïú¸Ä †I Jeµ¹ön¶:¥B?ÒÚVá6PgV8o¬¨<C2AC>r´<72>5uDH<44>
Íâê|ÉŽe'Ëà)¾F~üc­p×RßpÂöÉx™ª<E284A2> ßhÜGÉá ÊP"„,n¶…°:e.»X-|ÑKµÐ¹öÁ˜¢øµmrjY{•®H5ÎÂâ¥6ù6+íGÞ-5<·f€úÉà_Ãè834÷i¬Ÿ`uƒT®dQ>„Ð8oCü©ù¥Ô´/BVE<½Â3&… Yó¡ûáI‰øPºí6å:8Ž;ÊVçÓ‚°{ò´±~)ÃøÇSDÅÊ5v¸ÿw¾|fjÛ¾-±ÇI£:‰qD™ùå dÌ(µA±S¶<53>ÜXjM×!\wøºMµö¦Òìýüò›MU«æÜݾ[U³~Ì™ï³<C3AF>¹qÛÛš.|ètò$#¥Ç•Q
<EFBFBD>z~òöw6Ä.°¨ÐQ(ÐEWj:kÕJÕ¡ðjËXaï&Ä®6>¾Óö'0ÆÕë·jÏتï™8G ì·lhÿNvÖu¦oèF¦<46>Ž5Åÿ™d¡üCÇ="xQ(¶+Ø÷ÈVEýº¦…¤Ì)c·¬\,¼¿ò’+ÍæÁÏú£} ýàŒ&S~á87(*»)Õ[àÖÜv¸±ñÛ $A³äœ/5»y­5p¡‰B-¨šµ<C5A1>ž)Ad5]$=áät¯`!U»œ9pÃjøbF.Ê0ŒºEœ‚ã×禓`zûõÈÕ¦¾d¸µëÊX¦öú±ØuææiUê2Š­q‰v ®¢Å@r%µlGFºÀóD2ôN ?¿[ÙÒÙT/}}L†m+<2B>ˆÓY*-Èv˜Í”2¸ öÓä(š¹ª¯ õaÓÂ1êmXš•ô³alƒ~õÄÀ‡»Z[ÑBÛEg>Êx66C†Ü•HvJÔ´cßÊÔ=¬<> îö?¬ðž;¯A9ž<39>ȨXò†]ÝCùAÌú^<[…ä=ÂDµç<C2B5>˜¥„õï ¿/½!a !d÷ª,ša*èD<C3A8>gBä úYwûXoYR¥ð¿·"€[üzÖ!¯Ä¥™àVÂúÿ·áÏòl=ª÷BV†)Lg2®ßç«·?­]QV8‰õ™+&] Í\¤ ^Ê>ì]·<>„UJÊ<4A>è`àTuÉ JrÞî1òâxùi”Ù]œê” Ÿ c‡"¯2…hÚ}kGIÚHdþ_p0ƽijûi…€"0ÓQ¶ëD”…åã]¸R"w¼cigVæÍÁ`"ÙV(š¯LÎuÓœߊPFŽT<%œ½ÀÓ5iÍȺÖ¨EßÐе£Ôìß}R®­­ô™SÁEÀ‰ž<E280B0> E|jŒ+~N¹oikŒÍIîÑÌÀY{ °™Qc©ÉˆÊ“ß>>šŒÇO°³Pb…—¬kÙÔÁF€ …þÕI¯«˜Ú<¾J‡H0KM»¡„v<E2809E>åv\®¹Ê-zjo9š/c9ûô\HŒºã<EFBFBD>ÆÍ]Š…+ŸÁ䰘ϞP&Kõ•ì[1úü¹·ðšgOñ:þ™ ×>íêm#«(n¡~Âã<z†1ŸÎÚßBPf^Ñ(÷²¢à¾ÝH(ˆG2PÝÓ×Kÿžü´s<C2B4>ùÈXÚ<58>ߨ«ä¼_#«àÇJþ?Êü<ÁÚ¾‡ý*Xfy%éë)iî `±x¢Ã‡ŸŒñó­ ¯~1§É 1*ó¹¸CD­š•ÏÂT<C382>^Áa¯æbdw:wÇÇ M”+ùSªÑ(¼V@µöÓè—¢Üv>æaȇA­çv;žØî3@ôÙzÚõ Â×g5osÅHX~ÊDÌ ¦TG:ù³ iíòYtÁ~¥” U¡r ¢^±þî¬8»z¾üÏo ûªë朜oÜdê*’ù=€Vž }‰IŒYñd@²*ß<>ÏBÐíQEY€lºO“—Ì\ÛŸ¶sò÷^ù;w˜u<>ÈÕ ¬±*+¶:^²ó]ÂF æ;w.Þ<>R…±Ôñ@µAúO=2ÿ½=+¸÷%<C'ðHCÈ¢ÉsŽÛ© 
]óŒìR®þ,Îá Ž<ª¨ãÐN³¥ù4n1<EFBFBD>² UjïˆÕÖÛÜÞB/¾.ôLtKM{îFqƒV2ù(Aˆµh+}<EFBFBD>»>vÄA¤Y¾d<EFBFBD>=V¦hÇÒwï¬Ï<EFBFBD>uÚâånÑÓ§^È­«=-­ˆ;¾h &#C²í óšáXx˜3Í-[z.œ2$kV©­\Þ^(~Ž<EFBFBD>ÇX¸¡vÑ Mb|ƒ'ÆŽ׫õÚŠŒ×³)¢™ ¤¥þP´5IÎf¸Õ Ë8IŽÎ#+34lý-ÊÐíhB4 <>µdÑ:> ó†Ô”uÏIFølDe<44>|ˆÁ<CB86>Tq©gÔå*j¿ÇÏ7‡F†ïÆ~<7E>6vÕ§_òö,¶P ´DáÛ ×b0Íçü_¨íçké·bÏñ™¦³€•JU4Ô¤´:Žà7|Ã3rN`â¾áÍHŒpŸrR\Ó+¹ìoqò4+ÁÒ·D°õÐĺÊ[Ã&*¯/Æ2Ñκ“ÅèIô2Z·# ã¾>÷Vv„FéœÑ<C593>•Y†ïÞ?JƒhDgû¢ÒÙõèÉãpR<70>„îkжŠ´¡3 \©»èXžš§>'æëä«´>×dFž<EFBFBD>ÐíÙ8O7æò~)%zݵî(y²ÓöU_˜MXiaªÇ]øcPeųÞÒq¹ËÞï<EFBFBD>ëAPØ*M´ǺØѤ fÞüFw-4ăñìÕ:×ùÚýº0^ñóy" “†Êm>±ná+¡z1¢Ü]Pn¸˜²„+syu×<75>Ÿ<­eq±Ï¢PÑç©ËÀĨ̡¬M²µy“ËÓëàÔŸû±f.‡'lÚözéI0 ~4é(%;);ÒICCJa„J|pŽŸiee¸<E280BA>¢•Å·¢”Ôp ‘ô–¬#£nÇ
ì­¹ç>G2
/ñs´]Pâú#4®?ßýŽbX5Œº©º¯ëQþÈóOKF"oæc÷ð<C3B7>ÓŠuéO«º»; H®[%PõÐ̯°ð4q'ÇÁ¸ôâИ)˜â˜¬×"ê¤ç7­¾ËZœÀ`q9i?ûS¢nÂkÁQhÏhË1Æs3|œ}S¦üØ<EFBFBD>èWÓ±PðäîaÑWÉJ<EFBFBD>àÔYaæ¦áÃŽÅ ³}霮»òÌÐ[ïYÂGpÑ*ñ<ŸªM[ÿSMDÓÃvÊHˆX­òŸ<EFBFBD> ë²Õô±¥\Þ#  xòyB!ÈÜÊè¹Û1»æþÐ+Ü<EFBFBD>n$ýçÌ Ñ5[Q69^ëÄ 6y16PIÍm]<EFBFBD>g_Ü ÓN׬qŒ®œÂ[ <EFBFBD>¤ª7ç2 ¯åÒĽU Ýé"Îðúéíåké·b(FzÿŒ„g ?¬‰¬¦éÍÀ†fB>…‰MXqLs5¸ßMaŠ×ë¿<C3AB>l¼¥ÞÃÏbúê° bYëñ‰5!Â×Ǧ‰DâÜt0ÞöEî”JOyÿâO-„Í‹ž`Éö3²@"ïh´ÙX.OÉ-o<EFBFBD>q^Ù¦é?¨Íu®úœh]è/¡¿åÂ}A´/Ø~ôÉ:åuó+5ÙrQS2Ò¢¹|-qná3PÈ lHLLBa2½_fÛw¬s,õ%44Ä ©9ôŸoÇp)/¬¸C bôGæÝoÕ¼ØÒç}þ
cÒ3ç¥ðPoù¥ŸK<iß7díîD\¹ÙzáàŠ»hj¢yö<EFBFBD>Ÿ¤Š 먧šÍÀå<¼Ò²@üb¾ÐÎ.×ÛÉb&§«%. d\æšrõUgy8¥`ìlõÎKuPÌëíf×ÙÌv[$ZÂL²Œ<EFBFBD>ÖÁÛÏ<EFBFBD>17t¤¢«ö¹¬ºB­æäî~!ì9·âs&é¨é"IqîÑQÏ?M26`H¢¾<>g!Ô&N-BôÚ{¦\›å'Oè(f×Ẫ¹ýÖ<C3BD>—D]{"Gš9Ã$Á++bB.Î8ß8E«D¸Ô«;U¹cYC%`ð®´Ü|Çe<EFBFBD>
ï:&ùf£Êá/#Ì>±H´®˜~^t&c%fßCé<EFBFBD>ù¡Þ×Î6ï©*ª˜^*§Š
ˆøÔF¾5¯ÓTN;ôYqªí7ò{7[&-Óyiœ0É)¬úª5ÕŒ<Û]ˆËT)¿œ^$(ØþvŸ½ ŒœBlv[Ì:ðÐ
Ê]*Vk'˜$0ÓI5è(Ø¢Òƒñ|y#I¹Ð©i ÛŒî(j¸‡*ö7_35´| é`E<>zÒ­[ãýdx5v1nöúõ<ã4Bø:ú4£¾28f£X ×`ò87AÇ5Øq<C398><71>àó,Y<XÜ{Æ×ÐJÉ“P™<50>%/ü°”VêÛõcÜMž0HÎÐüDô&éÜÞ‡Bÿ^Åî{_-žCDŒêÁPòš(hþXiü¼ìö0„™QQùêHæå®RÒ¨&>£#<•WQ2Ë<}ÿrU¶
/Š-Ž­¥{¢<EFBFBD>~)<EFBFBD>~ØŽ îçyÀŸ¥lI¿ÖY|pÌÙK´Ó±éSyC%.åÿìoO9X!-®¤LSý'æw1·:E6ñ¼œ†@©{Ð[ñ3<14>2“²kÊ*üþyTW*°?|.¦›ÂŸ&Iú¥þº?88E=õc£ §«!ìœn<Æ®h]_d´V±nßqƒ».ŸBÇŸåìvjï8#3¹»§¸¿\V®Ÿsþ¥“º<E2809C>Š}/Y¤· rõs™àEä3­7vûº. +%£Nm4lz"w<1A> úœ6ÔýÞ£¦«)¬>>Í)=`à‡YdßcßJé¡„wrqrô¶™Œ‡†‡ Ë®¡œ›œàö±°±3õØËÆÅÆH
íàÛÚÛ]õðïðr4
I,œ^A4/./±sVIDCDƈk^YXYÛ<EFBFBD>snmn𲈃ƒǪ<EFBFBD>˜˜Ü¿²­¬­/ñÔÇÂÁÂDéÜ×Ö×Yþñìëìn0ƒE(˜Z=0+*+­oRE@?@ÂgZUTU×|ojijì®~æØ»®©¨©+íÐþ½¾@åØÓÒÓUúíèçèj,ýüýA$V9,'&'©kNA<;<¾cVQPQÓxkfefèª<EFBFBD>{z{ý¿¢<EFBFBD><EFBFBD><EFBFBD>Ô·ª¥¤¥'éÌ¿º¹º<þáÔÏÎÏQöéäãäf( þùøù{=  <0E>R5(#"#¥gJ=878º|_RMLMÏtgbab䦉|wvwù»žŒŒг¦¡ ¡#åÈ»¶µ¶8úÝÐËÊËMòåàßàb$úõôõw9
ŒN1$¡cF9434x[NIHIË<EFBFBD>pc^]^à¢xsrsõ·š<EFBFBD>ˆˆ
̯¢<EFBFBD>œ<EFBFBD>áÄ·²±²4öÙÌÇÆÇI îáÜÛÜ^ öñðñs5 ˆJ- <EFBFBD>_B50/0²tWJEDEÇl_ZYZÜž<EFBFBD>tonoñ³ƒÈ«ž˜=£
--]]

Binary file not shown.

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- app-allmem.lua : Memory usage tester
-- Authors: 20kdc

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- app-eeprog.lua : Tiny EEPROM flasher
-- Authors: 20kdc

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- app-ghostcall.lua : Who are you gonna call?
-- Authors: 20kdc

View File

@ -0,0 +1,610 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- app-metamachine.lua : Virtual Machine
-- Authors: 20kdc
local loaderPkg, loaderPid, vmName = ...
local icecap = neo.requireAccess("x.neo.pub.base", "fs")
local libVGPU = require("metamachine-vgpu")
local vmBaseCoroutineWrap
local vmComponent, vmComputer, vmOs
local vmEnvironment
local vmSelfdestruct = false
local vmSuperVM = true
local signalStack = {}
local postVMRList = {}
-- File structure:
-- vm-* : Virtual machine configuration
-- vm-
local vmConfiguration = {
-- true : Physical
-- {type, ...} : Virtual
-- NOTE : The following rules are set.
-- k-computer always exists
-- k-gpu always exists
-- k-log always exists
-- k-tmpfs always exists in non-Super VMs
["world"] = {"filesystem", "/", false},
["eeprom"] = {"eeprom", "/confboot.lua", "/confdata.bin", "Configurator", true},
["screen"] = {"screen", "configurator", 50, 15, 8}
}
if vmName then
neo.ensurePathComponent("vm-" .. vmName)
vmSuperVM = false
local f = icecap.open("/vm-" .. vmName, false)
vmConfiguration = require("serial").deserialize(f.read("*a"))
f.close()
if not vmConfiguration then error("The VM configuration was unloadable.") end
vmConfiguration["k-tmpfs"] = {"filesystem", "/vt-" .. vmName .. "/", false}
end
local function clone(t)
if type(t) == "table" then
local b = {}
for k, v in pairs(t) do
b[k] = v
end
return b
end
return t
end
-- by window ID = {address, internal}
local screensInt = {
}
-- by component address = callback
local screensAll = {
}
local tmpAddress = "k-tmpfs"
local passthroughs = {}
local fakeArch = _VERSION
local components = {
["k-computer"] = {
type = "computer",
beep = function ()
end,
start = function ()
return false
end,
stop = function ()
vmSelfdestruct = true
coroutine.yield(0.05)
end,
isRunning = function ()
return true
end,
getProgramLocations = function ()
-- Entries of {"file", "lootdisk"}
return {}
end,
getDeviceInfo = function ()
return {
["k-computer"] = {
["class"] = "system",
["description"] = "Computer",
["product"] = "Freeziflow Liquid-Cooling Unit",
["vendor"] = "KDC Subsystems",
["capacity"] = "10",
["width"] = "",
["clock"] = ""
},
["k-processor"] = {
["class"] = "processor",
["description"] = "CPU",
["product"] = "Celesti4 Quantum Computing System",
["vendor"] = "KDC Subsystems",
["capacity"] = "",
["width"] = "",
["clock"] = "9000"
},
["k-memory"] = {
["class"] = "memory",
["description"] = "Memory bank",
["product"] = "Lun4 Paging Subsystem",
["vendor"] = "KDC Subsystems",
["capacity"] = "",
["width"] = "",
["clock"] = "9000"
},
["k-gpu"] = {
["class"] = "display",
["description"] = "Graphics controller",
["product"] = "Tw1-l GPU Multiplexer",
["vendor"] = "KDC Subsystems",
["capacity"] = "8000",
["width"] = "8",
["clock"] = "9000"
}
}
end,
getArchitectures = function ()
return {fakeArch}
end,
setArchitecture = function (a)
fakeArch = a
end,
getArchitecture = function ()
return fakeArch
end
},
["k-gpu"] = libVGPU.newGPU(screensAll),
["k-log"] = {
type = "ocemu",
log = neo.emergency
}
}
-- Clones of components made on-demand.
local proxies = {}
setmetatable(proxies, {__mode = "v"})
vmComponent = {
list = function (filter, exact)
-- This is an iterator :(
local t = {}
local tk = {}
for k, v in pairs(components) do
local ok = false
if filter then
if v.type == filter or ((not exact) and v.type:match(filter, 1, true)) then
ok = true
end
else
ok = true
end
if ok then
table.insert(t, {k, v.type})
tk[k] = v.type
end
end
setmetatable(tk, {
__call = function ()
local tr1 = table.remove(t, 1)
if not tr1 then return end
return table.unpack(tr1)
end
})
return tk
end,
invoke = function (com, me, ...)
if not components[com] then error("no such component " .. com) end
if not components[com][me] then error("no such method " .. com .. "." .. me) end
return components[com][me](...)
end,
proxy = function (com)
if not components[com] then
return nil, "no such component"
end
local p = proxies[com]
if p then return p end
p = clone(components[com])
p.address = com
p.fields = {}
p.slot = 0
proxies[com] = p
return p
end,
type = function (com)
if not components[com] then
return nil, "no such component"
end
return components[com].type
end,
methods = function (com)
if not components[com] then
return nil, "no such component"
end
local mt = {}
for k, v in pairs(components[com]) do
if type(v) == "function" then
mt[k] = true
end
end
return mt
end,
fields = function (com)
-- This isn't actually supported,
-- because fields are bad-sec nonsense.
-- Luckily, everybody knows this, so nobody uses them.
return {}
end,
doc = function (address, method)
if not components[address] then
error("no such component")
end
if not components[address][method] then
return
end
return tostring(components[address][method])
end
}
-- Prepare configured components
local insertionCallbacks = {
["screen"] = function (address, title, w, h, d)
local activeLines = {}
local scrW = neo.requireAccess("x.neo.pub.window", "primary window")(w, h, title)
local gpuC, scrI, scrC
gpuC, scrI, scrC = libVGPU.newBuffer(scrW, {address .. "-kb"}, w, h, function (nw, nh)
table.insert(signalStack, {"screen_resized", address, nw, nh})
end, function (l)
if activeLines[l] then
return
end
activeLines[l] = true
table.insert(postVMRList, function ()
scrI.line(l)
activeLines = {}
end)
end)
components[address] = scrC
components[address .. "-kb"] = {type = "keyboard"}
screensInt[scrW.id] = {address, scrI}
screensAll[address] = gpuC
end,
["eeprom"] = function (address, boot, data, name, ro)
local codeSize = 4096
local dataSize = 256
local function getCore(fd)
local f = icecap.open(fd, false)
if not f then return "" end
local contents = f.read("*a")
f.close()
return contents
end
local function setCore(fd, size, contents, important)
checkArg(1, contents, "string")
if #contents > size then return nil, "too large" end
if ro and important then
return nil, "storage is readonly"
end
local f = icecap.open(fd, true)
if not f then return nil, "storage is readonly" end
f.write(contents)
f.close()
return true
end
components[address] = {
type = "eeprom",
get = function ()
return getCore(boot)
end,
set = function (contents)
return setCore(boot, codeSize, contents, true)
end,
makeReadonly = function ()
ro = true
return true
end,
getChecksum = function ()
return "00000000"
end,
getSize = function ()
return codeSize
end,
getDataSize = function ()
return dataSize
end,
getData = function ()
return getCore(data)
end,
setData = function (contents)
return setCore(data, dataSize, contents, false)
end
}
end,
["filesystem"] = function (address, path, ro)
components[address] = require("metamachine-vfs")(icecap, address, path, ro)
end
}
for k, v in pairs(vmConfiguration) do
if type(v) == "string" then
local root = neo.requireAccess("k.root", "component passthrough")
local ty = root.component.type(k)
if ty then
passthroughs[k] = true
components[k] = root.component.proxy(k)
if ty == "screen" then
-- Need to ensure the screen in question is for the taking
local div = neo.requireAccess("x.neo.sys.session", "ability to divorce screens")
div.disclaimMonitor(k)
local div2 = neo.requireAccess("x.neo.sys.screens", "ability to claim screens")
screensAll[k] = div2.claim(k)
assert(screensAll[k], "Hardware screen " .. k .. " unavailable.")
end
end
else
assert(insertionCallbacks[v[1]], "Cannot insert virtual " .. v[1])
insertionCallbacks[v[1]](k, table.unpack(v, 2))
end
end
vmOs = clone(os)
vmComputer = {}
vmComputer.shutdown = function (...)
vmSelfdestruct = true
coroutine.yield(0.05)
end
vmComputer.pushSignal = function (...)
table.insert(signalStack, {...})
end
vmComputer.pullSignal = function (time)
if not signalStack[1] then
if type(time) == "number" then
time = time + os.uptime()
coroutine.yield(time)
if not signalStack[1] then
return
end
else
while not signalStack[1] do
coroutine.yield(math.huge)
end
end
end
return table.unpack(table.remove(signalStack, 1))
end
vmComputer.totalMemory = os.totalMemory
vmOs.totalMemory = nil
vmComputer.freeMemory = os.freeMemory
vmOs.freeMemory = nil
vmComputer.energy = os.energy
vmOs.energy = nil
vmComputer.maxEnergy = os.maxEnergy
vmOs.maxEnergy = nil
local startupUptime = os.uptime()
vmComputer.uptime = function ()
return os.uptime() - startupUptime
end
vmOs.uptime = nil
vmComputer.address = os.address
vmOs.address = nil
vmComputer.isRobot = function ()
return false
end
vmComputer.address = function ()
return "k-computer"
end
vmComputer.tmpAddress = function ()
return tmpAddress
end
local eepromAddress = "k-eeprom"
vmComputer.getBootAddress = function ()
return eepromAddress
end
vmComputer.setBootAddress = function (a)
eepromAddress = a
end
vmComputer.users = function ()
return {}
end
vmComputer.addUser = function ()
return false, "user support not available"
end
vmComputer.removeUser = function ()
return false, "user support not available"
end
vmComputer.beep = function (...)
return vmComponent.invoke("k-computer", "beep", ...)
end
vmComputer.getDeviceInfo = function (...)
return vmComponent.invoke("k-computer", "getDeviceInfo", ...)
end
vmComputer.getProgramLocations = function (...)
return vmComponent.invoke("k-computer", "getProgramLocations", ...)
end
vmComputer.getArchitectures = function (...)
return vmComponent.invoke("k-computer", "getArchitectures", ...)
end
vmComputer.getArchitecture = function (...)
return vmComponent.invoke("k-computer", "getArchitecture", ...)
end
vmComputer.setArchitecture = function (...)
return vmComponent.invoke("k-computer", "setArchitecture", ...)
end
vmUnicode = clone(unicode)
vmUnicode.safeTextSupport = nil
vmUnicode.undoSafeTextSupport = nil
vmEnvironment = {
_VERSION = _VERSION,
component = vmComponent,
computer = vmComputer,
table = clone(table),
math = clone(math),
string = clone(string),
unicode = vmUnicode,
-- Scheme here:
-- A yield's first argument is nil for an actual yield,
-- or the time to add a timer at (math.huge if no timeout) for a pullSignal.
-- This is not exactly the same, but is very similar, to that of machine.lua,
-- differing mainly in how pullSignal timeout scheduling occurs.
coroutine = {
-- NOTE: I can't give you a definitive list from OC because OC (or OpenOS?) screws up a metatable!
yield = function (...)
return coroutine.yield(nil, ...)
end,
-- The way this is defined by machine.lua makes it true even when it arguably shouldn't be. Oh well.
isyieldable = coroutine.isyieldable,
status = coroutine.status,
create = function (f)
return coroutine.create(function (...)
return nil, f(...)
end)
end,
running = coroutine.running,
wrap = function (f)
local pf = coroutine.wrap(function (...)
return nil, f(...)
end)
return function (...)
local last = {...}
while true do
local tabpack = {pf(table.unpack(last))}
if not tabpack[1] then
return table.unpack(tabpack, 2)
end
last = {coroutine.yield(tabpack[1])}
end
end
end,
resume = function (co, ...)
local last = {...}
while true do
local tabpack = {coroutine.resume(co, table.unpack(last))}
if not tabpack[1] then
neo.emergency(co, table.unpack(tabpack))
return table.unpack(tabpack)
elseif not tabpack[2] then
return tabpack[1], table.unpack(tabpack, 3)
end
last = {coroutine.yield(tabpack[2])}
end
end
},
os = vmOs,
debug = clone(debug),
bit32 = clone(bit32),
utf8 = clone(utf8),
assert = assert,
ipairs = ipairs,
next = next,
load = function (a, b, c, d)
if rawequal(d, nil) then
d = vmEnvironment
end
return load(a, b, c, d)
end,
pairs = pairs, pcall = function (...)
local r = {pcall(...)}
if not r[1] then
neo.emergency("pcall error:", table.unpack(r, 2))
end
return table.unpack(r)
end,
xpcall = xpcall, select = select,
type = type, error = error,
tonumber = tonumber, tostring = tostring,
setmetatable = setmetatable, getmetatable = getmetatable,
rawset = rawset, rawget = rawget,
rawlen = rawlen, rawequal = rawequal,
checkArg = checkArg
}
vmEnvironment._G = vmEnvironment
if vmSuperVM then
vmEnvironment._MMstartVM = function (vmName)
neo.executeAsync("app-metamachine", vmName)
end
vmEnvironment._MMserial = function (...)
return require("serial").serialize(...)
end
vmEnvironment._MMdeserial = function (...)
return require("serial").deserialize(...)
end
vmEnvironment.print = neo.emergency
local root = neo.requestAccess("k.root")
if root then
vmEnvironment._MMcomList = root.component.list
else
vmEnvironment._MMcomList = function ()
return function ()
end
end
end
end
-- bootstrap
vmBaseCoroutineWrap = coroutine.wrap(function ()
vmBaseCoroutine = coroutine.running()
eepromAddress = vmComponent.list("eeprom")()
if not eepromAddress then
error("No EEPROM")
end
local code = vmComponent.invoke(eepromAddress, "get")
local res, f = load(code, "=eeprom", "t", vmEnvironment)
if not res then
error(f)
else
res()
end
end)
while ((not vmBaseCoroutine) or (coroutine.status(vmBaseCoroutine) ~= "dead")) and not vmSelfdestruct do
local details = {vmBaseCoroutineWrap()}
while postVMRList[1] do
table.remove(postVMRList, 1)()
end
if details[1] then
local checkTimer = nil
if details[1] ~= math.huge then
checkTimer = neo.scheduleTimer(details[1])
--neo.emergency("metamachine timer " .. details[1])
else
--neo.emergency("metamachine HANG")
end
while true do
local ev = {coroutine.yield()}
if ev[1] == "k.timer" then
if ev[2] == checkTimer then
break
end
elseif ev[1]:sub(1, 2) == "h." then
if passthroughs[ev[2]] then
ev[1] = ev[1]:sub(3)
table.insert(signalStack, ev)
break
end
elseif ev[1] == "x.neo.pub.window" then
local id = ev[2]
if ev[3] == "key" then
if ev[6] then
table.insert(signalStack, {"key_down", screensInt[id][1] .. "-kb", ev[4], ev[5], "neo"})
else
table.insert(signalStack, {"key_up", screensInt[id][1] .. "-kb", ev[4], ev[5], "neo"})
end
break
elseif ev[3] == "line" then
screensInt[id][2].line(ev[4])
elseif ev[3] == "clipboard" then
table.insert(signalStack, {ev[3], screensInt[id][1] .. "-kb", ev[4], "neo"})
break
elseif ev[3] == "touch" or ev[3] == "drag" or ev[3] == "drop" or ev[3] == "scroll" then
local x = ev[4]
local y = ev[5]
if screensInt[id][2].precise then
x = (x - 1) + ev[6]
y = (y - 1) + ev[7]
end
table.insert(signalStack, {ev[3], screensInt[id][1], x, y, ev[8], "neo"})
break
elseif ev[3] == "close" then
return
end
end
end
else
error("Yield in root coroutine")
end
end

View File

@ -0,0 +1,353 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- app-nbcompose.lua : Music!
-- Authors: 20kdc
local nb = neo.requireAccess("c.iron_noteblock", "noteblocks").list()()
local ic = neo.requireAccess("x.neo.pub.base", "fs")
local event = require("event")(neo)
local neoux = require("neoux")(event, neo)
local iTranslation = {
[0] = 0, -- piano / air (def)
4, -- double bass / wood (def)
1, -- bass drum / stone (def)
2, -- snare drum / sand (def)
3, -- click / glass (def)
-- JUST GIVE UP
4, -- guitar / wool
5, -- flute / clay
6, -- bell / gold
6, -- chime / pice
6, -- xylo / bone
}
local instKey = {
[2] = 0,
[3] = 1,
[4] = 2,
[5] = 3,
[6] = 4,
[144] = 5,
[7] = 5,
[8] = 6,
[9] = 7,
[10] = 8,
[11] = 9
}
local noteKey = "1q2w3er5t6yu8i9o0pzsxdcvg"
-- Application State
local fileData
local uptime = os.uptime()
local songPosition = 0
local selectionL, selectionR = -8, -9
local running = true
local playing = false
local timerExistsFlag = false
local window
local defInst = 0
--
local tick -- Tick function for timer making
local file = require("knbs").new()
-- Window width is always 50. Height is layers + 3, for the top bar.
local theStatusBar, theNotePane, genMain
local function updateStatusAndPane()
if theStatusBar.update then theStatusBar.update(window) end
if theNotePane then
for _, v in ipairs(theNotePane) do
v.update(window)
end
end
end
local function commonKey(a, c, f)
if a == 32 then
playing = not playing
theStatusBar.update(window)
if playing then
if not timerExistsFlag then
uptime = os.uptime()
event.runAt(uptime, tick)
timerExistsFlag = true
end
end
elseif a == 91 then
selectionL = songPosition
updateStatusAndPane()
elseif a == 93 then
selectionR = songPosition
updateStatusAndPane()
elseif c == 203 and (f.shift or f.rshift) then
songPosition = 0
updateStatusAndPane()
elseif c == 205 and (f.shift or f.rshift) then
songPosition = file.length
updateStatusAndPane()
elseif c == 203 then
songPosition = math.max(0, songPosition - 1)
updateStatusAndPane()
elseif c == 205 then
songPosition = songPosition + 1
updateStatusAndPane()
end
end
theStatusBar = {
x = 1,
y = 3,
w = 50,
h = 1,
selectable = true,
line = function (window, x, y, lined, bg, fg, selected)
if selected then
bg, fg = fg, bg
end
window.span(x, y, ((playing and "Playing") or "Paused") .. " (SPACE) ; " .. (songPosition + 1) .. "/" .. file.length .. " ([Shift-]←/→)", bg, fg)
end,
key = function (window, update, a, c, d, f)
if not d then return end
commonKey(a, c, f)
end
}
local function genLayers()
theStatusBar.update = nil
theNotePane = nil
local layers = {}
for i = 1, file.height do
local layer = i - 1
table.insert(layers, neoux.tcfield(1, i + 1, 40, function (tx)
file.layers[layer][1] = tx or file.layers[layer][1]
return file.layers[layer][1]
end))
table.insert(layers, neoux.tcrawview(42, i + 1, {"Vol."}))
table.insert(layers, neoux.tcfield(46, i + 1, 5, function (tx)
if tx then
file.layers[layer][2] = math.max(0, math.min(255, math.floor(tonumber(tx) or 0)))
end
return tostring(file.layers[layer][2])
end))
end
return 50, file.height + 1, nil, neoux.tcwindow(50, file.height + 1, {
neoux.tcbutton(1, 1, "Purge Extra Layers", function (w)
local knbs = require("knbs")
local layerCount = knbs.correctSongLH(file)
knbs.resizeLayers(file, layerCount)
w.reset(genLayers())
end),
neoux.tcbutton(21, 1, "Del.Last", function (w)
require("knbs").resizeLayers(file, file.height - 1)
w.reset(genLayers())
end),
neoux.tcbutton(31, 1, "Append", function (w)
require("knbs").resizeLayers(file, file.height + 1)
w.reset(genLayers())
end),
table.unpack(layers)
}, function (w)
w.reset(genMain())
end, 0xFFFFFF, 0)
end
function genMain()
theNotePane = {}
for l = 1, file.height do
local layer = l - 1
theNotePane[l] = {
x = 1,
y = 3 + l,
w = 50,
h = 1,
selectable = true,
line = function (window, x, y, lined, bg, fg, selected)
if selected then
bg, fg = fg, bg
end
local text = ""
for i = 1, 5 do
local noteL, noteR = " ", " "
local tick = songPosition + i - 3
if songPosition == tick then
noteL = "["
noteR = "]"
end
if selectionR >= selectionL then
if selectionL == tick then
noteL = "{"
end
if selectionR == tick then
noteR = "}"
end
end
text = text .. noteL
local fd = file.ticks[tick]
fd = fd and fd[layer]
if fd then
text = text .. string.format(" %02i/%02i", fd[1], fd[2])
else
text = text .. " "
end
text = text .. noteR
end
window.span(x, y, text, bg, fg)
end,
key = function (window, update, a, c, d, f)
if not d then return end
commonKey(a, c, f)
if a == 8 then
if file.ticks[songPosition] then
file.ticks[songPosition][layer] = nil
require("knbs").correctSongLH(file)
update()
theStatusBar.update(window)
end
elseif instKey[c] and (f.shift or f.rshift) then
file.ticks[songPosition] = file.ticks[songPosition] or {}
defInst = instKey[c]
local nt = 45
if file.ticks[songPosition][layer] then
file.ticks[songPosition][layer][1] = defInst
nt = file.ticks[songPosition][layer][2]
end
if nb then
nb.playNote(iTranslation[defInst] or 0, nt - 33, file.layers[layer][2] / 100)
end
require("knbs").correctSongLH(file)
update()
theStatusBar.update(window)
elseif a >= 0 and a < 256 and noteKey:find(string.char(a), 1, true) then
file.ticks[songPosition] = file.ticks[songPosition] or {}
local note = noteKey:find(string.char(a), 1, true) - 1
file.ticks[songPosition][layer] = {defInst, note + 33}
if nb then
nb.playNote(iTranslation[defInst] or 0, note, file.layers[layer][2] / 100)
end
require("knbs").correctSongLH(file)
update()
theStatusBar.update(window)
elseif a == 123 then
if selectionR >= selectionL then
local storage = {}
for i = selectionL, selectionR do
storage[i] = file.ticks[i] and file.ticks[i][layer] and {table.unpack(file.ticks[i][layer])}
end
for i = selectionL, selectionR do
local p = songPosition + (i - selectionL)
file.ticks[p] = file.ticks[p] or {}
file.ticks[p][layer] = storage[i]
end
require("knbs").correctSongLH(file)
update()
theStatusBar.update(window)
end
end
end
}
end
-- We totally lie about the height here to tcwindow. "Bit of a cheat, but who's counting", anyone?
-- It is explicitly documented that the width and height are for background drawing, BTW.
return 50, file.height + 3, nil, neoux.tcwindow(50, 3, {
neoux.tcfield(1, 1, 20, function (tx)
file.name = tx or file.name
return file.name
end),
neoux.tcfield(21, 1, 15, function (tx)
file.transcriptor = tx or file.transcriptor
return file.transcriptor
end),
neoux.tcfield(36, 1, 15, function (tx)
file.songwriter = tx or file.songwriter
return file.songwriter
end),
neoux.tcbutton(1, 2, "New", function (w)
file = require("knbs").new()
songPosition = 0
playing = false
window.reset(genMain())
end),
neoux.tcbutton(6, 2, "Load", function (w)
neoux.fileDialog(false, function (f)
if not f then return end
file = nil
file = require("knbs").deserialize(f.read("*a"))
f.close()
songPosition = 0
playing = false
window.reset(genMain())
end)
end),
neoux.tcbutton(12, 2, "Save", function (w)
neoux.fileDialog(true, function (f)
if not f then return end
require("knbs").serialize(file, f.write)
f.close()
end)
end),
neoux.tcbutton(18, 2, "Ds.L", function (w)
neoux.fileDialog(false, function (f)
if not f then return end
file.description = f.read("*a")
f.close()
end)
end),
neoux.tcbutton(24, 2, "Ds.S", function (w)
neoux.fileDialog(true, function (f)
if not f then return end
f.write(file.description)
f.close()
end)
end),
neoux.tcbutton(30, 2, "Layers", function (w)
window.reset(genLayers())
end),
neoux.tcrawview(39, 2, {"cT/S"}),
neoux.tcfield(43, 2, 8, function (tx)
if tx then
local txn = tonumber(tx) or 0
file.tempo = math.min(math.max(0, math.floor(txn)), 65535)
end
return tostring(file.tempo)
end),
theStatusBar,
table.unpack(theNotePane)
}, function (w)
w.close()
running = false
end, 0xFFFFFF, 0)
end
function tick()
if playing then
-- Stop the user from entering such a low tempo that stuff freezes by:
-- 1. Stopping tempo from going too low to cause /0
-- 2. Ensuring timer is at most 1 second away
local temp = 1 / math.max(file.tempo / 100, 0.01)
if os.uptime() >= uptime + temp then
-- execute at this song position
if file.ticks[songPosition] and nb then
for i = 0, file.height - 1 do
local tck = file.ticks[songPosition][i]
if tck then
nb.playNote(iTranslation[tck[1]] or 0, tck[2] - 33, file.layers[i][2] / 100)
end
end
end
songPosition = songPosition + 1
if songPosition >= file.length then songPosition = 0 end
updateStatusAndPane()
uptime = uptime + temp
end
event.runAt(math.min(os.uptime() + 1, uptime + temp), tick)
else
timerExistsFlag = false
end
end
window = neoux.create(genMain())
while running do event.pull() end

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- app-nbox2018.lua : NODEBOX 2018
-- Authors: 20kdc

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- app-nprt2018.lua : 3D printing application
-- Authors: 20kdc

View File

@ -0,0 +1,41 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- app-pclogix-upload: Upload to PCLogix Hastebin (paste.pc-logix.com)
local inet = neo.requireAccess("c.internet", "to upload").list()()
assert(inet, "no internet card")
local event = require("event")(neo)
local neoux = require("neoux")(event, neo)
local f = neoux.fileDialog(false)
if not f then return end
local data = f.read("*a")
f.close()
local s = inet.request("http://paste.pc-logix.com/documents", data)
assert(s, "no socket")
s.finishConnect()
local code, msg = s.response()
local res = tostring(code) .. " " .. tostring(msg) .. "\n"
while true do
local chk, err = s.read(neo.readBufSize)
if not chk then
res = res .. "\n" .. tostring(err)
break
end
if chk == "" then
event.sleepTo(os.uptime() + 0.05)
else
res = res .. chk
end
end
res = res .. "\nPrefix ID with http://paste.pc-logix.com/raw/"
neoux.startDialog(res, "result", true)
s.close()
-- that's a hastebin client for OC!
-- done! now I'll upload this with itself.
-- - 20kdc

View File

@ -0,0 +1,66 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- app-rsctrl: redstone control
local event = require("event")(neo)
local neoux = require("neoux")(event, neo)
local rs = neo.requireAccess("c.redstone", "redstone control")
neo.requireAccess("s.h.redstone_changed", "updating to new input")
local running = true
local window
--123456789012345678901234567890123456789012345
--PPPPPPPP-PPPP-PPPP-PPPPPPPPPPPP Wake=[x]
-- D=y[x] U=y[x] N=y[x] S=y[x] W=y[x] E=y[x]
local function mainGen()
local ctrls = {}
local prs = 0
for pri in rs.list() do
prs = prs + 1
local ins = {}
local outs = {}
local wt = pri.getWakeThreshold()
for i = 0, 5 do
ins[i + 1] = pri.getInput(i)
outs[i + 1] = pri.getOutput(i)
end
table.insert(ctrls, neoux.tcrawview(1, (prs * 2) - 1, {
unicode.safeTextFormat(pri.address),
string.format(" D=%01x U=%01x N=%01x S=%01x W=%01x E=%01x", table.unpack(ins))
}))
table.insert(ctrls, neoux.tcrawview(38, (prs * 2) - 1, {
"Wake="
}))
table.insert(ctrls, neoux.tcfield(43, (prs * 2) - 1, 3, function (tx)
if tx then
wt = math.floor(tonumber("0x" .. tx:sub(-1)) or 0)
pri.setWakeThreshold(wt)
end
return string.format("%01x", wt)
end))
for i = 0, 5 do
table.insert(ctrls, neoux.tcfield(6 + (i * 7), prs * 2, 3, function (tx)
if tx then
outs[i + 1] = tonumber("0x" .. tx:sub(-1)) or 0
pri.setOutput(i, outs[i + 1])
end
return string.format("%01x", outs[i + 1])
end))
end
end
return 45, prs * 2, nil, neoux.tcwindow(45, prs * 2, ctrls, function ()
window.close()
running = false
end, 0xFFFFFF, 0)
end
window = neoux.create(mainGen())
while running do
local hv = event.pull()
if hv == "redstone_changed" then
window.reset(mainGen())
end
end

View File

@ -0,0 +1,367 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- app-tapedeck.lua : Computronics Tape interface.
-- Added note: Computerized record discs aren't available, so it can't be called vinylscratch.
-- Authors: 20kdc
local tapeRate = 4096
local tapeAccess = neo.requireAccess("c.tape_drive", "tapedrives")
local event = require("event")(neo)
local neoux = require("neoux")(event, neo)
-- There's no way to get these, so they get reset
local pcvals = {vol = 100, spd = 100}
local function pcbox(x, y, low, high, id, fun)
return neoux.tcfield(x, y, 5, function (tx)
if tx then
pcvals[id] = math.min(math.max(0, math.floor(tonumber(tx) or 0)), high)
fun(math.max(pcvals[id], low) / 100)
end
return tostring(pcvals[id])
end)
end
local window
local running = true
local focused = true
local updateTick
local downloadCancelled = false
local genPlayer, genList -- used to return to player
local function genDownloading(downloadText, inst)
downloadCancelled = false
local lclLabelText = {downloadText}
local lclLabel = neoux.tcrawview(1, 1, lclLabelText)
local thr = {
"/",
"-",
"\\",
"|"
}
local thri = 0
updateTick = function ()
lclLabelText[1] = downloadText .. " " .. (inst.getPosition() / (1024 * 1024)) .. "MB " .. thr[(thri % #thr) + 1]
thri = thri + 1
lclLabel.update(window)
end
return 40, 1, nil, neoux.tcwindow(40, 1, {
lclLabel
}, function (w)
downloadCancelled = true
end, 0xFFFFFF, 0)
end
local function maybeSleep()
if math.random() > 0.98 then
event.sleepTo(os.uptime() + 0.05)
end
end
local function doINetThing(inet, url, inst)
inet = inet.list()()
assert(inet, "No available card")
inst.stop()
inst.seek(-inst.getSize())
window.reset(genDownloading("downloading...", inst))
local req = assert(inet.request(url))
req.finishConnect()
local tapePos = 0
local tapeSize = inst.getSize()
while (not downloadCancelled) and tapePos < tapeSize do
local n, n2 = req.read(neo.readBufSize)
if not n then
if n2 then
req.close()
error(n2)
end
break
elseif n == "" then
event.sleepTo(os.uptime() + 0.05)
else
inst.write(n)
tapePos = tapePos + #n
end
end
req.close()
inst.seek(-inst.getSize())
end
local function genWeb(inst)
updateTick = nil
local url = ""
local lockout = false
return 40, 3, nil, neoux.tcwindow(40, 3, {
neoux.tcrawview(1, 1, {"URL to write to tape?"}),
neoux.tcfield(1, 2, 40, function (t)
url = t or url
return url
end),
neoux.tcbutton(1, 3, "Download & Write", function (w)
lockout = true
local inet = neo.requestAccess("c.internet")
lockout = false
if inet then
local ok, err = pcall(doINetThing, inet, url, inst)
if not ok then
neoux.startDialog("Couldn't download: " .. tostring(err), "error")
end
end
w.reset(genPlayer(inst))
end)
}, function (w)
w.reset(genPlayer(inst))
end, 0xFFFFFF, 0)
end
-- The actual main UI --
genPlayer = function (inst)
local cachedLabel = inst.getLabel() or ""
local cachedState = inst.getState()
local function pausePlay()
if inst.getState() == "PLAYING" then
inst.stop()
else
inst.play()
end
window.reset(genPlayer(inst))
end
-- Common code for reading/writing tapes.
local function rwButton(mode)
local fh = neoux.fileDialog(mode)
if not fh then return end
inst.stop()
local tapeSize = inst.getSize()
inst.seek(-tapeSize)
local tapePos = 0
window.reset(genDownloading("working...", inst))
while tapePos < tapeSize and not downloadCancelled do
if mode then
local data = inst.read(neo.readBufSize)
if not data then break end
tapePos = tapePos + #data
local res, ifo = fh.write(data)
if not res then
neoux.startDialog(tostring(ifo), "issue")
break
end
else
local data = fh.read(neo.readBufSize)
if not data then break end
tapePos = tapePos + #data
inst.write(data)
end
maybeSleep()
end
inst.seek(-tapeSize)
fh.close()
window.reset(genPlayer(inst))
end
local elems = {
neoux.tcrawview(1, 1, {
"Label:"
}),
neoux.tcfield(7, 1, 34, function (tx)
if tx then
inst.setLabel(tx)
cachedLabel = tx
end
return cachedLabel
end),
{
x = 1,
y = 5,
w = 40,
h = 1,
selectable = true,
line = function (w, x, y, lined, bg, fg, selected)
local lx = ""
local pos = inst.getPosition()
local sz = inst.getSize()
if inst.isReady() then
-- Show a bar
local tick = sz / 23
for i = 1, 23 do
local alpos = (tick * i) - (tick / 2)
if pos > alpos then
lx = lx .. "="
else
lx = lx .. "-"
end
end
else
lx = "NO TAPE HERE."
end
local sec = pos / tapeRate
local secz = sz / tapeRate
lx = lx .. string.format(" %03i:%02i / %03i:%02i ",
math.floor(sec / 60), math.floor(sec) % 60,
math.floor(secz / 60), math.floor(secz) % 60)
if selected then bg, fg = fg, bg end
window.span(x, y, lx, bg, fg)
end,
key = function (w, update, a, b, c, kf)
local amount = tapeRate * 10
if kf.shift or kf.rshift then
amount = amount * 24
end
if c then
if a == 32 then
pausePlay()
elseif b == 203 then
inst.seek(-amount)
update()
return true
elseif b == 205 then
inst.seek(amount)
update()
return true
end
end
end
},
neoux.tcrawview(33, 3, {
"% Volume"
}),
neoux.tcrawview(20, 3, {
"% Speed"
}),
pcbox(15, 3, 25, 200, "spd", inst.setSpeed),
pcbox(28, 3, 0, 100, "vol", inst.setVolume),
neoux.tcrawview(1, 4, {
"Seeker: use ◃/▹ (shift goes faster)"
}),
neoux.tcbutton(1, 3, "«", function (w)
inst.seek(-inst.getSize())
end),
neoux.tcbutton(11, 3, "»", function (w)
inst.seek(inst.getSize())
end),
neoux.tcbutton(4, 3, ((inst.getState() == "PLAYING") and "Pause") or "Play", function (w)
pausePlay()
end),
-- R/W buttons
neoux.tcbutton(1, 2, "Read", function (w)
rwButton(true)
end),
neoux.tcbutton(7, 2, "Write", function (w)
rwButton(false)
end),
neoux.tcbutton(14, 2, "Write Web", function (w)
w.reset(genWeb(inst))
end),
neoux.tcbutton(25, 2, "Copy", function (w)
w.reset(genList(function (inst2)
local ts1 = inst.getSize()
inst.stop()
inst.seek(-ts1)
local ts2 = inst2.getSize()
inst2.stop()
inst2.seek(-ts2)
if ts1 < ts2 then
w.reset(genDownloading("copying...", inst))
else
w.reset(genDownloading("copying...", inst2))
end
local pos = 0
while pos < ts1 and pos < ts2 and not downloadCancelled do
local dat = inst.read(neo.readBufSize)
inst2.write(dat)
pos = pos + #dat
maybeSleep()
end
inst.seek(-ts1)
inst2.seek(-ts2)
inst2.setLabel((inst.getLabel() or "") .. " Copy")
w.reset(genPlayer(inst))
end))
end),
neoux.tcbutton(31, 2, "Erase", function (w)
local ts1 = inst.getSize()
inst.stop()
inst.seek(-ts1)
w.reset(genDownloading("erasing...", inst))
local blank = ("\x00"):rep(neo.readBufSize)
local pos = 0
while pos < ts1 and not downloadCancelled do
inst.write(blank)
pos = pos + #blank
maybeSleep()
end
inst.seek(-ts1)
w.reset(genPlayer(inst))
end)
}
updateTick = function ()
local lcl = cachedLabel
cachedLabel = inst.getLabel() or ""
elems[3].update(window)
if inst.getState() ~= cachedState then
window.reset(genPlayer(inst))
elseif lcl ~= cachedLabel then
elems[2].update(window)
end
end
local n = neoux.tcwindow(40, 5, elems, function (w)
updateTick = nil
running = false
w.close()
end, 0xFFFFFF, 0)
return 40, 5, inst.address, function (a, ...)
if a == "focus" then
focused = (...) or true
end
return n(a, ...)
end
end
genList = function(callback)
updateTick = nil
local elems = {}
local tapes = {}
for v in tapeAccess.list() do
table.insert(tapes, v)
end
for k, v in ipairs(tapes) do
-- There's 38 chars available...
local desc1 = neoux.pad(v.address, 13, false, true)
if v.isReady() then
desc1 = desc1 .. ": " .. neoux.pad(v.getLabel() or "", 23, false, true)
else
desc1 = desc1 .. " (no tape)"
end
elems[k] = neoux.tcbutton(1, k, desc1, function (w)
callback(v)
end)
end
return 40, #elems, nil, neoux.tcwindow(40, #elems, elems, function (w)
running = false
w.close()
end, 0xFFFFFF, 0)
end
window = neoux.create(genList(function (v)
window.reset(genPlayer(v))
end))
-- Timer for time update
local function tick()
if updateTick then
updateTick()
end
event.runAt(os.uptime() + ((focused and 1) or 10), tick)
end
event.runAt(0, tick)
while running do
event.pull()
end

View File

@ -0,0 +1,100 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- app-telnet.lua : just a utility now
-- Authors: 20kdc
local inet = neo.requireAccess("c.internet", "internet").list()()
local _, _, termId = ...
local ok = pcall(function ()
assert(string.sub(termId, 1, 12) == "x.neo.pub.t/")
end)
local termClose
if not ok then
termId = nil
assert(neo.executeAsync("svc-t", function (res)
termId = res.access
termClose = res.close
neo.scheduleTimer(0)
end, "kmt"))
while true do
if coroutine.yield() == "k.timer" then break end
end
end
local term = neo.requireAccess(termId, "terminal")
term.write([[
KittenOS NEO MUD Terminal
export TERM=ansi.sys <- IMPORTANT!!!
Enter target server:port...
]])
local targetBuffer = ""
neo.scheduleTimer(0)
while true do
local e = {coroutine.yield()}
if e[1] == "k.timer" then
while tcp do
local b, e = tcp.read(neo.readBufSize)
if not b then
if e then
term.write("\nkmt: " .. tostring(e) .. "\n")
tcp.close()
tcp = nil
end
elseif b == "" then
break
else
term.write(b)
end
end
neo.scheduleTimer(os.uptime() + 0.049)
elseif e[1] == "k.procdie" then
if e[3] == term.pid then
break
end
elseif e[1] == termId then
if targetBuffer and e[2] == "data" then
targetBuffer = targetBuffer .. e[3]:gsub("\r", "")
local p = targetBuffer:find("\n")
if p then
local ok, res, rer = pcall(inet.connect, targetBuffer:sub(1, p - 1))
targetBuffer = targetBuffer:sub(p + 1):gsub("\n", "\r\n")
if not ok then
-- Likes to return this kind
term.write("kmt: " .. tostring(res) .. "\n")
elseif not res then
-- Could theoretically return this kind
term.write("kmt: " .. tostring(rer) .. "\n")
else
-- Hopefully this kind
term.write("kmt: Connecting...\n")
tcp = res
tcp.write(targetBuffer)
targetBuffer = nil
end
end
elseif tcp and e[2] == "data" or e[2] == "telnet" then
tcp.write(e[3])
end
end
end
if tcp then
tcp.close()
end

View File

@ -0,0 +1,53 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- svc-vdrslamp.lua : Virtual Redstone Lamp
-- Authors: 20kdc
local vdev = neo.requireAccess("x.svc.virtudev", "lamp dev")
local evr = neo.requireAccess("x.neo.pub.window", "the lamp")
local wnd = evr(12, 6)
local bLine = (" "):rep(12)
local function blank()
return 0
end
local total = 0
vdev.install({
type = "redstone",
address = "vdrslamp-" .. neo.pid,
slot = 0,
getWakeThreshold = blank,
setWakeThreshold = blank,
getInput = blank,
getOutput = function (i)
return total
end,
setOutput = function (i, v)
total = v
wnd.setSize(12, 6)
end
})
while true do
local e = {coroutine.yield()}
if e[1] == "x.neo.pub.window" then
if e[3] == "close" then
-- the ignorance of unregistration is deliberate
-- a working impl. will properly recover
return
elseif e[3] == "line" then
local bg = 0xFFFFFF
if total == 0 then bg = 0 end
wnd.span(1, e[4], bLine, bg, bg)
end
end
end

View File

@ -1,5 +1,9 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- svc-ghostie.lua : Ghostie the test ghost!
-- Authors: 20kdc

View File

@ -0,0 +1,160 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- svc-virtudev.lua : Virtual Device interface
-- Authors: 20kdc
local ic = neo.requireAccess("x.neo.pub.base", "to lock x.svc.virtudev")
-- this is a pretty powerful permission, and PROBABLY EXPLOITABLE
ic.lockPerm("x.svc.virtudev")
local r = neo.requireAccess("r.svc.virtudev", "api endpoint")
local root = neo.requireAccess("k.root", "the ability to modify the component API")
local proxies = {}
local types = {}
local function uninstall(k)
proxies[k] = nil
types[k] = nil
root.computer.pushSignal("component_removed", k, types[k])
end
local users = {}
local userCount = 0
r(function (pkg, pid, sendSig)
local userAddresses = {}
users[pid] = userAddresses
userCount = userCount + 1
return {
install = function (proxy)
local proxyAddress = proxy.address
local proxyType = proxy.type
assert(type(proxyAddress) == "string")
assert(type(proxyType) == "string")
assert(not proxies[proxyAddress], "proxy address in use: " .. proxyAddress)
proxies[proxyAddress] = proxy
types[proxyAddress] = proxyType
userAddresses[proxyAddress] = true
root.computer.pushSignal("component_added", proxyAddress, proxyType)
return function (a, ...)
root.computer.pushSignal(a, proxyAddress, ...)
end
end,
uninstall = function (k)
if userAddresses[k] then
uninstall(k)
userAddresses[k] = nil
end
end
}
end)
local componentProxyRaw = root.component.proxy
local componentTypeRaw = root.component.type
local componentMethodsRaw = root.component.methods
local componentFieldsRaw = root.component.fields
local componentDocRaw = root.component.doc
local componentInvokeRaw = root.component.invoke
local componentListRaw = root.component.list
root.component.proxy = function (address)
if proxies[address] then
return proxies[address]
end
return componentProxyRaw(address)
end
root.component.type = function (address)
if types[address] then
return types[address]
end
return componentTypeRaw(address)
end
local function methodsFieldsHandler(address, methods)
if proxies[address] then
local mt = {}
for k, v in pairs(proxies[address]) do
if (type(v) == "function") == methods then
mt[k] = true
end
end
return mt
end
if methods then
return componentMethodsRaw(address)
else
return componentFieldsRaw(address)
end
end
root.component.methods = function (address) return methodsFieldsHandler(address, true) end
root.component.fields = function (address) return methodsFieldsHandler(address, false) end
root.component.doc = function (address, method)
if proxies[address] then
return tostring(proxies[address][method])
end
return componentDocRaw(address, method)
end
root.component.invoke = function (address, method, ...)
if proxies[address] then
return proxies[address][method](...)
end
return componentInvokeRaw(address, method, ...)
end
root.component.list = function (f, e)
local iter = componentListRaw(f, e)
local ended = false
local others = {}
for k, v in pairs(types) do
if (f == v) or ((not e) and v:find(f, 1, true)) then
table.insert(others, {k, v})
end
end
return function ()
if not ended then
local a, t = iter()
if not a then
ended = true
else
return a, t
end
end
-- at end of that, so what about others
local ent = table.remove(others, 1)
if ent then
return table.unpack(ent)
end
end
end
while true do
local e1, e2, e3 = coroutine.yield()
if e1 == "k.procdie" then
if users[e3] then
for k, _ in pairs(users[e3]) do
uninstall(k)
end
users[e3] = nil
userCount = userCount - 1
if userCount == 0 then
break
end
end
end
end
root.component.proxy = componentProxyRaw
root.component.type = componentTypeRaw
root.component.methods = componentMethodsRaw
root.component.fields = componentFieldsRaw
root.component.doc = componentDocRaw
root.component.invoke = componentInvokeRaw
root.component.list = componentListRaw

View File

@ -1,157 +0,0 @@
-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- local.lua : CLAW Repository Metadata
-- Authors: 20kdc
return {
["app-eeprog"] = {
desc = "Example program: EEPROM programmer / copier",
v = 0,
deps = {
"neo",
"zzz-license-pd"
},
dirs = {
"apps",
"docs",
"docs/repoauthors"
},
files = {
"apps/app-eeprog.lua",
"docs/repoauthors/app-eeprog"
},
},
["neo-docs"] = {
desc = "KittenOS NEO system documentation",
v = 2,
deps = {
"zzz-license-pd"
},
dirs = {
"docs",
"docs/repoauthors"
},
files = {
"docs/an-intro",
"docs/kn-intro",
"docs/kn-refer",
"docs/kn-sched",
"docs/kn-perms",
"docs/us-perms",
"docs/us-nxapp",
"docs/us-setti",
"docs/us-evrst",
"docs/ul-seria",
"docs/ul-fwrap",
"docs/ul-event",
"docs/ul-fmttx",
"docs/ul-neoux",
"docs/ul-brail",
"docs/ul-bmp__",
"docs/gp-pedan",
"docs/repoauthors/neo-docs"
},
},
["app-nbox2018"] = {
desc = "NBOX2018 and NPRT2018, a 3D-printing toolbox",
v = 0,
deps = {
"neo",
"zzz-license-pd"
},
dirs = {
"apps",
"docs",
"docs/repoauthors"
},
files = {
"apps/app-nbox2018.lua",
"apps/app-nprt2018.lua",
"docs/repoauthors/app-nbox2018"
},
},
["app-allmem"] = {
desc = "Near-reproducible memory usage figures",
v = 0,
deps = {
"neo",
"zzz-license-pd"
},
dirs = {
"apps",
"docs",
"docs/repoauthors"
},
files = {
"apps/app-allmem.lua",
"docs/repoauthors/app-allmem"
},
},
["svc-ghostie"] = {
desc = "Application that schedules a scare after a random time to test svc autostart",
v = 0,
deps = {
"neo",
"zzz-license-pd"
},
dirs = {
"apps",
"docs",
"docs/repoauthors"
},
files = {
"apps/svc-ghostie.lua",
"apps/app-ghostcall.lua",
"docs/repoauthors/svc-ghostie"
},
},
["app-launchbar"] = {
desc = "Application launcher bar",
v = 0,
deps = {
"neo",
"zzz-license-pd"
},
dirs = {
"apps",
"docs/repoauthors"
},
files = {
"apps/app-launchbar.lua",
"docs/repoauthors/app-launchbar"
},
},
["app-slaunch"] = {
desc = "Searching launcher",
v = 0,
deps = {
"neo",
"zzz-license-pd"
},
dirs = {
"apps",
"docs/repoauthors"
},
files = {
"apps/app-slaunch.lua",
"docs/repoauthors/app-slaunch"
},
},
-- licenses (MUST BE IMMUTABLE)
["zzz-license-pd"] = {
desc = "license file 'Public Domain'",
v = 0,
deps = {
},
dirs = {
"docs",
"docs/licensing",
"docs/repoauthors"
},
files = {
"docs/licensing/Public Domain",
"docs/repoauthors/zzz-license-pd"
},
}
}

View File

@ -0,0 +1,265 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- confboot.lua : VM configuration program
-- Authors: 20kdc
-- _MMstartVM(name)
-- _MMcomList(...)
-- _MMserial(str)
-- _MMdeserial(str)
local screen = component.proxy(component.list("screen", true)())
local gpu = component.proxy(component.list("gpu", true)())
local fs = component.proxy("world")
screen.turnOn()
gpu.bind(screen.address)
gpu.setResolution(50, 15)
gpu.setForeground(0)
gpu.setBackground(0xFFFFFF)
local menu
local currentY
local currentVMId
local currentVM
local genMainMenu, genFSSelector, genEditor
function genFSSelector(cb)
local fsName = ""
menu = {
{" - Select VFS -", function () end},
{"Cancel", cb},
{"New FS: ", function ()
fs.makeDirectory("fs-" .. fsName)
genFSSelector(cb)
end, function (text)
if text then fsName = text end
return fsName
end}
}
currentY = 2
local fsl = fs.list("")
table.sort(fsl)
for k, v in ipairs(fsl) do
if v:sub(#v) == "/" and v:sub(1, 3) == "fs-" then
local id = v:sub(4, #v - 1)
table.insert(menu, {id, function ()
cb(id)
end})
table.insert(menu, {" Delete", function ()
fs.remove("fs-" .. id)
genFSSelector(cb)
end})
end
end
end
local function doVMSave()
local f = fs.open("vm-" .. currentVMId, "wb")
if not f then error("VM Save failed...") end
fs.write(f, _MMserial(currentVM))
fs.close(f)
end
function genEditor()
menu = {
--01234567890123456789012345678901234567890123456789
{" - configuring VM: " .. currentVMId, function () end},
{"Save & Return", function ()
doVMSave()
currentVM, currentVMId = nil
genMainMenu()
end},
{"Save & Launch", function ()
doVMSave()
_MMstartVM(currentVMId)
computer.shutdown()
end},
{"Delete", function ()
fs.remove("vm-" .. currentVMId)
currentVM, currentVMId = nil
genMainMenu()
end},
}
currentY = 3
for k, v in pairs(currentVM) do
local v1 = tostring(v)
if type(v) ~= "string" then
v1 = "virt. ".. v[1]
end
table.insert(menu, {"Del. " .. v1 .. " " .. k, function ()
currentVM[k] = nil
genEditor()
end})
end
if not currentVM["k-eeprom"] then
table.insert(menu, {"+ Virtual EEPROM (R/W, preloaded w/ LUCcABOOT)...", function ()
currentVM[currentVMId .. "-eeprom"] = {"eeprom", "/vc-" .. currentVMId .. ".lua", "/vd-" .. currentVMId .. ".bin", "VM BIOS", false}
genEditor()
-- do file copy now!
local handleA = fs.open("/lucaboot.lua", "rb")
local handleB = fs.open("/vc-" .. currentVMId .. ".lua", "wb")
if not handleA then if handleB then fs.close(handleB) end return end
if not handleB then fs.close(handleA) return end
while true do
local s = fs.read(handleA, 2048)
if not s then break end
fs.write(handleB, s)
end
fs.close(handleA)
fs.close(handleB)
end})
end
table.insert(menu, {"+ Virtual FS (R/W)...", function ()
genFSSelector(function (fsa)
if fsa then
currentVM["fs-" .. fsa] = {"filesystem", "/fs-" .. fsa .. "/", false}
end
genEditor()
end)
end})
table.insert(menu, {"+ Virtual FS (R/O)...", function ()
genFSSelector(function (fsa)
if fsa then
currentVM[fsa .. "-fs"] = {"filesystem", fsa, true}
end
genEditor()
end)
end})
local tx = {
"+ Screen 50x15:",
"+ Screen 80x24:",
"+ Screen 160x49:"
}
local txw = {
50,
80,
160
}
local txh = {
15,
24,
49
}
for i = 1, 3 do
local cName = currentVMId .. "-screen"
local nt = 0
while currentVM[cName] do
nt = nt + 1
cName = currentVMId .. "-" .. nt
end
table.insert(menu, {tx[i], function ()
currentVM[cName] = {"screen", cName, txw[i], txh[i], 8}
genEditor()
end, function (text)
if text then cName = text end
return cName
end})
end
for address, ty in _MMcomList("") do
if (not currentVM[address]) and ty ~= "gpu" then
table.insert(menu, {"+ Host " .. ty .. " " .. address, function ()
currentVM[address] = ty
genEditor()
end})
end
end
end
function genMainMenu()
local vmName = ""
menu = {
--01234567890123456789012345678901234567890123456789
{" - metamachine configurator -- use keyboard - ", function () end},
{"Shutdown", computer.shutdown},
{"New VM: ", function ()
local f = fs.open("vm-" .. vmName, "wb")
if not f then return end
fs.write(f, _MMserial({
[vmName .. "-eeprom"] = {"eeprom", "/lucaboot.lua", "/vd-" .. vmName .. ".bin", "LUCcABOOT VM BIOS", true},
[vmName .. "-screen"] = {"screen", vmName, 50, 15, 8}
}))
fs.close(f)
genMainMenu()
end, function (text)
if text then vmName = text end
return vmName
end}
}
currentY = 3
local fsl = fs.list("")
table.sort(fsl)
for k, v in ipairs(fsl) do
if v:sub(#v) == "/" then
elseif v:sub(1, 3) == "vm-" then
local id = v:sub(4)
table.insert(menu, #menu, {id, function ()
local f = fs.open("vm-" .. id, "rb")
if not f then return end
local str = ""
while true do
local sb = fs.read(f, 2048)
if not sb then break end
str = str .. sb
end
currentVM = _MMdeserial(str) or {}
fs.close(f)
currentVMId = id
genEditor()
end})
end
end
end
----
genMainMenu()
local function draw()
gpu.fill(1, 1, 50, 15, " ")
local camera = math.max(0, math.min(math.floor(currentY - 7), #menu - 15))
for i = 1, #menu do
local pfx = " "
if currentY == i then
pfx = "> "
end
local pox = ""
if menu[i][3] then
pox = menu[i][3]()
end
gpu.set(1, i - camera, pfx .. menu[i][1] .. pox)
end
end
-- Final main loop.
draw()
while true do
local t = {computer.pullSignal()}
if t[1] == "key_down" then
if t[4] == 200 then
currentY = math.max(1, currentY - 1)
draw()
elseif t[4] == 208 then
currentY = math.min(currentY + 1, #menu)
draw()
elseif t[3] == 13 then
menu[currentY][2]()
draw()
elseif t[3] == 8 then
local tx = menu[currentY][3]()
menu[currentY][3](unicode.sub(tx, 1, unicode.len(tx) - 1))
draw()
elseif t[3] >= 32 then
if menu[currentY][3] then
menu[currentY][3](menu[currentY][3]() .. unicode.char(t[3]))
draw()
end
end
end
end

View File

@ -0,0 +1,145 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- lucaboot.lua : Fake EEPROM for VM.
-- Authors: 20kdc
-- LUCcABOOT v0
-- awful name I know
local eprox = component.proxy(component.list("eeprom", true)())
computer.getBootAddress = eprox.getData
computer.setBootAddress = eprox.setData
eprox = nil
local logo = {
" ⣀▄⣀ ",
" ⣀▄⠶▀⠉ ⠉█⠶▄⣀ ",
" ⣀▄⠶▀⠉ ◢◤ ⠉▀⠶▄⣀ ",
" ⣀▄⠶▀⠉ ◢◤ ⠉▀⠶▄⣀ ",
" ◢⠿⣭⣀ meta ◯ machine ⣀⣭⠿◣ ",
" █ ⠉▀⠶▄⣀ ◢◤ ⣀▄⠶▀⠉ █ ",
" █ ⠉▀⠶▄⣀ ◢◤ ⣀▄⠶▀⠉ ⣀▄⠶ █ ",
" █⣀ ⠉▀⠿▄▄▄⠶▀⠉ ⠉ ⣀█ ",
" ⠉▀⠶▄⣀ █ ⣀▄⠶▀⠉ ",
" ⠉▀⠶▄⣀ █ ⣀▄⠶▀⠉ ",
" ⠉▀⠶▄⣀ █ ⣀▄⠶▀⠉ ",
" ⠉▀▀▀⠉ ", -- 12
" metamachine virtual computing ",
" Press F3 to enter boot manager. ",
" "
}
local gpuA = component.list("gpu", true)()
local screenA = component.list("screen", true)()
local bootManager = false
if gpuA and screenA then
local gpuP = component.proxy(gpuA)
gpuP.bind(screenA)
gpuP.setResolution(33, 15)
local targetUptime = computer.uptime()
for j = 0, 10 do
targetUptime = targetUptime + 0.1
local ej = j
if j > 5 then
-- 5 * 50 = 250
ej = 5 - (j - 5)
ej = ej * 50
elseif j == 5 then
targetUptime = targetUptime + 4
ej = 255
else
ej = ej * 50
end
gpuP.setForeground(ej + (ej * 0x100) + (ej * 0x10000))
gpuP.setBackground(0)
for i = 1, #logo do
gpuP.set(1, i, logo[i])
end
while true do
local tl = targetUptime - computer.uptime()
if tl <= 0.01 then break end
local v = {computer.pullSignal(tl)}
if v[1] == "key_down" then
if v[4] == 61 then
-- boot manager
bootManager = true
logo[14] = " - Entering boot manager now. - "
break
end
end
end
end
gpuP.setForeground(0xFFFFFF)
gpuP.setBackground(0)
local selY = 1
while bootManager do
gpuP.fill(1, 1, 33, 15, " ")
local y = 1
local mapping = {}
for a in component.list("filesystem", true) do
local pfx = " "
if selY == y then
pfx = ">"
end
if computer.getBootAddress() == a then
pfx = pfx .. "*"
else
pfx = pfx .. " "
end
mapping[y] = a
gpuP.set(1, y, pfx .. a .. ":" .. (component.invoke(a, "getLabel") or ""))
y = y + 1
end
while true do
local v = {computer.pullSignal()}
if v[1] == "key_down" then
if v[4] == 200 then
selY = math.max(1, selY - 1)
break
elseif v[4] == 208 then
selY = math.min(selY + 1, y - 1)
break
elseif v[3] == 13 then
computer.setBootAddress(mapping[selY] or "")
bootManager = nil
break
end
end
end
end
end
local lr = "(no inits)"
local function boot(fsa)
local dat = component.proxy(fsa)
local fh = dat.open("/init.lua", "rb")
if fh then
local ttl = ""
while true do
local chk = dat.read(fh, 2048)
if not chk then break end
ttl = ttl .. chk
end
local fn, r = load(ttl, "=init.lua", "t")
if not fn then
lr = r
dat.close(fh)
else
dat.close(fh)
computer.setBootAddress(fsa)
fn()
error("Returned from init")
end
end
end
if component.type(computer.getBootAddress()) then
boot(computer.getBootAddress())
end
for a in component.list("filesystem", true) do
boot(a)
end
error("No available operating systems. " .. lr)

View File

@ -53,9 +53,9 @@ Here, "*" means that everything after
program to start up.
"k.computer": The "computer" table,
with wrapMeta applied,
pullSignal removed,
and also pushSignal.
with wrapMeta applied.
The security check may be aliased to
the "k.root" permission in future.
"k.kill": function (pid) to kill any
process on the system.
@ -63,7 +63,8 @@ Here, "*" means that everything after
"r.*": Registers a service's API for
retrieval via the "x." mechanism.
Returns a:
function (function (pkg, pid, send))
function (function (pkg, pid, send),
secret)
While the registration is locked on
success, attempting to use it will
fail, as no handler has been given.
@ -71,6 +72,11 @@ Here, "*" means that everything after
registration with a callback used
for when a process tries to use the
registered API.
Unless 'secret' is truthy, a
k.registration event is sent to all
processes; using the secret flag is
useful for a more ad-hoc security
approach.
What that API returns goes to the
target process.
The given "sendSig" function can be

View File

@ -123,28 +123,19 @@ os is extended with:
address = computer.address
The following are just host functions
(*: wrapped for security - these
functions detect metatable abuse):
(*: wrapped for security):
assert, ipairs, load, next*,
assert, ipairs, load*, next*,
pairs, pcall, xpcall, select,
type, error, tonumber, tostring,
setmetatable, getmetatable*,
rawset*, rawget, rawlen, rawequal
(NOTE: Before you consider that load
has no checks: The policy regarding
load is taken from the host system.
Which means bytecode loading is
almost certainly off. If it's not
off, then this is the user's fault,
as doing so is marked as a security
risk for very obvious reasons.
As for trying to use the environment
to bypass a metatable, I tested.
Metatables do apply to environments,
including global creation.
There is no way to win.)
(Apparently load, if not given an
argument, uses the global metatable.
This is of course a security hole.
A very big one. So it ended up
getting wrapped as of R3.)
"require" and "neo" are the parts of
the environment where a NEO-specific
@ -236,22 +227,10 @@ For libraries, it contains:
ensurePathComponent(s):
Ensures that a string is a safe
filename via a character list and
some special filename checks.
UTF-8 characters are just flat out
disallowed until someone can give
me proof they won't blow up
something somewhere.
(This restriction is Windows's
fault - I can't trust the encoding
mess to not find some new and
imaginative way of breaking
filenames, so I'd rather that they
get avoided until someone can try
actually using them.)
This does NOT ACCOUNT for *all* the
Windows total nonsense (aux, com1)
because if OC doesn't cover up
that then you're kinda doomed.
some special filename checks, for
".." and ".".
Rather permissive right now, but
don't go relying on that.
ensureType(v, ts):
Checks that a value is of a given
type, and errors otherwise. If the

View File

@ -0,0 +1,24 @@
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
These words in the given order with
the given full stops, quotes and
commas (newlines and code comment
tags such as "--" shall be ignored)
will be referred to as
"KittenOS NEO BSD0" in the project.
It is intended to be equivalent to
CC0, and whichever interpretation is
less restrictive should be favoured.
The reason it is used rather than CC0
is simply that it is smaller, which
is somewhat important in a memory-
limited system that KittenOS NEO is
designed to run within.

View File

@ -1,2 +1,2 @@
repository/apps/app-allmem.lua: 20kdc, Public Domain
repository/apps/app-allmem.lua: 20kdc, KittenOS NEO BSD0

Some files were not shown because too many files have changed in this diff Show More