diff --git a/Makefile b/Makefile index 2d9b3fa2..0147c5d9 100644 --- a/Makefile +++ b/Makefile @@ -218,14 +218,15 @@ include src/sdkHeaders.mk # The SDK's own files (README, sdk.mk, newProject.sh, project templates) # live in src/sdk; its bin/ holds the host tools a project build runs. SDK_SRC = src/sdk -SDK_TOOLS = bin/host/dvxres bin/host/dvxhlpc bin/host/bascomp bin/host/mkfont src/tools/dxeGen.sh +SDK_TOOLS = bin/host/dvxres bin/host/dvxhlpc bin/host/bascomp bin/host/mkfont bin/host/dvxpkg src/tools/dxeGen.sh -# The host tools come from tools (dvxres, dvxhlpc, mkfont) and apps (bascomp). -deploy-sdk: tools apps +# The host tools come from tools (dvxres, dvxhlpc, mkfont), apps (bascomp), +# and installer (dvxpkg). +deploy-sdk: tools apps installer @echo "Building SDK..." @# Start from an empty tree so a header dropped from the lists goes away. rm -rf $(SDKDIR) - @mkdir -p $(SDKDIR)/bin $(SDKDIR)/include/core $(SDKDIR)/include/shell $(SDKDIR)/include/tasks $(SDKDIR)/include/sql $(SDKDIR)/include/texthelp $(SDKDIR)/include/listhelp $(SDKDIR)/include/richtext $(SDKDIR)/include/fileops $(SDKDIR)/include/hlpfile $(SDKDIR)/include/xfer $(SDKDIR)/include/conn $(SDKDIR)/include/print $(SDKDIR)/include/game $(SDKDIR)/include/joystick $(SDKDIR)/include/audio $(SDKDIR)/include/music $(SDKDIR)/include/net $(SDKDIR)/include/basic $(SDKDIR)/samples/basic/basdemo $(SDKDIR)/samples/basic/widshow + @mkdir -p $(SDKDIR)/bin $(SDKDIR)/include/core $(SDKDIR)/include/shell $(SDKDIR)/include/tasks $(SDKDIR)/include/sql $(SDKDIR)/include/texthelp $(SDKDIR)/include/listhelp $(SDKDIR)/include/richtext $(SDKDIR)/include/fileops $(SDKDIR)/include/hlpfile $(SDKDIR)/include/xfer $(SDKDIR)/include/conn $(SDKDIR)/include/print $(SDKDIR)/include/game $(SDKDIR)/include/joystick $(SDKDIR)/include/audio $(SDKDIR)/include/music $(SDKDIR)/include/pkg $(SDKDIR)/include/json $(SDKDIR)/include/http $(SDKDIR)/include/pkgnet $(SDKDIR)/include/net $(SDKDIR)/include/basic $(SDKDIR)/samples/basic/basdemo $(SDKDIR)/samples/basic/widshow cp $(SDK_CORE_H) $(SDKDIR)/include/core/ cp $(SDK_SHELL_H) $(SDKDIR)/include/shell/ cp $(SDK_TASKS_H) $(SDKDIR)/include/tasks/ @@ -242,6 +243,10 @@ deploy-sdk: tools apps cp $(SDK_JOYSTICK_H) $(SDKDIR)/include/joystick/ cp $(SDK_AUDIO_H) $(SDKDIR)/include/audio/ cp $(SDK_MUSIC_H) $(SDKDIR)/include/music/ + cp $(SDK_PKG_H) $(SDKDIR)/include/pkg/ + cp $(SDK_JSON_H) $(SDKDIR)/include/json/ + cp $(SDK_HTTP_H) $(SDKDIR)/include/http/ + cp $(SDK_PKGNET_H) $(SDKDIR)/include/pkgnet/ @# Networking: dvxNet.h and the standard socket headers, which keep their @# subdirectories (sys/, netinet/, arpa/); sdk.mk adds include/net only @# for programs that set NET = 1. diff --git a/config/ASSOC.INI b/config/ASSOC.INI index cdb8bc90..37ce1201 100644 --- a/config/ASSOC.INI +++ b/config/ASSOC.INI @@ -23,3 +23,4 @@ CLS = apps/kpunch/dvxbasic/dvxbasic.app CTL = apps/kpunch/dvxbasic/dvxbasic.app HLP = apps/kpunch/dvxhelp/dvxhelp.app DHS = apps/kpunch/dvxhelp/helpedit.app +DPK = apps/kpunch/software/software.app diff --git a/config/README.md b/config/README.md index 2a7f8446..86fe89e1 100644 --- a/config/README.md +++ b/config/README.md @@ -23,6 +23,7 @@ the shipped list. | `wpaper/blueglow.jpg` | `bin/shared/wpaper/blueglow.jpg` | Wallpaper image | | `wpaper/swoop.jpg` | `bin/shared/wpaper/swoop.jpg` | Wallpaper image | | `wpaper/triangle.jpg` | `bin/shared/wpaper/triangle.jpg` | Wallpaper image | +| `keys/*.KEY` | `bin/system/KEYS/*.KEY` | DVX's repository key: packages it signs (DVX's own, and everything the package server publishes) are trusted as DVX's | The desktop sounds (`bin/shared/sounds/`) are made by mksounds and the fonts (`bin/shared/fonts/`) by mkfont (src/tools). @@ -193,3 +194,18 @@ ignored. Names are case-insensitive. Wallpapers support BMP, PNG, JPEG, and GIF formats. They are pre-rendered to screen dimensions in native pixel format at load time. + +## The Repository Key + +DVX trusts as its own the packages signed by the keys in `SYSTEM\KEYS`, +and refuses an update of DVX itself (`kpunch.dvx`) signed by any other. +The key pair is made once, on a computer the package server never +touches: + + bin/host/dvxpkg keygen "DVX Repository" KPUNCH + +`KPUNCH.KEY` (the public half) goes in `config/keys/`, so every build +ships it. `KPUNCH.SEC` stays offline, with the operator who approves +packages (server/README.md); `config/keys/.gitignore` keeps a stray copy +of it out of the repository. + diff --git a/config/dvx.ini b/config/dvx.ini index f4418dc8..471f6af0 100644 --- a/config/dvx.ini +++ b/config/dvx.ini @@ -51,6 +51,19 @@ dns2 = hostname = DVX vector = auto +; The package server (Software's File > Package Server): Software gets +; packages and updates from it, and publishes with the account. +; server: its host name, port: its port (both empty: DVX's own server) +; user, password: an account there, to publish packages (empty: none). +; Software saves the password scrambled; one typed here by hand is read +; as it is and scrambled the next time Software saves the settings. + +[packages] +server = +port = +user = +password = + ; Printer settings (Control Panel's Printer page and File > Page Setup). ; port: LPT1, LPT2, LPT3 or FILE (print into the file named by file) ; model: text (any printer, text only), epson9, epson24 (Epson ESC/P @@ -96,8 +109,9 @@ footer = Page &p ; mpuMode: what is on the MIDI port: gm (a General MIDI synthesizer), ; mt32 (a Roland MT-32) or raw (send music unchanged) ; Optional: oplPort (default 388) and mpuPort (default 330), in hex; -; midiBank = the instrument bank file for MIDI music (OP2 format, path -; from the DVX folder; default libs\kpunch\music\genmidi.op2) +; midiBank = the instrument bank for MIDI music (OP2 format): its name in +; SHARED\BANKS or USER\BANKS (an add-on's as VENDOR\NAME), or a path from +; the DVX folder; not set, the bundled one. Control Panel picks it. ; The Sound Blaster's port, IRQ and DMA come from the BLASTER setting (for ; example SET BLASTER=A220 I5 D1 H5 in AUTOEXEC.BAT). diff --git a/config/keys/.gitignore b/config/keys/.gitignore new file mode 100644 index 00000000..806ccbfb --- /dev/null +++ b/config/keys/.gitignore @@ -0,0 +1,3 @@ +# Secret keys never belong in the repository: only the public KPUNCH.KEY goes here +*.SEC +*.sec diff --git a/config/keys/KPUNCH.KEY b/config/keys/KPUNCH.KEY new file mode 100644 index 00000000..7dcd002e --- /dev/null +++ b/config/keys/KPUNCH.KEY @@ -0,0 +1,4 @@ +[key] +name = DVX Repository +id = f32a2ff2e9471fd7 +public = 1f67ffe49191d83792c8bba8d10b5abaa890db05421e6001d7d3b9b6093c2f10 diff --git a/docs/dvx_basic_reference.html b/docs/dvx_basic_reference.html index 10bc6814..753fdbc4 100644 --- a/docs/dvx_basic_reference.html +++ b/docs/dvx_basic_reference.html @@ -46,6 +46,7 @@ img { max-width: 100%; }
  • Code Editor
  • Form Designer
  • Project System
  • +
  • Making a Package
  • UserControls in the IDE
  • Properties Panel
  • Toolbox
  • @@ -181,6 +182,7 @@ img { max-width: 100%; }
  • ActiveControl
  • Add
  • Add File
  • +
  • Add-on packages
  • AddChildItem
  • AddItem
  • AddItem
  • @@ -425,6 +427,7 @@ img { max-width: 100%; }
  • DlgColor
  • DlgFileOpenIn$
  • DlgFileSaveIn$
  • +
  • DlgFolder$
  • DlgFont
  • DlgFontName$
  • DlgFontSize
  • @@ -436,6 +439,7 @@ img { max-width: 100%; }
  • DoEvents
  • DOEVENTS
  • Double
  • +
  • DPK files
  • Drag
  • Drag and Drop
  • DragDrop
  • @@ -637,6 +641,9 @@ img { max-width: 100%; }
  • Hotkeys
  • HOUR
  • HScrollBar
  • +
  • http.bas
  • +
  • HttpOpen
  • +
  • HttpRunFor
  • Icon (form)
  • Icon views you draw yourself
  • IDE
  • @@ -688,6 +695,9 @@ img { max-width: 100%; }
  • joystick.bas
  • JoyX
  • JoyY
  • +
  • JSON
  • +
  • json.bas
  • +
  • JsonParse
  • Key Codes
  • Keyboard Shortcuts
  • KILL
  • @@ -745,6 +755,7 @@ img { max-width: 100%; }
  • LTRIM$
  • Make Control
  • Make Control
  • +
  • Make Package
  • Mask
  • MaskColor
  • Masked input
  • @@ -847,6 +858,8 @@ img { max-width: 100%; }
  • OptionIndex
  • OR
  • Output Window
  • +
  • Package name
  • +
  • Packages
  • Padding
  • Padding
  • PAINT
  • @@ -863,6 +876,16 @@ img { max-width: 100%; }
  • PEN ON
  • Picture
  • PictureBox
  • +
  • pkg.bas
  • +
  • PkgBuild
  • +
  • PkgInstallStart
  • +
  • PkgKeygen
  • +
  • pkgnet.bas
  • +
  • PkgOpen
  • +
  • PkgPlan
  • +
  • PkgRemoveStart
  • +
  • PkgRunFor
  • +
  • PkgSign
  • PLAY
  • PLAY function
  • PlayNoise
  • @@ -886,9 +909,11 @@ img { max-width: 100%; }
  • PrefsDvxIni$
  • PrefsGetChoice
  • PrefsGetChoiceFrom
  • +
  • PrefsGetSecret$
  • PrefsKeyName$
  • PrefsLoad
  • PrefsSaveAs
  • +
  • PrefsSetSecret
  • PrefsVideoWidth
  • PRESET
  • PRINT
  • @@ -1109,6 +1134,7 @@ img { max-width: 100%; }
  • Show
  • Show
  • ShowTopic
  • +
  • Signing a package
  • SIN
  • Single
  • SizeChars
  • @@ -1409,6 +1435,7 @@ img { max-width: 100%; }
  • VT100
  • Watch Expressions
  • Watch Window
  • +
  • Web requests
  • WEEKDAY
  • WEEKDAYNAME
  • WEND
  • @@ -1502,6 +1529,7 @@ img { max-width: 100%; } --- Make Executable... Compile the project and save it as a standalone .app file. Prompts for a debug or release build. A release build leaves out debugging information and gives forms and controls short internal names, except in a program that uses names as text (the Name property, Controls, DesignSelection, or a DesignMouse event procedure), which keeps them. Make Control... In a control project (Project Properties, Type Control) this item replaces Make Executable: it compiles the project and saves its controls as a control file (.wgt, named after the project). See UserControls in the IDE. + Make Package... Make the program (or, in a control project, the control file) and its help file into an add-on package (.DPK) that anyone can install with Software. See Making a Package. --- Rename File... Give the file selected in the Project Explorer a new name, on disk and in the project. See Renaming a File under Project System. Remove File Remove the selected file from the project (prompts to save if modified). @@ -1745,7 +1773,7 @@ Count = 1 New UserControl Adds a new UserControl to the project and opens it (see UserControls in the IDE). Rename File Gives a file a new name on disk and in the project file (see Renaming a File below). Remove File Removes a file from the project (prompts to save if modified). Removing the startup form clears the Startup Form setting. - Project Properties Opens a dialog for editing project metadata (name, type, author, company, version, copyright, description, icon, help file, startup form), the fonts the program carries, whether more than one copy may run at once, and the Arguments for runs from the IDE. + Project Properties Opens a dialog for editing project metadata (name, type, author, company, version, copyright, description, icon, help file, startup form), the fonts the program carries, whether more than one copy may run at once, the Arguments for runs from the IDE, and the Package and Needs that File > Make Package uses.

    Renaming a File

    Select a file in the Project Explorer and choose File > Rename File. Type the new file name; the extension may be left out (a module stays .bas, a class module .cls, a UserControl .ctl, and a form .frm, and cannot be given another kind's extension). The file is renamed in the project's directory and the project file is saved at once with the new name, so the two always agree. Nothing changes, and a message says why, when the name is not a valid file name, when another project file or any file in the directory already has it, or when the file or the project file cannot be written -- an existing file is never overwritten. A file that has never been saved is simply given the new name, and is written under it when you save.

    A class module named after its file (it has no Attribute VB_Name line, or one that matches the file name, as File > New Class Module writes it) takes the new name as its class name: its Attribute VB_Name line is changed to match (an edit that is saved with the file, like any other). The new name must then be a valid name that no other form, class module, or UserControl uses. Code that uses the old class name is not changed. A UserControl named after its file (as File > New UserControl makes it) takes the new name too, on its Begin UserControl line; forms that use the old name are not changed. A form keeps its Name property; rename the form itself in the Properties window.

    @@ -1770,6 +1798,16 @@ Option Explicit

    UserControls in the IDE

    Back to Overview

    +
    +

    Making a Package

    +

    File > Make Package turns the project into an add-on package: one .DPK file holding the program (built as a release build) and its help file, which anyone can install with the Software program -- it puts the program in Program Manager's Installed Programs -- and remove again. In a control project the package holds the control file instead, and installs it for every program to use.

    +

    The first time, Make Package asks for the package name: who made it and what it is, with a dot between them, such as acme.chess (each part 1 to 8 lower-case letters, digits, or _). It suggests one from the project's Publisher and Name. Keep the same package name for every version, so that Software installs a new version over the old one. The name is kept in the project and can be changed under File > Project Properties (Package).

    +

    It then asks where to save the package and which key to sign it with. Signing tells the people who install the package that it came from you and was not changed on the way; the list offers the keys in your KEYS folder (make one with Package Builder's New Key) and Not signed. The key chosen is offered first the next time.

    +

    The package takes the rest from the project: its name, Version (give each version you hand out a higher one, such as 1.0.0, then 1.1.0), Description (shown as its summary), Publisher, and Author. When the program needs other add-on packages to work, list them under Project Properties, Needs: each package name with the oldest version that will do, with commas between them, such as acme.cards 1.0, acme.dice 2.1 (a package name alone takes any version). DVX's own libraries need not be listed.

    +

    A program that needs more files than its help -- data files, or files in other folders -- is packaged with Package Builder, which takes a whole folder.

    +

    Project System

    +

    Back to Overview

    +

    UserControls in the IDE

    A UserControl is a control you write in BASIC (see UserControls: Your Own Controls in the BASIC Language Reference for how to write one). This topic covers making one in the IDE, editing it, trying it on a form, and turning it into a control file every program can use.

    @@ -1801,7 +1839,7 @@ End Sub Width, Height Enter a whole number of pixels (at least 1): the control's natural size. CanGetFocus Each double-click switches between True and False. BackColor Pick a color in the color dialog. - ToolboxIcon Type the name of a 24 x 24 picture file in the project's folder. + ToolboxIcon Type the name of a 24 x 24 picture file in the project's folder, or click Browse... to pick it. Description Type the toolbox tooltip. DefaultEvent Pick one of the control's Events (or none): the event the designer opens when a placed control is double-clicked.

    The change is written into the header lines of the file (the rest of the file stays as it is) and saved with the file. The Code Editor shows the new header; breakpoints in the code move with the lines below it. You can also edit the header lines in the Code Editor by hand.

    @@ -1827,7 +1865,7 @@ End Sub

    The Properties panel (Window > Properties) has two sections:

    Each control type exposes different properties (e.g., Caption, Text, Width, Height, MaxWidth, MaxHeight, Weight, Alignment, Enabled, Visible, and type-specific properties like DataSource and DataField for data-bound controls). A UserControl placed on a form lists the properties its code defines.

    The Font row shows the font the selected control, or the form, draws with: its family, size in pixels, and styles, such as "DejaVu Sans, 14, Bold". Double-click it to choose a font in the Font dialog; the design surface shows it at once. The choice is kept as the six Font properties (FontName, FontSize, FontBold, FontItalic, FontUnderline, and FontStrikethru), which then appear in the list and can be changed one at a time too. Choosing the font the control would have anyway (its container's or the form's) removes them, so the control follows the form's font again. A font chosen for the form is used by every control that has none of its own. For a TextBox, TextArea, ComboBox, SpinButton, or Terminal the dialog lists only fixed-pitch families.

    @@ -2258,6 +2296,7 @@ End Sub
  • Paths may be written with / or \ wherever a path is taken (OPEN, KILL, NAME, MKDIR, CHDIR, DIR$, FILELEN, and the file and folder functions of the libraries), mixed too; paths DVX gives back (CURDIR$, App.Path, App.Data, the file dialogs, the FileOps results) use \, as DOS shows them. New: FileOpsPathNative$ gives a path of your own that form. DIR$("C:\*.*") lists the root of C: (before, it listed C:'s current folder). SHELL commands still go to DOS as written, where / starts a switch.
  • The IDE shows a project's file paths with \ and keeps writing them with / in the .dbp file, so a project reads the same everywhere.
  • The Open and Save As dialogs (DlgFileOpen$, DlgFileSave$, and the IDE's own) have a Drive list beside the Path box (Alt+D); a drive with no disk in it says it is not ready.
  • +
  • New: DlgFolder$(title, folder) in commdlg.bas lets the user choose a folder, existing or new, for a Browse button beside a folder box. A title of "" gives any of the file dialogs its usual title (it used to leave the title bar empty).
  • Changed: FileOpsValidName takes the folder the name is for, FileOpsValidName(folder, itemName), since the rules depend on its disk; XferSafeName$ takes the folder a file is received into, XferSafeName$(name, folder). New: FileOpsLongNames, FileOpsShortPath$, FileOpsValidLongName, and FileOpsValidShortName (fileops.bas), and ShellArgQuote$ (shell.bas), which puts a path with spaces in quotes for a command line.
  • Programs compiled with an older release must be rebuilt. A program whose procedure table records more than 64 locals for a procedure is refused when it is loaded.
  • A class may have at most 65536 member variables and a form at most 65536 form-level variables (as many as the compiled program can number); more is a compile error ("Too many member variables in X", "Too many form-level variables"), and a program file that records more is refused when it is loaded.
  • @@ -2300,6 +2339,7 @@ End Sub

    IDE

    +
  • Adding and Removing Software
  • Changing Settings
  • Task Manager
  • Getting Help
  • @@ -158,7 +159,9 @@ img { max-width: 100%; }
  • Help Files from BASIC
  • +
  • HTTP Library
  • Joystick Library
  • +
  • JSON Library
  • libtasks -- Cooperative Task Switching
  • List Helper Library
  • Music Library
  • +
  • Package Server Library
  • +
  • Package Library
  • Printing Library
  • Styled Text Library
  • @@ -2237,7 +2294,7 @@ img { max-width: 100%; }

    Printing

    Programs that can print have Print... in their File menu, and programs that print text also have Page Setup.... Every program prints to the same printer, so you only tell DVX once which printer you have and where it is plugged in (see Changing Settings). You can also send printouts into a file instead of a printer.

    The Print Dialog

    -

    File > Print... opens the Print dialog. Choose where the printout goes (a printer port such as LPT1, or a file), how many copies you want, and (for a document of several pages) whether to print all of it or only some pages. Then choose Print.

    +

    File > Print... opens the Print dialog. Choose where the printout goes (a printer port such as LPT1, or a file), how many copies you want, and (for a document of several pages) whether to print all of it or only some pages. Then choose Print. When the printout goes into a file, type the file's name, or click Browse... to pick it.

    While a printout is going to the printer, a small Printing window shows it is busy. You can keep working in other programs, and Cancel stops the printout.

    When the Printer Has a Problem

    If the printer is out of paper, switched off, off line, or not answering, a message says what is wrong. Fix the problem and choose Retry to carry on, or Cancel to stop printing. Nothing waits forever.

    @@ -2249,7 +2306,7 @@ img { max-width: 100%; }

    Running Programs

    Finding and Starting a Program

    -

    Every program installed in DVX appears as an icon in the Program Manager, in its Installed Programs group, and you can put icons for the programs you use most into groups of your own. Select an icon to see a short description of the program in the Program Manager's status bar.

    +

    Every program installed in DVX appears as an icon in the Program Manager, in its Installed Programs group, and you can put icons for the programs you use most into groups of your own. Programs you add later come as packages you install with the Software program (see Adding and Removing Software). Select an icon to see a short description of the program in the Program Manager's status bar.

    Double-click an icon to start that program, or select it with the arrow keys and press Enter. The program opens in a new window. While it is loading, the mouse pointer shows an hourglass. The Program Manager's Search box finds a program in every group as you type its name.

    To start a program file that has no icon, choose Run... from the Program Manager's File menu and type or pick the file.

    Opening Documents

    @@ -2277,6 +2334,7 @@ img { max-width: 100%; }

    If a program has no help of its own, F1 opens this guide instead.

    Getting Help

    Task Manager

    +

    Adding and Removing Software

    The Program Manager

    @@ -2290,6 +2348,26 @@ img { max-width: 100%; }

    You can make your own groups, add, move, and copy icons, give an icon a shortcut key that starts it from any program, and choose large icons, small icons, or a list. Everything you arrange is remembered the next time you start DVX. The Program Manager's own help explains all of this: press F1 while it is the active window, or choose Contents from its Help menu.

    Warning: Closing the Program Manager window leaves DVX, after asking you first.
    +
    +

    Adding and Removing Software

    +

    Programs, fonts, sounds, themes, desktop pictures, MIDI instruments, and the libraries and widgets other programs use can be added to DVX after it is installed. Each comes as a package: one file whose name ends in .DPK.

    +

    Installing a Package

    +

    Open the .DPK file in File Manager, or start the Software program and choose Install from File. Software shows what will be installed first -- including other packages it needs from the same folder -- who signed it, and how much disk space it takes, then installs it when you choose Install. A new program appears in the Program Manager's Installed Programs group; new fonts, sounds, themes, desktop pictures, and instruments appear where you choose them in the settings program. Libraries and widgets work from the next time DVX starts.

    +

    Software can also get packages from the package server over the network: its Available page lists every package the server offers. Choose one and Install; Software fetches it with any packages it needs, then shows what will be installed exactly as for a file.

    +

    A package can be signed by the people who made it, like a seal on a letter. Software tells you when a package is not signed by anyone you trust: install it only if you trust where it came from. To trust a publisher, get their key file (it ends in .KEY) and choose Trust a Publisher's Key in Software's File menu.

    +

    Keeping DVX Up to Date

    +

    Software's Updates page asks the package server what is newer than what you have, DVX itself included, and Update All installs it. When DVX itself is updated, the new version runs from the next time DVX starts; Software lists any add-ons that will stop working with it until they are updated too. The package server's address and port are set with Package Server in Software's File menu.

    +

    Publishing Your Own Packages

    +

    With an account on the package server (its email and password given in the same Package Server window), Software's File > Publish a Package sends a package you made to the server. It is offered to everyone once the server's operator has reviewed it; Software's Published page shows how the review went.

    +

    Removing a Package

    +

    Software's Installed list shows every package in DVX. Choose one and Remove. If other packages need it, Software names them and removes them too (or nothing); files you changed since they were installed are kept unless you say otherwise.

    +

    Making Your Own Packages

    +

    Package Builder makes a package of your own program, fonts, or other files, for other people to install with Software, and signs it with a key of yours. The DVX BASIC IDE makes a package of a BASIC program in one step: File > Make Package. Their help explains how.

    +

    If DVX Will Not Start

    +

    If DVX does not start after you installed something, hold down a Shift key while DVX starts. DVX then starts without the libraries and widgets add-ons installed, so you can remove the one that causes the trouble with Software. See Troubleshooting.

    +

    Running Programs

    +

    Troubleshooting

    +

    Changing Settings

    The mouse, the screen colors, the desktop picture, the screen resolution, which program opens each kind of document, the network, the printer, joysticks, and the sounds are changed with the settings program, which has an icon in the Installed Programs group of the Program Manager. Start it like any other program. Its own help, opened with F1 or its Help menu, explains every setting.

    @@ -2378,15 +2456,19 @@ img { max-width: 100%; }

    Safe video mode

    If the screen resolution DVX is set to use does not work on your computer (for example after changing video cards), DVX starts in safe video mode instead: 640x480, which every video card can show. A message tells you so and asks whether to use this mode from now on. Answer Yes to keep it, or No to try the old resolution again at the next start.

    If DVX starts but the screen cannot be read (the monitor cannot show the saved resolution), hold down a Shift key while DVX starts. DVX then starts in safe video mode and asks the same question. Typing DVX /SAFE at the DOS prompt does the same.

    +

    Starting this way is a safe start: DVX also leaves out the libraries and widgets that add-on packages installed, in case one of them is what stops DVX from starting. A message says so once the desktop is up. Start DVX the usual way to use them again, or remove the add-on that causes the trouble with the Software program.

    +

    Add-ons that could not be loaded

    +

    When an add-on package's library or widget cannot be loaded (it was made for another version of DVX, it needs something that is not installed, or its file is damaged), DVX starts without it and a message once the desktop is up lists what was left out and why. The rest of DVX works as usual. Reinstall the add-on (a version made for this DVX), or remove it with the Software program.

    You can pick another resolution at any time with the settings program (see Changing Settings).

    There is no sound

    DVX finds the sound hardware by itself. A Sound Blaster needs its BLASTER setting in AUTOEXEC.BAT, for example SET BLASTER=A220 I5 D1 H5 (the card's manual gives the numbers). Without a sound card DVX uses the PC speaker, which plays tones and beeps but not recorded sounds. The settings program shows the sound hardware in use, lets you pick it yourself, and sets the volume; if a sound is set to none there, nothing is heard for it. Without any sound hardware DVX and its programs work the same, silently.

    An empty floppy or CD-ROM drive

    When a program tries to read a drive that has no disk in it, or to write to a write-protected disk, the program simply reports that it could not read or write the file. DVX never stops at the DOS "Abort, Retry, Fail" question. Put the disk in and try again.

    Installing, upgrading, and removing DVX

    -

    DVX is installed with SETUP, from the DVX CD or from Disk 1 of the DVX floppy disks: change to that drive and type SETUP. Setup shows the licence, asks for the folder to install DVX in and which fonts to install, checks that there is room, and unpacks DVX there, asking for each floppy disk it needs in turn. It can also make DVX start when the computer starts (it shows the line it adds to AUTOEXEC.BAT and keeps a copy of the old file as AUTOEXEC.DVX). Setup never writes to the CD or the floppy disks.

    +

    DVX is installed with SETUP, from the DVX CD or from Disk 1 of the DVX floppy disks: change to that drive and type SETUP. After the licence, Setup asks what you would like to do: Install DVX, Upgrade or repair DVX, or Remove DVX. Click the picture or the button of your choice. To install, choose the folder for DVX (type it, or click Browse... to pick it; a folder that does not exist yet is made) and which fonts to install. Setup checks that there is room and unpacks DVX there, asking for each floppy disk it needs in turn. It can also make DVX start when the computer starts (it shows the line it adds to AUTOEXEC.BAT and keeps a copy of the old file as AUTOEXEC.DVX). Setup never writes to the CD or the floppy disks.

    The fonts are up to you. None keeps DVX to its own font; the base set adds DejaVu Sans, Sans Mono, and Serif in all their styles (about 2 MB); and All adds every font that comes with DVX (about 8 MB). A program that asks for a font you did not install shows its text in DVX's own font instead, unless it carries that font itself (some programs do, such as Welcome and the DVX Demo). Every font file holds its own licence: the Licence button of the Font dialog shows it. With None or the base set, Setup does not ask for the last floppy disks, which hold only fonts.

    -

    To upgrade or repair DVX, run Setup again and choose the same folder: answer Yes to keep your settings and saved files (the USER folder), or No to remove them and start over. Setup starts with the fonts that folder already has; choose others to add fonts or remove them. To remove DVX, run SETUP in the DVX folder (for example C:\DVX\SETUP) and choose Uninstall. Files you added to the folder yourself are not removed, and the USER folder is only removed if you ask.

    +

    To upgrade or repair DVX, run Setup again and choose Upgrade or repair DVX, with the folder DVX is in. (When DVX is in the usual folder, such as C:\DVX, Setup says so and suggests this choice.) Your settings and saved files (the USER folder) are kept, unless you tick Start over to remove them. Setup starts with the fonts that folder already has; choose others to add fonts or remove them. If you choose Install DVX for a folder that already has DVX, Setup offers to upgrade it instead.

    +

    To remove DVX, run Setup and choose Remove DVX, with the folder DVX is in. The SETUP in the DVX folder (for example C:\DVX\SETUP) can remove DVX too, from its own folder only. Files you added to the folder yourself are not removed, and the USER folder is only removed if you ask.

    Running DVX from a CD-ROM or a write-protected disk

    DVX also runs straight from a disk it cannot write to. It then keeps your settings and files in a DVXUSER folder in your TEMP folder, and tells you so once. To keep them somewhere of your choosing, set DVXUSER to a folder on your hard disk before starting DVX (see Changing Settings). Without a TEMP folder, DVX tells you that changed settings last only until you exit DVX. Either way the help files are used as they are. To keep everything on your hard disk, install DVX there with Setup.

    A program is already running

    @@ -5964,21 +6046,23 @@ int32_t pick = dvxButtonBox(ctx, "Copy", "The disk is full."
      Define      Value    Description
       ------      -----    -----------
       FD_OPEN     0x0000   File open dialog (default)
    -  FD_SAVE     0x0001   File save dialog (with overwrite prompt)
    + FD_SAVE 0x0001 File save dialog (with overwrite prompt) + FD_FOLDER 0x0002 Choose a folder, new or existing

    dvxFileDialog

    bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const char *initialDir, const FileFilterT *filters, int32_t filterCount, char *outPath, int32_t outPathSize);

    Display a modal file open/save dialog. Shows a directory listing with parent/drive-letter navigation, a drive dropdown, a filename input, and an optional filter dropdown. The path box always shows the current folder, and the drive dropdown (platformDriveList, so listing the drives never touches empty floppy or CD-ROM drives) its drive; choosing a drive goes to its root, and a drive that cannot be read shows "Drive X: is not ready." and leaves the dialog where it was. Typing "D:" or "D:folder" in the path box changes drive/folder; the file name box also accepts a relative or absolute path ("sub\x.txt", "C:\X\A.TXT") and navigates to its folder. A folder that does not exist shows an "Invalid Path" error. The dialog closes at once if the app that owns it is ended.

    +

    With FD_FOLDER the dialog chooses a folder: the list shows folders only (filters are not used), the name box is "Folder name", and OK returns the folder shown, or the folder of the typed name in it (or the typed path itself when it is absolute). That folder need not exist yet; the caller makes it (platformMkdirRecursive). A name that is a file shows an "Invalid Folder" error, and so does a new name the disk cannot hold. An initialDir that does not exist yet opens at its nearest existing folder with the rest already in the name box, so OK alone returns initialDir unchanged. The default title is "Choose a Folder".

      Parameter     Description
       ---------     -----------
       ctx           Application context
    -  title         Window title
    -  flags         FD_OPEN or FD_SAVE
    +  title         Window title (NULL = "Open", "Save As", or "Choose a Folder")
    +  flags         FD_OPEN, FD_SAVE, or FD_FOLDER
       initialDir    Starting directory (NULL = current working directory)
       filters       Array of FileFilterT, or NULL
       filterCount   Number of filter entries
       outPath       Output buffer for selected path
       outPathSize   Capacity of outPath buffer
    -

    Returns: true if the user selected a file, false if cancelled or closed.

    +

    Returns: true if the user selected a file (or folder), false if cancelled or closed.

    Input Boxes

    dvxInputBox

    bool dvxInputBox(AppContextT *ctx, const char *title, const char *prompt, const char *defaultText, char *outBuf, int32_t outBufSize);
    @@ -6193,7 +6277,7 @@ if (dvxFontDialog(ctx, NULL, 0, &choice)) {

    dvxPrefs.h -- INI Preferences System

    Handle-based API over classic INI files. Each prefsLoad/prefsCreate returns a PrefsHandleT that must be passed to all subsequent calls and freed with prefsClose when done. Multiple INI files can be open simultaneously.

    -

    BASIC programs reach the same functions through prefs.bas (dvxPrefsBas.c in libdvx): PrefsLoad, PrefsCreate, PrefsGet$, PrefsGetInt, PrefsGetBool, PrefsSet, PrefsSetInt, PrefsSetBool, PrefsRemove, PrefsKeyName$ (prefsKeyName, "" past the last key), PrefsSave, PrefsSaveAs, and PrefsClose, on handles (1, 2, ...; 0 = none) that belong to the program that made them and are closed, unsaved, when it ends. True is -1, False 0. PrefsDvxIni$ is platformDvxIniPath() (the user folder's DVX.INI), PrefsSetVideoMode is prefsSetVideoMode, and PrefsVideoWidth / PrefsVideoHeight / PrefsVideoBpp are the three values of prefsGetVideoMode (DVX_DEFAULT_VIDEO_* for a missing key or a bad handle). The choice tables below are PREFS_CHOICE_ACCEL (0), PREFS_CHOICE_WALLPAPER (1), and PREFS_CHOICE_WHEEL (2) there: PrefsChoiceCount, PrefsChoiceLabel$, PrefsChoiceName$ (iniName), PrefsChoiceValue, PrefsChoiceIndex, PrefsChoiceIndexOfValue, PrefsChoiceDefault (the index of the choice MOUSE_ACCEL_DEFAULT / WALLPAPER_MODE_DEFAULT / MOUSE_WHEEL_DIR_DEFAULT names; -1 for an unknown table), and PrefsGetChoice(handle, section, key, table) with the table's default. PrefsGetChoiceFrom(handle, section, key, choiceNames, defaultName) is prefsGetChoice on a table the program gives as "|"-separated names (each choice's iniName and label is its name, its value its index).

    +

    BASIC programs reach the same functions through prefs.bas (dvxPrefsBas.c in libdvx): PrefsLoad, PrefsCreate, PrefsGet$, PrefsGetInt, PrefsGetBool, PrefsSet, PrefsSetInt, PrefsSetBool, PrefsGetSecret$, PrefsSetSecret, PrefsRemove, PrefsKeyName$ (prefsKeyName, "" past the last key), PrefsSave, PrefsSaveAs, and PrefsClose, on handles (1, 2, ...; 0 = none) that belong to the program that made them and are closed, unsaved, when it ends. True is -1, False 0. PrefsDvxIni$ is platformDvxIniPath() (the user folder's DVX.INI), PrefsSetVideoMode is prefsSetVideoMode, and PrefsVideoWidth / PrefsVideoHeight / PrefsVideoBpp are the three values of prefsGetVideoMode (DVX_DEFAULT_VIDEO_* for a missing key or a bad handle). The choice tables below are PREFS_CHOICE_ACCEL (0), PREFS_CHOICE_WALLPAPER (1), and PREFS_CHOICE_WHEEL (2) there: PrefsChoiceCount, PrefsChoiceLabel$, PrefsChoiceName$ (iniName), PrefsChoiceValue, PrefsChoiceIndex, PrefsChoiceIndexOfValue, PrefsChoiceDefault (the index of the choice MOUSE_ACCEL_DEFAULT / WALLPAPER_MODE_DEFAULT / MOUSE_WHEEL_DIR_DEFAULT names; -1 for an unknown table), and PrefsGetChoice(handle, section, key, table) with the table's default. PrefsGetChoiceFrom(handle, section, key, choiceNames, defaultName) is prefsGetChoice on a table the program gives as "|"-separated names (each choice's iniName and label is its name, its value its index).

    PrefsHandleT *prefsBasHandle(int32_t handle);

    The settings behind a prefs.bas handle, NULL for 0 or a handle that is not open. Other libraries' BASIC entry points that take a prefs.bas handle (system.bas, joystick.bas, net.bas, print.bas, sound.bas) resolve it with this; libdvx is loaded first, so the symbol is always there.

    The system INI

    @@ -6273,6 +6357,16 @@ if (dvxFontDialog(ctx, NULL, 0, &choice)) { key Key name defaultVal Returned if the key is absent or unrecognised

    Returns: Parsed bool, or defaultVal.

    +

    prefsGetSecret

    +
    void prefsGetSecret(PrefsHandleT *h, const char *section, const char *key, char *out, int32_t outSize);
    +

    Read back a secret prefsSetSecret stored, into out (cut to outSize - 1 characters and always terminated; nothing is written when outSize is 0 or less). A missing key, or a NULL handle, gives "". A value prefsSetSecret did not write (one typed into the file by hand: no leading "*", or not hex digits after it) is copied as it is, so a password can be put in the file by hand and is scrambled the next time the program saves it.

    +
      Parameter    Description
    +  ---------    -----------
    +  h            Preferences handle
    +  section      Section name
    +  key          Key name
    +  out          Receives the text
    +  outSize      Size of out in bytes

    prefsGetVideoMode / prefsSetVideoMode

    void prefsGetVideoMode(PrefsHandleT *h, int32_t *width, int32_t *height, int32_t *bpp);
     void prefsSetVideoMode(PrefsHandleT *h, int32_t width, int32_t height, int32_t bpp);
    @@ -6287,6 +6381,9 @@ void prefsSetVideoMode(PrefsHandleT *h, int32_t width, int32_t height, int32_t b

    prefsSetBool

    void prefsSetBool(PrefsHandleT *h, const char *section, const char *key, bool value);

    Set a boolean value (stored as "true"/"false").

    +

    prefsSetSecret

    +
    void prefsSetSecret(PrefsHandleT *h, const char *section, const char *key, const char *value);
    +

    Store a secret, such as a password, so it cannot be read at a glance: "*", a 32-bit salt as 8 hex digits, then each byte of the text XORed with an xorshift32 keystream started from the salt, as 2 hex digits. Each call takes a new salt (clock(), time(), and a call count), so the same text is stored differently each time. This is obfuscation, not encryption: anyone with this code can read the value back, so the file still needs the care of any file holding a password. An empty or NULL value is stored as "". prefsGetSecret reads it back.

    prefsRemove

    void prefsRemove(PrefsHandleT *h, const char *section, const char *key);

    Remove a key from a section. No-op if the key does not exist.

    @@ -6784,7 +6881,10 @@ bool platformPathSibling(char *out, int32_t outSize, const char *path, const cha

    Write path with its file-name extension replaced by ext into out, followed by as many leading characters of the original extension as fit in PLATFORM_SIBLING_EXT_MAX characters (a full-length ext such as ".BAK" replaces the extension outright; a name with no extension gets ext appended). Replacing rather than appending keeps an 8.3 name valid on DOS without long file names, and keeping part of the original extension gives files that differ only in extension distinct scratch names: USER\DVX.INI becomes USER\DVX.$IN (temp) and USER\DVX.~IN (backup), FOO.APP and FOO.HLP become FOO.$AP and FOO.$HL. Returns false when the result does not fit in outSize or would name path itself (compared case-insensitively).

    platformReplaceFile

    bool platformReplaceFile(const char *tmpPath, const char *path, const char *bakPath);
    -

    Crash-safe swap of a fully written tmpPath in for path. The original is renamed aside to bakPath first (DOS rename cannot overwrite) and removed only once the replacement is in place; if the swap fails the original is restored and tmpPath removed. A directory at path is never moved aside: the call fails and removes tmpPath. A leftover bakPath is removed only while path exists; with path missing it may be the only copy left by a crash between the two renames, so it is kept (prefsLoad reads it back). The preferences system, the resource writer, and the help compiler all save this way, with the PLATFORM_TMP_EXT and PLATFORM_BAK_EXT siblings from platformPathSibling.

    +

    Crash-safe swap of a fully written tmpPath in for path. The original is renamed aside to bakPath first (DOS rename cannot overwrite) and removed only once the replacement is in place; if the swap fails the original is restored and tmpPath removed. A directory at path is never moved aside: the call fails and removes tmpPath. A leftover bakPath is removed only while path exists; with path missing it may be the only copy left by a crash between the two renames, so it is kept (prefsLoad reads it back). The resource writer and the help compiler save this way, with the PLATFORM_TMP_EXT and PLATFORM_BAK_EXT siblings from platformPathSibling.

    +

    platformWriteFile

    +
    bool platformWriteFile(const char *path, const void *data, int32_t len);
    +

    Write len bytes of data as the whole of path, all or nothing: into the PLATFORM_TMP_EXT sibling, then swapped in with platformReplaceFile, so a full disk or an I/O error leaves the file as it was. False when it was not done. The preferences system, the IDE's saves, Setup, and the package records use it.

    platformReadFile

    char *platformReadFile(const char *path, int32_t *outLen);

    Slurp a whole file into a freshly malloc'd, NUL-terminated buffer. Caller frees. Binary-safe: the NUL is past the end of the reported length.

    @@ -6829,12 +6929,15 @@ char platformBootDrive(void); #define DVX_SOUNDS_DIR "SOUNDS" #define DVX_THEMES_DIR "THEMES" #define DVX_WPAPER_DIR "WPAPER" +#define DVX_BANKS_DIR "BANKS" +#define DVX_VENDOR_DIR "KPUNCH" typedef enum { DvxKindFontsE, DvxKindSoundsE, DvxKindThemesE, DvxKindWpaperE, + DvxKindBanksE, DvxKindCountE } DvxKindE; @@ -6857,12 +6960,12 @@ char **platformKindReadDir(DvxKindE kind); SYSTEM DVX's own tools and files, and the shipped DVX.INI and ASSOC.INI LIBS, WIDGETS The libraries and widgets APPS\<vendor>\<program> A program, its help, and DATA (the files it ships; read-only) - SHARED FONTS, SOUNDS, THEMES, and WPAPER: what DVX ships for every program - USER (the user folder) DVX.INI, ASSOC.INI (the user's changes to SYSTEM's), DVX.LOG, the user's own FONTS, SOUNDS, THEMES, and WPAPER, APPS\<program path> (each program's settings and saved files), and DOWNLOAD -

    Only DVX's own package puts files in SHARED. A program reads it but never writes or installs into it; a program's own files live with it (its DATA folder, or resources inside the .app), under its own vendor and program folder, so two programs' files never collide.

    + SHARED FONTS, SOUNDS, THEMES, WPAPER, and BANKS: what DVX and add-on packages ship for every program (an add-on's in a folder of its vendor's, such as FONTS\ACME) + USER (the user folder) DVX.INI, ASSOC.INI (the user's changes to SYSTEM's), DVX.LOG, the user's own FONTS, SOUNDS, THEMES, WPAPER, and BANKS, KEYS (publishers' keys the user trusts), APPS\<program path> (each program's settings and saved files), and DOWNLOAD +

    Only packages put files in SHARED: DVX's own, and add-on packages (pkg.lib), each in its vendor's folders. A program reads it but never writes or installs into it; a program's own files live with it (its DATA folder, or resources inside the .app), under its own vendor and program folder, so two programs' files never collide.

    platformUserInit, called once by the loader, decides the user folder: the folder the DVXUSER environment variable names, else USER in the DVX folder, made when missing. When it cannot be written (DVX on a CD-ROM without DVXUSER), DVXUSER in the TEMP folder is used instead and platformUserInTemp is true; without a TEMP folder nothing can be saved and platformIsReadOnly is true. The first start copies SYSTEM\DVX.INI to the user folder. platformUserDir is the folder (DVXUSER or USER before platformUserInit has decided); platformUserPath joins a name to it; platformDvxIniPath is its DVX.INI. platformUserDirOf is the user folder of a DVX installed in another folder (DVXUSER, else that folder's USER), for Setup.

    platformAppUserDir is a program's folder in the user folder: APPS\KPUNCH\TERMINAL\TERMINAL.APP gives USER\APPS\KPUNCH\TERMINAL; a program outside the DVX folder gives USER\APPS\<its file name without the extension>. It is not made here (the shell's userDir and BASIC's App.User are made on first use).

    -

    SHARED and the user folder have the same four kinds of folder (DvxKindE). platformKindName is a kind's folder name; platformKindDir is SHARED's (user false) or the user's (user true); platformKindBrowseDir is where a file dialog for the kind starts (the user's when it exists, else SHARED's). platformKindFind finds a file of a kind by name, the user's first, so a user's file of the same name wins; a name with a folder in it is taken as the path it is. platformKindNameOf is the other way: a file directly in one of the kind's folders gives its name alone, which is what settings store (DVX.INI [sounds] and [desktop] wallpaper), so moving the user folder or DVX keeps them working. platformKindReadDir lists both folders: the user's names (sorted, case ignored), then SHARED's not among them; free it with dvxReadDirFree. BASIC: FileOpsUserDir$, FileOpsUserPath$, FileOpsUserInTemp, FileOpsKindDir$, FileOpsKindBrowseDir$, FileOpsKindFind$, FileOpsKindName$, and FileOpsKindList (fileops.bas), and App.User for a program's own folder.

    +

    SHARED and the user folder have the same five kinds of folder (DvxKindE): fonts, sounds, themes, desktop pictures, and MIDI instrument banks. Each may hold folders of add-on vendors (SHARED\FONTS\ACME), one level deep, whose files are named VENDOR\NAME. platformKindName is a kind's folder name; platformKindDir is SHARED's (user false) or the user's (user true); platformKindBrowseDir is where a file dialog for the kind starts (the user's when it exists, else SHARED's). platformKindFind finds a file of a kind by name (NAME or VENDOR\NAME, either separator), the user's first, so a user's file of the same name wins; any other name with a folder in it, or an absolute one, is taken as the path it is (a VENDOR\NAME not in the kind's folders is tried that way too). platformKindNameOf is the other way: a file in one of the kind's folders gives its name alone, or VENDOR\NAME in a vendor's folder, which is what settings store (DVX.INI [sounds], [desktop] wallpaper, and [audio] midiBank), so moving the user folder or DVX keeps them working. platformKindReadDir lists both folders: the user's names (sorted, case ignored), then SHARED's not among them, with each vendor folder's files as VENDOR\NAME (the folders themselves are not listed); free it with dvxReadDirFree. BASIC: FileOpsUserDir$, FileOpsUserPath$, FileOpsUserInTemp, FileOpsKindDir$, FileOpsKindBrowseDir$, FileOpsKindFind$, FileOpsKindName$, and FileOpsKindList (fileops.bas, with FILEOPS_KIND_FONTS to FILEOPS_KIND_BANKS), and App.User for a program's own folder.

    Read-only media: platformSetReadOnly / platformIsReadOnly / platformDirIsWritable / platformTempDir / platformSetLogMemory / platformLogMemory

    #define PLATFORM_LOG_MEM_MAX 16384
     
    @@ -7031,7 +7134,7 @@ audioFmFree(audio, v);                          // key off, back to everyone
    +  midiBank     the OP2 instrument bank for FM MIDI (music.lib): its name in SHARED\BANKS or the user's BANKS folder (an add-on's as VENDOR\NAME), or a path from the DVX folder; not set, the bundled one (Control Panel's Sounds page picks it)

    A settings screen reads and writes the keys it offers through the library, so the key names live in one place (audio.h, Settings; prefs is a copy of DVX.INI the caller loads and saves): audioSettingDriver(prefs) (AUDIO_DRIVER_AUTO when missing), audioSettingMidi(prefs) (an index in audioMidiChoices, auto when missing or unknown), audioSettingVolume(prefs) (percent, clamped to 0..100, 100 when missing), and audioSettingsStore(prefs, driver, midi, volume) writes all three (an empty driver stores auto, a midi index out of range stores auto). audioMidiChoices[AUDIO_MIDI_CHOICE_COUNT] holds the midi choices in list order, each with its label for people and the name stored (a PrefsChoiceT, dvxPrefs.h). audioPercentToVolume(percent) turns a percent into 0..AUDIO_VOLUME_MAX for audioSetMasterVolume. The device reads the driver and volume through the same functions when it starts. The Control Panel's Sounds page uses all of these.

    Desktop Sounds

    The library plays the sounds DVX makes for its events (dvxPlaySound in dvxApp.h): when it loads it sets DVX's sound hook to audioDeskPlay(event, wait). The file of each event is read from DVX.INI [sounds] at the first sound: the key is audioDeskKey(event) and the value a WAV or VOC file, empty or missing for none: a name alone is found in the sound folders (the user folder's SOUNDS first, then SHARED\SOUNDS: platformKindFind), anything else is a path; audioDeskReload makes the next sound read the section again (the Control Panel calls it after saving). The sounds play on a context of the library's own, so no program's end cuts one short, at full volume under the master volume. When no sound device starts, the sounds stay silent without trying again until the next reload. With wait the call returns once the sound has played (at most 5 seconds); after the exit sound further events are quiet (DVX is ending). audioDeskLabel(event) names an event for people, audioDeskPlayFile(path) plays any file the same way (to try one out). audioDeskFile(prefs, event) reads an event's file from a copy of DVX.INI ("" for none) and audioDeskSetFile(prefs, event, path) sets it; the library's own reading of [sounds] goes through audioDeskFile too. The default set, made by DVX's own sound generator at build time, is in SHARED\SOUNDS.

    @@ -8821,6 +8924,44 @@ free(matches);

    src/include/basic/hlpfile.bas declares the BASIC functions (hlpFileBas.c): HlpOpen, HlpClose, HlpTopicCount, HlpTopicId$, HlpTopicTitle$, HlpFindTopic, HlpStartTopic, HlpTocCount, HlpTocTitle$, HlpTocTopic, HlpTocDepth, HlpTocExpanded, HlpIndexCount, HlpIndexKeyword$, HlpIndexTopic, HlpSearch, HlpSearchTopic, HlpLoadTopic, HlpRecordType, HlpRecordFlags, HlpRecordText$, HlpRecordLink$, HlpRecordImageSave, and HlpWrap$, with the HLP_REC_, HLP_NOTE_, and HLP_IMG_ constants of hlpFormat.h.

    A handle (a small number, 0 = none) is an open file plus what the program last asked of it -- HlpSearch keeps the topics found for HlpSearchTopic, HlpLoadTopic copies a topic's records for the HlpRecord functions -- since BASIC cannot take a callback or a byte array. Each handle records the program that opened it; the shell's app-cleanup hook closes what a program leaves open when it ends. hlpFileBasHandle(handle) gives the HlpFileT behind a handle to C code (the HelpPane control's ShowTopic uses it). A returned string stays valid until the next call returning one (the BASIC runtime copies it at once).

    +
    +

    HTTP Library

    +

    An HTTP/1.1 client: GET, POST, and other requests to a web server over the network library's sockets, run a step at a time like the transfer and package jobs, so a program draws its progress and stays responsive. Software talks to the package server with it.

    +

    Header: http/http.h

    +

    Loaded as: bin/libs/kpunch/http/http.lib (it needs the network library, dvxnet)

    +

    BASIC: http.bas (HttpOpen, HttpStart, HttpRunFor, HttpStatus, HttpBody$, ...).

    +

    A Request

    +
    HttpT *h = httpCreate("GET", "http://example.com/v1/catalogue");
    +
    +if (h && httpStart(h, nowMs)) {
    +    while (httpPoll(h, nowMs) == HttpBusyE) {
    +        // draw progress (httpProgress), yield to other programs
    +    }
    +}
    +
    +// HttpDoneE: httpStatus(h) is 200, 404, ...; httpBody(h, &len) the reply
    +// HttpFailedE: httpMessage(h) says why
    +httpFree(h);
    +

    nowMs is a millisecond clock (PLATFORM_TICKS_TO_MS(platformClock())); it may wrap. Each httpPoll does one bounded piece of work: start connecting, send, or read up to 4 KB; on DOS it also lets the network stack run. Every request says Connection: close.

    +
      Function                                    Description
    +  --------                                    -----------
    +  httpCreate(method, url)                     A request; NULL when out of memory.
    +  httpHeaderAdd(h, name, value)               A header to send (before starting).
    +  httpBodySet(h, data, len, contentType)      A body to send (copied); Content-Length is sent for it, and for any POST or PUT.
    +  httpSaveTo(h, path, resume)                 The body to a file instead of memory; with resume, a part already there is kept and the rest asked for (a Range request).
    +  httpTimeoutSet(h, ms)                       Give up after ms without progress (HTTP_TIMEOUT_DEFAULT_MS, 30 seconds, to start with).
    +  httpStart(h, nowMs)                         False (httpMessage) for an address that is not http://.
    +  httpPoll(h, nowMs)                          HttpBusyE, HttpDoneE, or HttpFailedE.
    +  httpProgress(h, &done, &total)              Bytes of the body so far (counting a resumed file's first part), and the whole, HTTP_SIZE_UNKNOWN until the server says.
    +  httpStatus(h), httpHeader(h, name)          The reply's status and its headers (any case).
    +  httpBody(h, &len)                           The body in memory, NUL-terminated ("" when it went to a file).
    +  httpUrl(h)                                  Where the reply came from, after redirects.
    +  httpUrlParse(url, &parts, err, size)        Split an address into host, port, and path.
    +

    What Comes Back

    +

    Any status is an answer (HttpDoneE): the program decides what 404 or 500 means. HttpFailedE is for no answer: a bad address, a name that cannot be found, no connection, silence past the timeout, a reply that is not HTTP, or one cut short. Bodies may be sized (Content-Length), chunked, or end when the server closes; they go to memory up to HTTP_MEMORY_MAX (1 MB), past which the request fails -- save big ones to a file. Redirects (301, 302, 303, 307, 308) are followed, HTTP_REDIRECTS_MAX (5) at most; after a 303, or a 301 or 302 of a POST, the request becomes a GET without its body.

    +

    Saving to a file: only a 2xx body is written to it. A resumed download that the server answers with 206 is appended to the part there; one it answers with 200 (it will not resume) starts the file again; 416 (asked for past the end: the file was already whole) leaves it as it is. Any other status's body -- an error page -- goes to memory and the file stays as it was, so a download can be resumed later. Check the file afterwards (its size and SHA-256, as the package library does) before using it.

    +

    Only plain http:// works: there is no encryption, so send nothing secret, and check what comes back by its signature or hash.

    +

    Joystick Library

    Game controllers for DVX programs behind one API, whatever the hardware: the devices the drivers found, axes calibrated to -32768..32767 with a dead zone round the centre, buttons, hats, read when the program wants (polled) or delivered as events. A program's handles close when it ends, however it ends. Calibration and the other settings live in DVX.INI [joystick]; the Control Panel's Game Controllers page sets them.

    @@ -8881,6 +9022,37 @@ free(matches);

    For a settings program (the Control Panel's Game Controllers page) it also declares: JoyHats, JoyAxisPresent(joy, axis), JoyCanCalibrate(joy) (JoyInfoT hatCount, axisPresent, JOY_CAP_CALIBRATE); JoyConnected(joy) (joyRead's connected); JoyRescan (joyRescanApp for the calling program: its own handles and STICK readings go first; -1 while another program has a joystick open); JoyRawAxis(joy, axis) and JoyRawButtons(joy) (joyGetRaw); JoySetCalibration(joy, axis, minV, centerV, maxV) and JoyClearCalibration(joy, axis) (joySetCalibration); JoyDeadZone / JoySetDeadZone (joyGetDeadZone / joySetDeadZone); JoyChoiceCount(which), JoyChoiceLabel$(which, index), JoyChoice(which), JoySetChoice(which, index) with JOY_CHOICE_GAMEPORT = 0 and JOY_CHOICE_FALLBACK = 1 (JoyChoiceE; joyChoices / joyChoiceGet / joyChoiceSet); JoyKeyCount and JoyKeyName$(index); JoyKbdSlotCount, JoyKbdSlot(slot), JoySetKbdSlot(slot, keyIndex) (joyKbdSlotGet / Set); JoySetting$(key, defaultValue) and JoySetSetting(key, value) ("" removes; joySettingGet / Set); JoySettingsLoad(prefs) and JoySettingsStore(prefs) with a prefs.bas handle (prefsBasHandle); JoyCalibration(joy, axis, part) (joyGetCalibration; part JOY_CAL_MIN / JOY_CAL_CENTER / JOY_CAL_MAX, joystick.h; 0 when joyGetCalibration fails); JoyIsKeyboard(joy) (JOY_CAP_KEYBOARD); JoyKeyIndex(keyName), JoyKeyCode(index), JoyKeyNameOf$(rawCode) (joyKeyIndex / joyKeyCode / joyKeyNameOf). CONSTs JOY_DEADZONE_DEFAULT / _MAX, JOY_POLL_HZ_DEFAULT / _MIN / _MAX, and JOY_KEY_POLL_HZ mirror joystick.h. Lists (choices, keys, slots) are numbered from 0; True is -1.

    The BASIC runtime calls joyBasStick(n) and joyBasStrig(n) for QBasic's STICK and STRIG. Joystick A is device 0 (axes 0-1, buttons 0 and 1); joystick B is device 1, or axes 2-3 and buttons 2-3 of device 0 when it is the only device and has more than two axes. STICK(0) reads A's X and remembers A's Y and B's X and Y for STICK(1) to STICK(3) (per program, forgotten when it ends); positions are 1 to 200 ((value - JOY_AXIS_MIN) x 199 / 65535 + 1), 0 without that joystick. Even STRIG numbers use joyButtonWentDown (0 A button 1, 2 B button 1, 4 A button 2, 6 B button 2), odd ones the state joyRead reports; True is -1.

    +
    +

    JSON Library

    +

    Reads JSON text -- a web service's reply, a settings file -- into a tree, finds values in it by path, and quotes text for JSON of your own. The package server's replies are read with it.

    +

    Header: json/json.h

    +

    Loaded as: bin/libs/kpunch/json/json.lib

    +

    BASIC: json.bas (JsonParse, JsonText$, JsonNumber, JsonCount, JsonKey$, JsonQuote$, ...).

    +

    Reading

    +

    jsonParse(text, len, err, errSize) reads a document and returns it (NULL when it is not JSON: err says why and on which line; jsonFree frees it). Its nodes are numbered from 0, the root. jsonFind(doc, node, path) finds a node from another: a path is member names and [index]es, such as packages[3].version, [0], or "" (the node itself); members match by exact name, the first when a name repeats. JSON_NO_NODE stands for a node that is not there, and every function accepts it.

    +
      Function                          Description
    +  --------                          -----------
    +  jsonType(doc, node)               JsonNullE, JsonBoolE, JsonNumberE, JsonStringE, JsonArrayE, JsonObjectE; JsonNoneE for no node.
    +  jsonText(doc, node)               A string's text ("" for anything else).
    +  jsonNumber(doc, node)             A number's value, a double (0 for anything else).
    +  jsonBool(doc, node)               True only for true.
    +  jsonCount(doc, node)              An array's items or an object's members (0 for anything else).
    +  jsonChild(doc, node, index)       Item or member index, in order.
    +  jsonKey(doc, node)                A member's name ("" for anything else).
    +

    Strings are held decoded: the escapes \n, \t, \", \\, \/, \b, \f, and \r become their characters, \u escapes below 128 their character and others '?'; other bytes, UTF-8 included, pass through. Arrays and objects nest JSON_DEPTH_MAX (64) deep at most.

    +
    char      err[JSON_ERR_MAX];
    +JsonDocT *doc = jsonParse(text, len, err, sizeof(err));
    +
    +for (int32_t i = 0; doc && i < jsonCount(doc, jsonFind(doc, 0, "packages")); i++) {
    +    int32_t pkg = jsonChild(doc, jsonFind(doc, 0, "packages"), i);
    +
    +    printf("%s %s\n", jsonText(doc, jsonFind(doc, pkg, "id")), jsonText(doc, jsonFind(doc, pkg, "version")));
    +}
    +
    +jsonFree(doc);
    +

    Writing

    +

    jsonQuote(text, out, outSize) writes text as a JSON string -- quotes, escapes, control characters as \u00XX -- and returns the length it needs (as snprintf does), so a NULL out with size 0 measures it.

    +

    Writing Games for DVX

    A game in DVX is a program with its own main loop (hasMainLoop = true in its SHELL_APP_DESCRIPTOR). DVX gives it its own task: appMain runs the game loop itself, draws into the game's window, and waits for each frame on the high-resolution clock while the other programs keep running. Nothing in DVX has to stop for a game: the desktop, other programs, the Task Manager (Ctrl+Esc), and help all stay available.

    @@ -9164,7 +9336,7 @@ End Sub
      Command                            Does
       -------                            ----
       make                               Builds the module with its resources, and NAME.hlp from the help source
    -  make package                       Lays out pkg/ exactly as the files go into the DVX directory
    +  make package                       Lays out pkg/ exactly as the files go into the DVX directory, and makes NAME.dpk, the add-on package Software installs
       make install DVX_DIR=/path/to/dvx  Packages, then copies pkg/ into a DVX installation
       make clean                         Removes everything the build made

    Every .c file in the project directory is compiled with warnings as errors (-Wall -Wextra -Werror) and every SDK include directory on the path. Add your own flags with CFLAGS, for example CFLAGS=-I../myLib to use a library's header. VENDOR (default local) is the vendor directory the module installs under.

    @@ -9415,7 +9587,7 @@ End Sub

    Packaging and Installing

    -

    make package lays out pkg/ exactly as the files go into the DVX directory; make install DVX_DIR=... copies it there. VENDOR (default local) names the vendor directory; use a short name of your own (8 characters at most).

    +

    make package lays out pkg/ exactly as the files go into the DVX directory and makes NAME.dpk from it, the add-on package anyone installs with DVX's Software program; make install DVX_DIR=... copies pkg/ into a DVX directory. VENDOR (default local) names the vendor directory; use a short name of your own (8 characters at most: a-z, 0-9, and _, since the package name is VENDOR.NAME in lower case).

      Kind       Directory                     Files
       ----       ---------                     -----
       Program    APPS/<VENDOR>/<NAME>/         NAME.app, NAME.hlp, DATA/ with the DATA files (if any)
    @@ -9425,9 +9597,28 @@ End Sub
  • Programs carry their compiled help. Widgets and libraries carry help sources, which DVX compiles into its system help when the set of installed modules changes.
  • .dep files are written with DOS line endings.
  • A program's data files (levels, graphics, sounds it loads at run time) go in the Makefile's DATA variable, for example DATA = levels sounds; the package puts them in the DATA folder beside NAME.app, where the program finds them through shellDataPath (BASIC: App.Data). Small ones can instead travel inside the .app as binary resources (see the .res file).
  • -
  • To ship a module, zip the contents of pkg/; the user unpacks it into the DVX directory. A library's header and .bas file go to the developers who use it, not into the DVX directory.
  • -
  • Removing a module is deleting its directory.
  • +
  • To ship a module, give out NAME.dpk: Software installs it (and what it needs from the same folder), records it, and removes it cleanly again. A library's header and .bas file go to the developers who use it, not into the package.
  • +

    The Add-on Package

    +

    The package's details come from sdk.mk settings, on the make command line or in the project Makefile:

    +
      Setting         Meaning
    +  -------         -------
    +  PKG_TITLE       The name Software shows (default NAME)
    +  PKG_VERSION     major.minor.patch (default 1.0.0); raise it for every version you hand out, so Software installs it over the old one
    +  PKG_SUMMARY     A sentence about it
    +  PKG_PUBLISHER   Who publishes it; also PKG_AUTHOR, PKG_LICENCE, and PKG_HOMEPAGE
    +  PKG_NEEDS       The packages it needs, id=oldest-version each: PKG_NEEDS="acme.cards=1.0 acme.dice=2.1"
    +  PKG_KEY         The secret key file to sign it with
    +

    Programs, libraries, and widgets are marked with the binary interface version of the SDK that built them (see Binary Interface Version); Software will not install them on a DVX of another version. Sign packages so the people who install them can tell they are yours:

    +
    bin/dvxpkg keygen "Acme Games" acme              # once: acme.SEC and acme.KEY
    +make package PKG_KEY=acme.SEC PKG_VERSION=1.1.0
    +bin/dvxpkg info NAME.dpk                         # what the package says
    +

    Give acme.KEY to the people who install your packages (Software, File > Trust a Publisher's Key) and keep acme.SEC private and backed up: anyone who has it can sign as you, and an update must be signed with the key that signed the version installed. bin/dvxpkg also signs and checks packages and lists their contents; on DVX, SYSTEM/DVXPKG.EXE does the same, and installs and removes packages. The package library and its file format are described under Package Library.

    +

    Publishing on the Package Server

    +

    Software's Available and Updates pages list the packages on the DVX package server. To publish one there, make an account on the server (POST /v1/account/register, then the link in the mail), give its email and password in Software's File > Package Server, and choose File > Publish a Package; Software's Published page shows each version's review. From the build machine, sign in (POST /v1/account/login gives a session token) and upload the package with it:

    +
    curl -H "Authorization: Bearer TOKEN" -H "Content-Type: application/octet-stream" \
    +     --data-binary @NAME.dpk http://SERVER/v1/upload
    +

    The server checks the package as DVX does before installing it and holds it for review; GET /v1/mine shows its state, and the note when it is turned down. Once the server's operator approves it, it is signed with DVX's repository key and listed, and every DVX that has an older version is offered the update. An id belongs to the account that first uploaded it, so later versions must come from the same account; ids of the vendor kpunch are DVX's own. The server's API and its rules are in server/README.md in the DVX source tree.

    libtasks -- Cooperative Task Switching

    @@ -9889,7 +10080,7 @@ if (rc != MUSIC_OK) { software the software synthesizer

    On the FM chip and the MPU-401 port the song is played by the timer service (500 times a second), so it keeps time however busy the programs are. On the FM chip every note takes a voice from audio.lib's shared pool; when none is free the note plays on the software synthesizer instead, which then runs beside the chip (its stream is always open for that). The software synthesizer runs in the stream's fill function (the shell's idle time), 64 frames between event checks, 150 ms ahead of what is heard; a program that keeps the desktop busy every frame should call audioPump once a frame, as for any stream.

    General MIDI on FM

    -

    Instruments come from an OP2 bank (the DMX "GENMIDI" format: 128 melodic instruments and 47 percussion instruments for notes 35 to 81, each one or two two-operator voices, with a note offset, a fixed note for percussion, and a detune for the second voice). DVX ships Freedoom's GENMIDI bank (see Licences below); DVX.INI [audio] midiBank names another OP2 file instead (a path from the DVX folder), for example a bank made with an OPL editor, or another freely licensed bank such as DMXOPL. The bank is read once, at the first MIDI music that needs it.

    +

    Instruments come from an OP2 bank (the DMX "GENMIDI" format: 128 melodic instruments and 47 percussion instruments for notes 35 to 81, each one or two two-operator voices, with a note offset, a fixed note for percussion, and a detune for the second voice). DVX ships Freedoom's GENMIDI bank (see Licences below); DVX.INI [audio] midiBank (MUSIC_INI_BANK) names another OP2 file instead: a bank in SHARED\BANKS or the user's BANKS folder by its name (an add-on package's as VENDOR\NAME), or any OP2 file by its path from the DVX folder, for example a bank made with an OPL editor, or another freely licensed bank such as DMXOPL. Control Panel's Sounds page lists the banks to choose from. A bank that is not there falls back to the bundled one (MUSIC_BANK_DEFAULT). The bank is read once, at the first MIDI music that needs it, so a new choice takes effect when DVX next starts.

    What is played: note on / off, velocity, program change, channel volume (CC7), expression (CC11), pan (CC10: left, both, or right on an OPL3 and the software synthesizer), sustain pedal (CC64), pitch bend with the bend range from RPN 0 (default 2 semitones), all notes off (CC123, CC120), reset controllers (CC121), and General MIDI System On (a reset). Channel 10 plays percussion. Loudness is velocity x volume x expression x the music's volume on a 40 log10 curve, added as attenuation to the voice's level. OP2 banks are tuned for DMX, which sounds note n where MIDI has note n + 12, so notes are played an octave up from the bank's offsets. Not played: modulation (CC1), aftertouch, the FM chip's own rhythm mode, four-operator voices.

    The Software Synthesizer

    When there is no FM chip (or it is busy), MIDI plays on DVX's own software FM synthesizer: an integer two-operator FM chip modelled on the OPL2 (the same log-sine and exponential tables, waveforms 0 to 3, feedback, the chip's envelope rates and key scaling, key scale level, tremolo, and vibrato) with 24 voices, rendering at the device's rate into a stream. It is driven with the same bank and the same register values as the chip, so music sounds alike on both. The host tests compare it with emu8950, an accurate OPL2 emulator, for several patches: pitch, loudness over time, and harmonics agree closely. Cost on a 486: roughly 1 to 2 percent of a 486DX2-66 per sounding voice at 22,050 Hz.

    @@ -9929,6 +10120,142 @@ musicMmlPlay(tune, "MB T140 O3 L8 CDEC CDEC EFG4 EFG4");

    The GENMIDI instrument bank is from Freedoom 0.13.0, Copyright (C) 2001-2024 Contributors to the Freedoom project. All rights reserved. BSD 3-clause licence:

    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the Freedoom project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

    +
    +

    Package Server Library

    +

    The client of a DVX package server: what it offers (its catalogue), what it has that is newer than the packages installed (DVX itself among them), and fetching packages, with everything they need that is not installed, into a download folder; and, with an account on the server, publishing packages and listing what the account has sent. There the package library reads them (pkgOffersInFolder) and plans and installs them like any package file, so a package from the server is checked and trusted exactly as one from a disk. Software uses it for its Available and Updates pages.

    +

    Header: pkgnet/pkgNet.h

    +

    Loaded as: bin/libs/kpunch/pkgnet/pkgnet.lib (it needs the package, HTTP, and JSON libraries)

    +

    BASIC: pkgnet.bas (PkgNetOpen, PkgNetCatalogue, PkgNetUpdates, PkgNetFetch, PkgNetRunFor, PkgNetRows$, ...), with pkg.bas's PkgOfferFolder to plan what was fetched.

    +

    A Fetch

    +
    PkgNetT *n = pkgNetCreate(server, "", downloadDir);
    +const char *ids[] = { "acme.chess" };
    +
    +if (n && pkgNetFetchStart(n, ids, 1, nowMs)) {
    +    while (pkgNetPoll(n, nowMs) == PkgNetBusyE) {
    +        // draw progress (pkgNetProgress, pkgNetCurrent), yield
    +    }
    +}
    +
    +// PkgNetDoneE: plan and install from pkgNetDir(n) (pkgPlan.h), then pkgNetClear(n)
    +// PkgNetFailedE: pkgNetMessage(n) says why
    +pkgNetFree(n);
    +

    One job at a time; each poll does one piece of it (a piece of a request, or a slice of checking a file). nowMs is a millisecond clock, as for the HTTP library.

    +
      Function                                    Description
    +  --------                                    -----------
    +  pkgNetCreate(server, root, dir)             A client of the server at server ("http://host[:port]"), for the DVX folder root ("" the current one), fetching into dir.
    +  pkgNetCatalogueStart(n, nowMs)              Read the catalogue: every package the server offers, at its newest version.
    +  pkgNetUpdatesStart(n, nowMs)                Ask what is newer than the packages installed (this DVX's version and ABI are sent with the list).
    +  pkgNetFetchStart(n, ids, count, nowMs)      Fetch packages by id, with what they need that is not installed in a version that will do.
    +  pkgNetPoll(n, nowMs)                        PkgNetBusyE, PkgNetDoneE, or PkgNetFailedE.
    +  pkgNetCancel(n)                             Stop the job; a download stopped part way is kept to resume.
    +  pkgNetEntries(n, &count)                    The answer: the catalogue (after reading it or fetching) or the updates, as PkgNetEntryT (the manifest's fields and needs, the package file's size and SHA-256, and whether the repository signed it).
    +  pkgNetDb(n)                                 The installed packages, as the last job read them.
    +  pkgNetProgress(n, &done, &total)            A fetch's bytes so far and in all.
    +  pkgNetCurrent(n), pkgNetMessage(n)          The package the job is on, and why it failed.
    +  pkgNetDir(n), pkgNetClear(n)                The download folder, and removing what the last fetch put there.
    +  pkgNetConfigRead(&c), pkgNetConfigWrite(&c)  DVX.INI's [packages]: server (a host name), port, user (an account's email), and password (scrambled by prefsSetSecret), as a PkgNetConfigT (the defaults: PKGNET_SERVER_DEFAULT, port 80, no account).
    +  pkgNetConfigUrl(&c, out, size)              The address they make: "http://host", with ":port" when not 80.
    +  pkgNetAccountSet(n, user, password)         The account the handle's account jobs use.
    +  pkgNetUploadStart(n, path, nowMs)           Publish the package file at path for the server's review.
    +  pkgNetMineStart(n, nowMs), pkgNetMine(n, &count)  What the account has sent, each version with its state (pending, approved, rejected, withdrawn) and the review's note.
    +

    What a Fetch Checks

    +

    Each package fetched must have exactly the size and SHA-256 the catalogue gives; one that does not is removed and the fetch fails. The repository's signature of it is fetched beside it (NAME.SIG), so whether DVX trusts it is decided as for any package: a package signed by a key in SYSTEM\KEYS (DVX's own repository key) is trusted by DVX. The server itself is never trusted: it is plain HTTP, and only the signatures count.

    +

    Each package's file is named from its id and version (eight hex digits, a DOS name), so a download that stops part way (NAME.PRT) goes on from where it stopped the next time the same version is fetched, and one fetched before is checked again rather than downloaded again.

    +

    What a package needs that no version on the server will do for is left for the planner to name. Installing what was fetched is the package library's job: read the folder's offers, plan, and run the install as a job.

    +

    An Account

    +

    The account's jobs sign in first (POST /v1/account/login) when the handle has no session yet, keep the session for the handle's later jobs, and sign in once more when the server answers that the session has gone. A wrong email or password fails the job saying so. The password is DVX.INI's (prefsGetSecret); the session's token is kept only in memory and wiped, with the password, when the handle is freed.

    +
    +
    +

    Package Library

    +

    Add-on packages: the .DPK file format, manifests, Ed25519 keys and signatures, the packages installed in a DVX folder, the keys DVX trusts, planning what an install or removal takes, and installing and removing a package a step at a time. Software, Package Builder, the IDE's File > Make Package, DVXPKG.EXE, the build machine's dvxpkg, and Setup all use it.

    +

    Header: pkg/pkg.h (it includes the others below)

    +

    Loaded as: bin/libs/kpunch/pkg/pkg.lib

    +

    BASIC: pkg.bas (sessions, offers, plans, install and remove jobs, building, signing, and keys).

    +

    A Package

    +

    A package is NAME.DPK: a ZIP read front to back, whose first entry is PACKAGE.INI, the manifest; then PACKAGE.SIG when the publisher signed it; then the files, named relative to the DVX folder with / between parts.

    +
    [package]
    +format    = 1
    +id        = acme.chess          ; vendor.name, each 1..8 of a-z 0-9 _
    +name      = Chess
    +version   = 1.2.0
    +kind      = app                 ; app library widget font bank theme wallpaper sound help bundle system
    +summary   = Play chess against the computer.
    +publisher = Acme Games          ; also author, licence, homepage
    +dvx       = 1.0.0               ; the oldest DVX it runs on
    +abi       = 10                  ; programs, libraries, widgets: DVX_ABI_VERSION
    +[depends]
    +acme.cards = 1.0.0              ; id = the oldest version that will do
    +[files]
    +APPS/ACME/CHESS/CHESS.APP = 61234 <SHA-256, 64 hex digits>
    +

    Every file is listed with its size and SHA-256 and every listed file is in the package, so the one signature over the manifest's bytes covers everything. Each kind keeps to its own folders (pkgKindFolder gives the folder for an id; pkgFileAllowed checks a name):

    +
      Kind                  Folder
    +  ----                  ------
    +  app, help             APPS/VENDOR/NAME (help may also go beside a library or widget of its name)
    +  library, widget       LIBS/VENDOR/NAME, WIDGETS/VENDOR/NAME
    +  font, bank            SHARED/FONTS/VENDOR, SHARED/BANKS/VENDOR
    +  theme, wallpaper      SHARED/THEMES/VENDOR, SHARED/WPAPER/VENDOR
    +  sound                 SHARED/SOUNDS/VENDOR
    +  bundle                no files: a set of packages, its dependencies
    +  system                DVX itself (kpunch.dvx): anywhere but the user folder
    +

    Manifests (pkgManifest.h)

    +
      Function                                       Description
    +  --------                                       -----------
    +  pkgManifestParse(text, len, &m, err, size)     A manifest's text; then pkgManifestCheck before trusting it.
    +  pkgManifestCheck(&m, err, size)                The id, the kind's rules, every file's name and folder, nothing listed twice, no dependency on itself.
    +  pkgManifestFormat(&m, &len)                    The text (CR LF lines), malloc'd.
    +  pkgTemplateRead(path, &m, err, size)           A template: a manifest whose format and [files] a build writes, so it may leave them out; other sections are ignored.
    +  pkgKindFolder(kind, id, out, size)             The folder a package id of kind keeps its files in (APPS/ACME/CHESS); false for bundle and system.
    +  pkgKindIsCode(kind)                            Programs, libraries, and widgets: their manifests need an abi.
    +  pkgIdValid(id), pkgIdSplit(id, vendor, name)   Package ids.
    +  pkgVersionParse, pkgVersionFormat, pkgVersionCompare   major.minor.patch versions.
    +

    Keys and Signatures (pkgSign.h)

    +

    A key pair is NAME.KEY (public: id, name, public key) and NAME.SEC (secret: id, name, seed), small INI files. A key's id is the first 8 bytes of its public key's SHA-256, so a signature names its key. The repository key that signs DVX's own packages is kept offline; the server never holds a secret key.

    +
      Function                                             Description
    +  --------                                             -----------
    +  pkgRandomBytes(buf, len)                             Random bytes for a seed: the security library's generator topped up with fresh hardware entropy.
    +  pkgKeyWrite(seed, name, base, &key, err, size)       Make a pair from seed and write BASE.KEY and BASE.SEC.
    +  pkgKeyRead, pkgSecretRead, pkgSigRead                Read a key, secret key, or signature file.
    +  pkgSign(&secret, msg, len, &sig)                     Sign bytes (a manifest's).
    +  pkgSigCheck(&sig, &key, msg, len)                    True when key made sig over the bytes.
    +  pkgSecretWipe(&secret)                               Clear a secret key from memory once it has signed.
    +

    Building and Reading Packages (pkgDpk.h)

    +
      Function                                                       Description
    +  --------                                                       -----------
    +  pkgDpkBuild(&templ, folder, out, secret, err, size)            Build from a template and every file under folder, named relative to it (a folder laid out like the DVX folder); secret NULL: unsigned. A program, library, or widget without abi gets DVX_ABI_VERSION.
    +  pkgDpkBuildFiles(&templ, files, count, out, secret, err, size) The same from a list of PkgDpkFileT (the name in the package, the file it comes from).
    +  pkgDpkFolderFiles(folder, prefix, &files)                      Every file under folder as that list, named prefix/...; pkgDpkFilesFree frees it.
    +  pkgDpkReadInfo(path, &info, err, size)                         The manifest and PACKAGE.SIG, checked; pkgDpkInfoFree.
    +  pkgDpkCheckFiles(path, &info, err, size)                       Every file's size and SHA-256 against the manifest.
    +  pkgDpkSign(path, &secret, detached, err, size)                 Sign into the package, or beside it as NAME.SIG.
    +  pkgDpkSigner(&info, detached, keys, count)                     Which of keys made the signature (or the detached one).
    +
    PkgManifestT templ;
    +PkgSecretT   secret;
    +char         err[PKG_ERR_MAX];
    +bool         ok;
    +
    +ok = pkgTemplateRead("CHESS.INI", &templ, err, sizeof(err)) && pkgSecretRead("ACME.SEC", &secret, err, sizeof(err));
    +ok = ok && pkgDpkBuild(&templ, "STAGE", "CHESS.DPK", &secret, err, sizeof(err));
    +pkgSecretWipe(&secret);
    +pkgManifestFree(&templ);
    +

    Installed Packages and Trust (pkgDb.h, pkgTrust.h)

    +

    Each installed package has a record, SYSTEM/PACKAGES/VENDOR/NAME.INI: its manifest as installed, whether it was chosen or only needed by another, and the key that signed it. pkgDbLoad reads them all; pkgDbFind, pkgDbOwner (who has a file), pkgDbPut, and pkgDbDrop keep them. Setup writes DVX's own record (kpunch.dvx, with no files).

    +

    pkgKeyringLoad reads the keys DVX trusts: SYSTEM/KEYS (DVX's own) and the user's KEYS folder (publishers the user chose); pkgKeyringAdd trusts another. pkgTrustOf says how far a package is trusted (PkgTrustSystemE, PkgTrustUserE, PkgTrustNoneE). The rules the planner keeps: kpunch.* packages must be signed by a SYSTEM key; an upgrade must be signed by the key that signed the installed version (or a SYSTEM key); an unsigned package from a file is installed only after a warning.

    +

    Planning (pkgPlan.h)

    +

    An offer (PkgOfferT) is a package that can be installed: pkgOfferRead reads one file, pkgOffersFor reads a file and every package in its folder (what it may need), pkgOffersInFolder a whole folder. pkgPlanInstall(db, offers, count, wanted, wantedCount, &plan) plans installing the offers wanted with everything they need, in the order they go in, each checked (the DVX and ABI it needs, files another package has, a newer version installed, the signing rules); plan.err says why when it fails, plan.bytes is what it unpacks, and plan.stale lists the add-ons an update of DVX itself would leave unable to load. pkgPlanDependents lists the installed packages that need some going (they go too); pkgPlanOrphans those only they needed.

    +

    Installing and Removing (pkgOp.h)

    +

    pkgOpInstall(db, path, ring, explicitly) and pkgOpRemove(db, id, removeChanged) make an operation; pkgOpStep runs it a bounded piece at a time (PKG_OP_STEP bytes) until PkgOpDoneE or PkgOpFailedE (pkgOpMessage says why), with pkgOpProgress for a progress bar. Installing unpacks every file under a scratch name beside where it goes, checking each against the manifest, and only then swaps them all in, keeping the files they replace until every swap is done (and putting them back if one fails); then it removes what an older version had that this one does not, and writes the record. Removing deletes the files unchanged since they were installed (all of them with removeChanged), the folders that leaves empty, and the record; pkgOpKept lists the changed files it left. pkgOpFree before the swap cancels an install; after it, the operation is finished first.

    +

    The Package Tool

    +

    DVXPKG.EXE (DVX's SYSTEM folder) and bin/dvxpkg (the SDK, on the build machine) take the same commands:

    +
    dvxpkg build TEMPLATE.INI FOLDER OUT.DPK [KEY.SEC]
    +dvxpkg sign NAME.DPK KEY.SEC [-d]
    +dvxpkg verify NAME.DPK [-s NAME.SIG] [KEY.KEY ...]
    +dvxpkg info NAME.DPK
    +dvxpkg keygen "Key name" BASE
    +dvxpkg install NAME.DPK [-r DVXDIR] [-y]
    +dvxpkg remove VENDOR.NAME [-r DVXDIR] [-f]
    +dvxpkg list [-r DVXDIR]
    +

    On DOS, DVXPKG remove is the way out when an add-on keeps DVX from starting (DVX /SAFE, or holding Shift, also starts DVX without add-ons). The SDK's make package builds NAME.dpk from a project (sdk.mk's PKG_* settings), and the IDE's File > Make Package from a BASIC project.

    +

    Printing Library

    Printer ports and print jobs, text laid out on pages, pictures for dot-matrix and laser printers, the printer settings, and the common Print and Page Setup dialogs. Notepad, Image Viewer, Icon Editor, the Control Panel's Printer page, and BASIC's LPRINT all print through it; any program can.

    diff --git a/screenshots/86box/addons-addon-no.png b/screenshots/86box/addons-addon-no.png new file mode 100644 index 00000000..07e38fee --- /dev/null +++ b/screenshots/86box/addons-addon-no.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87a79d1bb9a57eba5bc045892b359d85c20a332a95de259c233ec36b1de3ef2e +size 18229 diff --git a/screenshots/86box/apps-clock.png b/screenshots/86box/apps-clock.png index b3aa3b16..aac25d3e 100644 --- a/screenshots/86box/apps-clock.png +++ b/screenshots/86box/apps-clock.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a6e4ccd7233092f377ac0398a323d2a57f465da9a91c633e8a09402794f5ebd -size 17977 +oid sha256:c47ef1d6f91f6bf682c65cffe7a635fac96e7b09d59fd05a54736b9ae8e72880 +size 17993 diff --git a/screenshots/86box/apps-cpanel.png b/screenshots/86box/apps-cpanel.png index 4df6fcb0..eb7203d1 100644 --- a/screenshots/86box/apps-cpanel.png +++ b/screenshots/86box/apps-cpanel.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf2fc814413dc000180596ba36f49716ec9571669a1959a2a5ce9bbfbad33f63 -size 16430 +oid sha256:6bf51593a279f8352dde3ef83d94942cb7d82d3522c91d2a3b43758674bd3535 +size 16427 diff --git a/screenshots/86box/apps-desktop.png b/screenshots/86box/apps-desktop.png index 4f9610d8..9fc09f1a 100644 --- a/screenshots/86box/apps-desktop.png +++ b/screenshots/86box/apps-desktop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d14ced0f9793e53e27952b0ea5f69475f373a8c389838877ccb71ae8170818f -size 18508 +oid sha256:0a697ed80887720ffc0e258516c4a59068e86521f79c4d9d777e5b6d703a73cf +size 18588 diff --git a/screenshots/86box/apps-dvxbasic.png b/screenshots/86box/apps-dvxbasic.png index adafd78e..16b7bf11 100644 --- a/screenshots/86box/apps-dvxbasic.png +++ b/screenshots/86box/apps-dvxbasic.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ca33ce84d1585ab033f84a3e9063897633471477cd038439db44659931e86999 -size 18318 +oid sha256:c5b9aedac500ad8ff4cbbb7b59bf49751f4bde23f6e8f8a3ae9f0f34424307a1 +size 18393 diff --git a/screenshots/86box/apps-iconed.png b/screenshots/86box/apps-iconed.png index f305a142..658cb98c 100644 --- a/screenshots/86box/apps-iconed.png +++ b/screenshots/86box/apps-iconed.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8ea0efb748f7f264cd034cdafbc33b5d35aa9ac0324be02d0daacf213aad97d -size 14855 +oid sha256:35e2e173eb0155a1799f985effc2d4100ea31be802ad7e312d5ca66751601fa0 +size 14832 diff --git a/screenshots/86box/apps-imgview.png b/screenshots/86box/apps-imgview.png index a50ad693..65619ad3 100644 --- a/screenshots/86box/apps-imgview.png +++ b/screenshots/86box/apps-imgview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c92d11a6a8ad8e9194751f84f37f126d728ee4789e075a2b55f17053c0d0fee1 -size 12908 +oid sha256:20216cfc2f5c629596ee18af24de18501b2431af8ee731ceb5be4427e219661b +size 12892 diff --git a/screenshots/86box/apps-notepad.png b/screenshots/86box/apps-notepad.png index 5185b7e5..f8d5861d 100644 --- a/screenshots/86box/apps-notepad.png +++ b/screenshots/86box/apps-notepad.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:690342da9774fdaaef6f8d681afbe50eb8e99158362c91d38e5c43dd89dc19d1 -size 13734 +oid sha256:cbbc99f6095370b23c0cda96486df13fb5c1e6654ff371b9a67b8fe62a116eda +size 13750 diff --git a/screenshots/86box/apps-resedit.png b/screenshots/86box/apps-resedit.png index 6778216b..4becf7fd 100644 --- a/screenshots/86box/apps-resedit.png +++ b/screenshots/86box/apps-resedit.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e27933594c4af7e9623583d9113f7ace247b8ae92eee8bfd602d1920eb266b26 -size 14149 +oid sha256:3fc86dd4c3acc583cdb962e02b1c3a563a67bd5d84c0ed1a19314937ab191c51 +size 14168 diff --git a/screenshots/86box/banked-bk-back.png b/screenshots/86box/banked-bk-back.png index 60f8bb96..7976b145 100644 --- a/screenshots/86box/banked-bk-back.png +++ b/screenshots/86box/banked-bk-back.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:991866c7119668909f5e578089f241f97d42b0e97c81c48cbc6f489bcddc0781 -size 18485 +oid sha256:3425e4a35d856bef29f68f9732c7fad18669dfd3ebd1178835c0191827029cfb +size 18540 diff --git a/screenshots/86box/banked-bk-desk.png b/screenshots/86box/banked-bk-desk.png index aaa46454..c93e492a 100644 --- a/screenshots/86box/banked-bk-desk.png +++ b/screenshots/86box/banked-bk-desk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:185883d27ca0d039b7f26acfadb1908a9c7aeb4a2cdd5216c9cca13478f2d4b5 -size 18376 +oid sha256:588e46fa25956b91e94945fa2c0ee8f26648a4748da1317a7997b72967defd4e +size 18426 diff --git a/screenshots/86box/banked-bk-drag.png b/screenshots/86box/banked-bk-drag.png index c7870511..51540044 100644 --- a/screenshots/86box/banked-bk-drag.png +++ b/screenshots/86box/banked-bk-drag.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b9512db9b61617ef2f25ccf302b3b600575ef5fd5b94d793c9dec199a597666d -size 15161 +oid sha256:d815eedadc4035fea2027775eb340ae6b33b2b4f9bf90115ddacfe565096f7d9 +size 15053 diff --git a/screenshots/86box/banked-bk-note.png b/screenshots/86box/banked-bk-note.png index 06a90b66..f80814d6 100644 --- a/screenshots/86box/banked-bk-note.png +++ b/screenshots/86box/banked-bk-note.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ed706f905ee45aadee7723995d1bb203a9c49de2d6e675bd96de0e470f0309a -size 13741 +oid sha256:1ffe8d8b71867ed7b7c676213a0b715f4de49565e1e83f0694466d2d701673cb +size 13754 diff --git a/screenshots/86box/boot-boot.png b/screenshots/86box/boot-boot.png index aaa46454..c93e492a 100644 --- a/screenshots/86box/boot-boot.png +++ b/screenshots/86box/boot-boot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:185883d27ca0d039b7f26acfadb1908a9c7aeb4a2cdd5216c9cca13478f2d4b5 -size 18376 +oid sha256:588e46fa25956b91e94945fa2c0ee8f26648a4748da1317a7997b72967defd4e +size 18426 diff --git a/screenshots/86box/cpanel-cp-color.png b/screenshots/86box/cpanel-cp-color.png index 00d41f40..5261386c 100644 --- a/screenshots/86box/cpanel-cp-color.png +++ b/screenshots/86box/cpanel-cp-color.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a695a45d25535a2cdfa489e932d221c87bc029e8e79991478ddcd1925d37b6e -size 18280 +oid sha256:660bf211134751affcd3a05e3d5e8e8c297cda750a417873a642c2ee72861542 +size 18266 diff --git a/screenshots/86box/cpanel-cp-desk.png b/screenshots/86box/cpanel-cp-desk.png index 76360a2e..c912aaa5 100644 --- a/screenshots/86box/cpanel-cp-desk.png +++ b/screenshots/86box/cpanel-cp-desk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cfe0af167d1535d62e34ddfd9e53deaaf6441e397d815d9e4246293b7e953be4 -size 14435 +oid sha256:97022e5d29c1c26e7d47625d3fb54121173c54b9f58f884a17c451a555a3b3b3 +size 14436 diff --git a/screenshots/86box/cpanel-cp-joy.png b/screenshots/86box/cpanel-cp-joy.png index 902d99a9..a2a740d4 100644 --- a/screenshots/86box/cpanel-cp-joy.png +++ b/screenshots/86box/cpanel-cp-joy.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c5abef727eff67db56905b030e31f83dea87ad20391a4af15427cffd4996256 -size 16348 +oid sha256:eb4bdad8d10caa593d3cfab42126f537714a1d2ea70e06ca64656e26fbecb785 +size 16334 diff --git a/screenshots/86box/cpanel-cp-mouse.png b/screenshots/86box/cpanel-cp-mouse.png index 4df6fcb0..eb7203d1 100644 --- a/screenshots/86box/cpanel-cp-mouse.png +++ b/screenshots/86box/cpanel-cp-mouse.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf2fc814413dc000180596ba36f49716ec9571669a1959a2a5ce9bbfbad33f63 -size 16430 +oid sha256:6bf51593a279f8352dde3ef83d94942cb7d82d3522c91d2a3b43758674bd3535 +size 16427 diff --git a/screenshots/86box/cpanel-cp-net.png b/screenshots/86box/cpanel-cp-net.png index e93f245d..8a0078a8 100644 --- a/screenshots/86box/cpanel-cp-net.png +++ b/screenshots/86box/cpanel-cp-net.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1f37bac1331a4f7d3c58ee9bd45aed805fdd8ffa95a059487e84f5ec5fb1e63 -size 16214 +oid sha256:27f0996dda3d29cd134257c40b19a6ef215ce45162999250c704004570995426 +size 16199 diff --git a/screenshots/86box/cpanel-cp-print.png b/screenshots/86box/cpanel-cp-print.png index bd712763..6b3a71c0 100644 --- a/screenshots/86box/cpanel-cp-print.png +++ b/screenshots/86box/cpanel-cp-print.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0978c06a46d4d39b7d09acc02714e6f48689346f67d05f94363b0819616fa9ff -size 15645 +oid sha256:e35a2a6c663538c45692d66e5335d808e323bda1c5f1c53e51f1124a2e6cba85 +size 15909 diff --git a/screenshots/86box/cpanel-cp-sound.png b/screenshots/86box/cpanel-cp-sound.png new file mode 100644 index 00000000..8b22b99b --- /dev/null +++ b/screenshots/86box/cpanel-cp-sound.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8af0d409e9b907747748030acb9e6974893bdd3a426d142d679a023bfd1ac6ce +size 17643 diff --git a/screenshots/86box/cpanel-cp-types.png b/screenshots/86box/cpanel-cp-types.png index cc510c12..1e405480 100644 --- a/screenshots/86box/cpanel-cp-types.png +++ b/screenshots/86box/cpanel-cp-types.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:362e4f37e6e99b0368da74c2bc15b5e08d9d6bb6267aeddbccdd908745767993 -size 23159 +oid sha256:53601bf8609488ed1a85b388e405854ec038f216b2cf6d4350d749be05195dd5 +size 23172 diff --git a/screenshots/86box/cpanel-cp-video.png b/screenshots/86box/cpanel-cp-video.png index 94174e34..ba8d7ec3 100644 --- a/screenshots/86box/cpanel-cp-video.png +++ b/screenshots/86box/cpanel-cp-video.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c7ca998583545535beb72a7cd1347ef66917f213df5a72783079a805cf0a142 -size 19483 +oid sha256:0811ca3be22aeba9decba8821ed65220c26b37d3155d01320fd8b46ec9116285 +size 19815 diff --git a/screenshots/86box/exit-cancel.png b/screenshots/86box/exit-cancel.png index 05fb49db..cee10809 100644 --- a/screenshots/86box/exit-cancel.png +++ b/screenshots/86box/exit-cancel.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b1857f90c8a3b3df3504042146bda7ddafec400ff43afece3f4b55e7c1223e24 -size 14330 +oid sha256:3478e1d84decc5ac10844883062e6bea6885e30ec7ebe0101ebee7d9444ae619 +size 14351 diff --git a/screenshots/86box/exit-savechg.png b/screenshots/86box/exit-savechg.png index 089b9519..74b3e466 100644 --- a/screenshots/86box/exit-savechg.png +++ b/screenshots/86box/exit-savechg.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ceb4762d64848d9756af89143d495d13acbfd682f81f85d8941eb9b8450dbcb5 -size 14324 +oid sha256:c375e0886534255d93d2ae1ecf32683d0036cfbfb628320140a7a5fc9b019695 +size 14348 diff --git a/screenshots/86box/f1-h-cpanel.png b/screenshots/86box/f1-h-cpanel.png index a2a88cdf..a75dbb95 100644 --- a/screenshots/86box/f1-h-cpanel.png +++ b/screenshots/86box/f1-h-cpanel.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c608a08ba5206a0cb5bfbb9c4845087b99fae8d9dff28323fc161fc2a48a5aab -size 22602 +oid sha256:e6fe705e41b7a4dc8b969945e455b2d79fd32d517f78b829ded6cfd44324db70 +size 22663 diff --git a/screenshots/86box/filedlg-fd-back.png b/screenshots/86box/filedlg-fd-back.png index 007a9b08..f579cfe5 100644 --- a/screenshots/86box/filedlg-fd-back.png +++ b/screenshots/86box/filedlg-fd-back.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53dcaf3108803a4510e3a2c2b422b79ab147a9f4437edc9dd96c77339d10a589 -size 15236 +oid sha256:7382ad12f6411a8448df45ea184d8306423a51e0fbd8bb2d46beeaa34b43ac29 +size 15234 diff --git a/screenshots/86box/filedlg-fd-list.png b/screenshots/86box/filedlg-fd-list.png index 51f29728..e0f6968e 100644 --- a/screenshots/86box/filedlg-fd-list.png +++ b/screenshots/86box/filedlg-fd-list.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ccaa69aacc953385111373661bd2b3bc7e75b12edec75164f6d1a1720b5bb8e -size 15337 +oid sha256:6c48f8df6224e37237a612f8440c83040123955ea3b7ce6c2c33907df55df575 +size 15346 diff --git a/screenshots/86box/filedlg-fd-nordy.png b/screenshots/86box/filedlg-fd-nordy.png index f4b50424..383cd199 100644 --- a/screenshots/86box/filedlg-fd-nordy.png +++ b/screenshots/86box/filedlg-fd-nordy.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7414f2f6a1a2c09989945d3fbe2bf5e0470a1873592ca12e05911ae81514f438 +oid sha256:b6b6cd04bf85ff797c3be50ecff2fe0a3efc61e19ea397fc00c31ec7369f1111 size 15443 diff --git a/screenshots/86box/filedlg-fd-open.png b/screenshots/86box/filedlg-fd-open.png index 156d4715..56bee79c 100644 --- a/screenshots/86box/filedlg-fd-open.png +++ b/screenshots/86box/filedlg-fd-open.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7729d94bde811e105e739f3acd5394012840d3d007bbea98c101b7e0535ee280 -size 15194 +oid sha256:fa786de9621f9d9a39373e800572fbbce9807a7c432f0330572908f602a2060a +size 15205 diff --git a/screenshots/86box/fullmode-fm-desk.png b/screenshots/86box/fullmode-fm-desk.png index aaa46454..c93e492a 100644 --- a/screenshots/86box/fullmode-fm-desk.png +++ b/screenshots/86box/fullmode-fm-desk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:185883d27ca0d039b7f26acfadb1908a9c7aeb4a2cdd5216c9cca13478f2d4b5 -size 18376 +oid sha256:588e46fa25956b91e94945fa2c0ee8f26648a4748da1317a7997b72967defd4e +size 18426 diff --git a/screenshots/86box/fullscr-fs-desk.png b/screenshots/86box/fullscr-fs-desk.png index 3e95b4f3..c2e26be0 100644 --- a/screenshots/86box/fullscr-fs-desk.png +++ b/screenshots/86box/fullscr-fs-desk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b28bb041c7adbdf8f345ab1d0df79d1f44519ac65a8df42eaf53567cb15fefd6 -size 17396 +oid sha256:adb402decf2e7dc5f420b0f5d479eaaa0cec605c2e4b58ed44ab6575165a0fd5 +size 17558 diff --git a/screenshots/86box/hang-ended.png b/screenshots/86box/hang-ended.png index f5c8b114..87a65414 100644 --- a/screenshots/86box/hang-ended.png +++ b/screenshots/86box/hang-ended.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c7a6244ed6897f52b30ebc88e44f09a4e246e02ec7ce5d041fa3dd15ba7a72b6 -size 15159 +oid sha256:967335f41a1b4b76a584e0eeffbfd607ce3063794b7c9a066ef097e3ce152d3b +size 15165 diff --git a/screenshots/86box/hang-taskmgr.png b/screenshots/86box/hang-taskmgr.png index 68b484ca..f365e9ad 100644 --- a/screenshots/86box/hang-taskmgr.png +++ b/screenshots/86box/hang-taskmgr.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3bf75064c347afb77a293b7f3105d02ce1dbe48bf6ab4e380d4ca5defc59da0 -size 16185 +oid sha256:2953c993db395b28cbdae6e951af44a949cd201cda152d9ef7e9f4af80a661ec +size 16192 diff --git a/screenshots/86box/install-in-desk.png b/screenshots/86box/install-in-desk.png index 7786d5eb..00444c34 100644 --- a/screenshots/86box/install-in-desk.png +++ b/screenshots/86box/install-in-desk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c83a79e9526c36849107b879f81a18f1bc9216fee6bca539d99e45542f51bda -size 18409 +oid sha256:78ef264c66bdc27492ebbfc01453f7face1b2fbca1f9eb0621836481aae7c499 +size 18548 diff --git a/screenshots/86box/install-su-fold.png b/screenshots/86box/install-su-fold.png index e46b670f..5b0363a6 100644 --- a/screenshots/86box/install-su-fold.png +++ b/screenshots/86box/install-su-fold.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0e4194fee2f4b9853464a41f83fe88aa5c65d9578c0d7ad86e315ebe90f2cc4 -size 21772 +oid sha256:2b6900ec5a8858fd1f12e78aca97d7c8e919c9194a2729b8cbe2727407033a7e +size 20564 diff --git a/screenshots/86box/install-su-start.png b/screenshots/86box/install-su-start.png new file mode 100644 index 00000000..18e80052 --- /dev/null +++ b/screenshots/86box/install-su-start.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:920606c7ed76ffa3dacd9be76ce1134da0d6e365efdf5caa107f0510903fc04b +size 20530 diff --git a/screenshots/86box/install-su-unin.png b/screenshots/86box/install-su-unin.png index 3438b52b..9e493f59 100644 --- a/screenshots/86box/install-su-unin.png +++ b/screenshots/86box/install-su-unin.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd1dab047ddf2342e3fafed3d7de37bcacb82b692d05c5b6a8a2e833af7ef006 -size 18398 +oid sha256:19d8eaf493f9134b8fe4fa89c916760c00e01f64d6f17ebcdcca2d2725069114 +size 18617 diff --git a/screenshots/86box/instbase-su-fold.png b/screenshots/86box/instbase-su-fold.png index 75848fe9..2f1d49cd 100644 --- a/screenshots/86box/instbase-su-fold.png +++ b/screenshots/86box/instbase-su-fold.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90ff9a56778f1e60fe530830089c48ec0c46f5dcd6ecc1755c362cc7ae839689 -size 21774 +oid sha256:c420a85d11918e5cf0fa2d9849c07cb0636af382a144db80a29b008ac67b363b +size 20553 diff --git a/screenshots/86box/instbase-su-start.png b/screenshots/86box/instbase-su-start.png new file mode 100644 index 00000000..18e80052 --- /dev/null +++ b/screenshots/86box/instbase-su-start.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:920606c7ed76ffa3dacd9be76ce1134da0d6e365efdf5caa107f0510903fc04b +size 20530 diff --git a/screenshots/86box/instbase-su-unin.png b/screenshots/86box/instbase-su-unin.png index 3438b52b..9e493f59 100644 --- a/screenshots/86box/instbase-su-unin.png +++ b/screenshots/86box/instbase-su-unin.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd1dab047ddf2342e3fafed3d7de37bcacb82b692d05c5b6a8a2e833af7ef006 -size 18398 +oid sha256:19d8eaf493f9134b8fe4fa89c916760c00e01f64d6f17ebcdcca2d2725069114 +size 18617 diff --git a/screenshots/86box/menus-m-altdn.png b/screenshots/86box/menus-m-altdn.png index b973564f..95e69470 100644 --- a/screenshots/86box/menus-m-altdn.png +++ b/screenshots/86box/menus-m-altdn.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3198835d6ce56caccb99633baaf23e337fe01f79d49a2212598ea7a69ff021a -size 16818 +oid sha256:0e1679bdc50865c2087f3687a210a7677c7175c07b9d050bcf6e04dc21d3d39e +size 16816 diff --git a/screenshots/86box/menus-m-altup.png b/screenshots/86box/menus-m-altup.png index 11f8b529..290bda7e 100644 --- a/screenshots/86box/menus-m-altup.png +++ b/screenshots/86box/menus-m-altup.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4944d691ae52c5420751cd71a8e7532273bd644ee186ab14fe899a5af8789467 -size 16436 +oid sha256:2bd9800a5cf14a50f770c69b5a785724482fdb62aac9ee7de8069aa9c16eb2ed +size 16426 diff --git a/screenshots/86box/menus-m-design.png b/screenshots/86box/menus-m-design.png index 917b6fca..65a83dc5 100644 --- a/screenshots/86box/menus-m-design.png +++ b/screenshots/86box/menus-m-design.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:697d37a38da9c8c668a14d77c9b3a80345dc54df17a5377d6af8dca2b1146442 -size 23484 +oid sha256:b520534eccc14ec24b1826a4d0ef7fe0c9e7d56f86befef504de05d7faa25b46 +size 23621 diff --git a/screenshots/86box/menus-m-drop.png b/screenshots/86box/menus-m-drop.png index b973564f..95e69470 100644 --- a/screenshots/86box/menus-m-drop.png +++ b/screenshots/86box/menus-m-drop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3198835d6ce56caccb99633baaf23e337fe01f79d49a2212598ea7a69ff021a -size 16818 +oid sha256:0e1679bdc50865c2087f3687a210a7677c7175c07b9d050bcf6e04dc21d3d39e +size 16816 diff --git a/screenshots/86box/menus-m-file.png b/screenshots/86box/menus-m-file.png index b63007f4..bedb5c3d 100644 --- a/screenshots/86box/menus-m-file.png +++ b/screenshots/86box/menus-m-file.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:831607e84c478326d61e42c57f52e47c3d62f428aa562c40f540e1432e5b6558 -size 14711 +oid sha256:5805a5feb76aa404a93656e174b878dbce68fea3cb26a178b2ef6803e864611c +size 14731 diff --git a/screenshots/86box/menus-m-run.png b/screenshots/86box/menus-m-run.png index 55a5d8d7..08415ab8 100644 --- a/screenshots/86box/menus-m-run.png +++ b/screenshots/86box/menus-m-run.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c240f7fa799ab61cfbadbfcf5f8b0ada8ca157aadf02aaff2767d13151b1b05f -size 18173 +oid sha256:1cacc50a8de3c2c2594d2ab5f99adce74b3fd124f67c49652767f843a475a5b1 +size 18218 diff --git a/screenshots/86box/net-netsmk.png b/screenshots/86box/net-netsmk.png index 59a62096..e6ed02ab 100644 --- a/screenshots/86box/net-netsmk.png +++ b/screenshots/86box/net-netsmk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eff11e9bc8ba606e1b1c9801d53ef0aaa03d47d593ffe5d3cb2e4b92e044cce3 -size 16692 +oid sha256:2d559bf9d6b37813421ef3623c2c49427cb3356b0ad887e776f352f84f3fb54b +size 16779 diff --git a/screenshots/86box/pardsgn-pardsgn.png b/screenshots/86box/pardsgn-pardsgn.png index 3bf1cbb8..ab8ba75f 100644 --- a/screenshots/86box/pardsgn-pardsgn.png +++ b/screenshots/86box/pardsgn-pardsgn.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a766622dda77ef8d68cfb3f210d74a7585494babecb44ce98c806181ce313a6 -size 14458 +oid sha256:9d93f1f23f8ffc5fedd123ef6156982bd420a237247c7bf2b2e1a40029119945 +size 14439 diff --git a/screenshots/86box/parhlp-parhlp-l.png b/screenshots/86box/parhlp-parhlp-l.png index c2175854..ba97be84 100644 --- a/screenshots/86box/parhlp-parhlp-l.png +++ b/screenshots/86box/parhlp-parhlp-l.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b2a9b275ec04d5ebba95daea4824cab032a694206b650c9f1500c8f87df4804 -size 15586 +oid sha256:1466e4d7946dc061dbb1c3485acb38ee6cbcd0930806a25b48fc355f641a2020 +size 15611 diff --git a/screenshots/86box/parhlp-parhlp.png b/screenshots/86box/parhlp-parhlp.png index dbdd07b2..72a5f511 100644 --- a/screenshots/86box/parhlp-parhlp.png +++ b/screenshots/86box/parhlp-parhlp.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da365c8447dc60454576d3330595ce1768789514112fb20586e6fe37e6343f06 -size 15584 +oid sha256:c114ad172296848ae10b9e4cd85574be604f42018ebb95024199603713ac0fda +size 15611 diff --git a/screenshots/86box/parpm-parpm.png b/screenshots/86box/parpm-parpm.png index af27c7ba..08b4193d 100644 --- a/screenshots/86box/parpm-parpm.png +++ b/screenshots/86box/parpm-parpm.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a633e667e01222db13c40b0c2a76508f0030e1929b6ff7a16603b7f5b45d0e7 -size 17147 +oid sha256:032f48556016077f70afc3308a88898fee372db590b371ba381e6408472626d5 +size 17348 diff --git a/screenshots/86box/paths-paths.png b/screenshots/86box/paths-paths.png index 74b0aa43..2b823c9e 100644 --- a/screenshots/86box/paths-paths.png +++ b/screenshots/86box/paths-paths.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91f9cc6604359edf4e355bc5b1eb56e44f0802124ee3a00b41fe1cf667d0ac58 -size 16581 +oid sha256:58503a736c280538316a7afa3e0d14485397b3122fc68a04a4ad94515662379d +size 16660 diff --git a/screenshots/86box/pkgbuild-pb-built.png b/screenshots/86box/pkgbuild-pb-built.png new file mode 100644 index 00000000..1bb324ca --- /dev/null +++ b/screenshots/86box/pkgbuild-pb-built.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2f25e42b7b9bf006adad669937416d1343832368977f6f96fac4c93cb766aaf +size 18198 diff --git a/screenshots/86box/pkgbuild-pb-files.png b/screenshots/86box/pkgbuild-pb-files.png new file mode 100644 index 00000000..777dbaa1 --- /dev/null +++ b/screenshots/86box/pkgbuild-pb-files.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f134f1db85a6f36eae06837472375beec9440f395a1bbbd132e1a7c84a05a7be +size 19151 diff --git a/screenshots/86box/pkgbuild-pb-start.png b/screenshots/86box/pkgbuild-pb-start.png new file mode 100644 index 00000000..a07c13e6 --- /dev/null +++ b/screenshots/86box/pkgbuild-pb-start.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef468a476929c5011936593e2683077f4d32c8ff7cb129a946c8ecfa3af88a23 +size 15438 diff --git a/screenshots/86box/pkgsrv-ps-avail.png b/screenshots/86box/pkgsrv-ps-avail.png new file mode 100644 index 00000000..8f11efd2 --- /dev/null +++ b/screenshots/86box/pkgsrv-ps-avail.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0855340a3827c82e84dc6fd1d1417c418cc65ec18232c46563dd8e56c77eba2e +size 16654 diff --git a/screenshots/86box/pkgsrv-ps-done.png b/screenshots/86box/pkgsrv-ps-done.png new file mode 100644 index 00000000..4c9e22d4 --- /dev/null +++ b/screenshots/86box/pkgsrv-ps-done.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72941aa120f26b645042c777b777228beefadb60efbd9ff49f3f6ae456af81a4 +size 15576 diff --git a/screenshots/86box/pkgsrv-ps-insta.png b/screenshots/86box/pkgsrv-ps-insta.png new file mode 100644 index 00000000..acc0ac35 --- /dev/null +++ b/screenshots/86box/pkgsrv-ps-insta.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:300b8f8d00b837ec7257b4843654f6bfa4b10b613869d3c6c085283e11c0dd40 +size 16237 diff --git a/screenshots/86box/pkgsrv-ps-mine.png b/screenshots/86box/pkgsrv-ps-mine.png new file mode 100644 index 00000000..63fbb375 --- /dev/null +++ b/screenshots/86box/pkgsrv-ps-mine.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:475d0c547c286d64d75f485992eee4276d0a75c8a1008025e8e0556309328c06 +size 15304 diff --git a/screenshots/86box/pkgsrv-ps-plan.png b/screenshots/86box/pkgsrv-ps-plan.png new file mode 100644 index 00000000..86ed48b0 --- /dev/null +++ b/screenshots/86box/pkgsrv-ps-plan.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b9d01a2ec12c42293c82c55b1589ba40737202f313417c9fb8c35f177105c51 +size 16689 diff --git a/screenshots/86box/pkgsrv-ps-pub.png b/screenshots/86box/pkgsrv-ps-pub.png new file mode 100644 index 00000000..9ed20f33 --- /dev/null +++ b/screenshots/86box/pkgsrv-ps-pub.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4147a3f0131225b65faa41701a4ee2ef0a622e2a7cf8e7f660c6782114cd1b80 +size 17450 diff --git a/screenshots/86box/pkgsrv-ps-updat.png b/screenshots/86box/pkgsrv-ps-updat.png new file mode 100644 index 00000000..2f913e65 --- /dev/null +++ b/screenshots/86box/pkgsrv-ps-updat.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0bd334684fedfc4a767ad5d9ac90388e9a54b55f6b16b7807c4e7c9e8a07d66 +size 16018 diff --git a/screenshots/86box/pkgsrv-ps-updon.png b/screenshots/86box/pkgsrv-ps-updon.png new file mode 100644 index 00000000..e6a5a672 --- /dev/null +++ b/screenshots/86box/pkgsrv-ps-updon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d989cdde3af2ca80b167fb808ef6beaab9e0119ce42be0bc81eebbd53accb0f +size 16829 diff --git a/screenshots/86box/pkgsrv-ps-uplan.png b/screenshots/86box/pkgsrv-ps-uplan.png new file mode 100644 index 00000000..2e902c1a --- /dev/null +++ b/screenshots/86box/pkgsrv-ps-uplan.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cf0d4373d0735f9a1926444a582642bd692a18f3a9f0a80c21ce4df6cf6e481 +size 18049 diff --git a/screenshots/86box/print-print1.png b/screenshots/86box/print-print1.png index 9bb5a700..c82bc456 100644 --- a/screenshots/86box/print-print1.png +++ b/screenshots/86box/print-print1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b33259536eda180e9a524db68ed15cc0aafd7f7b79ebbe54dab04c7db3b5fe94 -size 14848 +oid sha256:ad64cbc363a6f7ec0491d1ea4e480ed1b48d2b47c09363c722cd89d2bd5d56ad +size 14872 diff --git a/screenshots/86box/safevid-sv-desk.png b/screenshots/86box/safevid-sv-desk.png index aaa46454..c93e492a 100644 --- a/screenshots/86box/safevid-sv-desk.png +++ b/screenshots/86box/safevid-sv-desk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:185883d27ca0d039b7f26acfadb1908a9c7aeb4a2cdd5216c9cca13478f2d4b5 -size 18376 +oid sha256:588e46fa25956b91e94945fa2c0ee8f26648a4748da1317a7997b72967defd4e +size 18426 diff --git a/screenshots/86box/sdk-sdkapp.png b/screenshots/86box/sdk-sdkapp.png index f5a79ac7..323617f2 100644 --- a/screenshots/86box/sdk-sdkapp.png +++ b/screenshots/86box/sdk-sdkapp.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d186d8870aa1a1e4f1dde4a0136b81b04ee020ce3aa2d2ea48d09e3aaf5b204c -size 17600 +oid sha256:af5d5842a32438d1b385e419e7fbf828e01e6b80ae36f4b22836e754d0473fb7 +size 17648 diff --git a/screenshots/86box/sdk-sdkbas.png b/screenshots/86box/sdk-sdkbas.png index abad0582..e4fcbf90 100644 --- a/screenshots/86box/sdk-sdkbas.png +++ b/screenshots/86box/sdk-sdkbas.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:605b7b625968172ad9230527203a6c2bb6d915bcbbac01f131943c5be2d4093c -size 16799 +oid sha256:c8e1ec94de567e5720ad98859d35615b94e8407ecb7f360433024884925c7b44 +size 16835 diff --git a/screenshots/86box/sdk-sdkgreet.png b/screenshots/86box/sdk-sdkgreet.png index 996f44e0..50ba0573 100644 --- a/screenshots/86box/sdk-sdkgreet.png +++ b/screenshots/86box/sdk-sdkgreet.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:350a86a83d5be6c60adea783b58192b539e551ec5e1dee4c33df516633ad4b00 -size 16735 +oid sha256:963ffc7f70050d21d40be5d6212dc8cd74dcb0ed55dd80811a93c4f1a80d35a2 +size 16847 diff --git a/screenshots/86box/sdk-sdktbx.png b/screenshots/86box/sdk-sdktbx.png index 80120caf..6b5272d7 100644 --- a/screenshots/86box/sdk-sdktbx.png +++ b/screenshots/86box/sdk-sdktbx.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:68680747bb40b2de6f9aaf00d8589f107318e126eba5f4ded50b3003ec3a8e64 -size 23397 +oid sha256:ddbac3d1aa96225dcb75dbb31a10d3be96a14ea7a143b7929c1dfeb984dcf6a0 +size 23525 diff --git a/screenshots/86box/software-sw-done.png b/screenshots/86box/software-sw-done.png new file mode 100644 index 00000000..91354225 --- /dev/null +++ b/screenshots/86box/software-sw-done.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b67f1571788b597396f49cfd195c8d5e7189726deddefa48f6f2f814ce5683c0 +size 16954 diff --git a/screenshots/86box/software-sw-empty.png b/screenshots/86box/software-sw-empty.png new file mode 100644 index 00000000..850fa920 --- /dev/null +++ b/screenshots/86box/software-sw-empty.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed9c1ec9d6eb84663131a6384b223193924ec2f04317fde2f22c8f0b91616de7 +size 14118 diff --git a/screenshots/86box/software-sw-list.png b/screenshots/86box/software-sw-list.png new file mode 100644 index 00000000..b9f37c82 --- /dev/null +++ b/screenshots/86box/software-sw-list.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ecda14983f232c42ef31a02ca5601e45f3f69e13c32a819a69a0f7b05db6bf48 +size 15527 diff --git a/screenshots/86box/software-sw-needs.png b/screenshots/86box/software-sw-needs.png new file mode 100644 index 00000000..e407292f --- /dev/null +++ b/screenshots/86box/software-sw-needs.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03ba1b74edf52f8941dcfd7e0ebb7f2fc8db2dde71bf9336ba086b4f499460a7 +size 16342 diff --git a/screenshots/86box/software-sw-plan.png b/screenshots/86box/software-sw-plan.png new file mode 100644 index 00000000..441de340 --- /dev/null +++ b/screenshots/86box/software-sw-plan.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f09da4c5d11fbe26a2c29aa35ab702dcced4dbbe764a631548e6ea2ff465504 +size 18411 diff --git a/screenshots/86box/sql-sqlsmk.png b/screenshots/86box/sql-sqlsmk.png index 0676c825..aecc479c 100644 --- a/screenshots/86box/sql-sqlsmk.png +++ b/screenshots/86box/sql-sqlsmk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cea4451053e9ebf9b93fccf296e2a0b86d1e362e27fae329439064fff501c40c -size 16848 +oid sha256:929127846378dfc4a48ec57d28bad1491a02d51fbeb4ae19212561e8a4bbb7e3 +size 16921 diff --git a/screenshots/86box/ucdemo-ucdemo.png b/screenshots/86box/ucdemo-ucdemo.png index 14b8ed15..a007a82e 100644 --- a/screenshots/86box/ucdemo-ucdemo.png +++ b/screenshots/86box/ucdemo-ucdemo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:020814aa546db1f5f088dcf17211231362910941c89b186a13c5f2d295b8457a -size 15818 +oid sha256:788aadd3810a1f056f07ae5c8bbdfe0413439dbedd403672a40e719aad43a654 +size 15947 diff --git a/screenshots/86box/ucide-ucide-ds.png b/screenshots/86box/ucide-ucide-ds.png index 70d3877e..c6c86b18 100644 --- a/screenshots/86box/ucide-ucide-ds.png +++ b/screenshots/86box/ucide-ucide-ds.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0979cbe27301ac2299b437d14fbf6850bad838a150c206f58fc81f673c01d5e4 -size 31507 +oid sha256:c377444a4b6e7369fa35a92e916c3f86d90a3d95e961f16b3c8471464776712a +size 31429 diff --git a/screenshots/86box/ucide-ucide-ru.png b/screenshots/86box/ucide-ucide-ru.png index 7cbd6a05..b8aefcce 100644 --- a/screenshots/86box/ucide-ucide-ru.png +++ b/screenshots/86box/ucide-ucide-ru.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b4cfc025cde4eeda10c5761b232db9636466eb42650495b7c0472283d300f9be -size 25237 +oid sha256:1eef572ce0f038dcadc4f2c4acefca46008c44589362cba01d43c3df697f2034 +size 25351 diff --git a/screenshots/86box/ucide-ucide-tb.png b/screenshots/86box/ucide-ucide-tb.png index e147e726..aff9255b 100644 --- a/screenshots/86box/ucide-ucide-tb.png +++ b/screenshots/86box/ucide-ucide-tb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58103050f0a0174d722831a2a7771841662980826275be5a220c21816b115b94 -size 31620 +oid sha256:2a8dc683b59b689949ec7da17d049d42f8bbb16ba7ba7694d96546a747ca6462 +size 31580 diff --git a/screenshots/86box/upgrade-su-fold.png b/screenshots/86box/upgrade-su-fold.png deleted file mode 100644 index e46b670f..00000000 --- a/screenshots/86box/upgrade-su-fold.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d0e4194fee2f4b9853464a41f83fe88aa5c65d9578c0d7ad86e315ebe90f2cc4 -size 21772 diff --git a/screenshots/86box/upgrade-su-start.png b/screenshots/86box/upgrade-su-start.png new file mode 100644 index 00000000..0fce1288 --- /dev/null +++ b/screenshots/86box/upgrade-su-start.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89698205287ebf9d2f4ac573bef3ad9abc715a36f3fcd6e65d15c828581da536 +size 21106 diff --git a/screenshots/86box/upgrade-su-unin.png b/screenshots/86box/upgrade-su-unin.png index 3438b52b..9e493f59 100644 --- a/screenshots/86box/upgrade-su-unin.png +++ b/screenshots/86box/upgrade-su-unin.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd1dab047ddf2342e3fafed3d7de37bcacb82b692d05c5b6a8a2e833af7ef006 -size 18398 +oid sha256:19d8eaf493f9134b8fe4fa89c916760c00e01f64d6f17ebcdcca2d2725069114 +size 18617 diff --git a/screenshots/86box/upgrade-su-upgr.png b/screenshots/86box/upgrade-su-upgr.png index 8c7eca7e..b58ae831 100644 --- a/screenshots/86box/upgrade-su-upgr.png +++ b/screenshots/86box/upgrade-su-upgr.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5131d33e0640547b2f7a995c5ca2d4266c70f88c6704636018d8c82d0b013c2c -size 22391 +oid sha256:e93258d6519c57ad8f8ab4a4408f8e0e47970e288cbffdafca2d944948b308c9 +size 21573 diff --git a/screenshots/86box/video-v-800.png b/screenshots/86box/video-v-800.png index 3fda5f53..3ad661b2 100644 --- a/screenshots/86box/video-v-800.png +++ b/screenshots/86box/video-v-800.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ed7a7573e5fe8b5ec5d49ad759ea7253f6be9a79b7473a32ca8694a674d1666 -size 26164 +oid sha256:60fd02f0d1a4a0dfcf9a542c2ee25d352a6aac068ea68d06450e671df055bd54 +size 26736 diff --git a/screenshots/86box/video-v-pick.png b/screenshots/86box/video-v-pick.png index b9bb6f8a..2d0f7057 100644 --- a/screenshots/86box/video-v-pick.png +++ b/screenshots/86box/video-v-pick.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c37a4db6ca3c28af5e71682f886eab44d3fe5adbc92e008bc1520fb54d994eb3 -size 20076 +oid sha256:299f307b02e46be9d74b27c141734049f39f710e6cb11036d6065320c068cee7 +size 20592 diff --git a/screenshots/86box/winops-w-min.png b/screenshots/86box/winops-w-min.png index 8bf711f6..0764e0ab 100644 --- a/screenshots/86box/winops-w-min.png +++ b/screenshots/86box/winops-w-min.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8b0f888a347c99bc1e523143d2b7fd5fb9fbae945286739a8c4bc5f7dff33a2 -size 18596 +oid sha256:9d27acebba6cb4ec6c56b2f4cc243456fead36d108df990672b1ed977038c4b4 +size 18646 diff --git a/screenshots/86box/winops-w-move.png b/screenshots/86box/winops-w-move.png index 4a1084a3..b47f2f37 100644 --- a/screenshots/86box/winops-w-move.png +++ b/screenshots/86box/winops-w-move.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71a82787f30ceb27660c9b6e699d63d49f9ec313ed51227de108635af5ee1e05 -size 13511 +oid sha256:c521f421f2386015fde0b8549294d22b2dc41da962bf0fd1a6c54d6d8279aa1f +size 13484 diff --git a/screenshots/dosbox/addons-addon-no.png b/screenshots/dosbox/addons-addon-no.png new file mode 100644 index 00000000..07e38fee --- /dev/null +++ b/screenshots/dosbox/addons-addon-no.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87a79d1bb9a57eba5bc045892b359d85c20a332a95de259c233ec36b1de3ef2e +size 18229 diff --git a/screenshots/dosbox/apps-clock.png b/screenshots/dosbox/apps-clock.png index 5cb682bd..e4a58726 100644 --- a/screenshots/dosbox/apps-clock.png +++ b/screenshots/dosbox/apps-clock.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d0c37c3077ab96d4908c6277bce8732919423b80a6e1c98ad1dfb7a7f178e80 -size 17914 +oid sha256:fb0cf0a293985c16f50cddb6287044fd462a35f99a88bba055ea9f4e4f9b403d +size 18018 diff --git a/screenshots/dosbox/apps-cpanel.png b/screenshots/dosbox/apps-cpanel.png index 4df6fcb0..eb7203d1 100644 --- a/screenshots/dosbox/apps-cpanel.png +++ b/screenshots/dosbox/apps-cpanel.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf2fc814413dc000180596ba36f49716ec9571669a1959a2a5ce9bbfbad33f63 -size 16430 +oid sha256:6bf51593a279f8352dde3ef83d94942cb7d82d3522c91d2a3b43758674bd3535 +size 16427 diff --git a/screenshots/dosbox/apps-desktop.png b/screenshots/dosbox/apps-desktop.png index 4f9610d8..9fc09f1a 100644 --- a/screenshots/dosbox/apps-desktop.png +++ b/screenshots/dosbox/apps-desktop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d14ced0f9793e53e27952b0ea5f69475f373a8c389838877ccb71ae8170818f -size 18508 +oid sha256:0a697ed80887720ffc0e258516c4a59068e86521f79c4d9d777e5b6d703a73cf +size 18588 diff --git a/screenshots/dosbox/apps-dvxbasic.png b/screenshots/dosbox/apps-dvxbasic.png index adafd78e..16b7bf11 100644 --- a/screenshots/dosbox/apps-dvxbasic.png +++ b/screenshots/dosbox/apps-dvxbasic.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ca33ce84d1585ab033f84a3e9063897633471477cd038439db44659931e86999 -size 18318 +oid sha256:c5b9aedac500ad8ff4cbbb7b59bf49751f4bde23f6e8f8a3ae9f0f34424307a1 +size 18393 diff --git a/screenshots/dosbox/apps-iconed.png b/screenshots/dosbox/apps-iconed.png index f305a142..658cb98c 100644 --- a/screenshots/dosbox/apps-iconed.png +++ b/screenshots/dosbox/apps-iconed.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8ea0efb748f7f264cd034cdafbc33b5d35aa9ac0324be02d0daacf213aad97d -size 14855 +oid sha256:35e2e173eb0155a1799f985effc2d4100ea31be802ad7e312d5ca66751601fa0 +size 14832 diff --git a/screenshots/dosbox/apps-imgview.png b/screenshots/dosbox/apps-imgview.png index a50ad693..65619ad3 100644 --- a/screenshots/dosbox/apps-imgview.png +++ b/screenshots/dosbox/apps-imgview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c92d11a6a8ad8e9194751f84f37f126d728ee4789e075a2b55f17053c0d0fee1 -size 12908 +oid sha256:20216cfc2f5c629596ee18af24de18501b2431af8ee731ceb5be4427e219661b +size 12892 diff --git a/screenshots/dosbox/apps-notepad.png b/screenshots/dosbox/apps-notepad.png index 06a90b66..f80814d6 100644 --- a/screenshots/dosbox/apps-notepad.png +++ b/screenshots/dosbox/apps-notepad.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ed706f905ee45aadee7723995d1bb203a9c49de2d6e675bd96de0e470f0309a -size 13741 +oid sha256:1ffe8d8b71867ed7b7c676213a0b715f4de49565e1e83f0694466d2d701673cb +size 13754 diff --git a/screenshots/dosbox/apps-resedit.png b/screenshots/dosbox/apps-resedit.png index 6778216b..4becf7fd 100644 --- a/screenshots/dosbox/apps-resedit.png +++ b/screenshots/dosbox/apps-resedit.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e27933594c4af7e9623583d9113f7ace247b8ae92eee8bfd602d1920eb266b26 -size 14149 +oid sha256:3fc86dd4c3acc583cdb962e02b1c3a563a67bd5d84c0ed1a19314937ab191c51 +size 14168 diff --git a/screenshots/dosbox/banked-bk-back.png b/screenshots/dosbox/banked-bk-back.png index 60f8bb96..7976b145 100644 --- a/screenshots/dosbox/banked-bk-back.png +++ b/screenshots/dosbox/banked-bk-back.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:991866c7119668909f5e578089f241f97d42b0e97c81c48cbc6f489bcddc0781 -size 18485 +oid sha256:3425e4a35d856bef29f68f9732c7fad18669dfd3ebd1178835c0191827029cfb +size 18540 diff --git a/screenshots/dosbox/banked-bk-desk.png b/screenshots/dosbox/banked-bk-desk.png index aaa46454..c93e492a 100644 --- a/screenshots/dosbox/banked-bk-desk.png +++ b/screenshots/dosbox/banked-bk-desk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:185883d27ca0d039b7f26acfadb1908a9c7aeb4a2cdd5216c9cca13478f2d4b5 -size 18376 +oid sha256:588e46fa25956b91e94945fa2c0ee8f26648a4748da1317a7997b72967defd4e +size 18426 diff --git a/screenshots/dosbox/banked-bk-drag.png b/screenshots/dosbox/banked-bk-drag.png index c7870511..51540044 100644 --- a/screenshots/dosbox/banked-bk-drag.png +++ b/screenshots/dosbox/banked-bk-drag.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b9512db9b61617ef2f25ccf302b3b600575ef5fd5b94d793c9dec199a597666d -size 15161 +oid sha256:d815eedadc4035fea2027775eb340ae6b33b2b4f9bf90115ddacfe565096f7d9 +size 15053 diff --git a/screenshots/dosbox/banked-bk-note.png b/screenshots/dosbox/banked-bk-note.png index 06a90b66..f8d5861d 100644 --- a/screenshots/dosbox/banked-bk-note.png +++ b/screenshots/dosbox/banked-bk-note.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ed706f905ee45aadee7723995d1bb203a9c49de2d6e675bd96de0e470f0309a -size 13741 +oid sha256:cbbc99f6095370b23c0cda96486df13fb5c1e6654ff371b9a67b8fe62a116eda +size 13750 diff --git a/screenshots/dosbox/boot-boot.png b/screenshots/dosbox/boot-boot.png index aaa46454..c93e492a 100644 --- a/screenshots/dosbox/boot-boot.png +++ b/screenshots/dosbox/boot-boot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:185883d27ca0d039b7f26acfadb1908a9c7aeb4a2cdd5216c9cca13478f2d4b5 -size 18376 +oid sha256:588e46fa25956b91e94945fa2c0ee8f26648a4748da1317a7997b72967defd4e +size 18426 diff --git a/screenshots/dosbox/cpanel-cp-color.png b/screenshots/dosbox/cpanel-cp-color.png index 00d41f40..5261386c 100644 --- a/screenshots/dosbox/cpanel-cp-color.png +++ b/screenshots/dosbox/cpanel-cp-color.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a695a45d25535a2cdfa489e932d221c87bc029e8e79991478ddcd1925d37b6e -size 18280 +oid sha256:660bf211134751affcd3a05e3d5e8e8c297cda750a417873a642c2ee72861542 +size 18266 diff --git a/screenshots/dosbox/cpanel-cp-desk.png b/screenshots/dosbox/cpanel-cp-desk.png index 76360a2e..c912aaa5 100644 --- a/screenshots/dosbox/cpanel-cp-desk.png +++ b/screenshots/dosbox/cpanel-cp-desk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cfe0af167d1535d62e34ddfd9e53deaaf6441e397d815d9e4246293b7e953be4 -size 14435 +oid sha256:97022e5d29c1c26e7d47625d3fb54121173c54b9f58f884a17c451a555a3b3b3 +size 14436 diff --git a/screenshots/dosbox/cpanel-cp-joy.png b/screenshots/dosbox/cpanel-cp-joy.png index 902d99a9..a2a740d4 100644 --- a/screenshots/dosbox/cpanel-cp-joy.png +++ b/screenshots/dosbox/cpanel-cp-joy.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c5abef727eff67db56905b030e31f83dea87ad20391a4af15427cffd4996256 -size 16348 +oid sha256:eb4bdad8d10caa593d3cfab42126f537714a1d2ea70e06ca64656e26fbecb785 +size 16334 diff --git a/screenshots/dosbox/cpanel-cp-mouse.png b/screenshots/dosbox/cpanel-cp-mouse.png index 4df6fcb0..eb7203d1 100644 --- a/screenshots/dosbox/cpanel-cp-mouse.png +++ b/screenshots/dosbox/cpanel-cp-mouse.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf2fc814413dc000180596ba36f49716ec9571669a1959a2a5ce9bbfbad33f63 -size 16430 +oid sha256:6bf51593a279f8352dde3ef83d94942cb7d82d3522c91d2a3b43758674bd3535 +size 16427 diff --git a/screenshots/dosbox/cpanel-cp-net.png b/screenshots/dosbox/cpanel-cp-net.png index e93f245d..8a0078a8 100644 --- a/screenshots/dosbox/cpanel-cp-net.png +++ b/screenshots/dosbox/cpanel-cp-net.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1f37bac1331a4f7d3c58ee9bd45aed805fdd8ffa95a059487e84f5ec5fb1e63 -size 16214 +oid sha256:27f0996dda3d29cd134257c40b19a6ef215ce45162999250c704004570995426 +size 16199 diff --git a/screenshots/dosbox/cpanel-cp-print.png b/screenshots/dosbox/cpanel-cp-print.png index bd712763..6b3a71c0 100644 --- a/screenshots/dosbox/cpanel-cp-print.png +++ b/screenshots/dosbox/cpanel-cp-print.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0978c06a46d4d39b7d09acc02714e6f48689346f67d05f94363b0819616fa9ff -size 15645 +oid sha256:e35a2a6c663538c45692d66e5335d808e323bda1c5f1c53e51f1124a2e6cba85 +size 15909 diff --git a/screenshots/dosbox/cpanel-cp-sound.png b/screenshots/dosbox/cpanel-cp-sound.png new file mode 100644 index 00000000..8b22b99b --- /dev/null +++ b/screenshots/dosbox/cpanel-cp-sound.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8af0d409e9b907747748030acb9e6974893bdd3a426d142d679a023bfd1ac6ce +size 17643 diff --git a/screenshots/dosbox/cpanel-cp-types.png b/screenshots/dosbox/cpanel-cp-types.png index cc510c12..1e405480 100644 --- a/screenshots/dosbox/cpanel-cp-types.png +++ b/screenshots/dosbox/cpanel-cp-types.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:362e4f37e6e99b0368da74c2bc15b5e08d9d6bb6267aeddbccdd908745767993 -size 23159 +oid sha256:53601bf8609488ed1a85b388e405854ec038f216b2cf6d4350d749be05195dd5 +size 23172 diff --git a/screenshots/dosbox/cpanel-cp-video.png b/screenshots/dosbox/cpanel-cp-video.png index b0723009..e9b7cb6f 100644 --- a/screenshots/dosbox/cpanel-cp-video.png +++ b/screenshots/dosbox/cpanel-cp-video.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3747a43521e7552ed90512e486809dbb59cf0955b37ff21762ed28dde90ae52 -size 19765 +oid sha256:d4cc901354dfe241498eab49317b5c9c91603c4c582209dbec21973b61054c7b +size 20198 diff --git a/screenshots/dosbox/exit-cancel.png b/screenshots/dosbox/exit-cancel.png index 420c8e4d..e565888c 100644 --- a/screenshots/dosbox/exit-cancel.png +++ b/screenshots/dosbox/exit-cancel.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c20ee5dcc0270837cf6f2bbc99974afff411f6114670ec0f1b140f8f78bd447b -size 14337 +oid sha256:f7508aa18520dc0675e54492895fe3e348ff659bf52b6c0c7f2efe3e6035c38d +size 14342 diff --git a/screenshots/dosbox/exit-savechg.png b/screenshots/dosbox/exit-savechg.png index 089b9519..74b3e466 100644 --- a/screenshots/dosbox/exit-savechg.png +++ b/screenshots/dosbox/exit-savechg.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ceb4762d64848d9756af89143d495d13acbfd682f81f85d8941eb9b8450dbcb5 -size 14324 +oid sha256:c375e0886534255d93d2ae1ecf32683d0036cfbfb628320140a7a5fc9b019695 +size 14348 diff --git a/screenshots/dosbox/f1-h-cpanel.png b/screenshots/dosbox/f1-h-cpanel.png index a2a88cdf..a75dbb95 100644 --- a/screenshots/dosbox/f1-h-cpanel.png +++ b/screenshots/dosbox/f1-h-cpanel.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c608a08ba5206a0cb5bfbb9c4845087b99fae8d9dff28323fc161fc2a48a5aab -size 22602 +oid sha256:e6fe705e41b7a4dc8b969945e455b2d79fd32d517f78b829ded6cfd44324db70 +size 22663 diff --git a/screenshots/dosbox/filedlg-fd-back.png b/screenshots/dosbox/filedlg-fd-back.png index 007a9b08..f579cfe5 100644 --- a/screenshots/dosbox/filedlg-fd-back.png +++ b/screenshots/dosbox/filedlg-fd-back.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53dcaf3108803a4510e3a2c2b422b79ab147a9f4437edc9dd96c77339d10a589 -size 15236 +oid sha256:7382ad12f6411a8448df45ea184d8306423a51e0fbd8bb2d46beeaa34b43ac29 +size 15234 diff --git a/screenshots/dosbox/filedlg-fd-list.png b/screenshots/dosbox/filedlg-fd-list.png index 25052646..eee89a60 100644 --- a/screenshots/dosbox/filedlg-fd-list.png +++ b/screenshots/dosbox/filedlg-fd-list.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:baf10713a6c6149bf8ff58de0f14db3df40758e7d7855ad99877ab9718d37de1 -size 15446 +oid sha256:6e5d6348e54496727f261d2760cbc13a37de24c8a96a20741c479dcb25602c90 +size 15447 diff --git a/screenshots/dosbox/filedlg-fd-nordy.png b/screenshots/dosbox/filedlg-fd-nordy.png index f4b50424..383cd199 100644 --- a/screenshots/dosbox/filedlg-fd-nordy.png +++ b/screenshots/dosbox/filedlg-fd-nordy.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7414f2f6a1a2c09989945d3fbe2bf5e0470a1873592ca12e05911ae81514f438 +oid sha256:b6b6cd04bf85ff797c3be50ecff2fe0a3efc61e19ea397fc00c31ec7369f1111 size 15443 diff --git a/screenshots/dosbox/filedlg-fd-open.png b/screenshots/dosbox/filedlg-fd-open.png index 156d4715..56bee79c 100644 --- a/screenshots/dosbox/filedlg-fd-open.png +++ b/screenshots/dosbox/filedlg-fd-open.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7729d94bde811e105e739f3acd5394012840d3d007bbea98c101b7e0535ee280 -size 15194 +oid sha256:fa786de9621f9d9a39373e800572fbbce9807a7c432f0330572908f602a2060a +size 15205 diff --git a/screenshots/dosbox/fullmode-fm-desk.png b/screenshots/dosbox/fullmode-fm-desk.png index aaa46454..c93e492a 100644 --- a/screenshots/dosbox/fullmode-fm-desk.png +++ b/screenshots/dosbox/fullmode-fm-desk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:185883d27ca0d039b7f26acfadb1908a9c7aeb4a2cdd5216c9cca13478f2d4b5 -size 18376 +oid sha256:588e46fa25956b91e94945fa2c0ee8f26648a4748da1317a7997b72967defd4e +size 18426 diff --git a/screenshots/dosbox/fullscr-fs-desk.png b/screenshots/dosbox/fullscr-fs-desk.png index 3e95b4f3..c2e26be0 100644 --- a/screenshots/dosbox/fullscr-fs-desk.png +++ b/screenshots/dosbox/fullscr-fs-desk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b28bb041c7adbdf8f345ab1d0df79d1f44519ac65a8df42eaf53567cb15fefd6 -size 17396 +oid sha256:adb402decf2e7dc5f420b0f5d479eaaa0cec605c2e4b58ed44ab6575165a0fd5 +size 17558 diff --git a/screenshots/dosbox/hang-ended.png b/screenshots/dosbox/hang-ended.png index 3ddffdbd..903e1f9c 100644 --- a/screenshots/dosbox/hang-ended.png +++ b/screenshots/dosbox/hang-ended.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:daccd7c5c6488ab2199a948873385f016271275e85f5b0724597802fbf131a5f -size 15143 +oid sha256:20c8a70d7547ebefa20be0b9c5cb5e9a1be589abae0e0eea1aba70977c3fbc61 +size 15158 diff --git a/screenshots/dosbox/hang-taskmgr.png b/screenshots/dosbox/hang-taskmgr.png index d367bbdf..3d00f119 100644 --- a/screenshots/dosbox/hang-taskmgr.png +++ b/screenshots/dosbox/hang-taskmgr.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5a7af537870dd735566f987649201d4f8e16e3b665b8a0345bd1a9de649d372 -size 16184 +oid sha256:b4c43dbc18bb351f0053d1862ce9ff76815cb11db0520454868b684f8efce1cc +size 16206 diff --git a/screenshots/dosbox/install-in-desk.png b/screenshots/dosbox/install-in-desk.png index 7786d5eb..00444c34 100644 --- a/screenshots/dosbox/install-in-desk.png +++ b/screenshots/dosbox/install-in-desk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c83a79e9526c36849107b879f81a18f1bc9216fee6bca539d99e45542f51bda -size 18409 +oid sha256:78ef264c66bdc27492ebbfc01453f7face1b2fbca1f9eb0621836481aae7c499 +size 18548 diff --git a/screenshots/dosbox/install-su-disk.png b/screenshots/dosbox/install-su-disk.png index f475d942..9f856b23 100644 --- a/screenshots/dosbox/install-su-disk.png +++ b/screenshots/dosbox/install-su-disk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:80472d3fc9852f45ae4fa1b1ff29535b6586d28853fd842a585151cb95d9e78c -size 23422 +oid sha256:2edb8d8a27f05a7b1bbba59b9dc18747da863e4e9150ac6723136914442ddb3a +size 21310 diff --git a/screenshots/dosbox/install-su-fold.png b/screenshots/dosbox/install-su-fold.png index 10c49f77..05bb0a78 100644 --- a/screenshots/dosbox/install-su-fold.png +++ b/screenshots/dosbox/install-su-fold.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16570797a20a808d62efb179973f26e9f9e5b8c17de085690c149ed1d547a7b4 -size 21789 +oid sha256:13aba99db7b50efc2c665bb9f21436b3a5fed8b4e6059a95f4b03e8db5b082ba +size 20565 diff --git a/screenshots/dosbox/install-su-start.png b/screenshots/dosbox/install-su-start.png new file mode 100644 index 00000000..18e80052 --- /dev/null +++ b/screenshots/dosbox/install-su-start.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:920606c7ed76ffa3dacd9be76ce1134da0d6e365efdf5caa107f0510903fc04b +size 20530 diff --git a/screenshots/dosbox/install-su-unin.png b/screenshots/dosbox/install-su-unin.png index 3438b52b..d2710d7e 100644 --- a/screenshots/dosbox/install-su-unin.png +++ b/screenshots/dosbox/install-su-unin.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd1dab047ddf2342e3fafed3d7de37bcacb82b692d05c5b6a8a2e833af7ef006 -size 18398 +oid sha256:62e86203241e98ef012cf0371c8faf08780ab83008ac5e79b948bd7ebeaf8863 +size 18638 diff --git a/screenshots/dosbox/install-su-wrong.png b/screenshots/dosbox/install-su-wrong.png index 952e4585..bfdae766 100644 --- a/screenshots/dosbox/install-su-wrong.png +++ b/screenshots/dosbox/install-su-wrong.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15fc79b37a8cab0ddbe6e802857655c8445f20eeba3419e2a69725203bc9e2ba -size 22170 +oid sha256:81136864ffb01f763ed7584e05e5645a0ef766283c6729957c8482d18d881827 +size 22060 diff --git a/screenshots/dosbox/instbase-su-fold.png b/screenshots/dosbox/instbase-su-fold.png index 2356a6c1..2f1d49cd 100644 --- a/screenshots/dosbox/instbase-su-fold.png +++ b/screenshots/dosbox/instbase-su-fold.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3ffe7725b830def55f89dcdfebfc292b29ddcb653374b83a52a04dc25e4f3c1 -size 21738 +oid sha256:c420a85d11918e5cf0fa2d9849c07cb0636af382a144db80a29b008ac67b363b +size 20553 diff --git a/screenshots/dosbox/instbase-su-start.png b/screenshots/dosbox/instbase-su-start.png new file mode 100644 index 00000000..18e80052 --- /dev/null +++ b/screenshots/dosbox/instbase-su-start.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:920606c7ed76ffa3dacd9be76ce1134da0d6e365efdf5caa107f0510903fc04b +size 20530 diff --git a/screenshots/dosbox/instbase-su-unin.png b/screenshots/dosbox/instbase-su-unin.png index 3438b52b..9e493f59 100644 --- a/screenshots/dosbox/instbase-su-unin.png +++ b/screenshots/dosbox/instbase-su-unin.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd1dab047ddf2342e3fafed3d7de37bcacb82b692d05c5b6a8a2e833af7ef006 -size 18398 +oid sha256:19d8eaf493f9134b8fe4fa89c916760c00e01f64d6f17ebcdcca2d2725069114 +size 18617 diff --git a/screenshots/dosbox/menus-m-altdn.png b/screenshots/dosbox/menus-m-altdn.png index b973564f..95e69470 100644 --- a/screenshots/dosbox/menus-m-altdn.png +++ b/screenshots/dosbox/menus-m-altdn.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3198835d6ce56caccb99633baaf23e337fe01f79d49a2212598ea7a69ff021a -size 16818 +oid sha256:0e1679bdc50865c2087f3687a210a7677c7175c07b9d050bcf6e04dc21d3d39e +size 16816 diff --git a/screenshots/dosbox/menus-m-altup.png b/screenshots/dosbox/menus-m-altup.png index 11f8b529..290bda7e 100644 --- a/screenshots/dosbox/menus-m-altup.png +++ b/screenshots/dosbox/menus-m-altup.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4944d691ae52c5420751cd71a8e7532273bd644ee186ab14fe899a5af8789467 -size 16436 +oid sha256:2bd9800a5cf14a50f770c69b5a785724482fdb62aac9ee7de8069aa9c16eb2ed +size 16426 diff --git a/screenshots/dosbox/menus-m-design.png b/screenshots/dosbox/menus-m-design.png index 917b6fca..65a83dc5 100644 --- a/screenshots/dosbox/menus-m-design.png +++ b/screenshots/dosbox/menus-m-design.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:697d37a38da9c8c668a14d77c9b3a80345dc54df17a5377d6af8dca2b1146442 -size 23484 +oid sha256:b520534eccc14ec24b1826a4d0ef7fe0c9e7d56f86befef504de05d7faa25b46 +size 23621 diff --git a/screenshots/dosbox/menus-m-drop.png b/screenshots/dosbox/menus-m-drop.png index b973564f..95e69470 100644 --- a/screenshots/dosbox/menus-m-drop.png +++ b/screenshots/dosbox/menus-m-drop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3198835d6ce56caccb99633baaf23e337fe01f79d49a2212598ea7a69ff021a -size 16818 +oid sha256:0e1679bdc50865c2087f3687a210a7677c7175c07b9d050bcf6e04dc21d3d39e +size 16816 diff --git a/screenshots/dosbox/menus-m-file.png b/screenshots/dosbox/menus-m-file.png index b63007f4..bedb5c3d 100644 --- a/screenshots/dosbox/menus-m-file.png +++ b/screenshots/dosbox/menus-m-file.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:831607e84c478326d61e42c57f52e47c3d62f428aa562c40f540e1432e5b6558 -size 14711 +oid sha256:5805a5feb76aa404a93656e174b878dbce68fea3cb26a178b2ef6803e864611c +size 14731 diff --git a/screenshots/dosbox/menus-m-run.png b/screenshots/dosbox/menus-m-run.png index 55a5d8d7..08415ab8 100644 --- a/screenshots/dosbox/menus-m-run.png +++ b/screenshots/dosbox/menus-m-run.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c240f7fa799ab61cfbadbfcf5f8b0ada8ca157aadf02aaff2767d13151b1b05f -size 18173 +oid sha256:1cacc50a8de3c2c2594d2ab5f99adce74b3fd124f67c49652767f843a475a5b1 +size 18218 diff --git a/screenshots/dosbox/net-netsmk.png b/screenshots/dosbox/net-netsmk.png index 59a62096..e6ed02ab 100644 --- a/screenshots/dosbox/net-netsmk.png +++ b/screenshots/dosbox/net-netsmk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eff11e9bc8ba606e1b1c9801d53ef0aaa03d47d593ffe5d3cb2e4b92e044cce3 -size 16692 +oid sha256:2d559bf9d6b37813421ef3623c2c49427cb3356b0ad887e776f352f84f3fb54b +size 16779 diff --git a/screenshots/dosbox/pardsgn-pardsgn.png b/screenshots/dosbox/pardsgn-pardsgn.png index 3bf1cbb8..ab8ba75f 100644 --- a/screenshots/dosbox/pardsgn-pardsgn.png +++ b/screenshots/dosbox/pardsgn-pardsgn.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a766622dda77ef8d68cfb3f210d74a7585494babecb44ce98c806181ce313a6 -size 14458 +oid sha256:9d93f1f23f8ffc5fedd123ef6156982bd420a237247c7bf2b2e1a40029119945 +size 14439 diff --git a/screenshots/dosbox/parhlp-parhlp-l.png b/screenshots/dosbox/parhlp-parhlp-l.png index c2175854..ba97be84 100644 --- a/screenshots/dosbox/parhlp-parhlp-l.png +++ b/screenshots/dosbox/parhlp-parhlp-l.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b2a9b275ec04d5ebba95daea4824cab032a694206b650c9f1500c8f87df4804 -size 15586 +oid sha256:1466e4d7946dc061dbb1c3485acb38ee6cbcd0930806a25b48fc355f641a2020 +size 15611 diff --git a/screenshots/dosbox/parhlp-parhlp.png b/screenshots/dosbox/parhlp-parhlp.png index dbdd07b2..72a5f511 100644 --- a/screenshots/dosbox/parhlp-parhlp.png +++ b/screenshots/dosbox/parhlp-parhlp.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da365c8447dc60454576d3330595ce1768789514112fb20586e6fe37e6343f06 -size 15584 +oid sha256:c114ad172296848ae10b9e4cd85574be604f42018ebb95024199603713ac0fda +size 15611 diff --git a/screenshots/dosbox/parpm-parpm.png b/screenshots/dosbox/parpm-parpm.png index 2290e609..cb031cc7 100644 --- a/screenshots/dosbox/parpm-parpm.png +++ b/screenshots/dosbox/parpm-parpm.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:45deee7eaf5ba24f1f349dc86b57d4e160abae454943cb0b65bd1fef92295de1 -size 17255 +oid sha256:87c7a3bf5f3fdc650ec7d00458828d5dca01d28be6d1ae33ac0b85ccb2023616 +size 17434 diff --git a/screenshots/dosbox/paths-paths.png b/screenshots/dosbox/paths-paths.png index 74b0aa43..2b823c9e 100644 --- a/screenshots/dosbox/paths-paths.png +++ b/screenshots/dosbox/paths-paths.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91f9cc6604359edf4e355bc5b1eb56e44f0802124ee3a00b41fe1cf667d0ac58 -size 16581 +oid sha256:58503a736c280538316a7afa3e0d14485397b3122fc68a04a4ad94515662379d +size 16660 diff --git a/screenshots/dosbox/pkgbuild-pb-built.png b/screenshots/dosbox/pkgbuild-pb-built.png new file mode 100644 index 00000000..1bb324ca --- /dev/null +++ b/screenshots/dosbox/pkgbuild-pb-built.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2f25e42b7b9bf006adad669937416d1343832368977f6f96fac4c93cb766aaf +size 18198 diff --git a/screenshots/dosbox/pkgbuild-pb-files.png b/screenshots/dosbox/pkgbuild-pb-files.png new file mode 100644 index 00000000..777dbaa1 --- /dev/null +++ b/screenshots/dosbox/pkgbuild-pb-files.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f134f1db85a6f36eae06837472375beec9440f395a1bbbd132e1a7c84a05a7be +size 19151 diff --git a/screenshots/dosbox/pkgbuild-pb-start.png b/screenshots/dosbox/pkgbuild-pb-start.png new file mode 100644 index 00000000..a07c13e6 --- /dev/null +++ b/screenshots/dosbox/pkgbuild-pb-start.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef468a476929c5011936593e2683077f4d32c8ff7cb129a946c8ecfa3af88a23 +size 15438 diff --git a/screenshots/dosbox/pkgsrv-ps-avail.png b/screenshots/dosbox/pkgsrv-ps-avail.png new file mode 100644 index 00000000..8f11efd2 --- /dev/null +++ b/screenshots/dosbox/pkgsrv-ps-avail.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0855340a3827c82e84dc6fd1d1417c418cc65ec18232c46563dd8e56c77eba2e +size 16654 diff --git a/screenshots/dosbox/pkgsrv-ps-done.png b/screenshots/dosbox/pkgsrv-ps-done.png new file mode 100644 index 00000000..4c9e22d4 --- /dev/null +++ b/screenshots/dosbox/pkgsrv-ps-done.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72941aa120f26b645042c777b777228beefadb60efbd9ff49f3f6ae456af81a4 +size 15576 diff --git a/screenshots/dosbox/pkgsrv-ps-insta.png b/screenshots/dosbox/pkgsrv-ps-insta.png new file mode 100644 index 00000000..acc0ac35 --- /dev/null +++ b/screenshots/dosbox/pkgsrv-ps-insta.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:300b8f8d00b837ec7257b4843654f6bfa4b10b613869d3c6c085283e11c0dd40 +size 16237 diff --git a/screenshots/dosbox/pkgsrv-ps-mine.png b/screenshots/dosbox/pkgsrv-ps-mine.png new file mode 100644 index 00000000..63fbb375 --- /dev/null +++ b/screenshots/dosbox/pkgsrv-ps-mine.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:475d0c547c286d64d75f485992eee4276d0a75c8a1008025e8e0556309328c06 +size 15304 diff --git a/screenshots/dosbox/pkgsrv-ps-plan.png b/screenshots/dosbox/pkgsrv-ps-plan.png new file mode 100644 index 00000000..86ed48b0 --- /dev/null +++ b/screenshots/dosbox/pkgsrv-ps-plan.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b9d01a2ec12c42293c82c55b1589ba40737202f313417c9fb8c35f177105c51 +size 16689 diff --git a/screenshots/dosbox/pkgsrv-ps-pub.png b/screenshots/dosbox/pkgsrv-ps-pub.png new file mode 100644 index 00000000..9ed20f33 --- /dev/null +++ b/screenshots/dosbox/pkgsrv-ps-pub.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4147a3f0131225b65faa41701a4ee2ef0a622e2a7cf8e7f660c6782114cd1b80 +size 17450 diff --git a/screenshots/dosbox/pkgsrv-ps-updat.png b/screenshots/dosbox/pkgsrv-ps-updat.png new file mode 100644 index 00000000..2f913e65 --- /dev/null +++ b/screenshots/dosbox/pkgsrv-ps-updat.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0bd334684fedfc4a767ad5d9ac90388e9a54b55f6b16b7807c4e7c9e8a07d66 +size 16018 diff --git a/screenshots/dosbox/pkgsrv-ps-updon.png b/screenshots/dosbox/pkgsrv-ps-updon.png new file mode 100644 index 00000000..e6a5a672 --- /dev/null +++ b/screenshots/dosbox/pkgsrv-ps-updon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d989cdde3af2ca80b167fb808ef6beaab9e0119ce42be0bc81eebbd53accb0f +size 16829 diff --git a/screenshots/dosbox/pkgsrv-ps-uplan.png b/screenshots/dosbox/pkgsrv-ps-uplan.png new file mode 100644 index 00000000..2e902c1a --- /dev/null +++ b/screenshots/dosbox/pkgsrv-ps-uplan.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cf0d4373d0735f9a1926444a582642bd692a18f3a9f0a80c21ce4df6cf6e481 +size 18049 diff --git a/screenshots/dosbox/print-print1.png b/screenshots/dosbox/print-print1.png index 3c1345a3..c82bc456 100644 --- a/screenshots/dosbox/print-print1.png +++ b/screenshots/dosbox/print-print1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:89d20bb579b7b76cd591e20fb9ac81049cdb5ae32c563341eda8b7300fce3297 -size 14853 +oid sha256:ad64cbc363a6f7ec0491d1ea4e480ed1b48d2b47c09363c722cd89d2bd5d56ad +size 14872 diff --git a/screenshots/dosbox/safevid-sv-desk.png b/screenshots/dosbox/safevid-sv-desk.png index aaa46454..c93e492a 100644 --- a/screenshots/dosbox/safevid-sv-desk.png +++ b/screenshots/dosbox/safevid-sv-desk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:185883d27ca0d039b7f26acfadb1908a9c7aeb4a2cdd5216c9cca13478f2d4b5 -size 18376 +oid sha256:588e46fa25956b91e94945fa2c0ee8f26648a4748da1317a7997b72967defd4e +size 18426 diff --git a/screenshots/dosbox/sdk-sdkapp.png b/screenshots/dosbox/sdk-sdkapp.png index f5a79ac7..323617f2 100644 --- a/screenshots/dosbox/sdk-sdkapp.png +++ b/screenshots/dosbox/sdk-sdkapp.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d186d8870aa1a1e4f1dde4a0136b81b04ee020ce3aa2d2ea48d09e3aaf5b204c -size 17600 +oid sha256:af5d5842a32438d1b385e419e7fbf828e01e6b80ae36f4b22836e754d0473fb7 +size 17648 diff --git a/screenshots/dosbox/sdk-sdkbas.png b/screenshots/dosbox/sdk-sdkbas.png index abad0582..e4fcbf90 100644 --- a/screenshots/dosbox/sdk-sdkbas.png +++ b/screenshots/dosbox/sdk-sdkbas.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:605b7b625968172ad9230527203a6c2bb6d915bcbbac01f131943c5be2d4093c -size 16799 +oid sha256:c8e1ec94de567e5720ad98859d35615b94e8407ecb7f360433024884925c7b44 +size 16835 diff --git a/screenshots/dosbox/sdk-sdkgreet.png b/screenshots/dosbox/sdk-sdkgreet.png index 996f44e0..50ba0573 100644 --- a/screenshots/dosbox/sdk-sdkgreet.png +++ b/screenshots/dosbox/sdk-sdkgreet.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:350a86a83d5be6c60adea783b58192b539e551ec5e1dee4c33df516633ad4b00 -size 16735 +oid sha256:963ffc7f70050d21d40be5d6212dc8cd74dcb0ed55dd80811a93c4f1a80d35a2 +size 16847 diff --git a/screenshots/dosbox/sdk-sdktbx.png b/screenshots/dosbox/sdk-sdktbx.png index 80120caf..6b5272d7 100644 --- a/screenshots/dosbox/sdk-sdktbx.png +++ b/screenshots/dosbox/sdk-sdktbx.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:68680747bb40b2de6f9aaf00d8589f107318e126eba5f4ded50b3003ec3a8e64 -size 23397 +oid sha256:ddbac3d1aa96225dcb75dbb31a10d3be96a14ea7a143b7929c1dfeb984dcf6a0 +size 23525 diff --git a/screenshots/dosbox/software-sw-done.png b/screenshots/dosbox/software-sw-done.png new file mode 100644 index 00000000..91354225 --- /dev/null +++ b/screenshots/dosbox/software-sw-done.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b67f1571788b597396f49cfd195c8d5e7189726deddefa48f6f2f814ce5683c0 +size 16954 diff --git a/screenshots/dosbox/software-sw-empty.png b/screenshots/dosbox/software-sw-empty.png new file mode 100644 index 00000000..850fa920 --- /dev/null +++ b/screenshots/dosbox/software-sw-empty.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed9c1ec9d6eb84663131a6384b223193924ec2f04317fde2f22c8f0b91616de7 +size 14118 diff --git a/screenshots/dosbox/software-sw-list.png b/screenshots/dosbox/software-sw-list.png new file mode 100644 index 00000000..b9f37c82 --- /dev/null +++ b/screenshots/dosbox/software-sw-list.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ecda14983f232c42ef31a02ca5601e45f3f69e13c32a819a69a0f7b05db6bf48 +size 15527 diff --git a/screenshots/dosbox/software-sw-needs.png b/screenshots/dosbox/software-sw-needs.png new file mode 100644 index 00000000..e407292f --- /dev/null +++ b/screenshots/dosbox/software-sw-needs.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03ba1b74edf52f8941dcfd7e0ebb7f2fc8db2dde71bf9336ba086b4f499460a7 +size 16342 diff --git a/screenshots/dosbox/software-sw-plan.png b/screenshots/dosbox/software-sw-plan.png new file mode 100644 index 00000000..441de340 --- /dev/null +++ b/screenshots/dosbox/software-sw-plan.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f09da4c5d11fbe26a2c29aa35ab702dcced4dbbe764a631548e6ea2ff465504 +size 18411 diff --git a/screenshots/dosbox/sql-sqlsmk.png b/screenshots/dosbox/sql-sqlsmk.png index 0676c825..aecc479c 100644 --- a/screenshots/dosbox/sql-sqlsmk.png +++ b/screenshots/dosbox/sql-sqlsmk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cea4451053e9ebf9b93fccf296e2a0b86d1e362e27fae329439064fff501c40c -size 16848 +oid sha256:929127846378dfc4a48ec57d28bad1491a02d51fbeb4ae19212561e8a4bbb7e3 +size 16921 diff --git a/screenshots/dosbox/ucdemo-ucdemo.png b/screenshots/dosbox/ucdemo-ucdemo.png index 14b8ed15..a007a82e 100644 --- a/screenshots/dosbox/ucdemo-ucdemo.png +++ b/screenshots/dosbox/ucdemo-ucdemo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:020814aa546db1f5f088dcf17211231362910941c89b186a13c5f2d295b8457a -size 15818 +oid sha256:788aadd3810a1f056f07ae5c8bbdfe0413439dbedd403672a40e719aad43a654 +size 15947 diff --git a/screenshots/dosbox/ucide-ucide-ds.png b/screenshots/dosbox/ucide-ucide-ds.png index 70d3877e..c6c86b18 100644 --- a/screenshots/dosbox/ucide-ucide-ds.png +++ b/screenshots/dosbox/ucide-ucide-ds.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0979cbe27301ac2299b437d14fbf6850bad838a150c206f58fc81f673c01d5e4 -size 31507 +oid sha256:c377444a4b6e7369fa35a92e916c3f86d90a3d95e961f16b3c8471464776712a +size 31429 diff --git a/screenshots/dosbox/ucide-ucide-ru.png b/screenshots/dosbox/ucide-ucide-ru.png index 7cbd6a05..b8aefcce 100644 --- a/screenshots/dosbox/ucide-ucide-ru.png +++ b/screenshots/dosbox/ucide-ucide-ru.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b4cfc025cde4eeda10c5761b232db9636466eb42650495b7c0472283d300f9be -size 25237 +oid sha256:1eef572ce0f038dcadc4f2c4acefca46008c44589362cba01d43c3df697f2034 +size 25351 diff --git a/screenshots/dosbox/ucide-ucide-tb.png b/screenshots/dosbox/ucide-ucide-tb.png index e147e726..aff9255b 100644 --- a/screenshots/dosbox/ucide-ucide-tb.png +++ b/screenshots/dosbox/ucide-ucide-tb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58103050f0a0174d722831a2a7771841662980826275be5a220c21816b115b94 -size 31620 +oid sha256:2a8dc683b59b689949ec7da17d049d42f8bbb16ba7ba7694d96546a747ca6462 +size 31580 diff --git a/screenshots/dosbox/upgrade-su-fold.png b/screenshots/dosbox/upgrade-su-fold.png deleted file mode 100644 index 10c49f77..00000000 --- a/screenshots/dosbox/upgrade-su-fold.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:16570797a20a808d62efb179973f26e9f9e5b8c17de085690c149ed1d547a7b4 -size 21789 diff --git a/screenshots/dosbox/upgrade-su-start.png b/screenshots/dosbox/upgrade-su-start.png new file mode 100644 index 00000000..0fce1288 --- /dev/null +++ b/screenshots/dosbox/upgrade-su-start.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89698205287ebf9d2f4ac573bef3ad9abc715a36f3fcd6e65d15c828581da536 +size 21106 diff --git a/screenshots/dosbox/upgrade-su-unin.png b/screenshots/dosbox/upgrade-su-unin.png index 3438b52b..d2710d7e 100644 --- a/screenshots/dosbox/upgrade-su-unin.png +++ b/screenshots/dosbox/upgrade-su-unin.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd1dab047ddf2342e3fafed3d7de37bcacb82b692d05c5b6a8a2e833af7ef006 -size 18398 +oid sha256:62e86203241e98ef012cf0371c8faf08780ab83008ac5e79b948bd7ebeaf8863 +size 18638 diff --git a/screenshots/dosbox/upgrade-su-upgr.png b/screenshots/dosbox/upgrade-su-upgr.png index 8c7eca7e..b58ae831 100644 --- a/screenshots/dosbox/upgrade-su-upgr.png +++ b/screenshots/dosbox/upgrade-su-upgr.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5131d33e0640547b2f7a995c5ca2d4266c70f88c6704636018d8c82d0b013c2c -size 22391 +oid sha256:e93258d6519c57ad8f8ab4a4408f8e0e47970e288cbffdafca2d944948b308c9 +size 21573 diff --git a/screenshots/dosbox/video-v-800.png b/screenshots/dosbox/video-v-800.png index 66e591a6..7ecc5797 100644 --- a/screenshots/dosbox/video-v-800.png +++ b/screenshots/dosbox/video-v-800.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd23dda69ce3a87fdaca7a05d02198550890f0c8a4e9ee78a4eb4487bb5e938f -size 26521 +oid sha256:b1c866d754308a03556dbc88d9829173ba968b7493ee3e26ef0d595e66c63947 +size 26979 diff --git a/screenshots/dosbox/video-v-pick.png b/screenshots/dosbox/video-v-pick.png index 3d8a7d5b..ddfa2e65 100644 --- a/screenshots/dosbox/video-v-pick.png +++ b/screenshots/dosbox/video-v-pick.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6332d987d3381a24db47510daff060d793c892584b13287bc8e05fe902e6c000 -size 20106 +oid sha256:14b9358260721db22e68dde83e99a1ee2618b0e888e7163bdc445711c913750f +size 20572 diff --git a/screenshots/dosbox/winops-w-min.png b/screenshots/dosbox/winops-w-min.png index 8bf711f6..0764e0ab 100644 --- a/screenshots/dosbox/winops-w-min.png +++ b/screenshots/dosbox/winops-w-min.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8b0f888a347c99bc1e523143d2b7fd5fb9fbae945286739a8c4bc5f7dff33a2 -size 18596 +oid sha256:9d27acebba6cb4ec6c56b2f4cc243456fead36d108df990672b1ed977038c4b4 +size 18646 diff --git a/screenshots/dosbox/winops-w-move.png b/screenshots/dosbox/winops-w-move.png index 4a1084a3..b47f2f37 100644 --- a/screenshots/dosbox/winops-w-move.png +++ b/screenshots/dosbox/winops-w-move.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71a82787f30ceb27660c9b6e699d63d49f9ec313ed51227de108635af5ee1e05 -size 13511 +oid sha256:c521f421f2386015fde0b8549294d22b2dc41da962bf0fd1a6c54d6d8279aa1f +size 13484 diff --git a/screenshots/masks/banked-bk-note.mask b/screenshots/masks/banked-bk-note.mask new file mode 100644 index 00000000..caa42381 --- /dev/null +++ b/screenshots/masks/banked-bk-note.mask @@ -0,0 +1,2 @@ +# Blinking text caret +94 132 1 16 diff --git a/screenshots/masks/filedlg-fd-open.mask b/screenshots/masks/filedlg-fd-open.mask new file mode 100644 index 00000000..22c3edf2 --- /dev/null +++ b/screenshots/masks/filedlg-fd-open.mask @@ -0,0 +1,2 @@ +# Blinking text caret in the Path box +249 107 1 17 diff --git a/screenshots/masks/install-su-fold.mask b/screenshots/masks/install-su-fold.mask index 9097904a..dc53f889 100644 --- a/screenshots/masks/install-su-fold.mask +++ b/screenshots/masks/install-su-fold.mask @@ -1,4 +1,4 @@ # Setup's "DVX needs about N MB" figure (grows with the package) and the rest of that line -202 163 176 16 +202 301 176 16 # Blinking text caret after C:\DVX in the folder box -123 211 3 18 +123 189 3 18 diff --git a/screenshots/masks/install-su-unin.mask b/screenshots/masks/install-su-unin.mask new file mode 100644 index 00000000..aecaa1d0 --- /dev/null +++ b/screenshots/masks/install-su-unin.mask @@ -0,0 +1,2 @@ +# Blinking text caret after C:\DVX in the folder box +123 189 3 18 diff --git a/screenshots/masks/instbase-su-fold.mask b/screenshots/masks/instbase-su-fold.mask index 9097904a..dc53f889 100644 --- a/screenshots/masks/instbase-su-fold.mask +++ b/screenshots/masks/instbase-su-fold.mask @@ -1,4 +1,4 @@ # Setup's "DVX needs about N MB" figure (grows with the package) and the rest of that line -202 163 176 16 +202 301 176 16 # Blinking text caret after C:\DVX in the folder box -123 211 3 18 +123 189 3 18 diff --git a/screenshots/masks/instbase-su-unin.mask b/screenshots/masks/instbase-su-unin.mask new file mode 100644 index 00000000..aecaa1d0 --- /dev/null +++ b/screenshots/masks/instbase-su-unin.mask @@ -0,0 +1,2 @@ +# Blinking text caret after C:\DVX in the folder box +123 189 3 18 diff --git a/screenshots/masks/upgrade-su-unin.mask b/screenshots/masks/upgrade-su-unin.mask new file mode 100644 index 00000000..aecaa1d0 --- /dev/null +++ b/screenshots/masks/upgrade-su-unin.mask @@ -0,0 +1,2 @@ +# Blinking text caret after C:\DVX in the folder box +123 189 3 18 diff --git a/screenshots/masks/upgrade-su-fold.mask b/screenshots/masks/upgrade-su-upgr.mask similarity index 83% rename from screenshots/masks/upgrade-su-fold.mask rename to screenshots/masks/upgrade-su-upgr.mask index 9097904a..dc53f889 100644 --- a/screenshots/masks/upgrade-su-fold.mask +++ b/screenshots/masks/upgrade-su-upgr.mask @@ -1,4 +1,4 @@ # Setup's "DVX needs about N MB" figure (grows with the package) and the rest of that line -202 163 176 16 +202 301 176 16 # Blinking text caret after C:\DVX in the folder box -123 211 3 18 +123 189 3 18 diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 00000000..3d448c97 --- /dev/null +++ b/server/.gitignore @@ -0,0 +1,16 @@ +# dvx.conf carries the console token and the relay password, and review.conf the token and the +# path to the repository's secret key: both are per-deployment. dvx.conf.example belongs in history. +dvx.conf +review.conf +data/ +review/ +*.log + +# The tests' keys, packages, and database, made fresh on every run +test/data/ + +# Secret keys never belong in history; the repository's stays on the operator's own computer. +*.SEC + +# The top-level lib/ rule is for the build's libraries; this lib/ is the server's own source. +!lib/ diff --git a/server/README.md b/server/README.md new file mode 100644 index 00000000..94782a85 --- /dev/null +++ b/server/README.md @@ -0,0 +1,149 @@ +# DVX package server + +The catalogue of add-on packages (`.DPK` files) that DVX's Software program browses, installs, and +updates from, and where publishers upload them. It is a [calog](../../calog) application over +SQLite, built on [singemaster](../../singemaster)'s shared modules: accounts, sessions, the log, +mail, keys, the blob store, and `lib/service.lua`'s route wrapper, account routes, and operator +checks. The two services can run in one calog process. + + cp dvx.conf.example dvx.conf # and edit it + calog dvxserver.lua -- dvx.conf # in this folder + +The server finds singemaster at `../../singemaster` and calog at `../../calog` from its own folder, +wherever calog is started; `singe=DIR` and `calog=DIR` arguments say where they are when they are +elsewhere. + +## Both services in one process + + calog ../../singemaster/master.lua dvxserver.lua -- singe=../../singemaster master=master.conf dvx=dvx.conf + +`calogArgs()` is one list for every script in a run, so each service takes its own configuration +by name (`master=`, `dvx=`). Each listens on its own `bindPort` with its own database, and the +shared modules are loaded once per service. `test/run.sh` runs both this way. + +## How it is deployed + +As singemaster is: plain HTTP bound to loopback, behind a reverse proxy that answers for the public +name. One difference matters: DVX's web library speaks plain HTTP only (there is no TLS on a DOS +machine), so the proxy must answer DVX's requests over plain HTTP as well as HTTPS. That costs no +trust, because no package is trusted for the way it arrived: DVX installs a package only when the +repository's signature of it checks against the repository key DVX was installed with. + +A client that opens a connection and then stalls holds one serving context until it gives up, so +the proxy should time out slow requests (`client_header_timeout` and `client_body_timeout` in +nginx) and buffer responses, which also frees a context as soon as a slow download is handed to +the proxy. + +## Trust + +* **The repository key stays offline.** The service holds only the repository's public key + (`repoKey`). The operator signs an approved package on their own computer + (`tools/review.lua`), and the service checks that signature with the package tool before it + publishes the version. A stolen server can withhold or withdraw packages, but it cannot make one + DVX will install. +* **Every upload is checked by the package tool** (`dvxpkg check`, the same code DVX installs + with): the manifest, every file against it, and the rules for where each kind of package may put + its files. Nothing a manifest says is taken on the uploader's word. +* **Publishers may sign their own packages** with their own keys (`dvxpkg keygen`, or the Package + Builder). The catalogue shows each version's publisher key, if it has one, so a user can tell two + publishers apart. +* **An id belongs to the account that first uploaded it.** Another account's upload of the same id + is refused, as is a second upload of a version already taken. Ids of the vendor `kpunch` are + DVX's own and only the operator uploads them. + +## The API + +All JSON except the downloads. Every reply carries `X-Request-Id`, which is also in the log. + +| Method | Path | What it does | +|---|---|---| +| GET | `/v1/health` | Liveness and the schema version. | +| GET | `/v1/catalogue` | Every published package at its newest approved version: id, version, name, kind, summary, publisher, author, licence, homepage, dvx, abi, size, sha256, signer, signed, and depends (`[{id, min}]`). No session. | +| GET | `/v1/package/` | One package's approved versions, newest first. | +| GET | `/v1/download//` | The bytes, with `X-Content-Sha256`. Resumable: `Range` is answered. | +| GET | `/v1/signature//` | The repository's signature of that version (a `NAME.SIG` file). | +| POST | `/v1/updates` | `{dvx, abi, installed: [{id, version}]}`, the client's own list: answers `updates` (the newer versions) and `unknown` (ids the catalogue does not have). Nothing of it is kept. | +| POST | `/v1/upload` | The `.DPK` as the body, from a signed-in account. Taken in as a pending version for review. DVX's Software sends it (File > Publish a Package) with the account its settings give. | +| GET | `/v1/mine` | The account's own packages, every version with its review state and note. | +| GET | `/v1/admin/pending` | Operator. Versions waiting for a review, oldest first. | +| GET | `/v1/admin/download//` | Operator. Any version's bytes, to review and sign. | +| POST | `/v1/admin/version/state` | Operator. `{package, version, state: approved or rejected, signature, note}`. Approving needs the repository's signature of the version. | +| POST | `/v1/admin/package/state` | Operator. `{package, state: published or withdrawn}`. | +| GET | `/v1/admin/log` | Operator. The log, searched by any field (as singemaster's). | +| POST | `/v1/admin/key/grant`, `/v1/admin/key/revoke` | Operator. `{account, key}`; the key `admin` makes an administrator. | + +The account routes (`/v1/account/register`, `verify`, `login`, `logout`, `recover`, `reset`, and +`GET /v1/account`) are singemaster's, with mail that names DVX. + +The operator is the console token (`adminToken`) or the session of an account holding the `admin` +key. Withdrawing a package stops it being listed and downloaded; the copies installed keep working +and simply stop being offered updates. + +## Serving contexts + +A DOS machine downloads slowly. The main context listens and does the housekeeping (queued mail, +expired accounts); `serveContexts` serving contexts, each with its own database connection and +routes, accept on its listener, so one slow download holds up only the context sending it. Each +connection goes to one of them; with every one busy, new connections wait in the system's queue +until one is free. + +## Reviewing uploads + +`tools/review.lua` runs on the operator's own computer, the one place the repository's secret key +is kept. It needs singemaster's `lib/config.lua` (`singe=DIR` when singemaster is not beside this +repository) and the package tool from a DVX build or the SDK. + + calog tools/review.lua -- review.conf pending + calog tools/review.lua -- review.conf fetch acme.cards 1.0.0 + calog tools/review.lua -- review.conf approve acme.cards 1.0.0 "looks fine" + calog tools/review.lua -- review.conf reject acme.cards 1.1.0 "the help file is missing" + +`review.conf` holds `server` (the service's public address), `token` (the console token or an +administrator's session), `dvxpkg`, `secret` (the repository's secret key), and `work` (where +fetched packages and their signatures are kept). `fetch` checks the download against the digest the +service sent and shows the manifest and files as the package tool reads them. `approve` signs the +fetched file itself, so what is approved is exactly what was reviewed; if that file has changed +since, the service refuses the signature. `withdraw` and `publish` take a package off the catalogue +and put it back. + +## Publishing DVX itself + +`make` in the DVX tree writes `dist/DVXUPD.DPK`: DVX itself as the package `kpunch.dvx` (kind +`system`), every file of the release but the optional fonts, at this DVX's version and ABI. Packages +of the vendor `kpunch` are the operator's, so it is uploaded with the console token and approved +like any other: + + curl -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/octet-stream" \ + --data-binary @dist/DVXUPD.DPK https://packages.example.com/v1/upload + calog tools/review.lua -- review.conf approve kpunch.dvx 1.0.1 + +Software then lists it first on its Updates page. DVX installs it only when the repository's +signature checks against the key in its own `SYSTEM\KEYS`, and names the add-ons a new ABI would +leave unable to load before anything is installed. + +## Configuration + +`dvx.conf.example` lists every key with a comment. Beyond singemaster's (`bindHost`, `bindPort`, +`publicOrigin`, `dataDir`, `database`, `pbkdf2Iterations`, `mailFrom`, the relay settings, and +`adminToken`), the server needs `dvxpkg` (the package tool), `repoKey` (the repository's public +key), `serveContexts`, and `uploadMax` (the largest upload in bytes). + +## Tests + + test/run.sh + +Needs `bin/host/dvxpkg` from a DVX build, calog, and singemaster beside this repository. It makes +its own keys and packages with the package tool, starts the service on a scratch database, and runs +two suites: `testPackages` (uploads checked and refused for the right reasons, review with the +repository's signature, the catalogue, resumed downloads that DVX would trust, updates, withdrawing, +an account's own packages, and a slow client not holding up the others) and `testReview` (the review +tool end to end, including a changed file whose signature is refused). Then it starts singemaster +and this service in one calog process and checks that both answer. + + test/serve.sh DATA PORT FIXTURES DVXPKG + +The package server for DVX's DOS tests: the DOS test harness runs it for a script that says +`# REQUIRES: PKGSERVER`. It makes a test repository key, gives the test DVX that key, a record of +DVX itself, and a theme to update (installed with the package tool), uploads and approves the +fixture packages and the next version of DVX with `tools/review.lua`, and serves them on +`127.0.0.1:PORT`, which the emulator's guest reaches as `10.0.2.2`. diff --git a/server/dvx.conf.example b/server/dvx.conf.example new file mode 100644 index 00000000..31de1831 --- /dev/null +++ b/server/dvx.conf.example @@ -0,0 +1,37 @@ +# dvx.conf -- the DVX package server's settings (calog dvxserver.lua -- dvx.conf). +# Every key without a comment saying otherwise is required. + +# Where it listens: loopback, behind the reverse proxy that answers for the public name +bindHost = 127.0.0.1 +bindPort = 18100 +# What the world calls it (links in mail are built from this) +publicOrigin = http://packages.dvx.example + +# Its files: the database, the stored packages (dataDir/blobs), and uploads arriving (dataDir/work) +dataDir = ./data +database = ./data/dvx.db + +# Password hashing work (PBKDF2 iterations) +pbkdf2Iterations = 200000 + +# The package tool (dvxpkg from a DVX build: bin/host/dvxpkg) and the repository's PUBLIC key +# (REPO.KEY): approvals carry a signature made with its secret key on the operator's own computer, +# and are checked against this. The secret key never comes near the server. +dvxpkg = ../bin/host/dvxpkg +repoKey = ./REPO.KEY + +# Connections served at once (a DOS machine downloads slowly; more wait their turn), and the +# largest upload +serveContexts = 4 +uploadMax = 67108864 + +# The operator console's token (tools/review.lua uses it); empty: only administrators' sessions +adminToken = + +# Mail: the sender, and the relay (empty smtpHost: mail waits in the queue) +mailFrom = DVX Packages +smtpHost = +smtpPort = 587 +smtpTls = starttls +smtpUser = +smtpPassword = diff --git a/server/dvxserver.lua b/server/dvxserver.lua new file mode 100644 index 00000000..a1e8ab80 --- /dev/null +++ b/server/dvxserver.lua @@ -0,0 +1,500 @@ +-- dvxserver.lua -- the DVX package server: the catalogue of add-on packages that DVX's Software +-- program browses, installs, and updates from, and where publishers upload them. +-- +-- calog dvxserver.lua -- dvx.conf (in this folder, beside ../../singemaster) +-- calog ../../singemaster/master.lua dvxserver.lua -- singe=../../singemaster master=master.conf dvx=dvx.conf +-- +-- Plain HTTP on loopback, behind a reverse proxy (as singemaster: see its README). Built on +-- singemaster's shared modules (accounts, the log, mail, keys, the blob store, and service.lua's +-- route wrapper, account routes, and operator checks), so the two can run in one calog process. +-- +-- Nothing a package says is taken on the uploader's word: the DVX package tool (dvxpkg check, the +-- code DVX installs with) checks every upload. A version is published only when the operator +-- approves it with the repository's signature, made on the operator's own computer +-- (tools/review.lua): the service holds no secret key, only the repository's public key, with which +-- it checks the signature it is given. DOS machines download slowly, so several contexts serve, +-- and one download does not hold up everyone else. + +-- calogScript() is this file, so this folder, singemaster (../../singemaster), and calog (../../calog) +-- are found from wherever calog was started; singe=DIR and calog=DIR in calogArgs() say where the +-- other two are when they are elsewhere. calogArgs() is one list for every script in the run, so +-- with another service in the same process dvx=PATH says which configuration is whose; run alone, +-- the first plain argument is the configuration. These come before anything is loaded, so they +-- cannot come from singemaster's modules (master.lua has its own). A serving context is a task, +-- which no file started: it is given this folder as DVX_HERE (see the end of this file). +local function named(name) + for _, value in ipairs(calogArgs()) do + local found = value:match("^" .. name .. "=(.+)$") + if found then + return found + end + end + return nil +end + +local function ownArg(name) + local found = named(name) + if found then + return found + end + for _, value in ipairs(calogArgs()) do + if not value:find("=", 1, true) then + return value + end + end + return nil +end + +local HERE = DVX_HERE or (calogScript():match("^(.*)[/\\]") or ".") +local SINGE = named("singe") or (HERE .. "/../../singemaster") +local CALOG = named("calog") or (HERE .. "/../../calog") + +local function need(path) + return load(fsRead(path), "@" .. path)() +end + +local config = need(SINGE .. "/lib/config.lua") +local db = need(SINGE .. "/lib/db.lua") +local audit = need(SINGE .. "/lib/audit.lua") +local web = need(SINGE .. "/lib/web.lua") +local accounts = need(SINGE .. "/lib/accounts.lua") +local mail = need(SINGE .. "/lib/mail.lua") +local keys = need(SINGE .. "/lib/keys.lua") +local blobs = need(SINGE .. "/lib/blobs.lua") +local service = need(SINGE .. "/lib/service.lua") +local packages = need(HERE .. "/lib/packages.lua") +local httpd = need(CALOG .. "/examples/httpd.lua") +local smtp = need(CALOG .. "/examples/smtp.lua") + +-- The keys dvx.conf must have, and the ones that are numbers +local REQUIRED = { "bindHost", "bindPort", "publicOrigin", "dataDir", "database", "pbkdf2Iterations", "mailFrom", + "dvxpkg", "repoKey", "serveContexts", "uploadMax" } +local NUMERIC = { bindPort = true, pbkdf2Iterations = true, smtpPort = true, serveContexts = true, uploadMax = true } + +-- Every event the service writes (audit.lua refuses a route naming one that is not here) +local EVENTS = { + "account.register", "account.verify", "account.login", "account.logout", "account.read", + "account.recover", "account.reset", "account.sweepPending", + "catalogue.list", "package.get", "package.download", "package.signature", "updates.check", + "package.upload", "catalogue.mine", + "admin.pending", "admin.download", "admin.versionState", "version.approve", "version.reject", + "admin.packageState", "admin.log", "key.grant", "key.revoke", + "health", "service.start", "service.stop", "mail.sent", "mail.failed", +} + +-- The service's own tables, after the shared ones (accounts, sessions, the log, rate limits, mail, +-- keys). A package is vendor.name; each version's bytes are a blob, stored once by SHA-256. +local SCHEMA = { +[[ +CREATE TABLE blob ( + id INTEGER PRIMARY KEY, + size INTEGER NOT NULL, + sha256 TEXT NOT NULL UNIQUE +) +]], +[[ +CREATE TABLE package ( + id TEXT PRIMARY KEY, -- vendor.name + ownerAccountId INTEGER REFERENCES account(id), -- NULL: the operator's (DVX's own) + state TEXT NOT NULL, -- published | withdrawn + createdAt INTEGER NOT NULL +) +]], +[[CREATE INDEX packageOwner ON package(ownerAccountId)]], +[[ +CREATE TABLE packageVersion ( + packageId TEXT NOT NULL REFERENCES package(id) ON DELETE CASCADE, + version TEXT NOT NULL, -- major.minor.patch + rank INTEGER NOT NULL, -- the version as one number, for ordering + name TEXT NOT NULL, + kind TEXT NOT NULL, + summary TEXT, + publisher TEXT, + author TEXT, + licence TEXT, + homepage TEXT, + dvx TEXT, -- the oldest DVX it runs on, or NULL + abi INTEGER, -- code: the ABI it was made for + manifest TEXT NOT NULL, -- PACKAGE.INI exactly as signed + signerKey TEXT, -- the publisher's key id (PACKAGE.SIG), or NULL + repoSig TEXT, -- the repository's detached signature, set on approval + blobId INTEGER NOT NULL REFERENCES blob(id), + state TEXT NOT NULL, -- pending | approved | rejected + note TEXT, + uploadedBy INTEGER REFERENCES account(id), + uploadedAt INTEGER NOT NULL, + reviewedAt INTEGER, + PRIMARY KEY (packageId, version) +) +]], +[[CREATE INDEX packageVersionState ON packageVersion(state, uploadedAt)]], +[[ +CREATE TABLE packageDepend ( + packageId TEXT NOT NULL, + version TEXT NOT NULL, + dependId TEXT NOT NULL, + minVersion TEXT NOT NULL, + PRIMARY KEY (packageId, version, dependId), + FOREIGN KEY (packageId, version) REFERENCES packageVersion(packageId, version) ON DELETE CASCADE +) +]], +} + +local MIGRATIONS = { db.join(db.SHARED_SCHEMA, db.KEY_SCHEMA, SCHEMA) } + +local CONFIG_KEY = "dvxserver.config" -- kv: the configuration's path, for the serving contexts +local LISTENER_KEY = "dvxserver.listener" -- kv: the listener the serving contexts accept on +local STOP_MS = 500 -- the longest a context waits (an accept, a pause) to see a stop +local RETRY_MS = 20 -- the pause after an accept that failed +local CHORE_SECONDS = 30 +local PATH_ID_VERSION = "^([%w_.]+)/([%d.]+)$" -- / in a path + + +-- A serving context started by the main one (see the end of this file) is numbered, and finds the +-- configuration and the listener in kv; the main context reads its arguments and puts them there. +local worker = DVX_WORKER +local configPath = worker and kvGet(CONFIG_KEY) or (ownArg("dvx") or (HERE .. "/dvx.conf")) +local settings, configError = config.load(configPath, REQUIRED, NUMERIC) +if not settings then + calogPrint("dvxserver configuration:", configError) + calogExit(1) +end + +fsMkdir(settings.dataDir) +local handle, schemaVersion = db.open(settings.database, MIGRATIONS) +if not handle then + calogPrint("dvxserver database:", schemaVersion) + calogExit(1) +end +blobs.TABLE = "blob" + +local log = audit.new(handle, EVENTS) +local server = httpd.new() +local svc = service.new({ handle = handle, log = log, settings = settings, server = server, product = "DVX", + audit = audit, web = web, accounts = accounts, mail = mail, keys = keys, smtp = smtp }) + + +-- The operator: the console token, or an account holding the admin key. Unlike svc:operator, +-- asking does not mark the request refused. +local function isOperator(req, account) + return svc:console(req) or (account ~= nil and keys.holds(handle, account.id, keys.ADMIN)) +end + + +-- / at the end of a request's path, the version as the catalogue writes it ("2.0" is +-- 2.0.0), or nil. +local function idVersion(req) + local id, text = (req.rest or ""):match(PATH_ID_VERSION) + local version = text and packages.versionOf(text) + + if not version then + return nil + end + return id, version +end + + +-- A version's bytes, resumable, as id-version.dpk: the reply, the bytes sent, and whether resumed. +local function sendVersion(req, id, version, row) + return web.fileReply(req, { path = blobs.path(settings.dataDir, row.sha256), size = row.size, sha256 = row.sha256 }, + id .. "-" .. version .. ".dpk") +end + + +-- Run the package tool: its exit status and what it printed. +local function dvxpkg(args) + local argv = { settings.dvxpkg } + + for _, a in ipairs(args) do + argv[#argv + 1] = a + end + local r = procRun(argv) + return r.exit, r.stdout or "", r.stderr or "" +end + + +-- What the package tool says about a refused file, without its "dvxpkg: " prefix. +local function toolReason(text) + return (tostring(text):gsub("^dvxpkg:%s*", ""):gsub("%s+$", "")) +end + + +svc:route("GET", "/v1/health", "health", function(req, context) + return web.json(200, { ok = true, service = "dvx-packages", schema = schemaVersion }) +end) + +svc:accountRoutes() +svc:keyRoutes() +svc:logRoute() + + +-- Every published package at its newest approved version. Anonymous: the catalogue is public. +svc:route("GET", "/v1/catalogue", "catalogue.list", function(req, context) + local list = packages.catalogue(handle) + + context.detail = { count = #list } + return web.json(200, { packages = list, count = #list }) +end) + + +-- /v1/package/ -- one package and its approved versions, newest first. +svc:route("GET", "/v1/package/", "package.get", function(req, context) + local id = (req.rest or ""):match("^([%w_.]+)$") + local versions = id and packages.versions(handle, id) + + if not versions then + return web.fail(404, "noPackage", "no such package") + end + context.targetType = "package" + context.targetId = id + return web.json(200, { id = id, versions = versions }) +end) + + +-- /v1/download// -- the bytes of an approved version, resumable. +svc:route("GET", "/v1/download/", "package.download", function(req, context) + local id, version = idVersion(req) + local row = id and packages.servable(handle, id, version) + + if not row then + return web.fail(404, "noVersion", "no such package or version") + end + context.targetType = "package" + context.targetId = id + context.targetVersion = version + local reply, sent, resumed = sendVersion(req, id, version, row) + context.detail = { size = row.size, sent = sent, resumed = resumed } + return reply +end) + + +-- /v1/signature// -- the repository's signature of an approved version (a NAME.SIG +-- file), which DVX checks with the repository key it was installed with. +svc:route("GET", "/v1/signature/", "package.signature", function(req, context) + local id, version = idVersion(req) + local row = id and packages.servable(handle, id, version) + + if not row or not row.repoSig then + return web.fail(404, "noSignature", "no such package or version") + end + context.targetType = "package" + context.targetId = id + context.targetVersion = version + return { status = 200, headers = { ["Content-Type"] = "text/plain" }, body = row.repoSig } +end) + + +-- What is newer than the packages a DVX has: {installed: [{id, version}]}. The list is the +-- client's own and nothing of it is kept; the answer names nothing private. +svc:route("POST", "/v1/updates", "updates.check", function(req, context) + local body, bad = web.body(req) + if not body then return bad end + if type(body.installed) ~= "table" then + return web.fail(400, "badRequest", "installed must be a list of {id, version}") + end + local newer, unknown = packages.updates(handle, body.installed) + + context.detail = { installed = #body.installed, newer = #newer, unknown = #unknown, dvx = body.dvx, abi = body.abi } + return web.json(200, { updates = newer, unknown = unknown }) +end) + + +-- An upload: a .DPK as the body, from a signed-in account (or the operator), taken in as a +-- pending version for review once the package tool has checked it. +svc:route("POST", "/v1/upload", "package.upload", function(req, context) + local account = svc:signedIn(req, context) + local operator = isOperator(req, account) + + if not account and not operator then + context.result = "denied" + return web.fail(401, "noSession", "sign in first") + end + if not req.bodyFile or (req.bodySize or 0) == 0 then + return web.fail(400, "badRequest", "the body is the package (.dpk)") + end + local status, manifest, why = dvxpkg({ "check", req.bodyFile }) + if status ~= 0 then + context.detail = { reason = toolReason(why) } + return web.fail(400, "badPackage", toolReason(why)) + end + local _, info = dvxpkg({ "info", req.bodyFile }) + local signer = info:match("signed by key (%x+)") + local blobId = blobs.put(handle, settings.dataDir, req.bodyFile) + local id, result, reason = packages.intake(handle, manifest, blobId, signer, (not operator) and account.id or nil) + + if not id then + context.result = "denied" + context.detail = { reason = reason } + return web.fail(result, "refused", reason) + end + local version = result + context.targetType = "package" + context.targetId = id + context.targetVersion = version + context.description = string.format("%s uploaded %s %s for review", account and account.email or "the operator", id, version) + return web.json(202, { id = id, version = version, state = "pending" }) +end, { spool = { dir = settings.dataDir .. "/work", max = settings.uploadMax } }) + + +-- The uploader's own packages, every version with the review's word on it. +svc:route("GET", "/v1/mine", "catalogue.mine", function(req, context) + local account = svc:signedIn(req, context) + + if not account then + context.result = "denied" + return web.fail(401, "noSession", "sign in first") + end + return web.json(200, { versions = packages.mine(handle, account.id) }) +end) + + +-- Operator: the versions waiting for review. +svc:route("GET", "/v1/admin/pending", "admin.pending", function(req, context) + local ok, refusal = svc:operator(req, context) + if not ok then return refusal end + local rows = packages.pending(handle) + + context.detail = { count = #rows } + return web.json(200, { pending = rows, count = #rows }) +end) + + +-- Operator: /v1/admin/download// -- any version's bytes, to review and sign. +svc:route("GET", "/v1/admin/download/", "admin.download", function(req, context) + local ok, refusal = svc:operator(req, context) + if not ok then return refusal end + local id, version = idVersion(req) + local row = id and packages.servable(handle, id, version, true) + + if not row then + return web.fail(404, "noVersion", "no such package or version") + end + context.targetType = "package" + context.targetId = id + context.targetVersion = version + return (sendVersion(req, id, version, row)) +end) + + +-- Operator: {package, version, state: approved or rejected, note, signature}. Approving takes the +-- repository's detached signature of the package (dvxpkg sign NAME.DPK REPO.SEC -d, on the +-- operator's own computer), which must check against the repository's public key. +svc:route("POST", "/v1/admin/version/state", "admin.versionState", function(req, context) + local ok, refusal = svc:operator(req, context) + if not ok then return refusal end + local body, bad = web.body(req) + if not body then return bad end + local id = web.field(body, "package") + local version = packages.versionOf(web.field(body, "version")) + local state = web.field(body, "state") + local row = id and version and packages.servable(handle, id, version, true) + + if state ~= "approved" and state ~= "rejected" then + return web.fail(400, "badState", "state is approved or rejected") + end + if not row then + return web.fail(404, "noVersion", "no such package or version") + end + local signature = web.field(body, "signature") + if state == "approved" then + if not signature then + return web.fail(400, "noSignature", "approving needs the repository's signature of the package") + end + local sigFile = blobs.workPath(settings.dataDir) + fsWrite(sigFile, signature) + local status, _, why = dvxpkg({ "verify", blobs.path(settings.dataDir, row.sha256), "-s", sigFile, settings.repoKey }) + fsRemove(sigFile) + if status ~= 0 then + context.result = "denied" + context.detail = { reason = toolReason(why) } + return web.fail(400, "badSignature", "that is not the repository's signature of this package: " .. toolReason(why)) + end + end + packages.review(handle, id, version, state == "approved", signature, body.note) + context.action = (state == "approved") and "version.approve" or "version.reject" + context.targetType = "package" + context.targetId = id + context.targetVersion = version + context.description = string.format("%s %s %s %s%s", svc:operatorName(context), state, id, version, + body.note and (": " .. tostring(body.note)) or "") + context.detail = { state = state, note = body.note } + return web.json(200, { package = id, version = version, state = state }) +end) + + +-- Operator: {package, state: published or withdrawn}. Withdrawn packages are not listed or handed +-- out; the copies installed keep working, and stop being offered updates. +svc:route("POST", "/v1/admin/package/state", "admin.packageState", function(req, context) + local ok, refusal = svc:operator(req, context) + if not ok then return refusal end + local body, bad = web.body(req) + if not body then return bad end + local id = web.field(body, "package") + local state = web.field(body, "state") + + if state ~= "published" and state ~= "withdrawn" then + return web.fail(400, "badState", "state is published or withdrawn") + end + if not id or packages.setState(handle, id, state) == 0 then + return web.fail(404, "noPackage", "no such package") + end + context.targetType = "package" + context.targetId = id + context.description = string.format("%s set %s to %s", svc:operatorName(context), id, state) + return web.json(200, { package = id, state = state }) +end) + + +-- Serving. The main context listens and does the chores; serveContexts serving contexts, each with +-- its own database handle and routes, accept on its listener, so a slow download to a DOS machine +-- holds up only the context sending it. Each connection goes to one of them; with every one busy, +-- new connections wait in the system's queue until one is free. An accept that fails (a client gone +-- before it was taken, no descriptors to spare) is under pcall so it cannot end a serving context; +-- the pause after it is outside, where calog's stop (calogExit, Ctrl-C, SIGTERM) still reaches. +if worker then + local listener = kvGet(LISTENER_KEY) + + while true do + local ok, conn = pcall(tcpAccept, listener, STOP_MS) + if not ok then + timeSleep(RETRY_MS) + elseif conn then + server:handle(conn) + end + end +end + +local listener = tcpListen(settings.bindPort, { host = settings.bindHost }) +if not listener then + calogPrint(string.format("dvxserver: cannot listen on %s:%d", settings.bindHost, settings.bindPort)) + calogExit(1) +end +kvSet(CONFIG_KEY, configPath) +kvSet(LISTENER_KEY, listener) + +local script = HERE .. "/dvxserver.lua" +for n = 1, settings.serveContexts do + taskSpawn("lua", string.format("DVX_WORKER = %d\nDVX_HERE = %q\nassert(load(fsRead(%q), %q))()", n, HERE, script, "@" .. script)) +end + +audit.write(log, { requestId = "-", action = "service.start", result = "ok", + description = string.format("the service started, schema %d, listening on %s:%d with %d serving contexts", + schemaVersion, settings.bindHost, settings.bindPort, settings.serveContexts), + detail = { schema = schemaVersion, host = settings.bindHost, port = settings.bindPort, contexts = settings.serveContexts } }) +calogPrint(string.format("dvx packages: schema %d, listening on %s:%d for %s (%d serving contexts)", + schemaVersion, settings.bindHost, settings.bindPort, settings.publicOrigin, settings.serveContexts)) + + +-- The chores (queued mail, expired accounts), every CHORE_SECONDS. The pause is short so a stop +-- reaches this context promptly: calog raises it at the next native call, and a sleep does not wake. +local lastChore = 0 + +while true do + local now = math.floor(timeNow()) + + if now - lastChore >= CHORE_SECONDS then + lastChore = now + svc:chores() + end + timeSleep(STOP_MS) +end diff --git a/server/lib/packages.lua b/server/lib/packages.lua new file mode 100644 index 00000000..9fe28757 --- /dev/null +++ b/server/lib/packages.lua @@ -0,0 +1,253 @@ +-- packages.lua -- the DVX package catalogue: packages, their versions, what each needs, and the +-- review that publishes a version. +-- +-- What a package is comes from its manifest, PACKAGE.INI, which the service never takes on the +-- uploader's word: the DVX package tool (dvxpkg check, the same code DVX installs with) checks the +-- manifest and every file's size and SHA-256 against it, and hands back the manifest exactly as it +-- is in the package. Only then is it read here, for what the catalogue shows. The rules about +-- what a package may be live in that one place, not here. +-- +-- A package (vendor.name) belongs to the account that first uploaded it; DVX's own (kpunch.*) +-- belong to the operator. A version is pending until the operator approves it with the +-- repository's signature (made on the operator's own computer: the service holds no secret key) +-- or rejects it. Only approved versions of published packages are listed or handed out. + +local packages = {} + +packages.DVX_VENDOR = "kpunch" -- DVX's own packages: the operator's to upload +packages.VERSION_PARTS = 3 -- major.minor.patch +packages.RANK_STEP = 65536 -- each version part is 0..65535 + +-- The fields of [package] the catalogue keeps (the manifest is kept whole as well). +local FIELDS = { "name", "kind", "summary", "publisher", "author", "licence", "homepage", "dvx" } + + +-- A version's text ("1.2" or "1.2.0") as the catalogue writes it and its rank (for ordering), or +-- nil. The rules are DVX's (pkgVersionParse): one to VERSION_PARTS parts, each digits, none empty. +function packages.versionOf(text) + local parts = {} + + for part in (tostring(text or "") .. "."):gmatch("([^.]*)%.") do + local n = part:match("^%d+$") and tonumber(part) + if not n or n >= packages.RANK_STEP then + return nil + end + parts[#parts + 1] = n + end + if #parts > packages.VERSION_PARTS then + return nil + end + while #parts < packages.VERSION_PARTS do + parts[#parts + 1] = 0 + end + local rank = (parts[1] * packages.RANK_STEP + parts[2]) * packages.RANK_STEP + parts[3] + return string.format("%d.%d.%d", parts[1], parts[2], parts[3]), rank +end + + +-- A manifest's text (as dvxpkg check gives it) read into { fields..., depends = {{id, min}} }. +-- The tool has already checked it; this only reads it. +function packages.readManifest(text) + local m = { depends = {} } + local section = nil + + -- As DVX reads it (pkgIni.c): a line starting with ; or # is a comment, section names are + -- any case, and keys and values lose the blanks around them + for line in (text .. "\n"):gmatch("([^\r\n]*)\r?\n") do + local stripped = line:match("^%s*(.-)%s*$") + local name = stripped:match("^%[%s*(.-)%s*%]$") + if stripped == "" or stripped:match("^[;#]") then + -- a blank line or a comment + elseif name then + section = name:lower() + else + local key, value = stripped:match("^([^=]-)%s*=%s*(.*)$") + if key and section == "package" then + m[key:lower()] = value + elseif key and section == "depends" then + m.depends[#m.depends + 1] = { id = key, min = value } + end + end + end + return m +end + + +-- The package id, or nil. +function packages.get(handle, id) + return dbQuery(handle, "SELECT * FROM package WHERE id = ?", id)[1] +end + + +-- A version of a package as the catalogue gives it: its fields, size, digest, signer, and needs. +local function entryOf(handle, row) + local entry = { id = row.packageId, version = row.version, size = row.size, sha256 = row.sha256, + signer = row.signerKey, abi = row.abi, signed = row.repoSig ~= nil, depends = {} } + + for _, field in ipairs(FIELDS) do + entry[field] = row[field] + end + for _, d in ipairs(dbQuery(handle, "SELECT dependId, minVersion FROM packageDepend WHERE packageId = ? AND version = ? ORDER BY dependId", + row.packageId, row.version)) do + entry.depends[#entry.depends + 1] = { id = d.dependId, min = d.minVersion } + end + return entry +end + + +local VERSION_COLUMNS = "v.*, b.size, b.sha256" +local VERSION_JOIN = "packageVersion v JOIN blob b ON b.id = v.blobId JOIN package p ON p.id = v.packageId" + + +-- Every published package at its newest approved version, by id. +function packages.catalogue(handle) + local rows = dbQuery(handle, + "SELECT " .. VERSION_COLUMNS .. " FROM " .. VERSION_JOIN .. " " .. + "WHERE p.state = 'published' AND v.state = 'approved' AND v.rank = " .. + "(SELECT MAX(rank) FROM packageVersion w WHERE w.packageId = v.packageId AND w.state = 'approved') " .. + "ORDER BY v.packageId") + local list = {} + + for _, row in ipairs(rows) do + list[#list + 1] = entryOf(handle, row) + end + return list +end + + +-- A published package's approved versions, newest first (nil when it is not listed). +function packages.versions(handle, id) + local pkg = packages.get(handle, id) + + if not pkg or pkg.state ~= "published" then + return nil + end + local list = {} + for _, row in ipairs(dbQuery(handle, "SELECT " .. VERSION_COLUMNS .. " FROM " .. VERSION_JOIN .. " " .. + "WHERE v.packageId = ? AND v.state = 'approved' ORDER BY v.rank DESC", id)) do + list[#list + 1] = entryOf(handle, row) + end + return list +end + + +-- One version with its file, when it may be handed out: approved, of a published package (or, +-- with any true, whatever its state, for the operator's review). +function packages.servable(handle, id, version, any) + local row = dbQuery(handle, "SELECT " .. VERSION_COLUMNS .. ", p.state AS packageState FROM " .. VERSION_JOIN .. + " WHERE v.packageId = ? AND v.version = ?", id, version)[1] + + if not row or (not any and (row.state ~= "approved" or row.packageState ~= "published")) then + return nil + end + return row +end + + +-- What is newer than a client's own list ({{id, version}}): the newest approved version of each +-- package it names that is newer than the one it has, and the ids the catalogue does not know. +function packages.updates(handle, installed) + local newer, unknown = {}, {} + local byId = {} + + for _, entry in ipairs(packages.catalogue(handle)) do + byId[entry.id] = entry + end + for _, have in ipairs(installed or {}) do + local entry = byId[tostring(have.id or "")] + if not entry then + unknown[#unknown + 1] = tostring(have.id) + else + local _, haveRank = packages.versionOf(have.version) + local _, newRank = packages.versionOf(entry.version) + if haveRank and newRank > haveRank then + newer[#newer + 1] = entry + end + end + end + return newer, unknown +end + + +-- Take in a checked package: its manifest's text, the blob holding its bytes, the key its +-- publisher signed it with (or nil), and the account uploading it (nil: the operator). Returns the +-- version's id and version, or nil, an HTTP status, and why. +function packages.intake(handle, manifestText, blobId, signerKey, accountId) + local m = packages.readManifest(manifestText) + local id = tostring(m.id or "") + local version, rank = packages.versionOf(m.version) + local vendor = id:match("^([^.]+)%.") + + if id == "" or not version then + return nil, 400, "the manifest has no id or version" + end + if vendor == packages.DVX_VENDOR and accountId then + return nil, 403, id .. " is one of DVX's own packages: only the operator uploads those" + end + local pkg = packages.get(handle, id) + if pkg and pkg.ownerAccountId ~= accountId then + return nil, 403, id .. " belongs to another account" + end + if dbQuery(handle, "SELECT 1 AS x FROM packageVersion WHERE packageId = ? AND version = ?", id, version)[1] then + return nil, 409, id .. " " .. version .. " has been uploaded already: give the new one a higher version" + end + + local now = math.floor(timeNow()) + dbExec(handle, "BEGIN") + if not pkg then + dbExec(handle, "INSERT INTO package (id, ownerAccountId, state, createdAt) VALUES (?, ?, 'published', ?)", id, accountId, now) + end + dbExec(handle, + "INSERT INTO packageVersion (packageId, version, rank, name, kind, summary, publisher, author, licence, homepage, " .. + "dvx, abi, manifest, signerKey, blobId, state, uploadedBy, uploadedAt) " .. + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?)", + id, version, rank, m.name or id, m.kind or "", m.summary, m.publisher, m.author, m.licence, m.homepage, + m.dvx, tonumber(m.abi), manifestText, signerKey, blobId, accountId, now) + for _, d in ipairs(m.depends) do + dbExec(handle, "INSERT INTO packageDepend (packageId, version, dependId, minVersion) VALUES (?, ?, ?, ?)", + id, version, d.id, d.min) + end + dbExec(handle, "COMMIT") + return id, version +end + + +-- An account's own packages, every version with the review's word on it. +function packages.mine(handle, accountId) + local list = {} + + for _, row in ipairs(dbQuery(handle, "SELECT v.packageId, v.version, v.name, v.state, v.note, v.uploadedAt, v.reviewedAt, p.state AS packageState " .. + "FROM packageVersion v JOIN package p ON p.id = v.packageId " .. + "WHERE p.ownerAccountId = ? ORDER BY v.packageId, v.rank DESC", accountId)) do + list[#list + 1] = row + end + return list +end + + +-- The versions waiting for the operator, oldest first, with who sent each. +function packages.pending(handle) + return dbQuery(handle, + "SELECT v.packageId, v.version, v.name, v.kind, v.summary, v.signerKey, v.uploadedAt, a.email AS uploader, b.size, b.sha256 " .. + "FROM packageVersion v JOIN blob b ON b.id = v.blobId LEFT JOIN account a ON a.id = v.uploadedBy " .. + "WHERE v.state = 'pending' ORDER BY v.uploadedAt") +end + + +-- The operator's word on a version the caller has found: approved (with the repository's +-- signature, already checked) or rejected (with a note). +function packages.review(handle, id, version, approved, signature, note) + dbExec(handle, "UPDATE packageVersion SET state = ?, repoSig = ?, note = ?, reviewedAt = ? WHERE packageId = ? AND version = ?", + approved and "approved" or "rejected", approved and signature or nil, note, math.floor(timeNow()), id, version) +end + + +-- Withdraw a package (no longer listed or handed out; the copies installed keep working) or +-- publish it again. The number of packages changed. +function packages.setState(handle, id, state) + dbExec(handle, "UPDATE package SET state = ? WHERE id = ?", state, id) + return dbQuery(handle, "SELECT changes() AS n")[1].n +end + + +return packages diff --git a/server/test/account.lua b/server/test/account.lua new file mode 100644 index 00000000..228fec3b --- /dev/null +++ b/server/test/account.lua @@ -0,0 +1,54 @@ +-- account.lua -- an account on the service under test, made as a person makes one: registered, the +-- link in its mail followed (read from the mail queue in the database), then signed in. Loaded by +-- the suites, and run by serve.sh for the DOS tests' account: +-- +-- calog test/account.lua -- + +local account = {} + +local JSON = { ["Content-Type"] = "application/json" } + + +local function post(base, path, body) + local r = httpRequest({ method = "POST", url = base .. path, headers = JSON, body = jsonStringify(body) }) + local ok, value = pcall(jsonParse, r.body or "") + return r.status, (ok and type(value) == "table") and value or {} +end + + +-- Make the account: its session's token and the text of the mail it was sent (nil and why when +-- something refused). The rate limits are cleared first, since a test makes many. +function account.make(base, database, email, password) + local h = dbOpen("sqlite", database) + dbExec(h, "DELETE FROM rateLimit") + dbClose(h) + post(base, "/v1/account/register", { email = email, password = password }) + + h = dbOpen("sqlite", database) + local rows = dbQuery(h, "SELECT body FROM mailQueue WHERE recipient = ? ORDER BY id DESC LIMIT 1", email) + dbClose(h) + if not rows[1] then + return nil, "no mail was queued for " .. email + end + local mail = rows[1].body + post(base, "/v1/account/verify", { token = mail:match("verify%?token=(%x+)") }) + local status, session = post(base, "/v1/account/login", { email = email, password = password }) + if status ~= 200 or not session.token then + return nil, "signing in answered " .. tostring(status) + end + return session.token, mail +end + + +-- Run by itself: make the account the arguments name +local args = calogArgs() +if not ACCOUNT_LOADED and #args == 4 and args[1]:match("^http://") then + local token, why = account.make(args[1], args[2], args[3], args[4]) + if not token then + calogPrint("account.lua: " .. tostring(why)) + calogExit(1) + end + calogExit(0) +end + +return account diff --git a/server/test/run.sh b/server/test/run.sh new file mode 100755 index 00000000..3681b25c --- /dev/null +++ b/server/test/run.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# run.sh -- the DVX package server's tests, as singemaster's are run: fixtures made, the server +# started on a port of its own with a configuration of its own, the suites run against it, the +# server stopped. Then both services in one calog process, each answering on its own port. +# +# server/test/run.sh (from anywhere; needs bin/host/dvxpkg from a DVX build, and calog) +# +# Exit status 0 when every suite passed. + +set -u +cd "$(dirname "$0")/.." +CALOG=${CALOG:-$HOME/claude/calog/bin/calog} +SINGE=${SINGE_HOME:-$(cd ../../singemaster && pwd)} +DVXPKG=$(cd .. && pwd)/bin/host/dvxpkg +PORT=18101 +MASTER_PORT=18102 +ADMIN=testoperatortoken0123456789abcdef +DATA=$(pwd)/test/data + +rm -rf "$DATA" && mkdir -p "$DATA/stage" + +# The repository's key pair (its secret stays here, as on the operator's computer) and a publisher's +"$DVXPKG" keygen "DVX Test Repository" "$DATA/REPO" > /dev/null +"$DVXPKG" keygen "Acme Test" "$DATA/ACME" > /dev/null + +# package